Переглянути джерело

Merge branch '3.7' of https://github.com/esotericsoftware/spine-runtimes into 3.7

badlogic 6 роки тому
батько
коміт
b0629e51ec

+ 2 - 0
CHANGELOG.md

@@ -99,6 +99,8 @@
 * Updated to Unreal Engine 4.20 (samples require 4.17+), see the `spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/SpinePlugin.build.cs` file on how to compile in 4.20 with the latest UBT API changes.
 * Updated to Unreal Engine 4.21 (samples require 4.21).
 * **Breaking change**: `UBoneDriverComponent` and `UBoneFollowerComponent` are now `USceneComponent` instead of `UActorComponent`. They either update only themselves, or also the owning `UActor`, depending on whether the new flag `UseComponentTransform` is set. See https://github.com/EsotericSoftware/spine-runtimes/pull/1175
+* Added query methods for slots, bones, skins and animations to `SpineSkeletonComponent` and `UTrackEntry`. These allow you to query these objects by name in both C++ and blueprints.
+* Added `Preview Animation` and `Preview Skin` properties to `SpineSkeletonAnimationComponent`. Enter an animation or skin name to live-preview it in the editor. Enter an empty string to reset the animation or skin.
 
 ## C# ##
 * **Breaking changes**

+ 4 - 1
spine-cpp/README.md

