Merge pull request #348 from Fenrisul/master

Spine Unity Runtime Update
This commit is contained in:
Fenrisul 2015-01-23 00:30:09 -08:00
commit 47452a0c54
73 changed files with 4259 additions and 446 deletions

View File

@ -32,15 +32,15 @@ using System;
namespace Spine {
public class AtlasAttachmentLoader : AttachmentLoader {
private Atlas atlas;
private Atlas[] atlasArray;
public AtlasAttachmentLoader (Atlas atlas) {
if (atlas == null) throw new ArgumentNullException("atlas cannot be null.");
this.atlas = atlas;
public AtlasAttachmentLoader (params Atlas[] atlasArray) {
if (atlasArray == null) throw new ArgumentNullException("atlas array cannot be null.");
this.atlasArray = atlasArray;
}
public RegionAttachment NewRegionAttachment (Skin skin, String name, String path) {
AtlasRegion region = atlas.FindRegion(path);
AtlasRegion region = FindRegion(path);
if (region == null) throw new Exception("Region not found in atlas: " + path + " (region attachment: " + name + ")");
RegionAttachment attachment = new RegionAttachment(name);
attachment.RendererObject = region;
@ -55,7 +55,7 @@ namespace Spine {
}
public MeshAttachment NewMeshAttachment (Skin skin, String name, String path) {
AtlasRegion region = atlas.FindRegion(path);
AtlasRegion region = FindRegion(path);
if (region == null) throw new Exception("Region not found in atlas: " + path + " (mesh attachment: " + name + ")");
MeshAttachment attachment = new MeshAttachment(name);
attachment.RendererObject = region;
@ -74,7 +74,7 @@ namespace Spine {
}
public SkinnedMeshAttachment NewSkinnedMeshAttachment (Skin skin, String name, String path) {
AtlasRegion region = atlas.FindRegion(path);
AtlasRegion region = FindRegion(path);
if (region == null) throw new Exception("Region not found in atlas: " + path + " (skinned mesh attachment: " + name + ")");
SkinnedMeshAttachment attachment = new SkinnedMeshAttachment(name);
attachment.RendererObject = region;
@ -95,5 +95,17 @@ namespace Spine {
public BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, String name) {
return new BoundingBoxAttachment(name);
}
public AtlasRegion FindRegion(string name) {
AtlasRegion region;
for (int i = 0; i < atlasArray.Length; i++) {
region = atlasArray[i].FindRegion(name);
if (region != null)
return region;
}
return null;
}
}
}

View File

@ -42,8 +42,8 @@ namespace Spine {
private AttachmentLoader attachmentLoader;
public float Scale { get; set; }
public SkeletonJson (Atlas atlas)
: this(new AtlasAttachmentLoader(atlas)) {
public SkeletonJson (params Atlas[] atlasArray)
: this(new AtlasAttachmentLoader(atlasArray)) {
}
public SkeletonJson (AttachmentLoader attachmentLoader) {

View File

@ -0,0 +1,4 @@
fileFormatVersion: 2
guid: 63212ccaf5776bd489cba58fb67a2233
DefaultImporter:
userData:

Binary file not shown.

View File

@ -0,0 +1,4 @@
fileFormatVersion: 2
guid: c5673b83016f67a4c99772dfb7b3c437
DefaultImporter:
userData:

View File

@ -71,11 +71,17 @@ public class BasicPlatformerController : MonoBehaviour {
#if UNITY_4_5
[Header("Animation")]
#endif
[SpineAnimation(dataField: "skeletonAnimation")]
public string walkName = "Walk";
[SpineAnimation(dataField: "skeletonAnimation")]
public string runName = "Run";
[SpineAnimation(dataField: "skeletonAnimation")]
public string idleName = "Idle";
[SpineAnimation(dataField: "skeletonAnimation")]
public string jumpName = "Jump";
[SpineAnimation(dataField: "skeletonAnimation")]
public string fallName = "Fall";
[SpineAnimation(dataField: "skeletonAnimation")]
public string crouchName = "Crouch";
#if UNITY_4_5

View File

@ -0,0 +1,51 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.1
*
* Copyright (c) 2013, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable and
* non-transferable license to install, execute and perform the Spine Runtimes
* Software (the "Software") solely for internal use. Without the written
* permission of Esoteric Software (typically granted by licensing Spine), you
* may not (a) modify, translate, adapt or otherwise create derivative works,
* improvements of the Software or develop new applications using the Software
* or (b) remove, delete, alter or obscure any trademarks or any copyright,
* trademark, patent or other intellectual property or proprietary rights
* notices on or in the Software, including any copy thereof. Redistributions
* in binary or source form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
/*****************************************************************************
* Basic Platformer Controller created by Mitch Thompson
* Full irrevocable rights and permissions granted to Esoteric Software
*****************************************************************************/
using UnityEngine;
using System.Collections;
public class Chimera : MonoBehaviour {
public SkeletonDataAsset skeletonDataSource;
[SpineAttachment(currentSkinOnly: false, returnAttachmentPath: true, dataField: "skeletonDataSource")]
public string attachmentPath;
[SpineSlot]
public string targetSlot;
void Start() {
GetComponent<SkeletonRenderer>().skeleton.FindSlot(targetSlot).Attachment = SpineAttachment.GetAttachment(attachmentPath, skeletonDataSource);
}
}

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5053fe97a7657b5418b0c307b7338b0c
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:

View File

@ -0,0 +1,66 @@
using UnityEngine;
using System.Collections;
public class DynamicSpineBone : MonoBehaviour {
public Transform speedReference;
[SpineBone]
public string boneName;
[Range(-90, 90)]
public float minRotation = -45;
[Range(-90, 90)]
public float maxRotation = 45;
[Range(-2000, 2000)]
public float rotationFactor = 300;
[Range(5, 30)]
public float returnSpeed = 10;
[Range(100, 1000)]
public float boneSpeed = 300;
public float returnThreshhold = 0.01f;
public bool useAcceleration;
SkeletonAnimation skeletonAnimation;
float goalRotation;
Spine.Bone bone;
Vector3 velocity;
Vector3 acceleration;
Vector3 lastPosition;
void Start() {
if (speedReference == null)
speedReference = transform;
skeletonAnimation = GetComponent<SkeletonAnimation>();
bone = SpineBone.GetBone(boneName, skeletonAnimation);
skeletonAnimation.UpdateLocal += UpdateLocal;
lastPosition = speedReference.position;
}
void FixedUpdate() {
acceleration = (speedReference.position - lastPosition) - velocity;
velocity = speedReference.position - lastPosition;
lastPosition = speedReference.position;
}
void UpdateLocal(SkeletonAnimation animation) {
Vector3 vec = useAcceleration ? acceleration : velocity;
if (Mathf.Abs(vec.x) < returnThreshhold)
goalRotation = Mathf.Lerp(goalRotation, 0, returnSpeed * Time.deltaTime);
else
goalRotation += vec.x * rotationFactor * Time.deltaTime * (bone.WorldFlipX ? -1 : 1);
goalRotation = Mathf.Clamp(goalRotation, minRotation, maxRotation);
bone.Rotation = Mathf.Lerp(bone.Rotation, bone.Rotation + goalRotation, boneSpeed * Time.deltaTime);
}
}

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7dae3f4db9a24bf4abe2059526bfd689
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:

View File

@ -0,0 +1,85 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.1
*
* Copyright (c) 2013, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable and
* non-transferable license to install, execute and perform the Spine Runtimes
* Software (the "Software") solely for internal use. Without the written
* permission of Esoteric Software (typically granted by licensing Spine), you
* may not (a) modify, translate, adapt or otherwise create derivative works,
* improvements of the Software or develop new applications using the Software
* or (b) remove, delete, alter or obscure any trademarks or any copyright,
* trademark, patent or other intellectual property or proprietary rights
* notices on or in the Software, including any copy thereof. Redistributions
* in binary or source form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
/*****************************************************************************
* FootSoldierExample created by Mitch Thompson
* Full irrevocable rights and permissions granted to Esoteric Software
*****************************************************************************/
using UnityEngine;
using System.Collections;
public class FootSoldierExample : MonoBehaviour {
[SpineAnimation("Idle")]
public string idleAnimation;
[SpineAnimation]
public string attackAnimation;
[SpineSlot]
public string eyesSlot;
[SpineAttachment(currentSkinOnly: true, slotField: "eyesSlot")]
public string eyesOpenAttachment;
[SpineAttachment(currentSkinOnly: true, slotField: "eyesSlot")]
public string blinkAttachment;
[Range(0, 0.2f)]
public float blinkDuration = 0.05f;
private SkeletonAnimation skeletonAnimation;
void Awake() {
skeletonAnimation = GetComponent<SkeletonAnimation>();
}
void Start() {
skeletonAnimation.state.SetAnimation(0, idleAnimation, true);
StartCoroutine("Blink");
}
void Update() {
if (Input.GetKey(KeyCode.Space)) {
if (skeletonAnimation.state.GetCurrent(0).Animation.Name != attackAnimation) {
skeletonAnimation.state.SetAnimation(0, attackAnimation, false);
skeletonAnimation.state.AddAnimation(0, idleAnimation, true, 0);
}
}
}
IEnumerator Blink() {
while (true) {
yield return new WaitForSeconds(Random.Range(0.25f, 3f));
skeletonAnimation.skeleton.SetAttachment(eyesSlot, blinkAttachment);
yield return new WaitForSeconds(blinkDuration);
skeletonAnimation.skeleton.SetAttachment(eyesSlot, eyesOpenAttachment);
}
}
}

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3c826b50b0cfee343be3bdbbf59d0f7c
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:

View File

@ -1,4 +1,38 @@
using UnityEngine;
/******************************************************************************
* Spine Runtimes Software License
* Version 2.1
*
* Copyright (c) 2013, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable and
* non-transferable license to install, execute and perform the Spine Runtimes
* Software (the "Software") solely for internal use. Without the written
* permission of Esoteric Software (typically granted by licensing Spine), you
* may not (a) modify, translate, adapt or otherwise create derivative works,
* improvements of the Software or develop new applications using the Software
* or (b) remove, delete, alter or obscure any trademarks or any copyright,
* trademark, patent or other intellectual property or proprietary rights
* notices on or in the Software, including any copy thereof. Redistributions
* in binary or source form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
/*****************************************************************************
* SpineboyController created by Mitch Thompson
* Full irrevocable rights and permissions granted to Esoteric Software
*****************************************************************************/
using UnityEngine;
using System.Collections;
[RequireComponent(typeof(SkeletonAnimation), typeof(Rigidbody2D))]

View File

@ -0,0 +1,5 @@
fileFormatVersion: 2
guid: 7a709e690449c1b40b198bb86f707c41
folderAsset: yes
DefaultImporter:
userData:

View File

@ -0,0 +1,5 @@
fileFormatVersion: 2
guid: e90f1603e5c99c745a28d42e61afe5b2
folderAsset: yes
DefaultImporter:
userData:

View File

@ -0,0 +1,34 @@
Equipment.png
size: 512,128
format: RGBA8888
filter: Linear,Linear
repeat: none
Equipment/shield1
rotate: true
xy: 220, 33
size: 71, 118
orig: 256, 256
offset: 92, 69
index: -1
Equipment/shield2
rotate: true
xy: 340, 22
size: 82, 111
orig: 256, 256
offset: 87, 72
index: -1
Equipment/sword1
rotate: false
xy: 2, 2
size: 161, 31
orig: 512, 256
offset: 217, 112
index: -1
Equipment/sword4
rotate: false
xy: 2, 35
size: 216, 69
orig: 512, 256
offset: 200, 94
index: -1

View File

@ -0,0 +1,4 @@
fileFormatVersion: 2
guid: 4f0639ff8bc42314d8d62ee0f7ba541f
TextScriptImporter:
userData:

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View File

@ -0,0 +1,47 @@
fileFormatVersion: 2
guid: ddb89f63d0296cf4f8572b0448bb6b30
TextureImporter:
fileIDToRecycleName: {}
serializedVersion: 2
mipmaps:
mipMapMode: 0
enableMipMap: 0
linearTexture: 0
correctGamma: 0
fadeOut: 0
borderMipMap: 0
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: .25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 0
seamlessCubemap: 0
textureFormat: -3
maxTextureSize: 2048
textureSettings:
filterMode: -1
aniso: -1
mipBias: -1
wrapMode: -1
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: .5, y: .5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaIsTransparency: 0
textureType: -1
buildTargetSettings: []
spriteSheet:
sprites: []
spritePackingTag:
userData:

View File

@ -0,0 +1,4 @@
fileFormatVersion: 2
guid: c574489dd067c2b4cb4dc165a4c410cc
NativeFormatImporter:
userData:

View File

@ -0,0 +1,4 @@
fileFormatVersion: 2
guid: e6e388faa521c96449984cfa4b60d74e
NativeFormatImporter:
userData:

View File

@ -0,0 +1,90 @@
FS_White.png
size: 256,256
format: RGBA8888
filter: Linear,Linear
repeat: none
White/arm
rotate: false
xy: 143, 156
size: 111, 98
orig: 111, 98
offset: 0, 0
index: -1
White/arm 2
rotate: false
xy: 95, 36
size: 46, 79
orig: 46, 79
offset: 0, 0
index: -1
White/body
rotate: false
xy: 2, 12
size: 91, 103
orig: 91, 103
offset: 0, 0
index: -1
White/eyes
rotate: true
xy: 195, 87
size: 67, 31
orig: 67, 31
offset: 0, 0
index: -1
White/eyes blink
rotate: true
xy: 228, 87
size: 67, 22
orig: 67, 22
offset: 0, 0
index: -1
White/feet
rotate: false
xy: 95, 2
size: 50, 32
orig: 50, 32
offset: 0, 0
index: -1
White/feet 2
rotate: false
xy: 193, 58
size: 55, 27
orig: 55, 27
offset: 0, 0
index: -1
White/hand
rotate: false
xy: 147, 9
size: 32, 28
orig: 32, 28
offset: 0, 0
index: -1
White/head 1
rotate: false
xy: 2, 117
size: 139, 137
orig: 139, 137
offset: 0, 0
index: -1
White/leg
rotate: false
xy: 143, 39
size: 48, 55
orig: 48, 55
offset: 0, 0
index: -1
White/leg 2
rotate: false
xy: 143, 96
size: 50, 58
orig: 50, 58
offset: 0, 0
index: -1
White/mouth
rotate: false
xy: 193, 35
size: 28, 21
orig: 28, 21
offset: 0, 0
index: -1

View File

@ -0,0 +1,4 @@
fileFormatVersion: 2
guid: 5ca7c05342912804eb0a2fd5bbe85b58
TextScriptImporter:
userData:

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

View File

@ -0,0 +1,47 @@
fileFormatVersion: 2
guid: 57b57f94df266f94ea0981915a4472e1
TextureImporter:
fileIDToRecycleName: {}
serializedVersion: 2
mipmaps:
mipMapMode: 0
enableMipMap: 0
linearTexture: 0
correctGamma: 0
fadeOut: 0
borderMipMap: 0
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: .25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 0
seamlessCubemap: 0
textureFormat: -3
maxTextureSize: 2048
textureSettings:
filterMode: -1
aniso: -1
mipBias: -1
wrapMode: -1
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: .5, y: .5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaIsTransparency: 0
textureType: -1
buildTargetSettings: []
spriteSheet:
sprites: []
spritePackingTag:
userData:

View File

@ -0,0 +1,4 @@
fileFormatVersion: 2
guid: e3c7e74834cd8424f8735ba05e94a688
NativeFormatImporter:
userData:

View File

@ -0,0 +1,4 @@
fileFormatVersion: 2
guid: 5a3598dafa118754db95756064347da7
NativeFormatImporter:
userData:

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,4 @@
fileFormatVersion: 2
guid: b075ad1d14f5db64b941d266e53de57d
TextScriptImporter:
userData:

View File

@ -0,0 +1,4 @@
fileFormatVersion: 2
guid: e57cdb51287d3924ebb2ececf816733b
NativeFormatImporter:
userData:

View File

@ -0,0 +1,5 @@
Copyright (c) 2014, XDTech
The project file and images in this "FootSoldier" project are provided for
demonstration purposes only and may not be redistributed for any reason nor
used as the basis for derivative work.

View File

@ -0,0 +1,4 @@
fileFormatVersion: 2
guid: 6fe8b327d2ab4bd438a63ffec150a911
TextScriptImporter:
userData:

View File

@ -0,0 +1,5 @@
Copyright (c) 2014, XDTech
The project file and images in this "Hero" project are provided for
demonstration purposes only and may not be redistributed for any reason nor
used as the basis for derivative work.

View File

@ -0,0 +1,4 @@
fileFormatVersion: 2
guid: 0d9db1748e0cf9f408129df308299463
TextScriptImporter:
userData:

View File

@ -0,0 +1,49 @@
using UnityEngine;
using System.Collections;
using Spine;
public class AtlasRegionAttacher : MonoBehaviour {
[System.Serializable]
public class SlotRegionPair {
[SpineSlot]
public string slot;
[SpineAtlasRegion]
public string region;
}
public AtlasAsset atlasAsset;
public SlotRegionPair[] attachments;
[HideInInspector]
public SkeletonRenderer skeletonRenderer;
Atlas atlas;
void Start() {
atlas = atlasAsset.GetAtlas();
this.skeletonRenderer = GetComponent<SkeletonRenderer>();
AtlasAttachmentLoader loader = new AtlasAttachmentLoader(atlas);
float scaleMultiplier = skeletonRenderer.skeletonDataAsset.scale;
var enumerator = attachments.GetEnumerator();
while (enumerator.MoveNext()) {
var entry = (SlotRegionPair)enumerator.Current;
var regionAttachment = loader.NewRegionAttachment(null, entry.region, entry.region);
regionAttachment.Width = regionAttachment.RegionOriginalWidth * scaleMultiplier;
regionAttachment.Height = regionAttachment.RegionOriginalHeight * scaleMultiplier;
regionAttachment.SetColor(new Color(1, 1, 1, 1));
regionAttachment.UpdateOffset();
var slot = this.skeletonRenderer.skeleton.FindSlot(entry.slot);
slot.Attachment = regionAttachment;
}
}
}

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: afde57cc4fd39bb4dbd61b73403ae6a8
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:

View File

@ -0,0 +1,36 @@
using UnityEngine;
using System.Collections;
using Spine;
public class CustomSkin : MonoBehaviour {
[System.Serializable]
public class SkinPair {
[SpineAttachment(currentSkinOnly: false, returnAttachmentPath: true, dataField: "skinSource")]
public string sourceAttachment;
[SpineSlot]
public string targetSlot;
[SpineAttachment(currentSkinOnly: true, placeholdersOnly: true)]
public string targetAttachment;
}
public SkeletonDataAsset skinSource;
public SkinPair[] skinning;
public Skin customSkin;
SkeletonRenderer skeletonRenderer;
void Start() {
skeletonRenderer = GetComponent<SkeletonRenderer>();
Skeleton skeleton = skeletonRenderer.skeleton;
customSkin = new Skin("CustomSkin");
foreach (var pair in skinning) {
var attachment = SpineAttachment.GetAttachment(pair.sourceAttachment, skinSource);
customSkin.AddAttachment(skeleton.FindSlotIndex(pair.targetSlot), pair.targetAttachment, attachment);
}
skeleton.SetSkin(customSkin);
}
}

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6e55c8477eccddc4cb5c3551a3945ca7
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:

View File

@ -28,14 +28,21 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
using Spine;
[CustomEditor(typeof(AtlasAsset))]
public class AtlasAssetInspector : Editor {
private SerializedProperty atlasFile, materials;
void OnEnable () {
SpineEditorUtilities.ConfirmInitialization();
atlasFile = serializedObject.FindProperty("atlasFile");
materials = serializedObject.FindProperty("materials");
}
@ -44,11 +51,43 @@ public class AtlasAssetInspector : Editor {
serializedObject.Update();
AtlasAsset asset = (AtlasAsset)target;
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(atlasFile);
EditorGUILayout.PropertyField(materials, true);
if (EditorGUI.EndChangeCheck())
serializedObject.ApplyModifiedProperties();
if (materials.arraySize == 0) {
EditorGUILayout.LabelField(new GUIContent("Error: Missing materials", SpineEditorUtilities.Icons.warning));
return;
}
for (int i = 0; i < materials.arraySize; i++) {
SerializedProperty prop = materials.GetArrayElementAtIndex(i);
Material mat = (Material)prop.objectReferenceValue;
if (mat == null) {
EditorGUILayout.LabelField(new GUIContent("Error: Materials cannot be null", SpineEditorUtilities.Icons.warning));
return;
}
}
if (atlasFile.objectReferenceValue != null) {
Atlas atlas = asset.GetAtlas();
FieldInfo field = typeof(Atlas).GetField("regions", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.NonPublic);
List<AtlasRegion> regions = (List<AtlasRegion>)field.GetValue(atlas);
EditorGUILayout.LabelField("Regions");
EditorGUI.indentLevel++;
for (int i = 0; i < regions.Count; i++) {
EditorGUILayout.LabelField(regions[i].name);
}
EditorGUI.indentLevel--;
}
if (serializedObject.ApplyModifiedProperties() ||
(Event.current.type == EventType.ValidateCommand && Event.current.commandName == "UndoRedoPerformed")
(UnityEngine.Event.current.type == EventType.ValidateCommand && UnityEngine.Event.current.commandName == "UndoRedoPerformed")
) {
asset.Reset();
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 649 B

View File

@ -0,0 +1,47 @@
fileFormatVersion: 2
guid: 4a1646cf39026224c85ecba92d7d6948
TextureImporter:
fileIDToRecycleName: {}
serializedVersion: 2
mipmaps:
mipMapMode: 0
enableMipMap: 0
linearTexture: 1
correctGamma: 0
fadeOut: 0
borderMipMap: 0
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: .25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 0
seamlessCubemap: 0
textureFormat: -1
maxTextureSize: 1024
textureSettings:
filterMode: -1
aniso: 1
mipBias: -1
wrapMode: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: .5, y: .5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaIsTransparency: 1
textureType: 2
buildTargetSettings: []
spriteSheet:
sprites: []
spritePackingTag:
userData:

View File

@ -5,8 +5,8 @@ TextureImporter:
serializedVersion: 2
mipmaps:
mipMapMode: 0
enableMipMap: 1
linearTexture: 0
enableMipMap: 0
linearTexture: 1
correctGamma: 0
fadeOut: 0
borderMipMap: 0
@ -25,10 +25,10 @@ TextureImporter:
maxTextureSize: 1024
textureSettings:
filterMode: -1
aniso: -1
aniso: 1
mipBias: -1
wrapMode: -1
nPOTScale: 1
wrapMode: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
@ -38,8 +38,8 @@ TextureImporter:
spritePivot: {x: .5, y: .5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaIsTransparency: 0
textureType: -1
alphaIsTransparency: 1
textureType: 2
buildTargetSettings: []
spriteSheet:
sprites: []

Binary file not shown.

After

Width:  |  Height:  |  Size: 698 B

View File

@ -0,0 +1,47 @@
fileFormatVersion: 2
guid: 0b1bcb09fa228d049ba3c9ea6a57e1ee
TextureImporter:
fileIDToRecycleName: {}
serializedVersion: 2
mipmaps:
mipMapMode: 0
enableMipMap: 0
linearTexture: 1
correctGamma: 0
fadeOut: 0
borderMipMap: 0
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: .25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 0
seamlessCubemap: 0
textureFormat: -3
maxTextureSize: 1024
textureSettings:
filterMode: -1
aniso: 1
mipBias: -1
wrapMode: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: .5, y: .5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaIsTransparency: 1
textureType: 2
buildTargetSettings: []
spriteSheet:
sprites: []
spritePackingTag:
userData:

View File

@ -44,42 +44,48 @@ using Spine;
[CustomEditor(typeof(SkeletonDataAsset))]
public class SkeletonDataAssetInspector : Editor {
private SerializedProperty atlasAsset, skeletonJSON, scale, fromAnimation, toAnimation, duration, defaultMix;
private bool showAnimationStateData = true;
#if UNITY_4_3
private bool m_showAnimationList = true;
#else
private AnimBool m_showAnimationList = new AnimBool(true);
#endif
static bool showAnimationStateData = true;
static bool showAnimationList = true;
static bool showSlotList = false;
static bool showAttachments = false;
private SerializedProperty atlasAssets, skeletonJSON, scale, fromAnimation, toAnimation, duration, defaultMix;
private bool m_initialized = false;
private SkeletonDataAsset m_skeletonDataAsset;
private SkeletonData m_skeletonData;
private string m_skeletonDataAssetGUID;
void OnEnable () {
try {
atlasAsset = serializedObject.FindProperty("atlasAsset");
List<string> warnings = new List<string>();
void OnEnable() {
SpineEditorUtilities.ConfirmInitialization();
try {
atlasAssets = serializedObject.FindProperty("atlasAssets");
skeletonJSON = serializedObject.FindProperty("skeletonJSON");
scale = serializedObject.FindProperty("scale");
fromAnimation = serializedObject.FindProperty("fromAnimation");
toAnimation = serializedObject.FindProperty("toAnimation");
duration = serializedObject.FindProperty("duration");
defaultMix = serializedObject.FindProperty("defaultMix");
m_skeletonDataAsset = (SkeletonDataAsset)target;
m_skeletonDataAssetGUID = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(m_skeletonDataAsset));
EditorApplication.update += Update;
EditorApplication.update += Update;
} catch {
}
m_skeletonData = m_skeletonDataAsset.GetSkeletonData(true);
RepopulateWarnings();
}
void OnDestroy () {
void OnDestroy() {
m_initialized = false;
EditorApplication.update -= Update;
this.DestroyPreviewInstances();
@ -88,156 +94,341 @@ public class SkeletonDataAssetInspector : Editor {
this.m_previewUtility = null;
}
}
override public void OnInspectorGUI () {
override public void OnInspectorGUI() {
serializedObject.Update();
SkeletonDataAsset asset = (SkeletonDataAsset)target;
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(atlasAsset);
EditorGUILayout.PropertyField(atlasAssets, true);
EditorGUILayout.PropertyField(skeletonJSON);
EditorGUILayout.PropertyField(scale);
if (EditorGUI.EndChangeCheck()) {
if (m_previewUtility != null) {
m_previewUtility.Cleanup();
m_previewUtility = null;
if (serializedObject.ApplyModifiedProperties()) {
if (m_previewUtility != null) {
m_previewUtility.Cleanup();
m_previewUtility = null;
}
RepopulateWarnings();
OnEnable();
return;
}
}
SkeletonData skeletonData = asset.GetSkeletonData(asset.atlasAsset == null || asset.skeletonJSON == null);
if (skeletonData != null) {
showAnimationStateData = EditorGUILayout.Foldout(showAnimationStateData, "Animation State Data");
if (showAnimationStateData) {
EditorGUILayout.PropertyField(defaultMix);
// Animation names
String[] animations = new String[skeletonData.Animations.Count];
for (int i = 0; i < animations.Length; i++)
animations[i] = skeletonData.Animations[i].Name;
for (int i = 0; i < fromAnimation.arraySize; i++) {
SerializedProperty from = fromAnimation.GetArrayElementAtIndex(i);
SerializedProperty to = toAnimation.GetArrayElementAtIndex(i);
SerializedProperty durationProp = duration.GetArrayElementAtIndex(i);
EditorGUILayout.BeginHorizontal();
from.stringValue = animations[EditorGUILayout.Popup(Math.Max(Array.IndexOf(animations, from.stringValue), 0), animations)];
to.stringValue = animations[EditorGUILayout.Popup(Math.Max(Array.IndexOf(animations, to.stringValue), 0), animations)];
durationProp.floatValue = EditorGUILayout.FloatField(durationProp.floatValue);
if (GUILayout.Button("Delete")) {
duration.DeleteArrayElementAtIndex(i);
toAnimation.DeleteArrayElementAtIndex(i);
fromAnimation.DeleteArrayElementAtIndex(i);
}
EditorGUILayout.EndHorizontal();
}
EditorGUILayout.BeginHorizontal();
EditorGUILayout.Space();
if (GUILayout.Button("Add Mix")) {
duration.arraySize++;
toAnimation.arraySize++;
fromAnimation.arraySize++;
}
EditorGUILayout.Space();
EditorGUILayout.EndHorizontal();
}
if (m_skeletonData != null) {
DrawAnimationStateInfo();
DrawAnimationList();
DrawSlotList();
if (GUILayout.Button(new GUIContent("Setup Pose", SpineEditorUtilities.Icons.skeleton), GUILayout.Width(105), GUILayout.Height(18))) {
StopAnimation();
m_skeletonAnimation.skeleton.SetToSetupPose();
m_requireRefresh = true;
}
#if UNITY_4_3
m_showAnimationList = EditorGUILayout.Foldout(m_showAnimationList, new GUIContent("Animations", SpineEditorUtilities.Icons.animationRoot));
if(m_showAnimationList){
#else
m_showAnimationList.target = EditorGUILayout.Foldout(m_showAnimationList.target, new GUIContent("Animations", SpineEditorUtilities.Icons.animationRoot));
if (EditorGUILayout.BeginFadeGroup(m_showAnimationList.faded)) {
#endif
EditorGUILayout.LabelField("Name", "Duration");
foreach (Spine.Animation a in skeletonData.Animations) {
GUILayout.BeginHorizontal();
if (m_skeletonAnimation != null && m_skeletonAnimation.state != null) {
if (m_skeletonAnimation.state.GetCurrent(0) != null && m_skeletonAnimation.state.GetCurrent(0).Animation == a) {
GUI.contentColor = Color.black;
if (GUILayout.Button("\u25BA", GUILayout.Width(24))) {
StopAnimation();
}
GUI.contentColor = Color.white;
} else {
if (GUILayout.Button("\u25BA", GUILayout.Width(24))) {
PlayAnimation(a.Name, true);
}
}
} else {
GUILayout.Label("?", GUILayout.Width(24));
}
EditorGUILayout.LabelField(new GUIContent(a.Name, SpineEditorUtilities.Icons.animation), new GUIContent(a.Duration.ToString("f3") + "s" + ("(" + (Mathf.RoundToInt(a.Duration * 30)) + ")").PadLeft(12, ' ')));
GUILayout.EndHorizontal();
}
}
#if !UNITY_4_3
EditorGUILayout.EndFadeGroup();
#endif
} else {
DrawReimportButton();
//Show Warnings
foreach (var str in warnings)
EditorGUILayout.LabelField(new GUIContent(str, SpineEditorUtilities.Icons.warning));
}
if (!Application.isPlaying) {
if (serializedObject.ApplyModifiedProperties() ||
(UnityEngine.Event.current.type == EventType.ValidateCommand && UnityEngine.Event.current.commandName == "UndoRedoPerformed")
) {
) {
asset.Reset();
}
}
}
void DrawReimportButton() {
EditorGUI.BeginDisabledGroup(skeletonJSON.objectReferenceValue == null);
if (GUILayout.Button(new GUIContent("Attempt Reimport", SpineEditorUtilities.Icons.warning))) {
SpineEditorUtilities.ImportSpineContent(new string[] { AssetDatabase.GetAssetPath(skeletonJSON.objectReferenceValue) }, true);
if (m_previewUtility != null) {
m_previewUtility.Cleanup();
m_previewUtility = null;
}
RepopulateWarnings();
OnEnable();
return;
}
EditorGUI.EndDisabledGroup();
}
void DrawAnimationStateInfo() {
showAnimationStateData = EditorGUILayout.Foldout(showAnimationStateData, "Animation State Data");
if (!showAnimationStateData)
return;
EditorGUILayout.PropertyField(defaultMix);
// Animation names
String[] animations = new String[m_skeletonData.Animations.Count];
for (int i = 0; i < animations.Length; i++)
animations[i] = m_skeletonData.Animations[i].Name;
for (int i = 0; i < fromAnimation.arraySize; i++) {
SerializedProperty from = fromAnimation.GetArrayElementAtIndex(i);
SerializedProperty to = toAnimation.GetArrayElementAtIndex(i);
SerializedProperty durationProp = duration.GetArrayElementAtIndex(i);
EditorGUILayout.BeginHorizontal();
from.stringValue = animations[EditorGUILayout.Popup(Math.Max(Array.IndexOf(animations, from.stringValue), 0), animations)];
to.stringValue = animations[EditorGUILayout.Popup(Math.Max(Array.IndexOf(animations, to.stringValue), 0), animations)];
durationProp.floatValue = EditorGUILayout.FloatField(durationProp.floatValue);
if (GUILayout.Button("Delete")) {
duration.DeleteArrayElementAtIndex(i);
toAnimation.DeleteArrayElementAtIndex(i);
fromAnimation.DeleteArrayElementAtIndex(i);
}
EditorGUILayout.EndHorizontal();
}
EditorGUILayout.BeginHorizontal();
EditorGUILayout.Space();
if (GUILayout.Button("Add Mix")) {
duration.arraySize++;
toAnimation.arraySize++;
fromAnimation.arraySize++;
}
EditorGUILayout.Space();
EditorGUILayout.EndHorizontal();
}
void DrawAnimationList() {
showAnimationList = EditorGUILayout.Foldout(showAnimationList, new GUIContent("Animations", SpineEditorUtilities.Icons.animationRoot));
if (!showAnimationList)
return;
if (GUILayout.Button(new GUIContent("Setup Pose", SpineEditorUtilities.Icons.skeleton), GUILayout.Width(105), GUILayout.Height(18))) {
StopAnimation();
m_skeletonAnimation.skeleton.SetToSetupPose();
m_requireRefresh = true;
}
EditorGUILayout.LabelField("Name", "Duration");
foreach (Spine.Animation a in m_skeletonData.Animations) {
GUILayout.BeginHorizontal();
if (m_skeletonAnimation != null && m_skeletonAnimation.state != null) {
if (m_skeletonAnimation.state.GetCurrent(0) != null && m_skeletonAnimation.state.GetCurrent(0).Animation == a) {
GUI.contentColor = Color.black;
if (GUILayout.Button("\u25BA", GUILayout.Width(24))) {
StopAnimation();
}
GUI.contentColor = Color.white;
} else {
if (GUILayout.Button("\u25BA", GUILayout.Width(24))) {
PlayAnimation(a.Name, true);
}
}
} else {
GUILayout.Label("?", GUILayout.Width(24));
}
EditorGUILayout.LabelField(new GUIContent(a.Name, SpineEditorUtilities.Icons.animation), new GUIContent(a.Duration.ToString("f3") + "s" + ("(" + (Mathf.RoundToInt(a.Duration * 30)) + ")").PadLeft(12, ' ')));
GUILayout.EndHorizontal();
}
}
void DrawSlotList() {
showSlotList = EditorGUILayout.Foldout(showSlotList, new GUIContent("Slots", SpineEditorUtilities.Icons.slotRoot));
if (!showSlotList)
return;
if (m_skeletonAnimation == null || m_skeletonAnimation.skeleton == null)
return;
EditorGUI.indentLevel++;
try {
showAttachments = EditorGUILayout.ToggleLeft("Show Attachments", showAttachments);
} catch {
return;
}
List<Attachment> slotAttachments = new List<Attachment>();
List<string> slotAttachmentNames = new List<string>();
List<string> defaultSkinAttachmentNames = new List<string>();
var defaultSkin = m_skeletonData.Skins[0];
Skin skin = m_skeletonAnimation.skeleton.Skin;
if (skin == null) {
skin = defaultSkin;
}
for (int i = m_skeletonAnimation.skeleton.Slots.Count-1; i >= 0; i--) {
Slot slot = m_skeletonAnimation.skeleton.Slots[i];
EditorGUILayout.LabelField(new GUIContent(slot.Data.Name, SpineEditorUtilities.Icons.slot));
if (showAttachments) {
EditorGUI.indentLevel++;
slotAttachments.Clear();
slotAttachmentNames.Clear();
defaultSkinAttachmentNames.Clear();
skin.FindNamesForSlot(i, slotAttachmentNames);
skin.FindAttachmentsForSlot(i, slotAttachments);
if (skin != defaultSkin) {
defaultSkin.FindNamesForSlot(i, defaultSkinAttachmentNames);
defaultSkin.FindNamesForSlot(i, slotAttachmentNames);
defaultSkin.FindAttachmentsForSlot(i, slotAttachments);
} else {
defaultSkin.FindNamesForSlot(i, defaultSkinAttachmentNames);
}
for (int a = 0; a < slotAttachments.Count; a++) {
Attachment attachment = slotAttachments[a];
string name = slotAttachmentNames[a];
Texture2D icon = null;
var type = attachment.GetType();
if (type == typeof(RegionAttachment))
icon = SpineEditorUtilities.Icons.image;
else if (type == typeof(MeshAttachment))
icon = SpineEditorUtilities.Icons.mesh;
else if (type == typeof(BoundingBoxAttachment))
icon = SpineEditorUtilities.Icons.boundingBox;
else if (type == typeof(SkinnedMeshAttachment))
icon = SpineEditorUtilities.Icons.weights;
else
icon = SpineEditorUtilities.Icons.warning;
//TODO: Waterboard Nate
//if (name != attachment.Name)
//icon = SpineEditorUtilities.Icons.skinPlaceholder;
bool initialState = slot.Attachment == attachment;
bool toggled = EditorGUILayout.ToggleLeft(new GUIContent(name, icon), slot.Attachment == attachment);
if (!defaultSkinAttachmentNames.Contains(name)) {
Rect skinPlaceHolderIconRect = GUILayoutUtility.GetLastRect();
skinPlaceHolderIconRect.width = SpineEditorUtilities.Icons.skinPlaceholder.width;
skinPlaceHolderIconRect.height = SpineEditorUtilities.Icons.skinPlaceholder.height;
GUI.DrawTexture(skinPlaceHolderIconRect, SpineEditorUtilities.Icons.skinPlaceholder);
}
if (toggled != initialState) {
if (toggled) {
slot.Attachment = attachment;
} else {
slot.Attachment = null;
}
m_requireRefresh = true;
}
}
EditorGUI.indentLevel--;
}
}
EditorGUI.indentLevel--;
}
void RepopulateWarnings() {
warnings.Clear();
if (skeletonJSON.objectReferenceValue == null)
warnings.Add("Missing Skeleton JSON");
else {
if (SpineEditorUtilities.IsSpineJSON((TextAsset)skeletonJSON.objectReferenceValue) == false) {
warnings.Add("Skeleton JSON is not a Valid JSON file");
} else {
bool detectedNullAtlasEntry = false;
List<Atlas> atlasList = new List<Atlas>();
for (int i = 0; i < atlasAssets.arraySize; i++) {
if (atlasAssets.GetArrayElementAtIndex(i).objectReferenceValue == null) {
detectedNullAtlasEntry = true;
break;
} else {
atlasList.Add(((AtlasAsset)atlasAssets.GetArrayElementAtIndex(i).objectReferenceValue).GetAtlas());
}
}
if (detectedNullAtlasEntry)
warnings.Add("AtlasAsset elements cannot be Null");
else {
//get requirements
var missingPaths = SpineEditorUtilities.GetRequiredAtlasRegions(AssetDatabase.GetAssetPath((TextAsset)skeletonJSON.objectReferenceValue));
foreach (var atlas in atlasList) {
for (int i = 0; i < missingPaths.Count; i++) {
if (atlas.FindRegion(missingPaths[i]) != null) {
missingPaths.RemoveAt(i);
i--;
}
}
}
foreach (var str in missingPaths)
warnings.Add("Missing Region: '" + str + "'");
}
}
}
}
//preview window stuff
private PreviewRenderUtility m_previewUtility;
private GameObject m_previewInstance;
private Vector2 previewDir;
private SkeletonAnimation m_skeletonAnimation;
private SkeletonData m_skeletonData;
//private SkeletonData m_skeletonData;
private static int sliderHash = "Slider".GetHashCode();
private float m_lastTime;
private bool m_playing;
private bool m_requireRefresh;
private Color m_originColor = new Color(0.3f, 0.3f, 0.3f, 1);
private void StopAnimation () {
private void StopAnimation() {
m_skeletonAnimation.state.ClearTrack(0);
m_playing = false;
}
List<Spine.Event> m_animEvents = new List<Spine.Event>();
List<float> m_animEventFrames = new List<float>();
private void PlayAnimation (string animName, bool loop) {
private void PlayAnimation(string animName, bool loop) {
m_animEvents.Clear();
m_animEventFrames.Clear();
m_skeletonAnimation.state.SetAnimation(0, animName, loop);
Spine.Animation a = m_skeletonAnimation.state.GetCurrent(0).Animation;
foreach (Timeline t in a.Timelines) {
if (t.GetType() == typeof(EventTimeline)) {
EventTimeline et = (EventTimeline)t;
for (int i = 0; i < et.Events.Length; i++) {
m_animEvents.Add(et.Events[i]);
m_animEventFrames.Add(et.Frames[i]);
}
}
}
m_playing = true;
}
private void InitPreview () {
private void InitPreview() {
if (this.m_previewUtility == null) {
this.m_lastTime = Time.realtimeSinceStartup;
this.m_previewUtility = new PreviewRenderUtility(true);
@ -247,48 +438,59 @@ public class SkeletonDataAssetInspector : Editor {
this.CreatePreviewInstances();
}
}
private void CreatePreviewInstances () {
private void CreatePreviewInstances() {
this.DestroyPreviewInstances();
if (this.m_previewInstance == null) {
string skinName = EditorPrefs.GetString(m_skeletonDataAssetGUID + "_lastSkin", "");
m_previewInstance = SpineEditorUtilities.SpawnAnimatedSkeleton((SkeletonDataAsset)target, skinName).gameObject;
m_previewInstance.hideFlags = HideFlags.HideAndDontSave;
m_previewInstance.layer = 0x1f;
m_skeletonAnimation = m_previewInstance.GetComponent<SkeletonAnimation>();
m_skeletonAnimation.initialSkinName = skinName;
m_skeletonAnimation.LateUpdate();
m_skeletonData = m_skeletonAnimation.skeletonDataAsset.GetSkeletonData(true);
m_previewInstance.renderer.enabled = false;
m_initialized = true;
AdjustCameraGoals(true);
try {
string skinName = EditorPrefs.GetString(m_skeletonDataAssetGUID + "_lastSkin", "");
m_previewInstance = SpineEditorUtilities.SpawnAnimatedSkeleton((SkeletonDataAsset)target, skinName).gameObject;
m_previewInstance.hideFlags = HideFlags.HideAndDontSave;
m_previewInstance.layer = 0x1f;
m_skeletonAnimation = m_previewInstance.GetComponent<SkeletonAnimation>();
m_skeletonAnimation.initialSkinName = skinName;
m_skeletonAnimation.LateUpdate();
m_skeletonData = m_skeletonAnimation.skeletonDataAsset.GetSkeletonData(true);
m_previewInstance.renderer.enabled = false;
m_initialized = true;
AdjustCameraGoals(true);
} catch {
}
}
}
private void DestroyPreviewInstances () {
private void DestroyPreviewInstances() {
if (this.m_previewInstance != null) {
DestroyImmediate(this.m_previewInstance);
m_previewInstance = null;
}
m_initialized = false;
}
public override bool HasPreviewGUI () {
public override bool HasPreviewGUI() {
//TODO: validate json data
for (int i = 0; i < atlasAssets.arraySize; i++) {
var prop = atlasAssets.GetArrayElementAtIndex(i);
if (prop.objectReferenceValue == null)
return false;
}
return skeletonJSON.objectReferenceValue != null;
}
Texture m_previewTex = new Texture();
public override void OnInteractivePreviewGUI (Rect r, GUIStyle background) {
public override void OnInteractivePreviewGUI(Rect r, GUIStyle background) {
this.InitPreview();
if (UnityEngine.Event.current.type == EventType.Repaint) {
if (m_requireRefresh) {
this.m_previewUtility.BeginPreview(r, background);
@ -299,50 +501,54 @@ public class SkeletonDataAssetInspector : Editor {
if (this.m_previewTex != null)
GUI.DrawTexture(r, m_previewTex, ScaleMode.StretchToFill, false);
}
DrawSkinToolbar(r);
NormalizedTimeBar(r);
//TODO: implement panning
// this.previewDir = Drag2D(this.previewDir, r);
MouseScroll(r);
}
float m_orthoGoal = 1;
Vector3 m_posGoal = new Vector3(0, 0, -10);
double m_adjustFrameEndTime = 0;
private void AdjustCameraGoals (bool calculateMixTime) {
private void AdjustCameraGoals(bool calculateMixTime) {
if (this.m_previewInstance == null)
return;
if (calculateMixTime) {
if (m_skeletonAnimation.state.GetCurrent(0) != null) {
m_adjustFrameEndTime = EditorApplication.timeSinceStartup + m_skeletonAnimation.state.GetCurrent(0).Mix;
}
}
GameObject go = this.m_previewInstance;
Bounds bounds = go.renderer.bounds;
m_orthoGoal = bounds.size.y;
m_posGoal = bounds.center + new Vector3(0, 0, -10);
}
private void AdjustCameraGoals () {
private void AdjustCameraGoals() {
AdjustCameraGoals(false);
}
private void AdjustCamera () {
private void AdjustCamera() {
if (m_previewUtility == null)
return;
if (EditorApplication.timeSinceStartup < m_adjustFrameEndTime) {
AdjustCameraGoals();
}
float orthoSet = Mathf.Lerp(this.m_previewUtility.m_Camera.orthographicSize, m_orthoGoal, 0.1f);
this.m_previewUtility.m_Camera.orthographicSize = orthoSet;
float dist = Vector3.Distance(m_previewUtility.m_Camera.transform.position, m_posGoal);
if (dist > 60f * ((SkeletonDataAsset)target).scale) {
Vector3 pos = Vector3.Lerp(this.m_previewUtility.m_Camera.transform.position, m_posGoal, 0.1f);
@ -352,138 +558,138 @@ public class SkeletonDataAssetInspector : Editor {
m_requireRefresh = true;
}
}
private void DoRenderPreview (bool drawHandles) {
private void DoRenderPreview(bool drawHandles) {
GameObject go = this.m_previewInstance;
if (m_requireRefresh) {
if (m_requireRefresh && go != null) {
go.renderer.enabled = true;
if (EditorApplication.isPlaying) {
//do nothing
} else {
m_skeletonAnimation.Update((Time.realtimeSinceStartup - m_lastTime));
}
m_lastTime = Time.realtimeSinceStartup;
if (!EditorApplication.isPlaying)
m_skeletonAnimation.LateUpdate();
if (drawHandles) {
Handles.SetCamera(m_previewUtility.m_Camera);
Handles.color = m_originColor;
Handles.DrawLine(new Vector3(-1000 * m_skeletonDataAsset.scale, 0, 0), new Vector3(1000 * m_skeletonDataAsset.scale, 0, 0));
Handles.DrawLine(new Vector3(0, 1000 * m_skeletonDataAsset.scale, 0), new Vector3(0, -1000 * m_skeletonDataAsset.scale, 0));
}
this.m_previewUtility.m_Camera.Render();
go.renderer.enabled = false;
}
}
void Update () {
void Update() {
AdjustCamera();
if (m_playing) {
m_requireRefresh = true;
Repaint();
} else if (m_requireRefresh) {
Repaint();
} else {
#if !UNITY_4_3
if (m_showAnimationList.isAnimating)
Repaint();
#endif
}
Repaint();
} else {
//only needed if using smooth menus
}
}
void DrawSkinToolbar (Rect r) {
void DrawSkinToolbar(Rect r) {
if (m_skeletonAnimation == null)
return;
if (m_skeletonAnimation.skeleton != null) {
string label = (m_skeletonAnimation.skeleton != null && m_skeletonAnimation.skeleton.Skin != null) ? m_skeletonAnimation.skeleton.Skin.Name : "default";
Rect popRect = new Rect(r);
popRect.y += 32;
popRect.x += 4;
popRect.height = 24;
popRect.width = 40;
EditorGUI.DropShadowLabel(popRect, new GUIContent("Skin", SpineEditorUtilities.Icons.skinsRoot));
popRect.y += 11;
popRect.width = 150;
popRect.x += 44;
if (GUI.Button(popRect, label, EditorStyles.popup)) {
SelectSkinContext();
}
}
}
void SelectSkinContext () {
void SelectSkinContext() {
GenericMenu menu = new GenericMenu();
foreach (Skin s in m_skeletonData.Skins) {
menu.AddItem(new GUIContent(s.Name), this.m_skeletonAnimation.skeleton.Skin == s, SetSkin, (object)s);
}
menu.ShowAsContext();
}
void SetSkin (object o) {
void SetSkin(object o) {
Skin skin = (Skin)o;
m_skeletonAnimation.initialSkinName = skin.Name;
m_skeletonAnimation.Reset();
m_requireRefresh = true;
EditorPrefs.SetString(m_skeletonDataAssetGUID + "_lastSkin", skin.Name);
}
void NormalizedTimeBar (Rect r) {
void NormalizedTimeBar(Rect r) {
if (m_skeletonAnimation == null)
return;
Rect barRect = new Rect(r);
barRect.height = 32;
barRect.x += 4;
barRect.width -= 4;
GUI.Box(barRect, "");
Rect lineRect = new Rect(barRect);
float width = lineRect.width;
TrackEntry t = m_skeletonAnimation.state.GetCurrent(0);
if (t != null) {
int loopCount = (int)(t.Time / t.EndTime);
float currentTime = t.Time - (t.EndTime * loopCount);
float normalizedTime = currentTime / t.Animation.Duration;
lineRect.x = barRect.x + (width * normalizedTime) - 0.5f;
lineRect.width = 2;
GUI.color = Color.red;
GUI.DrawTexture(lineRect, EditorGUIUtility.whiteTexture);
GUI.color = Color.white;
for (int i = 0; i < m_animEvents.Count; i++) {
//TODO: Tooltip
//Spine.Event spev = animEvents[i];
float fr = m_animEventFrames[i];
Rect evRect = new Rect(barRect);
evRect.x = Mathf.Clamp(((fr / t.Animation.Duration) * width) - (SpineEditorUtilities.Icons._event.width / 2), barRect.x, float.MaxValue);
evRect.width = SpineEditorUtilities.Icons._event.width;
evRect.height = SpineEditorUtilities.Icons._event.height;
evRect.y += SpineEditorUtilities.Icons._event.height;
GUI.DrawTexture(evRect, SpineEditorUtilities.Icons._event);
//TODO: Tooltip
/*
UnityEngine.Event ev = UnityEngine.Event.current;
@ -500,24 +706,24 @@ public class SkeletonDataAssetInspector : Editor {
}
}
}
void MouseScroll (Rect position) {
void MouseScroll(Rect position) {
UnityEngine.Event current = UnityEngine.Event.current;
int controlID = GUIUtility.GetControlID(sliderHash, FocusType.Passive);
switch (current.GetTypeForControl(controlID)) {
case EventType.ScrollWheel:
if (position.Contains(current.mousePosition)) {
m_orthoGoal += current.delta.y * ((SkeletonDataAsset)target).scale * 10;
GUIUtility.hotControl = controlID;
current.Use();
}
break;
case EventType.ScrollWheel:
if (position.Contains(current.mousePosition)) {
m_orthoGoal += current.delta.y * ((SkeletonDataAsset)target).scale * 10;
GUIUtility.hotControl = controlID;
current.Use();
}
break;
}
}
//TODO: Implement preview panning
/*
static Vector2 Drag2D(Vector2 scrollPosition, Rect position)
@ -559,45 +765,45 @@ public class SkeletonDataAssetInspector : Editor {
return scrollPosition;
}
*/
public override GUIContent GetPreviewTitle () {
public override GUIContent GetPreviewTitle() {
return new GUIContent("Preview");
}
public override void OnPreviewSettings () {
public override void OnPreviewSettings() {
if (!m_initialized) {
GUILayout.HorizontalSlider(0, 0, 2, GUILayout.MaxWidth(64));
} else {
float speed = GUILayout.HorizontalSlider(m_skeletonAnimation.timeScale, 0, 2, GUILayout.MaxWidth(64));
//snap to nearest 0.25
float y = speed / 0.25f;
int q = Mathf.RoundToInt(y);
speed = q * 0.25f;
m_skeletonAnimation.timeScale = speed;
}
}
//TODO: Fix first-import error
//TODO: Update preview without thumbnail
public override Texture2D RenderStaticPreview (string assetPath, UnityEngine.Object[] subAssets, int width, int height) {
public override Texture2D RenderStaticPreview(string assetPath, UnityEngine.Object[] subAssets, int width, int height) {
Texture2D tex = new Texture2D(width, height, TextureFormat.ARGB32, false);
this.InitPreview();
if (this.m_previewUtility.m_Camera == null)
return null;
m_requireRefresh = true;
this.DoRenderPreview(false);
AdjustCameraGoals(false);
this.m_previewUtility.m_Camera.orthographicSize = m_orthoGoal / 2;
this.m_previewUtility.m_Camera.transform.position = m_posGoal;
this.m_previewUtility.BeginStaticPreview(new Rect(0, 0, width, height));
this.DoRenderPreview(false);
//TODO: Figure out why this is throwing errors on first attempt
// if(m_previewUtility != null){
// Handles.SetCamera(this.m_previewUtility.m_Camera);

View File

@ -33,15 +33,17 @@ using UnityEngine;
[CustomEditor(typeof(SkeletonRenderer))]
public class SkeletonRendererInspector : Editor {
protected SerializedProperty skeletonDataAsset, initialSkinName, normals, tangents, meshes, immutableTriangles;
protected SerializedProperty skeletonDataAsset, initialSkinName, normals, tangents, meshes, immutableTriangles, submeshSeparators;
protected virtual void OnEnable () {
SpineEditorUtilities.ConfirmInitialization();
skeletonDataAsset = serializedObject.FindProperty("skeletonDataAsset");
initialSkinName = serializedObject.FindProperty("initialSkinName");
normals = serializedObject.FindProperty("calculateNormals");
tangents = serializedObject.FindProperty("calculateTangents");
meshes = serializedObject.FindProperty("renderMeshes");
immutableTriangles = serializedObject.FindProperty("immutableTriangles");
submeshSeparators = serializedObject.FindProperty("submeshSeparators");
}
protected virtual void gui () {
@ -52,8 +54,11 @@ public class SkeletonRendererInspector : Editor {
float reloadWidth = GUI.skin.label.CalcSize(new GUIContent("Reload")).x + 20;
if (GUILayout.Button("Reload", GUILayout.Width(reloadWidth))) {
if (component.skeletonDataAsset != null) {
if (component.skeletonDataAsset.atlasAsset != null)
component.skeletonDataAsset.atlasAsset.Reset();
foreach (AtlasAsset aa in component.skeletonDataAsset.atlasAssets) {
if (aa != null)
aa.Reset();
}
component.skeletonDataAsset.Reset();
}
component.Reset();
@ -92,6 +97,7 @@ public class SkeletonRendererInspector : Editor {
new GUIContent("Immutable Triangles", "Enable to optimize rendering for skeletons that never change attachment visbility"));
EditorGUILayout.PropertyField(normals);
EditorGUILayout.PropertyField(tangents);
EditorGUILayout.PropertyField(submeshSeparators, true);
}
override public void OnInspectorGUI () {

View File

@ -0,0 +1,585 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.1
*
* Copyright (c) 2013, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable and
* non-transferable license to install, execute and perform the Spine Runtimes
* Software (the "Software") solely for internal use. Without the written
* permission of Esoteric Software (typically granted by licensing Spine), you
* may not (a) modify, translate, adapt or otherwise create derivative works,
* improvements of the Software or develop new applications using the Software
* or (b) remove, delete, alter or obscure any trademarks or any copyright,
* trademark, patent or other intellectual property or proprietary rights
* notices on or in the Software, including any copy thereof. Redistributions
* in binary or source form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
/*****************************************************************************
* Spine Attribute Drawers created by Mitch Thompson
* Full irrevocable rights and permissions granted to Esoteric Software
*****************************************************************************/
using UnityEngine;
using UnityEditor;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Linq;
using System.Reflection;
using Spine;
public struct SpineDrawerValuePair {
public string str;
public SerializedProperty property;
public SpineDrawerValuePair(string val, SerializedProperty property) {
this.str = val;
this.property = property;
}
}
[CustomPropertyDrawer(typeof(SpineSlot))]
public class SpineSlotDrawer : PropertyDrawer {
SkeletonDataAsset skeletonDataAsset;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
if (property.propertyType != SerializedPropertyType.String) {
EditorGUI.LabelField(position, "ERROR:", "May only apply to type string");
return;
}
SpineSlot attrib = (SpineSlot)attribute;
var dataProperty = property.serializedObject.FindProperty(attrib.dataField);
if (dataProperty != null) {
if (dataProperty.objectReferenceValue is SkeletonDataAsset) {
skeletonDataAsset = (SkeletonDataAsset)dataProperty.objectReferenceValue;
} else if (dataProperty.objectReferenceValue is SkeletonRenderer) {
var renderer = (SkeletonRenderer)dataProperty.objectReferenceValue;
if (renderer != null)
skeletonDataAsset = renderer.skeletonDataAsset;
} else {
EditorGUI.LabelField(position, "ERROR:", "Invalid reference type");
return;
}
} else if (property.serializedObject.targetObject is Component) {
var component = (Component)property.serializedObject.targetObject;
if (component.GetComponent<SkeletonRenderer>() != null) {
var skeletonRenderer = component.GetComponent<SkeletonRenderer>();
skeletonDataAsset = skeletonRenderer.skeletonDataAsset;
}
}
if (skeletonDataAsset == null) {
EditorGUI.LabelField(position, "ERROR:", "Must have reference to a SkeletonDataAsset");
return;
}
position = EditorGUI.PrefixLabel(position, label);
if (GUI.Button(position, property.stringValue, EditorStyles.popup)) {
Selector(property);
}
}
void Selector(SerializedProperty property) {
SpineSlot attrib = (SpineSlot)attribute;
SkeletonData data = skeletonDataAsset.GetSkeletonData(true);
if (data == null)
return;
GenericMenu menu = new GenericMenu();
menu.AddDisabledItem(new GUIContent(skeletonDataAsset.name));
menu.AddSeparator("");
for (int i = 0; i < data.Slots.Count; i++) {
string name = data.Slots[i].Name;
if (name.StartsWith(attrib.startsWith))
menu.AddItem(new GUIContent(name), name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
}
menu.ShowAsContext();
}
void HandleSelect(object val) {
var pair = (SpineDrawerValuePair)val;
pair.property.stringValue = pair.str;
pair.property.serializedObject.ApplyModifiedProperties();
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
return 18;
}
}
[CustomPropertyDrawer(typeof(SpineSkin))]
public class SpineSkinDrawer : PropertyDrawer {
SkeletonDataAsset skeletonDataAsset;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
if (property.propertyType != SerializedPropertyType.String) {
EditorGUI.LabelField(position, "ERROR:", "May only apply to type string");
return;
}
SpineSkin attrib = (SpineSkin)attribute;
var dataProperty = property.serializedObject.FindProperty(attrib.dataField);
if (dataProperty != null) {
if (dataProperty.objectReferenceValue is SkeletonDataAsset) {
skeletonDataAsset = (SkeletonDataAsset)dataProperty.objectReferenceValue;
} else if (dataProperty.objectReferenceValue is SkeletonRenderer) {
var renderer = (SkeletonRenderer)dataProperty.objectReferenceValue;
if (renderer != null)
skeletonDataAsset = renderer.skeletonDataAsset;
} else {
EditorGUI.LabelField(position, "ERROR:", "Invalid reference type");
return;
}
} else if (property.serializedObject.targetObject is Component) {
var component = (Component)property.serializedObject.targetObject;
if (component.GetComponent<SkeletonRenderer>() != null) {
var skeletonRenderer = component.GetComponent<SkeletonRenderer>();
skeletonDataAsset = skeletonRenderer.skeletonDataAsset;
}
}
if (skeletonDataAsset == null) {
EditorGUI.LabelField(position, "ERROR:", "Must have reference to a SkeletonDataAsset");
return;
}
position = EditorGUI.PrefixLabel(position, label);
if (GUI.Button(position, property.stringValue, EditorStyles.popup)) {
Selector(property);
}
}
void Selector(SerializedProperty property) {
SpineSkin attrib = (SpineSkin)attribute;
SkeletonData data = skeletonDataAsset.GetSkeletonData(true);
if (data == null)
return;
GenericMenu menu = new GenericMenu();
menu.AddDisabledItem(new GUIContent(skeletonDataAsset.name));
menu.AddSeparator("");
for (int i = 0; i < data.Skins.Count; i++) {
string name = data.Skins[i].Name;
if (name.StartsWith(attrib.startsWith))
menu.AddItem(new GUIContent(name), name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
}
menu.ShowAsContext();
}
void HandleSelect(object val) {
var pair = (SpineDrawerValuePair)val;
pair.property.stringValue = pair.str;
pair.property.serializedObject.ApplyModifiedProperties();
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
return 18;
}
}
[CustomPropertyDrawer(typeof(SpineAtlasRegion))]
public class SpineAtlasRegionDrawer : PropertyDrawer {
Component component;
SerializedProperty atlasProp;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
if (property.propertyType != SerializedPropertyType.String) {
EditorGUI.LabelField(position, "ERROR:", "May only apply to type string");
return;
}
component = (Component)property.serializedObject.targetObject;
if (component != null)
atlasProp = property.serializedObject.FindProperty("atlasAsset");
else
atlasProp = null;
if (atlasProp == null) {
EditorGUI.LabelField(position, "ERROR:", "Must have AtlasAsset variable!");
return;
} else if (atlasProp.objectReferenceValue == null) {
EditorGUI.LabelField(position, "ERROR:", "Atlas variable must not be null!");
return;
} else if (atlasProp.objectReferenceValue.GetType() != typeof(AtlasAsset)) {
EditorGUI.LabelField(position, "ERROR:", "Atlas variable must be of type AtlasAsset!");
}
position = EditorGUI.PrefixLabel(position, label);
if (GUI.Button(position, property.stringValue, EditorStyles.popup)) {
Selector(property);
}
}
void Selector(SerializedProperty property) {
GenericMenu menu = new GenericMenu();
AtlasAsset atlasAsset = (AtlasAsset)atlasProp.objectReferenceValue;
Atlas atlas = atlasAsset.GetAtlas();
FieldInfo field = typeof(Atlas).GetField("regions", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.NonPublic);
List<AtlasRegion> regions = (List<AtlasRegion>)field.GetValue(atlas);
for (int i = 0; i < regions.Count; i++) {
string name = regions[i].name;
menu.AddItem(new GUIContent(name), name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
}
menu.ShowAsContext();
}
void HandleSelect(object val) {
var pair = (SpineDrawerValuePair)val;
pair.property.stringValue = pair.str;
pair.property.serializedObject.ApplyModifiedProperties();
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
return 18;
}
}
[CustomPropertyDrawer(typeof(SpineAnimation))]
public class SpineAnimationDrawer : PropertyDrawer {
SkeletonDataAsset skeletonDataAsset;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
if (property.propertyType != SerializedPropertyType.String) {
EditorGUI.LabelField(position, "ERROR:", "May only apply to type string");
return;
}
SpineAnimation attrib = (SpineAnimation)attribute;
var dataProperty = property.serializedObject.FindProperty(attrib.dataField);
if (dataProperty != null) {
if (dataProperty.objectReferenceValue is SkeletonDataAsset) {
skeletonDataAsset = (SkeletonDataAsset)dataProperty.objectReferenceValue;
} else if (dataProperty.objectReferenceValue is SkeletonRenderer) {
var renderer = (SkeletonRenderer)dataProperty.objectReferenceValue;
if (renderer != null)
skeletonDataAsset = renderer.skeletonDataAsset;
} else {
EditorGUI.LabelField(position, "ERROR:", "Invalid reference type");
return;
}
} else if (property.serializedObject.targetObject is Component) {
var component = (Component)property.serializedObject.targetObject;
if (component.GetComponent<SkeletonRenderer>() != null) {
var skeletonRenderer = component.GetComponent<SkeletonRenderer>();
skeletonDataAsset = skeletonRenderer.skeletonDataAsset;
}
}
if (skeletonDataAsset == null) {
EditorGUI.LabelField(position, "ERROR:", "Must have reference to a SkeletonDataAsset");
return;
}
position = EditorGUI.PrefixLabel(position, label);
if (GUI.Button(position, property.stringValue, EditorStyles.popup)) {
Selector(property);
}
}
void Selector(SerializedProperty property) {
SpineAnimation attrib = (SpineAnimation)attribute;
GenericMenu menu = new GenericMenu();
var animations = skeletonDataAsset.GetAnimationStateData().SkeletonData.Animations;
for (int i = 0; i < animations.Count; i++) {
string name = animations[i].Name;
if (name.StartsWith(attrib.startsWith))
menu.AddItem(new GUIContent(name), name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
}
menu.ShowAsContext();
}
void HandleSelect(object val) {
var pair = (SpineDrawerValuePair)val;
pair.property.stringValue = pair.str;
pair.property.serializedObject.ApplyModifiedProperties();
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
return 18;
}
}
[CustomPropertyDrawer(typeof(SpineAttachment))]
public class SpineAttachmentDrawer : PropertyDrawer {
SkeletonDataAsset skeletonDataAsset;
SkeletonRenderer skeletonRenderer;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
if (property.propertyType != SerializedPropertyType.String) {
EditorGUI.LabelField(position, "ERROR:", "May only apply to type string");
return;
}
SpineAttachment attrib = (SpineAttachment)attribute;
var dataProperty = property.serializedObject.FindProperty(attrib.dataField);
if (dataProperty != null) {
if (dataProperty.objectReferenceValue is SkeletonDataAsset) {
skeletonDataAsset = (SkeletonDataAsset)dataProperty.objectReferenceValue;
} else if (dataProperty.objectReferenceValue is SkeletonRenderer) {
var renderer = (SkeletonRenderer)dataProperty.objectReferenceValue;
if (renderer != null)
skeletonDataAsset = renderer.skeletonDataAsset;
else {
EditorGUI.LabelField(position, "ERROR:", "No SkeletonRenderer");
}
} else {
EditorGUI.LabelField(position, "ERROR:", "Invalid reference type");
return;
}
} else if (property.serializedObject.targetObject is Component) {
var component = (Component)property.serializedObject.targetObject;
if (component.GetComponent<SkeletonRenderer>() != null) {
skeletonRenderer = component.GetComponent<SkeletonRenderer>();
skeletonDataAsset = skeletonRenderer.skeletonDataAsset;
}
}
if (skeletonDataAsset == null && skeletonRenderer == null) {
EditorGUI.LabelField(position, "ERROR:", "Must have reference to a SkeletonDataAsset or SkeletonRenderer");
return;
}
position = EditorGUI.PrefixLabel(position, label);
if (GUI.Button(position, property.stringValue, EditorStyles.popup)) {
Selector(property);
}
}
void Selector(SerializedProperty property) {
SkeletonData data = skeletonDataAsset.GetSkeletonData(true);
if (data == null)
return;
SpineAttachment attrib = (SpineAttachment)attribute;
List<Skin> validSkins = new List<Skin>();
if (skeletonRenderer != null && attrib.currentSkinOnly) {
if (skeletonRenderer.skeleton.Skin != null) {
validSkins.Add(skeletonRenderer.skeleton.Skin);
} else {
validSkins.Add(data.Skins[0]);
}
} else {
foreach (Skin skin in data.Skins) {
if (skin != null)
validSkins.Add(skin);
}
}
GenericMenu menu = new GenericMenu();
List<string> attachmentNames = new List<string>();
List<string> placeholderNames = new List<string>();
string prefix = "";
if (skeletonRenderer != null && attrib.currentSkinOnly)
menu.AddDisabledItem(new GUIContent(skeletonRenderer.gameObject.name + " (SkeletonRenderer)"));
else
menu.AddDisabledItem(new GUIContent(skeletonDataAsset.name));
menu.AddSeparator("");
menu.AddItem(new GUIContent("Null"), property.stringValue == "", HandleSelect, new SpineDrawerValuePair("", property));
menu.AddSeparator("");
Skin defaultSkin = data.Skins[0];
SerializedProperty slotProperty = property.serializedObject.FindProperty(attrib.slotField);
string slotMatch = "";
if (slotProperty != null) {
if (slotProperty.propertyType == SerializedPropertyType.String) {
slotMatch = slotProperty.stringValue.ToLower();
}
}
foreach (Skin skin in validSkins) {
string skinPrefix = skin.Name + "/";
if (validSkins.Count > 1)
prefix = skinPrefix;
for (int i = 0; i < data.Slots.Count; i++) {
if (slotMatch.Length > 0 && data.Slots[i].Name.ToLower().Contains(slotMatch) == false)
continue;
attachmentNames.Clear();
placeholderNames.Clear();
skin.FindNamesForSlot(i, attachmentNames);
if (skin != defaultSkin) {
defaultSkin.FindNamesForSlot(i, attachmentNames);
skin.FindNamesForSlot(i, placeholderNames);
}
for (int a = 0; a < attachmentNames.Count; a++) {
string attachmentPath = attachmentNames[a];
string menuPath = prefix + data.Slots[i].Name + "/" + attachmentPath;
string name = attachmentNames[a];
if (attrib.returnAttachmentPath)
name = skin.Name + "/" + data.Slots[i].Name + "/" + attachmentPath;
if (attrib.placeholdersOnly && placeholderNames.Contains(attachmentPath) == false) {
menu.AddDisabledItem(new GUIContent(menuPath));
} else {
menu.AddItem(new GUIContent(menuPath), name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
}
}
}
}
menu.ShowAsContext();
}
void HandleSelect(object val) {
var pair = (SpineDrawerValuePair)val;
pair.property.stringValue = pair.str;
pair.property.serializedObject.ApplyModifiedProperties();
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
return 18;
}
}
[CustomPropertyDrawer(typeof(SpineBone))]
public class SpineBoneDrawer : PropertyDrawer {
SkeletonDataAsset skeletonDataAsset;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
if (property.propertyType != SerializedPropertyType.String) {
EditorGUI.LabelField(position, "ERROR:", "May only apply to type string");
return;
}
SpineBone attrib = (SpineBone)attribute;
var dataProperty = property.serializedObject.FindProperty(attrib.dataField);
if (dataProperty != null) {
if (dataProperty.objectReferenceValue is SkeletonDataAsset) {
skeletonDataAsset = (SkeletonDataAsset)dataProperty.objectReferenceValue;
} else if (dataProperty.objectReferenceValue is SkeletonRenderer) {
var renderer = (SkeletonRenderer)dataProperty.objectReferenceValue;
if (renderer != null)
skeletonDataAsset = renderer.skeletonDataAsset;
} else {
EditorGUI.LabelField(position, "ERROR:", "Invalid reference type");
return;
}
} else if (property.serializedObject.targetObject is Component) {
var component = (Component)property.serializedObject.targetObject;
if (component.GetComponent<SkeletonRenderer>() != null) {
var skeletonRenderer = component.GetComponent<SkeletonRenderer>();
skeletonDataAsset = skeletonRenderer.skeletonDataAsset;
}
}
if (skeletonDataAsset == null) {
EditorGUI.LabelField(position, "ERROR:", "Must have reference to a SkeletonDataAsset");
return;
}
position = EditorGUI.PrefixLabel(position, label);
if (GUI.Button(position, property.stringValue, EditorStyles.popup)) {
Selector(property);
}
}
void Selector(SerializedProperty property) {
SpineBone attrib = (SpineBone)attribute;
SkeletonData data = skeletonDataAsset.GetSkeletonData(true);
if (data == null)
return;
GenericMenu menu = new GenericMenu();
menu.AddDisabledItem(new GUIContent(skeletonDataAsset.name));
menu.AddSeparator("");
for (int i = 0; i < data.Bones.Count; i++) {
string name = data.Bones[i].Name;
if (name.StartsWith(attrib.startsWith))
menu.AddItem(new GUIContent(name), name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
}
menu.ShowAsContext();
}
void HandleSelect(object val) {
var pair = (SpineDrawerValuePair)val;
pair.property.stringValue = pair.str;
pair.property.serializedObject.ApplyModifiedProperties();
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
return 18;
}
}

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f2de282d583d4a641bf1c349f0a3eef9
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:

View File

@ -39,6 +39,8 @@ using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Linq;
using System.Reflection;
using Spine;
[InitializeOnLoad]
@ -51,10 +53,12 @@ public class SpineEditorUtilities : AssetPostprocessor {
public static Texture2D poseBones;
public static Texture2D boneNib;
public static Texture2D slot;
public static Texture2D slotRoot;
public static Texture2D skinPlaceholder;
public static Texture2D image;
public static Texture2D boundingBox;
public static Texture2D mesh;
public static Texture2D weights;
public static Texture2D skin;
public static Texture2D skinsRoot;
public static Texture2D animation;
@ -78,7 +82,7 @@ public class SpineEditorUtilities : AssetPostprocessor {
new Vector3(0.1f, 0.1f, 0)
};
_boneMesh.uv = new Vector2[4];
_boneMesh.triangles = new int[6]{0,1,2,2,3,0};
_boneMesh.triangles = new int[6] { 0, 1, 2, 2, 3, 0 };
_boneMesh.RecalculateBounds();
_boneMesh.RecalculateNormals();
}
@ -108,17 +112,19 @@ public class SpineEditorUtilities : AssetPostprocessor {
internal static Material _boneMaterial;
public static void Initialize () {
public static void Initialize() {
skeleton = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-skeleton.png");
nullBone = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-null.png");
bone = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-bone.png");
poseBones = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-poseBones.png");
boneNib = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-boneNib.png");
slot = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-slot.png");
slotRoot = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-slotRoot.png");
skinPlaceholder = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-skinPlaceholder.png");
image = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-image.png");
boundingBox = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-boundingBox.png");
mesh = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-mesh.png");
weights = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-weights.png");
skin = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-skinPlaceholder.png");
skinsRoot = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-skinsRoot.png");
animation = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-animation.png");
@ -140,12 +146,17 @@ public class SpineEditorUtilities : AssetPostprocessor {
public static float defaultScale = 0.01f;
public static float defaultMix = 0.2f;
public static string defaultShader = "Spine/Skeleton";
public static bool initialized;
static SpineEditorUtilities() {
Initialize();
}
static SpineEditorUtilities () {
static void Initialize(){
DirectoryInfo rootDir = new DirectoryInfo(Application.dataPath);
FileInfo[] files = rootDir.GetFiles("SpineEditorUtilities.cs", SearchOption.AllDirectories);
editorPath = Path.GetDirectoryName(files[0].FullName.Replace("\\", "/").Replace(Application.dataPath, "Assets"));
editorGUIPath = editorPath + "/GUI";
editorGUIPath = editorPath + "/GUI";
Icons.Initialize();
@ -156,9 +167,15 @@ public class SpineEditorUtilities : AssetPostprocessor {
EditorApplication.hierarchyWindowItemOnGUI += HierarchyWindowItemOnGUI;
HierarchyWindowChanged();
initialized = true;
}
public static void ConfirmInitialization(){
if(!initialized || Icons.skeleton == null)
Initialize();
}
static void HierarchyWindowChanged () {
static void HierarchyWindowChanged() {
skeletonRendererTable.Clear();
skeletonUtilityBoneTable.Clear();
@ -172,122 +189,353 @@ public class SpineEditorUtilities : AssetPostprocessor {
skeletonUtilityBoneTable.Add(b.gameObject.GetInstanceID(), b);
}
static void HierarchyWindowItemOnGUI (int instanceId, Rect selectionRect) {
static void HierarchyWindowItemOnGUI(int instanceId, Rect selectionRect) {
if (skeletonRendererTable.ContainsKey(instanceId)) {
Rect r = new Rect(selectionRect);
Rect r = new Rect(selectionRect);
r.x = r.width - 15;
r.width = 15;
GUI.Label(r, Icons.spine);
} else if (skeletonUtilityBoneTable.ContainsKey(instanceId)) {
Rect r = new Rect(selectionRect);
r.x -= 26;
Rect r = new Rect(selectionRect);
r.x -= 26;
if (skeletonUtilityBoneTable[instanceId] != null) {
if (skeletonUtilityBoneTable[instanceId].transform.childCount == 0)
r.x += 13;
r.y += 2;
if (skeletonUtilityBoneTable[instanceId] != null) {
if (skeletonUtilityBoneTable[instanceId].transform.childCount == 0)
r.x += 13;
r.width = 13;
r.height = 13;
r.y += 2;
if (skeletonUtilityBoneTable[instanceId].mode == SkeletonUtilityBone.Mode.Follow) {
GUI.DrawTexture(r, Icons.bone);
} else {
GUI.DrawTexture(r, Icons.poseBones);
}
r.width = 13;
r.height = 13;
if (skeletonUtilityBoneTable[instanceId].mode == SkeletonUtilityBone.Mode.Follow) {
GUI.DrawTexture(r, Icons.bone);
} else {
GUI.DrawTexture(r, Icons.poseBones);
}
}
}
[MenuItem("Assets/Spine/Ingest")]
static void IngestSpineProjectFromSelection () {
TextAsset spineJson = null;
TextAsset atlasText = null;
List<TextAsset> spineJsonList = new List<TextAsset>();
foreach (UnityEngine.Object o in Selection.objects) {
if (o.GetType() != typeof(TextAsset))
continue;
string fileName = Path.GetFileName(AssetDatabase.GetAssetPath(o));
if (fileName.EndsWith(".json"))
spineJson = (TextAsset)o;
else if (fileName.EndsWith(".atlas.txt"))
atlasText = (TextAsset)o;
}
if (spineJson == null) {
EditorUtility.DisplayDialog("Error!", "Spine JSON file not found in selection!", "OK");
return;
}
string primaryName = Path.GetFileNameWithoutExtension(spineJson.name);
string assetPath = Path.GetDirectoryName(AssetDatabase.GetAssetPath(spineJson));
if (atlasText == null) {
string atlasPath = assetPath + "/" + primaryName + ".atlas.txt";
atlasText = (TextAsset)AssetDatabase.LoadAssetAtPath(atlasPath, typeof(TextAsset));
}
AtlasAsset atlasAsset = IngestSpineAtlas(atlasText);
IngestSpineProject(spineJson, atlasAsset);
}
static void OnPostprocessAllAssets (string[] imported, string[] deleted, string[] moved, string[] movedFromAssetPaths) {
//debug
// return;
static void OnPostprocessAllAssets(string[] imported, string[] deleted, string[] moved, string[] movedFromAssetPaths) {
ImportSpineContent(imported, false);
}
public static void ImportSpineContent(string[] imported, bool reimport = false) {
AtlasAsset sharedAtlas = null;
System.Array.Sort<string>(imported);
List<string> atlasPaths = new List<string>();
List<string> imagePaths = new List<string>();
List<string> skeletonPaths = new List<string>();
foreach (string str in imported) {
if (Path.GetExtension(str).ToLower() == ".json") {
TextAsset spineJson = (TextAsset)AssetDatabase.LoadAssetAtPath(str, typeof(TextAsset));
if (IsSpineJSON(spineJson)) {
if (sharedAtlas != null) {
string spinePath = Path.GetDirectoryName(AssetDatabase.GetAssetPath(spineJson));
string atlasPath = Path.GetDirectoryName(AssetDatabase.GetAssetPath(sharedAtlas));
if (spinePath != atlasPath)
sharedAtlas = null;
string extension = Path.GetExtension(str).ToLower();
switch (extension) {
case ".txt":
if (str.EndsWith(".atlas.txt")) {
atlasPaths.Add(str);
}
SkeletonDataAsset data = AutoIngestSpineProject(spineJson, sharedAtlas);
if (data == null)
continue;
sharedAtlas = data.atlasAsset;
string dir = Path.GetDirectoryName(Path.GetDirectoryName(AssetDatabase.GetAssetPath(data)));
string prefabPath = Path.Combine(dir, data.skeletonJSON.name + ".prefab").Replace("\\", "/");
if (File.Exists(prefabPath) == false) {
SkeletonAnimation anim = SpawnAnimatedSkeleton(data);
PrefabUtility.CreatePrefab(prefabPath, anim.gameObject, ReplacePrefabOptions.ReplaceNameBased);
if (EditorApplication.isPlaying)
GameObject.Destroy(anim.gameObject);
else
GameObject.DestroyImmediate(anim.gameObject);
} else {
break;
case ".png":
case ".jpg":
imagePaths.Add(str);
break;
case ".json":
TextAsset spineJson = (TextAsset)AssetDatabase.LoadAssetAtPath(str, typeof(TextAsset));
if (IsSpineJSON(spineJson)) {
skeletonPaths.Add(str);
}
break;
}
}
List<AtlasAsset> atlases = new List<AtlasAsset>();
//import atlases first
foreach (string ap in atlasPaths) {
if (!reimport && CheckForValidAtlas(ap))
continue;
TextAsset atlasText = (TextAsset)AssetDatabase.LoadAssetAtPath(ap, typeof(TextAsset));
AtlasAsset atlas = IngestSpineAtlas(atlasText);
atlases.Add(atlas);
}
//import skeletons and match them with atlases
bool abortSkeletonImport = false;
foreach (string sp in skeletonPaths) {
if (!reimport && CheckForValidSkeletonData(sp)) {
Debug.Log("Automatically skipping: " + sp);
continue;
}
string dir = Path.GetDirectoryName(sp);
var localAtlases = FindAtlasesAtPath(dir);
var requiredPaths = GetRequiredAtlasRegions(sp);
var atlasMatch = GetMatchingAtlas(requiredPaths, localAtlases);
if (atlasMatch != null) {
IngestSpineProject(AssetDatabase.LoadAssetAtPath(sp, typeof(TextAsset)) as TextAsset, atlasMatch);
} else {
bool resolved = false;
while (!resolved) {
int result = EditorUtility.DisplayDialogComplex("Skeleton JSON Import Error!", "Could not find matching AtlasAsset for " + Path.GetFileNameWithoutExtension(sp), "Select", "Skip", "Abort");
switch (result) {
case -1:
Debug.Log("Select Atlas");
AtlasAsset selectedAtlas = GetAtlasDialog(Path.GetDirectoryName(sp));
if (selectedAtlas != null) {
localAtlases.Clear();
localAtlases.Add(selectedAtlas);
atlasMatch = GetMatchingAtlas(requiredPaths, localAtlases);
if (atlasMatch != null) {
resolved = true;
IngestSpineProject(AssetDatabase.LoadAssetAtPath(sp, typeof(TextAsset)) as TextAsset, atlasMatch);
}
}
break;
case 0:
var atlasList = MultiAtlasDialog(requiredPaths, Path.GetDirectoryName(sp), Path.GetFileNameWithoutExtension(sp));
if (atlasList != null)
IngestSpineProject(AssetDatabase.LoadAssetAtPath(sp, typeof(TextAsset)) as TextAsset, atlasList.ToArray());
resolved = true;
break;
case 1:
Debug.Log("Skipped importing: " + Path.GetFileName(sp));
resolved = true;
break;
case 2:
//abort
abortSkeletonImport = true;
resolved = true;
break;
}
}
}
if (abortSkeletonImport)
break;
}
//TODO: any post processing of images
}
static bool CheckForValidSkeletonData(string skeletonJSONPath) {
string dir = Path.GetDirectoryName(skeletonJSONPath);
TextAsset textAsset = (TextAsset)AssetDatabase.LoadAssetAtPath(skeletonJSONPath, typeof(TextAsset));
DirectoryInfo dirInfo = new DirectoryInfo(dir);
FileInfo[] files = dirInfo.GetFiles("*.asset");
foreach (var f in files) {
string localPath = dir + "/" + f.Name;
var obj = AssetDatabase.LoadAssetAtPath(localPath, typeof(Object));
if (obj is SkeletonDataAsset) {
var skeletonDataAsset = (SkeletonDataAsset)obj;
if (skeletonDataAsset.skeletonJSON == textAsset)
return true;
}
}
return false;
}
static bool CheckForValidAtlas(string atlasPath) {
string dir = Path.GetDirectoryName(atlasPath);
TextAsset textAsset = (TextAsset)AssetDatabase.LoadAssetAtPath(atlasPath, typeof(TextAsset));
DirectoryInfo dirInfo = new DirectoryInfo(dir);
FileInfo[] files = dirInfo.GetFiles("*.asset");
foreach (var f in files) {
string localPath = dir + "/" + f.Name;
var obj = AssetDatabase.LoadAssetAtPath(localPath, typeof(Object));
if (obj is AtlasAsset) {
var atlasAsset = (AtlasAsset)obj;
if (atlasAsset.atlasFile == textAsset)
return true;
}
}
return false;
}
static List<AtlasAsset> MultiAtlasDialog(List<string> requiredPaths, string initialDirectory, string header = "") {
List<AtlasAsset> atlasAssets = new List<AtlasAsset>();
bool resolved = false;
string lastAtlasPath = initialDirectory;
while (!resolved) {
StringBuilder sb = new StringBuilder();
sb.AppendLine(header);
sb.AppendLine("Atlases:");
if (atlasAssets.Count == 0) {
sb.AppendLine("\t--none--");
}
for (int i = 0; i < atlasAssets.Count; i++) {
sb.AppendLine("\t" + atlasAssets[i].name);
}
sb.AppendLine();
sb.AppendLine("Missing Regions:");
List<string> missingRegions = new List<string>(requiredPaths);
foreach (var atlasAsset in atlasAssets) {
var atlas = atlasAsset.GetAtlas();
for (int i = 0; i < missingRegions.Count; i++) {
if (atlas.FindRegion(missingRegions[i]) != null) {
missingRegions.RemoveAt(i);
i--;
}
}
}
if (missingRegions.Count == 0) {
break;
}
for (int i = 0; i < missingRegions.Count; i++) {
sb.AppendLine("\t" + missingRegions[i]);
}
int result = EditorUtility.DisplayDialogComplex("Atlas Selection", sb.ToString(), "Select", "Finish", "Abort");
switch(result){
case 0:
AtlasAsset selectedAtlasAsset = GetAtlasDialog(lastAtlasPath);
if (selectedAtlasAsset != null) {
var atlas = selectedAtlasAsset.GetAtlas();
bool hasValidRegion = false;
foreach (string str in missingRegions) {
if (atlas.FindRegion(str) != null) {
hasValidRegion = true;
break;
}
}
atlasAssets.Add(selectedAtlasAsset);
}
break;
case 1:
resolved = true;
break;
case 2:
atlasAssets = null;
resolved = true;
break;
}
}
return atlasAssets;
}
static AtlasAsset GetAtlasDialog(string dirPath) {
string path = EditorUtility.OpenFilePanel("Select AtlasAsset...", dirPath, "asset");
if (path == "")
return null;
int subLen = Application.dataPath.Length - 6;
string assetRelativePath = path.Substring(subLen, path.Length - subLen).Replace("\\", "/");
Object obj = AssetDatabase.LoadAssetAtPath(assetRelativePath, typeof(AtlasAsset));
if (obj == null || obj.GetType() != typeof(AtlasAsset))
return null;
return (AtlasAsset)obj;
}
public static List<string> GetRequiredAtlasRegions(string jsonPath) {
List<string> requiredPaths = new List<string>();
TextAsset spineJson = (TextAsset)AssetDatabase.LoadAssetAtPath(jsonPath, typeof(TextAsset));
StringReader reader = new StringReader(spineJson.text);
var root = Json.Deserialize(reader) as Dictionary<string, object>;
foreach (KeyValuePair<string, object> entry in (Dictionary<string, object>)root["skins"]) {
foreach (KeyValuePair<string, object> slotEntry in (Dictionary<string, object>)entry.Value) {
foreach (KeyValuePair<string, object> attachmentEntry in ((Dictionary<string, object>)slotEntry.Value)) {
var data = ((Dictionary<string, object>)attachmentEntry.Value);
if (data.ContainsKey("path"))
requiredPaths.Add((string)data["path"]);
else if (data.ContainsKey("name"))
requiredPaths.Add((string)data["name"]);
else
requiredPaths.Add(attachmentEntry.Key);
//requiredPaths.Add((string)sdf["path"]);
}
}
}
return requiredPaths;
}
static AtlasAsset GetMatchingAtlas(List<string> requiredPaths, List<AtlasAsset> atlasAssets) {
AtlasAsset atlasAssetMatch = null;
foreach (AtlasAsset a in atlasAssets) {
Atlas atlas = a.GetAtlas();
bool failed = false;
foreach (string regionPath in requiredPaths) {
if (atlas.FindRegion(regionPath) == null) {
failed = true;
break;
}
}
if (!failed) {
atlasAssetMatch = a;
break;
}
}
return atlasAssetMatch;
}
static bool IsSpineJSON (TextAsset asset) {
static List<AtlasAsset> FindAtlasesAtPath(string path) {
List<AtlasAsset> arr = new List<AtlasAsset>();
DirectoryInfo dir = new DirectoryInfo(path);
FileInfo[] assetInfoArr = dir.GetFiles("*.asset");
int subLen = Application.dataPath.Length - 6;
foreach (var f in assetInfoArr) {
string assetRelativePath = f.FullName.Substring(subLen, f.FullName.Length - subLen).Replace("\\", "/");
Object obj = AssetDatabase.LoadAssetAtPath(assetRelativePath, typeof(AtlasAsset));
if (obj != null) {
arr.Add(obj as AtlasAsset);
}
}
return arr;
}
public static bool IsSpineJSON(TextAsset asset) {
object obj = Json.Deserialize(new StringReader(asset.text));
if (obj == null) {
Debug.LogError("Is not valid JSON");
@ -307,51 +555,7 @@ public class SpineEditorUtilities : AssetPostprocessor {
return true;
}
static SkeletonDataAsset AutoIngestSpineProject (TextAsset spineJson, Object atlasSource = null) {
TextAsset atlasText = null;
AtlasAsset atlasAsset = null;
if (atlasSource != null) {
if (atlasSource.GetType() == typeof(TextAsset)) {
atlasText = (TextAsset)atlasSource;
} else if (atlasSource.GetType() == typeof(AtlasAsset)) {
atlasAsset = (AtlasAsset)atlasSource;
}
}
if (atlasText == null && atlasAsset == null) {
string primaryName = Path.GetFileNameWithoutExtension(spineJson.name);
string assetPath = Path.GetDirectoryName(AssetDatabase.GetAssetPath(spineJson));
if (atlasText == null) {
string atlasPath = assetPath + "/" + primaryName + ".atlas.txt";
atlasText = (TextAsset)AssetDatabase.LoadAssetAtPath(atlasPath, typeof(TextAsset));
if (atlasText == null) {
//can't find atlas, likely because using a shared atlas
bool abort = !EditorUtility.DisplayDialog("Atlas not Found", "Expecting " + spineJson.name + ".atlas\n" + "Press OK to select Atlas", "OK", "Abort");
if (abort) {
//do nothing, let it error later
} else {
string path = EditorUtility.OpenFilePanel("Find Atlas source...", Path.GetDirectoryName(Application.dataPath) + "/" + assetPath, "txt");
if (path != "") {
path = path.Replace("\\", "/");
path = path.Replace(Application.dataPath.Replace("\\", "/"), "Assets");
atlasText = (TextAsset)AssetDatabase.LoadAssetAtPath(path, typeof(TextAsset));
}
}
}
}
}
if (atlasAsset == null)
atlasAsset = IngestSpineAtlas(atlasText);
return IngestSpineProject(spineJson, atlasAsset);
}
static AtlasAsset IngestSpineAtlas (TextAsset atlasText) {
static AtlasAsset IngestSpineAtlas(TextAsset atlasText) {
if (atlasText == null) {
Debug.LogWarning("Atlas source cannot be null!");
return null;
@ -376,17 +580,17 @@ public class SpineEditorUtilities : AssetPostprocessor {
string[] atlasLines = atlasStr.Split('\n');
List<string> pageFiles = new List<string>();
for (int i = 0; i < atlasLines.Length-1; i++) {
for (int i = 0; i < atlasLines.Length - 1; i++) {
if (atlasLines[i].Length == 0)
pageFiles.Add(atlasLines[i + 1]);
}
atlasAsset.materials = new Material[pageFiles.Count];
for (int i = 0; i < pageFiles.Count; i++) {
string texturePath = assetPath + "/" + pageFiles[i];
Texture2D texture = (Texture2D)AssetDatabase.LoadAssetAtPath(texturePath, typeof(Texture2D));
TextureImporter texImporter = (TextureImporter)TextureImporter.GetAtPath(texturePath);
texImporter.textureFormat = TextureImporterFormat.AutomaticTruecolor;
texImporter.mipmapEnabled = false;
@ -396,13 +600,13 @@ public class SpineEditorUtilities : AssetPostprocessor {
EditorUtility.SetDirty(texImporter);
AssetDatabase.ImportAsset(texturePath);
AssetDatabase.SaveAssets();
string pageName = Path.GetFileNameWithoutExtension(pageFiles[i]);
//because this looks silly
if (pageName == primaryName && pageFiles.Count == 1)
pageName = "Material";
string materialPath = assetPath + "/" + primaryName + "_" + pageName + ".mat";
Material mat = (Material)AssetDatabase.LoadAssetAtPath(materialPath, typeof(Material));
@ -410,12 +614,12 @@ public class SpineEditorUtilities : AssetPostprocessor {
mat = new Material(Shader.Find(defaultShader));
AssetDatabase.CreateAsset(mat, materialPath);
}
mat.mainTexture = texture;
EditorUtility.SetDirty(mat);
AssetDatabase.SaveAssets();
atlasAsset.materials[i] = mat;
}
@ -429,40 +633,41 @@ public class SpineEditorUtilities : AssetPostprocessor {
return (AtlasAsset)AssetDatabase.LoadAssetAtPath(atlasPath, typeof(AtlasAsset));
}
static SkeletonDataAsset IngestSpineProject (TextAsset spineJson, AtlasAsset atlasAsset = null) {
static SkeletonDataAsset IngestSpineProject(TextAsset spineJson, params AtlasAsset[] atlasAssets) {
string primaryName = Path.GetFileNameWithoutExtension(spineJson.name);
string assetPath = Path.GetDirectoryName(AssetDatabase.GetAssetPath(spineJson));
string filePath = assetPath + "/" + primaryName + "_SkeletonData.asset";
if (spineJson != null && atlasAsset != null) {
if (spineJson != null && atlasAssets != null) {
SkeletonDataAsset skelDataAsset = (SkeletonDataAsset)AssetDatabase.LoadAssetAtPath(filePath, typeof(SkeletonDataAsset));
if (skelDataAsset == null) {
skelDataAsset = SkeletonDataAsset.CreateInstance<SkeletonDataAsset>();
skelDataAsset.atlasAsset = atlasAsset;
skelDataAsset.atlasAssets = atlasAssets;
skelDataAsset.skeletonJSON = spineJson;
skelDataAsset.fromAnimation = new string[0];
skelDataAsset.toAnimation = new string[0];
skelDataAsset.duration = new float[0];
skelDataAsset.defaultMix = defaultMix;
skelDataAsset.scale = defaultScale;
AssetDatabase.CreateAsset(skelDataAsset, filePath);
AssetDatabase.SaveAssets();
} else {
skelDataAsset.atlasAssets = atlasAssets;
skelDataAsset.Reset();
skelDataAsset.GetSkeletonData(true);
}
return skelDataAsset;
} else {
EditorUtility.DisplayDialog("Error!", "Must specify both Spine JSON and Atlas TextAsset", "OK");
EditorUtility.DisplayDialog("Error!", "Must specify both Spine JSON and AtlasAsset array", "OK");
return null;
}
}
[MenuItem("Assets/Spine/Spawn")]
static void SpawnAnimatedSkeleton () {
static void SpawnAnimatedSkeleton() {
Object[] arr = Selection.objects;
foreach (Object o in arr) {
string guid = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(o));
@ -474,59 +679,66 @@ public class SpineEditorUtilities : AssetPostprocessor {
}
[MenuItem("Assets/Spine/Spawn", true)]
static bool ValidateSpawnAnimatedSkeleton () {
static bool ValidateSpawnAnimatedSkeleton() {
Object[] arr = Selection.objects;
if (arr.Length == 0)
return false;
foreach (Object o in arr) {
if (o.GetType() != typeof(SkeletonDataAsset))
return false;
}
return true;
}
public static SkeletonAnimation SpawnAnimatedSkeleton (SkeletonDataAsset skeletonDataAsset, string skinName) {
public static SkeletonAnimation SpawnAnimatedSkeleton(SkeletonDataAsset skeletonDataAsset, string skinName) {
return SpawnAnimatedSkeleton(skeletonDataAsset, skeletonDataAsset.GetSkeletonData(true).FindSkin(skinName));
}
public static SkeletonAnimation SpawnAnimatedSkeleton (SkeletonDataAsset skeletonDataAsset, Skin skin = null) {
public static SkeletonAnimation SpawnAnimatedSkeleton(SkeletonDataAsset skeletonDataAsset, Skin skin = null) {
GameObject go = new GameObject(skeletonDataAsset.name.Replace("_SkeletonData", ""), typeof(MeshFilter), typeof(MeshRenderer), typeof(SkeletonAnimation));
SkeletonAnimation anim = go.GetComponent<SkeletonAnimation>();
anim.skeletonDataAsset = skeletonDataAsset;
bool requiresNormals = false;
foreach (Material m in anim.skeletonDataAsset.atlasAsset.materials) {
if (m.shader.name.Contains("Lit")) {
requiresNormals = true;
break;
foreach (AtlasAsset atlasAsset in anim.skeletonDataAsset.atlasAssets) {
foreach (Material m in atlasAsset.materials) {
if (m.shader.name.Contains("Lit")) {
requiresNormals = true;
break;
}
}
}
anim.calculateNormals = requiresNormals;
SkeletonData data = skeletonDataAsset.GetSkeletonData(true);
if (data == null) {
string reloadAtlasPath = AssetDatabase.GetAssetPath(skeletonDataAsset.atlasAsset);
skeletonDataAsset.atlasAsset = (AtlasAsset)AssetDatabase.LoadAssetAtPath(reloadAtlasPath, typeof(AtlasAsset));
for(int i = 0; i < skeletonDataAsset.atlasAssets.Length; i++){
string reloadAtlasPath = AssetDatabase.GetAssetPath(skeletonDataAsset.atlasAssets[i]);
skeletonDataAsset.atlasAssets[i] = (AtlasAsset)AssetDatabase.LoadAssetAtPath(reloadAtlasPath, typeof(AtlasAsset));
}
data = skeletonDataAsset.GetSkeletonData(true);
}
if (skin == null)
skin = data.DefaultSkin;
if (skin == null)
skin = data.Skins[0];
anim.Reset();
anim.skeleton.SetSkin(skin);
anim.initialSkinName = skin.Name;
anim.skeleton.Update(1);
anim.state.Update(1);
anim.state.Apply(anim.skeleton);
@ -534,4 +746,4 @@ public class SpineEditorUtilities : AssetPostprocessor {
return anim;
}
}
}

View File

@ -35,7 +35,7 @@ using UnityEngine;
using Spine;
public class SkeletonDataAsset : ScriptableObject {
public AtlasAsset atlasAsset;
public AtlasAsset[] atlasAssets;
public TextAsset skeletonJSON;
public float scale = 1;
public String[] fromAnimation;
@ -45,13 +45,14 @@ public class SkeletonDataAsset : ScriptableObject {
private SkeletonData skeletonData;
private AnimationStateData stateData;
public void Reset () {
public void Reset() {
skeletonData = null;
stateData = null;
}
public SkeletonData GetSkeletonData (bool quiet) {
if (atlasAsset == null) {
public SkeletonData GetSkeletonData(bool quiet) {
if (atlasAssets == null) {
atlasAssets = new AtlasAsset[0];
if (!quiet)
Debug.LogError("Atlas not set for SkeletonData asset: " + name, this);
Reset();
@ -65,16 +66,33 @@ public class SkeletonDataAsset : ScriptableObject {
return null;
}
Atlas atlas = atlasAsset.GetAtlas();
if (atlas == null) {
if (atlasAssets.Length == 0) {
Reset();
return null;
}
Atlas[] atlasArr = new Atlas[atlasAssets.Length];
for (int i = 0; i < atlasAssets.Length; i++) {
if (atlasAssets[i] == null) {
Reset();
return null;
}
atlasArr[i] = atlasAssets[i].GetAtlas();
if (atlasArr[i] == null) {
Reset();
return null;
}
}
if (skeletonData != null)
return skeletonData;
SkeletonJson json = new SkeletonJson(atlas);
SkeletonJson json = new SkeletonJson(atlasArr);
json.Scale = scale;
try {
skeletonData = json.ReadSkeletonData(new StringReader(skeletonJSON.text));
@ -95,7 +113,7 @@ public class SkeletonDataAsset : ScriptableObject {
return skeletonData;
}
public AnimationStateData GetAnimationStateData () {
public AnimationStateData GetAnimationStateData() {
if (stateData != null)
return stateData;
GetSkeletonData(false);

View File

@ -38,71 +38,71 @@ using System.Collections;
using Spine;
public static class SkeletonExtensions {
public static void SetColor (this Slot slot, Color color) {
public static void SetColor(this Slot slot, Color color) {
slot.A = color.a;
slot.R = color.r;
slot.G = color.g;
slot.B = color.b;
}
public static void SetColor (this Slot slot, Color32 color) {
public static void SetColor(this Slot slot, Color32 color) {
slot.A = color.a / 255f;
slot.R = color.r / 255f;
slot.G = color.g / 255f;
slot.B = color.b / 255f;
}
public static void SetColor (this RegionAttachment attachment, Color color) {
public static void SetColor(this RegionAttachment attachment, Color color) {
attachment.A = color.a;
attachment.R = color.r;
attachment.G = color.g;
attachment.B = color.b;
}
public static void SetColor (this RegionAttachment attachment, Color32 color) {
public static void SetColor(this RegionAttachment attachment, Color32 color) {
attachment.A = color.a / 255f;
attachment.R = color.r / 255f;
attachment.G = color.g / 255f;
attachment.B = color.b / 255f;
}
public static void SetColor (this MeshAttachment attachment, Color color) {
public static void SetColor(this MeshAttachment attachment, Color color) {
attachment.A = color.a;
attachment.R = color.r;
attachment.G = color.g;
attachment.B = color.b;
}
public static void SetColor (this MeshAttachment attachment, Color32 color) {
public static void SetColor(this MeshAttachment attachment, Color32 color) {
attachment.A = color.a / 255f;
attachment.R = color.r / 255f;
attachment.G = color.g / 255f;
attachment.B = color.b / 255f;
}
public static void SetColor (this SkinnedMeshAttachment attachment, Color color) {
public static void SetColor(this SkinnedMeshAttachment attachment, Color color) {
attachment.A = color.a;
attachment.R = color.r;
attachment.G = color.g;
attachment.B = color.b;
}
public static void SetColor (this SkinnedMeshAttachment attachment, Color32 color) {
public static void SetColor(this SkinnedMeshAttachment attachment, Color32 color) {
attachment.A = color.a / 255f;
attachment.R = color.r / 255f;
attachment.G = color.g / 255f;
attachment.B = color.b / 255f;
}
public static void SetPosition (this Bone bone, Vector2 position) {
public static void SetPosition(this Bone bone, Vector2 position) {
bone.X = position.x;
bone.Y = position.y;
}
public static void SetPosition (this Bone bone, Vector3 position) {
public static void SetPosition(this Bone bone, Vector3 position) {
bone.X = position.x;
bone.Y = position.y;
}
}
}

View File

@ -51,6 +51,14 @@ public class SkeletonRenderer : MonoBehaviour {
public float zSpacing;
public bool renderMeshes = true, immutableTriangles;
public bool logErrors = false;
[SpineSlot]
public string[] submeshSeparators = new string[0];
[HideInInspector]
public List<Slot> submeshSeparatorSlots = new List<Slot>();
private MeshFilter meshFilter;
private Mesh mesh1, mesh2;
private bool useMesh1;
@ -62,6 +70,7 @@ public class SkeletonRenderer : MonoBehaviour {
private Material[] sharedMaterials = new Material[0];
private readonly List<Material> submeshMaterials = new List<Material>();
private readonly List<Submesh> submeshes = new List<Submesh>();
public virtual void Reset () {
if (meshFilter != null)
@ -99,6 +108,12 @@ public class SkeletonRenderer : MonoBehaviour {
skeleton = new Skeleton(skeletonData);
if (initialSkinName != null && initialSkinName.Length > 0 && initialSkinName != "default")
skeleton.SetSkin(initialSkinName);
submeshSeparatorSlots.Clear();
for (int i = 0; i < submeshSeparators.Length; i++) {
submeshSeparatorSlots.Add(skeleton.FindSlot(submeshSeparators[i]));
}
if (OnReset != null)
OnReset(this);
}
@ -156,7 +171,8 @@ public class SkeletonRenderer : MonoBehaviour {
// Populate submesh when material changes.
Material material = (Material)((AtlasRegion)rendererObject).page.rendererObject;
if ((lastMaterial != material && lastMaterial != null) || slot.Data.name[0] == '*') {
if ((lastMaterial != material && lastMaterial != null) || submeshSeparatorSlots.Contains(slot)) {
AddSubmesh(lastMaterial, submeshStartSlotIndex, i, submeshTriangleCount, submeshFirstVertex, false);
submeshTriangleCount = 0;
submeshFirstVertex = vertexCount;

View File

@ -4,6 +4,7 @@ using System.Collections.Generic;
public class SkeletonUtilityKinematicShadow : MonoBehaviour {
public bool hideShadow = true;
public Transform parent;
Dictionary<Transform, Transform> shadowTable;
GameObject shadowRoot;
@ -12,7 +13,10 @@ public class SkeletonUtilityKinematicShadow : MonoBehaviour {
if (hideShadow)
shadowRoot.hideFlags = HideFlags.HideInHierarchy;
shadowRoot.transform.parent = transform.root;
if(parent == null)
shadowRoot.transform.parent = transform.root;
else
shadowRoot.transform.parent = parent;
shadowTable = new Dictionary<Transform, Transform>();

View File

@ -0,0 +1,198 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.1
*
* Copyright (c) 2013, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable and
* non-transferable license to install, execute and perform the Spine Runtimes
* Software (the "Software") solely for internal use. Without the written
* permission of Esoteric Software (typically granted by licensing Spine), you
* may not (a) modify, translate, adapt or otherwise create derivative works,
* improvements of the Software or develop new applications using the Software
* or (b) remove, delete, alter or obscure any trademarks or any copyright,
* trademark, patent or other intellectual property or proprietary rights
* notices on or in the Software, including any copy thereof. Redistributions
* in binary or source form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
/*****************************************************************************
* Spine Attributes created by Mitch Thompson
* Full irrevocable rights and permissions granted to Esoteric Software
*****************************************************************************/
using UnityEngine;
using System.Collections;
public class SpineSlot : PropertyAttribute {
public string startsWith = "";
public string dataField = "";
/// <summary>
/// Smart popup menu for Spine Slots
/// </summary>
/// <param name="startsWith">Filters popup results to elements that begin with supplied string.</param>
/// <param name="dataField">If specified, a locally scoped field with the name supplied by in dataField will be used to fill the popup results.
/// Valid types are SkeletonDataAsset and SkeletonRenderer (and derivatives).
/// If left empty and the script the attribute is applied to is derived from Component, GetComponent<SkeletonRenderer>() will be called as a fallback.
/// </param>
public SpineSlot(string startsWith = "", string dataField = "") {
this.startsWith = startsWith;
this.dataField = dataField;
}
}
public class SpineSkin : PropertyAttribute {
public string startsWith = "";
public string dataField = "";
/// <summary>
/// Smart popup menu for Spine Skins
/// </summary>
/// <param name="startsWith">Filters popup results to elements that begin with supplied string.</param>
/// <param name="dataField">If specified, a locally scoped field with the name supplied by in dataField will be used to fill the popup results.
/// Valid types are SkeletonDataAsset and SkeletonRenderer (and derivatives)
/// If left empty and the script the attribute is applied to is derived from Component, GetComponent<SkeletonRenderer>() will be called as a fallback.
/// </param>
public SpineSkin(string startsWith = "", string dataField = "") {
this.startsWith = startsWith;
this.dataField = dataField;
}
}
public class SpineAnimation : PropertyAttribute {
public string startsWith = "";
public string dataField = "";
/// <summary>
/// Smart popup menu for Spine Animations
/// </summary>
/// <param name="startsWith">Filters popup results to elements that begin with supplied string.</param>
/// <param name="dataField">If specified, a locally scoped field with the name supplied by in dataField will be used to fill the popup results.
/// Valid types are SkeletonDataAsset and SkeletonRenderer (and derivatives)
/// If left empty and the script the attribute is applied to is derived from Component, GetComponent<SkeletonRenderer>() will be called as a fallback.
/// </param>
public SpineAnimation(string startsWith = "", string dataField = "") {
this.startsWith = startsWith;
this.dataField = dataField;
}
}
public class SpineAttachment : PropertyAttribute {
public bool returnAttachmentPath = false;
public bool currentSkinOnly = false;
public bool placeholdersOnly = false;
public string dataField = "";
public string slotField = "";
public SpineAttachment() {
}
/// <summary>
/// Smart popup menu for Spine Attachments
/// </summary>
/// <param name="currentSkinOnly">Filters popup results to only include the current Skin. Only valid when a SkeletonRenderer is the data source.</param>
/// <param name="returnAttachmentPath">Returns a fully qualified path for an Attachment in the format "Skin/Slot/AttachmentName"</param>
/// <param name="placeholdersOnly">Filters popup results to exclude attachments that are not children of Skin Placeholders</param>
/// <param name="slotField">If specified, a locally scoped field with the name supplied by in slotField will be used to limit the popup results to children of a named slot</param>
/// <param name="dataField">If specified, a locally scoped field with the name supplied by in dataField will be used to fill the popup results.
/// Valid types are SkeletonDataAsset and SkeletonRenderer (and derivatives)
/// If left empty and the script the attribute is applied to is derived from Component, GetComponent<SkeletonRenderer>() will be called as a fallback.
/// </param>
public SpineAttachment(bool currentSkinOnly = true, bool returnAttachmentPath = false, bool placeholdersOnly = false, string slotField = "", string dataField = "") {
this.currentSkinOnly = currentSkinOnly;
this.returnAttachmentPath = returnAttachmentPath;
this.placeholdersOnly = placeholdersOnly;
this.slotField = slotField;
this.dataField = dataField;
}
public static Hierarchy GetHierarchy(string fullPath) {
return new Hierarchy(fullPath);
}
public static Spine.Attachment GetAttachment(string attachmentPath, Spine.SkeletonData skeletonData) {
var hierarchy = SpineAttachment.GetHierarchy(attachmentPath);
if (hierarchy.name == "")
return null;
return skeletonData.FindSkin(hierarchy.skin).GetAttachment(skeletonData.FindSlotIndex(hierarchy.slot), hierarchy.name);
}
public static Spine.Attachment GetAttachment(string attachmentPath, SkeletonDataAsset skeletonDataAsset) {
return GetAttachment(attachmentPath, skeletonDataAsset.GetSkeletonData(true));
}
public struct Hierarchy {
public string skin;
public string slot;
public string name;
public Hierarchy(string fullPath) {
string[] chunks = fullPath.Split(new char[]{'/'}, System.StringSplitOptions.RemoveEmptyEntries);
if (chunks.Length == 0) {
skin = "";
slot = "";
name = "";
return;
}
else if (chunks.Length < 2) {
throw new System.Exception("Cannot generate Attachment Hierarchy from string! Not enough components! [" + fullPath + "]");
}
skin = chunks[0];
slot = chunks[1];
name = "";
for (int i = 2; i < chunks.Length; i++) {
name += chunks[i];
}
}
}
}
public class SpineBone : PropertyAttribute {
public string startsWith = "";
public string dataField = "";
/// <summary>
/// Smart popup menu for Spine Bones
/// </summary>
/// <param name="startsWith">Filters popup results to elements that begin with supplied string.</param>
/// <param name="dataField">If specified, a locally scoped field with the name supplied by in dataField will be used to fill the popup results.
/// Valid types are SkeletonDataAsset and SkeletonRenderer (and derivatives)
/// If left empty and the script the attribute is applied to is derived from Component, GetComponent<SkeletonRenderer>() will be called as a fallback.
/// </param>
public SpineBone(string startsWith = "", string dataField = "") {
this.startsWith = startsWith;
this.dataField = dataField;
}
public static Spine.Bone GetBone(string boneName, SkeletonRenderer renderer) {
if (renderer.skeleton == null)
return null;
return renderer.skeleton.FindBone(boneName);
}
public static Spine.BoneData GetBoneData(string boneName, SkeletonDataAsset skeletonDataAsset) {
var data = skeletonDataAsset.GetSkeletonData(true);
return data.FindBone(boneName);
}
}
public class SpineAtlasRegion : PropertyAttribute {
//TODO: Standardize with Skeleton attributes
//NOTE: For now, relies on locally scoped field named "atlasAsset" for source.
}

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ce216f51ebc1d3f40929f4e58d1c65e5
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData: