Przeglądaj źródła

Added basic support for animation events

BearishSun 9 lat temu
rodzic
commit
773d549e57

+ 2 - 0
Source/BansheeCore/CMakeSources.cmake

@@ -504,6 +504,7 @@ set(BS_BANSHEECORE_INC_ANIMATION
 	"Include/BsAnimation.h"
 	"Include/BsAnimationManager.h"
 	"Include/BsCurveCache.h"
+	"Include/BsAnimationUtility.h"
 )
 
 set(BS_BANSHEECORE_SRC_ANIMATION
@@ -512,6 +513,7 @@ set(BS_BANSHEECORE_SRC_ANIMATION
 	"Source/BsSkeleton.cpp"
 	"Source/BsAnimation.cpp"
 	"Source/BsAnimationManager.cpp"
+	"Source/BsAnimationUtility.cpp"
 )
 
 source_group("Header Files\\Components" FILES ${BS_BANSHEECORE_INC_COMPONENTS})

+ 11 - 0
Source/BansheeCore/Include/BsAnimation.h

@@ -279,9 +279,20 @@ namespace BansheeEngine
 		 */
 		void setState(const HAnimationClip& clip, AnimationClipState state);
 
+		/** 
+		 * Triggers any events between the last frame and current one. 
+		 *
+		 * @param[in]	lastFrameTime	Time of the last frame.
+		 * @param[in]	delta			Difference between the last and this frame.
+		 */
+		void triggerEvents(float lastFrameTime, float delta);
+
 		/** Creates a new empty Animation object. */
 		static SPtr<Animation> create();
 
+		/** Triggered whenever an animation event is reached. */
+		Event<void(const HAnimationClip&, const String&)> onEventTriggered;
+
 		/** @name Internal 
 		 *  @{
 		 */

+ 29 - 0
Source/BansheeCore/Include/BsAnimationClip.h

@@ -73,6 +73,21 @@ namespace BansheeEngine
 		Vector<TNamedAnimationCurve<float>> generic;
 	};
 