@@ -8,7 +8,7 @@ This spine Runtime may only be used for personal or internal use, typically to e
 
 The spine Runtimes are developed with the intent to be used with data exported from spine. By purchasing spine, `Section 2` of the [spine Software License](https://esotericsoftware.com/files/license.txt) grants the right to create and distribute derivative works of the spine Runtimes.
 
-## spine version
+## Spine version
 
 spine-cpp works with data exported from spine 3.7.xx.
 
@@ -19,6 +19,9 @@ spine-cpp supports all spine features.
 1. Download the spine Runtimes source using [git](https://help.github.com/articles/set-up-git) or by downloading it as a zip via the download button above.
 2. Copy the contents of the `spine-cpp/spine-cpp/src` and `spine-cpp/spine-cpp/include` directories into your project. Be sure your header search is configured to find the contents of the `spine-cpp/spine-cpp/include` directory. Note that the includes use `spine/Xxx.h`, so the `spine` directory cannot be omitted when copying the files.
 
+## Usage
+### [Please see the spine-cpp guide for full documentation](http://esotericsoftware.com/spine-cpp)
+
 ## Extension
 
 Extending spine-cpp requires implementing both the `SpineExtension` class and the TextureLoader class:

+ 2 - 0
spine-cpp/spine-cpp/src/spine/AnimationState.cpp

@@ -171,6 +171,8 @@ void TrackEntry::reset() {
 	_mixingFrom = NULL;
 	_mixingTo = NULL;
 
+	setRendererObject(NULL);
+
 	_timelineMode.clear();
 	_timelineHoldMix.clear();
 	_timelinesRotation.clear();

+ 1 - 1
spine-libgdx/spine-libgdx/pom.xml

@@ -10,7 +10,7 @@
 	<groupId>com.esotericsoftware.spine</groupId>
 	<artifactId>spine-libgdx</artifactId>
 	<packaging>jar</packaging>
-	<version>3.7.83.2-SNAPSHOT</version>
+	<version>3.7.92.1-SNAPSHOT</version>
 
 
 	<name>spine-libgdx</name>

+ 1 - 1
spine-ue4/Plugins/SpinePlugin/Source/SpineEditorPlugin/Private/SpineSkeletonImportFactory.cpp

@@ -81,9 +81,9 @@ UObject* USpineSkeletonAssetFactory::FactoryCreateFile (UClass * InClass, UObjec
 	if (!FFileHelper::LoadFileToArray(rawData, *Filename, 0)) {
 		return nullptr;
 	}
+	asset->SetSkeletonDataFileName(FName(*Filename));
 	asset->SetRawData(rawData);
 	
-	asset->SetSkeletonDataFileName(FName(*Filename));
 	const FString longPackagePath = FPackageName::GetLongPackagePath(asset->GetOutermost()->GetPathName());
 	LoadAtlas(Filename, longPackagePath);
 	return asset;

+ 11 - 0
spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpineSkeletonAnimationComponent.cpp

@@ -96,6 +96,17 @@ void USpineSkeletonAnimationComponent::InternalTick(float DeltaTime, bool CallDe
 	CheckState();
 
 	if (state && bAutoPlaying) {
+		if (lastPreviewAnimation != PreviewAnimation) {
+			if (PreviewAnimation != "") SetAnimation(0, PreviewAnimation, true);
+			else SetEmptyAnimation(0, 0);
+			lastPreviewAnimation = PreviewAnimation;
+		}
+
+		if (lastPreviewSkin != PreviewSkin) {
+			if (PreviewSkin != "") SetSkin(PreviewSkin);
+			else SetSkin("default");
+			lastPreviewSkin = PreviewSkin;
+		}
 		state->update(DeltaTime);
 		state->apply(*skeleton);
 		if (CallDelegates) BeforeUpdateWorldTransform.Broadcast(this);

+ 76 - 7
spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpineSkeletonComponent.cpp

@@ -42,7 +42,7 @@ USpineSkeletonComponent::USpineSkeletonComponent () {
 	bAutoActivate = true;
 }
 
-bool USpineSkeletonComponent::SetSkin(const FString& skinName) {
+bool USpineSkeletonComponent::SetSkin (const FString skinName) {
 	CheckState();
 	if (skeleton) {
 		Skin* skin = skeleton->getData()->findSkin(TCHAR_TO_UTF8(*skinName));
@@ -53,7 +53,24 @@ bool USpineSkeletonComponent::SetSkin(const FString& skinName) {
 	else return false;
 }
 
-bool USpineSkeletonComponent::SetAttachment (const FString& slotName, const FString& attachmentName) {
+void USpineSkeletonComponent::GetSkins (TArray<FString> &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 USpineSkeletonComponent::HasSkin (const FString skinName) {
+	CheckState();
+	if (skeleton) {
+		return skeleton->getData()->findAnimation(TCHAR_TO_UTF8(*skinName)) != nullptr;
+	}
+	return false;
+}
+
+bool USpineSkeletonComponent::SetAttachment (const FString slotName, const FString attachmentName) {
 	CheckState();
 	if (skeleton) {
 		if (!skeleton->getAttachment(TCHAR_TO_UTF8(*slotName), TCHAR_TO_UTF8(*attachmentName))) return false;
@@ -127,7 +144,7 @@ void USpineSkeletonComponent::SetBoneWorldPosition (const FString& BoneName, con
 	}
 }
 
-void USpineSkeletonComponent::UpdateWorldTransform() {
+void USpineSkeletonComponent::UpdateWorldTransform () {
 	CheckState();
 	if (skeleton) {
 		skeleton->updateWorldTransform();
@@ -154,24 +171,24 @@ void USpineSkeletonComponent::SetScaleX (float scaleX) {
 	if (skeleton) skeleton->setScaleX(scaleX);
 }
 
-float USpineSkeletonComponent::GetScaleX() {
+float USpineSkeletonComponent::GetScaleX () {
 	CheckState();
 	if (skeleton) return skeleton->getScaleX();
 	return 1;
 }
 
-void USpineSkeletonComponent::SetScaleY(float scaleY) {
+void USpineSkeletonComponent::SetScaleY (float scaleY) {
 	CheckState();
 	if (skeleton) skeleton->setScaleY(scaleY);
 }
 
-float USpineSkeletonComponent::GetScaleY() {
+float USpineSkeletonComponent::GetScaleY () {
 	CheckState();
 	if (skeleton) return skeleton->getScaleY();
 	return 1;
 }
 
-void USpineSkeletonComponent::GetBones(TArray<FString> &Bones) {
+void USpineSkeletonComponent::GetBones (TArray<FString> &Bones) {
 	CheckState();
 	if (skeleton) {
 		for (size_t i = 0, n = skeleton->getBones().size(); i < n; i++) {
@@ -180,6 +197,58 @@ void USpineSkeletonComponent::GetBones(TArray<FString> &Bones) {
 	}
 }
 
+bool USpineSkeletonComponent::HasBone (const FString BoneName) {
+	CheckState();
+	if (skeleton) {
+		return skeleton->getData()->findBone(TCHAR_TO_UTF8(*BoneName)) != nullptr;
+	}
+	return false;
+}
+
+void USpineSkeletonComponent::GetSlots (TArray<FString> &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 USpineSkeletonComponent::HasSlot (const FString SlotName) {
+	CheckState();
+	if (skeleton) {
+		return skeleton->getData()->findSlot(TCHAR_TO_UTF8(*SlotName)) != nullptr;
+	}
+	return false;
+}
+
+void USpineSkeletonComponent::GetAnimations(TArray<FString> &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 USpineSkeletonComponent::HasAnimation(FString AnimationName) {
+	CheckState();
+	if (skeleton) {
+		return skeleton->getData()->findAnimation(TCHAR_TO_UTF8(*AnimationName)) != nullptr;
+	}
+	return false;
+}
+
+float USpineSkeletonComponent::GetAnimationDuration(FString AnimationName) {
+	CheckState();
+	if (skeleton) {
+		Animation *animation = skeleton->getData()->findAnimation(TCHAR_TO_UTF8(*AnimationName));
+		if (animation == nullptr) return 0;
+		else return animation->getDuration();
+	}
+	return 0;
+}
+
 void USpineSkeletonComponent::BeginPlay() {
 	Super::BeginPlay();
 }

+ 80 - 0
spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpineSkeletonDataAsset.cpp

@@ -76,6 +76,7 @@ void USpineSkeletonDataAsset::Serialize (FArchive& Ar) {
 	Super::Serialize(Ar);
 	if (Ar.IsLoading() && Ar.UE4Ver() < VER_UE4_ASSET_IMPORT_DATA_AS_JSON && !importData)
 		importData = NewObject<UAssetImportData>(this, TEXT("AssetImportData"));
+	LoadInfo();
 }
 
 #endif
@@ -92,13 +93,92 @@ void USpineSkeletonDataAsset::BeginDestroy () {
 	Super::BeginDestroy();
 }
 
+class SP_API NullAttachmentLoader : public AttachmentLoader {
+public:
+
+	virtual RegionAttachment* newRegionAttachment(Skin& skin, const String& name, const String& path) {
+		return new(__FILE__, __LINE__) RegionAttachment(name);
+	}
+
+	virtual MeshAttachment* newMeshAttachment(Skin& skin, const String& name, const String& path) {
+		return new(__FILE__, __LINE__) MeshAttachment(name);
+	}
+
+	virtual BoundingBoxAttachment* newBoundingBoxAttachment(Skin& skin, const String& name) {
+		return new(__FILE__, __LINE__) BoundingBoxAttachment(name);
+	}
+
+	virtual PathAttachment* newPathAttachment(Skin& skin, const String& name) {
+		return new(__FILE__, __LINE__) PathAttachment(name);
+	}
+
+	virtual PointAttachment* newPointAttachment(Skin& skin, const String& name) {
+		return new(__FILE__, __LINE__) PointAttachment(name);
+	}
+
+	virtual ClippingAttachment* newClippingAttachment(Skin& skin, const String& name) {
+		return new(__FILE__, __LINE__) ClippingAttachment(name);
+	}
+
+	virtual void configureAttachment(Attachment* attachment) {
+
+	}
+};
+
+void USpineSkeletonDataAsset::LoadInfo() {
+#if WITH_EDITORONLY_DATA
+	int dataLen = rawData.Num();
+	if (dataLen == 0) return;
+	NullAttachmentLoader loader;
+	SkeletonData* skeletonData = nullptr;
+	if (skeletonDataFileName.GetPlainNameString().Contains(TEXT(".json"))) {
+		SkeletonJson* json = new (__FILE__, __LINE__) SkeletonJson(&loader);
+		skeletonData = json->readSkeletonData((const char*)rawData.GetData());
+		if (!skeletonData) {
+			FMessageDialog::Debugf(FText::FromString(UTF8_TO_TCHAR(json->getError().buffer())));
+			UE_LOG(SpineLog, Error, TEXT("Couldn't load skeleton data and atlas: %s"), UTF8_TO_TCHAR(json->getError().buffer()));
+		}
+		delete json;
+	} else {
+		SkeletonBinary* binary = new (__FILE__, __LINE__) SkeletonBinary(&loader);
+		skeletonData = binary->readSkeletonData((const unsigned char*)rawData.GetData(), (int)rawData.Num());
+		if (!skeletonData) {
+			FMessageDialog::Debugf(FText::FromString(UTF8_TO_TCHAR(binary->getError().buffer())));
+			UE_LOG(SpineLog, Error, TEXT("Couldn't load skeleton data and atlas: %s"), UTF8_TO_TCHAR(binary->getError().buffer()));
+		}
+		delete binary;
+	}
+	if (skeletonData) {
+		Bones.Empty();
+		for (int i = 0; i < skeletonData->getBones().size(); i++)
+			Bones.Add(UTF8_TO_TCHAR(skeletonData->getBones()[i]->getName().buffer()));
+		Skins.Empty();
+		for (int i = 0; i < skeletonData->getSkins().size(); i++)
+			Skins.Add(UTF8_TO_TCHAR(skeletonData->getSkins()[i]->getName().buffer()));
+		Slots.Empty();
+		for (int i = 0; i < skeletonData->getSlots().size(); i++)
+			Slots.Add(UTF8_TO_TCHAR(skeletonData->getSlots()[i]->getName().buffer()));
+		Animations.Empty();
+		for (int i = 0; i < skeletonData->getAnimations().size(); i++)
+			Animations.Add(UTF8_TO_TCHAR(skeletonData->getAnimations()[i]->getName().buffer()));
+		Events.Empty();
+		for (int i = 0; i < skeletonData->getEvents().size(); i++)
+			Events.Add(UTF8_TO_TCHAR(skeletonData->getEvents()[i]->getName().buffer()));
+		delete skeletonData;
+	}
+#endif
+}
+
 void USpineSkeletonDataAsset::SetRawData(TArray<uint8> &Data) {
 	this->rawData.Empty();
 	this->rawData.Append(Data);
+
 	if (skeletonData) {
 		delete skeletonData;
 		skeletonData = nullptr;
 	}
+
+	LoadInfo();
 }
 
 SkeletonData* USpineSkeletonDataAsset::GetSkeletonData (Atlas* Atlas) {

+ 88 - 82
spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpineSkeletonRendererComponent.cpp

@@ -76,95 +76,101 @@ void USpineSkeletonRendererComponent::TickComponent (float DeltaTime, ELevelTick
 		UClass* skeletonClass = USpineSkeletonComponent::StaticClass();
 		USpineSkeletonComponent* skeleton = Cast<USpineSkeletonComponent>(owner->GetComponentByClass(skeletonClass));
 		
-		if (skeleton && !skeleton->IsBeingDestroyed() && skeleton->GetSkeleton() && skeleton->Atlas) {
-			skeleton->GetSkeleton()->getColor().set(Color.R, Color.G, Color.B, Color.A);
-
-			if (atlasNormalBlendMaterials.Num() != skeleton->Atlas->atlasPages.Num()) {
-				atlasNormalBlendMaterials.SetNum(0);
-				pageToNormalBlendMaterial.Empty();
-				atlasAdditiveBlendMaterials.SetNum(0);
-				pageToAdditiveBlendMaterial.Empty();
-				atlasMultiplyBlendMaterials.SetNum(0);
-				pageToMultiplyBlendMaterial.Empty();
-				atlasScreenBlendMaterials.SetNum(0);
-				pageToScreenBlendMaterial.Empty();
-								
-				for (int i = 0; i < skeleton->Atlas->atlasPages.Num(); i++) {
-					AtlasPage* currPage = skeleton->Atlas->GetAtlas()->getPages()[i];
-					
-					UMaterialInstanceDynamic* material = UMaterialInstanceDynamic::Create(NormalBlendMaterial, owner);
-					material->SetTextureParameterValue(TextureParameterName, skeleton->Atlas->atlasPages[i]);
-					atlasNormalBlendMaterials.Add(material);
-					pageToNormalBlendMaterial.Add(currPage, material);
-					
-					material = UMaterialInstanceDynamic::Create(AdditiveBlendMaterial, owner);
-					material->SetTextureParameterValue(TextureParameterName, skeleton->Atlas->atlasPages[i]);
-					atlasAdditiveBlendMaterials.Add(material);
-					pageToAdditiveBlendMaterial.Add(currPage, material);
-					
-					material = UMaterialInstanceDynamic::Create(MultiplyBlendMaterial, owner);
-					material->SetTextureParameterValue(TextureParameterName, skeleton->Atlas->atlasPages[i]);
-					atlasMultiplyBlendMaterials.Add(material);
-					pageToMultiplyBlendMaterial.Add(currPage, material);
-					
-					material = UMaterialInstanceDynamic::Create(ScreenBlendMaterial, owner);
-					material->SetTextureParameterValue(TextureParameterName, skeleton->Atlas->atlasPages[i]);
-					atlasScreenBlendMaterials.Add(material);
-					pageToScreenBlendMaterial.Add(currPage, material);					
+		UpdateRenderer(skeleton);
+	}
+}
+
+void USpineSkeletonRendererComponent::UpdateRenderer(USpineSkeletonComponent* skeleton)
+{
+	if (skeleton && !skeleton->IsBeingDestroyed() && skeleton->GetSkeleton() && skeleton->Atlas) {
+		skeleton->GetSkeleton()->getColor().set(Color.R, Color.G, Color.B, Color.A);
+
+		if (atlasNormalBlendMaterials.Num() != skeleton->Atlas->atlasPages.Num()) {
+			atlasNormalBlendMaterials.SetNum(0);
+			pageToNormalBlendMaterial.Empty();
+			atlasAdditiveBlendMaterials.SetNum(0);
+			pageToAdditiveBlendMaterial.Empty();
+			atlasMultiplyBlendMaterials.SetNum(0);
+			pageToMultiplyBlendMaterial.Empty();
+			atlasScreenBlendMaterials.SetNum(0);
+			pageToScreenBlendMaterial.Empty();
+
+			for (int i = 0; i < skeleton->Atlas->atlasPages.Num(); i++) {
+				AtlasPage* currPage = skeleton->Atlas->GetAtlas()->getPages()[i];
+
+				UMaterialInstanceDynamic* material = UMaterialInstanceDynamic::Create(NormalBlendMaterial, this);
+				material->SetTextureParameterValue(TextureParameterName, skeleton->Atlas->atlasPages[i]);
+				atlasNormalBlendMaterials.Add(material);
+				pageToNormalBlendMaterial.Add(currPage, material);
+
+				material = UMaterialInstanceDynamic::Create(AdditiveBlendMaterial, this);
+				material->SetTextureParameterValue(TextureParameterName, skeleton->Atlas->atlasPages[i]);
+				atlasAdditiveBlendMaterials.Add(material);
+				pageToAdditiveBlendMaterial.Add(currPage, material);
+
+				material = UMaterialInstanceDynamic::Create(MultiplyBlendMaterial, this);
+				material->SetTextureParameterValue(TextureParameterName, skeleton->Atlas->atlasPages[i]);
+				atlasMultiplyBlendMaterials.Add(material);
+				pageToMultiplyBlendMaterial.Add(currPage, material);
+
+				material = UMaterialInstanceDynamic::Create(ScreenBlendMaterial, this);
+				material->SetTextureParameterValue(TextureParameterName, skeleton->Atlas->atlasPages[i]);
+				atlasScreenBlendMaterials.Add(material);
+				pageToScreenBlendMaterial.Add(currPage, material);
+			}
+		}
+		else {
+			pageToNormalBlendMaterial.Empty();
+			pageToAdditiveBlendMaterial.Empty();
+			pageToMultiplyBlendMaterial.Empty();
+			pageToScreenBlendMaterial.Empty();
+
+			for (int i = 0; i < skeleton->Atlas->atlasPages.Num(); i++) {
+				AtlasPage* currPage = skeleton->Atlas->GetAtlas()->getPages()[i];
+
+				UTexture2D* texture = skeleton->Atlas->atlasPages[i];
+				UTexture* oldTexture = nullptr;
+
+				UMaterialInstanceDynamic* current = atlasNormalBlendMaterials[i];
+				if (!current || !current->GetTextureParameterValue(TextureParameterName, oldTexture) || oldTexture != texture) {
+					UMaterialInstanceDynamic* material = UMaterialInstanceDynamic::Create(NormalBlendMaterial, this);
+					material->SetTextureParameterValue(TextureParameterName, texture);
+					atlasNormalBlendMaterials[i] = material;
+				}
+				pageToNormalBlendMaterial.Add(currPage, atlasNormalBlendMaterials[i]);
+
+				current = atlasAdditiveBlendMaterials[i];
+				if (!current || !current->GetTextureParameterValue(TextureParameterName, oldTexture) || oldTexture != texture) {
+					UMaterialInstanceDynamic* material = UMaterialInstanceDynamic::Create(AdditiveBlendMaterial, this);
+					material->SetTextureParameterValue(TextureParameterName, texture);
+					atlasAdditiveBlendMaterials[i] = material;
+				}
+				pageToAdditiveBlendMaterial.Add(currPage, atlasAdditiveBlendMaterials[i]);
+
+				current = atlasMultiplyBlendMaterials[i];
+				if (!current || !current->GetTextureParameterValue(TextureParameterName, oldTexture) || oldTexture != texture) {
+					UMaterialInstanceDynamic* material = UMaterialInstanceDynamic::Create(MultiplyBlendMaterial, this);
+					material->SetTextureParameterValue(TextureParameterName, texture);
+					atlasMultiplyBlendMaterials[i] = material;
 				}
-			} else {
-				pageToNormalBlendMaterial.Empty();
-				pageToAdditiveBlendMaterial.Empty();
-				pageToMultiplyBlendMaterial.Empty();
-				pageToScreenBlendMaterial.Empty();
-								
-				for (int i = 0; i < skeleton->Atlas->atlasPages.Num(); i++) {
-					AtlasPage* currPage = skeleton->Atlas->GetAtlas()->getPages()[i];
-
-					UTexture2D* texture = skeleton->Atlas->atlasPages[i];
-					UTexture* oldTexture = nullptr;
-					
-					UMaterialInstanceDynamic* current = atlasNormalBlendMaterials[i];
-					if(!current || !current->GetTextureParameterValue(TextureParameterName, oldTexture) || oldTexture != texture) {
-						UMaterialInstanceDynamic* material = UMaterialInstanceDynamic::Create(NormalBlendMaterial, owner);
-						material->SetTextureParameterValue(TextureParameterName, texture);
-						atlasNormalBlendMaterials[i] = material;
-					}
-					pageToNormalBlendMaterial.Add(currPage, atlasNormalBlendMaterials[i]);
-					
-					current = atlasAdditiveBlendMaterials[i];
-					if(!current || !current->GetTextureParameterValue(TextureParameterName, oldTexture) || oldTexture != texture) {
-						UMaterialInstanceDynamic* material = UMaterialInstanceDynamic::Create(AdditiveBlendMaterial, owner);
-						material->SetTextureParameterValue(TextureParameterName, texture);
-						atlasAdditiveBlendMaterials[i] = material;
-					}
-					pageToAdditiveBlendMaterial.Add(currPage, atlasAdditiveBlendMaterials[i]);
-					
-					current = atlasMultiplyBlendMaterials[i];
-					if(!current || !current->GetTextureParameterValue(TextureParameterName, oldTexture) || oldTexture != texture) {
-						UMaterialInstanceDynamic* material = UMaterialInstanceDynamic::Create(MultiplyBlendMaterial, owner);
-						material->SetTextureParameterValue(TextureParameterName, texture);
-						atlasMultiplyBlendMaterials[i] = material;
-					}
-					pageToMultiplyBlendMaterial.Add(currPage, atlasMultiplyBlendMaterials[i]);
-					
-					current = atlasScreenBlendMaterials[i];
-					if(!current || !current->GetTextureParameterValue(TextureParameterName, oldTexture) || oldTexture != texture) {
-						UMaterialInstanceDynamic* material = UMaterialInstanceDynamic::Create(ScreenBlendMaterial, owner);
-						material->SetTextureParameterValue(TextureParameterName, texture);
-						atlasScreenBlendMaterials[i] = material;
-					}
-					pageToScreenBlendMaterial.Add(currPage, atlasScreenBlendMaterials[i]);
+				pageToMultiplyBlendMaterial.Add(currPage, atlasMultiplyBlendMaterials[i]);
+
+				current = atlasScreenBlendMaterials[i];
+				if (!current || !current->GetTextureParameterValue(TextureParameterName, oldTexture) || oldTexture != texture) {
+					UMaterialInstanceDynamic* material = UMaterialInstanceDynamic::Create(ScreenBlendMaterial, this);
+					material->SetTextureParameterValue(TextureParameterName, texture);
+					atlasScreenBlendMaterials[i] = material;
 				}
+				pageToScreenBlendMaterial.Add(currPage, atlasScreenBlendMaterials[i]);
 			}
-			UpdateMesh(skeleton->GetSkeleton());
-		} else {
-			ClearAllMeshSections();
 		}
+		UpdateMesh(skeleton->GetSkeleton());
+	}
+	else {
+		ClearAllMeshSections();
 	}
 }
 
-
 void USpineSkeletonRendererComponent::Flush (int &Idx, TArray<FVector> &Vertices, TArray<int32> &Indices, TArray<FVector2D> &Uvs, TArray<FColor> &Colors, TArray<FVector>& Colors2, UMaterialInstanceDynamic* Material) {
 	if (Vertices.Num() == 0) return;
 	SetMaterial(Idx, Material);

+ 19 - 1
spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Public/SpineSkeletonAnimationComponent.h

@@ -90,7 +90,7 @@ public:
 	UFUNCTION(BlueprintCallable, Category="Components|Spine|TrackEntry")
 	bool GetLoop () { return entry ? entry->getLoop() : false; }
 	UFUNCTION(BlueprintCallable, Category="Components|Spine|TrackEntry")
-		void SetLoop(bool loop) { if (entry) entry->setLoop(loop); }
+	void SetLoop(bool loop) { if (entry) entry->setLoop(loop); }
 	
 	UFUNCTION(BlueprintCallable, Category="Components|Spine|TrackEntry")
 	float GetEventThreshold () { return entry ? entry->getEventThreshold() : 0; }
@@ -157,6 +157,15 @@ public:
 	UFUNCTION(BlueprintCallable, Category="Components|Spine|TrackEntry")
 	void SetMixDuration(float mixDuration) { if (entry) entry->setMixDuration(mixDuration); }
 
+	UFUNCTION(BlueprintCallable, Category = "Components|Spine|TrackEntry")
+	FString getAnimationName() { return entry ? entry->getAnimation()->getName().buffer() : ""; }
+
+	UFUNCTION(BlueprintCallable, Category = "Components|Spine|TrackEntry")
+	float getAnimationDuration() { return entry ? entry->getAnimation()->getDuration(): 0; }
+
+	UFUNCTION(BlueprintCallable, Category = "Components|Spine|TrackEntry")
+	float isValidAnimation() { return entry != nullptr; }
+
 	UPROPERTY(BlueprintAssignable, Category = "Components|Spine|TrackEntry")
 	FSpineAnimationStartDelegate AnimationStart;
 
@@ -250,6 +259,12 @@ public:
 
 	UPROPERTY(BlueprintAssignable, Category="Components|Spine|Animation")
 	FSpineAnimationDisposeDelegate AnimationDispose;
+
+	UPROPERTY(Transient, EditAnywhere, Category=Spine)
+	FString PreviewAnimation;
+
+	UPROPERTY(Transient, EditAnywhere, Category=Spine)
+	FString PreviewSkin;
 	
 	// used in C event callback. Needs to be public as we can't call
 	// protected methods from plain old C function.
@@ -270,4 +285,7 @@ private:
 	/* If the animation should update automatically. */
 	UPROPERTY()
 	bool bAutoPlaying;
+
+	FString lastPreviewAnimation;
+	FString lastPreviewSkin;
 };

+ 26 - 2
spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Public/SpineSkeletonComponent.h

@@ -53,12 +53,18 @@ public:
 	USpineSkeletonDataAsset* SkeletonData;
 	
 	spine::Skeleton* GetSkeleton () { return skeleton; };
+
+	UFUNCTION(BlueprintPure, Category = "Components|Spine|Skeleton")
+	void GetSkins(TArray<FString> &Skins);
 	
 	UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton")
-	bool SetSkin (const FString& SkinName);
+	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);
+	bool SetAttachment (const FString slotName, const FString attachmentName);
 	
 	UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton")
 	FTransform GetBoneWorldTransform (const FString& BoneName);
@@ -92,6 +98,24 @@ public:
 
 	UFUNCTION(BlueprintPure, Category = "Components|Spine|Skeleton")
 	void GetBones(TArray<FString> &Bones);
+
+	UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton")
+	bool HasBone(const FString BoneName);
+
+	UFUNCTION(BlueprintPure, Category = "Components|Spine|Skeleton")
+	void GetSlots(TArray<FString> &Slots);
+
+	UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton")
+	bool HasSlot(const FString SlotName);
+
+	UFUNCTION(BlueprintPure, Category = "Components|Spine|Skeleton")
+	void GetAnimations(TArray<FString> &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")
 	FSpineBeforeUpdateWorldTransformDelegate BeforeUpdateWorldTransform;

+ 18 - 1
spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Public/SpineSkeletonDataAsset.h

@@ -46,7 +46,7 @@ public:
 	FString To;
 	
 	UPROPERTY(EditAnywhere, BlueprintReadWrite)
-		float Mix = 0;
+	float Mix = 0;
 };
 
 UCLASS(BlueprintType, ClassGroup=(Spine))
@@ -70,6 +70,21 @@ public:
 
 	UPROPERTY(EditAnywhere, BlueprintReadWrite)
 	TArray<FSpineAnimationStateMixData> MixData;
+
+	UPROPERTY(Transient, VisibleAnywhere)
+	TArray<FString> Bones;
+
+	UPROPERTY(Transient, VisibleAnywhere)
+	TArray<FString> Slots;
+
+	UPROPERTY(Transient, VisibleAnywhere)
+	TArray<FString> Skins;
+
+	UPROPERTY(Transient, VisibleAnywhere)
+	TArray<FString> Animations;
+
+	UPROPERTY(Transient, VisibleAnywhere)
+	TArray<FString> Events;
 	
 protected:
 	UPROPERTY()
@@ -94,4 +109,6 @@ protected:
 	virtual void GetAssetRegistryTags(TArray<FAssetRegistryTag>& OutTags) const override;
 	virtual void Serialize (FArchive& Ar) override;
 #endif
+
+	void LoadInfo();
 };

+ 3 - 0
spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Public/SpineSkeletonRendererComponent.h

@@ -47,6 +47,9 @@ public:
 		
 	virtual void TickComponent (float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
 
+	/* Updates this skeleton renderer using the provided skeleton animation component. */
+	void UpdateRenderer(USpineSkeletonComponent* Skeleton);
+
 	// Material Instance parents
 	UPROPERTY(Category = Spine, EditAnywhere, BlueprintReadOnly)
 	UMaterialInterface* NormalBlendMaterial;