坦克大战项目总结

1,创建坦克类

在c++中继承pawn命名为tank,创建c++类TankPlayerController继承PlayerController

TankPlayerController中#include "Tank_Pawn"会报错,解决办法


先编译一下,然后先选择Development Editor再选择Development报错就修复了


注意的是,命名一个类,类名为TankrController但是在项目中前面要加一个A,这个容易忽略


由于在controller中通过getPawn函数就能获取到控制的角色,TankPlayerController中getPawn拿到的是BP_tank坦克蓝图(因为GameMode的默认DefaultPawn是拖动到游戏中的蓝图坦克),它继承的是pawn,没法转成ATank因此要把BP_Tank-->类设置--->父类改成Tank


创建TankAIController继承AIController,敌人坦克不仅要拿到自身的pawn还要拿到主角tank

加入头文件

函数:


找到BP_tank(这是拖动游戏中的坦克蓝图,它继承c++写好的tank)将属性AIController改成TankAIController

 TankPlayerController是c++写的,要把UI界面的坦克瞄准点显示要放到蓝图中的controller,因此,右键创建子类的蓝图类BP_TankPlayerController。在beginPlay中add 当前widget到当前窗口

当前坦克没有物理特性,添加物理特性,在坦克蓝图类中属性Physics--->Simulate Physics打钩,MassInKg输多少公斤


2,找到坦克瞄准点对应的空间坐标

坦克每时每刻都要判断当前瞄准点对准的物体是否是坦克,因此在TankPlayerController中的Tick中

在下面函数中传入一个三维坐标HitLocation找到瞄准点撞到的物体坐标


要找到撞击到物体的点,首先找到起点和射线方向,起点就是相机的点,方向这样求,通过GetViewportSize传入两个int值得到当前分辨率下横向和纵向的像素,由于屏幕中的瞄准点在屏幕中横向二分之一(CrosshariXLocation)纵向三分之一(CrosshairYLocation)。因此,横向像素乘以CrosshariXLocation得到瞄准点的屏幕坐标

通过DeprojectScreenPositionToWorld函数传入瞄准点的屏幕坐标,得到屏幕坐标转到世界空间中的点和方向,这个方向就是我们要求的射线方向


上面的函数通过将屏幕坐标转化为方向,DeprojectScreenPositionToWorld函数主要判断这个方向上屏幕这个点上有没有撞击点,返回的bool如果是true,才进行下面的射线检测

然后根据该方向(WorldDirection),求出起点,通过PlayerCameraManager拿到相机位置,该位置+方向*10000得到终点,通过GetWorld的射线函数输入起始点和终点得到该射线碰到任意物体的碰撞点


3,添加炮塔和炮管

由于射击时旋转炮塔,因此要用c++写,创建TankTurrent继承StaticMeshComponent,第一行的意思是让该类可以在蓝图类中可以添加,倒数第二行是在定义了炮筒移动速度为20后让该属性可以在蓝图的属性中可以自己调整


这样,在坦克的蓝图类就可以添加该c++类到蓝图中,而且右侧属性也有了写好的移动速度

                       

这里的射击不同于一般的技能射击,它是先让Tank传射击点给火控组件Aiming Component,然后旋转炮塔旋转到目标,再移动炮管的上下位置,调整好角度再发射炮弹,因此还得用c++写炮管TankBarrel


c++创建TankAimingComponent继承Actor Component,include炮管和炮塔,起点是炮管的位置,我们在炮管蓝图中右下角Socket Manager中添加一个Socket(节点),


TankAimingComponent写个函数拿到炮管和炮塔,函数声明这样写,因为要在坦克蓝图中调用该c++类的函数把炮管和炮塔传进去,第一行意思是蓝图中可调用该函数

                    

                                                                                    

函数里这样写:

                  

在蓝图中beginPlay就拿到炮筒和炮塔  

         

TankAimingComponent中写个函数AimAt,在里面拿到Barrel->GetSocketLocation的起始位置,通过SuggestProjectileVelocity函数传入起始和终点坐标得到角度,得到最后的FireVector,通过.GetSafeNormal单位化


然后知道了向量FireVector就调用炮塔的旋转函数,

得到炮塔前向向量的Rotation和目标Rotation,然后它们的Yaw相减,得到yaw插值,

要让炮塔转动时平滑旋转而不是一下子转过去,该函数因为在Controller中的tick调用,tick是每帧都执行,因此让它每秒转20度(MaxDegreesPerSecond),那么每帧就转多少度呢?

答:每帧转 20*每帧的时间,得到的就是每帧转的度数,那么每帧时间用DeltaTimeSeconds获得,也即从上一帧到当前帧的时间

得到的RotationChange是每帧旋转的角度,然后加上当前的Rotation,并通过SetRelativeRotation来设置当前帧的旋转Rotation,这样就实现了旋转的平滑过渡