+	/** Event that is triggered when animation reaches a certain point. */
+	struct AnimationEvent
+	{
+		AnimationEvent()
+			:time(0.0f)
+		{ }
+
+		AnimationEvent(const String& name, float time)
+			:name(name), time(time)
+		{ }
+
+		String name;
+		float time;
+	};
+
 	/** Types of curves in an AnimationClip. */
 	enum class CurveType
 	{
@@ -100,6 +115,12 @@ namespace BansheeEngine
 		/** Assigns a new set of curves to be used by the animation. The clip will store a copy of this object.*/
 		void setCurves(const AnimationCurves& curves);
 
+		/** Returns all events that will be triggered by the animation. */
+		const Vector<AnimationEvent>& getEvents() const { return mEvents; }
+
+		/** Sets events that will be triggered as the animation is playing. */
+		void setEvents(const Vector<AnimationEvent>& events) { mEvents = events; }
+
 		/**
 		 * Maps skeleton bone names to animation clip names, and returns a set of indices that can be easily used for
 		 * locating an animation curve based on the bone index.
@@ -117,6 +138,9 @@ namespace BansheeEngine
 		 */
 		bool isAdditive() const { return mIsAdditive; }
 
+		/** Returns the length of the animation clip, in seconds. */
+		float getLength() const { return mLength; }
+
 		/** 
 		 * Returns a version that can be used for detecting modifications on the clip by external systems. Whenever the clip
 		 * is modified the version is increased by one.
@@ -158,6 +182,9 @@ namespace BansheeEngine
 		/** Creates a name -> curve index mapping for quicker curve lookup by name. */
 		void buildNameMapping();
 
+		/** Calculate the length of the clip based on assigned curves. */
+		void calculateLength();
+
 		UINT64 mVersion;
 
 		/** 
@@ -172,7 +199,9 @@ namespace BansheeEngine
 		 */
 		UnorderedMap<String, UINT32[4]> mNameMapping;
 
+		Vector<AnimationEvent> mEvents;
 		bool mIsAdditive;
+		float mLength;
 
 		/************************************************************************/
 		/* 								SERIALIZATION                      		*/

+ 51 - 0
Source/BansheeCore/Include/BsAnimationClipRTTI.h

@@ -14,6 +14,55 @@ namespace BansheeEngine
 	 *  @{
 	 */
 
+	template<>
+	struct RTTIPlainType<AnimationEvent>
+	{
+		enum { id = TID_AnimationEvent }; enum { hasDynamicSize = 1 };
+
+		/** @copydoc RTTIPlainType::toMemory */
+		static void toMemory(const AnimationEvent& data, char* memory)
+		{
+			UINT32 size = sizeof(UINT32);
+			char* memoryStart = memory;
+			memory += sizeof(UINT32);
+
+			UINT8 version = 0;
+			memory = rttiWriteElem(version, memory, size);
+			memory = rttiWriteElem(data.time, memory, size);
+			memory = rttiWriteElem(data.name, memory, size);
+
+			memcpy(memoryStart, &size, sizeof(UINT32));
+		}
+
+		/** @copydoc RTTIPlainType::fromMemory */
+		static UINT32 fromMemory(AnimationEvent& data, char* memory)
+		{
+			UINT32 size = 0;
+			memory = rttiReadElem(size, memory);
+
+			UINT8 version;
+			memory = rttiReadElem(version, memory);
+			assert(version == 0);
+
+			memory = rttiReadElem(data.time, memory);
+			memory = rttiReadElem(data.name, memory);
+
+			return size;
+		}
+
+		/** @copydoc RTTIPlainType::getDynamicSize */
+		static UINT32 getDynamicSize(const AnimationEvent& data)
+		{
+			UINT64 dataSize = sizeof(UINT32) * 2;
+			dataSize += rttiGetElemSize(data.time);
+			dataSize += rttiGetElemSize(data.name);
+
+			assert(dataSize <= std::numeric_limits<UINT32>::max());
+
+			return (UINT32)dataSize;
+		}
+	};
+
 	class BS_CORE_EXPORT AnimationClipRTTI : public RTTIType <AnimationClip, Resource, AnimationClipRTTI>
 	{
 	private:
@@ -23,6 +72,8 @@ namespace BansheeEngine
 			BS_RTTI_MEMBER_PLAIN_NAMED(scaleCurves, mCurves->scale, 2)
 			BS_RTTI_MEMBER_PLAIN_NAMED(genericCurves, mCurves->generic, 3)
 			BS_RTTI_MEMBER_PLAIN(mIsAdditive, 4)
+			BS_RTTI_MEMBER_PLAIN(mLength, 5)
+			BS_RTTI_MEMBER_PLAIN(mEvents, 6)
 		BS_END_RTTI_MEMBERS
 	public:
 		AnimationClipRTTI()

+ 3 - 3
Source/BansheeCore/Include/BsAnimationCurve.h

@@ -87,6 +87,9 @@ namespace BansheeEngine
 		 */
 		void makeAdditive();
 
+		/** Returns the length of the animation curve, from time zero to last keyframe. */
+		float getLength() const { return mEnd; }
+
 		/** Returns the total number of key-frames in the curve. */
 		UINT32 getNumKeyFrames() const { return (UINT32)mKeyframes.size(); }
 
@@ -143,9 +146,6 @@ namespace BansheeEngine
 		 */
 		T evaluateCache(float time, const TCurveCache<T>& animInstance) const;
 
-		/** Clamps the time argument to valid range. */
-		void clampTime(float& time, bool loop) const;
-
 		static const UINT32 CACHE_LOOKAHEAD;
 
 		Vector<KeyFrame> mKeyframes;

+ 30 - 0
Source/BansheeCore/Include/BsAnimationUtility.h

@@ -0,0 +1,30 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
+#pragma once
+
+#include "BsCorePrerequisites.h"
+#include "BsCoreObject.h"
+
+namespace BansheeEngine
+{
+	/** @addtogroup Animation
+	 *  @{
+	 */
+
+	/** Helper class for dealing with animations, animation clips and curves. */
+	class BS_CORE_EXPORT AnimationUtility : public CoreObject
+	{
+	public:
+		/**
+		 * Wraps or clamps the provided time value between the provided range.
+		 *
+		 * @param[in,out]	time	Time value to wrap/clamp.
+		 * @param[in]		start	Start of the range.
+		 * @param[in]		end		End of the range.
+		 * @param[in]		loop	If true the value will be wrapped, otherwise clamped to range.
+		 */
+		static void wrapTime(float& time, float start, float end, bool loop);
+	};
+
+	/** @} */
+}

+ 2 - 1
Source/BansheeCore/Include/BsCorePrerequisites.h

@@ -527,7 +527,8 @@ namespace BansheeEngine
 		TID_Skeleton = 1119,
 		TID_SkeletonBoneInfo = 1120,
 		TID_AnimationSplitInfo = 1121,
-		TID_CAnimation = 1122
+		TID_CAnimation = 1122,
+		TID_AnimationEvent = 1123
 	};
 }
 

+ 39 - 0
Source/BansheeCore/Source/BsAnimation.cpp

@@ -3,6 +3,7 @@
 #include "BsAnimation.h"
 #include "BsAnimationManager.h"
 #include "BsAnimationClip.h"
+#include "BsAnimationUtility.h"
 
 namespace BansheeEngine
 {
@@ -761,6 +762,44 @@ namespace BansheeEngine
 		mDirty |= AnimDirtyStateFlag::Value;
 	}
 
+	void Animation::triggerEvents(float lastFrameTime, float delta)
+	{
+		for (auto& clipInfo : mClipInfos)
+		{
+			if (!clipInfo.clip.isLoaded())
+				continue;
+
+			const Vector<AnimationEvent>& events = clipInfo.clip->getEvents();
+			bool loop = clipInfo.state.wrapMode == AnimWrapMode::Loop;
+
+			float start = lastFrameTime;
+			float end = start + delta;
+
+			float clipLength = clipInfo.clip->getLength();
+			AnimationUtility::wrapTime(start, 0.0f, clipLength, loop);
+			AnimationUtility::wrapTime(end, 0.0f, clipLength, false);
+
+			for (auto& event : events)
+			{
+				if (event.time > start && event.time <= end)
+					onEventTriggered(clipInfo.clip, event.name);
+			}
+
+			// Check the looped portion
+			if(loop && end >= clipLength)
+			{
+				start = 0.0f;
+				end = end - clipLength;
+
+				for (auto& event : events)
+				{
+					if (event.time > start && event.time <= end)
+						onEventTriggered(clipInfo.clip, event.name);
+				}
+			}
+		}
+	}
+
 	SPtr<Animation> Animation::create()
 	{
 		Animation* anim = new (bs_alloc<Animation>()) Animation();

+ 22 - 3
Source/BansheeCore/Source/BsAnimationClip.cpp

@@ -81,15 +81,16 @@ namespace BansheeEngine
 	}
 
 	AnimationClip::AnimationClip()
-		: Resource(false), mVersion(0), mCurves(bs_shared_ptr_new<AnimationCurves>()), mIsAdditive(false)
+		: Resource(false), mVersion(0), mCurves(bs_shared_ptr_new<AnimationCurves>()), mIsAdditive(false), mLength(0.0f)
 	{
 
 	}
 
 	AnimationClip::AnimationClip(const SPtr<AnimationCurves>& curves, bool isAdditive)
-		: Resource(false), mVersion(0), mCurves(curves), mIsAdditive(isAdditive)
+		: Resource(false), mVersion(0), mCurves(curves), mIsAdditive(isAdditive), mLength(0.0f)
 	{
-
+		buildNameMapping();
+		calculateLength();
 	}
 
 	HAnimationClip AnimationClip::create(bool isAdditive)
@@ -128,9 +129,27 @@ namespace BansheeEngine
 		*mCurves = curves;
 
 		buildNameMapping();
+		calculateLength();
 		mVersion++;
 	}
 
