• API
• FAQ
• Tools
• Archive
A Pastebin account makes a great Christmas gift
SHARE
TWEET

# WIP Unity3D character controller

mvaganov May 17th, 2015 (edited) 295 Never
ENDING IN00days00hours00mins00secs

1. // comment the line below if missing Lines.cs (found at http://pastebin.com/8m69iTut )
2. #define SHOW_AXIS_MATH
3. using UnityEngine;
4. using System.Collections;
5.
6. // TODO camera zoom with mouse wheel AxisCamera
7. // TODO gravity source
8.
9. /// <summary>
10. /// used for calculating and organizing 3D axis math
11. /// </summary>
12. public class Axis {
13.         public Vector3 forward, up, position;
14.         private Vector3 right;
15.
16.         public void SetIdentity() {
17.                 up = Vector3.up;
18.                 forward = Vector3.forward;
19.                 right = Vector3.right;
20.                 position = Vector3.zero;
21.         }
22.
23.         public bool Equals(Transform t) {
24.                 return up == t.up && forward == t.forward && position == t.position;
25.         }
26.
27.         public Axis(Axis other) {
28.                 up = other.up;
29.                 right = other.right;
30.                 forward = other.forward;
31.                 position = other.position;
32.         }
33.         public Axis() { }
34.
35.         public Vector3 GetRight() {
36.                 return right;
37.                 //return Vector3.Cross(up, forward).normalized;
38.         }
39. #if SHOW_AXIS_MATH
40.         public LineRenderer DrawSimple(ref GameObject lineObj, Color c, float size) {
41.                 const float epsilon = 0.001f;
42.                 Vector3 u = up * size, f = forward * size;
43.                 Vector3[] points = {
44.                         position + u,
45.                         position + u * epsilon,
46.                         position + f * epsilon,
47.                         position + f * (1 - epsilon),
48.                         position + f,
49.                         Vector3.zero };
50.                 points[points.Length - 1] = Vector3.Lerp(points[3], points[0], 0.125f);
51.                 LineRenderer lr = Lines.Make(ref lineObj, c, points, points.Length, .1f, .1f);
52.                 return lr;
53.         }
54.         private Vector3[] thumbtack_points_base = null;
55.         public LineRenderer DrawThumbtack(ref GameObject lineObj, Color c, float size, float lineWidth) {
56.                 const float epsilon = 0.001f;
57.                 if(thumbtack_points_base == null) {
58.                         Vector3 pstn = Vector3.zero;
59.                         Vector3 fwrd = Vector3.forward * size;
60.                         Vector3 rght = Vector3.right * size;
61.                         Vector3 up__ = Vector3.up;
62.                         float startAngle = (360.0f / 4) - (360.0f / 32);
63.                         Vector3 v = Quaternion.AngleAxis(startAngle, up__) * fwrd;
64.                         Lines.MakeArc(ref thumbtack_points_base, 32, up__, v, 360, pstn);
65.                         Vector3 tip = pstn + fwrd * Mathf.Sqrt(2);
66.                         thumbtack_points_base[0] = thumbtack_points_base[thumbtack_points_base.Length - 1];
67.                         int m = (32 * 5 / 8) + 1;
68.                         thumbtack_points_base[m++] = thumbtack_points_base[m] + (tip - thumbtack_points_base[m]) * (1 - epsilon);
69.                         thumbtack_points_base[m++] = tip;
70.                         int n = (32 * 7 / 8) + 1;
71.                         while(n < 32) {
72.                                 thumbtack_points_base[m++] = thumbtack_points_base[n++];
73.                         }
74.                         Vector3 side = pstn + rght;
75.                         thumbtack_points_base[m++] = thumbtack_points_base[m] + (side - thumbtack_points_base[m]) * (1 - epsilon);
76.                         thumbtack_points_base[m++] = pstn + rght;
77.                         thumbtack_points_base[m++] = pstn + rght * epsilon;
78.                         thumbtack_points_base[m++] = pstn;
79.                         thumbtack_points_base[m++] = pstn + up__ * size * (1 - epsilon);
80.                         thumbtack_points_base[m++] = pstn + up__ * size;
81.                 }
82.                 LineRenderer lr = Lines.Make(ref lineObj, c, thumbtack_points_base, thumbtack_points_base.Length, lineWidth, lineWidth);
83.                 lr.useWorldSpace = false;
84.                 return lr;
85.         }
86. #endif
87.         public void SetFrom(Transform t) {
88.                 position = t.position;
89.                 up = t.up;
90.                 forward = t.forward;
91.                 right = t.right;
92.         }
93.
94.         public void SetFrom(Axis t) {
95.                 position = t.position;
96.                 up = t.up;
97.                 forward = t.forward;
98.                 right = t.right;
99.         }
100.
101.         public void WriteInto(Transform t) {
102.                 t.position = position;
103.                 t.rotation = Quaternion.LookRotation(forward, up);
104.         }
105.
106.         public void Turn(float t) {
107.                 Quaternion turn = Quaternion.AngleAxis(t, up);
108.                 forward = turn * forward;
109.                 right = turn * right;
110.         }
111.
112.         public void Pitch(float p) {
113.                 Quaternion turn = Quaternion.AngleAxis(p, right);
114.                 forward = turn * forward;
115.                 up = turn * up;
116.         }
117.
118.         public Quaternion GetUpright(Vector3 nextUp) {
119.                 Vector3 r = Vector3.Cross(forward, nextUp);
120.                 Vector3 fwd = Vector3.Cross(nextUp, r);
121.                 return Quaternion.LookRotation(fwd, nextUp);
122.         }
123.
124.         public Quaternion DeltaTo(Quaternion rotation) {
125.                 Quaternion q = Quaternion.LookRotation(this.forward, this.up);
126.                 return rotation * Quaternion.Inverse(q);
127.         }
128.
129.         public Quaternion UprightDelta(Vector3 nextUp) {
130.                 return DeltaTo(GetUpright(nextUp));
131.         }
132.
133.         public void Rotate(Quaternion rotation) {
134.                 forward = rotation * forward;
135.                 up = rotation * up;
136.                 right = rotation * right;
137.         }
138.
139.         public void SetRotation(Quaternion rotation) {
140.                 forward = rotation * Vector3.forward;
141.                 up = rotation * Vector3.up;
142.                 right = rotation * Vector3.right;
143.         }
144.
145.         public void SetUpright(Vector3 up) {
146.                 SetRotation(GetUpright(up));
147.         }
148. }
149.
150. [System.Serializable]
151. public struct TupleMinMax {
152.         public float min, max;
153.         public TupleMinMax(float min, float max) { this.min = min; this.max = max; }
154.         public float Difference(float value) {
155.                 if(value > max) { return max - value; }
156.                 if(value < min) { return min - value; }
157.                 return 0;
158.         }
159.         public float Lerp(float t) { return (max - min) * t + min; }
160.         public float PercentageOf(float value) { return (value - min) / (max - min); }
161. }
162.
163. public class AxisAgent : MonoBehaviour {
164.         /// <summary>how the body is oriented, which decides how key input creates movement impulses</summary>
165.         private Axis body = new Axis();
166.         /// <summary>how the face is oriented, which decides where the agent's head transform is looking (if there is one)</summary>
167.         private Axis face = new Axis();
168.         /// <summary>cached rigidbody of this agent</summary>
169.         private Rigidbody rb;
170.
171.         [Tooltip("What is moved with mouse-look, and determines the direction being aimed at.\n\nRight-click to setup a controllable agent from an otherwise Empty object."),
172.         ContextMenuItem("Setup Standard Axis Agent", "SetStandardAgent")]
174.         public Camera playerCamera;
175. #if SHOW_AXIS_MATH
176.         GameObject line_body, line_head, line_stand, line_gravity, line_velocity, line_move;
177. #endif
178.
179.         void SetStandardAgent() {
184.                                 head.position = transform.position + transform.up * 0.875f;
187.                         }
188.                 }
189.                 if(playerCamera == null) {
191.                         playerCamera = (t != null) ? t.GetComponent<Camera>() : null;
192.                         if(playerCamera == null) {
195.                                 playerCamera.transform.localPosition = new Vector3(0, 5, -10);
196.                                 playerCamera.transform.localRotation = Quaternion.Euler(25, 0, 0);
197.                         }
198.                 }
199.                 Collider col = GetComponent<Collider>();
200.                 if(col == null) {
202.                         cap.height = 2;
204.                 }
205.                 rb = GetComponent<Rigidbody>();
206.                 if(rb == null) {
208.                 }
209.         }
210.
211.         [System.Serializable]
212.         public struct Gravity {
213.                 [SerializeField, Tooltip("How strong the gravity force is")]
214.                 private float force;
215.                 [SerializeField, Tooltip("The direction of the gravity force")]
216.                 private Vector3 direction;
217.                 private Vector3 calculatedForce;
218.
219.                 public Gravity(float force, Vector3 dir) {
220.                         this.force = force; this.direction = dir; this.calculatedForce = force*dir; }
221.                 public Vector3 GetDirection() { return direction; }
222.                 public void SetDirection(Vector3 dir) { direction = dir; Refresh(); }
223.                 public float GetForce() { return force; }
224.                 public void SetForce(float force) { force = this.force; Refresh(); }
225.                 public Vector3 Force() { return calculatedForce; }
226.                 public void Negate() { calculatedForce = Vector3.zero; }
227.                 public void Refresh() { calculatedForce = force * direction; }
228.
229.                 public Vector3 FixedUpdate(Rigidbody rb) { Vector3 v; rb.AddForce(v = Force()); return v; }
230.         }
231.         public Gravity gravity = new Gravity(20, Vector3.down);
232.
233.         [System.Serializable]
234.         /// <summary>a system opposing gravity that allows an agent to hover very slightly over the ground, and move without friction</summary>
235.         public class Stand {
236.                 [SerializeField, Tooltip("How far from the center (in the gravity direction) the standing volume goes")]
237.                 private float distance = 1;
238.                 [SerializeField, Tooltip("The radius of the standing volume")]
239.                 private float radius = .375f;
240.                 [Tooltip("If non-null, this transform's position will be set to the location where the standing is centered.\n\nSet to a sphere with the radius above to visualize the standing volume.")]
241.                 public Transform marker;
242.
243.                 /// <summary>the actual spot being stood on</summary>
244.                 private Vector3 groundContact;
245.                 /// <summary>how the ground is sloped in 3D space</summary>
246.                 private Vector3 surfaceNormal = Vector3.up;
247.                 /// <summary>a 1D value describing how the ground is sloped. used to limit standing force</summary>
248.                 private float surfaceAngle;
249.                 /// <summary>used to keep the standing volume from pushing fully out of the ground. keeping the standing volume slightly overlapping helps verify that the agent is still standing.</summary>
250.                 private float standRadiusEpsilon = 1 / 128.0f;
251.                 /// <summary>used as fast adjustment to position using the physics system (without directly setting transform.position)</summary></summary>
252.                 private Vector3 oneTimeForce;
253.                 /// true if an adjustment to position needs to be made</summary>
254.                 private bool usingOneTimeForce = false;
255.                 /// <summary>true if standing volume is on the ground</summary>
256.                 private bool isOnGround = true;
257.                 [SerializeField, Tooltip("If true, will automatically match motion of transform being stood on")]
258.                 private bool becomeParentedToPlatform = true;
259.                 /// <summary>the actual transform being stood on</summary>
260.                 Transform stoodOn = null;
261.                 /// <summary>the velocity of the transform being stood on</summary>
262.                 private Vector3 stoodOnVelocity;
263.                 /// <summary>axis (position and rotation) of the transform being stood on</summary>
264.                 Axis parentTransform = new Axis();
265.                 /// <summary>the physics calculation done last frame to keep up with the stood-on transform</summary>
266.                 Vector3 lastPlatformMove;
267.
268.                 public bool IsOnGround() { return isOnGround; }
269.                 public void SetOnGround(bool onGround) { isOnGround = onGround; }
270.                 public float GetSurfaceAngle() { return surfaceAngle; }
271.                 public Vector3 GetSurfaceNormal() { return surfaceNormal; }
272.                 public Vector3 GetGroundContact() { return groundContact; }
273.
274.                 /// <param name="a"></param>
275.                 /// <returns>force to apply to a Rigidbody, using AddForce</returns>
276.                 public Vector3 Force(AxisAgent a, ref Gravity g) {
277.                         Vector3 force = Vector3.zero;
278.                         if(usingOneTimeForce) {
279.                                 usingOneTimeForce = false;
280.                                 force -= oneTimeForce;
281.                         }
282.                         // if non-rigid-body parent has moved
283.                         if(becomeParentedToPlatform && stoodOn != null && !parentTransform.Equals(stoodOn)) {
285.                                 stoodOnVelocity = stoodOn.position - parentTransform.position;
286.                                 lastPlatformMove = stoodOnVelocity / Time.deltaTime;
287.                                 parentTransform.SetFrom(stoodOn);
289.                                 a.transform.position = a.transform.position + stoodOnVelocity;
290.                         }
291.                         Vector3 standingDirection = -g.GetDirection();
292.                         Ray ray = new Ray(a.transform.position, -standingDirection);
293.                         RaycastHit hitInfo = new RaycastHit();
294.                         a.GetComponent<Collider>().enabled = false;
295.                         if(!a.jumping.IsJumping() && Physics.SphereCast(ray, radius, out hitInfo, distance) && hitInfo.distance <= distance) {
296.                                 groundContact = hitInfo.point;
297.                                 surfaceNormal = hitInfo.normal;
298.                                 surfaceAngle = Vector3.Angle(standingDirection, surfaceNormal);
299.                                 if(Vector3.Dot(a.body.forward, surfaceNormal) > 0) {
300.                                         surfaceAngle *= -1;
301.                                 }
302.                                 Vector3 centerOfFoot = a.transform.position - standingDirection * distance;
303.                                 if(marker != null) {
304.                                         marker.position = centerOfFoot;
305.                                 }
306.                                 float dist = Vector3.Distance(groundContact, centerOfFoot);
307.                                 float groundOverlap = radius - dist;
308.                                 if(groundOverlap > 0) {
309.                                         // instead of setting the position, set the force needed to clip out, then remove that force next frame.
310.                                         usingOneTimeForce = true;
311.                                         oneTimeForce = (surfaceNormal * (groundOverlap - standRadiusEpsilon)) / (Time.deltaTime*Time.deltaTime);
312.                                         force += oneTimeForce;
313.                                 }
314.                                 float downForce = Vector2.Dot(-standingDirection, a.rb.velocity);
315.                                 if(!isOnGround && downForce > 0) {
316.                                         force += standingDirection * downForce / Time.deltaTime;
317.                                 }
318.                                 // reset double-jump variable if on the ground
319.                                 a.jumping.ResetJumpCount();
320.                                 isOnGround = true;
321.                                 g.Negate();
322.                                 if(becomeParentedToPlatform) {
323.                                         if(stoodOn != hitInfo.collider.transform) {
324.                                                 stoodOn = hitInfo.collider.transform;
325.                                                 parentTransform.SetFrom(stoodOn);
326.                                         }
327.                                 }
328.                         } else {
329.                                 isOnGround = false;
330.                                 g.Refresh();
331.                                 if(becomeParentedToPlatform) {
332.                                         //if(stoodOn != null) print("no parent");
333.                                         stoodOn = null;
334.                                         parentTransform.SetIdentity();
335.                                 }
336.                         }
337.                         a.GetComponent<Collider>().enabled = true;
338.                         return force;
339.                 }
340.                 public Vector3 FixedUpdate(Rigidbody rb, AxisAgent a, ref Gravity gravity) {
341.                         Vector3 standForce = Force(a, ref gravity);
343.                         return standForce;
344.                 }
345.         }
346.         public Stand stand;
347.
348.         [System.Serializable]
349.         public class Jump {
350.                 public TupleMinMax height = new TupleMinMax(1, 5);
351.                 public float fullJumpPressDuration = 0.5f;
352.                 public int maxJumps = 2;
353.
354.                 /// how long the user has been holding jump
355.                 float timeJumpHeld;
356.                 /// how the jump is currently doing
357.                 float currentJumpVelocity;
358.                 float targetHeight;
359.                 float heightReached;
360.                 Vector3 jumpStart;
361.                 bool jumpPeaked = false;
362.                 /// whether or not the user is currently jumping
363.                 bool jumpImpulseActive = false;
364.                 int jumpsSoFar = 0;
365.
366.                 public enum JumpDirection { opposingGravity, opposingGroundSurface }
367.                 GameObject jumpShow;
368.                 float heightReachedTotal;
369.                 public bool IsRequestingJump(AxisAgent a) { return !jumpImpulseActive && (a.input.jump || a.input.jumpHeld); }
370.                 public bool IsJumping() { return currentJumpVelocity > 0; }
371.                 public void ResetJumpCount() { jumpsSoFar = 0; }
372.
373.                 public Vector3 Force(AxisAgent a, ref Gravity g, ref Movement movement) {
374.                         Vector3 force = Vector3.zero;
375.                         Vector3 jumpDirection = -g.GetDirection();//onlyJumpAgainstGravity ? -g.GetDirection() : a.stand.GetSurfaceNormal();//
376.                         // if the user wants to jump, and is allowed to jump again
377.                         if(IsRequestingJump(a) && (jumpsSoFar < maxJumps)) {
378.                                 if(jumpsSoFar == 0) {
379.                                         jumpStart = a.transform.position;
380.                                         heightReachedTotal = 0;
381.                                 }
382.                                 heightReached = 0;
383.                                 timeJumpHeld = 0;
384.                                 jumpsSoFar++;
385.                                 targetHeight = height.min;
386.                                 float velocityRequiredToJump = Mathf.Sqrt(targetHeight * 2 * g.GetForce());
387.                                 currentJumpVelocity = velocityRequiredToJump;
388.                                 jumpPeaked = false;
389.                                 force = (jumpDirection * currentJumpVelocity) / Time.deltaTime;
390.                                 jumpImpulseActive = true;
391.                                 a.stand.SetOnGround(false);
392.                         } else
393.                                 // if a jump is happening
394.                                 if(currentJumpVelocity > 0) {
395.                                         // handle jump height: the longer you hold jump, the higher you jump
396.                                         if(a.input.jumpHeld) {
397.                                                 timeJumpHeld += Time.deltaTime;
398.                                                 if(timeJumpHeld >= fullJumpPressDuration) {
399.                                                         targetHeight = height.max;
400.                                                         timeJumpHeld = fullJumpPressDuration;
401.                                                 } else {
402.                                                         targetHeight = height.Lerp(timeJumpHeld / fullJumpPressDuration);
403.                                                 }
404.                                                 if(heightReached < targetHeight) {
405.                                                         float requiredJumpVelocity = Mathf.Sqrt((targetHeight - heightReached) * 2 * g.GetForce());
406.                                                         float forceNeeded = requiredJumpVelocity - currentJumpVelocity;
407.                                                         force += (jumpDirection * forceNeeded) / Time.deltaTime;
408.                                                         currentJumpVelocity = requiredJumpVelocity;
409.                                                 }
410.                                         } else {
411.                                                 jumpImpulseActive = false;
412.                                         }
413.                                 }
414.                         if(currentJumpVelocity > 0) {
415.                                 float moved = currentJumpVelocity * Time.deltaTime;
416.                                 heightReached += moved;
417.                                 heightReachedTotal += moved;
418. #if SHOW_AXIS_MATH
419.                                 Lines.Make(ref jumpShow, Color.black, jumpStart, a.transform.position, .5f, 0f);
420. #endif
421.                                 currentJumpVelocity -= g.GetForce() * Time.deltaTime;
422.                         } else if(!jumpPeaked && !a.stand.IsOnGround()) {
423.                                 jumpPeaked = true;
424.                                 jumpImpulseActive = false;
425.                         }
426.                         return force;
427.                 }
428.
429.                 public Vector3 FixedUpdate(Rigidbody rb, AxisAgent a, ref Gravity gravity, ref Movement movement) {
430.                         Vector3 f = Force(a, ref gravity, ref movement);
432.                         return f;
433.                 }
434.         }
435.         public Jump jumping;
436.
437.         [System.Serializable]
438.         public class Input {
439.                 public enum Source { none, mouseAndKeyboard }
440.                 [Tooltip("Where to get input from")]
441.                 public Source source = Source.mouseAndKeyboard;
443.                 /// variable:+/-, forward:forward/backward, strafe:right/left, turn:right/left, pitch:down/up
444.                 [HideInInspector] public float forward, strafe, turn, pitch;
445.                 /// [Tooltip("button inputs")]
446.                 [HideInInspector] public bool jump, jumpHeld, fire1, fire1Held, fire2, fire2Held, gravityToggle, fullStop, invertPitch, runHeld;
447.
448.                 public void ReleaseAllInput() {
449.                         forward = strafe = turn = pitch = 0; //mouseWheel = 0;
450.                         jump = jumpHeld = fire1 = fire1Held = fire2 = fire2Held = runHeld = false;
451.                 }
452.                 [System.Serializable]
453.                 public class Sensitivity {
454.                         [Tooltip("x mouse sensitivity")]
455.                         public float horizontal = 10F;
456.                         [Tooltip("y mouse sensitivity")]
457.                         public float vertical = 5F;
458.                 }
459.                 public Sensitivity sensitivity;
460.
462.                         switch(source) {
463.                         case Source.none: break;
464.                         case Source.mouseAndKeyboard:
465.                                 forward = UnityEngine.Input.GetAxis("Vertical");
466.                                 strafe = UnityEngine.Input.GetAxis("Horizontal");
467.                                 turn = UnityEngine.Input.GetAxis("Mouse X") * sensitivity.horizontal;
468.                                 pitch = -UnityEngine.Input.GetAxis("Mouse Y") * sensitivity.vertical;
469.                                 jump = UnityEngine.Input.GetButtonDown("Jump");
470.                                 jumpHeld = UnityEngine.Input.GetButton("Jump");
471.                                 fire1 = UnityEngine.Input.GetButtonDown("Fire1");
472.                                 fire1Held = UnityEngine.Input.GetButton("Fire1");
473.                                 fire2 = UnityEngine.Input.GetButtonDown("Fire2");
474.                                 fire2Held = UnityEngine.Input.GetButton("Fire2");
475.                                 runHeld = UnityEngine.Input.GetKey(KeyCode.LeftShift)
476.                                                 ||UnityEngine.Input.GetKey(KeyCode.RightShift);
477.                                 break;
478.                         default:
479.                                 throw new System.Exception("unsupported controller " + source);
480.                         }
481.                         if(invertPitch) pitch *= -1;
482.                 }
483.         }
484.         public Input input;
485.
486.         [System.Serializable]
487.         public class Movement {
488.                 ///<summary>current movement force</summary>
489.                 private Vector3 force;
490.                 ///<summary>velocity vector if viewed directly from above</summary>
491.                 private Vector3 groundVelocity;
492.                 ///<summary>groundVelocity.magnitude</summary>
493.                 private Vector3 groundDir;
494.                 ///<summary>the direction according to the input</summary>
495.                 private Vector3 inputDir;
496.                 ///<summary>how much rise/fall is going on</summary>
497.                 private float verticalVelocity;
498.                 ///<summary>velocity absent rise/fall</summary>
499.                 private float groundSpeed;
500.                 ///<summary>max speed based on input, taking directional speed limits into account</summary>
501.                 private float maxInputSpeed;
502.                 ///<summary>how much faster the agent is going than the current speed limit</summary>
503.                 private float overSpeed;
504.
505.                 public Vector3 defaultUpDirection = Vector3.up;
506.
507.                 Axis movementAxis;
508.
509.                 ///<summary>how far the 3rd person camera can move back based on obstacles in the way</summary>
510.                 float distanceLimitedTo;
511.                 ///<summary>where the 3rd person camera is</summary>
512.                 float actualDistance;
513.
514. #if SHOW_AXIS_MATH
515.                 GameObject line_movementAxis;
516. #endif
517.                 public Vector3 CalculatedSurfaceUpDirection(AxisAgent a) {
518.                         return a.stand.IsOnGround() ? a.stand.GetSurfaceNormal() : defaultUpDirection;
519.                 }
520.
521.                 public Vector3 Force(AxisAgent a, Settings s) {
522.                         if(movementAxis == null) movementAxis = new Axis();
523.                         movementAxis.SetFrom(a.body);
524.                         Vector3 upDirection = (Mathf.Abs(a.stand.GetSurfaceAngle()) < s.maxIncline) ? CalculatedSurfaceUpDirection(a) : defaultUpDirection;//
525.                         if(a.body.up != upDirection) {
526.                                 movementAxis.SetUpright(upDirection);
527.                         }
528. #if SHOW_AXIS_MATH
529.                         movementAxis.DrawThumbtack(ref line_movementAxis, Color.blue, 2, .0625f);
530.                         movementAxis.WriteInto(line_movementAxis.transform);
531. #endif
532.                         force = Vector3.zero;
533.                         verticalVelocity = Vector3.Dot(defaultUpDirection, a.rb.velocity);
534.                         groundVelocity = a.rb.velocity - (defaultUpDirection * verticalVelocity);
535.                         groundSpeed = groundVelocity.magnitude;
536.                         groundDir = groundVelocity / groundSpeed;
537.                         inputDir = Vector3.zero;
538.                         // input impulse
539.                         if(a.input.forward != 0 || a.input.strafe != 0) {
540.
541.                                 maxInputSpeed = s.GetSpeedBasedOnInput(a.input, a.stand.GetSurfaceAngle());
542.                                 inputDir = movementAxis.forward * a.input.forward + movementAxis.GetRight() * a.input.strafe;
543.                                 inputDir.Normalize();
544.                                 float accelSpeedInCurrentDirection = Vector3.Dot(inputDir, groundVelocity);
545.                                 force = inputDir * ((accelSpeedInCurrentDirection > 0) ? s.acceleration : s.deceleration);
546.                                 if(groundSpeed > maxInputSpeed) {
547.                                         overSpeed = Vector3.Dot(groundDir, force);
548.                                         overSpeed += groundSpeed - maxInputSpeed;
549.                                         force += groundDir * -overSpeed;
550.                                         force = force.normalized * s.acceleration;
551.                                 }
552.                         } else {
553.                                 // stop logic
554.                                 if(s.stopWhenNoInputGiven && groundSpeed != 0) {
555.                                         // figure out how much force is needed to stop
556.                                         float stopNeededToBringToZero = groundSpeed / Time.deltaTime;
557.                                         // if that is too much force, go with the max possible.
558.                                         if(stopNeededToBringToZero > s.deceleration) {
559.                                                 force += -groundDir * s.deceleration;
560.                                         } else {
561.                                                 force += -groundDir * stopNeededToBringToZero;
562.                                         }
563.                                 }
564.                         }
565.                         return force;
566.                 }
567.
568.                 [System.Serializable]
569.                 public class Settings {
570.                         [Tooltip("What to use this movement profile. For example, there could be different movement settings for: walk, run, swim, climb, parkour, jump, fall, fly, hover")]
571.                         public string name = "standard";
572.                         [Tooltip("Maximum speed when pressing forward/backward")]
573.                         public float speedForward = 15, speedBackward = 5;
574.                         [Tooltip("Maximum speed when pressing strafe right/left")]
575.                         public float speedRight = 10, speedLeft = 10;
576.                         [Tooltip("Maximum acceleration when going forward/backward or strafing right/left")]
577.                         public float acceleration = 20;
578.                         [Tooltip("Maximum acceleration when bringing velocity toward zero")]
579.                         public float deceleration = 30;
580.                         [Tooltip("If true, movement calculations will cause a stop if inputs are released")]
581.                         public bool stopWhenNoInputGiven = true;
582.                         [Tooltip("the agent will not be able to move around on surfaces with an incline greater than this")]
583.                         public float maxIncline = 50;
584.                         [Tooltip("Percentage of speed possible as the current surface angle approaches the max incline")]
585.                         public AnimationCurve inclineSpeedLoss = AnimationCurve.Linear(0,0,1,1);
586.
587.                         public float GetInclineSpeedAdjustment(float surfaceAngle) {
588.                                 return (surfaceAngle > 0) ? (1-inclineSpeedLoss.Evaluate(surfaceAngle / maxIncline)) :1;
589.                         }
590.
591.                         [System.Serializable]
593.                                 public TupleMinMax pitchLimit = new TupleMinMax(-140, 110);
594.                                 float currentVerticalAngle;
595.                                 // TODO allow the head transform to turn independently of the body, and make the body match if the turnLimit is reached or movement starts
596. //                              public TupleMinMax turnLimit = new TupleMinMax(-50, 50);
597. //                              public bool turnBodyBeyondMaximum = true;
598. //                              public bool pitchBodyBeyondMaximum = false;
599.
600.                                 public void Update(AxisAgent a) {
601.                                         if(a.input.pitch != 0) {
603.                                                 a.face.Pitch(a.input.pitch);
604.                                                 currentVerticalAngle = Vector3.Angle(a.body.up, a.face.up);
605.                                                 if(Vector3.Dot(a.face.forward, a.body.up) > 0) {
606.                                                         currentVerticalAngle *= -1;
607.                                                 }
608.                                                 float difference = pitchLimit.Difference(currentVerticalAngle);
609.                                                 if(difference != 0) {
610.                                                         a.face.Pitch(difference);
611.                                                         currentVerticalAngle += difference;
612.                                                 }
614.                                                 thirdPersonCamera.CameraAngleUpdate(a.playerCamera, pitchLimit.PercentageOf(currentVerticalAngle));
615.                                         }
616.                                         float wheel = UnityEngine.Input.GetAxis("Mouse ScrollWheel");
617.                                         if(wheel != 0) {
618.                                                 thirdPersonCamera.Move(a.playerCamera.transform, wheel);
619.                                         }
620.                                 }
621.
622.                                 [System.Serializable]
623.                                 public class ThirdPersonCameraController {
624.                                         [Tooltip("How far the user wants the camera to be")]
625.                                         public float thirdPersonDistance = 10;
626.                                         [Tooltip("How quickly to move the camera to it's spot (lower is faster)"), Range(1, 128)]
627.                                         public float collisionSpeed = 8;
628.                                         public AnimationCurve viewAngle3rdPerson = DefaultCameraLookCurve();
629.
630.                                         public static AnimationCurve DefaultCameraLookCurve() {
631.                                                 AnimationCurve curve = new AnimationCurve();
636.                                                 return curve;
637.                                         }
638.
639.                                         public void Move(Transform camera, float change) {
640.                                                 change *= 5;
641.                                                 thirdPersonDistance -= change;
642.                                                 if(thirdPersonDistance < change) {
643.                                                         camera.position = camera.parent.position;
644.                                                         thirdPersonDistance = 0;
645.                                                 }
646.                                         }
647.
648.                                         public void CameraAngleUpdate(Camera camera, float vertical) {
649.                                                 vertical = viewAngle3rdPerson.Evaluate(vertical);
650.                                                 float fieldOfView = camera.fieldOfView;
651.                                                 float viewAngle = fieldOfView * vertical;
652.                                                 camera.transform.localRotation = Quaternion.Euler(viewAngle - 30, 0, 0);
653.                                         }
654.                                 }
655.                                 public ThirdPersonCameraController thirdPersonCamera = new ThirdPersonCameraController();
656.                         }
658.
659.                         [System.Serializable]
660.                         public class RotationControl {
661.                                 [Tooltip("Be precise when turning with the mouse (no lag, or continued turning)")]
662.                                 public bool stopControlledAngularVelocity = true;
663.                                 [Tooltip("stop the spinning!")]
664.                                 public bool stopUncontrolledAngularVelocity = true;
665.                                 [Tooltip("set the player standing correctly if angle is perterbed")]
666.                                 public bool automaticallySetRightsideUp = true;
667.                                 [Tooltip("Force used to stop uncontrolled angular rotation")]
668.                                 public Vector3 angularVelocityCounterForce = new Vector3(15, 90, 15);
669.                                 [Tooltip("Force used to set player upright")]
670.                                 public float rightsideUpForce = 20;
671.                         }
672.                         public RotationControl rotationControl = new RotationControl();
673.
674.                         public Settings(float forward, float backward, float strafeRight, float strafeLeft, float accel, float decel) {
675.                                 this.speedForward = forward;
676.                                 this.speedBackward = backward;
677.                                 this.speedRight = strafeRight;
678.                                 this.speedLeft = strafeLeft;
679.                                 this.acceleration = accel;
680.                                 this.deceleration = decel;
681.                         }
682.
683.                         public float GetSpeedBasedOnInput(Input i, float angle) {
684.                                 float result;
685.                                 if(i.forward == 0) {
686.                                         if(i.strafe == 0) result = 0;
687.                                         else if(i.strafe < 0) result = speedLeft;
688.                                         else result = speedRight;
689.                                 } else if(i.strafe == 0) {
690.                                         if(i.forward > 0) result = speedForward;
691.                                         else result = speedBackward;
692.                                 } else {
693.                                         float sum, f, s;
694.                                         if(i.forward > 0) {
695.                                                 f = i.forward;
696.                                                 if(i.strafe > 0) { s = i.strafe; sum = f + s; f /= sum; s /= sum; result = (f * speedForward) + (s * speedRight); }
697.                                                 else             { s = -i.strafe; sum = f + s; f /= sum; s /= sum; result = (f * speedForward) + (s * speedLeft); }
698.                                         } else {
699.                                                 f = -i.forward;
700.                                                 if(i.strafe > 0) { s = i.strafe; sum = f + s; f /= sum; s /= sum; result = (f * speedBackward) + (s * speedRight); }
701.                                                 else             { s = -i.strafe; sum = f + s; f /= sum; s /= sum; result = (f * speedBackward) + (s * speedLeft); }
702.                                         }
703.                                 }
705.                         }
706.                 }
707.
708.                 public Settings currentSettings = new Settings(15, 5, 10, 10, 20, 60);
709.
710.                 private Vector3 angularJerk, standUpJerk;
711.
712. #if SHOW_AXIS_MATH
713.                 /// visuals to help debug rotation forces
714.                 GameObject rot_axis, rot_fwd, rot_up, rot_target;
715. #endif
716.                 public void FixedUpdateRotation(AxisAgent a, Input inputSource, Transform transform, Axis body, Rigidbody rb) {
717.                         bool balancingRotation = false;
718.                         // if the agent is controlling angular jerks, to emulate precise rotation, and there was an angular jerk last frame
719.                         if(currentSettings.rotationControl.stopControlledAngularVelocity && angularJerk != Vector3.zero) {
720.                                 // find out how much that angular jerk has depreciated
721.                                 Vector3 adjusted = angularJerk - (angularJerk * rb.angularDrag * Time.deltaTime);
722.                                 // remove the jerk from the current angular rotation
724.                                 // mark that there is no more angular jerk (it has been undone)
725.                                 angularJerk = Vector3.zero;
726.                                 // mark thata balancing rotation occured, which will prevent further balancing rotations
727.                                 balancingRotation = true;
728.                         }
729.                         if(currentSettings.rotationControl.automaticallySetRightsideUp && body.up != defaultUpDirection) {
730.                                 Quaternion uprightRotation = body.GetUpright(defaultUpDirection);
731.                                 Quaternion rotationToRightSelf = body.DeltaTo(uprightRotation);//body.UprightDelta(-gravity.GetDirection());
732.                                 Vector3 euler = rotationToRightSelf.eulerAngles;
733.                                 bool change = false;
734.                                 if(euler.x > 180) { euler.x -= 360; change = true; }
735.                                 if(euler.y > 180) { euler.y -= 360; change = true; }
736.                                 if(euler.z > 180) { euler.z -= 360; change = true; }
737.                                 const float epsilon = 1 / 128f;
738.                                 if(Mathf.Abs(euler.x) < epsilon) euler.x = 0;
739.                                 if(Mathf.Abs(euler.y) < epsilon) euler.y = 0;
740.                                 if(Mathf.Abs(euler.z) < epsilon) euler.z = 0;
741.                                 if(change) {
742.                                         //print(Time.time+":["+euler.x + " " + euler.y + " " + euler.z+"]");
743.                                         rotationToRightSelf = Quaternion.Euler(euler);
744.                                 }
745.                                 if(euler != Vector3.zero) {
746.                                         Vector3 rotAxis = Vector3.zero;
747.                                         float rotAngle;
748.                                         rotationToRightSelf.ToAngleAxis(out rotAngle, out rotAxis);
749. #if SHOW_AXIS_MATH
750.                                         Lines.Make(ref rot_axis, Color.magenta, transform.position + rotAxis, transform.position - rotAxis, .1f, .1f);
751.                                         Lines.MakeArc(ref rot_fwd, Color.magenta, transform.position, rotAxis, transform.forward, rotAngle, 8, .1f, 0);
752.                                         Lines.MakeArc(ref rot_up, Color.magenta, transform.position, rotAxis, transform.up, rotAngle, 8, .1f, 0);
753.                                         Axis next = new Axis(body);
754.                                         next.Rotate(rotationToRightSelf);
755.                                         next.DrawSimple(ref rot_target, Color.black, 1);
756. #endif
757.                                         float f = currentSettings.rotationControl.rightsideUpForce;
758.                                         euler.x = Mathf.Clamp(euler.x, -f, f);
759.                                         euler.y = Mathf.Clamp(euler.y, -f, f);
760.                                         euler.z = Mathf.Clamp(euler.z, -f, f);
761.                                         //print(Time.time + ": " + euler.x + " " + euler.y + " " + euler.z);
762.                                         if(standUpJerk != euler) {
763.                                                 // divide by 2 because... it makes it work better. TODO more controlled right-side-up angular forces!
765.                                                 standUpJerk = euler;
767.                                         }
768.                                 }
769.                         }
770.                         // if the player wants to turn (horizontal motion)
771.                         if(inputSource.turn != 0) {
772.                                 // turn horizontally
773.                                 angularJerk.y += inputSource.turn / Time.deltaTime;
775.                         } else {
776.                                 // if no turn is going on, and no balancing rotation is going on, and this agent should be stopping uncontrolled rotations
777.                                 if(currentSettings.rotationControl.stopUncontrolledAngularVelocity && !balancingRotation) {
778.                                         // if there is rotation
779.                                         Vector3 angle = rb.angularVelocity;
780.                                         if(angle != Vector3.zero) {
781.                                                 // calculate a counter-force to stop rotation
782.                                                 Vector3 counterForce = currentSettings.rotationControl.angularVelocityCounterForce;// Vector3.zero;
783.                                                 counterForce.x = Mathf.Clamp(angle.x, -counterForce.x, counterForce.x);
784.                                                 counterForce.y = Mathf.Clamp(angle.y, -counterForce.y, counterForce.y);
785.                                                 counterForce.z = Mathf.Clamp(angle.z, -counterForce.z, counterForce.z);
787.                                         }
788.                                 }
789.                         }
790.                 }
791.
792.                 public Vector3 Force(AxisAgent a) {
793.                         return Force(a, currentSettings);
794.                 }
795.
796.                 public Vector3 FixedUpdate(Rigidbody rb, AxisAgent a) {
797.                         Vector3 f = Force(a);
799.                         return f;
800.                 }
801.
802.                 public void MoveCameraBack(Transform camera) {
804.                         if(thirdPersonDistance == 0) return;
805.                         RaycastHit rh = new RaycastHit();
806.                         Vector3 dir = -camera.parent.forward;
807.                         float nextPosition = actualDistance;
808.                         if(!Physics.Raycast(camera.parent.position, dir, out rh, thirdPersonDistance)) {
809.                                 distanceLimitedTo = thirdPersonDistance;
810.                         } else {
811.                                 distanceLimitedTo = rh.distance;
812.                         }
813.                         if(actualDistance != distanceLimitedTo) {
814.                                 float delta = (distanceLimitedTo - actualDistance) / currentSettings.headTurning.thirdPersonCamera.collisionSpeed;
815.                                 nextPosition = (Mathf.Abs(delta) > 1 / 128.0f) ? (actualDistance + delta) : distanceLimitedTo;
816.                         }
817.                         if(nextPosition != actualDistance) {
818.                                 actualDistance = nextPosition;
819.                                 camera.localPosition = -Vector3.forward * actualDistance;
820.                         }
821.                 }
822.
823.                 public void Update(AxisAgent a) {
825.                         MoveCameraBack(a.playerCamera.transform);
826.                 }
827.         }
828.
829.         [Tooltip("Variables that set how this agent moves, and keep track of orientation")]
830.         public Movement movement;
831.
832.         public void SetMovementSettings(Movement.Settings moveSettings) {
833.                 movement.currentSettings = moveSettings;
834.         }
835.
836.         ///////////////////////////////////////////////////////////////////////////////////////////////
837.
838.         /// uses Rigidbody.AddForce() to modify the rigidbody's behavior.
839.         void FixedUpdate () {
840.                 // update axis
841.                 body.SetFrom(transform);
843.                 // update components. keep track of forces locally so visuals can be updated
844.                 Vector3 moveForce = movement.FixedUpdate(rb, this);
845.                 Vector3 gravityForce = gravity.FixedUpdate(rb);
846.                 Vector3 jumpingForce = jumping.FixedUpdate(rb, this, ref gravity, ref movement);
847.                 Vector3 standForce = stand.FixedUpdate(rb, this, ref gravity);
848.                 // TODO RotationMovement
849.                 movement.FixedUpdateRotation(this, input, transform, body, rb);
850. #if SHOW_AXIS_MATH
851.                 DrawAxisOnTransform(ref line_body, Color.cyan, body, transform, .75f);
853.                 Vector3 p = transform.position;
854.                 Vector3 s = stand.GetGroundContact();
855.                 Vector3 v = transform.position + rb.velocity;
856.                 Lines.Make(ref line_gravity, Color.magenta, p, p + gravityForce, .2f, 0);
857.                 Lines.Make(ref line_stand, Color.green, s, s + standForce, .4f, 0);
858.                 Lines.Make(ref line_velocity, Color.blue, p, v, .1f, .1f);
859.                 Lines.Make(ref line_move, Color.red, v, v + moveForce, .1f, .1f);
860. #endif
861.         }
862.
863. #if SHOW_AXIS_MATH
864.         void DrawAxisOnTransform(ref GameObject line_, Color c, Axis a, Transform t, float size) {
865.                 a.DrawThumbtack(ref line_, c, size, .1f);
866.                 line_.transform.SetParent(t);
867.                 line_.transform.localPosition = Vector3.zero;
868.                 line_.transform.localRotation = Quaternion.identity;
869.         }
870. #endif
871.
872.         struct MoveSettingsManager {
873.                 Movement.Settings defaultMoveSetting;
874.                 [Tooltip("walk, run, swim, climb, parkour, jump, fall, fly, hover")]
875.                 public Movement.Settings[] movementSettings;
876.                 // TODO walk button, run button, climb button, parkour minimum speed, hover button, fly minimum speed, swim toggle
877.         }
878.
879.         void Start() {
880.                 rb = GetComponent<Rigidbody>();
881.                 rb.useGravity = false;
882.                 rb.angularDrag = 0;
883.                 rb.maxAngularVelocity = 360;
884.         }
885.
886.         void Update() {