diff --git a/spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpineWidget.cpp b/spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpineWidget.cpp index 7e17b255d..895325855 100644 --- a/spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpineWidget.cpp +++ b/spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpineWidget.cpp @@ -32,6 +32,7 @@ #include "SpineWidget.h" #include "SSpineWidget.h" #include "Engine.h" +#include "spine/spine.h" #define LOCTEXT_NAMESPACE "Spine" @@ -83,24 +84,43 @@ const FText USpineWidget::GetPaletteCategory() { } #endif -void USpineWidget::InternalTick(float DeltaTime) { +void USpineWidget::InternalTick(float DeltaTime, bool CallDelegates, bool Preview) { CheckState(); if (skeleton) { + if (CallDelegates) BeforeUpdateWorldTransform.Broadcast(this); skeleton->updateWorldTransform(); + if (CallDelegates) AfterUpdateWorldTransform.Broadcast(this); } } void USpineWidget::CheckState() { - if (lastAtlas != Atlas || lastData != SkeletonData) { + bool needsUpdate = lastAtlas != Atlas || lastData != SkeletonData; + + if (!needsUpdate) { + // Are we doing a re-import? Then check if the underlying spine-cpp data + // has changed. + if (lastAtlas && lastAtlas == Atlas && lastData && lastData == SkeletonData) { + spine::Atlas* atlas = Atlas->GetAtlas(); + if (lastSpineAtlas != atlas) { + needsUpdate = true; + } + if (skeleton && skeleton->getData() != SkeletonData->GetSkeletonData(atlas)) { + needsUpdate = true; + } + } + } + + if (needsUpdate) { DisposeState(); if (Atlas && SkeletonData) { spine::SkeletonData* data = SkeletonData->GetSkeletonData(Atlas->GetAtlas()); - skeleton = new (__FILE__, __LINE__) spine::Skeleton(data); + if (data) skeleton = new (__FILE__, __LINE__) spine::Skeleton(data); } lastAtlas = Atlas; + lastSpineAtlas = Atlas ? Atlas->GetAtlas() : nullptr; lastData = SkeletonData; } } @@ -115,4 +135,147 @@ void USpineWidget::DisposeState() { void USpineWidget::FinishDestroy() { DisposeState(); Super::FinishDestroy(); +} + +bool USpineWidget::SetSkin(const FString skinName) { + CheckState(); + if (skeleton) { + spine::Skin* skin = skeleton->getData()->findSkin(TCHAR_TO_UTF8(*skinName)); + if (!skin) return false; + skeleton->setSkin(skin); + return true; + } + else return false; +} + +void USpineWidget::GetSkins(TArray &Skins) { + CheckState(); + if (skeleton) { + for (size_t i = 0, n = skeleton->getData()->getSkins().size(); i < n; i++) { + Skins.Add(skeleton->getData()->getSkins()[i]->getName().buffer()); + } + } +} + +bool USpineWidget::HasSkin(const FString skinName) { + CheckState(); + if (skeleton) { + return skeleton->getData()->findAnimation(TCHAR_TO_UTF8(*skinName)) != nullptr; + } + return false; +} + +bool USpineWidget::SetAttachment(const FString slotName, const FString attachmentName) { + CheckState(); + if (skeleton) { + if (!skeleton->getAttachment(TCHAR_TO_UTF8(*slotName), TCHAR_TO_UTF8(*attachmentName))) return false; + skeleton->setAttachment(TCHAR_TO_UTF8(*slotName), TCHAR_TO_UTF8(*attachmentName)); + return true; + } + return false; +} + +void USpineWidget::UpdateWorldTransform() { + CheckState(); + if (skeleton) { + skeleton->updateWorldTransform(); + } +} + +void USpineWidget::SetToSetupPose() { + CheckState(); + if (skeleton) skeleton->setToSetupPose(); +} + +void USpineWidget::SetBonesToSetupPose() { + CheckState(); + if (skeleton) skeleton->setBonesToSetupPose(); +} + +void USpineWidget::SetSlotsToSetupPose() { + CheckState(); + if (skeleton) skeleton->setSlotsToSetupPose(); +} + +void USpineWidget::SetScaleX(float scaleX) { + CheckState(); + if (skeleton) skeleton->setScaleX(scaleX); +} + +float USpineWidget::GetScaleX() { + CheckState(); + if (skeleton) return skeleton->getScaleX(); + return 1; +} + +void USpineWidget::SetScaleY(float scaleY) { + CheckState(); + if (skeleton) skeleton->setScaleY(scaleY); +} + +float USpineWidget::GetScaleY() { + CheckState(); + if (skeleton) return skeleton->getScaleY(); + return 1; +} + +void USpineWidget::GetBones(TArray &Bones) { + CheckState(); + if (skeleton) { + for (size_t i = 0, n = skeleton->getBones().size(); i < n; i++) { + Bones.Add(skeleton->getBones()[i]->getData().getName().buffer()); + } + } +} + +bool USpineWidget::HasBone(const FString BoneName) { + CheckState(); + if (skeleton) { + return skeleton->getData()->findBone(TCHAR_TO_UTF8(*BoneName)) != nullptr; + } + return false; +} + +void USpineWidget::GetSlots(TArray &Slots) { + CheckState(); + if (skeleton) { + for (size_t i = 0, n = skeleton->getSlots().size(); i < n; i++) { + Slots.Add(skeleton->getSlots()[i]->getData().getName().buffer()); + } + } +} + +bool USpineWidget::HasSlot(const FString SlotName) { + CheckState(); + if (skeleton) { + return skeleton->getData()->findSlot(TCHAR_TO_UTF8(*SlotName)) != nullptr; + } + return false; +} + +void USpineWidget::GetAnimations(TArray &Animations) { + CheckState(); + if (skeleton) { + for (size_t i = 0, n = skeleton->getData()->getAnimations().size(); i < n; i++) { + Animations.Add(skeleton->getData()->getAnimations()[i]->getName().buffer()); + } + } +} + +bool USpineWidget::HasAnimation(FString AnimationName) { + CheckState(); + if (skeleton) { + return skeleton->getData()->findAnimation(TCHAR_TO_UTF8(*AnimationName)) != nullptr; + } + return false; +} + +float USpineWidget::GetAnimationDuration(FString AnimationName) { + CheckState(); + if (skeleton) { + spine::Animation *animation = skeleton->getData()->findAnimation(TCHAR_TO_UTF8(*AnimationName)); + if (animation == nullptr) return 0; + else return animation->getDuration(); + } + return 0; } \ 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 87a36854d..4a8e4a568 100644 --- a/spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Public/SpineWidget.h +++ b/spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Public/SpineWidget.h @@ -39,6 +39,9 @@ class SSpineWidget; +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FSpineWidgetBeforeUpdateWorldTransformDelegate, USpineWidget*, skeleton); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FSpineWidgetAfterUpdateWorldTransformDelegate, USpineWidget*, skeleton); + UCLASS(ClassGroup = (Spine), meta = (BlueprintSpawnableComponent)) class SPINEPLUGIN_API USpineWidget: public UWidget { GENERATED_UCLASS_BODY() @@ -80,6 +83,69 @@ public: UPROPERTY(Category = Spine, EditAnywhere, BlueprintReadOnly) FSlateBrush Brush; + UFUNCTION(BlueprintPure, Category = "Components|Spine|Skeleton") + void GetSkins(TArray &Skins); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + bool SetSkin(const FString SkinName); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + bool HasSkin(const FString SkinName); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + bool SetAttachment(const FString slotName, const FString attachmentName); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + void UpdateWorldTransform(); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + void SetToSetupPose(); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + void SetBonesToSetupPose(); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + void SetSlotsToSetupPose(); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + void SetScaleX(float scaleX); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + float GetScaleX(); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + void SetScaleY(float scaleY); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + float GetScaleY(); + + UFUNCTION(BlueprintPure, Category = "Components|Spine|Skeleton") + void GetBones(TArray &Bones); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + bool HasBone(const FString BoneName); + + UFUNCTION(BlueprintPure, Category = "Components|Spine|Skeleton") + void GetSlots(TArray &Slots); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + bool HasSlot(const FString SlotName); + + UFUNCTION(BlueprintPure, Category = "Components|Spine|Skeleton") + void GetAnimations(TArray &Animations); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + bool HasAnimation(FString AnimationName); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + float GetAnimationDuration(FString AnimationName); + + UPROPERTY(BlueprintAssignable, Category = "Components|Spine|Skeleton") + FSpineWidgetBeforeUpdateWorldTransformDelegate BeforeUpdateWorldTransform; + + UPROPERTY(BlueprintAssignable, Category = "Components|Spine|Skeleton") + FSpineWidgetAfterUpdateWorldTransformDelegate AfterUpdateWorldTransform; + virtual void FinishDestroy() override; protected: @@ -87,12 +153,14 @@ protected: virtual TSharedRef RebuildWidget() override; virtual void CheckState(); - virtual void InternalTick(float DeltaTime); + virtual void InternalTick(float DeltaTime, bool CallDelegates = true, bool Preview = false); virtual void DisposeState(); - TSharedPtr slateWidget; + TSharedPtr slateWidget; + spine::Skeleton* skeleton; USpineAtlasAsset* lastAtlas = nullptr; + spine::Atlas* lastSpineAtlas = nullptr; USpineSkeletonDataAsset* lastData = nullptr; // Need to hold on to the dynamic instances, or the GC will kill us while updating them