+	void AnimationClip::calculateLength()
+	{
+		mLength = 0.0f;
+
+		for (auto& entry : mCurves->position)
+			mLength = std::max(mLength, entry.curve.getLength());
+
+		for (auto& entry : mCurves->rotation)
+			mLength = std::max(mLength, entry.curve.getLength());
+
+		for (auto& entry : mCurves->scale)
+			mLength = std::max(mLength, entry.curve.getLength());
+
+		for (auto& entry : mCurves->generic)
+			mLength = std::max(mLength, entry.curve.getLength());
+	}
+
 	void AnimationClip::buildNameMapping()
 	{
 		mNameMapping.clear();

+ 3 - 24
Source/BansheeCore/Source/BsAnimationCurve.cpp

@@ -5,6 +5,7 @@
 #include "BsVector3.h"
 #include "BsQuaternion.h"
 #include "BsMath.h"
+#include "BsAnimationUtility.h"
 
 namespace BansheeEngine
 {
@@ -256,7 +257,7 @@ namespace BansheeEngine
 		if (mKeyframes.size() == 0)
 			return T();
 
-		clampTime(time, loop);
+		AnimationUtility::wrapTime(time, mStart, mEnd, loop);
 
 		UINT32 leftKeyIdx;
 		UINT32 rightKeyIdx;
@@ -298,7 +299,7 @@ namespace BansheeEngine
 		if (mKeyframes.size() == 0)
 			return TKeyframe<T>();
 
-		clampTime(time, loop);
+		AnimationUtility::wrapTime(time, mStart, mEnd, loop);
 
 		UINT32 leftKeyIdx;
 		UINT32 rightKeyIdx;
@@ -508,28 +509,6 @@ namespace BansheeEngine
 			mKeyframes[i].value = getDiff(mKeyframes[i].value, refKey.value);
 	}
 
-	template <class T>
-	void TAnimationCurve<T>::clampTime(float& time, bool loop) const
-	{
-		// Clamp to start or loop
-		if (time < mStart)
-		{
-			if (loop)
-				time = time - std::floor(time / mLength) * mLength;
-			else // Clamping
-				time = mStart;
-		}
-
-		// Clamp to end or loop
-		if (time > mEnd)
-		{
-			if (loop)
-				time = time - std::floor(time / mLength) * mLength;
-			else // Clamping
-				time = mEnd;
-		}
-	}
-
 	template class TAnimationCurve<Vector3>;
 	template class TAnimationCurve<Quaternion>;
 	template class TAnimationCurve<float>;

+ 4 - 0
Source/BansheeCore/Source/BsAnimationManager.cpp

@@ -39,6 +39,10 @@ namespace BansheeEngine
 		// Make sure we don't load obsolete skeletal pose and other evaluation ouputs written by the animation thread
 		std::atomic_thread_fence(std::memory_order_acquire);
 
+		// Trigger events
+		for (auto& anim : mAnimations)
+			anim.second->triggerEvents(mAnimationTime, gTime().getFrameDelta());
+
 		// TODO - Write TRS animation results to relevant SceneObjects
 		// TODO - Transfer generic curve evaluated data back to Animation
 

+ 29 - 0
Source/BansheeCore/Source/BsAnimationUtility.cpp

@@ -0,0 +1,29 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
+#include "BsAnimationUtility.h"
+
+namespace BansheeEngine
+{
+	void AnimationUtility::wrapTime(float& time, float start, float end, bool loop)
+	{
+		float length = end - start;
+
+		// Clamp to start or loop
+		if (time < start)
+		{
+			if (loop)
+				time = time - std::floor(time / length) * length;
+			else // Clamping
+				time = start;
+		}
+
+		// Clamp to end or loop
+		if (time > end)
+		{
+			if (loop)
+				time = time - std::floor(time / length) * length;
+			else // Clamping
+				time = end;
+		}
+	}
+}

+ 6 - 0
Source/BansheeEngine/Include/BsCAnimation.h

@@ -66,6 +66,9 @@ namespace BansheeEngine
 		/** @copydoc Animation::setState */
 		void setState(const HAnimationClip& clip, AnimationClipState state);
 
+		/** Triggered whenever an animation event is reached. */
+		Event<void(const HAnimationClip&, const String&)> onEventTriggered;
+
 		/** @name Internal
 		 *  @{
 		 */
@@ -101,6 +104,9 @@ namespace BansheeEngine
 		/** Destroys the internal Animation representation. */
 		void destroyInternal();
 
+		/** Callback triggered whenever an animation event is triggered. */
+		void eventTriggered(const HAnimationClip& clip, const String& name);
+
 		SPtr<Animation> mInternal;
 
 		HAnimationClip mDefaultClip;

+ 8 - 0
Source/BansheeEngine/Source/BsCAnimation.cpp

@@ -128,7 +128,10 @@ namespace BansheeEngine
 	void CAnimation::restoreInternal()
 	{
 		if (mInternal == nullptr)
+		{
 			mInternal = Animation::create();
+			mInternal->onEventTriggered.connect(std::bind(&CAnimation::eventTriggered, this, _1, _2));
+		}
 
 		mInternal->setWrapMode(mWrapMode);
 		mInternal->setSpeed(mSpeed);
@@ -161,6 +164,11 @@ namespace BansheeEngine
 		mInternal = nullptr;
 	}
 
+	void CAnimation::eventTriggered(const HAnimationClip& clip, const String& name)
+	{
+		onEventTriggered(clip, name);
+	}
+
 	RTTITypeBase* CAnimation::getRTTIStatic()
 	{
 		return CAnimationRTTI::instance();

+ 11 - 0
Source/MBansheeEngine/Animation/Animation.cs

@@ -321,6 +321,7 @@ namespace BansheeEngine
                 _native.Destroy();
 
             _native = new NativeAnimation();
+            _native.OnEventTriggered += EventTriggered;
 
             // Restore saved values after reset
             _native.WrapMode = serializableData.wrapMode;
@@ -359,6 +360,16 @@ namespace BansheeEngine
             }
         }
 
