You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
341 lines
10 KiB
C++
341 lines
10 KiB
C++
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "AnimNodes/AnimNode_RandomPlayer.h"
|
|
#include "AnimationRuntime.h"
|
|
#include "Animation/AnimInstanceProxy.h"
|
|
#include "Animation/AnimSequence.h"
|
|
|
|
FAnimNode_RandomPlayer::FAnimNode_RandomPlayer()
|
|
: CurrentEntry(INDEX_NONE)
|
|
, NextEntry(INDEX_NONE)
|
|
, CurrentDataIndex(0)
|
|
, bShuffleMode(false)
|
|
{
|
|
|
|
}
|
|
|
|
void FAnimNode_RandomPlayer::Initialize_AnyThread(const FAnimationInitializeContext& Context)
|
|
{
|
|
FAnimNode_Base::Initialize_AnyThread(Context);
|
|
GetEvaluateGraphExposedInputs().Execute(Context);
|
|
|
|
const int32 NumEntries = Entries.Num();
|
|
|
|
if(NumEntries == 0)
|
|
{
|
|
// early out here, no need to do anything at all if we're not playing anything
|
|
return;
|
|
}
|
|
|
|
NormalizedPlayChances.Empty(NormalizedPlayChances.Num());
|
|
NormalizedPlayChances.AddUninitialized(NumEntries);
|
|
|
|
// Initialize normalized play chance for each entry and validate entry data
|
|
float SumChances = 0.0f;
|
|
for(FRandomPlayerSequenceEntry& Entry : Entries)
|
|
{
|
|
SumChances += Entry.ChanceToPlay;
|
|
|
|
if(Entry.MaxLoopCount < Entry.MinLoopCount)
|
|
{
|
|
Swap(Entry.MaxLoopCount, Entry.MinLoopCount);
|
|
}
|
|
|
|
if(Entry.MaxPlayRate < Entry.MinPlayRate)
|
|
{
|
|
Swap(Entry.MaxLoopCount, Entry.MinLoopCount);
|
|
}
|
|
}
|
|
|
|
for(int32 Idx = 0 ; Idx < NumEntries ; ++Idx)
|
|
{
|
|
NormalizedPlayChances[Idx] = Entries[Idx].ChanceToPlay / SumChances;
|
|
}
|
|
|
|
// Initialize random stream and pick first entry
|
|
RandomStream.Initialize(FPlatformTime::Cycles());
|
|
|
|
CurrentEntry = GetNextEntryIndex();
|
|
NextEntry = GetNextEntryIndex();
|
|
|
|
PlayData.Empty(2);
|
|
PlayData.AddDefaulted(2);
|
|
|
|
FRandomAnimPlayData& CurrentData = PlayData[GetDataIndex(ERandomDataIndexType::Current)];
|
|
FRandomAnimPlayData& NextData = PlayData[GetDataIndex(ERandomDataIndexType::Next)];
|
|
|
|
// Init play data
|
|
CurrentData.BlendWeight = 1.0f;
|
|
CurrentData.PlayRate = RandomStream.FRandRange(Entries[CurrentEntry].MinPlayRate, Entries[CurrentEntry].MaxPlayRate);
|
|
CurrentData.RemainingLoops = FMath::Clamp(RandomStream.RandRange(Entries[CurrentEntry].MinLoopCount, Entries[CurrentEntry].MaxLoopCount), 0, MAX_int32);
|
|
|
|
NextData.BlendWeight = 0.0f;
|
|
NextData.PlayRate = RandomStream.FRandRange(Entries[NextEntry].MinPlayRate, Entries[NextEntry].MaxPlayRate);
|
|
NextData.RemainingLoops = FMath::Clamp(RandomStream.RandRange(Entries[NextEntry].MinLoopCount, Entries[NextEntry].MaxLoopCount), 0, MAX_int32);
|
|
}
|
|
|
|
void FAnimNode_RandomPlayer::Update_AnyThread(const FAnimationUpdateContext& Context)
|
|
{
|
|
GetEvaluateGraphExposedInputs().Execute(Context);
|
|
|
|
if(Entries.Num() == 0)
|
|
{
|
|
// We don't have any entries, play data will be invalid - early out
|
|
return;
|
|
}
|
|
|
|
FRandomAnimPlayData* CurrentData = &PlayData[GetDataIndex(ERandomDataIndexType::Current)];
|
|
FRandomAnimPlayData* NextData = &PlayData[GetDataIndex(ERandomDataIndexType::Next)];
|
|
|
|
if(UAnimSequence* CurrentSequence = Entries[CurrentEntry].Sequence)
|
|
{
|
|
float TimeRemaining = CurrentSequence->SequenceLength - CurrentData->InternalTimeAccumulator;
|
|
|
|
if(CurrentData->InternalTimeAccumulator < CurrentData->PreviousTimeAccumulator)
|
|
{
|
|
// We've looped, update remaining
|
|
--CurrentData->RemainingLoops;
|
|
|
|
if(CurrentData->RemainingLoops < 0)
|
|
{
|
|
// If we're switching to the same anim
|
|
if(CurrentEntry == NextEntry)
|
|
{
|
|
// Need to switch to the next anim, but first put our accumulator in the next data we're about to switch
|
|
// to so we don't see a pop
|
|
NextData->InternalTimeAccumulator = CurrentData->InternalTimeAccumulator;
|
|
}
|
|
|
|
SwitchNextToCurrent();
|
|
|
|
// Re-get data as we've switched over
|
|
CurrentData = &PlayData[GetDataIndex(ERandomDataIndexType::Current)];
|
|
NextData = &PlayData[GetDataIndex(ERandomDataIndexType::Next)];
|
|
}
|
|
}
|
|
|
|
// Cache time to detect loops
|
|
CurrentData->PreviousTimeAccumulator = CurrentData->InternalTimeAccumulator;
|
|
NextData->PreviousTimeAccumulator = NextData->InternalTimeAccumulator;
|
|
|
|
// If we're in the blend window start blending, but only if we're moving to a new animation,
|
|
// otherwise just keep looping.
|
|
const bool bInCrossfadeTime = TimeRemaining <= Entries[NextEntry].BlendIn.GetBlendTime();
|
|
const bool bNextAnimIsDifferent = NextEntry != CurrentEntry;
|
|
const bool bNeedMoreLoops = CurrentData->RemainingLoops > 0;
|
|
|
|
if(bInCrossfadeTime && !bNeedMoreLoops)
|
|
{
|
|
if(bNextAnimIsDifferent)
|
|
{
|
|
// Blending to next
|
|
Entries[NextEntry].BlendIn.Update(Context.GetDeltaTime());
|
|
|
|
float BlendedAlpha = Entries[NextEntry].BlendIn.GetBlendedValue();
|
|
|
|
if(BlendedAlpha < 1.0f)
|
|
{
|
|
NextData->BlendWeight = BlendedAlpha;
|
|
CurrentData->BlendWeight = 1.0f - BlendedAlpha;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we were blending but now we're done, switch play data
|
|
if(Entries[NextEntry].BlendIn.IsComplete())
|
|
{
|
|
SwitchNextToCurrent();
|
|
|
|
// Re-get data as we've switched over
|
|
CurrentData = &PlayData[GetDataIndex(ERandomDataIndexType::Current)];
|
|
NextData = &PlayData[GetDataIndex(ERandomDataIndexType::Next)];
|
|
}
|
|
|
|
if(FAnimInstanceProxy* AnimProxy = Context.AnimInstanceProxy)
|
|
{
|
|
FAnimGroupInstance* SyncGroup;
|
|
FAnimTickRecord& TickRecord = AnimProxy->CreateUninitializedTickRecord(INDEX_NONE, SyncGroup);
|
|
AnimProxy->MakeSequenceTickRecord(TickRecord, Entries[CurrentEntry].Sequence, true, CurrentData->PlayRate, CurrentData->BlendWeight, CurrentData->InternalTimeAccumulator, CurrentData->MarkerTickRecord);
|
|
|
|
if(NextData->BlendWeight > 0.0f)
|
|
{
|
|
FAnimTickRecord& NextTickRecord = AnimProxy->CreateUninitializedTickRecord(INDEX_NONE, SyncGroup);
|
|
AnimProxy->MakeSequenceTickRecord(NextTickRecord, Entries[NextEntry].Sequence, true, NextData->PlayRate, NextData->BlendWeight, NextData->InternalTimeAccumulator, NextData->MarkerTickRecord);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FAnimNode_RandomPlayer::Evaluate_AnyThread(FPoseContext& Output)
|
|
{
|
|
if(Entries.Num() > 0)
|
|
{
|
|
UAnimSequence* CurrentSequence = Entries[CurrentEntry].Sequence;
|
|
|
|
if(CurrentSequence)
|
|
{
|
|
FRandomAnimPlayData& CurrentData = PlayData[GetDataIndex(ERandomDataIndexType::Current)];
|
|
FRandomAnimPlayData& NextData = PlayData[GetDataIndex(ERandomDataIndexType::Next)];
|
|
|
|
if(CurrentData.BlendWeight != 1.0f)
|
|
{
|
|
if(FAnimInstanceProxy* AnimProxy = Output.AnimInstanceProxy)
|
|
{
|
|
// Start Blending
|
|
FCompactPose Poses[2];
|
|
FBlendedCurve Curves[2];
|
|
float Weights[2];
|
|
|
|
const FBoneContainer& RequiredBone = AnimProxy->GetRequiredBones();
|
|
Poses[0].SetBoneContainer(&RequiredBone);
|
|
Poses[1].SetBoneContainer(&RequiredBone);
|
|
|
|
Curves[0].InitFrom(RequiredBone);
|
|
Curves[1].InitFrom(RequiredBone);
|
|
|
|
Weights[0] = CurrentData.BlendWeight;
|
|
Weights[1] = NextData.BlendWeight;
|
|
|
|
UAnimSequence* NextSequence = Entries[NextEntry].Sequence;
|
|
|
|
CurrentSequence->GetAnimationPose(Poses[0], Curves[0], FAnimExtractContext(CurrentData.InternalTimeAccumulator, AnimProxy->ShouldExtractRootMotion()));
|
|
NextSequence->GetAnimationPose(Poses[1], Curves[1], FAnimExtractContext(NextData.InternalTimeAccumulator, AnimProxy->ShouldExtractRootMotion()));
|
|
|
|
FAnimationRuntime::BlendPosesTogether(Poses, Curves, Weights, Output.Pose, Output.Curve);
|
|
}
|
|
else
|
|
{
|
|
Output.ResetToRefPose();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Single anim
|
|
CurrentSequence->GetAnimationPose(Output.Pose, Output.Curve, FAnimExtractContext(CurrentData.InternalTimeAccumulator, Output.AnimInstanceProxy->ShouldExtractRootMotion()));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Output.ResetToRefPose();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Output.ResetToRefPose();
|
|
}
|
|
}
|
|
|
|
void FAnimNode_RandomPlayer::GatherDebugData(FNodeDebugData& DebugData)
|
|
{
|
|
FString DebugLine = DebugData.GetNodeName(this);
|
|
|
|
DebugData.AddDebugItem(DebugLine, true);
|
|
}
|
|
|
|
int32 FAnimNode_RandomPlayer::GetNextEntryIndex()
|
|
{
|
|
if(Entries.Num() > 0)
|
|
{
|
|
if(bShuffleMode)
|
|
{
|
|
if(ShuffleList.Num() == 0)
|
|
{
|
|
// Need a new list
|
|
BuildShuffleList();
|
|
}
|
|
|
|
// Get the top value, don't allow realloc
|
|
return ShuffleList.Pop(false);
|
|
}
|
|
else
|
|
{
|
|
float RandomVal = RandomStream.GetFraction();
|
|
const int32 NumEntries = Entries.Num();
|
|
|
|
// Grab the entry index corresponding to the value
|
|
for(int32 Idx = 0 ; Idx < NumEntries ; ++Idx)
|
|
{
|
|
RandomVal -= NormalizedPlayChances[Idx];
|
|
if(RandomVal <= 0.0f)
|
|
{
|
|
return Idx;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return INDEX_NONE;
|
|
}
|
|
|
|
int32 FAnimNode_RandomPlayer::GetDataIndex(const ERandomDataIndexType& Type)
|
|
{
|
|
if(Type == ERandomDataIndexType::Current)
|
|
{
|
|
return CurrentDataIndex;
|
|
}
|
|
else
|
|
{
|
|
// Next Accumulator
|
|
return (CurrentDataIndex + 1) % 2;
|
|
}
|
|
}
|
|
|
|
void FAnimNode_RandomPlayer::SwitchNextToCurrent()
|
|
{
|
|
// reset alpha blend we've possibly just taken
|
|
Entries[NextEntry].BlendIn.Reset();
|
|
|
|
// Switch which entry to get sequences and parameters from, and pre-generate the next entry index
|
|
CurrentEntry = NextEntry;
|
|
NextEntry = GetNextEntryIndex();
|
|
|
|
// Switch play data
|
|
CurrentDataIndex = (CurrentDataIndex + 1) %2;
|
|
|
|
// Get our play data
|
|
FRandomAnimPlayData& CurrentData = PlayData[GetDataIndex(ERandomDataIndexType::Current)];
|
|
FRandomAnimPlayData& NextData = PlayData[GetDataIndex(ERandomDataIndexType::Next)];
|
|
|
|
// Reset blendweights
|
|
CurrentData.BlendWeight = 1.0f;
|
|
NextData.BlendWeight = 0.0f;
|
|
|
|
// Set up data for next switch
|
|
NextData.InternalTimeAccumulator = 0.0f;
|
|
NextData.PreviousTimeAccumulator = 0.0f;
|
|
NextData.PlayRate = RandomStream.FRandRange(Entries[NextEntry].MinPlayRate, Entries[NextEntry].MaxPlayRate);
|
|
NextData.RemainingLoops = FMath::Clamp(RandomStream.RandRange(Entries[NextEntry].MinLoopCount, Entries[NextEntry].MaxLoopCount), 0, MAX_int32);
|
|
NextData.MarkerTickRecord.Reset();
|
|
}
|
|
|
|
void FAnimNode_RandomPlayer::BuildShuffleList()
|
|
{
|
|
ShuffleList.Reset(Entries.Num());
|
|
|
|
// Build entry index list
|
|
const int32 NumEntries = Entries.Num();
|
|
for(int32 i = 0 ; i < NumEntries ; ++i)
|
|
{
|
|
ShuffleList.Add(i);
|
|
}
|
|
|
|
// Shuffle the list
|
|
const int32 NumShuffles = ShuffleList.Num() - 1;
|
|
for(int32 i = 0 ; i < NumShuffles ; ++i)
|
|
{
|
|
int32 SwapIdx = RandomStream.RandRange(i, NumShuffles);
|
|
ShuffleList.Swap(i, SwapIdx);
|
|
}
|
|
|
|
if(ShuffleList.Num() > 1)
|
|
{
|
|
// Make sure we don't play the same thing twice, one at the end and one at the beginning of
|
|
// the list
|
|
if(ShuffleList.Last() == CurrentEntry)
|
|
{
|
|
// Swap first and last
|
|
ShuffleList.Swap(0, ShuffleList.Num() - 1);
|
|
}
|
|
}
|
|
}
|