mvaganov

WIP Unity3D character controller

May 17th, 2015
336
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  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")]
  173.     public Transform head;
  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() {
  180.         if(head == null) {
  181.             head = gameObject.transform.FindChild("head");
  182.             if(head == null) {
  183.                 head = new GameObject("head").transform;
  184.                 head.position = transform.position + transform.up * 0.875f;
  185.                 head.transform.SetParent(transform);
  186.                 head.localRotation = Quaternion.identity;
  187.             }
  188.         }
  189.         if(playerCamera == null) {
  190.             Transform t = head.FindChild("camera");
  191.             playerCamera = (t != null) ? t.GetComponent<Camera>() : null;
  192.             if(playerCamera == null) {
  193.                 playerCamera = new GameObject("camera").AddComponent<Camera>();
  194.                 playerCamera.transform.SetParent(head);
  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) {
  201.             CapsuleCollider cap = gameObject.AddComponent<CapsuleCollider>();
  202.             cap.height = 2;
  203.             cap.radius = 0.5f;
  204.         }
  205.         rb = GetComponent<Rigidbody>();
  206.         if(rb == null) {
  207.             rb = gameObject.AddComponent<Rigidbody>();
  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)) {
  284.                 a.rb.AddForce(-lastPlatformMove);
  285.                 stoodOnVelocity = stoodOn.position - parentTransform.position;
  286.                 lastPlatformMove = stoodOnVelocity / Time.deltaTime;
  287.                 parentTransform.SetFrom(stoodOn);
  288.                 a.rb.AddForce(lastPlatformMove);
  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);
  342.             rb.AddForce(standForce);
  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);
  431.             rb.AddForce(f);
  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;
  442.         /// [Header("Controller Variables, set automatically by reading controller input")]
  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.  
  461.         public void LoadInputFromController() {
  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]
  592.             public class HeadTurning {
  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) {
  602.                         a.face.SetFrom(a.head);
  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.                         }
  613.                         a.face.WriteInto(a.head);
  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();
  632.                         curve.AddKey(0, 0);
  633.                         curve.AddKey(.5f, .25f);
  634.                         curve.AddKey(.75f, .25f);
  635.                         curve.AddKey(1, 1);
  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.             }
  657.             public HeadTurning headTurning = new HeadTurning();
  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.                 }
  704.                 return result * GetInclineSpeedAdjustment(angle);
  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
  723.                 rb.AddTorque(-adjusted, ForceMode.Acceleration);
  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!
  764.                         rb.AddTorque(-standUpJerk/2, ForceMode.Acceleration);
  765.                         standUpJerk = euler;
  766.                         rb.AddTorque(euler, ForceMode.Acceleration);
  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;
  774.                 rb.AddTorque(angularJerk, ForceMode.Acceleration);
  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);
  786.                         rb.AddTorque(-counterForce / Time.deltaTime, ForceMode.Acceleration);
  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);
  798.             rb.AddForce(f);
  799.             return f;
  800.         }
  801.  
  802.         public void MoveCameraBack(Transform camera) {
  803.             float thirdPersonDistance = currentSettings.headTurning.thirdPersonCamera.thirdPersonDistance;
  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) {
  824.             currentSettings.headTurning.Update(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);
  842.         face.SetFrom(head);
  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);
  852.         DrawAxisOnTransform(ref line_head, Color.yellow, face, head, .5f);
  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() {
  887.         input.LoadInputFromController();
  888.         movement.Update(this);
  889.     }
  890. }
RAW Paste Data