+        /// <summary>
+        /// Called whenever an animation event triggers.
+        /// </summary>
+        /// <param name="clip">Clip that the event originated from.</param>
+        /// <param name="name">Name of the event.</param>
+        private void EventTriggered(AnimationClip clip, string name)
+        {
+            // TODO - Find a scene object, component and method based on the event name, and call it
+        }
+
         /// <summary>
         /// Holds all data the animation component needs to persist through serialization.
         /// </summary>

+ 53 - 0
Source/MBansheeEngine/Animation/AnimationClip.cs

@@ -15,6 +15,14 @@ namespace BansheeEngine
     /// </summary>
     public class AnimationClip : Resource
     {
+        /// <summary>
+        /// Returns the length of the animation clip, in seconds.
+        /// </summary>
+        public float Length
+        {
+            get { return Internal_GetLength(mCachedPtr); }
+        }
+
         /// <summary>
         /// A set of all curves stored in the animation clip.
         /// </summary>
@@ -24,6 +32,15 @@ namespace BansheeEngine
             set { Internal_SetAnimationCurves(mCachedPtr, value); }
         }
 
+        /// <summary>
+        /// A set of all events stored in the animation clip.
+        /// </summary>
+        public AnimationEvent[] Events
+        {
+            get { return Internal_GetAnimationEvents(mCachedPtr); }
+            set { Internal_SetAnimationEvents(mCachedPtr, value); }
+        }
+
         /// <summary>
         /// Constructor for internal use by the runtime.
         /// </summary>
