TomLooman_ActionRoguelike_第五章蓝图
该专栏用于保存对TomLooman的ActionRoguelike项目的学习笔记,学习过程中的思考与记录不一定准确。
教程参考:https://github.com/tomlooman/ActionRoguelike
基于UE5.0的项目实现:https://github.com/CarolBaggins2023/TomLooman_ActionRoguelike_Tutorial

2023_08_01
蓝图:蓝图的互动与铸造、在蓝图中制作动画、投射物蓝图与碰撞
另外自己实现了Character朝向随攻击变化(无动画)
我们创建了一个操纵杆的蓝图类,这个蓝图类直接继承Actor类,不像之前那些蓝图类继承我们在C++中创建的类。
我们先希望与操纵杆互动能够改变杆的角度。
因为“与操纵杆互动,改变操作杆角度”这一功能与之前的“与宝箱互动,打开宝箱”逻辑相同,所以这里可以复用之前定义的Interface类,让操纵杆类和宝箱类一样继承接口类。在蓝图中表现为添加一个接口,如下。

我们已经在Character上的ActorComponent上定义了一个函数,那个函数实现“检测互动物体是否实现了接口函数”和“在互动物体上调用接口函数”两个功能,所以这里我们要实现(重写/覆盖)Interface类中的接口函数。如下,添加蓝图中的Event(蓝图中的Event和C++中的函数类似,我们实现了这个Event就相当于实现了接口函数,蓝图中Event和Function的区别下面讲)

我们实现了调用接口函数后旋转操纵杆,还希望这个操纵杆能控制其它东西,这里我们用操纵杆控制爆炸桶爆炸。
这就需要在操纵杆实现的接口函数中,调用爆炸桶中的ForceComp->FireImpulse(),类似Projectile击中爆炸桶后的逻辑。
所以我们在爆炸桶的蓝图中创建一个事件,在该事件中执行FireImpulse()。需要注意的是,如果像这样在蓝图中调用成员变量(ForceComp),在成员变量的UFUNCTION中必须要BlueprintReadOnly或者BlueprintReadWrite的Specifier。

那么怎么在操纵杆实现的接口函数中调用爆炸桶的事件呢?我们可以将爆炸桶作为操纵杆的一个成员变量,然后就自然地相当于在成员函数中调用成员变量的成员函数。在蓝图中,我们创建一个Component,并选择其类型为ExplosiveBarrel,这样就相当于在操纵杆类中有了一个爆炸桶的成员变量。(教程中这里创建了Actor类型的变量,所以后面要多一步CastToExplosiveBarrel)

但是我们只知道这个成员变量是爆炸桶类型的,不知道它与哪个爆炸桶绑定,相当有成员变量,但是这个成员变量还没实例化。我们可以用一个已有的爆炸桶实例初始化该成员变量。在蓝图中,我们先勾选InstanceEditable,然后就能在内容浏览器里操纵杆实例的Details里选择要将哪个爆炸桶实例作为成员变量了。(用类似的方法可以实现一排操纵杆控制一排其它物体,比如爆炸桶,类似于游戏中的机关解密)


这样我们就实现了与操纵杆交互,控制爆炸桶爆炸。
蓝图中事件与函数的主要区别是:
(1) 函数可以有返回值,事件没有返回值(两者都可以有输入)

(2) 事件可以绑定到事件句柄上,例如计时器

之间我们通过改变宝箱盖子的Rotation模拟宝箱被打开,这样缺乏一个打开的过程,所以我们在蓝图中模拟开箱子的动画效果。注意,这里只是用蓝图模拟动画效果,并不想Character的攻击动作那样导入了MontageAnimation。
和与操纵杆的交互一样,这里我们依然是实现ItemChest中Interface类部分的接口函数。需要注意的是,宝箱的蓝图类是从C++类继承而来的,所以子类中又定义了一遍成员函数,相当于覆盖了父类的成员函数。因此,C++的ItemChest类中实现了接口函数就相当于无效了。
同样的,要在蓝图中使用LidMesh,要在C++的ItemChest类LidMesh成员变量前的UFUNCTION中加入BlueprintReadOnly的Specifier。

在Timeline的TimeTrack中,相比以前的开门等任务,我们更细致地模拟了宝箱开启的动画。

我们在宝箱的蓝图中新增了StaticMesh组件并增加了粒子效果,来模拟真实开宝箱的效果

粒子效果要取消AutoActivate(否则粒子效果在BeginPlay后就产生,而不是在交互后产生),并且在Timeline结束后触发。

另外,在debug时,我们可以选择我们的关注对象

还可以观察蓝图中某些值的变化。

之前我们的Projectile击中(发生碰撞)后,会停留在碰撞点。现在我们要实现Projectile击中后(1)触发粒子效果(2)消失。在蓝图中的实现如下,后面一部分一部分地解释。

先是最基础的实现,触发ComponentHit事件后,Spawn一个Emmiter(发射器),Emitter会播放attach父组件并跟随父组件的指定效果(这里就是我们的粒子效果)。使用Projectile的Location和Rotation,并在指定粒子效果播放结束之后自我销毁。

我们调整了Projectile碰撞的profile,然后发现由于Projectile是在Character手的位置Spawn的,所以一Spawn就产生了碰撞。因此我们要让从Character生成的Projectile忽视Character(包括Character的Component)。
简单地来想,在Projectile的Collision profile中将Character的类型设为Ignore就可以了。但是我们只想让从Character生成的Projectile忽视Character,而不想让其他Projectile也忽视Character。(自己的魔法飞弹不会对自己造成伤害,但别人的魔法飞弹可以)
因此我们要有一个变量表示Projectile是由谁Spawn的。Projectile是在C++的Character类的ASCharacter::PrimaryAttack_TimeElapsed
成员函数中Spawn的,使用GetWorld()->SpawnActor<AActor>(ProjectileClass, SpawnTM, SpawnParams);语句。那么通过在Spawn的额外参数SpawnParams
中添加SpawnParams.Instigator = this;(这里的this就是生成Projectile的Character),就可以得到Projectile是由谁生成的。
接着就可以在上面的蓝图中补充两部分。
(1)Projectile的碰撞组件始终忽视它自己的Instigator

(2)当Projectile Hit到的不是自己的Instigator(我们的Character)时,才进行后面的触发粒子效果和自我销毁。(实际上这里的!=判断是冗余的,因为有了(1)之后,Project忽视Character,也就不会发生Hit)

经过上面对Projectile的修改后,我们可以实现一个发射魔法飞弹的炮台,蓝图如下

SetTimerByEvent设置了一个定时器,每隔一段时间执行给定的delegate(委托,在这里就是下面的自定义事件OnTimeElapsed)(通过Looping打钩实现循环,否则就像Character攻击动画后生成Projectile那样,只执行一次)。
自定义事件中执行的SpawnActor和C++中类似,指定Actor的类别以及Spawn的Transform。
可以看出由这个炮台Spawn的Projectile是会HitCharacter的,因为此时Projectile的Instigator不是Character,而是这个炮台。
