1 using System;
2 using UnityEngine;
3 using UnityStandardAssets.CrossPlatformInput;
4
5 namespace UnityStandardAssets.Characters.FirstPerson
6 {
7 [RequireComponent(typeof (Rigidbody))]
8 [RequireComponent(typeof (CapsuleCollider))]
9 public class RigidbodyFirstPersonController : MonoBehaviour
10 {
11 [Serializable]
12 public class MovementSettings
13 {
14 public float ForwardSpeed = 8.0f; // Speed when walking forward
15 public float BackwardSpeed = 4.0f; // Speed when walking backwards
16 public float StrafeSpeed = 4.0f; // Speed when walking sideways
17 public float RunMultiplier = 2.0f; // Speed when sprinting
18 public KeyCode RunKey = KeyCode.LeftShift;
19 public float JumpForce = 30f;
20 public AnimationCurve SlopeCurveModifier = new AnimationCurve(new Keyframe(-90.0f, 1.0f), new Keyframe(0.0f, 1.0f), new Keyframe(90.0f, 0.0f));
21 [HideInInspector] public float CurrentTargetSpeed = 8f;
22
23 #if !MOBILE_INPUT
24 private bool m_Running;
25 #endif
26
27 public void UpdateDesiredTargetSpeed(Vector2 input)
28 {
29 if (input == Vector2.zero) return;
30 if (input.x > 0 || input.x < 0)
31 {
32 //strafe
33 CurrentTargetSpeed = StrafeSpeed;
34 }
35 if (input.y < 0)
36 {
37 //backwards
38 CurrentTargetSpeed = BackwardSpeed;
39 }
40 if (input.y > 0)
41 {
42 //forwards
43 //handled last as if strafing and moving forward at the same time forwards speed should take precedence
44 CurrentTargetSpeed = ForwardSpeed;
45 }
46 #if !MOBILE_INPUT
47 if (Input.GetKey(RunKey))
48 {
49 CurrentTargetSpeed *= RunMultiplier;
50 m_Running = true;
51 }
52 else
53 {
54 m_Running = false;
55 }
56 #endif
57 }
58
59 #if !MOBILE_INPUT
60 public bool Running
61 {
62 get { return m_Running; }
63 }
64 #endif
65 }
66
67
68 [Serializable]
69 public class AdvancedSettings
70 {
71 public float groundCheckDistance = 0.01f; // distance for checking if the controller is grounded ( 0.01f seems to work best for this )
72 public float stickToGroundHelperDistance = 0.5f; // stops the character
73 public float slowDownRate = 20f; // rate at which the controller comes to a stop when there is no input
74 public bool airControl; // can the user control the direction that is being moved in the air
75 [Tooltip("set it to 0.1 or more if you get stuck in wall")]
76 public float shellOffset; //reduce the radius by that ratio to avoid getting stuck in wall (a value of 0.1f is nice)
77 }
78
79
80 public Camera cam;
81 public MovementSettings movementSettings = new MovementSettings();
82 public MouseLook mouseLook = new MouseLook();
83 public AdvancedSettings advancedSettings = new AdvancedSettings();
84
85
86 private Rigidbody m_RigidBody;
87 private CapsuleCollider m_Capsule;
88 private float m_YRotation;
89 private Vector3 m_GroundContactNormal;
90 private bool m_Jump, m_PreviouslyGrounded, m_Jumping, m_IsGrounded;
91
92
93 public Vector3 Velocity
94 {
95 get { return m_RigidBody.velocity; }
96 }
97
98 public bool Grounded
99 {
100 get { return m_IsGrounded; }
101 }
102
103 public bool Jumping
104 {
105 get { return m_Jumping; }
106 }
107
108 public bool Running
109 {
110 get
111 {
112 #if !MOBILE_INPUT
113 return movementSettings.Running;
114 #else
115 return false;
116 #endif
117 }
118 }
119
120
121 private void Start()
122 {
123 m_RigidBody = GetComponent<Rigidbody>();
124 m_Capsule = GetComponent<CapsuleCollider>();
125 mouseLook.Init (transform, cam.transform);
126 }
127
128
129 private void Update()
130 {
131 RotateView();
132
133 if (CrossPlatformInputManager.GetButtonDown("Jump") && !m_Jump)
134 {
135 m_Jump = true;
136 }
137 }
138
139
140 private void FixedUpdate()
141 {
142 GroundCheck();
143 Vector2 input = GetInput();
144
145 if ((Mathf.Abs(input.x) > float.Epsilon || Mathf.Abs(input.y) > float.Epsilon) && (advancedSettings.airControl || m_IsGrounded))
146 {
147 // always move along the camera forward as it is the direction that it being aimed at
148 Vector3 desiredMove = cam.transform.forward*input.y + cam.transform.right*input.x;
149 desiredMove = Vector3.ProjectOnPlane(desiredMove, m_GroundContactNormal).normalized;
150
151 desiredMove.x = desiredMove.x*movementSettings.CurrentTargetSpeed;
152 desiredMove.z = desiredMove.z*movementSettings.CurrentTargetSpeed;
153 desiredMove.y = desiredMove.y*movementSettings.CurrentTargetSpeed;
154 if (m_RigidBody.velocity.sqrMagnitude <
155 (movementSettings.CurrentTargetSpeed*movementSettings.CurrentTargetSpeed))
156 {
157 m_RigidBody.AddForce(desiredMove*SlopeMultiplier(), ForceMode.Impulse);
158 }
159 }
160
161 if (m_IsGrounded)
162 {
163 m_RigidBody.drag = 5f;
164
165 if (m_Jump)
166 {
167 m_RigidBody.drag = 0f;
168 m_RigidBody.velocity = new Vector3(m_RigidBody.velocity.x, 0f, m_RigidBody.velocity.z);
169 m_RigidBody.AddForce(new Vector3(0f, movementSettings.JumpForce, 0f), ForceMode.Impulse);
170 m_Jumping = true;
171 }
172
173 if (!m_Jumping && Mathf.Abs(input.x) < float.Epsilon && Mathf.Abs(input.y) < float.Epsilon && m_RigidBody.velocity.magnitude < 1f)
174 {
175 m_RigidBody.Sleep();
176 }
177 }
178 else
179 {
180 m_RigidBody.drag = 0f;
181 if (m_PreviouslyGrounded && !m_Jumping)
182 {
183 StickToGroundHelper();
184 }
185 }
186 m_Jump = false;
187 }
188
189
190 private float SlopeMultiplier()
191 {
192 float angle = Vector3.Angle(m_GroundContactNormal, Vector3.up);
193 return movementSettings.SlopeCurveModifier.Evaluate(angle);
194 }
195
196
197 private void StickToGroundHelper()
198 {
199 RaycastHit hitInfo;
200 if (Physics.SphereCast(transform.position, m_Capsule.radius * (1.0f - advancedSettings.shellOffset), Vector3.down, out hitInfo,
201 ((m_Capsule.height/2f) - m_Capsule.radius) +
202 advancedSettings.stickToGroundHelperDistance, ~0, QueryTriggerInteraction.Ignore))
203 {
204 if (Mathf.Abs(Vector3.Angle(hitInfo.normal, Vector3.up)) < 85f)
205 {
206 m_RigidBody.velocity = Vector3.ProjectOnPlane(m_RigidBody.velocity, hitInfo.normal);
207 }
208 }
209 }
210
211
212 private Vector2 GetInput()
213 {
214
215 Vector2 input = new Vector2
216 {
217 x = CrossPlatformInputManager.GetAxis("Horizontal"),
218 y = CrossPlatformInputManager.GetAxis("Vertical")
219 };
220 movementSettings.UpdateDesiredTargetSpeed(input);
221 return input;
222 }
223
224
225 private void RotateView()
226 {
227 //avoids the mouse looking if the game is effectively paused
228 if (Mathf.Abs(Time.timeScale) < float.Epsilon) return;
229
230 // get the rotation before it's changed
231 float oldYRotation = transform.eulerAngles.y;
232
233 mouseLook.LookRotation (transform, cam.transform);
234
235 if (m_IsGrounded || advancedSettings.airControl)
236 {
237 // Rotate the rigidbody velocity to match the new direction that the character is looking
238 Quaternion velRotation = Quaternion.AngleAxis(transform.eulerAngles.y - oldYRotation, Vector3.up);
239 m_RigidBody.velocity = velRotation*m_RigidBody.velocity;
240 }
241 }
242
243 /// sphere cast down just beyond the bottom of the capsule to see if the capsule is colliding round the bottom
244 private void GroundCheck()
245 {
246 m_PreviouslyGrounded = m_IsGrounded;
247 RaycastHit hitInfo;
248 if (Physics.SphereCast(transform.position, m_Capsule.radius * (1.0f - advancedSettings.shellOffset), Vector3.down, out hitInfo,
249 ((m_Capsule.height/2f) - m_Capsule.radius) + advancedSettings.groundCheckDistance, ~0, QueryTriggerInteraction.Ignore))
250 {
251 m_IsGrounded = true;
252 m_GroundContactNormal = hitInfo.normal;
253 }
254 else
255 {
256 m_IsGrounded = false;
257 m_GroundContactNormal = Vector3.up;
258 }
259 if (!m_PreviouslyGrounded && m_IsGrounded && m_Jumping)
260 {
261 m_Jumping = false;
262 }
263 }
264 }
265 }