[ue4] Port of SkeletonAnimationComponent to SpineWidget. Tick needs to be explicitely called by user in blueprint.

This commit is contained in:
badlogic 2019-03-29 16:55:22 +01:00
parent cdaef06850
commit 39195fd710
3 changed files with 224 additions and 11 deletions

View File

@ -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;
}

View File

@ -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<UMaterialInterface> 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<UTrackEntry>();
uEntry->SetTrackEntry(entry);
trackEntries.Add(uEntry);
return uEntry;
}
else return NewObject<UTrackEntry>();
}
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<UTrackEntry>();
uEntry->SetTrackEntry(entry);
trackEntries.Add(uEntry);
return uEntry;
}
else return NewObject<UTrackEntry>();
}
UTrackEntry* USpineWidget::SetEmptyAnimation(int trackIndex, float mixDuration) {
CheckState();
if (state) {
TrackEntry* entry = state->setEmptyAnimation(trackIndex, mixDuration);
UTrackEntry* uEntry = NewObject<UTrackEntry>();
uEntry->SetTrackEntry(entry);
trackEntries.Add(uEntry);
return uEntry;
}
else return NewObject<UTrackEntry>();
}
UTrackEntry* USpineWidget::AddEmptyAnimation(int trackIndex, float mixDuration, float delay) {
CheckState();
if (state) {
TrackEntry* entry = state->addEmptyAnimation(trackIndex, mixDuration, delay);
UTrackEntry* uEntry = NewObject<UTrackEntry>();
uEntry->SetTrackEntry(entry);
trackEntries.Add(uEntry);
return uEntry;
}
else return NewObject<UTrackEntry>();
}
UTrackEntry* USpineWidget::GetCurrent(int trackIndex) {
CheckState();
if (state) {
TrackEntry* entry = state->getCurrent(trackIndex);
if (entry->getRendererObject()) {
return (UTrackEntry*)entry->getRendererObject();
}
else {
UTrackEntry* uEntry = NewObject<UTrackEntry>();
uEntry->SetTrackEntry(entry);
trackEntries.Add(uEntry);
return uEntry;
}
}
else return NewObject<UTrackEntry>();
}
void USpineWidget::ClearTracks() {
CheckState();
if (state) {
state->clearTracks();
}
}
void USpineWidget::ClearTrack(int trackIndex) {
CheckState();
if (state) {
state->clearTrack(trackIndex);
}
}

View File

@ -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<SWidget> RebuildWidget() override;
virtual void CheckState();
virtual void InternalTick(float DeltaTime, bool CallDelegates = true, bool Preview = false);
virtual void DisposeState();
TSharedPtr<SSpineWidget> slateWidget;
spine::Skeleton* skeleton;
spine::AnimationState* state;
USpineAtlasAsset* lastAtlas = nullptr;
spine::Atlas* lastSpineAtlas = nullptr;
USpineSkeletonDataAsset* lastData = nullptr;
@ -182,4 +242,14 @@ protected:
spine::Vector<float> worldVertices;
spine::SkeletonClipping clipper;
// keep track of track entries so they won't get GCed while
// in transit within a blueprint
UPROPERTY()
TSet<UTrackEntry*> trackEntries;
private:
/* If the animation should update automatically. */
UPROPERTY()
bool bAutoPlaying;
};