36. UE5 RPG在激活技能时使用蒙太奇动画

在上一篇文章里面,我们实现了一个简单的火球术,创建了火球术的火球,以及能发射它的技能。很简陋,在技能触发的时候,直接在武器的位置生成火球发射出去。在一篇文章里,我们要实现使用技能时,角色会播放攻击动画,然后再某个动画帧,将火球发射出去。

所以,我们要使用播放蒙太奇动画来实现动画覆盖,以及动画通知来实现角色手向前攻击时触发火球射出。

播放动画

首先,我们要在技能蓝图里面,在激活蓝图后,使用PlayMontageAndWait节点去播放动画。

我们要实现在释放技能时,播放角色动画,需要使用到技能内置的节点,它是专门用于在技能内使用的版本。

先找到对应的动画序列,

创建对应的蒙太奇

它需要指定插槽,我们这里使用了默认插槽。

并且你ABP里面,需要设置对应的插槽,这样,插槽前面的动画输入后,会应用插槽内播放的蒙太奇动画,然后混合输出。

在技能中,设置对应的蒙太奇动画

我们可以查看一下播放蒙太奇后面的播放顺序,大家也可以想一想它的执行顺序是怎么样的。

如果一个蒙太奇正常播放完成,Activate不是异步的在技能触发后会直接执行,BlendOut(混出)在蒙太奇播放快播放完成,防止动画直接切换的混合时间,所以是第二个,混合完成后,则触发完成。

如果想知道UE里面是如何实现这个Task类的,我们可以在代码编辑器里面查看,比如在rider里面,双击Shift键,搜索

这个类推荐大家都自己看看,学习一下如何制作自定义的Task类,下面我简单讲解一下

类里面是将CreatePlayMontageAndWaitProxy作为一个蓝图可以调用的函数

meta = (...): 这部分包含了关于函数的元数据,这些元数据可以控制函数在蓝图编辑器中的显示和行为。

DisplayName="PlayMontageAndWait": 这定义了函数在蓝图编辑器中的显示名称。这可以是与函数实际名称不同的字符串,以便在蓝图中更容易理解其用途。

HidePin = "OwningAbility": 这告诉蓝图编辑器隐藏名为"OwningAbility"的引脚(即输入参数)。在蓝图中创建节点时,这个参数将不会作为一个可连接的引脚出现,但函数内部仍然可以访问它。

DefaultToSelf = "OwningAbility": 当在蓝图中创建此函数的节点时,如果未明确连接"OwningAbility"引脚,它将默认使用当前蓝图实例(通常是当前激活的能力或组件)作为此参数的值。

BlueprintInternalUseOnly = "TRUE": 这意味着这个函数是为蓝图系统的内部使用而设计的,可能不适合在普通的蓝图逻辑中使用。它通常用于为蓝图提供底层支持或实现特定功能,而不是直接供设计者使用。

