InteractionTrigger.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. using UnityEngine;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using RootMotion.FinalIK;
  5. namespace RootMotion.FinalIK {
  6. /// <summary>
  7. /// When a character with an InteractionSystem component enters the trigger collider of this game object, this component will register itself to the InteractionSystem.
  8. /// The InteractionSystem can then use it to find the most appropriate InteractionObject and effectors to interact with.
  9. /// Use InteractionSystem.GetClosestTriggerIndex() and InteractionSystem.TriggerInteration() to trigger the interactions that the character is in contact with.
  10. /// </summary>
  11. [HelpURL("https://www.youtube.com/watch?v=-TDZpNjt2mk&index=15&list=PLVxSIA1OaTOu8Nos3CalXbJ2DrKnntMv6")]
  12. [AddComponentMenu("Scripts/RootMotion.FinalIK/Interaction System/Interaction Trigger")]
  13. public class InteractionTrigger: MonoBehaviour {
  14. // Open the User Manual URL
  15. [ContextMenu("User Manual")]
  16. void OpenUserManual()
  17. {
  18. Application.OpenURL("http://www.root-motion.com/finalikdox/html/page10.html");
  19. }
  20. // Open the Script Reference URL
  21. [ContextMenu("Scrpt Reference")]
  22. void OpenScriptReference()
  23. {
  24. Application.OpenURL("http://www.root-motion.com/finalikdox/html/class_root_motion_1_1_final_i_k_1_1_interaction_trigger.html");
  25. }
  26. // Open a video tutorial video
  27. [ContextMenu("TUTORIAL VIDEO")]
  28. void OpenTutorial4() {
  29. Application.OpenURL("https://www.youtube.com/watch?v=-TDZpNjt2mk&index=15&list=PLVxSIA1OaTOu8Nos3CalXbJ2DrKnntMv6");
  30. }
  31. // Link to the Final IK Google Group
  32. [ContextMenu("Support Group")]
  33. void SupportGroup() {
  34. Application.OpenURL("https://groups.google.com/forum/#!forum/final-ik");
  35. }
  36. // Link to the Final IK Asset Store thread in the Unity Community
  37. [ContextMenu("Asset Store Thread")]
  38. void ASThread() {
  39. Application.OpenURL("http://forum.unity3d.com/threads/final-ik-full-body-ik-aim-look-at-fabrik-ccd-ik-1-0-released.222685/");
  40. }
  41. /// <summary>
  42. /// Defines the valid range of the character's position and rotation relative to this trigger.
  43. /// </summary>
  44. [System.Serializable]
  45. public class CharacterPosition {
  46. /// <summary>
  47. /// If false, will not care where the character stands, as long as it is in contact with the trigger collider.
  48. /// </summary>
  49. [Tooltip("If false, will not care where the character stands, as long as it is in contact with the trigger collider.")]
  50. public bool use;
  51. /// <summary>
  52. /// The offset of the character's position relative to the trigger in XZ plane. Y position of the character is unlimited as long as it is contact with the collider.
  53. /// </summary>
  54. [Tooltip("The offset of the character's position relative to the trigger in XZ plane. Y position of the character is unlimited as long as it is contact with the collider.")]
  55. public Vector2 offset;
  56. /// <summary>
  57. /// Angle offset from the default forward direction..
  58. /// </summary>
  59. [Tooltip("Angle offset from the default forward direction.")]
  60. [Range(-180f, 180f)] public float angleOffset;
  61. /// <summary>
  62. /// Max angular offset of the character's forward from the direction of this trigger.
  63. /// </summary>
  64. [Tooltip("Max angular offset of the character's forward from the direction of this trigger.")]
  65. [Range(0f, 180f)] public float maxAngle = 45f;
  66. /// <summary>
  67. /// Max offset of the character's position from this range's center.
  68. /// </summary>
  69. [Tooltip("Max offset of the character's position from this range's center.")]
  70. public float radius = 0.5f;
  71. /// <summary>
  72. /// If true, will rotate the trigger around it's Y axis relative to the position of the character, so the object can be interacted with from all sides.
  73. /// </summary>
  74. [Tooltip("If true, will rotate the trigger around it's Y axis relative to the position of the character, so the object can be interacted with from all sides.")]
  75. public bool orbit;
  76. /// <summary>
  77. /// Fixes the Y axis of the trigger to Vector3.up. This makes the trigger symmetrical relative to the object.
  78. /// For example a gun will be able to be picked up from the same direction relative to the barrel no matter which side the gun is resting on.
  79. /// </summary>
  80. [Tooltip("Fixes the Y axis of the trigger to Vector3.up. This makes the trigger symmetrical relative to the object. For example a gun will be able to be picked up from the same direction relative to the barrel no matter which side the gun is resting on.")]
  81. public bool fixYAxis;
  82. // Returns the 2D offset as 3D vector.
  83. public Vector3 offset3D { get { return new Vector3(offset.x, 0f, offset.y); }}
  84. // Returns the default direction of this character position in world space.
  85. public Vector3 direction3D {
  86. get {
  87. return Quaternion.AngleAxis(angleOffset, Vector3.up) * Vector3.forward;
  88. }
  89. }
  90. // Is the character in range with this character position?
  91. public bool IsInRange(Transform character, Transform trigger, out float error) {
  92. // Do not use this character position, trigger is still valid
  93. error = 0f;
  94. if (!use) return true;
  95. // Invalid character position conditions
  96. error = 180f;
  97. if (radius <= 0f) return false;
  98. if (maxAngle <= 0f) return false;
  99. Vector3 forward = trigger.forward;
  100. if (fixYAxis) forward.y = 0f;
  101. if (forward == Vector3.zero) return false; // Singularity
  102. Vector3 up = (fixYAxis? Vector3.up: trigger.up);
  103. Quaternion triggerRotation = Quaternion.LookRotation(forward, up);
  104. Vector3 position = trigger.position + triggerRotation * offset3D;
  105. Vector3 origin = orbit? trigger.position: position;
  106. Vector3 toCharacter = character.position - origin;
  107. Vector3.OrthoNormalize(ref up, ref toCharacter);
  108. toCharacter *= Vector3.Project(character.position - origin, toCharacter).magnitude;
  109. if (orbit) {
  110. float mag = offset.magnitude;
  111. float dist = toCharacter.magnitude;
  112. if (dist < mag - radius || dist > mag + radius) return false;
  113. } else {
  114. if (toCharacter.magnitude > radius) return false;
  115. }
  116. Vector3 d = triggerRotation * direction3D;
  117. Vector3.OrthoNormalize(ref up, ref d);
  118. if (orbit) {
  119. Vector3 toPosition = position - trigger.position;
  120. if (toPosition == Vector3.zero) toPosition = Vector3.forward;
  121. Quaternion r = Quaternion.LookRotation(toPosition, up);
  122. toCharacter = Quaternion.Inverse(r) * toCharacter;
  123. float a = Mathf.Atan2(toCharacter.x, toCharacter.z) * Mathf.Rad2Deg;
  124. d = Quaternion.AngleAxis(a, up) * d;
  125. }
  126. float angle = Vector3.Angle(d, character.forward);
  127. if (angle > maxAngle) return false;
  128. error = (angle / maxAngle) * 180f;
  129. return true;
  130. }
  131. }
  132. /// <summary>
  133. /// Defines the valid range of the camera's position relative to this trigger.
  134. /// </summary>
  135. [System.Serializable]
  136. public class CameraPosition {
  137. /// <summary>
  138. /// What the camera should be looking at to trigger the interaction?
  139. /// </summary>
  140. [Tooltip("What the camera should be looking at to trigger the interaction? If null, this camera position will not be used.")]
  141. public Collider lookAtTarget;
  142. /// <summary>
  143. /// The direction from the lookAtTarget towards the camera (in lookAtTarget's space).
  144. /// </summary>
  145. [Tooltip("The direction from the lookAtTarget towards the camera (in lookAtTarget's space).")]
  146. public Vector3 direction = -Vector3.forward;
  147. /// <summary>
  148. /// Max distance from the lookAtTarget to the camera.
  149. /// </summary>
  150. [Tooltip("Max distance from the lookAtTarget to the camera.")]
  151. public float maxDistance = 0.5f;
  152. /// <summary>
  153. /// Max angle between the direction and the direction towards the camera.
  154. /// </summary>
  155. [Tooltip("Max angle between the direction and the direction towards the camera.")]
  156. [Range(0f, 180f)] public float maxAngle = 45f;
  157. /// <summary>
  158. /// Fixes the Y axis of the trigger to Vector3.up. This makes the trigger symmetrical relative to the object.
  159. /// </summary>
  160. [Tooltip("Fixes the Y axis of the trigger to Vector3.up. This makes the trigger symmetrical relative to the object.")]
  161. public bool fixYAxis;
  162. // Returns the rotation space of this CameraPosition.
  163. public Quaternion GetRotation() {
  164. Vector3 forward = lookAtTarget.transform.forward;
  165. if (fixYAxis) forward.y = 0f;
  166. if (forward == Vector3.zero) return Quaternion.identity; // Singularity
  167. Vector3 up = (fixYAxis? Vector3.up: lookAtTarget.transform.up);
  168. return Quaternion.LookRotation(forward, up);
  169. }
  170. // Is the camera raycast hit in range of this CameraPosition?
  171. public bool IsInRange(Transform raycastFrom, RaycastHit hit, Transform trigger, out float error) {
  172. // Not using the CameraPosition
  173. error = 0f;
  174. if (lookAtTarget == null) return true;
  175. // Not in range conditions
  176. error = 180f;
  177. if (raycastFrom == null) return false;
  178. if (hit.collider != lookAtTarget) return false;
  179. if (hit.distance > maxDistance) return false;
  180. if (direction == Vector3.zero) return false;
  181. if (maxDistance <= 0f) return false;
  182. if (maxAngle <= 0f) return false;
  183. Vector3 d = GetRotation() * direction;
  184. float a = Vector3.Angle(raycastFrom.position - hit.point, d);
  185. if (a > maxAngle) return false;
  186. error = (a / maxAngle) * 180f;
  187. return true;
  188. }
  189. }
  190. /// <summary>
  191. /// Defines the valid range of the character's and/or it's camera's position for one or multiple interactions.
  192. /// </summary>
  193. [System.Serializable]
  194. public class Range {
  195. [HideInInspector] public string name; // Name is composed automatically by InteractionTriggerInspector.cs. Editor only.
  196. [HideInInspector] public bool show = true; // Show this range in the Scene view? Editor only.
  197. /// <summary>
  198. /// Defines the interaction object and effectors that will be triggered when calling InteractionSystem.TriggerInteraction().
  199. /// </summary>
  200. [System.Serializable]
  201. public class Interaction {
  202. /// <summary>
  203. /// The InteractionObject to interact with.
  204. /// </summary>
  205. [Tooltip("The InteractionObject to interact with.")]
  206. public InteractionObject interactionObject;
  207. /// <summary>
  208. /// The effectors to interact with.
  209. /// </summary>
  210. [Tooltip("The effectors to interact with.")]
  211. public FullBodyBipedEffector[] effectors;
  212. }
  213. /// <summary>
  214. /// The range for the character's position and rotation.
  215. /// </summary>
  216. [Tooltip("The range for the character's position and rotation.")]
  217. public CharacterPosition characterPosition;
  218. /// <summary>
  219. /// The range for the character camera's position and rotation.
  220. /// </summary>
  221. [Tooltip("The range for the character camera's position and rotation.")]
  222. public CameraPosition cameraPosition;
  223. /// <summary>
  224. /// Definitions of the interactions associated with this range.
  225. /// </summary>
  226. [Tooltip("Definitions of the interactions associated with this range.")]
  227. public Interaction[] interactions;
  228. public bool IsInRange(Transform character, Transform raycastFrom, RaycastHit raycastHit, Transform trigger, out float maxError) {
  229. maxError = 0f;
  230. float characterError = 0f;
  231. float cameraError = 0f;
  232. if (!characterPosition.IsInRange(character, trigger, out characterError)) return false;
  233. if (!cameraPosition.IsInRange(raycastFrom, raycastHit, trigger, out cameraError)) return false;
  234. maxError = Mathf.Max(characterError, cameraError);
  235. return true;
  236. }
  237. }
  238. /// <summary>
  239. /// The valid ranges of the character's and/or it's camera's position for triggering interaction when the character is in contact with the collider of this trigger.
  240. /// </summary>
  241. [Tooltip("The valid ranges of the character's and/or it's camera's position for triggering interaction when the character is in contact with the collider of this trigger.")]
  242. public Range[] ranges = new Range[0];
  243. // Returns the index of the ranges that is best fit for the current position/rotation of the character and it's camera.
  244. public int GetBestRangeIndex(Transform character, Transform raycastFrom, RaycastHit raycastHit) {
  245. if (GetComponent<Collider>() == null) {
  246. Warning.Log("Using the InteractionTrigger requires a Collider component.", transform);
  247. return -1;
  248. }
  249. int bestRangeIndex = -1;
  250. float smallestError = 180f;
  251. float error = 0f;
  252. for (int i = 0; i < ranges.Length; i++) {
  253. if (ranges[i].IsInRange(character, raycastFrom, raycastHit, transform, out error)) {
  254. if (error <= smallestError) {
  255. smallestError = error;
  256. bestRangeIndex = i;
  257. }
  258. }
  259. }
  260. return bestRangeIndex;
  261. }
  262. }
  263. }