欢迎光临散文网 会员登陆 & 注册

TomLooman_ActionRoguelike_AssignmentTwo

2023-08-10 08:17 作者:别叫我小红  | 我要投稿

该专栏用于保存对TomLooman的ActionRoguelike项目的学习笔记,学习过程中的思考与记录不一定准确。


教程参考:https://github.com/tomlooman/ActionRoguelike

基于UE5.0的项目实现:https://github.com/CarolBaggins2023/TomLooman_ActionRoguelike_Tutorial

2023_08_03

Assignment Two:校正子弹方向,黑洞子弹,奥术跃迁子弹

 

在目前的实现中,Projectile在Spawn时的Rotation和Controller的Rotation相同。

因为在Character的构造函数中,我们用Controller的Rotation控制SpringArm组件,又因为Camera附着到SpringArm上,所以玩家看到的视野受控制器(鼠标等)控制。(因此下面用Camera的朝向就是Controller的朝向)

在Camera的位置改变前,从Camera进行LineTrace和从Character进行LineTrace,两条Line在俯视角下是重合的,因此Projectile的落点与屏幕中心点只会有z轴上的偏差(不考虑Projectile从Character的手部Spawn,而是从Character的中心Spawn)。

在Camera的位置改变后,从Camera进行LineTrace和从Character进行LineTrace,两条Line在俯视角下存在偏移,因此如果玩家瞄准面前竖直平面的中心进行Attack,则Projectile最终的落点会在左半平面。

 

为了解决上述问题,我们需要寻找一个新的Rotator来组成子弹Spawn时的Transform。

按照作业提示给出的逻辑,这个Rotator应该使子弹转向落点。这里的落点指的就是从Camera射出的射线(实现时用长线段)与游戏场景中物体的交点,如果没有交点,则落点就是长线段的端点。于是,已知起点(角色手部)和终点(上述落点),我们能够表示一个Rotator。

从世界坐标原点到子弹Spawn的起点和终点,可以做出两个方向向量分别记为Start和End,上述要求相当于求一个Rotator让Start转向End,那么这个Rotator对应的方向向量就是End-Start。

Vector相减还是Vector,不是Rotator,但我们可以先把Vector转换为RotationMatrix(旋转矩阵),再从RotationMatrix转成Rotator。

实现如下,

先求Start,和原本的求法一致。需要注意的是,向量和点可以认为是等同的。



然后求End,通过以上分析,End可以自然地通过LineTrace求得。

LineTrace的起点是Camera的位置,注意不是Character的眼部。LineTrace的终点是从LineTrace起点出发,沿着控制器Rotation的长线段(这里涉及了Rotator和Vector的转换)。注意,由于Camera绑定在SpringArm上,而SpringArm使用控制器Rotation控制转向,所以这里的ControlRotation相当于是Camera的Rotation。

然后是设置LineTrace中的一些参数,包括要检测什么类型的对象,碰撞形状是什么(射线横截面形状),以及要IgnoreCharacter自己。

然后通过SweepSingleByObjectType,我们可以得到Hit(包含碰撞到的对象的信息,因为这里是Single而不是Multi,所以只有一个对象,Hit也不是数组)。根据是否有碰撞到物体,决定End是确实存在的碰撞点,还是LineTrace的终点。

根据上述方法得到的子弹Spawn的位置Start和最终的位置End,我们计算出旋转对应的方向方向,再由方向向量转为旋转矩阵,由旋转矩阵转为Rotator。最终得到正确的子弹Spawn时的Transform。

 

 

 

UPROPERTY的Specifier中的Edit/VisibleDefaultsOnly和Edit/VisibleInstanceOnly的区别,关键在于区别Defaults和Instance。可以把Defaults理解为类,Instance理解为类的实例。因此,DefaultsOnly是只能对整个类修改,不能只改某个实例;InstanceOnly是只能改某个实例,不能改整个类。

当我们进入蓝图界面时,相当于针对整个类修改或浏览,此时只能看到DefaultsOnly的成员变量。

当我们在关卡编辑器中选中某个实例时,相当于针对某个实例修改或浏览,此时只能看到InstanceOnly的成员变量。

 

 