@@ -35,6 +52,42 @@ namespace BansheeEngine
 
         [MethodImpl(MethodImplOptions.InternalCall)]
         private static extern void Internal_SetAnimationCurves(IntPtr thisPtr, AnimationCurves curves);
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern AnimationEvent[] Internal_GetAnimationEvents(IntPtr thisPtr);
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern void Internal_SetAnimationEvents(IntPtr thisPtr, AnimationEvent[] events);
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern float Internal_GetLength(IntPtr thisPtr);
+    }
+
+    /// <summary>
+    /// Event that is triggered when animation reaches a certain point.
+    /// </summary>
+    public class AnimationEvent
+    {
+        /// <summary>
+        /// Constructs a new animation event.
+        /// </summary>
+        /// <param name="name">Name used to identify the event when it is triggered.</param>
+        /// <param name="time">Time at which to trigger the event, in seconds.</param>
+        public AnimationEvent(string name, float time)
+        {
+            Name = name;
+            Time = time;
+        }
+
+        /// <summary>
+        /// Name used to identify the event when it is triggered.
+        /// </summary>
+        public string Name;
+
+        /// <summary>
+        /// Time at which to trigger the event, in seconds.
+        /// </summary>
+        public float Time;
     }
 
     /** @} */

+ 7 - 0
Source/MBansheeEngine/Animation/Interop/NativeAnimation.cs

