From 39195fd710e62a47424ea2fe43388dbe1994ce5b Mon Sep 17 00:00:00 2001 From: badlogic Date: Fri, 29 Mar 2019 16:55:22 +0100 Subject: [PATCH] [ue4] Port of SkeletonAnimationComponent to SpineWidget. Tick needs to be explicitely called by user in blueprint. --- .../SpinePlugin/Private/SSpineWidget.cpp | 5 - .../SpinePlugin/Private/SpineWidget.cpp | 158 +++++++++++++++++- .../Source/SpinePlugin/Public/SpineWidget.h | 72 +++++++- 3 files changed, 224 insertions(+), 11 deletions(-) diff --git a/spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Private/SSpineWidget.cpp b/spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Private/SSpineWidget.cpp index 76254d427..3ddd69342 100644 --- a/spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Private/SSpineWidget.cpp +++ b/spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Private/SSpineWidget.cpp @@ -157,11 +157,6 @@ int32 SSpineWidget::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeo self->UpdateMesh(LayerId, OutDrawElements, AllottedGeometry, widget->skeleton); } - /*if (renderData.VertexData.Num() > 0 && renderData.IndexData.Num() > 0 && renderData.RenderingResourceHandle.IsValid()) { - FSlateDrawElement::MakeCustomVerts(OutDrawElements, LayerId, renderData.RenderingResourceHandle, renderData.VertexData, - renderData.IndexData, nullptr, 0, 0); - }*/ - return LayerId; } diff --git a/spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpineWidget.cpp b/spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpineWidget.cpp index 895325855..a7abb9865 100644 --- a/spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpineWidget.cpp +++ b/spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpineWidget.cpp @@ -36,6 +36,10 @@ #define LOCTEXT_NAMESPACE "Spine" +using namespace spine; + +void callback(AnimationState* state, spine::EventType type, TrackEntry* entry, Event* event); + USpineWidget::USpineWidget(const FObjectInitializer& ObjectInitializer): Super(ObjectInitializer) { static ConstructorHelpers::FObjectFinder NormalMaterialRef(TEXT("/SpinePlugin/UI_SpineUnlitNormalMaterial")); NormalBlendMaterial = NormalMaterialRef.Object; @@ -52,6 +56,8 @@ USpineWidget::USpineWidget(const FObjectInitializer& ObjectInitializer): Super(O TextureParameterName = FName(TEXT("SpriteTexture")); worldVertices.ensureCapacity(1024 * 2); + + bAutoPlaying = true; } void USpineWidget::SynchronizeProperties() { @@ -60,11 +66,12 @@ void USpineWidget::SynchronizeProperties() { if (slateWidget.IsValid()) { CheckState(); if (skeleton) { - InternalTick(0); + Tick(0, false); slateWidget->SetData(this); } else { slateWidget->SetData(nullptr); } + trackEntries.Empty(); } } @@ -84,10 +91,12 @@ const FText USpineWidget::GetPaletteCategory() { } #endif -void USpineWidget::InternalTick(float DeltaTime, bool CallDelegates, bool Preview) { +void USpineWidget::Tick(float DeltaTime, bool CallDelegates) { CheckState(); - if (skeleton) { + if (state && bAutoPlaying) { + state->update(DeltaTime); + state->apply(*skeleton); if (CallDelegates) BeforeUpdateWorldTransform.Broadcast(this); skeleton->updateWorldTransform(); if (CallDelegates) AfterUpdateWorldTransform.Broadcast(this); @@ -115,8 +124,15 @@ void USpineWidget::CheckState() { DisposeState(); if (Atlas && SkeletonData) { - spine::SkeletonData* data = SkeletonData->GetSkeletonData(Atlas->GetAtlas()); - if (data) skeleton = new (__FILE__, __LINE__) spine::Skeleton(data); + spine::SkeletonData *data = SkeletonData->GetSkeletonData(Atlas->GetAtlas()); + if (data) { + skeleton = new (__FILE__, __LINE__) Skeleton(data); + AnimationStateData* stateData = SkeletonData->GetAnimationStateData(Atlas->GetAtlas()); + state = new (__FILE__, __LINE__) AnimationState(stateData); + state->setRendererObject((void*)this); + state->setListener(callback); + trackEntries.Empty(); + } } lastAtlas = Atlas; @@ -126,10 +142,17 @@ void USpineWidget::CheckState() { } void USpineWidget::DisposeState() { + if (state) { + delete state; + state = nullptr; + } + if (skeleton) { delete skeleton; skeleton = nullptr; } + + trackEntries.Empty(); } void USpineWidget::FinishDestroy() { @@ -278,4 +301,129 @@ float USpineWidget::GetAnimationDuration(FString AnimationName) { else return animation->getDuration(); } return 0; +} + +void USpineWidget::SetAutoPlay(bool bInAutoPlays) +{ + bAutoPlaying = bInAutoPlays; +} + +void USpineWidget::SetPlaybackTime(float InPlaybackTime, bool bCallDelegates) +{ + CheckState(); + + if (state && state->getCurrent(0)) { + spine::Animation* CurrentAnimation = state->getCurrent(0)->getAnimation(); + const float CurrentTime = state->getCurrent(0)->getTrackTime(); + InPlaybackTime = FMath::Clamp(InPlaybackTime, 0.0f, CurrentAnimation->getDuration()); + const float DeltaTime = InPlaybackTime - CurrentTime; + state->update(DeltaTime); + state->apply(*skeleton); + + //Call delegates and perform the world transform + if (bCallDelegates) + { + BeforeUpdateWorldTransform.Broadcast(this); + } + skeleton->updateWorldTransform(); + if (bCallDelegates) + { + AfterUpdateWorldTransform.Broadcast(this); + } + } +} + +void USpineWidget::SetTimeScale(float timeScale) { + CheckState(); + if (state) state->setTimeScale(timeScale); +} + +float USpineWidget::GetTimeScale() { + CheckState(); + if (state) return state->getTimeScale(); + return 1; +} + +UTrackEntry* USpineWidget::SetAnimation(int trackIndex, FString animationName, bool loop) { + CheckState(); + if (state && skeleton->getData()->findAnimation(TCHAR_TO_UTF8(*animationName))) { + state->disableQueue(); + TrackEntry* entry = state->setAnimation(trackIndex, TCHAR_TO_UTF8(*animationName), loop); + state->enableQueue(); + UTrackEntry* uEntry = NewObject(); + uEntry->SetTrackEntry(entry); + trackEntries.Add(uEntry); + return uEntry; + } + else return NewObject(); + +} + +UTrackEntry* USpineWidget::AddAnimation(int trackIndex, FString animationName, bool loop, float delay) { + CheckState(); + if (state && skeleton->getData()->findAnimation(TCHAR_TO_UTF8(*animationName))) { + state->disableQueue(); + TrackEntry* entry = state->addAnimation(trackIndex, TCHAR_TO_UTF8(*animationName), loop, delay); + state->enableQueue(); + UTrackEntry* uEntry = NewObject(); + uEntry->SetTrackEntry(entry); + trackEntries.Add(uEntry); + return uEntry; + } + else return NewObject(); +} + +UTrackEntry* USpineWidget::SetEmptyAnimation(int trackIndex, float mixDuration) { + CheckState(); + if (state) { + TrackEntry* entry = state->setEmptyAnimation(trackIndex, mixDuration); + UTrackEntry* uEntry = NewObject(); + uEntry->SetTrackEntry(entry); + trackEntries.Add(uEntry); + return uEntry; + } + else return NewObject(); +} + +UTrackEntry* USpineWidget::AddEmptyAnimation(int trackIndex, float mixDuration, float delay) { + CheckState(); + if (state) { + TrackEntry* entry = state->addEmptyAnimation(trackIndex, mixDuration, delay); + UTrackEntry* uEntry = NewObject(); + uEntry->SetTrackEntry(entry); + trackEntries.Add(uEntry); + return uEntry; + } + else return NewObject(); +} + +UTrackEntry* USpineWidget::GetCurrent(int trackIndex) { + CheckState(); + if (state) { + TrackEntry* entry = state->getCurrent(trackIndex); + if (entry->getRendererObject()) { + return (UTrackEntry*)entry->getRendererObject(); + } + else { + UTrackEntry* uEntry = NewObject(); + uEntry->SetTrackEntry(entry); + trackEntries.Add(uEntry); + return uEntry; + } + } + else return NewObject(); +} + +void USpineWidget::ClearTracks() { + CheckState(); + if (state) { + state->clearTracks(); + } +} + +void USpineWidget::ClearTrack(int trackIndex) { + CheckState(); + if (state) { + state->clearTrack(trackIndex); + } } \ No newline at end of file diff --git a/spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Public/SpineWidget.h b/spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Public/SpineWidget.h index 4a8e4a568..96f39c8df 100644 --- a/spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Public/SpineWidget.h +++ b/spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Public/SpineWidget.h @@ -146,19 +146,79 @@ public: UPROPERTY(BlueprintAssignable, Category = "Components|Spine|Skeleton") FSpineWidgetAfterUpdateWorldTransformDelegate AfterUpdateWorldTransform; + /* Manages if this skeleton should update automatically or is paused. */ + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Animation") + void SetAutoPlay(bool bInAutoPlays); + + /* Directly set the time of the current animation, will clamp to animation range. */ + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Animation") + void SetPlaybackTime(float InPlaybackTime, bool bCallDelegates = true); + + // Blueprint functions + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Animation") + void SetTimeScale(float timeScale); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Animation") + float GetTimeScale(); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Animation") + UTrackEntry* SetAnimation(int trackIndex, FString animationName, bool loop); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Animation") + UTrackEntry* AddAnimation(int trackIndex, FString animationName, bool loop, float delay); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Animation") + UTrackEntry* SetEmptyAnimation(int trackIndex, float mixDuration); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Animation") + UTrackEntry* AddEmptyAnimation(int trackIndex, float mixDuration, float delay); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Animation") + UTrackEntry* GetCurrent(int trackIndex); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Animation") + void ClearTracks(); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Animation") + void ClearTrack(int trackIndex); + + UPROPERTY(BlueprintAssignable, Category = "Components|Spine|Animation") + FSpineAnimationStartDelegate AnimationStart; + + UPROPERTY(BlueprintAssignable, Category = "Components|Spine|Animation") + FSpineAnimationInterruptDelegate AnimationInterrupt; + + UPROPERTY(BlueprintAssignable, Category = "Components|Spine|Animation") + FSpineAnimationEventDelegate AnimationEvent; + + UPROPERTY(BlueprintAssignable, Category = "Components|Spine|Animation") + FSpineAnimationCompleteDelegate AnimationComplete; + + UPROPERTY(BlueprintAssignable, Category = "Components|Spine|Animation") + FSpineAnimationEndDelegate AnimationEnd; + + UPROPERTY(BlueprintAssignable, Category = "Components|Spine|Animation") + FSpineAnimationDisposeDelegate AnimationDispose; + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Animation") + void Tick(float DeltaTime, bool CallDelegates = true); + virtual void FinishDestroy() override; + // used in C event callback. Needs to be public as we can't call + // protected methods from plain old C function. + void GCTrackEntry(UTrackEntry* entry) { trackEntries.Remove(entry); } protected: friend class SSpineWidget; virtual TSharedRef RebuildWidget() override; virtual void CheckState(); - virtual void InternalTick(float DeltaTime, bool CallDelegates = true, bool Preview = false); virtual void DisposeState(); TSharedPtr slateWidget; spine::Skeleton* skeleton; + spine::AnimationState* state; USpineAtlasAsset* lastAtlas = nullptr; spine::Atlas* lastSpineAtlas = nullptr; USpineSkeletonDataAsset* lastData = nullptr; @@ -182,4 +242,14 @@ protected: spine::Vector worldVertices; spine::SkeletonClipping clipper; + + // keep track of track entries so they won't get GCed while + // in transit within a blueprint + UPROPERTY() + TSet trackEntries; + +private: + /* If the animation should update automatically. */ + UPROPERTY() + bool bAutoPlaying; }; \ No newline at end of file