cpp 复制代码
	/**
	在角色上开始播放一个动画组合,并等待其播放完毕
	如果StopWhenAbilityEnds为true,当能力正常结束时,此动画组合将被中止。当能力被明确取消时,它总是会被停止。
	在正常执行过程中,当动画组合混合退出时,会调用OnBlendOut;当动画组合完全播放完毕时,会调用OnCompleted
	如果另一个动画组合覆盖了此动画组合,则会调用OnInterrupted;如果能力或任务被取消,则会调用OnCancelled
	@param TaskInstanceName 设置以覆盖此任务的名称,以便后续查询
	@param MontageToPlay 要在角色上播放的动画组合
	@param Rate 更改播放动画组合的速度,使其更快或更慢
	@param StartSection 如果不为空,则动画组合将从指定的命名部分开始播放
	@param bStopWhenAbilityEnds 如果为true,当能力正常结束时,此动画组合将被中止。当能力被明确取消时,它总是会被停止
	@param AnimRootMotionTranslationScale 更改以修改根运动的大小,或设置为0以完全阻止它
	@param StartTimeSeconds 动画组合中的起始时间偏移量(以秒为单位),这将被StartSection参数覆盖(如果也设置了该参数)
	@param bAllowInterruptAfterBlendOut 如果为true,在OnBlendOut开始后,你可以收到OnInterrupted(否则,当中断时不会触发OnInterrupted,但你也不会收到OnComplete)。
	*/
	UFUNCTION(BlueprintCallable, Category="Ability|Tasks", meta = (DisplayName="PlayMontageAndWait", HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE"))
	static UAbilityTask_PlayMontageAndWait* CreatePlayMontageAndWaitProxy(UGameplayAbility* OwningAbility,
		FName TaskInstanceName, UAnimMontage* MontageToPlay, float Rate = 1.f, FName StartSection = NAME_None, bool bStopWhenAbilityEnds = true, float AnimRootMotionTranslationScale = 1.f, float StartTimeSeconds = 0.f, bool bAllowInterruptAfterBlendOut = false);

然后看一下实现,我们就是节点在蓝图中被创建后所做的内容,它创建了并返回了一个新的Task实例并将参数设置给实例。

cpp 复制代码
UAbilityTask_PlayMontageAndWait* UAbilityTask_PlayMontageAndWait::CreatePlayMontageAndWaitProxy(UGameplayAbility* OwningAbility,
	FName TaskInstanceName, UAnimMontage *MontageToPlay, float Rate, FName StartSection, bool bStopWhenAbilityEnds, float AnimRootMotionTranslationScale, float StartTimeSeconds, bool bAllowInterruptAfterBlendOut)
{

	UAbilitySystemGlobals::NonShipping_ApplyGlobalAbilityScaler_Rate(Rate);

	UAbilityTask_PlayMontageAndWait* MyObj = NewAbilityTask<UAbilityTask_PlayMontageAndWait>(OwningAbility, TaskInstanceName);
	MyObj->MontageToPlay = MontageToPlay;
	MyObj->Rate = Rate;
	MyObj->StartSection = StartSection;
	MyObj->AnimRootMotionTranslationScale = AnimRootMotionTranslationScale;
	MyObj->bStopWhenAbilityEnds = bStopWhenAbilityEnds;
	MyObj->bAllowInterruptAfterBlendOut = bAllowInterruptAfterBlendOut;
	MyObj->StartTimeSeconds = StartTimeSeconds;
	
	return MyObj;
}

还有就是节点执行时,其实是调用的ASC身上的PlayMontage去执行动画进行的后续的操作。这里我就简单的讲一下,推荐大家都看一下代码实现。

cpp 复制代码
void UAbilityTask_PlayMontageAndWait::Activate()
{
	if (Ability == nullptr)
	{
		return;
	}

	bool bPlayedMontage = false;

	if (UAbilitySystemComponent* ASC = AbilitySystemComponent.Get())
	{
		const FGameplayAbilityActorInfo* ActorInfo = Ability->GetCurrentActorInfo();
		UAnimInstance* AnimInstance = ActorInfo->GetAnimInstance();
		if (AnimInstance != nullptr)
		{
			if (ASC->PlayMontage(Ability, Ability->GetCurrentActivationInfo(), MontageToPlay, Rate, StartSection, StartTimeSeconds) > 0.f)
			{

到这里,我们实现了动画播放,火球虽然现在还在激活技能时直接触发,这不是我们想要的效果,我们想在角色攻击的那一刻,火球射出,所以,接下来,实现在动画的关键帧实现这个效果。

创建动画通知

要实现角色在攻击动画攻击的那一刻触发火球术,我们接下来要做的就是通过动画通知去触发一个通知事件,然后使用GameplayTag标签去通知在通知中发送对应的事件标签,然后使用监听事件去获取标签,如果触发对应标签,在武器上生成火球。

接下来,我们先添加蒙太奇的通知标签,由于这些内容不需要在c++里面用,我们可以直接在UE里面创建。

创建一个蓝图类,基于AnimNotify

打开蓝图,我们要覆盖父函数 已接收通知

在函数中,我们通过骨骼网格体组件获取所有者actor,然后通过SendGameplayEventToActor发送标签通知,为了能让这个标签通用,我们将标签设置为变量,Send节点,Payload属性可以设置额外数据用于传递,这里不需要,我只用展示,使用时记得删除。

标签变量的眼睛要打开,这样就可以在外部设置

制作完成,我们在蒙太奇动画中对应攻击那一刻,添加一个动画通知,直接设置我们创建的动画通知类。

选中添加的通知,我们可以在右侧细节设置它的标签,这样在蒙太奇播放的时候,就可以触发标签事件了。

在蓝图中,我们使用wait Gameplay Event节点来监听事件,并将标签设置,在接收到通知后打印一个字符串用于测试。

运行测试,成功打印就代表事件触发了。

下面,我们将修改创建火球术的逻辑,不能够在激活时直接触发火球术,而是在接收到通知时触发。

创建一个新的函数,在蓝图中可调用

cpp 复制代码
	UFUNCTION(BlueprintCallable, Category="Projectile")
	void SpawnProjectile();

将实现火球术的代码逻辑移到函数内,代码唯一变的事判断是否为服务器运行修改为了GetAvatarActorFromActorInfo()->HasAuthority()

cpp 复制代码
void UProjectileSpell::SpawnProjectile()
{
	const bool bIsServer = GetAvatarActorFromActorInfo()->HasAuthority(); //判断此函数是否在服务器运行
	if (!bIsServer) return;

	if (ICombatInterface* CombatInterface = Cast<ICombatInterface>(GetAvatarActorFromActorInfo()))
	{
		FTransform SpawnTransform;
		SpawnTransform.SetLocation(CombatInterface->GetCombatSocketLocation());
		SpawnTransform.SetRotation(GetAvatarActorFromActorInfo()->GetActorQuat());
		
		//SpawnActorDeferred将异步创建实例,在实例创建完成时,相应的数据已经应用到了实例身上
		AProjectile* Projectile = GetWorld()->SpawnActorDeferred<AProjectile>(
			ProjectileClass,
			SpawnTransform,
			GetOwningActorFromActorInfo(),
			Cast<APawn>(GetOwningActorFromActorInfo()),
			ESpawnActorCollisionHandlingMethod::AlwaysSpawn);

		//TODO:给 Projectile添加一个GE 去实现伤害

		//确保变换设置被正确应用
		Projectile->FinishSpawning(SpawnTransform);
	}
}

接着编译,打开UE,将技能蓝图修改为接收到事件后,调用生成火球,然后结束技能

这样,就完成了相应的功能

相关推荐
勤奋的凯尔森同学2 小时前
webmin配置终端显示样式,模仿UbuntuDesktop终端
linux·运维·服务器·ubuntu·webmin
技术小齐6 小时前
网络运维学习笔记 016网工初级(HCIA-Datacom与CCNA-EI)PPP点对点协议和PPPoE以太网上的点对点协议(此处只讲华为)
运维·网络·学习
ITPUB-微风6 小时前
Service Mesh在爱奇艺的落地实践:架构、运维与扩展
运维·架构·service_mesh
落幕6 小时前
C语言-进程
linux·运维·服务器
chenbin5207 小时前
Jenkins 自动构建Job
运维·jenkins
java 凯7 小时前
Jenkins插件管理切换国内源地址
运维·jenkins
AI服务老曹7 小时前
运用先进的智能算法和优化模型,进行科学合理调度的智慧园区开源了
运维·人工智能·安全·开源·音视频
sszdzq8 小时前
Docker
运维·docker·容器
book01218 小时前
MySql数据库运维学习笔记
运维·数据库·mysql
bugtraq20219 小时前
XiaoMi Mi5(gemini) 刷入Ubuntu Touch 16.04——安卓手机刷入Linux
linux·运维·ubuntu