@@ -23,6 +23,8 @@ namespace BansheeEngine
             set { Internal_SetSpeed(mCachedPtr, value); }
         }
 
+        public Action<AnimationClip, string> OnEventTriggered;
+
         public void Play(AnimationClip clip)
         {
             IntPtr clipPtr = IntPtr.Zero;
@@ -109,6 +111,11 @@ namespace BansheeEngine
             Internal_Destroy(mCachedPtr);
         }
 
+        private void Internal_OnEventTriggered(AnimationClip clip, string name)
+        {
+            OnEventTriggered?.Invoke(clip, name);
+        }
+
         [MethodImpl(MethodImplOptions.InternalCall)]
         private static extern void Internal_Create(NativeAnimation instance);
 

+ 2 - 0
Source/SBansheeEngine/CMakeSources.cmake

@@ -143,6 +143,7 @@ set(BS_SBANSHEEENGINE_INC_WRAPPERS
 	"Include/BsScriptAnimationClip.h"
 	"Include/BsScriptAnimation.h"
 	"Include/BsScriptAnimationCurve.h"
+	"Include/BsScriptAnimationCurves.h"
 )
 
 set(BS_SBANSHEEENGINE_INC_WRAPPERS_GUI
@@ -276,6 +277,7 @@ set(BS_SBANSHEEENGINE_SRC_WRAPPERS
 	"Source/BsScriptAnimationClip.cpp"
 	"Source/BsScriptAnimation.cpp"
 	"Source/BsScriptAnimationCurve.cpp"
+	"Source/BsScriptAnimationCurves.cpp"
 )
 
 set(BS_SBANSHEEENGINE_INC_SERIALIZATION

+ 6 - 0
Source/SBansheeEngine/Include/BsScriptAnimation.h

@@ -27,6 +27,9 @@ namespace BansheeEngine
 		ScriptAnimation(MonoObject* managedInstance);
 		~ScriptAnimation();
 
+		/** Callback triggered when animation event triggers. */
+		void eventTriggered(const HAnimationClip& clip, const String& name);
+
 		SPtr<Animation> mAnimation;
 
 		/************************************************************************/
@@ -50,6 +53,9 @@ namespace BansheeEngine
 
 		static bool internal_GetState(ScriptAnimation* thisPtr, ScriptAnimationClip* clip, AnimationClipState* state);
 		static void internal_SetState(ScriptAnimation* thisPtr, ScriptAnimationClip* clip, AnimationClipState* state);
+
+		typedef void(__stdcall *OnEventTriggeredThunkDef) (MonoObject*, MonoObject*, MonoString*, MonoException**);
+		static OnEventTriggeredThunkDef sOnEventTriggeredThunk;
 	};
 
 	/** Helper class for dealing with BlendSequentialClipInfo structure. */

+ 24 - 0
Source/SBansheeEngine/Include/BsScriptAnimationClip.h

@@ -31,6 +31,30 @@ namespace BansheeEngine
 		/************************************************************************/
 		static MonoObject* internal_GetAnimationCurves(ScriptAnimationClip* thisPtr);
 		static void internal_SetAnimationCurves(ScriptAnimationClip* thisPtr, MonoObject* curves);
+		static MonoArray* internal_GetAnimationEvents(ScriptAnimationClip* thisPtr);
+		static void internal_SetAnimationEvents(ScriptAnimationClip* thisPtr, MonoArray* events);
+		static float internal_GetLength(ScriptAnimationClip* thisPtr);
+	};
+
+	/**	Interop class between C++ & CLR for AnimationEvent. */
+	class BS_SCR_BE_EXPORT ScriptAnimationEvent : public ScriptObject<ScriptAnimationEvent>
+	{
+	public:
+		SCRIPT_OBJ(ENGINE_ASSEMBLY, "BansheeEngine", "AnimationEvent")
+
+		/** Converts managed animation event its native counterpart. */
+		static AnimationEvent toNative(MonoObject* object);
+
+		/** Converts native animation event to its managed counterpart. */
+		static MonoObject* toManaged(const AnimationEvent& event);
+	private:
+		ScriptAnimationEvent(MonoObject* instance);
+
+		/************************************************************************/
+		/* 								CLR HOOKS						   		*/
+		/************************************************************************/
+		static MonoField* sNameField;
+		static MonoField* sTimeField;
 	};
 
 	/** @} */

