forked from RonenNess/UnityUtils
-
Notifications
You must be signed in to change notification settings - Fork 0
/
FPSWalker.cs
223 lines (190 loc) · 9.36 KB
/
FPSWalker.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
/**
* Basic FPS-like walking, running, and jumping.
* This is just a modified version of the following sources:
* - http://wiki.unity3d.com/index.php?title=FPSWalkerEnhanced
*
* Author: Ronen Ness.
* Since: 2017.
*/
using UnityEngine;
using System.Collections;
namespace NesScripts.Controls
{
/// <summary>
/// Implement FPS walking / running / jumping controls.
/// In addition it includes handling falling, sliding on slopes, etc.
/// Note: this controller does not rotate camera, only walks based on current rotation.
///
/// Use this script with the FPS looker to implement full FPS controls.
/// </summary>
[RequireComponent(typeof(CharacterController))]
public class FPSWalker : MonoBehaviour
{
// walking speed
public float walkSpeed = 6.0f;
// running speed
public float runSpeed = 11.0f;
// If true, diagonal speed (when strafing + moving forward or back) can't exceed normal move speed; otherwise it's about 1.4 times faster
public bool limitDiagonalSpeed = true;
// If checked, the run key toggles between running and walking. Otherwise player runs if the key is held down and walks otherwise
// There must be a button set up in the Input Manager called "Run"
public bool toggleRun = false;
// jumping speed
public float jumpSpeed = 8.0f;
// gravity applied on controller
public float gravity = 20.0f;
// Units that player can fall before a falling damage function is run. To disable, type "infinity" in the inspector
public float fallingDamageThreshold = 10.0f;
// If the player ends up on a slope which is at least the Slope Limit as set on the character controller, then he will slide down
public bool slideWhenOverSlopeLimit = false;
// If checked and the player is on an object tagged "Slide", he will slide down it regardless of the slope limit
public bool slideOnTaggedObjects = false;
// sliding speed
public float slideSpeed = 12.0f;
// If checked, then the player can change direction while in the air
public bool airControl = false;
// Small amounts of this results in bumping when walking down slopes, but large amounts results in falling too fast
public float antiBumpFactor = .75f;
// Player must be grounded for at least this many physics frames before being able to jump again; set to 0 to allow bunny hopping
public int antiBunnyHopFactor = 1;
// internal stuff
private Vector3 moveDirection = Vector3.zero;
private bool grounded = false;
private CharacterController controller;
private Transform myTransform;
private float speed;
private RaycastHit hit;
private float fallStartLevel;
private bool falling;
private float slideLimit;
private float rayDistance;
private Vector3 contactPoint;
private bool playerControl = false;
private int jumpTimer;
/// <summary>
/// Init the controller.
/// </summary>
void Start()
{
controller = GetComponent<CharacterController>();
myTransform = transform;
speed = walkSpeed;
rayDistance = controller.height * .5f + controller.radius;
slideLimit = controller.slopeLimit - .1f;
jumpTimer = antiBunnyHopFactor;
}
/// <summary>
/// Fixed update - Do movement.
/// </summary>
void FixedUpdate()
{
// get movement on x and y axis
float inputX = Input.GetAxis("Horizontal");
float inputY = Input.GetAxis("Vertical");
// If both horizontal and vertical are used simultaneously, limit speed (if allowed), so the total doesn't exceed normal move speed
float inputModifyFactor = (inputX != 0.0f && inputY != 0.0f && limitDiagonalSpeed) ? .7071f : 1.0f;
// if on ground:
if (grounded)
{
// check if sliding
bool sliding = false;
// See if surface immediately below should be slid down. We use this normally rather than a ControllerColliderHit point,
// because that interferes with step climbing amongst other annoyances
if (Physics.Raycast(myTransform.position, -Vector3.up, out hit, rayDistance))
{
if (Vector3.Angle(hit.normal, Vector3.up) > slideLimit)
sliding = true;
}
// However, just raycasting straight down from the center can fail when on steep slopes
// So if the above raycast didn't catch anything, raycast down from the stored ControllerColliderHit point instead
else
{
Physics.Raycast(contactPoint + Vector3.up, -Vector3.up, out hit);
if (Vector3.Angle(hit.normal, Vector3.up) > slideLimit)
sliding = true;
}
// If we were falling, and we fell a vertical distance greater than the threshold, run a falling damage routine
if (falling)
{
falling = false;
if (myTransform.position.y < fallStartLevel - fallingDamageThreshold)
FallingDamageAlert(fallStartLevel - myTransform.position.y);
}
// If running isn't on a toggle, then use the appropriate speed depending on whether the run button is down
if (!toggleRun)
speed = Input.GetButton("Run") ? runSpeed : walkSpeed;
// If sliding (and it's allowed), or if we're on an object tagged "Slide", get a vector pointing down the slope we're on
if ((sliding && slideWhenOverSlopeLimit) || (slideOnTaggedObjects && hit.collider.tag == "Slide"))
{
Vector3 hitNormal = hit.normal;
moveDirection = new Vector3(hitNormal.x, -hitNormal.y, hitNormal.z);
Vector3.OrthoNormalize(ref hitNormal, ref moveDirection);
moveDirection *= slideSpeed;
playerControl = false;
}
// Otherwise recalculate moveDirection directly from axes, adding a bit of -y to avoid bumping down inclines
else
{
moveDirection = new Vector3(inputX * inputModifyFactor, -antiBumpFactor, inputY * inputModifyFactor);
moveDirection = myTransform.TransformDirection(moveDirection) * speed;
playerControl = true;
}
// Jump! But only if the jump button has been released and player has been grounded for a given number of frames
if (!Input.GetButton("Jump"))
jumpTimer++;
else if (jumpTimer >= antiBunnyHopFactor)
{
moveDirection.y = jumpSpeed;
jumpTimer = 0;
}
}
// if not on ground (in air):
else
{
// If we stepped over a cliff or something, set the height at which we started falling
if (!falling)
{
falling = true;
fallStartLevel = myTransform.position.y;
}
// If air control is allowed, check movement but don't touch the y component
if (airControl && playerControl)
{
moveDirection.x = inputX * speed * inputModifyFactor;
moveDirection.z = inputY * speed * inputModifyFactor;
moveDirection = myTransform.TransformDirection(moveDirection);
}
}
// Apply gravity
moveDirection.y -= gravity * Time.deltaTime;
// Move the controller, and set grounded true or false depending on whether we're standing on something
grounded = (controller.Move(moveDirection * Time.deltaTime) & CollisionFlags.Below) != 0;
}
/// <summary>
/// Update - handling walking / running toggle etc.
/// </summary>
void Update()
{
// If the run button is set to toggle, then switch between walk/run speed. (We use Update for this...
// FixedUpdate is a poor place to use GetButtonDown, since it doesn't necessarily run every frame and can miss the event)
if (toggleRun && grounded && Input.GetButtonDown("Run"))
speed = (speed == walkSpeed ? runSpeed : walkSpeed);
}
/// <summary>
/// Store point that we're in contact with for use in FixedUpdate if needed
/// </summary>
void OnControllerColliderHit(ControllerColliderHit hit)
{
contactPoint = hit.point;
}
/// <summary>
/// If falling damage occured, this is the place to do something about it. You can make the player
/// have hitpoints and remove some of them based on the distance fallen, add sound effects, etc.
/// </summary>
/// <param name="fallDistance">The distance the player fell before hitting the ground.</param>
void FallingDamageAlert(float fallDistance)
{
print("Ouch! Fell " + fallDistance + " units!");
}
}
}