using UnityEngine; using System; using System.Collections.Generic; using System.Security; namespace Flux { [Serializable] /** * @brief Range of frames. * @note Start is _not_ guaranteed to be smaller or equal to End, it is up to the user to make sure. */ public struct FrameRange { // start frame [SerializeField] private int _start; // end frame [SerializeField] private int _end; /// @brief Returns the start frame. public int Start { get { return _start; } set { _start = value; } } /// @brief Returns the end frame. public int End { get { return _end; } set { _end = value; } } /// @brief Sets / Gets the length. /// @note It doesn't cache the value. public int Length { set{ End = _start + value; } get{ return _end - _start; } } /** * @brief Create a frame range * @param start Start frame * @param end End frame * @note It is up to you to make sure start is smaller than end. */ public FrameRange( int start, int end ) { this._start = start; this._end = end; } /// @brief Returns \e i clamped to the range. public int Cull( int i ) { return Mathf.Clamp( i, _start, _end ); } /// @brief Returns if \e i is inside [start, end], i.e. including borders public bool Contains( int i ) { return i >= _start && i <= _end; } /// @brief Returns if \e i is inside ]start, end[, i.e. excluding borders public bool ContainsExclusive( int i ) { return i > _start && i < _end; } /// @brief Returns if the ranges intersect, i.e. touching returns false /// @note Assumes They are both valid public bool Collides( FrameRange range ) { return _start < range._end && _end > range._start; // return (range.start > start && range.start < end) || (range.end > start && range.end < end ); } /// @brief Returns if the ranges overlap, i.e. touching return true /// @note Assumes They are both valid public bool Overlaps( FrameRange range ) { return range.End >= _start && range.Start <= _end; } /// @brief Returns what kind of overlap it has with \e range. /// @note Assumes They are both valid public FrameRangeOverlap GetOverlap( FrameRange range ) { if( range._start >= _start ) { // contains, left or none if( range._end <= _end ) { return FrameRangeOverlap.ContainsFull; } else { if( range._start > _end ) { return FrameRangeOverlap.MissOnRight; } return FrameRangeOverlap.ContainsStart; } } else { // contained, right or none if( range._end < _start ) { return FrameRangeOverlap.MissOnLeft; } else { if( range._end > _end ) { return FrameRangeOverlap.IsContained; } return FrameRangeOverlap.ContainsEnd; } } } public static bool operator ==( FrameRange a, FrameRange b ) { return a._start == b._start && a._end == b._end; } public static bool operator !=( FrameRange a, FrameRange b ) { return !(a == b); } public override bool Equals( object obj ) { if( obj.GetType() != GetType() ) return false; return (FrameRange)obj == this; } public override int GetHashCode() { return _start + _end; } public override string ToString() { return string.Format("[{0}; {1}]", _start, _end); } } /// @brief Types of range overlap public enum FrameRangeOverlap { MissOnLeft = -2, /// @brief missed and is to the left of the range passed MissOnRight, /// @brief missed and is to the right of the range passed IsContained, /// @brief overlaps and is contained by the range passed ContainsFull, /// @brief overlaps and contains the range passed ContainsStart, /// @brief overlaps and contains the start of the range passed ContainsEnd /// @brief overlaps and contains the end of the range passed } public class FEventParam { public string name; public string val; public string valType; } /** * @brief Base class for Events * @sa FSequence, FTimeline, FTrack. */ public class FEvent : FObject { public override Transform Owner { get { return _track.Owner; } } public override FSequence Sequence { get { return _track.Sequence; } } // track that owns this event [SerializeField] [HideInInspector] protected FTrack _track = null; /// @brief Returns the track it belongs to public FTrack Track { get { return _track; } } [SerializeField] [HideInInspector] private bool _triggerOnSkip = true; /// @brief Should this event trigger if you skip it? public bool TriggerOnSkip { get { return _triggerOnSkip; } set { _triggerOnSkip = value; } } [SerializeField] [HideInInspector] private FrameRange _frameRange; /// @brief Range of the event. public FrameRange FrameRange { get { return _frameRange; } set { FrameRange oldFrameRange = _frameRange; _frameRange = value; OnFrameRangeChanged( oldFrameRange ); } } // has this event called Trigger already? private bool _hasTriggered = false; /// @brief Has Trigger been called already? public bool HasTriggered { get { return _hasTriggered; } } // has this event called Finish already? private bool _hasFinished = false; /// @brief Has Finish been called already? public bool HasFinished { get { return _hasFinished; } } public virtual string Text { get { return null; } set { } } protected Transform _casterTrans; protected Transform _targetTrans; protected Transform _cameraTrans; protected SkillActionFrameEventType _eventType = SkillActionFrameEventType.FE_NONE; public SkillActionFrameEventType EventType { get { return _eventType; } } protected List _eventParams; /** * @brief Create an event. Should be used to create events since it also * calls SetDefaultValues. * @param range Range of the event. */ public static T Create( FrameRange range ) where T : FEvent { GameObject go = new GameObject( typeof(T).ToString() ); T evt = go.AddComponent(); evt._frameRange = new FrameRange( range.Start, range.End ); return evt; } /// @overload public static FEvent Create( Type evtType, FrameRange range ) { GameObject go = new GameObject( evtType.ToString() ); FEvent evt = (FEvent)go.AddComponent(evtType); evt._frameRange = new FrameRange( range.Start, range.End ); return evt; } // sets the track this event belongs to, to be called only by FTrack internal void SetTrack( FTrack track ) { _track = track; if( _track ) { transform.parent = _track.transform; } else { transform.parent = null; } } /// @brief Use this function to setup default values for when events get created public void SetDefaultValues() { OnSetDefaultValues(); } /// @brief Use this function if you want to do something to the event when the frame range /// changed, e.g. adjust some variables to the new event size. /// @param oldFrameRange Previous FrameRange, the current one is set on the event. protected virtual void OnFrameRangeChanged( FrameRange oldFrameRange ) { } /** * @brief Called when the event gets reached. * The reason we pass the time is because they may have been frames skipped * or simply we may have jumped into the middle of an event, and that allows you * to skip to the right point. E.g. useful when you want to play an animation, * if you jumped to the middle of it you want to tell mecanim to start in the middle, * not at the start. * @param frameSinceTrigger Frames that passed since the actual TriggerFrame * @param timeSinceTrigger Time passed since the actual TriggerFrame * @sa TriggerFrame, TriggerTime, Finish */ public void Trigger( float timeSinceTrigger ) { _hasTriggered = true; OnTrigger( timeSinceTrigger ); } /// @brief At which frame will the event trigger, basically the start of it's range. public int TriggerFrame { get { return _frameRange.Start; } } /// @brief At which time the event triggers. /// @note Value isn't cached. public float TriggerTime { get { return _frameRange.Start * Sequence.InverseFrameRate; } } /** * @brief Used to setup your own code when Trigger is called. * @param framesSinceTrigger Frames passed since TriggerFrame * @param timeSinceTrigger Time passed since timeSinceTrigger */ protected virtual void OnTrigger( float timeSinceTrigger ){ } /** * @brief Called when the event ends, i.e. we pass the end of it's range. * @sa Trigger */ public void Finish() { _hasFinished = true; #if UNITY_EDITOR PreEvent(); #endif if( Sequence.IsPlayingForward ) OnFinish(); // only do this code if we're moving forward, otherwise it doesn't really matter #if UNITY_EDITOR PostEvent(); #endif } /// @brief Used to setup your own code when Finish is called. protected virtual void OnFinish(){ } public sealed override void Init() { _hasTriggered = false; _hasFinished = false; // init doesn't get wrapped between Pre/PostEvent because // it is here that vars will be initialized OnInit(); } protected virtual void OnSetDefaultValues() { if (Sequence.CasterActor != null) { _casterTrans = Sequence.CasterActor.transform; } if (Sequence.TargetActor != null) { _targetTrans = Sequence.TargetActor.transform; } if (Sequence.CameraGo != null) { _cameraTrans = Sequence.CameraGo.transform; } } /// @brief Used to setup your own code for when the sequence is initialized protected virtual void OnInit() { } public void Pause() { #if UNITY_EDITOR PreEvent(); #endif OnPause(); #if UNITY_EDITOR PostEvent(); #endif } /// @brief Used to setup your own code for when the sequence is paused protected virtual void OnPause() { } public void Resume() { #if UNITY_EDITOR PreEvent(); #endif OnResume(); #if UNITY_EDITOR PostEvent(); #endif } /// @brief Used to setup your own code for when the sequence is resumed protected virtual void OnResume() { } public sealed override void Stop() { _hasTriggered = false; _hasFinished = false; #if UNITY_EDITOR PreEvent(); #endif OnStop(); #if UNITY_EDITOR PostEvent(); #endif } /// @brief Used to setup your own code for when the sequence is stopped protected virtual void OnStop(){} /** * @brief Called each time the sequence gets updated, if the current frame is in this event's range. * @param framesSinceTrigger How many frames have passed since TriggerFrame * @param timeSinceTrigger How much time passed since TriggerFrame */ public void UpdateEvent( int framesSinceTrigger, float timeSinceTrigger ) { #if UNITY_EDITOR PreEvent(); #endif if( !_hasTriggered ) { Trigger( timeSinceTrigger ); } OnUpdateEvent( timeSinceTrigger ); if( framesSinceTrigger == Length ) { Finish(); } #if UNITY_EDITOR PostEvent(); #endif } /** * @brief Used to setup your code that gets called when the event updates. * @param framesSinceTrigger How many frames passed since TriggerFrame * @param timeSinceTrigger How much time passed since TriggerFrame */ protected virtual void OnUpdateEvent( float timeSinceTrigger ) { } /** * @brief Used to mark objects used as not to be saved, in order to not make the scene dirty when * scrubbing the editor. * @note This is called before every call to FEvent, i.e. Trigger, UpdateEvent, Stop, etc. */ protected virtual void PreEvent() { #if UNITY_EDITOR if( !Application.isPlaying ) Owner.gameObject.hideFlags = HideFlags.DontSave; #endif } /** * @brief Used to mark objects used as to be saved again. * @note This is called after every call to FEvent, i.e. Trigger, UpdateEvent, Stop, etc. */ protected virtual void PostEvent() { #if UNITY_EDITOR if( !Application.isPlaying ) Owner.gameObject.hideFlags = HideFlags.None; #endif } /// @brief Returns \e true if it is the first event of the track it belongs to. public bool IsFirstEvent { get { return GetId() == 0; } } /// @brief Returns \e true if it is the last event of the track it belongs to. public bool IsLastEvent { get { return GetId() == _track.Events.Count-1; } } /// @brief Shortcut to FrameRange.Start public int Start { get{ return _frameRange.Start; } set{ _frameRange.Start = value; } } /// @brief Shortcut to FrameRange.End public int End { get { return _frameRange.End; } set{ _frameRange.End = value; } } /// @brief Shortcut to FrameRange.Length public int Length { get{ return _frameRange.Length; } set{ _frameRange.Length = value; } } /// @brief What this the event starts. /// @note This value isn't cached. public float StartTime { get { return _frameRange.Start * Sequence.InverseFrameRate; } } /// @brief What this the event ends. /// @note This value isn't cached. public float EndTime { get { return _frameRange.End * Sequence.InverseFrameRate; } } /// @brief Length of the event in seconds. /// @note This value isn't cached. public float LengthTime { get { return _frameRange.Length * Sequence.InverseFrameRate; } } /// @brief What's the minimum length this event can have? /// @warning Events cannot be smaller than 1 frame. public virtual int GetMinLength() { return 1; } /// @brief What's the maximum length this event can have? public virtual int GetMaxLength() { return int.MaxValue; } public virtual SecurityElement SaveToXml() { int eventType = (int)EventType; SecurityElement node = new SecurityElement("event"); node.AddAttribute("startFrame", this.FrameRange.Start.ToString()); node.AddAttribute("endFrame", this.FrameRange.End.ToString()); node.AddAttribute("type", eventType.ToString()); return node; } public SecurityElement WriteParamNode(string paramName, string paramVal, string paramValueType) { if (string.IsNullOrEmpty(paramName) || string.IsNullOrEmpty(paramVal) || string.IsNullOrEmpty(paramValueType)) return null; SecurityElement paramNode = new SecurityElement("param"); paramNode.AddAttribute("name", paramName); paramNode.AddAttribute("value", paramVal); paramNode.AddAttribute("valueType", paramValueType); return paramNode; } public virtual void LoadFromXml(SecurityElement eventNode) { if (eventNode == null || eventNode.Children == null) return; _eventParams = new List(); for (int idx = 0; idx < eventNode.Children.Count; idx++) { var paramNode = eventNode.Children[idx] as SecurityElement; FEventParam param = new FEventParam(); param.name = paramNode.Attribute("name"); param.val = paramNode.Attribute("value"); param.valType = paramNode.Attribute("valueType"); if (string.IsNullOrEmpty(param.valType)) param.valType = paramNode.Attribute("valType"); _eventParams.Add(param); } } protected string GetSParam(string paramName) { if (_eventParams == null) return null; for(int idx =0; idx < _eventParams.Count;idx++) { if (_eventParams[idx].name == paramName) return _eventParams[idx].val; } return null; } protected int GetNParam(string paramName) { string strVal = GetSParam(paramName); if (string.IsNullOrEmpty(strVal)) return 0; int iVal = 0; int.TryParse(strVal, out iVal); return iVal; } protected float GetFParam(string paramName) { string strVal = GetSParam(paramName); if (string.IsNullOrEmpty(strVal)) return 0; float fVal = 0; float.TryParse(strVal, out fVal); return fVal; } protected Vector3 GetVParam(string paramName) { string strVal = GetSParam(paramName); if (string.IsNullOrEmpty(strVal)) return Vector3.zero; Vector3 vec = StringUtil.convertVector3(strVal); return vec; } /// @brief Does the Event collides the \e e? public bool Collides( FEvent e ) { return _frameRange.Collides( e.FrameRange ); } /// @brief Returns the biggest frame range this event can have public FrameRange GetMaxFrameRange() { FrameRange range = new FrameRange(0, 0); int id = GetId(); if( id == 0 ) { range.Start = 0; } else { range.Start = _track.Events[id-1].End; } if( id == _track.Events.Count-1 ) // last one? { range.End = _track.Timeline.Sequence.Length; } else { range.End = _track.Events[id+1].Start; } return range; } /// @brief Compares events based on their start frame, basically used to order them. /// @param e1 Event /// @param e2 Event public static int Compare( FEvent e1, FEvent e2 ) { return e1.Start.CompareTo( e2.Start ); } } /** * @brief Attribute that adds an Event to the add event menu. */ public class FEventAttribute : System.Attribute { // menu path public string menu; // type of track to be used public Type trackType; // public object _color = null; public FEventAttribute( string menu ) :this( menu, typeof(FTrack) ) { } public FEventAttribute( string menu, Type trackType ) { this.menu = menu; this.trackType = trackType; } // public FEventAttribute( string menu, Color color ) // :this( menu ) // { // _color = color; // } } }