+ 19 - 0
Source/SBansheeEngine/Source/BsScriptAnimation.cpp

@@ -3,17 +3,24 @@
 #include "BsScriptAnimation.h"
 #include "BsScriptMeta.h"
 #include "BsMonoField.h"
+#include "BsMonoMethod.h"
 #include "BsMonoClass.h"
 #include "BsMonoManager.h"
 #include "BsMonoUtil.h"
+#include "BsScriptResourceManager.h"
 #include "BsScriptAnimationClip.h"
 
+using namespace std::placeholders;
+
 namespace BansheeEngine
 {
+	ScriptAnimation::OnEventTriggeredThunkDef ScriptAnimation::sOnEventTriggeredThunk = nullptr;
+
 	ScriptAnimation::ScriptAnimation(MonoObject* managedInstance)
 		:ScriptObject(managedInstance), mAnimation(nullptr)
 	{
 		mAnimation = Animation::create();
+		mAnimation->onEventTriggered.connect(std::bind(&ScriptAnimation::eventTriggered, this, _1, _2));
 	}
 
 	ScriptAnimation::~ScriptAnimation()
@@ -39,6 +46,18 @@ namespace BansheeEngine
 
 		metaData.scriptClass->addInternalCall("Internal_GetState", &ScriptAnimation::internal_GetState);
 		metaData.scriptClass->addInternalCall("Internal_SetState", &ScriptAnimation::internal_SetState);
+
+		sOnEventTriggeredThunk = (OnEventTriggeredThunkDef)metaData.scriptClass->getMethod("Internal_OnEventTriggered", 2)->getThunk();
+	}
+
+	void ScriptAnimation::eventTriggered(const HAnimationClip& clip, const String& name)
+	{
+		ScriptAnimationClip* scriptClip = nullptr;
+		ScriptResourceManager::instance().getScriptResource(clip, &scriptClip, true);
+
+		MonoString* monoName = MonoUtil::stringToMono(name);
+
+		MonoUtil::invokeThunk(sOnEventTriggeredThunk, mManagedInstance, scriptClip->getManagedInstance(), monoName);
 	}
 
 	void ScriptAnimation::internal_Create(MonoObject* instance)

