-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathBNGPlayerController.cs
325 lines (251 loc) · 13.3 KB
/
BNGPlayerController.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
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace BNG {
public enum LocomotionType {
Teleport,
SmoothLocomotion,
None
}
/// <summary>
/// The BNGPlayerController handles basic player movement
/// </summary>
public class BNGPlayerController : MonoBehaviour {
[Header("Camera Options : ")]
[Tooltip("If true the CharacterController will move along with the HMD, as long as there are no obstacle's in the way")]
public bool MoveCharacterWithCamera = true;
[Tooltip("If true the CharacterController will rotate it's Y angle to match the HMD's Y angle")]
public bool RotateCharacterWithCamera = true;
[Header("Transform Setup ")]
[Tooltip("The TrackingSpace represents your tracking space origin.")]
public Transform TrackingSpace;
[Tooltip("The CameraRig is a Transform that is used to offset the main camera. The main camera should be parented to this.")]
public Transform CameraRig;
[Tooltip("The CenterEyeAnchor is typically the Transform that contains your Main Camera")]
public Transform CenterEyeAnchor;
[Header("Ground checks : ")]
[Tooltip("Raycast against these layers to check if player is grounded")]
public LayerMask GroundedLayers;
public int LayerInt;
/// <summary>
/// 0 means we are grounded
/// </summary>
[Tooltip("How far off the ground the player currently is. 0 = Grounded, 1 = 1 Meter in the air.")]
public float DistanceFromGround = 0;
[Header("Player Capsule Settings : ")]
/// <summary>
/// Minimum Height our Player's capsule collider can be (in meters)
/// </summary>
[Tooltip("Minimum Height our Player's capsule collider can be (in meters)")]
public float MinimumCapsuleHeight = 0.4f;
/// <summary>
/// Maximum Height our Player's capsule collider can be (in meters)
/// </summary>
[Tooltip("Maximum Height our Player's capsule collider can be (in meters)")]
public float MaximumCapsuleHeight = 3f;
[HideInInspector]
public float LastTeleportTime;
[Header("Player Y Offset : ")]
/// <summary>
/// Offset the height of the CharacterController by this amount
/// </summary>
[Tooltip("Offset the height of the CharacterController by this amount")]
public float CharacterControllerYOffset = -0.025f;
/// <summary>
/// Height of our camera in local coords
/// </summary>
[HideInInspector]
public float CameraHeight;
[Header("Misc : ")]
[Tooltip("If true the Camera will be offset by ElevateCameraHeight if no HMD is active or connected. This prevents the camera from falling to the floor and can allow you to use keyboard controls.")]
public bool ElevateCameraIfNoHMDPresent = true;
[Tooltip("How high (in meters) to elevate the player camera if no HMD is present and ElevateCameraIfNoHMDPresent is true. 1.65 = about 5.4' tall. ")]
public float ElevateCameraHeight = 1.65f;
/// <summary>
/// If player goes below this elevation they will be reset to their initial starting position.
/// If the player goes too far away from the center they may start to jitter due to floating point precisions.
/// Can also use this to detect if player somehow fell through a floor. Or if the "floor is lava".
/// </summary>
[Tooltip("Minimum Y position our player is allowed to go. Useful for floating point precision and making sure player didn't fall through the map.")]
public float MinElevation = -6000f;
/// <summary>
/// If player goes above this elevation they will be reset to their initial starting position.
/// If the player goes too far away from the center they may start to jitter due to floating point precisions.
/// </summary>
public float MaxElevation = 6000f;
[HideInInspector]
public float LastPlayerMoveTime;
// The controller to manipulate
CharacterController characterController;
// Use smooth movement if available
SmoothLocomotion smoothLocomotion;
// Optional components can be used to update LastMoved Time
PlayerClimbing playerClimbing;
// This the object that is currently beneath us
public RaycastHit groundHit;
Transform mainCamera;
private Vector3 _initialPosition;
void Start() {
characterController = GetComponentInChildren<CharacterController>();
smoothLocomotion = GetComponentInChildren<SmoothLocomotion>();
mainCamera = GameObject.FindGameObjectWithTag("MainCamera").transform;
if (characterController) {
_initialPosition = characterController.transform.position;
}
else {
_initialPosition = transform.position;
}
playerClimbing = GetComponentInChildren<PlayerClimbing>();
}
void Update() {
// Sanity check for camera
if (mainCamera == null && Camera.main != null) {
mainCamera = Camera.main.transform;
}
// Update the Character Controller's Capsule Height to match our Camera position
UpdateCharacterHeight();
// Update the position of our camera rig to account for our player's height
UpdateCameraRigPosition();
// After positioning the camera rig, we can update our main camera's height
UpdateCameraHeight();
CheckCharacterCollisionMove();
if (characterController) {
// Align TrackingSpace with Camera
if (RotateCharacterWithCamera) {
RotateTrackingSpaceToCamera();
}
}
LayerInt = groundHit.collider.gameObject.layer;
}
void FixedUpdate() {
UpdateDistanceFromGround();
CheckPlayerElevationRespawn();
}
/// <summary>
/// Check if the player has moved beyond the specified min / max elevation
/// Player should never go above or below 6000 units as physics can start to jitter due to floating point precision
/// Maybe they clipped through a floor, touched a set "lava" height, etc.
/// </summary>
public virtual void CheckPlayerElevationRespawn() {
// No need for elevation checks
if(MinElevation == 0 && MaxElevation == 0) {
return;
}
// Check Elevation based on Character Controller height
if(characterController != null && (characterController.transform.position.y < MinElevation || characterController.transform.position.y > MaxElevation)) {
Debug.Log("Player out of bounds; Returning to initial position.");
characterController.transform.position = _initialPosition;
}
}
public virtual void UpdateDistanceFromGround() {
if(characterController) {
if (Physics.Raycast(characterController.transform.position, -characterController.transform.up, out groundHit, 20, GroundedLayers, QueryTriggerInteraction.Ignore)) {
DistanceFromGround = Vector3.Distance(characterController.transform.position, groundHit.point);
DistanceFromGround += characterController.center.y;
DistanceFromGround -= (characterController.height * 0.5f) + characterController.skinWidth;
// Round to nearest thousandth
DistanceFromGround = (float)Math.Round(DistanceFromGround * 1000f) / 1000f;
}
else {
DistanceFromGround = 9999f;
}
}
// No CharacterController found. Update Distance based on current transform position
else {
if (Physics.Raycast(transform.position, transform.up, out groundHit, 20, GroundedLayers, QueryTriggerInteraction.Ignore)) {
DistanceFromGround = Vector3.Distance(transform.position, groundHit.point);
// Round to nearest thousandth
DistanceFromGround = (float)Math.Round(DistanceFromGround * 1000f) / 1000f;
}
else {
DistanceFromGround = 9999f;
}
}
}
public virtual void RotateTrackingSpaceToCamera() {
Vector3 initialPosition = TrackingSpace.position;
Quaternion initialRotation = TrackingSpace.rotation;
// Move the character controller to the proper rotation / alignment
characterController.transform.rotation = Quaternion.Euler(0.0f, CenterEyeAnchor.rotation.eulerAngles.y, 0.0f);
// Now we can rotate our tracking space back to initial position / rotation
TrackingSpace.position = initialPosition;
TrackingSpace.rotation = initialRotation;
}
public virtual void UpdateCameraRigPosition() {
float yPos = CharacterControllerYOffset;
// Get character controller position based on the height and center of the capsule
if (characterController != null) {
yPos = -(0.5f * characterController.height) + characterController.center.y + CharacterControllerYOffset;
}
// Offset the capsule a bit while climbing. This allows the player to more easily hoist themselves onto a ledge / platform.
if (playerClimbing != null && playerClimbing.GrippingAtLeastOneClimbable()) {
yPos -= 0.25f;
}
// If no HMD is active, bump our rig up a bit so it doesn't sit on the floor
if(!InputBridge.Instance.HMDActive && ElevateCameraIfNoHMDPresent) {
yPos += ElevateCameraHeight;
}
CameraRig.transform.localPosition = new Vector3(CameraRig.transform.localPosition.x, yPos, CameraRig.transform.localPosition.z);
}
public virtual void UpdateCharacterHeight() {
float minHeight = MinimumCapsuleHeight;
// Increase Min Height if no HMD is present. This prevents our character from being really small
if(!InputBridge.Instance.HMDActive && minHeight < 1f) {
minHeight = 1f;
}
// Update Character Height based on Camera Height.
if(characterController) {
characterController.height = Mathf.Clamp(CameraHeight + CharacterControllerYOffset - characterController.skinWidth, minHeight, MaximumCapsuleHeight);
// If we are climbing set the capsule center upwards
if (playerClimbing != null && playerClimbing.GrippingAtLeastOneClimbable()) {
characterController.height = playerClimbing.ClimbingCapsuleHeight;
characterController.center = new Vector3(0, playerClimbing.ClimbingCapsuleCenter, 0);
}
else {
characterController.center = new Vector3(0, -0.25f, 0);
}
}
}
public virtual void UpdateCameraHeight() {
// update camera height
if (CenterEyeAnchor) {
CameraHeight = CenterEyeAnchor.localPosition.y;
}
}
/// <summary>
/// Move the character controller to new camera position
/// </summary>
public virtual void CheckCharacterCollisionMove() {
if(!MoveCharacterWithCamera || characterController == null) {
return;
}
Vector3 initialCameraRigPosition = CameraRig.transform.position;
Vector3 cameraPosition = CenterEyeAnchor.position;
Vector3 delta = cameraPosition - characterController.transform.position;
// Ignore Y position
delta.y = 0;
// Move Character Controller and Camera Rig to Camera's delta
if (delta.magnitude > 0.0f) {
if(smoothLocomotion) {
smoothLocomotion.MoveCharacter(delta);
}
else if(characterController) {
characterController.Move(delta);
}
// Move Camera Rig back into position
CameraRig.transform.position = initialCameraRigPosition;
}
}
public bool IsGrounded() {
// Immediately check for a positive from a CharacterController if it's present
if(characterController != null) {
if(characterController.isGrounded) {
return true;
}
}
// DistanceFromGround is a bit more reliable as we can give a bit of leniency in what's considered grounded
return DistanceFromGround <= 0.001f;
}
}
}