我们对子弹类的进行了重构。

首先是创建了Projectile的基类class ACTIONROGUELIKE_API ASProjectileBase : public AActor。

在基类中我们声明了子弹通用的成员变量,碰撞组件、粒子效果组件、移动组件和爆炸的粒子效果,并在基类构造函数中进行了默认的初始化,默认初始化类似于原本的MagicProjectile,各派生类可后续进行覆盖和拓展。

我们还声明了子弹类通用的成员函数,主要包括Hit反应和爆炸,它们的实现类似于爆炸桶。

目前我们将ignore instigator的逻辑写在BeginPlay中,

 

然后,在有了ProjectileBase这样的基类后,我们对原本的MagicProjectile进行修改。

我们删去了成员变量的重复声明和在构造函数中的初始化。

需要注意的是,当涉及继承关系时,对于覆盖的虚函数,要考虑是否执行父类的函数实现。比如对于继承ProjectileBase的MagicProjectile,在BeginPlay中应该执行父类的BeginPlay,否则无法进行ignore instigator(在派生类的成员函数中再写一遍当然是可以的)。

 

我们创建了BlackholeProjectile,能吸附弹道上的物体,一定时间后自我销毁。它同样继承ProjectileBase,但为了实现吸附物体的效果,增加了径向力组件成员变量

径向力组件中有Impulse(冲量)和Force(力)两种可以影响范围内物体的方式。其中,Impulse直接给物体叠加新的速度,物体速度会马上变化,而Force给物体施加力,物体速度的变化存在一个过程。

因为我们不希望黑洞子弹会因为碰撞而停止移动,所以我们将碰撞的所有通道都设为overlap。而且我们也不想控制的Character受径向力的影响,所以将Pawn从ObjectTypeToEffect中去除。这些操作在构造函数中完成。(因为派生类构造函数先调用基类构造函数,所以构造函数中不存在用Super::来显式地调用构造函数)

自我销毁的实现和子弹延时射击配合动画的实现类似。

最终效果如下,黑洞会在5秒后消失。

 

我们还实现了传送子弹,它的逻辑是,Character发射子弹,子弹在DetonateDelay(引爆延时)后会自我引爆,或者发生Hit被引爆,引爆后再经过TeleportDelay后Character会被传送到子弹引爆的位置。

所以,传送子弹有两个额外的成员变量表示两个延时。

因为DetonateDelay在子弹射出时就开始计时,所以在BeginPlay中就布置计时器。

注意,这里使用的函数指针是Explode而不是Explode_Implementation,原因之前有讨论(因为_Implementation只是C++中的实现,不包括蓝图实现)。

在爆炸的实现中,我们需要表现爆炸的粒子效果、停止子弹飞行的粒子效果、停止子弹运动、设置Character的传送计时器。

需要注意的是,我们在一开始清空了引爆的计时器。这是因为能触发Explode的不只是Beginplay中的计时器,还有ProjectileBase中的OnActorHit。所以当子弹发生碰撞时,也会发生Explode,从而触发传送效果。如果不清空DetonateDelay计时器,那么当子弹发生碰撞时,会触发两次Explode,第一次是触发ProjectileBase的OnActorHit中的Explode,第二次是触发计时器设置的Explode。

我们用TeleportTo实现传送,传送的Location为子弹Explode的位置,Rotation为Character自身的Rotation。

注意:使用TeleportTo实现传送,当子弹在地面Explode时会失败。这可能是由于UE4中地面的性质和UE5中地面的性质不同所导致的。

因为TeleportTo会检测待传送的Actor是否能fit到传送的位置,并以bool值的形式返回。所以我们通过观察TeleportTo的返回值得到了以上的推测。

当子弹在Cube上Explode时,

当子弹在地面上Explode时,

为了避免这种情况,我们可以直接使用SetActorLocation,因为TeleportTo实际上也是调用SetActorLocation实现的,又在此之上增加了Actor是否fit传送地点的判定。


TomLooman_ActionRoguelike_AssignmentTwo的评论 (共 条)

分享到微博请遵守国家法律