InteractionObject.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526
  1. using UnityEngine;
  2. using System.Collections;
  3. using UnityEngine.Events;
  4. namespace RootMotion.FinalIK {
  5. /// <summary>
  6. /// Object than the InteractionSystem can interact with.
  7. /// </summary>
  8. [HelpURL("https://www.youtube.com/watch?v=r5jiZnsDH3M")]
  9. [AddComponentMenu("Scripts/RootMotion.FinalIK/Interaction System/Interaction Object")]
  10. public class InteractionObject : MonoBehaviour {
  11. // Open the User Manual URL
  12. [ContextMenu("User Manual")]
  13. void OpenUserManual()
  14. {
  15. Application.OpenURL("http://www.root-motion.com/finalikdox/html/page10.html");
  16. }
  17. // Open the Script Reference URL
  18. [ContextMenu("Scrpt Reference")]
  19. void OpenScriptReference()
  20. {
  21. Application.OpenURL("http://www.root-motion.com/finalikdox/html/class_root_motion_1_1_final_i_k_1_1_interaction_object.html");
  22. }
  23. // Open a video tutorial video
  24. [ContextMenu("TUTORIAL VIDEO (PART 1: BASICS)")]
  25. void OpenTutorial1() {
  26. Application.OpenURL("https://www.youtube.com/watch?v=r5jiZnsDH3M");
  27. }
  28. // Open a video tutorial video
  29. [ContextMenu("TUTORIAL VIDEO (PART 2: PICKING UP...)")]
  30. void OpenTutorial2() {
  31. Application.OpenURL("https://www.youtube.com/watch?v=eP9-zycoHLk");
  32. }
  33. // Open a video tutorial video
  34. [ContextMenu("TUTORIAL VIDEO (PART 3: ANIMATION)")]
  35. void OpenTutorial3() {
  36. Application.OpenURL("https://www.youtube.com/watch?v=sQfB2RcT1T4&index=14&list=PLVxSIA1OaTOu8Nos3CalXbJ2DrKnntMv6");
  37. }
  38. // Open a video tutorial video
  39. [ContextMenu("TUTORIAL VIDEO (PART 4: TRIGGERS)")]
  40. void OpenTutorial4() {
  41. Application.OpenURL("https://www.youtube.com/watch?v=-TDZpNjt2mk&index=15&list=PLVxSIA1OaTOu8Nos3CalXbJ2DrKnntMv6");
  42. }
  43. // Link to the Final IK Google Group
  44. [ContextMenu("Support Group")]
  45. void SupportGroup() {
  46. Application.OpenURL("https://groups.google.com/forum/#!forum/final-ik");
  47. }
  48. // Link to the Final IK Asset Store thread in the Unity Community
  49. [ContextMenu("Asset Store Thread")]
  50. void ASThread() {
  51. Application.OpenURL("http://forum.unity3d.com/threads/final-ik-full-body-ik-aim-look-at-fabrik-ccd-ik-1-0-released.222685/");
  52. }
  53. #region Main Interface
  54. /// <summary>
  55. /// Predefined interaction events for pausing, picking up, triggering animations and sending messages.
  56. /// </summary>
  57. [System.Serializable]
  58. public class InteractionEvent {
  59. /// <summary>
  60. /// The time of the event since interaction start.
  61. /// </summary>
  62. [Tooltip("The time of the event since interaction start.")]
  63. public float time;
  64. /// <summary>
  65. /// If true, the interaction will be paused on this event. The interaction can be resumed by InteractionSystem.ResumeInteraction() or InteractionSystem.ResumeAll;
  66. /// </summary>
  67. [Tooltip("If true, the interaction will be paused on this event. The interaction can be resumed by InteractionSystem.ResumeInteraction() or InteractionSystem.ResumeAll;")]
  68. public bool pause;
  69. /// <summary>
  70. /// If true, the object will be parented to the effector bone on this event. Note that picking up like this can be done by only a single effector at a time.
  71. /// If you wish to pick up an object with both hands, see the Interaction PickUp2Handed demo scene.
  72. /// </summary>
  73. [Tooltip("If true, the object will be parented to the effector bone on this event. Note that picking up like this can be done by only a single effector at a time. If you wish to pick up an object with both hands, see the Interaction PickUp2Handed demo scene.")]
  74. public bool pickUp;
  75. /// <summary>
  76. /// The animations called on this event.
  77. /// </summary>
  78. [Tooltip("The animations called on this event.")]
  79. public AnimatorEvent[] animations;
  80. /// <summary>
  81. /// The messages sent on this event using GameObject.SendMessage().
  82. /// </summary>
  83. [Tooltip("The messages sent on this event using GameObject.SendMessage().")]
  84. public Message[] messages;
  85. [TooltipAttribute("The UnityEvent to invoke on this event.")]
  86. /// <summary>
  87. /// The UnityEvent to invoke on this event.
  88. /// </summary>
  89. public UnityEvent unityEvent;
  90. // Activates this event
  91. public void Activate(Transform t) {
  92. unityEvent.Invoke();
  93. foreach (AnimatorEvent e in animations) e.Activate(pickUp);
  94. foreach (Message m in messages) m.Send(t);
  95. }
  96. }
  97. /// <summary>
  98. /// Definition of a message sent by an InteractionEvent.
  99. /// </summary>
  100. [System.Serializable]
  101. public class Message {
  102. /// <summary>
  103. /// The name of the function called.
  104. /// </summary>
  105. [Tooltip("The name of the function called.")]
  106. public string function;
  107. /// <summary>
  108. /// The recipient game object.
  109. /// </summary>
  110. [Tooltip("The recipient game object.")]
  111. public GameObject recipient;
  112. private const string empty = "";
  113. // Sends the message to the recipient
  114. public void Send(Transform t) {
  115. if (recipient == null) return;
  116. if (function == string.Empty || function == empty) return;
  117. recipient.SendMessage(function, t, SendMessageOptions.RequireReceiver);
  118. }
  119. }
  120. /// <summary>
  121. /// Calls an animation on an interaction event.
  122. /// </summary>
  123. [System.Serializable]
  124. public class AnimatorEvent {
  125. /// <summary>
  126. /// The Animator component that will receive the AnimatorEvents.
  127. /// </summary>
  128. [Tooltip("The Animator component that will receive the AnimatorEvents.")]
  129. public Animator animator;
  130. /// <summary>
  131. /// The Animation component that will receive the AnimatorEvents (Legacy).
  132. /// </summary>
  133. [Tooltip("The Animation component that will receive the AnimatorEvents (Legacy).")]
  134. public Animation animation;
  135. /// <summary>
  136. /// The name of the animation state.
  137. /// </summary>
  138. [Tooltip("The name of the animation state.")]
  139. public string animationState;
  140. /// <summary>
  141. /// The crossfading time.
  142. /// </summary>
  143. [Tooltip("The crossfading time.")]
  144. public float crossfadeTime = 0.3f;
  145. /// <summary>
  146. /// The layer of the animation state (if using Legacy, the animation state will be forced to this layer).
  147. /// </summary>
  148. [Tooltip("The layer of the animation state (if using Legacy, the animation state will be forced to this layer).")]
  149. public int layer;
  150. /// <summary>
  151. /// Should the animation always start from 0 normalized time?
  152. /// </summary>
  153. [Tooltip("Should the animation always start from 0 normalized time?")]
  154. public bool resetNormalizedTime;
  155. private const string empty = "";
  156. // Activate the animation
  157. public void Activate(bool pickUp) {
  158. if (animator != null) {
  159. // disable root motion because it may become a child of another Animator. Workaround for a Unity bug with an error message: "Transform.rotation on 'gameobject name' is no longer valid..."
  160. if (pickUp) animator.applyRootMotion = false;
  161. Activate(animator);
  162. }
  163. if (animation != null) Activate(animation);
  164. }
  165. // Activate a Mecanim animation
  166. private void Activate(Animator animator) {
  167. if (animationState == empty) return;
  168. if (resetNormalizedTime) animator.CrossFade(animationState, crossfadeTime, layer, 0f);
  169. else animator.CrossFade(animationState, crossfadeTime, layer);
  170. }
  171. // Activate a Legacy animation
  172. private void Activate(Animation animation) {
  173. if (animationState == empty) return;
  174. if (resetNormalizedTime) animation[animationState].normalizedTime = 0f;
  175. animation[animationState].layer = layer;
  176. animation.CrossFade(animationState, crossfadeTime);
  177. }
  178. }
  179. /// <summary>
  180. /// A Weight curve for various FBBIK channels.
  181. /// </summary>
  182. [System.Serializable]
  183. public class WeightCurve {
  184. /// <summary>
  185. /// The type of the weight curve
  186. /// </summary>
  187. [System.Serializable]
  188. public enum Type {
  189. PositionWeight, // IKEffector.positionWeight
  190. RotationWeight, // IKEffector.rotationWeight
  191. PositionOffsetX, // X offset from the interpolation direction relative to the character rotation
  192. PositionOffsetY, // Y offse from the interpolation direction relative to the character rotation
  193. PositionOffsetZ, // Z offset from the interpolation direction relative to the character rotation
  194. Pull, // FBIKChain.pull
  195. Reach, // FBIKChain.reach
  196. RotateBoneWeight, // Rotating the bone after FBBIK is finished
  197. Push, // FBIKChain.push
  198. PushParent, // FBIKChain.pushParent
  199. PoserWeight, // Weight of hand/generic Poser
  200. BendGoalWeight // Weight of the bend goal
  201. }
  202. /// <summary>
  203. /// The type of the curve (InteractionObject.WeightCurve.Type).
  204. /// </summary>
  205. [Tooltip("The type of the curve (InteractionObject.WeightCurve.Type).")]
  206. public Type type;
  207. /// <summary>
  208. /// The weight curve.
  209. /// </summary>
  210. [Tooltip("The weight curve.")]
  211. public AnimationCurve curve;
  212. // Evaluate the curve at the specified time
  213. public float GetValue(float timer) {
  214. return curve.Evaluate(timer);
  215. }
  216. }
  217. /// <summary>
  218. /// Multiplies a weight curve and uses the result for another FBBIK channel. (to reduce the amount of work with AnimationCurves)
  219. /// </summary>
  220. [System.Serializable]
  221. public class Multiplier {
  222. /// <summary>
  223. /// The curve type to multiply.
  224. /// </summary>
  225. [Tooltip("The curve type to multiply.")]
  226. public WeightCurve.Type curve;
  227. /// <summary>
  228. /// The multiplier of the curve's value.
  229. /// </summary>
  230. [Tooltip("The multiplier of the curve's value.")]
  231. public float multiplier = 1f;
  232. /// <summary>
  233. /// The resulting value will be applied to this channel.
  234. /// </summary>
  235. [Tooltip("The resulting value will be applied to this channel.")]
  236. public WeightCurve.Type result;
  237. // Get the multiplied value of the curve at the specified time
  238. public float GetValue(WeightCurve weightCurve, float timer) {
  239. return weightCurve.GetValue(timer) * multiplier;
  240. }
  241. }
  242. /// <summary>
  243. /// If the Interaction System has a 'Look At' LookAtIK component assigned, will use it to make the character look at the specified Transform. If unassigned, will look at this GameObject.
  244. /// </summary>
  245. [Tooltip("If the Interaction System has a 'Look At' LookAtIK component assigned, will use it to make the character look at the specified Transform. If unassigned, will look at this GameObject.")]
  246. public Transform otherLookAtTarget;
  247. /// <summary>
  248. /// The root Transform of the InteractionTargets. If null, will use this GameObject. GetComponentsInChildren<InteractionTarget>() will be used at initiation to find all InteractionTargets associated with this InteractionObject.
  249. /// </summary>
  250. [Tooltip("The root Transform of the InteractionTargets. If null, will use this GameObject. GetComponentsInChildren<InteractionTarget>() will be used at initiation to find all InteractionTargets associated with this InteractionObject.")]
  251. public Transform otherTargetsRoot;
  252. /// <summary>
  253. /// If assigned, all PositionOffset channels will be applied in the rotation space of this Transform. If not, they will be in the rotation space of the character.
  254. /// </summary>
  255. [Tooltip("If assigned, all PositionOffset channels will be applied in the rotation space of this Transform. If not, they will be in the rotation space of the character.")]
  256. public Transform positionOffsetSpace;
  257. /// <summary>
  258. /// The weight curves for the interaction.
  259. /// </summary>
  260. public WeightCurve[] weightCurves;
  261. /// <summary>
  262. /// The weight curve multipliers for the interaction.
  263. /// </summary>
  264. public Multiplier[] multipliers;
  265. /// <summary>
  266. /// The interaction events.
  267. /// </summary>
  268. public InteractionEvent[] events;
  269. /// <summary>
  270. /// Gets the length of the interaction (the longest curve).
  271. /// </summary>
  272. public float length { get; private set; }
  273. /// <summary>
  274. /// The last InteractionSystem that started an interaction with this InteractionObject.
  275. /// </summary>
  276. /// <value>The last used interaction system.</value>
  277. public InteractionSystem lastUsedInteractionSystem { get; private set; }
  278. /// <summary>
  279. /// Call if you have changed the curves in play mode or added/removed InteractionTargets.
  280. /// </summary>
  281. public void Initiate() {
  282. // Push length to the last weight curve key
  283. for (int i = 0; i < weightCurves.Length; i++) {
  284. if (weightCurves[i].curve.length > 0) {
  285. float l = weightCurves[i].curve.keys[weightCurves[i].curve.length - 1].time;
  286. length = Mathf.Clamp(length, l, length);
  287. }
  288. }
  289. // Push length to the last event time
  290. for (int i = 0; i < events.Length; i++) {
  291. length = Mathf.Clamp(length, events[i].time, length);
  292. }
  293. targets = targetsRoot.GetComponentsInChildren<InteractionTarget>();
  294. }
  295. /// <summary>
  296. /// Gets the look at target (returns otherLookAtTarget if not null).
  297. /// </summary>
  298. public Transform lookAtTarget {
  299. get {
  300. if (otherLookAtTarget != null) return otherLookAtTarget;
  301. return transform;
  302. }
  303. }
  304. /// <summary>
  305. /// Gets the InteractionTarget of the specified effector type and InteractionSystem tag.
  306. /// </summary>
  307. public InteractionTarget GetTarget(FullBodyBipedEffector effectorType, InteractionSystem interactionSystem) {
  308. if (interactionSystem.CompareTag(string.Empty) || interactionSystem.CompareTag("")) {
  309. foreach (InteractionTarget target in targets) {
  310. if (target.effectorType == effectorType) return target;
  311. }
  312. return null;
  313. }
  314. foreach (InteractionTarget target in targets) {
  315. if (target.effectorType == effectorType && target.CompareTag(interactionSystem.tag)) return target;
  316. }
  317. return null;
  318. }
  319. #endregion Main Interface
  320. // Returns true if the specified WeightCurve.Type is used by this InteractionObject
  321. public bool CurveUsed(WeightCurve.Type type) {
  322. foreach (WeightCurve curve in weightCurves) {
  323. if (curve.type == type) return true;
  324. }
  325. foreach (Multiplier multiplier in multipliers) {
  326. if (multiplier.result == type) return true;
  327. }
  328. return false;
  329. }
  330. // Returns all the InteractionTargets of this object
  331. public InteractionTarget[] GetTargets() {
  332. return targets;
  333. }
  334. // Returns the InteractionTarget of effector type and tag
  335. public Transform GetTarget(FullBodyBipedEffector effectorType, string tag) {
  336. if (tag == string.Empty || tag == "") return GetTarget(effectorType);
  337. for (int i = 0; i < targets.Length; i++) {
  338. if (targets[i].effectorType == effectorType && targets[i].CompareTag(tag)) return targets[i].transform;
  339. }
  340. return transform;
  341. }
  342. // Called when interaction is started with this InteractionObject
  343. public void OnStartInteraction(InteractionSystem interactionSystem) {
  344. this.lastUsedInteractionSystem = interactionSystem;
  345. }
  346. // Applies the weight curves and multipliers to the FBBIK solver
  347. public void Apply(IKSolverFullBodyBiped solver, FullBodyBipedEffector effector, InteractionTarget target, float timer, float weight) {
  348. for (int i = 0; i < weightCurves.Length; i++) {
  349. float mlp = target == null? 1f: target.GetValue(weightCurves[i].type);
  350. Apply(solver, effector, weightCurves[i].type, weightCurves[i].GetValue(timer), weight * mlp);
  351. }
  352. for (int i = 0; i < multipliers.Length; i++) {
  353. if (multipliers[i].curve == multipliers[i].result) {
  354. if (!Warning.logged) Warning.Log("InteractionObject Multiplier 'Curve' " + multipliers[i].curve.ToString() + "and 'Result' are the same.", transform);
  355. }
  356. int curveIndex = GetWeightCurveIndex(multipliers[i].curve);
  357. if (curveIndex != -1) {
  358. float mlp = target == null? 1f: target.GetValue(multipliers[i].result);
  359. Apply(solver, effector, multipliers[i].result, multipliers[i].GetValue(weightCurves[curveIndex], timer), weight * mlp);
  360. } else {
  361. if (!Warning.logged) Warning.Log("InteractionObject Multiplier curve " + multipliers[i].curve.ToString() + "does not exist.", transform);
  362. }
  363. }
  364. }
  365. // Gets the value of a weight curve/multiplier
  366. public float GetValue(WeightCurve.Type weightCurveType, InteractionTarget target, float timer) {
  367. int index = GetWeightCurveIndex(weightCurveType);
  368. if (index != -1) {
  369. float mlp = target == null? 1f: target.GetValue(weightCurveType);
  370. return weightCurves[index].GetValue(timer) * mlp;
  371. }
  372. for (int i = 0; i < multipliers.Length; i++) {
  373. if (multipliers[i].result == weightCurveType) {
  374. int wIndex = GetWeightCurveIndex(multipliers[i].curve);
  375. if (wIndex != -1) {
  376. float mlp = target == null? 1f: target.GetValue(multipliers[i].result);
  377. return multipliers[i].GetValue(weightCurves[wIndex], timer) * mlp;
  378. }
  379. }
  380. }
  381. return 0f;
  382. }
  383. // Get the root Transform of the targets
  384. public Transform targetsRoot {
  385. get {
  386. if (otherTargetsRoot != null) return otherTargetsRoot;
  387. return transform;
  388. }
  389. }
  390. private InteractionTarget[] targets = new InteractionTarget[0];
  391. // Initiate this Interaction Object
  392. void Start() {
  393. Initiate();
  394. }
  395. // Apply the curve to the specified solver, effector, with the value and weight.
  396. private void Apply(IKSolverFullBodyBiped solver, FullBodyBipedEffector effector, WeightCurve.Type type, float value, float weight) {
  397. switch(type) {
  398. case WeightCurve.Type.PositionWeight:
  399. solver.GetEffector(effector).positionWeight = Mathf.Lerp(solver.GetEffector(effector).positionWeight, value, weight);
  400. return;
  401. case WeightCurve.Type.RotationWeight:
  402. solver.GetEffector(effector).rotationWeight = Mathf.Lerp(solver.GetEffector(effector).rotationWeight, value, weight);
  403. return;
  404. case WeightCurve.Type.PositionOffsetX:
  405. solver.GetEffector(effector).position += (positionOffsetSpace != null? positionOffsetSpace.rotation: solver.GetRoot().rotation) * Vector3.right * value * weight;
  406. return;
  407. case WeightCurve.Type.PositionOffsetY:
  408. solver.GetEffector(effector).position += (positionOffsetSpace != null? positionOffsetSpace.rotation: solver.GetRoot().rotation) * Vector3.up * value * weight;
  409. return;
  410. case WeightCurve.Type.PositionOffsetZ:
  411. solver.GetEffector(effector).position += (positionOffsetSpace != null? positionOffsetSpace.rotation: solver.GetRoot().rotation) * Vector3.forward * value * weight;
  412. return;
  413. case WeightCurve.Type.Pull:
  414. solver.GetChain(effector).pull = Mathf.Lerp(solver.GetChain(effector).pull, value, weight);
  415. return;
  416. case WeightCurve.Type.Reach:
  417. solver.GetChain(effector).reach = Mathf.Lerp(solver.GetChain(effector).reach, value, weight);
  418. return;
  419. case WeightCurve.Type.Push:
  420. solver.GetChain(effector).push = Mathf.Lerp(solver.GetChain(effector).push, value, weight);
  421. return;
  422. case WeightCurve.Type.PushParent:
  423. solver.GetChain(effector).pushParent = Mathf.Lerp(solver.GetChain(effector).pushParent, value, weight);
  424. return;
  425. case WeightCurve.Type.BendGoalWeight:
  426. solver.GetChain(effector).bendConstraint.weight = Mathf.Lerp(solver.GetChain(effector).bendConstraint.weight, value, weight);
  427. return;
  428. }
  429. }
  430. // Gets the interaction target Transform
  431. private Transform GetTarget(FullBodyBipedEffector effectorType) {
  432. for (int i = 0; i < targets.Length; i++) {
  433. if (targets[i].effectorType == effectorType) return targets[i].transform;
  434. }
  435. return transform;
  436. }
  437. // Get the index of a weight curve of type
  438. private int GetWeightCurveIndex(WeightCurve.Type weightCurveType) {
  439. for (int i = 0; i < weightCurves.Length; i++) {
  440. if (weightCurves[i].type == weightCurveType) return i;
  441. }
  442. return -1;
  443. }
  444. // Get the index of a multiplayer of type
  445. private int GetMultiplierIndex(WeightCurve.Type weightCurveType) {
  446. for (int i = 0; i < multipliers.Length; i++) {
  447. if (multipliers[i].result == weightCurveType) return i;
  448. }
  449. return -1;
  450. }
  451. }
  452. }