Merge pull request #409 from Fenrisul/master

[Unity] Added BoundingBoxFollower
This commit is contained in:
Fenrisul 2015-04-10 01:34:06 -07:00
commit 9fae45306e
12 changed files with 332 additions and 43 deletions

View File

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

View 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];
}
}

View File

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

View File

@ -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 {

View File

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

View File

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

View File

@ -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:

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
@ -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:

View File

@ -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();

View File

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

View File

@ -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 ();

View File

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