Skip to content

Instantly share code, notes, and snippets.

Created February 3, 2018 03:22
Show Gist options
  • Save anonymous/eaccf9bb0787a33688fa94ade3cad77b to your computer and use it in GitHub Desktop.
Save anonymous/eaccf9bb0787a33688fa94ade3cad77b to your computer and use it in GitHub Desktop.
#if UNITY_EDITOR
/***********************************************
* Copyright ? Far-Flung Creations Ltd.
* Author: Marius George
* Date: 25 October 2017
* Email: [email protected]
* DISCLAIMER: THE SOURCE CODE IN THIS FILE IS PROVIDED ?AS IS? AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL FAR-FLUNG CREATIONS OR CONTRIBUTORS 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) SUSTAINED BY YOU OR A THIRD
* PARTY, HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* ARISING IN ANY WAY OUT OF THE USE OF THIS SAMPLE CODE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**********************************************/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Sirenix.Utilities;
using UnityEditor;
using UnityEngine;
namespace Util.Playables.TimelineEvents.Editor
{
[CustomPropertyDrawer(typeof(TimelineEventBehaviour))]
public class TimelineEventDrawer : PropertyDrawer
{
private List<string> _eventHandlerListStart = new List<string> {"None"};
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
int fieldCount = 1;
return fieldCount * EditorGUIUtility.singleLineHeight;
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
SerializedProperty clipEventHandlerStartProperty =
property.FindPropertyRelative("ClipEventHandler_ClipStart");
SerializedProperty clipEventHandlerEndProperty = property.FindPropertyRelative("ClipEventHandler_ClipEnd");
SerializedProperty trackEventHandlerStartProperty =
property.FindPropertyRelative("TrackEventHandler_ClipStart");
SerializedProperty trackEventHandlerEndProperty =
property.FindPropertyRelative("TrackEventHandler_ClipEnd");
SerializedProperty enableTrackEventsProperty = property.FindPropertyRelative("EnableTrackEvents");
SerializedProperty enableClipEventsProperty = property.FindPropertyRelative("EnableClipEvents");
SerializedProperty startSingleArgsProperty = property.FindPropertyRelative("StartSingleArgMethods");
SerializedProperty endSingleArgsProperty = property.FindPropertyRelative("EndSingleArgMethods");
SerializedProperty invokeEventsInEditModeProperty = property.FindPropertyRelative("InvokeEventsInEditMode");
SerializedProperty startArgProperty = property.FindPropertyRelative("StartArgValue");
SerializedProperty endArgProperty = property.FindPropertyRelative("EndArgValue");
TimelineEventClip clip = property.serializedObject.targetObject as TimelineEventClip;
GameObject gameObject =
clip.TargetObject.Resolve(property.serializedObject.context as IExposedPropertyTable);
bool hasEvents;
EditorGUILayout.Space();
EditorGUILayout.HelpBox("Call event handlers on the GameObject bound to the track.", MessageType.Info);
enableTrackEventsProperty.boolValue =
EditorGUILayout.BeginToggleGroup("Track Events", enableTrackEventsProperty.boolValue);
if (enableTrackEventsProperty.boolValue)
{
if (clip.TrackTargetObject == null)
{
EditorGUILayout.HelpBox(
"There is currently no GameObject bound to the track. Drag a GameObject from the scene into the field to the left of the track.",
MessageType.Warning);
}
startSingleArgsProperty.boolValue =
EditorGUILayout.BeginToggleGroup("Use Start argument?", startSingleArgsProperty.boolValue);
if (startSingleArgsProperty.boolValue)
{
EditorGUILayout.PropertyField(startArgProperty);
}
EditorGUILayout.EndToggleGroup();
hasEvents = AddMethodsPopup("At Clip Start", trackEventHandlerStartProperty, clip.TrackTargetObject,
startSingleArgsProperty.boolValue);
endSingleArgsProperty.boolValue =
EditorGUILayout.BeginToggleGroup("Use End argument?", endSingleArgsProperty.boolValue);
EditorGUILayout.EndToggleGroup();
if (endSingleArgsProperty.boolValue)
{
EditorGUILayout.PropertyField(endArgProperty);
}
AddMethodsPopup("At Clip End", trackEventHandlerEndProperty, clip.TrackTargetObject,
endSingleArgsProperty.boolValue);
if (!hasEvents)
{
EditorGUILayout.HelpBox(
"Unable to find any event handlers. The target GameObject must have at least one MonoBehaviour with one or more public parameterless methods to serve as event handlers.",
MessageType.Warning);
}
}
EditorGUILayout.EndToggleGroup();
EditorGUILayout.Space();
EditorGUILayout.Space();
EditorGUILayout.HelpBox("Call event handlers on the GameObject bound to this clip.", MessageType.Info);
enableClipEventsProperty.boolValue =
EditorGUILayout.BeginToggleGroup("Clip Events", enableClipEventsProperty.boolValue);
if (enableClipEventsProperty.boolValue)
{
if (clip.TrackTargetObject == null)
{
EditorGUILayout.HelpBox(
"There is currently no GameObject bound to this clip. Drag a GameObject from the scene into the Target Object field.",
MessageType.Warning);
}
hasEvents = AddMethodsPopup("At Clip Start", clipEventHandlerStartProperty, gameObject);
AddMethodsPopup("At Clip End", clipEventHandlerEndProperty, gameObject);
if (!hasEvents)
{
EditorGUILayout.HelpBox(
"Unable to find any event handlers. The target GameObject must have at least one MonoBehaviour with one or more public parameterless methods to serve as event handlers.",
MessageType.Warning);
}
}
EditorGUILayout.EndToggleGroup();
EditorGUILayout.Space();
EditorGUILayout.Space();
EditorGUILayout.HelpBox(
"Call event handlers while in Edit Mode, by dragging the time marker in the Timeline window.",
MessageType.Info);
invokeEventsInEditModeProperty.boolValue = EditorGUILayout.BeginToggleGroup("Invoke Events in Edit Mode",
invokeEventsInEditModeProperty.boolValue);
if (invokeEventsInEditModeProperty.boolValue)
{
EditorGUILayout.HelpBox(
"PLEASE NOTE! CHANGES MADE TO THE SCENE FROM WITHIN YOUR EVENT HANDLERS WILL BE PERSISTED!",
MessageType.Warning);
}
EditorGUILayout.EndToggleGroup();
}
private bool AddMethodsPopup(string label, SerializedProperty property, GameObject gameObject,
bool listSingleArgMethods = false)
{
if (gameObject == null)
{
return false;
}
MonoBehaviour[] behaviours = gameObject.GetComponents<MonoBehaviour>();
var callbackMethodsEnumarable = behaviours.SelectMany(
x => x.GetType()
.GetMethods(BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance))
.Where(
x =>
{
if (listSingleArgMethods)
{
return (x.ReturnType == typeof(void)) && (x.GetParameters().Length == 1) &&
x.GetParameters()[0].ParameterType == typeof(string);
}
else
{
return (x.ReturnType == typeof(void)) && (x.GetParameters().Length == 0);
}
}).Select(
x => x.DeclaringType.ToString() + "." + x.Name);
if (callbackMethodsEnumarable.Count() == 0)
{
property.stringValue = string.Empty;
return false;
}
string[] callbackMethods = _eventHandlerListStart.Concat(callbackMethodsEnumarable).ToArray();
int index = Array.IndexOf(callbackMethods, property.stringValue);
index = EditorGUILayout.Popup(label, index, callbackMethods, GUILayoutOptions.ExpandWidth(true));
if (index >= 0)
{
property.stringValue = callbackMethods[index];
}
return true;
}
}
}
#endif
/***********************************************
* Copyright ? Far-Flung Creations Ltd.
* Author: Marius George
* Date: 25 October 2017
* Email: [email protected]
* DISCLAIMER: THE SOURCE CODE IN THIS FILE IS PROVIDED ?AS IS? AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL FAR-FLUNG CREATIONS OR CONTRIBUTORS 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) SUSTAINED BY YOU OR A THIRD
* PARTY, HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* ARISING IN ANY WAY OUT OF THE USE OF THIS SAMPLE CODE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**********************************************/
using System;
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Timeline;
namespace Util.Playables.TimelineEvents
{
[Serializable]
public class TimelineEventClip : PlayableAsset, ITimelineClipAsset
{
public TimelineEventBehaviour template = new TimelineEventBehaviour();
public ExposedReference<GameObject> TargetObject;
public GameObject TrackTargetObject { get; set; }
public float ClipStartTime { get; set; }
public float ClipEndTime { get; set; }
public string StartArgValue { get; set; }
public string EndArgValue { get; set; }
public ClipCaps clipCaps
{
get { return ClipCaps.None; }
}
public override Playable CreatePlayable(PlayableGraph graph, GameObject owner)
{
var playable = ScriptPlayable<TimelineEventBehaviour>.Create(graph, template);
TimelineEventBehaviour clone = playable.GetBehaviour();
clone.TargetObject = TargetObject.Resolve(graph.GetResolver());
clone.ClipStartTime = ClipStartTime;
clone.ClipEndTime = ClipEndTime;
clone.TrackTargetObject = TrackTargetObject;
clone.StartArgValue = StartArgValue;
clone.EndArgValue = EndArgValue;
return playable;
}
}
}
#if UNITY_EDITOR
/***********************************************
* Copyright ? Far-Flung Creations Ltd.
* Author: Marius George
* Date: 25 October 2017
* Email: [email protected]
* DISCLAIMER: THE SOURCE CODE IN THIS FILE IS PROVIDED ?AS IS? AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL FAR-FLUNG CREATIONS OR CONTRIBUTORS 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) SUSTAINED BY YOU OR A THIRD
* PARTY, HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* ARISING IN ANY WAY OUT OF THE USE OF THIS SAMPLE CODE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**********************************************/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Sirenix.Utilities;
using UnityEditor;
using UnityEngine;
namespace Util.Playables.TimelineEvents.Editor
{
[CustomPropertyDrawer(typeof(TimelineEventBehaviour))]
public class TimelineEventDrawer : PropertyDrawer
{
private List<string> _eventHandlerListStart = new List<string> {"None"};
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
int fieldCount = 1;
return fieldCount * EditorGUIUtility.singleLineHeight;
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
SerializedProperty clipEventHandlerStartProperty =
property.FindPropertyRelative("ClipEventHandler_ClipStart");
SerializedProperty clipEventHandlerEndProperty = property.FindPropertyRelative("ClipEventHandler_ClipEnd");
SerializedProperty trackEventHandlerStartProperty =
property.FindPropertyRelative("TrackEventHandler_ClipStart");
SerializedProperty trackEventHandlerEndProperty =
property.FindPropertyRelative("TrackEventHandler_ClipEnd");
SerializedProperty enableTrackEventsProperty = property.FindPropertyRelative("EnableTrackEvents");
SerializedProperty enableClipEventsProperty = property.FindPropertyRelative("EnableClipEvents");
SerializedProperty startSingleArgsProperty = property.FindPropertyRelative("StartSingleArgMethods");
SerializedProperty endSingleArgsProperty = property.FindPropertyRelative("EndSingleArgMethods");
SerializedProperty invokeEventsInEditModeProperty = property.FindPropertyRelative("InvokeEventsInEditMode");
SerializedProperty startArgProperty = property.FindPropertyRelative("StartArgValue");
SerializedProperty endArgProperty = property.FindPropertyRelative("EndArgValue");
TimelineEventClip clip = property.serializedObject.targetObject as TimelineEventClip;
GameObject gameObject =
clip.TargetObject.Resolve(property.serializedObject.context as IExposedPropertyTable);
bool hasEvents;
EditorGUILayout.Space();
EditorGUILayout.HelpBox("Call event handlers on the GameObject bound to the track.", MessageType.Info);
enableTrackEventsProperty.boolValue =
EditorGUILayout.BeginToggleGroup("Track Events", enableTrackEventsProperty.boolValue);
if (enableTrackEventsProperty.boolValue)
{
if (clip.TrackTargetObject == null)
{
EditorGUILayout.HelpBox(
"There is currently no GameObject bound to the track. Drag a GameObject from the scene into the field to the left of the track.",
MessageType.Warning);
}
startSingleArgsProperty.boolValue =
EditorGUILayout.BeginToggleGroup("Use Start argument?", startSingleArgsProperty.boolValue);
if (startSingleArgsProperty.boolValue)
{
EditorGUILayout.PropertyField(startArgProperty);
}
EditorGUILayout.EndToggleGroup();
hasEvents = AddMethodsPopup("At Clip Start", trackEventHandlerStartProperty, clip.TrackTargetObject,
startSingleArgsProperty.boolValue);
endSingleArgsProperty.boolValue =
EditorGUILayout.BeginToggleGroup("Use End argument?", endSingleArgsProperty.boolValue);
EditorGUILayout.EndToggleGroup();
if (endSingleArgsProperty.boolValue)
{
EditorGUILayout.PropertyField(endArgProperty);
}
AddMethodsPopup("At Clip End", trackEventHandlerEndProperty, clip.TrackTargetObject,
endSingleArgsProperty.boolValue);
if (!hasEvents)
{
EditorGUILayout.HelpBox(
"Unable to find any event handlers. The target GameObject must have at least one MonoBehaviour with one or more public parameterless methods to serve as event handlers.",
MessageType.Warning);
}
}
EditorGUILayout.EndToggleGroup();
EditorGUILayout.Space();
EditorGUILayout.Space();
EditorGUILayout.HelpBox("Call event handlers on the GameObject bound to this clip.", MessageType.Info);
enableClipEventsProperty.boolValue =
EditorGUILayout.BeginToggleGroup("Clip Events", enableClipEventsProperty.boolValue);
if (enableClipEventsProperty.boolValue)
{
if (clip.TrackTargetObject == null)
{
EditorGUILayout.HelpBox(
"There is currently no GameObject bound to this clip. Drag a GameObject from the scene into the Target Object field.",
MessageType.Warning);
}
hasEvents = AddMethodsPopup("At Clip Start", clipEventHandlerStartProperty, gameObject);
AddMethodsPopup("At Clip End", clipEventHandlerEndProperty, gameObject);
if (!hasEvents)
{
EditorGUILayout.HelpBox(
"Unable to find any event handlers. The target GameObject must have at least one MonoBehaviour with one or more public parameterless methods to serve as event handlers.",
MessageType.Warning);
}
}
EditorGUILayout.EndToggleGroup();
EditorGUILayout.Space();
EditorGUILayout.Space();
EditorGUILayout.HelpBox(
"Call event handlers while in Edit Mode, by dragging the time marker in the Timeline window.",
MessageType.Info);
invokeEventsInEditModeProperty.boolValue = EditorGUILayout.BeginToggleGroup("Invoke Events in Edit Mode",
invokeEventsInEditModeProperty.boolValue);
if (invokeEventsInEditModeProperty.boolValue)
{
EditorGUILayout.HelpBox(
"PLEASE NOTE! CHANGES MADE TO THE SCENE FROM WITHIN YOUR EVENT HANDLERS WILL BE PERSISTED!",
MessageType.Warning);
}
EditorGUILayout.EndToggleGroup();
}
private bool AddMethodsPopup(string label, SerializedProperty property, GameObject gameObject,
bool listSingleArgMethods = false)
{
if (gameObject == null)
{
return false;
}
MonoBehaviour[] behaviours = gameObject.GetComponents<MonoBehaviour>();
var callbackMethodsEnumarable = behaviours.SelectMany(
x => x.GetType()
.GetMethods(BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance))
.Where(
x =>
{
if (listSingleArgMethods)
{
return (x.ReturnType == typeof(void)) && (x.GetParameters().Length == 1) &&
x.GetParameters()[0].ParameterType == typeof(string);
}
else
{
return (x.ReturnType == typeof(void)) && (x.GetParameters().Length == 0);
}
}).Select(
x => x.DeclaringType.ToString() + "." + x.Name);
if (callbackMethodsEnumarable.Count() == 0)
{
property.stringValue = string.Empty;
return false;
}
string[] callbackMethods = _eventHandlerListStart.Concat(callbackMethodsEnumarable).ToArray();
int index = Array.IndexOf(callbackMethods, property.stringValue);
index = EditorGUILayout.Popup(label, index, callbackMethods, GUILayoutOptions.ExpandWidth(true));
if (index >= 0)
{
property.stringValue = callbackMethods[index];
}
return true;
}
}
}
#endif
/***********************************************
* Copyright ? Far-Flung Creations Ltd.
* Author: Marius George
* Date: 25 October 2017
* Email: [email protected]
* DISCLAIMER: THE SOURCE CODE IN THIS FILE IS PROVIDED ?AS IS? AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL FAR-FLUNG CREATIONS OR CONTRIBUTORS 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) SUSTAINED BY YOU OR A THIRD
* PARTY, HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* ARISING IN ANY WAY OUT OF THE USE OF THIS SAMPLE CODE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**********************************************/
using UnityEngine;
using UnityEngine.Playables;
namespace Util.Playables.TimelineEvents
{
public class TimelineEventMixerBehaviour : PlayableBehaviour
{
private float _lastTime;
// NOTE: This function is called at runtime and edit time. Keep that in mind when setting the values of properties.
public override void ProcessFrame(Playable playable, FrameData info, object playerData)
{
/*GameObject trackBinding = playerData as GameObject;
if (!trackBinding)
return;*/
var go = playerData as GameObject;
int inputCount = playable.GetInputCount();
float time = (float) playable.GetGraph().GetRootPlayable(0).GetTime();
for (int i = 0; i < inputCount; i++)
{
float inputWeight = playable.GetInputWeight(i);
ScriptPlayable<TimelineEventBehaviour> inputPlayable =
(ScriptPlayable<TimelineEventBehaviour>) playable.GetInput(i);
TimelineEventBehaviour input = inputPlayable.GetBehaviour();
if ((_lastTime <= input.ClipStartTime) && (time > input.ClipStartTime))
{
input.OnEnter();
}
if ((_lastTime <= input.ClipEndTime) && (time > input.ClipEndTime))
{
input.OnExit();
}
}
_lastTime = time;
}
}
}
/***********************************************
* Copyright ? Far-Flung Creations Ltd.
* Author: Marius George
* Date: 25 October 2017
* Email: [email protected]
* DISCLAIMER: THE SOURCE CODE IN THIS FILE IS PROVIDED ?AS IS? AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL FAR-FLUNG CREATIONS OR CONTRIBUTORS 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) SUSTAINED BY YOU OR A THIRD
* PARTY, HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* ARISING IN ANY WAY OUT OF THE USE OF THIS SAMPLE CODE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**********************************************/
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Timeline;
namespace Util.Playables.TimelineEvents
{
[TrackColor(0.4448276f, 0f, 1f)]
[TrackClipType(typeof(TimelineEventClip))]
[TrackBindingType(typeof(GameObject))]
public class TimelineEventTrack : TrackAsset
{
public override Playable CreateTrackMixer(PlayableGraph graph, GameObject go, int inputCount)
{
var director = go.GetComponent<PlayableDirector>();
var trackTargetObject = director.GetGenericBinding(this) as GameObject;
foreach (var clip in GetClips())
{
var playableAsset = clip.asset as TimelineEventClip;
if (playableAsset)
{
playableAsset.TrackTargetObject = trackTargetObject;
playableAsset.ClipStartTime = (float) clip.start;
playableAsset.ClipEndTime = (float) clip.end;
}
}
var scriptPlayable = ScriptPlayable<TimelineEventMixerBehaviour>.Create(graph, inputCount);
return scriptPlayable;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment