本文使用 UE4.26,以及 GAS 系统,记录一下 Montage Next Section 的一个问题
一、问题背景和表现
1.1 问题背景
在 UE4 中,有时候需要在播放 Montage 的同时修改 Section 的连接关系(关于 Montage 和 Section 的含义和用法可以参考 这里),比如有时候想第 1 段后边接第 2 段,有时候想接第 3 段,这时候可以用 UGameplayAbility::MontageSetNextSectionName
设置第一段的下一段是哪个,然后就可以达到第一段结束之后,直接从哪一段继续播的效果。
如果不想当前段播完,而是直接跳转到下一段,从接口上看,没有一个接口是 “JumpToNextSection” 这种功能,只有 “MontageJumpToSection” 这样的接口,所以需要一个 “GetNextSection” 这样的接口拿到当前 Section 的下一个 Section 是哪个。
1.2 问题表现
问题表现一句话总结就是:FAnimMontageInstance::GetNextSection()
和 FAnimMontageInstance::GetNextSectionID
结果不一致。
FName FAnimMontageInstance::GetNextSection() const
{
if (Montage)
{
float CurrentPosition;
const int32 CurrentSectionIndex = Montage->GetAnimCompositeSectionIndexFromPos(Position, CurrentPosition);
if (Montage->IsValidSectionIndex(CurrentSectionIndex))
{
FCompositeSection& CurrentSection = Montage->GetAnimCompositeSection(CurrentSectionIndex);
return CurrentSection.NextSectionName;
}
}
return NAME_None;
}
int32 FAnimMontageInstance::GetNextSectionID(int32 const & CurrentSectionID) const
{
return NextSections.IsValidIndex(CurrentSectionID) ? NextSections[CurrentSectionID] : INDEX_NONE;
}
两个函数如上所示:
GetNextSection
是通过 Position,算出当前 Section,然后通过FCompositeSection
类型里的NextSectionName
变量得到 NextSection。- 而
GetNextSectionID
是通过 NextSections 数组,通过当前 Section 的 ID 得到 NextSection 的 ID。
通过第二种方法得到 NextSection 的 ID 之后可以得到 NextSection 的 Name:
FName FAnimMontageInstance::GetSectionNameFromID(int32 const & SectionID) const
{
if (Montage && Montage->IsValidSectionIndex(SectionID))
{
FCompositeSection const & CurrentSection = Montage->GetAnimCompositeSection(SectionID);
return CurrentSection.SectionName;
}
return NAME_None;
}
从函数名上看,两种应该只是返回名字和 ID 的区别(当当前所在 Section ID 作为参数传给 GetNextSectionID
时),但是由于用过两种完全不同的方式获得 NextSection(一种是 FCompositeSection
成员变量,一种是 NextSections
数组),实际结果可能不一样。
1.3 问题复现
配置一个如下所示的 Montage:
其中有四个 Section,每一段的下一段是后边一个 Section。
每一段里都配置了一个 “PrintString” 的动画通知,作用就是输出一个配置的字符串,每一段配置的就是对应的 “2”、“3”、“4”。
运行游戏,并播放这个 Montage,可以看到按顺序输出了四个,没有问题:
LogAbilitySystem: Error: Print String 1
LogAbilitySystem: Error: Print String 2
LogAbilitySystem: Error: Print String 3
LogAbilitySystem: Error: Print String 4
在第一段中配置一个动画通知,在通知触发时,进行 “SetNextSection” 和 “GetNextSection” 的操作(为了测试,虽然可以直接 Jump),
代码如下:
ACharacter* const Character = Cast<ACharacter>(GetOwningActorFromActorInfo());
if (!Character)
{
return;
}
UAbilitySystemComponent* const ASC = GetAbilitySystemComponentFromActorInfo_Checked();
if (!ASC)
{
return;
}
UAnimInstance* const AnimInstance = Character->GetMesh()->GetAnimInstance();
if (!AnimInstance)
{
return;
}
FAnimMontageInstance* const AnimMontageInstance = AnimInstance->GetActiveInstanceForMontage(ASC->GetCurrentMontage());
if (!AnimMontageInstance)
{
return;
}
MontageSetNextSectionName("1", "3");
FName const CurSection = AnimMontageInstance->GetCurrentSection();
FName const NextSection1 = AnimMontageInstance->GetNextSection();
FName const NextSection2 =
AnimMontageInstance->GetSectionNameFromID(AnimMontageInstance->GetNextSectionID(ASC->GetCurrentMontageSectionID()));
UE_LOG(LogAbilitySystem, Error,
TEXT("CurSection is %s; NextSection1 is %s; NextSection2 is %s;"),
*CurSection.ToString(),
*NextSection1.ToString(),
*NextSection2.ToString());
重点其实就是 MontageSetNextSectionName("1", "3");
设置了第 “1” 段的下一段是第 “3” 段而不是默认的第 “2” 段。
其中 MontageSetNextSectionName("1", "3");
和 AnimInstance->Montage_SetNextSection("1", "3", ASC->GetCurrentMontage());
和 AnimMontageInstance->SetNextSectionName("1", "3");
三种方式是一样的。
然后通过两种不同的方式 Get 出 NextSection,结果如下:
LogAbilitySystem: Error: CurSection is 1; NextSection1 is 2; NextSection2 is 3;
LogAbilitySystem: Error: Print String 1
LogAbilitySystem: Error: Print String 3
LogAbilitySystem: Error: Print String 4
从实际动画表现,和输出看,可以看到设置 NextSection 是生效了的,第 “1” 段结束接的是第 “3” 段,但是通过 AnimMontageInstance->GetCurrentSection()
得到的却是默认的第 “2” 段。
二、问题原因
UGameplayAbility::MontageSetNextSectionName(FName FromSectionName, FName ToSectionName)
上边这个函数最终会调到下边:
bool FAnimMontageInstance::SetNextSectionID(int32 const & SectionID, int32 const & NewNextSectionID)
而上边这个函数只有对成员变量 PrevSections
和 NextSections
这两个数组的修改,并没有真正修改 Montage 上的 Section 的 NextSection,如下图所示:
修改这个相当于修改资源的配置了,本身也不应该修改,所以个人感觉 FAnimMontageInstance::GetNextSection()
这个函数写的有点问题,应该返回 Montage Instance 上的 NextSection 而不是资源本身的未调整的配置。