+ 75 - 1
Source/SBansheeEngine/Source/BsScriptAnimationClip.cpp

@@ -17,8 +17,11 @@ namespace BansheeEngine
 	{
 		metaData.scriptClass->addInternalCall("Internal_GetAnimationCurves", &ScriptAnimationClip::internal_GetAnimationCurves);
 		metaData.scriptClass->addInternalCall("Internal_SetAnimationCurves", &ScriptAnimationClip::internal_SetAnimationCurves);
+		metaData.scriptClass->addInternalCall("Internal_GetAnimationEvents", &ScriptAnimationClip::internal_GetAnimationEvents);
+		metaData.scriptClass->addInternalCall("Internal_SetAnimationEvents", &ScriptAnimationClip::internal_SetAnimationEvents);
+		metaData.scriptClass->addInternalCall("Internal_GetLength", &ScriptAnimationClip::internal_GetLength);
 	}
-	
+
 	MonoObject* ScriptAnimationClip::createInstance()
 	{
 		return metaData.scriptClass->createInstance();
@@ -35,4 +38,75 @@ namespace BansheeEngine
 		SPtr<AnimationCurves> nativeCurves = ScriptAnimationCurves::toNative(curves);
 		thisPtr->getHandle()->setCurves(*nativeCurves);
 	}
+
+	MonoArray* ScriptAnimationClip::internal_GetAnimationEvents(ScriptAnimationClip* thisPtr)
+	{
+		const Vector<AnimationEvent>& events = thisPtr->getHandle()->getEvents();
+
+		UINT32 numEvents = (UINT32)events.size();
+		ScriptArray eventsArray = ScriptArray::create<ScriptAnimationEvent>(numEvents);
+		for (UINT32 i = 0; i < numEvents; i++)
+			eventsArray.set(i, ScriptAnimationEvent::toManaged(events[i]));
+
+		return eventsArray.getInternal();
+	}
+
+	void ScriptAnimationClip::internal_SetAnimationEvents(ScriptAnimationClip* thisPtr, MonoArray* events)
+	{
+		Vector<AnimationEvent> nativeEvents;
+
+		if (events != nullptr)
+		{
+			ScriptArray eventsArray(events);
+
+			for (UINT32 i = 0; i < eventsArray.size(); i++)
+			{
+				AnimationEvent event = ScriptAnimationEvent::toNative(eventsArray.get<MonoObject*>(i));
+				nativeEvents.push_back(event);
+			}
+		}
+
+		thisPtr->getHandle()->setEvents(nativeEvents);
+	}
+
+	float ScriptAnimationClip::internal_GetLength(ScriptAnimationClip* thisPtr)
+	{
+		return thisPtr->getHandle()->getLength();
+	}
+
+	MonoField* ScriptAnimationEvent::sNameField = nullptr;
+	MonoField* ScriptAnimationEvent::sTimeField = nullptr;
+
+	ScriptAnimationEvent::ScriptAnimationEvent(MonoObject* instance)
+		:ScriptObject(instance)
+	{ }
+
+	void ScriptAnimationEvent::initRuntimeData()
+	{
+		sNameField = metaData.scriptClass->getField("Name");
+		sTimeField = metaData.scriptClass->getField("Time");
+	}
+
+	AnimationEvent ScriptAnimationEvent::toNative(MonoObject* instance)
+	{
+		AnimationEvent output;
+
+		MonoString* monoName = nullptr;
+		sNameField->getValue(instance, &monoName);
+
+		output.name = MonoUtil::monoToString(monoName);
+
+		sTimeField->getValue(instance, &output.time);
+
+		return output;
+	}
+
+	MonoObject* ScriptAnimationEvent::toManaged(const AnimationEvent& event)
+	{
+		MonoString* monoString = MonoUtil::stringToMono(event.name);
+		float time = event.time;
+
+		void* params[2] = { monoString, &time };
+		return metaData.scriptClass->createInstance("string, float", params);
+	}
 }