mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-04 22:34:53 +08:00
Merge branch 'dev' into spine-lua-3.5
This commit is contained in:
commit
03070b9604
@ -188,7 +188,8 @@ static void _sortPathConstraintAttachmentBones(_spSkeleton* const internal, spAt
|
||||
int i = 0;
|
||||
while (i < pathBonesCount) {
|
||||
int boneCount = pathBones[i++];
|
||||
for (int n = i + boneCount; i < n; i++)
|
||||
int n;
|
||||
for (n = i + boneCount; i < n; i++)
|
||||
_sortBone(internal, bones[pathBones[i]]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -136,8 +136,8 @@ public class Animation {
|
||||
static public interface Timeline {
|
||||
/** Sets the value(s) for the specified time.
|
||||
* @param events May be null to not collect fired events.
|
||||
* @param setupPose If true, the timeline is mixed with the setup pose, else it is mixed with the current pose. Passing true
|
||||
* when alpha is 1 is slightly more efficient.
|
||||
* @param setupPose True when the timeline is mixed with the setup pose, false when it is mixed with the current pose.
|
||||
* Passing true when alpha is 1 is slightly more efficient.
|
||||
* @param mixingOut True when mixing over time toward the setup or current pose, false when mixing toward the keyed pose.
|
||||
* Irrelevant when alpha is 1. */
|
||||
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha, boolean setupPose,
|
||||
@ -916,7 +916,7 @@ public class Animation {
|
||||
if (time < frames[0]) return; // Time is before first frame.
|
||||
|
||||
// BOZO - Finish timelines handling setupPose and mixingOut from here down.
|
||||
|
||||
|
||||
IkConstraint constraint = skeleton.ikConstraints.get(ikConstraintIndex);
|
||||
|
||||
if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame.
|
||||
|
||||
@ -52,7 +52,7 @@ public class AnimationState {
|
||||
static private final Animation emptyAnimation = new Animation("<empty>", new Array(0), 0);
|
||||
|
||||
private AnimationStateData data;
|
||||
private final Array<TrackEntry> tracks = new Array();
|
||||
final Array<TrackEntry> tracks = new Array();
|
||||
private final Array<Event> events = new Array();
|
||||
final Array<AnimationStateListener> listeners = new Array();
|
||||
private final EventQueue queue = new EventQueue();
|
||||
@ -60,9 +60,6 @@ public class AnimationState {
|
||||
boolean animationsChanged;
|
||||
private float timeScale = 1;
|
||||
|
||||
StringBuilder last = new StringBuilder();
|
||||
StringBuilder log = new StringBuilder();
|
||||
|
||||
final Pool<TrackEntry> trackEntryPool = new Pool() {
|
||||
protected Object newObject () {
|
||||
return new TrackEntry();
|
||||
@ -112,9 +109,9 @@ public class AnimationState {
|
||||
}
|
||||
continue;
|
||||
}
|
||||
updateMixingFrom(current, delta);
|
||||
updateMixingFrom(current, delta, true);
|
||||
} else {
|
||||
updateMixingFrom(current, delta);
|
||||
updateMixingFrom(current, delta, true);
|
||||
// Clear the track when there is no next entry, the track end time is reached, and there is no mixingFrom.
|
||||
if (current.trackLast >= current.trackEnd && current.mixingFrom == null) {
|
||||
tracks.set(i, null);
|
||||
@ -130,11 +127,11 @@ public class AnimationState {
|
||||
queue.drain();
|
||||
}
|
||||
|
||||
private void updateMixingFrom (TrackEntry entry, float delta) {
|
||||
private void updateMixingFrom (TrackEntry entry, float delta, boolean canEnd) {
|
||||
TrackEntry from = entry.mixingFrom;
|
||||
if (from == null) return;
|
||||
|
||||
if (entry.mixTime >= entry.mixDuration && entry.mixTime > 0) {
|
||||
if (canEnd && entry.mixTime >= entry.mixDuration && entry.mixTime > 0) {
|
||||
queue.end(from);
|
||||
TrackEntry newFrom = from.mixingFrom;
|
||||
entry.mixingFrom = newFrom;
|
||||
@ -150,7 +147,7 @@ public class AnimationState {
|
||||
from.trackTime += mixingFromDelta;
|
||||
entry.mixTime += mixingFromDelta;
|
||||
|
||||
updateMixingFrom(from, delta);
|
||||
updateMixingFrom(from, delta, canEnd && from.alpha == 1);
|
||||
}
|
||||
|
||||
/** Poses the skeleton using the track entry animations. There are no side effects other than invoking listeners, so the
|
||||
@ -161,7 +158,7 @@ public class AnimationState {
|
||||
|
||||
Array<Event> events = this.events;
|
||||
|
||||
for (int i = 0; i < tracks.size; i++) {
|
||||
for (int i = 0, n = tracks.size; i < n; i++) {
|
||||
TrackEntry current = tracks.get(i);
|
||||
if (current == null || current.delay > 0) continue;
|
||||
|
||||
@ -171,24 +168,24 @@ public class AnimationState {
|
||||
|
||||
// Apply current entry.
|
||||
float animationLast = current.animationLast, animationTime = current.getAnimationTime();
|
||||
Array<Timeline> timelines = current.animation.timelines;
|
||||
log("apply current: " + current + ", mix: " + mix + " * " + current.alpha);
|
||||
int timelineCount = current.animation.timelines.size;
|
||||
Object[] timelines = current.animation.timelines.items;
|
||||
if (mix == 1) {
|
||||
for (int ii = 0, n = timelines.size; ii < n; ii++)
|
||||
timelines.get(ii).apply(skeleton, animationLast, animationTime, events, 1, false, false);
|
||||
for (int ii = 0; ii < timelineCount; ii++)
|
||||
((Timeline)timelines[ii]).apply(skeleton, animationLast, animationTime, events, 1, true, false);
|
||||
} else {
|
||||
boolean firstFrame = current.timelinesRotation.size == 0;
|
||||
if (firstFrame) current.timelinesRotation.setSize(timelines.size << 1);
|
||||
if (firstFrame) current.timelinesRotation.setSize(timelineCount << 1);
|
||||
float[] timelinesRotation = current.timelinesRotation.items;
|
||||
|
||||
boolean[] timelinesFirst = current.timelinesFirst.items;
|
||||
for (int ii = 0, n = timelines.size; ii < n; ii++) {
|
||||
Timeline timeline = timelines.get(ii);
|
||||
for (int ii = 0; ii < timelineCount; ii++) {
|
||||
Timeline timeline = (Timeline)timelines[ii];
|
||||
if (timeline instanceof RotateTimeline) {
|
||||
applyRotateTimeline((RotateTimeline)timeline, skeleton, animationLast, animationTime, events, mix,
|
||||
timelinesFirst[ii], false, timelinesRotation, ii << 1, firstFrame);
|
||||
} else {
|
||||
applyRotateTimeline(timeline, skeleton, animationTime, mix, timelinesFirst[ii], timelinesRotation, ii << 1,
|
||||
firstFrame);
|
||||
} else
|
||||
timeline.apply(skeleton, animationLast, animationTime, events, mix, timelinesFirst[ii], false);
|
||||
}
|
||||
}
|
||||
}
|
||||
queueEvents(current, animationTime);
|
||||
@ -197,62 +194,44 @@ public class AnimationState {
|
||||
}
|
||||
|
||||
queue.drain();
|
||||
|
||||
if (!log.toString().equals(last.toString())) {
|
||||
System.out.println(log);
|
||||
last.setLength(0);
|
||||
last.append(log);
|
||||
}
|
||||
log.setLength(0);
|
||||
}
|
||||
|
||||
void log (String m) {
|
||||
log.append(m);
|
||||
log.append('\n');
|
||||
}
|
||||
|
||||
private float applyMixingFrom (TrackEntry entry, Skeleton skeleton, float alpha) {
|
||||
TrackEntry from = entry.mixingFrom;
|
||||
if (from.mixingFrom != null) applyMixingFrom(from, skeleton, alpha);
|
||||
|
||||
float mix;
|
||||
if (entry.mixDuration == 0) // Single frame mix to undo mixingFrom changes.
|
||||
mix = 1;
|
||||
else {
|
||||
mix = alpha * entry.mixTime / entry.mixDuration;
|
||||
mix = entry.mixTime / entry.mixDuration;
|
||||
if (mix > 1) mix = 1;
|
||||
mix *= alpha;
|
||||
}
|
||||
|
||||
TrackEntry from = entry.mixingFrom;
|
||||
if (from.mixingFrom != null) applyMixingFrom(from, skeleton, alpha);
|
||||
|
||||
Array<Event> events = mix < from.eventThreshold ? this.events : null;
|
||||
boolean attachments = mix < from.attachmentThreshold, drawOrder = mix < from.drawOrderThreshold;
|
||||
|
||||
float animationLast = from.animationLast, animationTime = from.getAnimationTime();
|
||||
Array<Timeline> timelines = from.animation.timelines;
|
||||
int timelineCount = timelines.size;
|
||||
boolean[] timelinesFirst = from.timelinesFirst.items, timelinesLast = from.timelinesLast.items;
|
||||
float alphaFull = from.alpha, alphaMix = alphaFull * (1 - mix);
|
||||
alpha = from.alpha * (1 - mix);
|
||||
int timelineCount = from.animation.timelines.size;
|
||||
Object[] timelines = from.animation.timelines.items;
|
||||
boolean[] timelinesFirst = from.timelinesFirst.items;
|
||||
|
||||
boolean firstFrame = from.timelinesRotation.size == 0;
|
||||
if (firstFrame) from.timelinesRotation.setSize(timelineCount << 1);
|
||||
float[] timelinesRotation = from.timelinesRotation.items;
|
||||
|
||||
log("applyMixingFrom: " + entry.mixingFrom + " -> " + entry + ", mix: " + entry.mixTime / entry.mixDuration);
|
||||
if (timelineCount == 0) log("apply from: " + from + " " + alphaFull + " * " + entry.alpha);
|
||||
|
||||
for (int i = 0; i < timelineCount; i++) {
|
||||
Timeline timeline = timelines.get(i);
|
||||
Timeline timeline = (Timeline)timelines[i];
|
||||
boolean setupPose = timelinesFirst[i];
|
||||
float a = timelinesLast[i] ? alphaMix : alphaFull;
|
||||
log("apply from: " + from + " " + a + " * " + entry.alpha);
|
||||
if (timeline instanceof RotateTimeline) {
|
||||
applyRotateTimeline((RotateTimeline)timeline, skeleton, animationLast, animationTime, events, a, setupPose, setupPose,
|
||||
timelinesRotation, i << 1, firstFrame);
|
||||
} else {
|
||||
if (timeline instanceof RotateTimeline)
|
||||
applyRotateTimeline(timeline, skeleton, animationTime, alpha, setupPose, timelinesRotation, i << 1, firstFrame);
|
||||
else {
|
||||
if (!setupPose) {
|
||||
if (!attachments && timeline instanceof AttachmentTimeline) continue;
|
||||
if (!drawOrder && timeline instanceof DrawOrderTimeline) continue;
|
||||
}
|
||||
timeline.apply(skeleton, animationLast, animationTime, events, a, setupPose, setupPose);
|
||||
timeline.apply(skeleton, animationLast, animationTime, events, alpha, setupPose, true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -263,18 +242,18 @@ public class AnimationState {
|
||||
return mix;
|
||||
}
|
||||
|
||||
/** @param events May be null. */
|
||||
private void applyRotateTimeline (RotateTimeline timeline, Skeleton skeleton, float lastTime, float time, Array<Event> events,
|
||||
float alpha, boolean setupPose, boolean mixingOut, float[] timelinesRotation, int i, boolean firstFrame) {
|
||||
private void applyRotateTimeline (Timeline timeline, Skeleton skeleton, float time, float alpha, boolean setupPose,
|
||||
float[] timelinesRotation, int i, boolean firstFrame) {
|
||||
if (alpha == 1) {
|
||||
timeline.apply(skeleton, lastTime, time, events, 1, setupPose, setupPose);
|
||||
timeline.apply(skeleton, 0, time, null, 1, setupPose, false);
|
||||
return;
|
||||
}
|
||||
|
||||
float[] frames = timeline.frames;
|
||||
RotateTimeline rotateTimeline = (RotateTimeline)timeline;
|
||||
float[] frames = rotateTimeline.frames;
|
||||
if (time < frames[0]) return; // Time is before first frame.
|
||||
|
||||
Bone bone = skeleton.bones.get(timeline.boneIndex);
|
||||
Bone bone = skeleton.bones.get(rotateTimeline.boneIndex);
|
||||
|
||||
float r2;
|
||||
if (time >= frames[frames.length - ENTRIES]) // Time is after last frame.
|
||||
@ -284,7 +263,7 @@ public class AnimationState {
|
||||
int frame = Animation.binarySearch(frames, time, ENTRIES);
|
||||
float prevRotation = frames[frame + PREV_ROTATION];
|
||||
float frameTime = frames[frame];
|
||||
float percent = timeline.getCurvePercent((frame >> 1) - 1,
|
||||
float percent = rotateTimeline.getCurvePercent((frame >> 1) - 1,
|
||||
1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
|
||||
|
||||
r2 = frames[frame + ROTATION] - prevRotation;
|
||||
@ -293,7 +272,7 @@ public class AnimationState {
|
||||
r2 -= (16384 - (int)(16384.499999999996 - r2 / 360)) * 360;
|
||||
}
|
||||
|
||||
// Mix between two rotations using the direction of the shortest route on the first frame while detecting crosses.
|
||||
// Mix between rotations using the direction of the shortest route on the first frame while detecting crosses.
|
||||
float r1 = setupPose ? bone.data.rotation : bone.rotation;
|
||||
float total, diff = r2 - r1;
|
||||
if (diff == 0) {
|
||||
@ -319,7 +298,7 @@ public class AnimationState {
|
||||
if (Math.abs(lastTotal) > 180) lastTotal += 360 * Math.signum(lastTotal);
|
||||
dir = current;
|
||||
}
|
||||
total = diff + lastTotal - lastTotal % 360; // Keep loops part of lastTotal.
|
||||
total = diff + lastTotal - lastTotal % 360; // Store loops as part of lastTotal.
|
||||
if (dir != current) total += 360 * Math.signum(lastTotal);
|
||||
timelinesRotation[i] = total;
|
||||
}
|
||||
@ -405,19 +384,12 @@ public class AnimationState {
|
||||
if (from != null) {
|
||||
queue.interrupt(from);
|
||||
current.mixingFrom = from;
|
||||
// entry.mixTime = Math.max(0, entry.mixDuration - current.trackTime);
|
||||
// log("setCurrent mixTime: " + entry.mixDuration + " - " + current.trackTime + " = " + entry.mixTime);
|
||||
current.mixTime = 0;
|
||||
|
||||
from.timelinesRotation.clear(); // BOZO - Needed? Recursive?
|
||||
from.timelinesRotation.clear();
|
||||
|
||||
// float alpha = 1;
|
||||
float duration = from.animationEnd - from.animationStart;
|
||||
if (duration > 0) from.alpha *= (from.getAnimationTime() - from.animationStart) / duration;
|
||||
// do {
|
||||
// from.alpha *= alpha;
|
||||
// from = from.mixingFrom;
|
||||
// } while (from != null);
|
||||
// If not completely mixed in, set alpha so mixing out happens from current mix to zero.
|
||||
if (from.mixingFrom != null) from.alpha *= Math.min(from.mixTime / from.mixDuration, 1);
|
||||
}
|
||||
|
||||
queue.start(current);
|
||||
@ -592,32 +564,6 @@ public class AnimationState {
|
||||
TrackEntry entry = tracks.get(i);
|
||||
if (entry != null) checkTimelinesFirst(entry);
|
||||
}
|
||||
|
||||
// Compute timelinesLast from highest to lowest track entries that have mixingFrom.
|
||||
propertyIDs.clear();
|
||||
int lowestMixingFrom = n;
|
||||
for (i = 0; i < n; i++) { // Find lowest with a mixingFrom entry.
|
||||
TrackEntry entry = tracks.get(i);
|
||||
if (entry == null) continue;
|
||||
if (entry.mixingFrom != null) {
|
||||
lowestMixingFrom = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (i = n - 1; i >= lowestMixingFrom; i--) {
|
||||
TrackEntry entry = tracks.get(i);
|
||||
if (entry == null) continue;
|
||||
|
||||
Array<Timeline> timelines = entry.animation.timelines;
|
||||
for (int ii = 0, nn = timelines.size; ii < nn; ii++)
|
||||
propertyIDs.add(timelines.get(ii).getPropertyId());
|
||||
|
||||
entry = entry.mixingFrom;
|
||||
while (entry != null) {
|
||||
checkTimelinesUsage(entry, entry.timelinesLast);
|
||||
entry = entry.mixingFrom;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** From last to first mixingFrom entries, sets timelinesFirst to true on last, calls checkTimelineUsage on rest. */
|
||||
@ -727,7 +673,7 @@ public class AnimationState {
|
||||
float animationStart, animationEnd, animationLast, nextAnimationLast;
|
||||
float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale;
|
||||
float alpha, mixTime, mixDuration, mixAlpha;
|
||||
final BooleanArray timelinesFirst = new BooleanArray(), timelinesLast = new BooleanArray();
|
||||
final BooleanArray timelinesFirst = new BooleanArray();
|
||||
final FloatArray timelinesRotation = new FloatArray();
|
||||
|
||||
public void reset () {
|
||||
@ -736,7 +682,6 @@ public class AnimationState {
|
||||
animation = null;
|
||||
listener = null;
|
||||
timelinesFirst.clear();
|
||||
timelinesLast.clear();
|
||||
timelinesRotation.clear();
|
||||
}
|
||||
|
||||
|
||||
@ -153,28 +153,25 @@ public class Bone implements Updatable {
|
||||
break;
|
||||
}
|
||||
case noRotationOrReflection: {
|
||||
float psx = (float)Math.sqrt(pa * pa + pc * pc), psy, prx;
|
||||
if (psx > 0.0001f) {
|
||||
psy = Math.abs((pa * pd - pb * pc) / psx);
|
||||
float s = pa * pa + pc * pc, prx;
|
||||
if (s > 0.0001f) {
|
||||
s = Math.abs(pa * pd - pb * pc) / s;
|
||||
pb = pc * s;
|
||||
pd = pa * s;
|
||||
prx = atan2(pc, pa) * radDeg;
|
||||
} else {
|
||||
psx = 0;
|
||||
psy = (float)Math.sqrt(pb * pb + pd * pd);
|
||||
pa = 0;
|
||||
pc = 0;
|
||||
prx = 90 - atan2(pd, pb) * radDeg;
|
||||
}
|
||||
float cos = cosDeg(prx), sin = sinDeg(prx);
|
||||
pa = cos * psx;
|
||||
pb = -sin * psy;
|
||||
pc = sin * psx;
|
||||
pd = cos * psy;
|
||||
float rx = rotation + shearX - prx;
|
||||
float ry = rotation + shearY - prx + 90;
|
||||
float la = cosDeg(rx) * scaleX;
|
||||
float lb = cosDeg(ry) * scaleY;
|
||||
float lc = sinDeg(rx) * scaleX;
|
||||
float ld = sinDeg(ry) * scaleY;
|
||||
a = pa * la + pb * lc;
|
||||
b = pa * lb + pb * ld;
|
||||
a = pa * la - pb * lc;
|
||||
b = pa * lb - pb * ld;
|
||||
c = pc * la + pd * lc;
|
||||
d = pc * lb + pd * ld;
|
||||
break;
|
||||
|
||||
@ -447,6 +447,7 @@ public class SkeletonViewer extends ApplicationAdapter {
|
||||
table.add(new Label("", skin, "default", Color.LIGHT_GRAY)); // Version.
|
||||
}
|
||||
|
||||
// Events.
|
||||
window.addListener(new InputListener() {
|
||||
public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) {
|
||||
event.cancel();
|
||||
|
||||
@ -38,14 +38,14 @@ namespace Spine.Unity {
|
||||
public class AtlasAsset : ScriptableObject {
|
||||
public TextAsset atlasFile;
|
||||
public Material[] materials;
|
||||
private Atlas atlas;
|
||||
protected Atlas atlas;
|
||||
|
||||
public void Reset () {
|
||||
public virtual void Reset () {
|
||||
atlas = null;
|
||||
}
|
||||
|
||||
/// <returns>The atlas or null if it could not be loaded.</returns>
|
||||
public Atlas GetAtlas () {
|
||||
public virtual Atlas GetAtlas () {
|
||||
if (atlasFile == null) {
|
||||
Debug.LogError("Atlas file not set for atlas asset: " + name, this);
|
||||
Reset();
|
||||
|
||||
@ -35,9 +35,11 @@ using Spine;
|
||||
namespace Spine.Unity.Editor {
|
||||
|
||||
[CustomEditor(typeof(SkeletonAnimation))]
|
||||
[CanEditMultipleObjects]
|
||||
public class SkeletonAnimationInspector : SkeletonRendererInspector {
|
||||
protected SerializedProperty animationName, loop, timeScale, autoReset;
|
||||
protected bool wasAnimationNameChanged;
|
||||
protected bool requireRepaint;
|
||||
|
||||
protected override void OnEnable () {
|
||||
base.OnEnable();
|
||||
@ -46,64 +48,93 @@ namespace Spine.Unity.Editor {
|
||||
timeScale = serializedObject.FindProperty("timeScale");
|
||||
}
|
||||
|
||||
protected override void DrawInspectorGUI () {
|
||||
base.DrawInspectorGUI();
|
||||
protected override void DrawInspectorGUI (bool multi) {
|
||||
base.DrawInspectorGUI(multi);
|
||||
if (!TargetIsValid) return;
|
||||
bool sameData = SpineInspectorUtility.TargetsUseSameData(serializedObject);
|
||||
|
||||
SkeletonAnimation component = (SkeletonAnimation)target;
|
||||
if (!component.valid)
|
||||
// Try to reflect the animation name on the scene object.
|
||||
{
|
||||
if (multi)
|
||||
foreach (var o in targets)
|
||||
TrySetAnimation(o);
|
||||
else
|
||||
TrySetAnimation(target);
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
if (multi && !sameData)
|
||||
EditorGUILayout.DelayedTextField(animationName);
|
||||
else {
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(animationName);
|
||||
wasAnimationNameChanged |= EditorGUI.EndChangeCheck(); // Value used in the next update.
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField(loop);
|
||||
|
||||
EditorGUILayout.PropertyField(timeScale);
|
||||
if (multi) {
|
||||
foreach (var o in targets) {
|
||||
var component = o as SkeletonAnimation;
|
||||
component.timeScale = Mathf.Max(component.timeScale, 0);
|
||||
}
|
||||
} else {
|
||||
var component = (SkeletonAnimation)target;
|
||||
component.timeScale = Mathf.Max(component.timeScale, 0);
|
||||
}
|
||||
|
||||
if (!isInspectingPrefab) {
|
||||
if (requireRepaint) {
|
||||
SceneView.RepaintAll();
|
||||
requireRepaint = false;
|
||||
}
|
||||
|
||||
DrawSkeletonUtilityButton(multi);
|
||||
}
|
||||
}
|
||||
|
||||
protected void TrySetAnimation (Object o) {
|
||||
var skeletonAnimation = o as SkeletonAnimation;
|
||||
if (skeletonAnimation == null) return;
|
||||
if (!skeletonAnimation.valid)
|
||||
return;
|
||||
|
||||
if (!isInspectingPrefab) {
|
||||
if (wasAnimationNameChanged) {
|
||||
if (!Application.isPlaying) {
|
||||
if (component.state != null) component.state.ClearTrack(0);
|
||||
component.skeleton.SetToSetupPose();
|
||||
if (skeletonAnimation.state != null) skeletonAnimation.state.ClearTrack(0);
|
||||
skeletonAnimation.skeleton.SetToSetupPose();
|
||||
}
|
||||
|
||||
Spine.Animation animationToUse = component.skeleton.Data.FindAnimation(animationName.stringValue);
|
||||
Spine.Animation animationToUse = skeletonAnimation.skeleton.Data.FindAnimation(animationName.stringValue);
|
||||
|
||||
if (!Application.isPlaying) {
|
||||
if (animationToUse != null) animationToUse.Apply(component.skeleton, 0f, 0f, false, null);
|
||||
component.Update();
|
||||
component.LateUpdate();
|
||||
SceneView.RepaintAll();
|
||||
if (animationToUse != null) animationToUse.Apply(skeletonAnimation.skeleton, 0f, 0f, false, null);
|
||||
skeletonAnimation.Update();
|
||||
skeletonAnimation.LateUpdate();
|
||||
requireRepaint = true;
|
||||
} else {
|
||||
if (animationToUse != null)
|
||||
component.state.SetAnimation(0, animationToUse, loop.boolValue);
|
||||
skeletonAnimation.state.SetAnimation(0, animationToUse, loop.boolValue);
|
||||
else
|
||||
component.state.ClearTrack(0);
|
||||
skeletonAnimation.state.ClearTrack(0);
|
||||
}
|
||||
|
||||
wasAnimationNameChanged = false;
|
||||
}
|
||||
|
||||
// Reflect animationName serialized property in the inspector even if SetAnimation API was used.
|
||||
if (Application.isPlaying) {
|
||||
TrackEntry current = component.state.GetCurrent(0);
|
||||
bool multi = animationName.serializedObject.isEditingMultipleObjects;
|
||||
if (!multi && Application.isPlaying) {
|
||||
TrackEntry current = skeletonAnimation.state.GetCurrent(0);
|
||||
if (current != null) {
|
||||
if (component.AnimationName != animationName.stringValue)
|
||||
if (skeletonAnimation.AnimationName != animationName.stringValue)
|
||||
animationName.stringValue = current.Animation.Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(animationName);
|
||||
wasAnimationNameChanged |= EditorGUI.EndChangeCheck(); // Value used in the next update.
|
||||
|
||||
EditorGUILayout.PropertyField(loop);
|
||||
EditorGUILayout.PropertyField(timeScale);
|
||||
component.timeScale = Mathf.Max(component.timeScale, 0);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
if (!isInspectingPrefab) {
|
||||
if (component.GetComponent<SkeletonUtility>() == null) {
|
||||
if (GUILayout.Button(new GUIContent("Add Skeleton Utility", SpineEditorUtilities.Icons.skeletonUtility), GUILayout.Height(30)))
|
||||
component.gameObject.AddComponent<SkeletonUtility>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,10 +31,10 @@
|
||||
// Contributed by: Mitch Thompson
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
[CustomEditor(typeof(SkeletonAnimator))]
|
||||
[CanEditMultipleObjects]
|
||||
public class SkeletonAnimatorInspector : SkeletonRendererInspector {
|
||||
protected SerializedProperty layerMixModes;
|
||||
protected override void OnEnable () {
|
||||
@ -42,22 +42,14 @@ namespace Spine.Unity.Editor {
|
||||
layerMixModes = serializedObject.FindProperty("layerMixModes");
|
||||
}
|
||||
|
||||
protected override void DrawInspectorGUI () {
|
||||
base.DrawInspectorGUI();
|
||||
protected override void DrawInspectorGUI (bool multi) {
|
||||
base.DrawInspectorGUI(multi);
|
||||
EditorGUILayout.PropertyField(layerMixModes, true);
|
||||
var component = (SkeletonAnimator)target;
|
||||
if (!component.valid)
|
||||
return;
|
||||
|
||||
EditorGUILayout.Space();
|
||||
if (!TargetIsValid) return;
|
||||
|
||||
if (!isInspectingPrefab) {
|
||||
if (component.GetComponent<SkeletonUtility>() == null) {
|
||||
if (GUILayout.Button(new GUIContent("Add Skeleton Utility", SpineEditorUtilities.Icons.skeletonUtility), GUILayout.Height(30))) {
|
||||
component.gameObject.AddComponent<SkeletonUtility>();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isInspectingPrefab)
|
||||
DrawSkeletonUtilityButton(multi);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,17 +31,34 @@
|
||||
#define NO_PREFAB_MESH
|
||||
|
||||
using UnityEditor;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
|
||||
[CustomEditor(typeof(SkeletonRenderer))]
|
||||
[CanEditMultipleObjects]
|
||||
public class SkeletonRendererInspector : UnityEditor.Editor {
|
||||
protected static bool advancedFoldout;
|
||||
protected SerializedProperty skeletonDataAsset, initialSkinName, normals, tangents, meshes, immutableTriangles, separatorSlotNames, frontFacing, zSpacing, pmaVertexColors;
|
||||
protected SpineInspectorUtility.SerializedSortingProperties sortingProperties;
|
||||
protected bool isInspectingPrefab;
|
||||
protected MeshFilter meshFilter;
|
||||
|
||||
protected bool TargetIsValid {
|
||||
get {
|
||||
if (serializedObject.isEditingMultipleObjects) {
|
||||
foreach (var o in targets) {
|
||||
var component = (SkeletonRenderer)o;
|
||||
if (!component.valid)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
var component = (SkeletonRenderer)target;
|
||||
return component.valid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnEnable () {
|
||||
isInspectingPrefab = (PrefabUtility.GetPrefabType(target) == PrefabType.Prefab);
|
||||
@ -60,8 +77,8 @@ namespace Spine.Unity.Editor {
|
||||
frontFacing = serializedObject.FindProperty("frontFacing");
|
||||
zSpacing = serializedObject.FindProperty("zSpacing");
|
||||
|
||||
var renderer = ((SkeletonRenderer)target).GetComponent<Renderer>();
|
||||
sortingProperties = new SpineInspectorUtility.SerializedSortingProperties(renderer);
|
||||
SerializedObject rso = SpineInspectorUtility.GetRenderersSerializedObject(serializedObject);
|
||||
sortingProperties = new SpineInspectorUtility.SerializedSortingProperties(rso);
|
||||
}
|
||||
|
||||
public static void ReapplySeparatorSlotNames (SkeletonRenderer skeletonRenderer) {
|
||||
@ -76,62 +93,105 @@ namespace Spine.Unity.Editor {
|
||||
var slot = skeleton.FindSlot(separatorSlotNames[i]);
|
||||
if (slot != null) {
|
||||
separatorSlots.Add(slot);
|
||||
//Debug.Log(slot + " added as separator.");
|
||||
} else {
|
||||
Debug.LogWarning(separatorSlotNames[i] + " is not a slot in " + skeletonRenderer.skeletonDataAsset.skeletonJSON.name);
|
||||
}
|
||||
}
|
||||
|
||||
//Debug.Log("Reapplied Separator Slot Names. Count is now: " + separatorSlots.Count);
|
||||
}
|
||||
|
||||
protected virtual void DrawInspectorGUI () {
|
||||
// JOHN: todo: support multiediting.
|
||||
SkeletonRenderer component = (SkeletonRenderer)target;
|
||||
|
||||
using (new EditorGUILayout.HorizontalScope()) {
|
||||
EditorGUILayout.PropertyField(skeletonDataAsset);
|
||||
const string ReloadButtonLabel = "Reload";
|
||||
float reloadWidth = GUI.skin.label.CalcSize(new GUIContent(ReloadButtonLabel)).x + 20;
|
||||
if (GUILayout.Button(ReloadButtonLabel, GUILayout.Width(reloadWidth))) {
|
||||
if (component.skeletonDataAsset != null) {
|
||||
foreach (AtlasAsset aa in component.skeletonDataAsset.atlasAssets) {
|
||||
if (aa != null)
|
||||
aa.Reset();
|
||||
protected virtual void DrawInspectorGUI (bool multi) {
|
||||
bool valid = TargetIsValid;
|
||||
if (multi) {
|
||||
using (new EditorGUILayout.HorizontalScope()) {
|
||||
EditorGUILayout.PropertyField(skeletonDataAsset);
|
||||
const string ReloadButtonLabel = "Reload";
|
||||
float reloadWidth = GUI.skin.label.CalcSize(new GUIContent(ReloadButtonLabel)).x + 20;
|
||||
if (GUILayout.Button(ReloadButtonLabel, GUILayout.Width(reloadWidth))) {
|
||||
foreach (var c in targets) {
|
||||
var component = c as SkeletonRenderer;
|
||||
if (component.skeletonDataAsset != null) {
|
||||
foreach (AtlasAsset aa in component.skeletonDataAsset.atlasAssets) {
|
||||
if (aa != null)
|
||||
aa.Reset();
|
||||
}
|
||||
component.skeletonDataAsset.Reset();
|
||||
}
|
||||
component.Initialize(true);
|
||||
}
|
||||
component.skeletonDataAsset.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var c in targets) {
|
||||
var component = c as SkeletonRenderer;
|
||||
if (!component.valid) {
|
||||
component.Initialize(true);
|
||||
component.LateUpdate();
|
||||
if (!component.valid)
|
||||
continue;
|
||||
}
|
||||
|
||||
#if NO_PREFAB_MESH
|
||||
if (isInspectingPrefab) {
|
||||
MeshFilter meshFilter = component.GetComponent<MeshFilter>();
|
||||
if (meshFilter != null)
|
||||
meshFilter.sharedMesh = null;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (valid)
|
||||
EditorGUILayout.PropertyField(initialSkinName);
|
||||
} else {
|
||||
var component = (SkeletonRenderer)target;
|
||||
|
||||
using (new EditorGUILayout.HorizontalScope()) {
|
||||
EditorGUILayout.PropertyField(skeletonDataAsset);
|
||||
if (valid) {
|
||||
const string ReloadButtonLabel = "Reload";
|
||||
float reloadWidth = GUI.skin.label.CalcSize(new GUIContent(ReloadButtonLabel)).x + 20;
|
||||
if (GUILayout.Button(ReloadButtonLabel, GUILayout.Width(reloadWidth))) {
|
||||
if (component.skeletonDataAsset != null) {
|
||||
foreach (AtlasAsset aa in component.skeletonDataAsset.atlasAssets) {
|
||||
if (aa != null)
|
||||
aa.Reset();
|
||||
}
|
||||
component.skeletonDataAsset.Reset();
|
||||
}
|
||||
component.Initialize(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!component.valid) {
|
||||
component.Initialize(true);
|
||||
component.LateUpdate();
|
||||
if (!component.valid) {
|
||||
EditorGUILayout.HelpBox("Skeleton Data Asset required", MessageType.Warning);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!component.valid) {
|
||||
component.Initialize(true);
|
||||
component.LateUpdate();
|
||||
if (!component.valid)
|
||||
return;
|
||||
}
|
||||
|
||||
#if NO_PREFAB_MESH
|
||||
if (meshFilter == null)
|
||||
meshFilter = component.GetComponent<MeshFilter>();
|
||||
|
||||
if (isInspectingPrefab)
|
||||
meshFilter.sharedMesh = null;
|
||||
#endif
|
||||
|
||||
// Initial skin name.
|
||||
{
|
||||
string[] skins = new string[component.skeleton.Data.Skins.Count];
|
||||
int skinIndex = 0;
|
||||
for (int i = 0; i < skins.Length; i++) {
|
||||
string skinNameString = component.skeleton.Data.Skins.Items[i].Name;
|
||||
skins[i] = skinNameString;
|
||||
if (skinNameString == initialSkinName.stringValue)
|
||||
skinIndex = i;
|
||||
#if NO_PREFAB_MESH
|
||||
if (isInspectingPrefab) {
|
||||
MeshFilter meshFilter = component.GetComponent<MeshFilter>();
|
||||
if (meshFilter != null)
|
||||
meshFilter.sharedMesh = null;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Initial skin name.
|
||||
if (valid) {
|
||||
string[] skins = new string[component.skeleton.Data.Skins.Count];
|
||||
int skinIndex = 0;
|
||||
for (int i = 0; i < skins.Length; i++) {
|
||||
string skinNameString = component.skeleton.Data.Skins.Items[i].Name;
|
||||
skins[i] = skinNameString;
|
||||
if (skinNameString == initialSkinName.stringValue)
|
||||
skinIndex = i;
|
||||
}
|
||||
skinIndex = EditorGUILayout.Popup("Initial Skin", skinIndex, skins);
|
||||
initialSkinName.stringValue = skins[skinIndex];
|
||||
}
|
||||
skinIndex = EditorGUILayout.Popup("Initial Skin", skinIndex, skins);
|
||||
initialSkinName.stringValue = skins[skinIndex];
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
@ -139,6 +199,8 @@ namespace Spine.Unity.Editor {
|
||||
// Sorting Layers
|
||||
SpineInspectorUtility.SortingPropertyFields(sortingProperties, applyModifiedProperties: true);
|
||||
|
||||
if (!valid) return;
|
||||
|
||||
// More Render Options...
|
||||
using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) {
|
||||
EditorGUI.indentLevel++;
|
||||
@ -183,14 +245,47 @@ namespace Spine.Unity.Editor {
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawSkeletonUtilityButton (bool multi) {
|
||||
var buttonContent = new GUIContent("Add Skeleton Utility", SpineEditorUtilities.Icons.skeletonUtility);
|
||||
if (multi) {
|
||||
// Support multi-edit SkeletonUtility button.
|
||||
// EditorGUILayout.Space();
|
||||
// bool addSkeletonUtility = GUILayout.Button(buttonContent, GUILayout.Height(30));
|
||||
// foreach (var t in targets) {
|
||||
// var component = t as SkeletonAnimation;
|
||||
// if (addSkeletonUtility && component.GetComponent<SkeletonUtility>() == null)
|
||||
// component.gameObject.AddComponent<SkeletonUtility>();
|
||||
// }
|
||||
} else {
|
||||
EditorGUILayout.Space();
|
||||
var component = (SkeletonAnimation)target;
|
||||
if (component.GetComponent<SkeletonUtility>() == null) {
|
||||
if (GUILayout.Button(buttonContent, GUILayout.Height(30)))
|
||||
component.gameObject.AddComponent<SkeletonUtility>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override public void OnInspectorGUI () {
|
||||
//serializedObject.Update();
|
||||
DrawInspectorGUI();
|
||||
bool multi = serializedObject.isEditingMultipleObjects;
|
||||
DrawInspectorGUI(multi);
|
||||
if (serializedObject.ApplyModifiedProperties() ||
|
||||
(UnityEngine.Event.current.type == EventType.ValidateCommand && UnityEngine.Event.current.commandName == "UndoRedoPerformed")
|
||||
) {
|
||||
if (!Application.isPlaying)
|
||||
((SkeletonRenderer)target).Initialize(true);
|
||||
if (!Application.isPlaying) {
|
||||
if (multi) {
|
||||
foreach (var o in targets) {
|
||||
var sr = o as SkeletonRenderer;
|
||||
sr.Initialize(true);
|
||||
}
|
||||
} else {
|
||||
((SkeletonRenderer)target).Initialize(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -53,8 +53,11 @@ namespace Spine.Unity.Editor {
|
||||
internal const string NoneLabel = "<None>";
|
||||
|
||||
protected T TargetAttribute { get { return (T)attribute; } }
|
||||
protected SerializedProperty SerializedProperty { get; private set; }
|
||||
|
||||
public override void OnGUI (Rect position, SerializedProperty property, GUIContent label) {
|
||||
SerializedProperty = property;
|
||||
|
||||
if (property.propertyType != SerializedPropertyType.String) {
|
||||
EditorGUI.LabelField(position, "ERROR:", "May only apply to type string");
|
||||
return;
|
||||
@ -87,7 +90,7 @@ namespace Spine.Unity.Editor {
|
||||
|
||||
position = EditorGUI.PrefixLabel(position, label);
|
||||
|
||||
var propertyStringValue = property.stringValue;
|
||||
var propertyStringValue = (property.hasMultipleDifferentValues) ? SpineInspectorUtility.EmDash : property.stringValue;
|
||||
if (GUI.Button(position, string.IsNullOrEmpty(propertyStringValue) ? NoneLabel : propertyStringValue, EditorStyles.popup))
|
||||
Selector(property);
|
||||
|
||||
@ -302,7 +305,6 @@ namespace Spine.Unity.Editor {
|
||||
|
||||
[CustomPropertyDrawer(typeof(SpineBone))]
|
||||
public class SpineBoneDrawer : SpineTreeItemDrawerBase<SpineBone> {
|
||||
|
||||
protected override void PopulateMenu (GenericMenu menu, SerializedProperty property, SpineBone targetAttribute, SkeletonData data) {
|
||||
menu.AddDisabledItem(new GUIContent(skeletonDataAsset.name));
|
||||
menu.AddSeparator("");
|
||||
|
||||
@ -1162,7 +1162,7 @@ namespace Spine.Unity.Editor {
|
||||
var skeletonInfo = (Dictionary<string, object>)root["skeleton"];
|
||||
object jv;
|
||||
skeletonInfo.TryGetValue("spine", out jv);
|
||||
string jsonVersion = (jv == null) ? (string)jv : null;
|
||||
string jsonVersion = jv as string;
|
||||
if (!string.IsNullOrEmpty(jsonVersion)) {
|
||||
string[] jsonVersionSplit = jsonVersion.Split('.');
|
||||
bool match = false;
|
||||
|
||||
@ -30,6 +30,7 @@
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Spine.Unity.Editor {
|
||||
@ -43,6 +44,10 @@ namespace Spine.Unity.Editor {
|
||||
return n == 1 ? "" : "s";
|
||||
}
|
||||
|
||||
public static string EmDash {
|
||||
get { return "\u2014"; }
|
||||
}
|
||||
|
||||
public static void PropertyFieldWideLabel (SerializedProperty property, GUIContent label = null, float minimumLabelWidth = 150) {
|
||||
using (new EditorGUILayout.HorizontalScope()) {
|
||||
GUILayout.Label(label ?? new GUIContent(property.displayName, property.tooltip), GUILayout.MinWidth(minimumLabelWidth));
|
||||
@ -70,25 +75,72 @@ namespace Spine.Unity.Editor {
|
||||
public SerializedProperty sortingLayerID;
|
||||
public SerializedProperty sortingOrder;
|
||||
|
||||
public SerializedSortingProperties (Renderer r) {
|
||||
renderer = new SerializedObject(r);
|
||||
public SerializedSortingProperties (Renderer r) : this(new SerializedObject(r)) {}
|
||||
public SerializedSortingProperties (Object[] renderers) : this(new SerializedObject(renderers)) {}
|
||||
public SerializedSortingProperties (SerializedObject rendererSerializedObject) {
|
||||
renderer = rendererSerializedObject;
|
||||
sortingLayerID = renderer.FindProperty("m_SortingLayerID");
|
||||
sortingOrder = renderer.FindProperty("m_SortingOrder");
|
||||
}
|
||||
|
||||
public void ApplyModifiedProperties () {
|
||||
renderer.ApplyModifiedProperties();
|
||||
this.SetDirty();
|
||||
}
|
||||
|
||||
internal void SetDirty () {
|
||||
if (renderer.isEditingMultipleObjects)
|
||||
foreach (var o in renderer.targetObjects)
|
||||
EditorUtility.SetDirty(o);
|
||||
else
|
||||
EditorUtility.SetDirty(renderer.targetObject);
|
||||
}
|
||||
}
|
||||
|
||||
public static SerializedObject GetRenderersSerializedObject (SerializedObject serializedObject) {
|
||||
if (serializedObject.isEditingMultipleObjects) {
|
||||
var renderers = new List<Object>();
|
||||
foreach (var o in serializedObject.targetObjects) {
|
||||
var component = o as Component;
|
||||
if (component != null) {
|
||||
var renderer = component.GetComponent<Renderer>();
|
||||
if (renderer != null)
|
||||
renderers.Add(renderer);
|
||||
}
|
||||
}
|
||||
return new SerializedObject(renderers.ToArray());
|
||||
} else {
|
||||
var component = serializedObject.targetObject as Component;
|
||||
if (component != null) {
|
||||
var renderer = component.GetComponent<Renderer>();
|
||||
if (renderer != null)
|
||||
return new SerializedObject(renderer);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static bool TargetsUseSameData (SerializedObject so) {
|
||||
bool multi = so.isEditingMultipleObjects;
|
||||
if (multi) {
|
||||
int n = so.targetObjects.Length;
|
||||
var first = so.targetObjects[0] as SkeletonRenderer;
|
||||
for (int i = 1; i < n; i++) {
|
||||
var sr = so.targetObjects[i] as SkeletonRenderer;
|
||||
if (sr != null && sr.skeletonDataAsset != first.skeletonDataAsset)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void SortingPropertyFields (SerializedSortingProperties prop, bool applyModifiedProperties) {
|
||||
if (applyModifiedProperties) {
|
||||
EditorGUI.BeginChangeCheck();
|
||||
SortingPropertyFields(prop.sortingLayerID, prop.sortingOrder);
|
||||
if(EditorGUI.EndChangeCheck()) {
|
||||
if(EditorGUI.EndChangeCheck())
|
||||
prop.ApplyModifiedProperties();
|
||||
EditorUtility.SetDirty(prop.renderer.targetObject);
|
||||
}
|
||||
} else {
|
||||
SortingPropertyFields(prop.sortingLayerID, prop.sortingOrder);
|
||||
}
|
||||
|
||||
@ -40,6 +40,7 @@ namespace Spine.Unity.Editor {
|
||||
|
||||
[InitializeOnLoad]
|
||||
[CustomEditor(typeof(SkeletonGraphic))]
|
||||
[CanEditMultipleObjects]
|
||||
public class SkeletonGraphicInspector : UnityEditor.Editor {
|
||||
SerializedProperty material_, color_;
|
||||
SerializedProperty skeletonDataAsset_, initialSkinName_;
|
||||
|
||||
@ -114,6 +114,8 @@ namespace Spine.Unity.Editor {
|
||||
skeleton = skeletonRenderer.skeleton;
|
||||
}
|
||||
|
||||
if (!skeletonRenderer.valid) return;
|
||||
|
||||
UpdateAttachments();
|
||||
isPrefab |= PrefabUtility.GetPrefabType(this.target) == PrefabType.Prefab;
|
||||
}
|
||||
@ -142,7 +144,6 @@ namespace Spine.Unity.Editor {
|
||||
|
||||
void UpdateAttachments () {
|
||||
attachmentTable = new Dictionary<Slot, List<Attachment>>();
|
||||
|
||||
Skin skin = skeleton.Skin ?? skeletonRenderer.skeletonDataAsset.GetSkeletonData(true).DefaultSkin;
|
||||
for (int i = skeleton.Slots.Count-1; i >= 0; i--) {
|
||||
List<Attachment> attachments = new List<Attachment>();
|
||||
@ -176,20 +177,22 @@ namespace Spine.Unity.Editor {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!skeletonRenderer.valid) {
|
||||
GUILayout.Label(new GUIContent("Spine Component invalid. Check Skeleton Data Asset.", SpineEditorUtilities.Icons.warning));
|
||||
return;
|
||||
}
|
||||
|
||||
skeletonUtility.boneRoot = (Transform)EditorGUILayout.ObjectField("Bone Root", skeletonUtility.boneRoot, typeof(Transform), true);
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
EditorGUI.BeginDisabledGroup(skeletonUtility.boneRoot != null);
|
||||
{
|
||||
if (GUILayout.Button(new GUIContent("Spawn Hierarchy", SpineEditorUtilities.Icons.skeleton), GUILayout.Width(150), GUILayout.Height(24)))
|
||||
SpawnHierarchyContextMenu();
|
||||
using (new GUILayout.HorizontalScope()) {
|
||||
using (new EditorGUI.DisabledGroupScope(skeletonUtility.boneRoot != null)) {
|
||||
if (GUILayout.Button(new GUIContent("Spawn Hierarchy", SpineEditorUtilities.Icons.skeleton), GUILayout.Width(150), GUILayout.Height(24)))
|
||||
SpawnHierarchyContextMenu();
|
||||
}
|
||||
|
||||
// if (GUILayout.Button(new GUIContent("Spawn Submeshes", SpineEditorUtilities.Icons.subMeshRenderer), GUILayout.Width(150), GUILayout.Height(24)))
|
||||
// skeletonUtility.SpawnSubRenderers(true);
|
||||
}
|
||||
EditorGUI.EndDisabledGroup();
|
||||
|
||||
// if (GUILayout.Button(new GUIContent("Spawn Submeshes", SpineEditorUtilities.Icons.subMeshRenderer), GUILayout.Width(150), GUILayout.Height(24)))
|
||||
// skeletonUtility.SpawnSubRenderers(true);
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
skeleton.FlipX = EditorGUILayout.ToggleLeft("Flip X", skeleton.FlipX);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user