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