bug:上图倒数第二句话新的Rotation=变化的Rotation+当前的Rotation,但是当前的Rotation是通过GetForwardVector获得,如果坦克移动的时候,GetForwardVector向前的方向也在变化,因此,要把CurrentRotation改成RelativeRotation

float NewRotationYaw=RotationChange+RelativeRotation.Yaw

同理炮管的上下移动也一样

效果:


bug:炮塔在转到180度后再向右转时突然向左绕了一大圈,也即转的是补角,这是由于180度转过后变成了-179度


解决办法:加上判断,如果changeYaw为负数,加上360度即可


4,实现开火功能

创建炮弹,创建Projectile的C++文件继承Acor,因为要设置子弹的样子,右键该类,创建基于该c++文件的蓝图类BP_Projectile,在Projectile中要生成子弹因此

在属性中指定生成的子弹是哪个蓝图。


在c++类TankAimingComponent中写一个Fire函数,该函数可在蓝图中调用,鼠标左键触发Fire函数



在坦克点击鼠标发射炮弹时,炮弹生成但是不会向前走,以前项目中是在炮弹蓝图中添加一个ProjectileMovement,设置初始速度,但是在这个项目中要在c++类中生成ProjectileMovement,在Projectile中include


声明


在构造函数中,生成该组件


然后写发射子弹的函数

然后添加装弹间隔,设置一个private的double值LastFireTime,发射一次就通过FPlatformTime::Seconds记录当前时间,判断时间间隔是否超过开火时间


5,用枚举值设置当前坦克状态并根据不同状态让准星显示不同颜色

设置当前坦克的状态,在火控类中最前面声明一个枚举


在火控组件中的每一帧都进行判断并设置当前状态,如果当前炮塔旋转角度大于3度设置为瞄准状态,如果填弹时间不够,就为填弹状态


要让准星显示不同颜色,找到准星的UI,找到Color属性,点击bind打开绑定函数,添加变量,类型为Aiming Component的蓝图,给不同的枚举值赋予不同颜色,右键输入toColor就可以设置颜色,连线


虽然Get了变量的类型为Aiming Component但是没有设置(Set)它的引用到底是什么。我们需要在Controller的beginPlay中把它的引用设置给UI,因为Aiming Component这个类可以通过坦克get Component by Class拿到,而拿到Tank需要在TankPlayerController中调用GetControllerTank方法,为了让GetControlledTank函数在蓝图中被调用


这样,坦克在不同状态下准星的颜色就会变化

6,设置坦克移动

创建c++类tankTrack继承staticMeshComponent,命名为TankTrack,在该类中定义加速度(TankMaxDrivingForce)和施加力的百分比(SetThrottle)


要让履带行走,不仅要计算当前给履带施加力的大小,同时也要计算力的施加位置


施加力的大小就是前向向量*力的大小*力的百分比,施加力的位置就是当前组件的Location,那么给谁施加力呢?给履带还是给坦克?应该是给父物体施加这个力,这个力作用在履带上,那么要找到父物体,GetOwner得到该Actor,通过GetRootComponent得到TankBody


要调用GetOwner()—>GetRootComponent()的AddForceAtLocation方法发现没有这个函数这是因为该函数返回的是一个SceneComponent,该组件只有位置信息没有collision属性。


我们通过层级关系发现SceneComponent下的子类PrimitiveComponent组件具有Collision属性,因此强转成子类,然后调用AddForceAtLocation方法即可


设置q和e键控制左履带和右履带的行走,调用c++类的Set Throttle函数


2,让坦克在空中的时候按住移动键还能移动,在蓝图中的做法是,找到履带,属性中绑定Hit事件


但是要用c++来做,步骤复写BeginPlay函数,在BeginPlay中绑定Hit事件


在OnHit中产生撞击才会调用施加力的效果


3,进一步完善移动功能,坦克把移动交给移动组件,该组件处理后再交给履带移动,创建类TankMovementComponent继承NavMovementComponent

首先在该类的初始化函数中拿到左右履带的引用,


在蓝图中:

然后创建向前走和向右走的函数,调用两个履带移动


在蓝图中:


bug1:解决坦克松开移动按键后要溜很久才停下:

在TankBody属性中修改Linear  Damping和Angular Damping


bug2:在坦克开始移动时,头部突然翘起,


解决办法:

因为施加力作用在履带上,把力的位置放在履带前面,在履带前面Create Socket添加一个位置,放在履带前面


代码中即可

bu:3:解决坦克移动时候出现漂移效果:

产生原因:在移动时坦克向右前方移动会产生向右的力,通过求当前的速度方向在RightVector方向上的点积得到该速度在右边的分量速度大小SideSpeed,我们应当在右侧分量上给它一个相等的向左的力让其抵消,向左的力=质量*加速度,加速度SideSpeedAcceleration=速度/时间*方向,最后由于给两个履带施加力,所以力除以2







猜你喜欢

转载自blog.csdn.net/zhangxiaofan666/article/details/79508255