我试图找到一种同步动画的方法& sfx(可能是后来的粒子fx,相机移动等)要求我们的艺术家在我们的关卡中放置艺术时使用。我想出的方法是让一个具有基类类型FXEvent
的数组的触发器,其子类类型为AnimEvent
和SFXEvent
。 C#通常可以很好地处理多态性,但Unity的Inspector却没有。我担心这是我问题的根源。
4年前我解决了这个问题,所以我知道可以做到;但是,唉,我再也无法访问该代码了。我这次尝试使用ScriptableObject
和SerilizableObject
来构建解决方案,并且我有一些几乎的工作。
我将FXTriggerTimer
MonoBehavior(继承自FXTriggerBase
)应用于测试对象,然后向触发器添加AnimEvent
和SFXEvent
。然后我保存场景,并重新加载场景,我的测试对象有一个由两个空元素组成的数组(显然)不会在检查器中绘制。 但是,如果我编辑脚本(例如点击空间并保存),脚本刷新将神奇地重新填充正确的两个项目。所以我知道所有的部件都在引擎盖下,它们只需要在场景负载下恢复。
注意:我没有使用ScriptableObject和SerializableObject结合我当前的解决方案。对我来说感觉有点复杂,但它让我最接近真正的解决方案。
另外,there's an example scene downloadable here,如果有人想以这种方式看待问题。
我试图开始工作的基本多态性内容:
//Scripts/FXTriggerBase.cs
using UnityEngine;
using System.Collections;
public enum FXEventType {kNone, kAnim, kSFX,}
/*-----------------------------------------------------------------------------
*///---------------------------------------------------------------------------
[System.Serializable]
public class FXEvent: UnityEngine.ScriptableObject
{
[SerializeField]
public FXEventType type;
[SerializeField]
public GameObject go;
virtual public void Fire() { }
};
/*-----------------------------------------------------------------------------
*///---------------------------------------------------------------------------
[System.Serializable]
public class AnimEvent : FXEvent
{
[SerializeField]
public string animName = "";
public void Fire() {go.animation.Play(animName); }
}
/*-----------------------------------------------------------------------------
*///---------------------------------------------------------------------------
[System.Serializable]
public class SFXEvent: FXEvent
{
[SerializeField]
public AudioClip clip;
public void Fire() {go.audio.PlayOneShot(clip); }
}
/*-----------------------------------------------------------------------------
*///---------------------------------------------------------------------------
[AddComponentMenu("")] // prevents users from using this directly
public class FXTriggerBase : MonoBehaviour
{
[SerializeField]
public FXEvent[] events = new FXEvent[]{};
public void Fire()
{
for( int e = 0; e < events.Length; e++)
{
events[e].go = gameObject;
events[e].Fire();
}
}
}
使用它的示例MonoBehavior:
//Scripts/FXTriggerTimer.cs
using UnityEngine;
using System.Collections;
/*-----------------------------------------------------------------------------
*///---------------------------------------------------------------------------
public class FXTriggerTimer: FXTriggerBase
{
public float timeTrigger;
float timeCurrent;
public void Start()
{
timeCurrent = 0;
}
public void Update()
{
float frame = 1.0f/60.0f;
if(timeCurrent >= 0)
timeCurrent += frame;
if(timeCurrent > timeTrigger)
{
Fire();
timeCurrent = -1.0f;
}
}
}
使用以下命令编辑自定义检查器实用程序类:
//Editor/FXTriggerBaseUtil.cs
using UnityEngine;
using System.Collections;
using UnityEditor;
/*-----------------------------------------------------------------------------
*///---------------------------------------------------------------------------
public class FXEditorUtil
{
SerializedObject[] serial;
public bool dirtied = false;
/*-------------------------------------------------------------------------
*///-----------------------------------------------------------------------
public void OnSubGUI(this FXEvent fx)
{
//having trouble getting polymorphism to work right in the inspector,
// so this is what works.
if(fx.type == FXEventType.kAnim)
OnSubGUI((AnimEvent)fx);
else if(fx.type == FXEventType.kSFX)
OnSubGUI((SFXEvent)fx);
}
/*-------------------------------------------------------------------------
*///-----------------------------------------------------------------------
public void OnSubGUI(this AnimEvent anim)
{
string newAnimName = GUILayout.TextField(anim.animName);
if(newAnimName != anim.animName)
{
anim.animName = newAnimName;
dirtied = true;
}
}
/*-------------------------------------------------------------------------
*///-----------------------------------------------------------------------
public void OnSubGUI(this SFXEvent sfx)
{
AudioClip newSFX = (AudioClip)EditorGUILayout.ObjectField(sfx.clip, typeof(AudioClip));
if(newSFX != sfx.clip)
{
sfx.clip =newSFX;
dirtied = true;
}
}
/*-------------------------------------------------------------------------
*///-----------------------------------------------------------------------
public FXEvent OnGUI(FXEvent fx)
{
if(fx == null)
return fx;
GUILayout.BeginHorizontal();
FXEvent ret = null;
//if the enum changes, create new FXEvent sub type corresponding to the
// new enum, and return it
FXEventType newType = (FXEventType) EditorGUILayout.EnumPopup(fx.type, GUILayout.Width(60));
if(newType != fx.type)
{
switch(newType)
{
case FXEventType.kNone:
ret = ScriptableObject.CreateInstance<FXEvent>();
break;
case FXEventType.kSFX:
ret = ScriptableObject.CreateInstance<SFXEvent>();
break;
case FXEventType.kAnim:
ret = ScriptableObject.CreateInstance<AnimEvent>();
break;
default:
Debug.LogError("FXEvent.OnGUI(): Magic Error");
break;
}
ret.type = newType;
dirtied = true;
}
//handle element GUI, if that applies
if(fx.type != FXEventType.kNone)
{
OnSubGUI(fx);
}
else
GUILayout.Label("");
GUILayout.EndHorizontal();
return ret;
}
/*-------------------------------------------------------------------------
*///-----------------------------------------------------------------------
public void OnGUI(FXTriggerBase trigger)
{
FXTriggerBase triggerBase = trigger as FXTriggerBase;
if(triggerBase == null)
{
Debug.LogError("FXTriggerBaseInspector.OnGUI() : null item to inspect?");
return;
}
//build an array of serialized versions of our array elements, so that they
// save out properly
int numEvents = triggerBase.events.Length;
serial = new SerializedObject[numEvents];
for(int e = 0; e < numEvents; e++)
{
serial[e] = new SerializedObject(triggerBase.events[e]);
}
dirtied = false;
//Do inspector GUI of our array elements
int ret = ArrayGUI("events", trigger.events, trigger.gameObject);
//handle deletion or addition of elements
if(ret >= 0 && ret < trigger.events.Length)
ArrayHandleDelete(ref trigger.events, ret);
else if (ret == trigger.events.Length)
ArrayHandleAdd(ref trigger.events);
//if any changes have been registered, save our serialized versions out
if(dirtied)
{
numEvents = serial.Length;
for(int e = 0; e < numEvents; e++)
{
serial[e].ApplyModifiedProperties();
}
EditorUtility.SetDirty(trigger);
}
}
/*-------------------------------------------------------------------------
*///-----------------------------------------------------------------------
int ArrayGUI(string label, FXEvent[] arr, GameObject go)
{
int ret = -1;
Color defColor = GUI.color;
GUILayout.BeginVertical(GUILayout.Width(250));
{
GUILayout.Label(label);
int numObjects = arr.Length;
for (int o = 0; o < numObjects; o++)
{
GUILayout.BeginHorizontal();
{
//Draw a red '-' button that will remove this element
GUI.color = Color.red;
bool hit = GUILayout.Button("-", GUILayout.Width(20));
if (hit)
ret = o;
GUI.color = defColor;
//spacer
GUILayout.Label("", GUILayout.Width(10));
//draw custom GUI for each of the polymorphic fx event types
FXEvent fx = arr[o];
FXEvent newFX = OnGUI(fx);
if(newFX != null)
{
arr[o] = newFX;
dirtied = true;
serial[o] = new SerializedObject(arr[o]);
ScriptableObject.DestroyImmediate(fx);
}
}
GUILayout.EndHorizontal();
}
//finally, at the bottom draw a blue plus button that will
GUI.color = Color.cyan;
bool add = GUILayout.Button("+", GUILayout.Width(20));
if (add)
ret = arr.Length;
GUI.color = defColor;
}
GUILayout.EndVertical();
return ret;
}
/*-------------------------------------------------------------------------
*///-----------------------------------------------------------------------
void ArrayHandleDelete(ref FXEvent[] arr, int index)
{
FXEvent fx = arr[index];
ScriptableObject.DestroyImmediate(fx);
//copy over the element we are deleting, and continue compacting the array
for (int i = index; i < arr.Length - 1; i++)
{
arr[i] = arr[i + 1];
serial[i] = serial[i+1];
}
//remove last element to finalize deletion
System.Array.Resize(ref arr, arr.Length - 1);
System.Array.Resize(ref serial, serial.Length - 1);
dirtied = true;
}
/*-------------------------------------------------------------------------
*///-----------------------------------------------------------------------
void ArrayHandleAdd(ref FXEvent[] arr)
{
int size = arr.Length;
//add new element to the end
System.Array.Resize(ref arr, size + 1);
System.Array.Resize(ref serial, size + 1);
arr[size] = ScriptableObject.CreateInstance<FXEvent>();
serial[size] = new SerializedObject(arr[size]);
dirtied = true;
}
}
并使用该示例的自定义检查器示例:
//Editor/FXTriggerTimerInspector.cs
using UnityEngine;
using System.Collections;
using UnityEditor;
/*-----------------------------------------------------------------------------
*///---------------------------------------------------------------------------
[CustomEditor(typeof(FXTriggerTimer))]
public class FXTriggerTimerInspector : Editor
{
FXEditorUtil util;
public override void OnInspectorGUI()
{
FXTriggerTimer trigger = (FXTriggerTimer)target;
if(util == null)
{
util = new FXEditorUtil();
}
util.OnGUI(trigger);
GUILayout.BeginHorizontal();
GUILayout.Label("Timer");
trigger.timeTrigger = EditorGUILayout.FloatField(trigger.timeTrigger);
GUILayout.EndHorizontal();
if(util.dirtied)
{
EditorUtility.SetDirty(target);
//serializedObject.ApplyModifiedProperties();
}
}
}