InGameHintsExample.cs 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. // This example demonstrates how to display text in the UI that involves action bindings.
  2. // When the player switches control schemes or customizes controls (the latter is not set up
  3. // in this example but if supported, would work with the existing code as is), text that
  4. // is shown to the user may be affected.
  5. //
  6. // In the example, the player is able to move around the world and look at objects (simple
  7. // cubes). When an object is in sight, the player can pick the object with a button. While
  8. // having an object picked up, the player can then either throw the object or drop it back
  9. // on the ground.
  10. //
  11. // Depending on the current context, we display hints in the UI that reflect the currently
  12. // active bindings.
  13. using UnityEngine.UI;
  14. namespace UnityEngine.InputSystem.Samples.InGameHints
  15. {
  16. public class InGameHintsExample : MonoBehaviour
  17. {
  18. public Text helpText;
  19. public float moveSpeed;
  20. public float rotateSpeed;
  21. public float throwForce;
  22. public float pickupDistance;
  23. public float holdDistance;
  24. private Vector2 m_Rotation;
  25. private enum State
  26. {
  27. Wandering,
  28. ObjectInSights,
  29. ObjectPickedUp
  30. }
  31. private PlayerInput m_PlayerInput;
  32. private State m_CurrentState;
  33. private Transform m_CurrentObject;
  34. private MaterialPropertyBlock m_PropertyBlock;
  35. // Cached help texts so that we don't generate garbage all the time. Could even cache them by control
  36. // scheme to not create garbage during control scheme switching but we consider control scheme switches
  37. // rare so not worth the extra cost in complexity and memory.
  38. private string m_LookAtObjectHelpText;
  39. private string m_ThrowObjectHelpText;
  40. private const string kDefaultHelpTextFormat = "Move close to one of the cubes and look at it to pick up";
  41. private const string kLookAtObjectHelpTextFormat = "Press {pickup} to pick object up";
  42. private const string kThrowObjectHelpTextFormat = "Press {throw} to throw object; press {drop} to drop object";
  43. public void Awake()
  44. {
  45. m_PlayerInput = GetComponent<PlayerInput>();
  46. }
  47. public void OnEnable()
  48. {
  49. ChangeState(State.Wandering);
  50. }
  51. // This is invoked by PlayerInput when the controls on the player change. If the player switches control
  52. // schemes or keyboard layouts, we end up here and re-generate our hints.
  53. public void OnControlsChanged()
  54. {
  55. UpdateUIHints(regenerate: true); // Force re-generation of our cached text strings to pick up new bindings.
  56. }
  57. private int m_UpdateCount;
  58. public void Update()
  59. {
  60. var move = m_PlayerInput.actions["move"].ReadValue<Vector2>();
  61. var look = m_PlayerInput.actions["look"].ReadValue<Vector2>();
  62. Move(move);
  63. Look(look);
  64. switch (m_CurrentState)
  65. {
  66. case State.Wandering:
  67. case State.ObjectInSights:
  68. // While looking around for an object to pick up, we constantly raycast into the world.
  69. if (Physics.Raycast(transform.position, transform.forward, out var hitInfo,
  70. pickupDistance) && !hitInfo.collider.gameObject.isStatic)
  71. {
  72. if (m_CurrentState != State.ObjectInSights)
  73. ChangeState(State.ObjectInSights);
  74. m_CurrentObject = hitInfo.transform;
  75. // Set a custom color override on the object by installing our property block.
  76. if (m_PropertyBlock == null)
  77. {
  78. m_PropertyBlock = new MaterialPropertyBlock();
  79. m_PropertyBlock.SetColor("_Color", new Color(0.75f, 0, 0));
  80. }
  81. m_CurrentObject.GetComponent<MeshRenderer>().SetPropertyBlock(m_PropertyBlock);
  82. }
  83. else if (m_CurrentState != State.Wandering)
  84. {
  85. // No longer have object in sight.
  86. ChangeState(State.Wandering);
  87. if (m_CurrentObject != null)
  88. {
  89. // Clear property block on renderer to get rid of our custom color override.
  90. m_CurrentObject.GetComponent<Renderer>().SetPropertyBlock(null);
  91. m_CurrentObject = null;
  92. }
  93. }
  94. if (m_PlayerInput.actions["pickup"].triggered && m_CurrentObject != null)
  95. {
  96. PickUp();
  97. ChangeState(State.ObjectPickedUp);
  98. }
  99. break;
  100. case State.ObjectPickedUp:
  101. // If the player hits the throw button, throw the currently carried object.
  102. // For this example, let's call this good enough. In a real game, we'd want to avoid the raycast
  103. if (m_PlayerInput.actions["throw"].triggered)
  104. {
  105. Throw();
  106. ChangeState(State.Wandering);
  107. }
  108. else if (m_PlayerInput.actions["drop"].triggered)
  109. {
  110. Throw(drop: true);
  111. ChangeState(State.Wandering);
  112. }
  113. break;
  114. }
  115. }
  116. private void ChangeState(State newState)
  117. {
  118. switch (newState)
  119. {
  120. case State.Wandering:
  121. break;
  122. case State.ObjectInSights:
  123. break;
  124. case State.ObjectPickedUp:
  125. break;
  126. }
  127. m_CurrentState = newState;
  128. UpdateUIHints();
  129. }
  130. private void UpdateUIHints(bool regenerate = false)
  131. {
  132. if (regenerate)
  133. {
  134. m_ThrowObjectHelpText = default;
  135. m_LookAtObjectHelpText = default;
  136. }
  137. switch (m_CurrentState)
  138. {
  139. case State.ObjectInSights:
  140. if (m_LookAtObjectHelpText == null)
  141. m_LookAtObjectHelpText = kLookAtObjectHelpTextFormat.Replace("{pickup}",
  142. m_PlayerInput.actions["pickup"].GetBindingDisplayString());
  143. helpText.text = m_LookAtObjectHelpText;
  144. break;
  145. case State.ObjectPickedUp:
  146. if (m_ThrowObjectHelpText == null)
  147. m_ThrowObjectHelpText = kThrowObjectHelpTextFormat
  148. .Replace("{throw}", m_PlayerInput.actions["throw"].GetBindingDisplayString())
  149. .Replace("{drop}", m_PlayerInput.actions["drop"].GetBindingDisplayString());
  150. helpText.text = m_ThrowObjectHelpText;
  151. break;
  152. default:
  153. helpText.text = kDefaultHelpTextFormat;
  154. break;
  155. }
  156. }
  157. // Throw or drop currently picked up object.
  158. private void Throw(bool drop = false)
  159. {
  160. // Unmount it.
  161. m_CurrentObject.parent = null;
  162. // Turn physics back on.
  163. var rigidBody = m_CurrentObject.GetComponent<Rigidbody>();
  164. rigidBody.isKinematic = false;
  165. // Apply force.
  166. if (!drop)
  167. rigidBody.AddForce(transform.forward * throwForce, ForceMode.Impulse);
  168. m_CurrentObject = null;
  169. }
  170. private void PickUp()
  171. {
  172. // Mount to our transform.
  173. m_CurrentObject.position = default;
  174. m_CurrentObject.SetParent(transform, worldPositionStays: false);
  175. m_CurrentObject.localPosition += new Vector3(0, 0, holdDistance);
  176. // Remove color override.
  177. m_CurrentObject.GetComponent<Renderer>().SetPropertyBlock(null);
  178. // We don't want the object to be governed by physics while we hold it so turn it into a
  179. // kinematics body.
  180. m_CurrentObject.GetComponent<Rigidbody>().isKinematic = true;
  181. }
  182. private void Move(Vector2 direction)
  183. {
  184. if (direction.sqrMagnitude < 0.01)
  185. return;
  186. var scaledMoveSpeed = moveSpeed * Time.deltaTime;
  187. // For simplicity's sake, we just keep movement in a single plane here. Rotate
  188. // direction according to world Y rotation of player.
  189. var move = Quaternion.Euler(0, transform.eulerAngles.y, 0) * new Vector3(direction.x, 0, direction.y);
  190. transform.position += move * scaledMoveSpeed;
  191. }
  192. private void Look(Vector2 rotate)
  193. {
  194. if (rotate.sqrMagnitude < 0.01)
  195. return;
  196. var scaledRotateSpeed = rotateSpeed * Time.deltaTime;
  197. m_Rotation.y += rotate.x * scaledRotateSpeed;
  198. m_Rotation.x = Mathf.Clamp(m_Rotation.x - rotate.y * scaledRotateSpeed, -89, 89);
  199. transform.localEulerAngles = m_Rotation;
  200. }
  201. }
  202. }