mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-11 09:38:44 +08:00
Merge pull request #409 from Fenrisul/master
[Unity] Added BoundingBoxFollower
This commit is contained in:
commit
9fae45306e
@ -57,6 +57,8 @@ public class BoneFollower : MonoBehaviour {
|
||||
|
||||
|
||||
/// <summary>If a bone isn't set, boneName is used to find the bone.</summary>
|
||||
|
||||
[SpineBone(dataField: "skeletonRenderer")]
|
||||
public String boneName;
|
||||
public bool resetOnAwake = true;
|
||||
protected Transform cachedTransform;
|
||||
|
||||
160
spine-unity/Assets/spine-unity/BoundingBoxFollower.cs
Normal file
160
spine-unity/Assets/spine-unity/BoundingBoxFollower.cs
Normal file
@ -0,0 +1,160 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Spine;
|
||||
|
||||
[ExecuteInEditMode]
|
||||
public class BoundingBoxFollower : MonoBehaviour {
|
||||
|
||||
public SkeletonRenderer skeletonRenderer;
|
||||
|
||||
[SpineSlot(dataField: "skeletonRenderer", containsBoundingBoxes: true)]
|
||||
public string slotName;
|
||||
|
||||
//TODO: not this
|
||||
[Tooltip("LOL JK, Someone else do it!")]
|
||||
public bool use3DMeshCollider;
|
||||
|
||||
private Slot slot;
|
||||
private BoundingBoxAttachment currentAttachment;
|
||||
private PolygonCollider2D currentCollider;
|
||||
private string currentAttachmentName;
|
||||
private bool valid = false;
|
||||
private bool hasReset;
|
||||
|
||||
public Dictionary<BoundingBoxAttachment, PolygonCollider2D> colliderTable = new Dictionary<BoundingBoxAttachment, PolygonCollider2D>();
|
||||
public Dictionary<BoundingBoxAttachment, string> attachmentNameTable = new Dictionary<BoundingBoxAttachment, string>();
|
||||
|
||||
public string CurrentAttachmentName {
|
||||
get {
|
||||
return currentAttachmentName;
|
||||
}
|
||||
}
|
||||
|
||||
public BoundingBoxAttachment CurrentAttachment {
|
||||
get {
|
||||
return currentAttachment;
|
||||
}
|
||||
}
|
||||
|
||||
public PolygonCollider2D CurrentCollider {
|
||||
get {
|
||||
return currentCollider;
|
||||
}
|
||||
}
|
||||
|
||||
public Slot Slot {
|
||||
get {
|
||||
return slot;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void OnEnable () {
|
||||
ClearColliders();
|
||||
|
||||
if (skeletonRenderer == null)
|
||||
skeletonRenderer = GetComponentInParent<SkeletonRenderer>();
|
||||
|
||||
if (skeletonRenderer != null) {
|
||||
skeletonRenderer.OnReset -= HandleReset;
|
||||
skeletonRenderer.OnReset += HandleReset;
|
||||
}
|
||||
}
|
||||
|
||||
void OnDisable () {
|
||||
skeletonRenderer.OnReset -= HandleReset;
|
||||
}
|
||||
|
||||
void Start () {
|
||||
if (!hasReset && skeletonRenderer != null)
|
||||
HandleReset(skeletonRenderer);
|
||||
}
|
||||
|
||||
public void HandleReset (SkeletonRenderer renderer) {
|
||||
if (slotName == null || slotName == "")
|
||||
return;
|
||||
|
||||
hasReset = true;
|
||||
|
||||
ClearColliders();
|
||||
colliderTable.Clear();
|
||||
|
||||
if (skeletonRenderer.skeleton == null) {
|
||||
skeletonRenderer.OnReset -= HandleReset;
|
||||
skeletonRenderer.Reset();
|
||||
skeletonRenderer.OnReset += HandleReset;
|
||||
}
|
||||
|
||||
|
||||
var skeleton = skeletonRenderer.skeleton;
|
||||
slot = skeleton.FindSlot(slotName);
|
||||
int slotIndex = skeleton.FindSlotIndex(slotName);
|
||||
|
||||
foreach (var skin in skeleton.Data.Skins) {
|
||||
List<string> attachmentNames = new List<string>();
|
||||
skin.FindNamesForSlot(slotIndex, attachmentNames);
|
||||
|
||||
foreach (var name in attachmentNames) {
|
||||
var attachment = skin.GetAttachment(slotIndex, name);
|
||||
if (attachment is BoundingBoxAttachment) {
|
||||
var collider = SkeletonUtility.AddBoundingBoxAsComponent((BoundingBoxAttachment)attachment, gameObject, true);
|
||||
collider.enabled = false;
|
||||
collider.hideFlags = HideFlags.HideInInspector;
|
||||
colliderTable.Add((BoundingBoxAttachment)attachment, collider);
|
||||
attachmentNameTable.Add((BoundingBoxAttachment)attachment, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (colliderTable.Count == 0)
|
||||
valid = false;
|
||||
else
|
||||
valid = true;
|
||||
|
||||
if (!valid)
|
||||
Debug.LogWarning("Bounding Box Follower not valid! Slot [" + slotName + "] does not contain any Bounding Box Attachments!");
|
||||
}
|
||||
|
||||
void ClearColliders () {
|
||||
var colliders = GetComponents<PolygonCollider2D>();
|
||||
if (Application.isPlaying) {
|
||||
foreach (var c in colliders) {
|
||||
Destroy(c);
|
||||
}
|
||||
} else {
|
||||
foreach (var c in colliders) {
|
||||
DestroyImmediate(c);
|
||||
}
|
||||
}
|
||||
|
||||
colliderTable.Clear();
|
||||
attachmentNameTable.Clear();
|
||||
}
|
||||
|
||||
void LateUpdate () {
|
||||
if (!skeletonRenderer.valid)
|
||||
return;
|
||||
|
||||
if (slot != null) {
|
||||
if (slot.Attachment != currentAttachment)
|
||||
SetCurrent((BoundingBoxAttachment)slot.Attachment);
|
||||
}
|
||||
}
|
||||
|
||||
void SetCurrent (BoundingBoxAttachment attachment) {
|
||||
if (currentCollider)
|
||||
currentCollider.enabled = false;
|
||||
|
||||
if (attachment != null) {
|
||||
currentCollider = colliderTable[attachment];
|
||||
currentCollider.enabled = true;
|
||||
} else {
|
||||
currentCollider = null;
|
||||
}
|
||||
|
||||
currentAttachment = attachment;
|
||||
|
||||
currentAttachmentName = currentAttachment == null ? null : attachmentNameTable[attachment];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0317ee9ba6e1b1e49a030268e026d372
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
@ -35,6 +35,7 @@ using UnityEngine;
|
||||
public class BoneFollowerInspector : Editor {
|
||||
private SerializedProperty boneName, skeletonRenderer, followZPosition, followBoneRotation;
|
||||
BoneFollower component;
|
||||
bool needsReset;
|
||||
|
||||
void OnEnable () {
|
||||
skeletonRenderer = serializedObject.FindProperty("skeletonRenderer");
|
||||
@ -64,6 +65,12 @@ public class BoneFollowerInspector : Editor {
|
||||
}
|
||||
|
||||
override public void OnInspectorGUI () {
|
||||
if (needsReset) {
|
||||
component.Reset();
|
||||
component.DoUpdate();
|
||||
needsReset = false;
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
serializedObject.Update();
|
||||
|
||||
FindRenderer();
|
||||
@ -71,26 +78,16 @@ public class BoneFollowerInspector : Editor {
|
||||
EditorGUILayout.PropertyField(skeletonRenderer);
|
||||
|
||||
if (component.valid) {
|
||||
String[] bones = new String[1];
|
||||
try {
|
||||
bones = new String[component.skeletonRenderer.skeleton.Data.Bones.Count + 1];
|
||||
} catch {
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(boneName);
|
||||
if (EditorGUI.EndChangeCheck()) {
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
needsReset = true;
|
||||
serializedObject.Update();
|
||||
}
|
||||
|
||||
|
||||
|
||||
bones[0] = "<None>";
|
||||
for (int i = 0; i < bones.Length - 1; i++)
|
||||
bones[i + 1] = component.skeletonRenderer.skeleton.Data.Bones[i].Name;
|
||||
Array.Sort<String>(bones);
|
||||
int boneIndex = Math.Max(0, Array.IndexOf(bones, boneName.stringValue));
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.LabelField("Bone");
|
||||
EditorGUIUtility.LookLikeControls();
|
||||
boneIndex = EditorGUILayout.Popup(boneIndex, bones);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
boneName.stringValue = boneIndex == 0 ? null : bones[boneIndex];
|
||||
EditorGUILayout.PropertyField(followBoneRotation);
|
||||
EditorGUILayout.PropertyField(followZPosition);
|
||||
} else {
|
||||
|
||||
@ -0,0 +1,53 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System.Collections;
|
||||
|
||||
[CustomEditor(typeof(BoundingBoxFollower))]
|
||||
public class BoundingBoxFollowerInspector : Editor {
|
||||
SerializedProperty skeletonRenderer, slotName;
|
||||
BoundingBoxFollower follower;
|
||||
bool needToReset = false;
|
||||
|
||||
void OnEnable () {
|
||||
skeletonRenderer = serializedObject.FindProperty("skeletonRenderer");
|
||||
slotName = serializedObject.FindProperty("slotName");
|
||||
follower = (BoundingBoxFollower)target;
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI () {
|
||||
if (needToReset) {
|
||||
follower.HandleReset(null);
|
||||
needToReset = false;
|
||||
}
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(skeletonRenderer);
|
||||
EditorGUILayout.PropertyField(slotName, new GUIContent("Slot"));
|
||||
|
||||
if (EditorGUI.EndChangeCheck()){
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
needToReset = true;
|
||||
}
|
||||
|
||||
bool hasBone = follower.GetComponent<BoneFollower>() != null;
|
||||
|
||||
EditorGUI.BeginDisabledGroup(hasBone || follower.Slot == null);
|
||||
{
|
||||
if (GUILayout.Button(new GUIContent("Add Bone Follower", SpineEditorUtilities.Icons.bone))) {
|
||||
var boneFollower = follower.gameObject.AddComponent<BoneFollower>();
|
||||
boneFollower.boneName = follower.Slot.Data.BoneData.Name;
|
||||
}
|
||||
}
|
||||
EditorGUI.EndDisabledGroup();
|
||||
|
||||
|
||||
|
||||
//GUILayout.Space(20);
|
||||
GUILayout.Label("Attachment Names", EditorStyles.boldLabel);
|
||||
foreach (var kp in follower.attachmentNameTable) {
|
||||
string name = kp.Value;
|
||||
var collider = follower.colliderTable[kp.Key];
|
||||
bool isPlaceholder = name != kp.Key.Name;
|
||||
collider.enabled = EditorGUILayout.ToggleLeft(new GUIContent(!isPlaceholder ? name : name + " [" + kp.Key.Name + "]", isPlaceholder ? SpineEditorUtilities.Icons.skinPlaceholder : SpineEditorUtilities.Icons.boundingBox), collider.enabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 670a3cefa3853bd48b5da53a424fd542
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
@ -20,11 +20,8 @@ TextureImporter:
|
||||
isReadable: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 0
|
||||
cubemapConvolution: 0
|
||||
cubemapConvolutionSteps: 8
|
||||
cubemapConvolutionExponent: 1.5
|
||||
seamlessCubemap: 0
|
||||
textureFormat: -1
|
||||
textureFormat: -3
|
||||
maxTextureSize: 1024
|
||||
textureSettings:
|
||||
filterMode: -1
|
||||
@ -33,7 +30,6 @@ TextureImporter:
|
||||
wrapMode: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
rGBM: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
@ -49,5 +45,3 @@ TextureImporter:
|
||||
sprites: []
|
||||
spritePackingTag:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@ -5,8 +5,8 @@ TextureImporter:
|
||||
serializedVersion: 2
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 1
|
||||
linearTexture: 0
|
||||
enableMipMap: 0
|
||||
linearTexture: 1
|
||||
correctGamma: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
@ -20,20 +20,16 @@ TextureImporter:
|
||||
isReadable: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 0
|
||||
cubemapConvolution: 0
|
||||
cubemapConvolutionSteps: 8
|
||||
cubemapConvolutionExponent: 1.5
|
||||
seamlessCubemap: 0
|
||||
textureFormat: -1
|
||||
textureFormat: -3
|
||||
maxTextureSize: 1024
|
||||
textureSettings:
|
||||
filterMode: -1
|
||||
aniso: -1
|
||||
aniso: 1
|
||||
mipBias: -1
|
||||
wrapMode: -1
|
||||
nPOTScale: 1
|
||||
wrapMode: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
rGBM: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
@ -42,12 +38,10 @@ 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: []
|
||||
spritePackingTag:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@ -114,8 +114,35 @@ public class SpineSlotDrawer : PropertyDrawer {
|
||||
|
||||
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));
|
||||
if (name.StartsWith(attrib.startsWith)) {
|
||||
if (attrib.containsBoundingBoxes) {
|
||||
|
||||
int slotIndex = i;
|
||||
|
||||
List<Attachment> attachments = new List<Attachment>();
|
||||
foreach (var skin in data.Skins) {
|
||||
skin.FindAttachmentsForSlot(slotIndex, attachments);
|
||||
}
|
||||
|
||||
bool hasBoundingBox = false;
|
||||
foreach (var attachment in attachments) {
|
||||
if (attachment is BoundingBoxAttachment) {
|
||||
menu.AddItem(new GUIContent(name), name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
|
||||
hasBoundingBox = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasBoundingBox)
|
||||
menu.AddDisabledItem(new GUIContent(name));
|
||||
|
||||
|
||||
} else {
|
||||
menu.AddItem(new GUIContent(name), name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
menu.ShowAsContext();
|
||||
|
||||
@ -149,6 +149,7 @@ public class SpineEditorUtilities : AssetPostprocessor {
|
||||
public static string editorGUIPath = "";
|
||||
static Dictionary<int, GameObject> skeletonRendererTable;
|
||||
static Dictionary<int, SkeletonUtilityBone> skeletonUtilityBoneTable;
|
||||
static Dictionary<int, BoundingBoxFollower> boundingBoxFollowerTable;
|
||||
public static float defaultScale = 0.01f;
|
||||
public static float defaultMix = 0.2f;
|
||||
public static string defaultShader = "Spine/Skeleton";
|
||||
@ -172,6 +173,7 @@ public class SpineEditorUtilities : AssetPostprocessor {
|
||||
|
||||
skeletonRendererTable = new Dictionary<int, GameObject>();
|
||||
skeletonUtilityBoneTable = new Dictionary<int, SkeletonUtilityBone>();
|
||||
boundingBoxFollowerTable = new Dictionary<int, BoundingBoxFollower>();
|
||||
|
||||
EditorApplication.hierarchyWindowChanged += HierarchyWindowChanged;
|
||||
EditorApplication.hierarchyWindowItemOnGUI += HierarchyWindowItemOnGUI;
|
||||
@ -188,15 +190,19 @@ public class SpineEditorUtilities : AssetPostprocessor {
|
||||
static void HierarchyWindowChanged () {
|
||||
skeletonRendererTable.Clear();
|
||||
skeletonUtilityBoneTable.Clear();
|
||||
boundingBoxFollowerTable.Clear();
|
||||
|
||||
SkeletonRenderer[] arr = Object.FindObjectsOfType<SkeletonRenderer>();
|
||||
|
||||
foreach (SkeletonRenderer r in arr)
|
||||
skeletonRendererTable.Add(r.gameObject.GetInstanceID(), r.gameObject);
|
||||
|
||||
SkeletonUtilityBone[] boneArr = Object.FindObjectsOfType<SkeletonUtilityBone>();
|
||||
foreach (SkeletonUtilityBone b in boneArr)
|
||||
skeletonUtilityBoneTable.Add(b.gameObject.GetInstanceID(), b);
|
||||
|
||||
BoundingBoxFollower[] bbfArr = Object.FindObjectsOfType<BoundingBoxFollower>();
|
||||
foreach (BoundingBoxFollower bbf in bbfArr)
|
||||
boundingBoxFollowerTable.Add(bbf.gameObject.GetInstanceID(), bbf);
|
||||
}
|
||||
|
||||
static void HierarchyWindowItemOnGUI (int instanceId, Rect selectionRect) {
|
||||
@ -226,6 +232,21 @@ public class SpineEditorUtilities : AssetPostprocessor {
|
||||
}
|
||||
}
|
||||
|
||||
} else if (boundingBoxFollowerTable.ContainsKey(instanceId)) {
|
||||
Rect r = new Rect(selectionRect);
|
||||
r.x -= 26;
|
||||
|
||||
if (boundingBoxFollowerTable[instanceId] != null) {
|
||||
if (boundingBoxFollowerTable[instanceId].transform.childCount == 0)
|
||||
r.x += 13;
|
||||
|
||||
r.y += 2;
|
||||
|
||||
r.width = 13;
|
||||
r.height = 13;
|
||||
|
||||
GUI.DrawTexture(r, Icons.boundingBox);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -101,6 +101,28 @@ public class SkeletonUtility : MonoBehaviour {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static PolygonCollider2D AddBoundingBoxAsComponent (BoundingBoxAttachment boundingBox, GameObject gameObject, bool isTrigger = true) {
|
||||
if (boundingBox == null)
|
||||
return null;
|
||||
|
||||
var collider = gameObject.AddComponent<PolygonCollider2D>();
|
||||
collider.isTrigger = isTrigger;
|
||||
float[] floats = boundingBox.Vertices;
|
||||
int floatCount = floats.Length;
|
||||
int vertCount = floatCount / 2;
|
||||
|
||||
Vector2[] verts = new Vector2[vertCount];
|
||||
int v = 0;
|
||||
for (int i = 0; i < floatCount; i += 2, v++) {
|
||||
verts[v].x = floats[i];
|
||||
verts[v].y = floats[i + 1];
|
||||
}
|
||||
|
||||
collider.SetPath(0, verts);
|
||||
|
||||
return collider;
|
||||
}
|
||||
|
||||
|
||||
public delegate void SkeletonUtilityDelegate ();
|
||||
|
||||
|
||||
@ -38,6 +38,7 @@ using System.Collections;
|
||||
public class SpineSlot : PropertyAttribute {
|
||||
public string startsWith = "";
|
||||
public string dataField = "";
|
||||
public bool containsBoundingBoxes = false;
|
||||
|
||||
/// <summary>
|
||||
/// Smart popup menu for Spine Slots
|
||||
@ -47,9 +48,11 @@ public class SpineSlot : PropertyAttribute {
|
||||
/// 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 = "") {
|
||||
/// <param name="containsBoundingBoxes">Disables popup results that don't contain bounding box attachments when true.</param>
|
||||
public SpineSlot(string startsWith = "", string dataField = "", bool containsBoundingBoxes = false) {
|
||||
this.startsWith = startsWith;
|
||||
this.dataField = dataField;
|
||||
this.containsBoundingBoxes = containsBoundingBoxes;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user