daily pastebin goal
56%
SHARE
TWEET

PlayerControl.cs

mvaganov Oct 12th, 2016 (edited) 101 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. using UnityEngine;
  2. using UnityEditor;
  3. /// <summary>A custom Unity3D character controller
  4. /// Latest version at: https://pastebin.com/9M9qyBmP </summary>
  5. /// <description>MIT License - TL;DR - This code is free, don't bother me about it!</description>
  6. /// <author email="mvaganov@hotmail.com">Michael Vaganov</author>
  7.  
  8. public class PlayerControl : MobileEntity {
  9.     #region Public API
  10.     /// <summary>true if on the ground</summary>
  11.     [HideInInspector]
  12.     public bool IsStableOnGround { get; protected set; }
  13.     /// <summary>if OnCollision event happens with ground, this is true</summary>
  14.     [HideInInspector]
  15.     public bool IsCollidingWithGround { get; protected set; }
  16.     public enum GravityState { none, useGravity, noFalling }
  17.     [Tooltip("if false, disables gravity's effects, and enables flying")]
  18.     public GravityState gravityApplication = GravityState.useGravity;
  19.     /// <summary>the ground-plane normal, used to determine which direction 'forward' is when it comes to movement on the ground</summary>
  20.     public enum HowToDealWithGround {none, withPlaceholder, withParenting}
  21.     [Tooltip("how the player should stay stable if the ground is moving\n"+
  22.         "* none: don't bother trying to be stable\n"+
  23.         "* withPlaceholder: placeholder transform keeps track\n"+
  24.         "* withParenting: player is parented to ground (not safe if ground is scaled)\n")]
  25.     public HowToDealWithGround stickToGround = HowToDealWithGround.withPlaceholder;
  26.     [HideInInspector]
  27.     public Vector3 GroundNormal { get; protected set; }
  28.     [HideInInspector]
  29.     /// <summary>What object is being stood on</summary>
  30.     public GameObject StandingOnObject { get; protected set; }
  31.     /// <summary>the angle of standing, compared to gravity, used to determine if the player can reliably walk on this surface (it might be too slanted)</summary>
  32.     [HideInInspector]
  33.     public float StandAngle { get; protected set; }
  34.     [Tooltip("The maximum angle the player can move forward at while standing on the ground")]
  35.     public float MaxWalkAngle = 45;
  36.     /// <summary>if very far from ground, this value is infinity</summary>
  37.     [HideInInspector]
  38.     public float HeightFromGround { get; protected set; }
  39.     [HideInInspector]
  40.     public Collider CollideBox { get; protected set; }
  41.     /// <summary>expected distance from the center of the collider to the ground. auto-populates based on Box of Capsule collider</summary>
  42.     [HideInInspector]
  43.     public float ExpectedHeightFromGround;
  44.     /// <summary>expected distance from the center of the collider to a horizontal edge. auto-populates based on Box of Capsule collider</summary>
  45.     [HideInInspector]
  46.     public float ExpectedHorizontalRadius;
  47.     /// <summary>how much downward velocity the player is experiencing. non-zero when jumping or falling.</summary>
  48.     [HideInInspector]
  49.     public float VerticalVelocity { get; protected set; }
  50.  
  51.     public Vector3 GetUpOrientation() { return -gravity.dir; }
  52.     public Vector3 GetUpBodyDirection() { return GroundNormal; }
  53.     /// <summary>Used for temporary disabling of controls. Helpful for cutscenes/dialog</summary>
  54.     public void Toggle() { this.enabled = !this.enabled; }
  55.     public void RandomColor() { GetComponent<Renderer>().material.color = Random.ColorHSV(); }
  56.     /// <summary>Calculates correct look vectors given a general forward and an agreeable up</summary>
  57.     public static void CalculatePlanarMoveVectors(Vector3 generalForward, Vector3 upVector, out Vector3 forward, out Vector3 right) {
  58.         right = Vector3.Cross(upVector, generalForward).normalized;
  59.         forward = Vector3.Cross(right, upVector).normalized;
  60.     }
  61.     public float DistanceTo(Vector3 loc) {
  62.         return Vector3.Distance(transform.position, loc) - ExpectedHorizontalRadius;
  63.     }
  64.     public float DistanceTo(Transform mob) {
  65.         float otherRad = mob.transform.localScale.z;
  66.         Collider c = mob.GetComponent<Collider>();
  67.         if(c != null) { otherRad *= c.bounds.extents.z; }
  68.         return Vector3.Distance(transform.position, mob.transform.position) - (ExpectedHorizontalRadius + otherRad);
  69.     }
  70.     public Vector3 GetVelocity() { return rb.velocity; }
  71.     /// <summary>if a null pointer happens while trying to access a rigidbody variable, call this first.</summary>
  72.     public void EnsureRigidBody() {
  73.         if(rb == null) {
  74.             rb = GetComponent<Rigidbody>();
  75.             if(!rb) { rb = gameObject.AddComponent<Rigidbody>(); }
  76.             rb.useGravity = false; rb.freezeRotation = true;
  77.         }
  78.     }
  79.     #endregion // Public API
  80.     #region Grounded Camera Control
  81.     public class GroundedInputController : MobileEntity.InputController {
  82.         PlayerControl pc;
  83.  
  84.         public GroundedInputController(MobileEntity.InputController cameraControl) { base.Copy(cameraControl); }
  85.         public override void LateUpdate(MobileEntity me) {
  86.             bool mustUpdateCamera = OrientUp () | ChangeCameraDistanceBasedOnScrollWheel();
  87.             UpdateCamera (me, mustUpdateCamera);
  88.         }
  89.         public override void UpdateCameraAngles(float dx, float dy) {
  90.             if(pc.gravityApplication != GravityState.none) {
  91.                 currentPitch -= dy; // rotate accordingly, minus because of standard 'inverted' Y axis rotation
  92.                 camHandle.Rotate(Vector3.right, -currentPitch);// un-rotate zero-out camera's "up", re-applied soon
  93.                 if(cameraUp == Vector3.zero) return;
  94.                 Vector3 rightSide = (cameraUp != camHandle.forward)?Vector3.Cross(camHandle.forward, cameraUp):camHandle.right;
  95.                 Vector3 unrotatedMoveForward = Vector3.Cross(cameraUp, rightSide);
  96.                 camHandle.rotation = Quaternion.LookRotation(unrotatedMoveForward, cameraUp); // force zero rotation
  97.                 camHandle.Rotate(Vector3.up, dx); // re-apply rotation
  98.                 while(currentPitch > 180) { currentPitch -= 360; } // normalize the angles to be between -180 and 180
  99.                 while(currentPitch < -180) { currentPitch += 360; }
  100.                 currentPitch = Mathf.Clamp(currentPitch, minCameraPitch, maxCameraPitch);
  101.                 camHandle.Rotate(Vector3.right, currentPitch);
  102.             } else {
  103.                 base.UpdateCameraAngles(dx,dy); // simplistic gravity-less rotation
  104.                 cameraUp = camHandle.up;
  105.                 currentPitch = 0;
  106.             }
  107.         }
  108.         public override void Start(MobileEntity me) {
  109.             pc = me as PlayerControl;
  110.             base.Start (me);
  111.             // calculate current pitch based on camera
  112.             Vector3 currentRight = Vector3.Cross(cameraUp, camHandle.forward);
  113.             Vector3 currentMoveForward = Vector3.Cross(currentRight, cameraUp);
  114.             Quaternion playerIdentity = Quaternion.LookRotation(currentMoveForward, cameraUp);
  115.             currentPitch = Quaternion.Angle(playerIdentity, camHandle.rotation);
  116.         }
  117.         public override Vector3 UpdateMoveDirectionBasedOnInput (MobileEntity me) {
  118.             float input_forward = Input.GetAxis(forwardAxisName);
  119.             float input_right = Input.GetAxis(rightAxisName);
  120.             Vector3 MoveDirection = Vector3.zero;
  121.             if (input_forward != 0 || input_right != 0) {
  122.                 Vector3 currentRight, currentMoveForward;
  123.                 if (pc.gravityApplication == GravityState.useGravity) {
  124.                     GetMoveVectors (pc.GroundNormal /*isStableOnGround?GroundNormal:GetUpOrientation()*/, out currentMoveForward, out currentRight);
  125.                 } else {
  126.                     currentMoveForward = camHandle.forward; currentRight = camHandle.right;
  127.                 }
  128.                 MoveDirection = (currentRight * input_right) + (currentMoveForward * input_forward);
  129.                 MoveDirection.Normalize ();
  130.             } else if (AutoSlowdown) {
  131.                 me.IsBrakeOn = true;
  132.             }
  133.             return MoveDirection;
  134.         }
  135.         public override void GetMoveVectors(Vector3 upVector, out Vector3 forward, out Vector3 right) {
  136.             Transform importantT = myCamera.transform;
  137.             if(pc.gravityApplication != GravityState.none) {
  138.                 Vector3 generalDir = importantT.forward;
  139.                 if(importantT.forward == upVector) { generalDir = -importantT.up; } else if(importantT.forward == -upVector) { generalDir = importantT.up; }
  140.                 CalculatePlanarMoveVectors(generalDir, upVector, out forward, out right);
  141.                 if(currentPitch > 90) { right *= -1; forward *= -1; } // if we're upside-down, flip to keep it consistent
  142.             } else {
  143.                 right = importantT.right; forward = importantT.forward;
  144.             }
  145.         }
  146.         public void GetMoveVectors(out Vector3 forward, out Vector3 right) { GetMoveVectors(pc.GroundNormal, out forward, out right); }
  147.         private bool OrientUp() {
  148.             if (pc.gravityApplication != GravityState.none && cameraUp != -pc.gravity.dir) {
  149.                 Vector3 delta = -pc.gravity.dir - cameraUp;
  150.                 float upDifference = delta.magnitude;
  151.                 float movespeed = Time.deltaTime * pc.MoveSpeed / 2;
  152.                 if (upDifference < movespeed) {
  153.                     cameraUp = -pc.gravity.dir;
  154.                 } else {
  155.                     cameraUp += delta * movespeed;
  156.                 }
  157.                 return true;
  158.             }
  159.             return false;
  160.         }
  161.     }
  162.     #endregion // Grounded Camera Control
  163.     #region Steering Behaviors
  164.     protected override Vector3 CalculatePositionForAutomaticMovement(Vector3 position) {
  165.         if (gravityApplication == GravityState.useGravity && IsStableOnGround) {
  166.             Vector3 delta = position - transform.position;
  167.             float notOnGround = Vector3.Dot (GroundNormal, delta);
  168.             delta -= GroundNormal * notOnGround;
  169.             if (delta == Vector3.zero) {
  170.                 return Vector3.zero;
  171.             }
  172.             position = transform.position + delta;
  173.         }
  174.         return position;
  175.     }
  176.     #endregion
  177.     #region Core Controller Logic
  178.     /// <summary>used when the player controller needs to stay on a moving platform or vehicle</summary>
  179.     private Transform transformOnPlatform;
  180.     /// <summary>true if player is walking on ground that is too steep.</summary>
  181.     public bool tooSteep { get; protected set; }
  182.     /// <summary>where the 'leg' raycast comes down from. changes if the leg can't find footing.</summary>
  183.     private Vector3 legOffset;
  184.     /// <summary>cache the data structure with the object, to improve performance</summary>
  185.     private RaycastHit rayDownHit = new RaycastHit();
  186.  
  187.     void StandLogic() {
  188.         Vector3 hip = (legOffset!=Vector3.zero) ? transform.TransformPoint(legOffset) : transform.position;
  189.         const float epsilon = 0.0625f;
  190.         float lengthOfLeg = ExpectedHeightFromGround+ExpectedHorizontalRadius*5;
  191.         // use a raycast to check if we're on the ground
  192.         CollideBox.enabled = false;
  193.         // if we're grounded enough (close enough to the ground)
  194.         if(Physics.Raycast(hip, /*gravity.dir*/ -transform.up, out rayDownHit, lengthOfLeg)) {
  195.             // record some important details about the space we're standing at
  196.             GroundNormal = rayDownHit.normal;
  197.             StandAngle = Vector3.Angle(GetUpOrientation(), GroundNormal);
  198.             tooSteep = (StandAngle > MaxWalkAngle);
  199.             HeightFromGround = rayDownHit.distance;
  200.             if(IsCollidingWithGround || HeightFromGround < ExpectedHeightFromGround + epsilon) {
  201.                 IsStableOnGround = true;
  202.                 StandingOnObject = rayDownHit.collider.gameObject;
  203.                 switch(stickToGround){
  204.                 case HowToDealWithGround.withPlaceholder:
  205.                     if (transformOnPlatform == null) {
  206.                         transformOnPlatform = (new GameObject ("<where " + name + " stands>")).transform;
  207.                     }
  208.                     transformOnPlatform.SetParent (StandingOnObject.transform);
  209.                     break;
  210.                 case HowToDealWithGround.withParenting:
  211.                     transform.SetParent (StandingOnObject.transform);
  212.                     break;
  213.                 }
  214.                 legOffset = Vector3.zero;
  215.                 float downwardV = Vector3.Dot(rb.velocity, gravity.dir); // check if we're falling.
  216.                 if(downwardV > 0) {
  217.                     rb.velocity = rb.velocity - downwardV * gravity.dir; // stop falling, we're on the ground.
  218.                 }
  219.                 if(HeightFromGround < ExpectedHeightFromGround - epsilon) {
  220.                     rb.velocity += GetUpOrientation() * epsilon; // if we're not standing tall enough, edge up a little.
  221.                 }
  222.             } else {
  223.                 IsStableOnGround = false;
  224.             }
  225.         } else {
  226.             HeightFromGround = float.PositiveInfinity;
  227.             IsStableOnGround = false;
  228.         }
  229.         if(!IsStableOnGround) { // if we're not grounded enough, mark it
  230.             IsStableOnGround = false;
  231.             StandingOnObject = null;
  232.             tooSteep = false;
  233.             switch(stickToGround){
  234.             case HowToDealWithGround.withPlaceholder:
  235.                 if (transformOnPlatform != null) {
  236.                     transformOnPlatform.SetParent (null);
  237.                 }
  238.                 break;
  239.             }
  240.             // if we couldn't find ground with our leg here, try another location.
  241.             legOffset = new Vector3(
  242.                 Random.Range(-1.0f, 1.0f) * ExpectedHorizontalRadius, 0,
  243.                 Random.Range(-1.0f, 1.0f) * ExpectedHorizontalRadius);
  244.         }
  245.         CollideBox.enabled = true;
  246.     }
  247.  
  248.     public Vector3 NormalizeDirectionTo(Vector3 direction, Vector3 groundNormal) {
  249.         float amountOfVertical = Vector3.Dot (direction, groundNormal);
  250.         direction -= groundNormal * amountOfVertical;
  251.         if (direction != Vector3.zero) {  direction.Normalize (); }
  252.         return direction;
  253.     }
  254.     protected override void ApplyMove(float acceleration) {
  255.         // validate that the agent should be able to walk at this angle
  256.         float walkAngle = 90 - Vector3.Angle(GetUpOrientation(), rb.velocity);
  257.         if(walkAngle > MaxWalkAngle || StandAngle > MaxWalkAngle) { IsStableOnGround = false; }
  258.         // only apply gravity if the agent can't walk. TODO take mass into acount for gravity calc?
  259.         if(!IsStableOnGround) { rb.velocity += gravity.GetGravityPower() * gravity.dir * Time.deltaTime; }
  260.         VerticalVelocity = Vector3.Dot(rb.velocity, gravity.dir); // remember vertical velocity (for jumping!)
  261.         // prevent moving up hill if the hill is too steep (videogame logic!)
  262.         if(tooSteep && IsMovingIntentionally) {
  263.             Vector3 acrossSlope = Vector3.Cross(GroundNormal, -gravity.dir).normalized;
  264.             Vector3 intoSlope = Vector3.Cross(acrossSlope, -gravity.dir).normalized;
  265.             float upHillAmount = Vector3.Dot(MoveDirection, intoSlope);
  266.             if(upHillAmount > 0) {
  267.                 MoveDirection -= intoSlope * upHillAmount;
  268.                 MoveDirection.Normalize ();
  269.             }
  270.         }
  271.         base.ApplyMove(acceleration);
  272.         if(!IsStableOnGround) {
  273.             rb.velocity -= gravity.dir * Vector3.Dot(rb.velocity, gravity.dir); // subtract donward-force from move speed
  274.             rb.velocity += gravity.dir * VerticalVelocity; // re-apply vertical velocity from gravity/jumping
  275.         }
  276.     }
  277.     public override void MoveLogic() {
  278.         if(PlayerControlled) {
  279.             MoveDirection = inputController.UpdateMoveDirectionBasedOnInput (this);
  280.         }
  281.         if (IsBrakeOn && IsStableOnGround) {
  282.             ApplyBrakes (Acceleration);
  283.         }
  284.         IsMovingIntentionally = MoveDirection != Vector3.zero;
  285.         if(gravityApplication == GravityState.useGravity) {
  286.             if (IsMovingIntentionally) {
  287.                 // keeps motion aligned to ground, also makes it easy to put on the brakes
  288.                 MoveDirection = NormalizeDirectionTo (MoveDirection, GroundNormal);
  289.                 IsMovingIntentionally = MoveDirection != Vector3.zero;
  290.             }
  291.             ApplyMove (Acceleration);
  292.             jump.Update(this);
  293.         } else {
  294.             base.ApplyMove(Acceleration);
  295.         }
  296.         // turn body as needed
  297.         if(TurnSpeed != 0) { UpdateFacing (); }
  298.     }
  299.     protected void UpdateFacing() {
  300.         Vector3 rG, correctUp;
  301.         if(!IsCollidingWithGround && !IsStableOnGround && float.IsInfinity(HeightFromGround)) {
  302.             correctUp = GetUpOrientation();
  303.         } else {
  304.             correctUp = (GroundNormal!=Vector3.zero)?GroundNormal:transform.up;
  305.         }
  306.         // turn IF needed
  307.         if ((LookDirection != Vector3.zero && LookDirection != transform.forward) ||
  308.             (correctUp != transform.up)) {
  309.             if (PlayerControlled) {
  310.                 inputController.GetMoveVectors (correctUp, out LookDirection, out rG);
  311.                 correctUp = inputController.cameraUp;
  312.             } else {
  313.                 Vector3 r = Vector3.Cross (LookDirection, correctUp);
  314.                 correctUp = Vector3.Cross (r, LookDirection);
  315.             }
  316.             TurnToFace (LookDirection, correctUp);
  317.         }
  318.     }
  319.     protected override void EnforceSpeedLimit() {
  320.         if(gravityApplication == GravityState.useGravity) {
  321.             float actualSpeed = Vector3.Dot(MoveDirection, rb.velocity);
  322.             if(actualSpeed > MoveSpeed) {
  323.                 rb.velocity -= MoveDirection * actualSpeed;
  324.                 rb.velocity += MoveDirection * MoveSpeed;
  325.             }
  326.         } else {
  327.             base.EnforceSpeedLimit();
  328.         }
  329.     }
  330.     #endregion // Core Controller Logic
  331.     #region Jumping
  332.     public Jumping jump = new Jumping();
  333.  
  334.     [System.Serializable]
  335.     public class Jumping {
  336.         public float minJumpHeight = 0.25f, maxJumpHeight = 1;
  337.         [Tooltip("How long the jump button must be pressed to jump the maximum height")]
  338.         public float fullJumpPressDuration = 0.5f;
  339.         [Tooltip("for double-jumping, put a 2 here. To eliminate jumping, put a 0 here.")]
  340.         public int maxJumps = 1;
  341.         [HideInInspector]
  342.         private float currentJumpVelocity, heightReached, heightReachedTotal, timeHeld, holdJumpPlz, targetHeight;
  343.         private bool impulseActive, input, inputHeld, peaked = false;
  344.         [Tooltip("if false, double jumps won't 'restart' a jump, just add jump velocity")]
  345.         private bool jumpStartResetsVerticalMotion = true;
  346.         public int jumpsSoFar { get; protected set; }
  347.         /// <returns>if this instance is trying to jump</returns>
  348.         public bool IsJumping() { return inputHeld; }
  349.         /// <summary>pretends to hold the jump button for the specified duration</summary>
  350.         /// <param name="jumpHeldDuration">Jump held duration.</param>
  351.         public void ScriptedJump(float jumpHeldDuration) { holdJumpPlz = jumpHeldDuration; }
  352.         public void Update(PlayerControl p) {
  353.             if(p.PlayerControlled) {
  354.                 input = Input.GetButtonDown("Jump");
  355.                 inputHeld = Input.GetButton("Jump");
  356.                 if(holdJumpPlz > 0) {
  357.                     inputHeld = true;
  358.                     holdJumpPlz -= Time.deltaTime;
  359.                 }
  360.             }
  361.             if(impulseActive && !inputHeld) { impulseActive = false; }
  362.             if(!input && !inputHeld) return;
  363.             // check stable footing for the jump
  364.             if(p.IsStableOnGround) {
  365.                 jumpsSoFar = 0;
  366.                 heightReached = 0;
  367.                 currentJumpVelocity = 0;
  368.                 timeHeld = 0;
  369.             }
  370.             // calculate the jump
  371.             float gForce = p.gravity.GetGravityPower();
  372.             Vector3 jump_force = Vector3.zero, jumpDirection = -p.gravity.dir;
  373.             // if the user wants to jump, and is allowed to jump again
  374.             if(!impulseActive && (jumpsSoFar < maxJumps)) {
  375.                 heightReached = 0;
  376.                 timeHeld = 0;
  377.                 jumpsSoFar++;
  378.                 targetHeight = minJumpHeight;
  379.                 float velocityRequiredToJump = Mathf.Sqrt(targetHeight * 2 * gForce);
  380.                 // cancel out current jump/fall forces
  381.                 if(jumpStartResetsVerticalMotion) {
  382.                     float motionInVerticalDirection = Vector3.Dot(jumpDirection, p.rb.velocity);
  383.                     jump_force -= (motionInVerticalDirection * jumpDirection) / Time.deltaTime;
  384.                 }
  385.                 // apply proper jump force
  386.                 currentJumpVelocity = velocityRequiredToJump;
  387.                 peaked = false;
  388.                 jump_force += (jumpDirection * currentJumpVelocity) / Time.deltaTime;
  389.                 impulseActive = true;
  390.             } else
  391.                 // if a jump is happening      
  392.                 if(currentJumpVelocity > 0) {
  393.                 // handle jump height: the longer you hold jump, the higher you jump
  394.                 if(inputHeld) {
  395.                     timeHeld += Time.deltaTime;
  396.                     if(timeHeld >= fullJumpPressDuration) {
  397.                         targetHeight = maxJumpHeight;
  398.                         timeHeld = fullJumpPressDuration;
  399.                     } else {
  400.                         targetHeight = minJumpHeight + ((maxJumpHeight-minJumpHeight) * timeHeld / fullJumpPressDuration);
  401.                     }
  402.                     if(heightReached < targetHeight) {
  403.                         float requiredJumpVelocity = Mathf.Sqrt((targetHeight - heightReached) * 2 * gForce);
  404.                         float forceNeeded = requiredJumpVelocity - currentJumpVelocity;
  405.                         jump_force += (jumpDirection * forceNeeded) / Time.deltaTime;
  406.                         currentJumpVelocity = requiredJumpVelocity;
  407.                     }
  408.                 }
  409.             } else {
  410.                 impulseActive = false;
  411.             }
  412.             if(currentJumpVelocity > 0) {
  413.                 float moved = currentJumpVelocity * Time.deltaTime;
  414.                 heightReached += moved;
  415.                 heightReachedTotal += moved;
  416.                 currentJumpVelocity -= gForce * Time.deltaTime;
  417.             } else if(!peaked && !p.IsStableOnGround) {
  418.                 peaked = true;
  419.                 impulseActive = false;
  420.             }
  421.             p.rb.AddForce(jump_force);
  422.         }
  423.     }
  424.     #endregion // Jumping
  425.     #region Gravity
  426.     public Gravity gravity = new Gravity();
  427.  
  428.     [System.Serializable]
  429.     public class Gravity {
  430.         [Tooltip("'down' direction for the player, which pulls the player")]
  431.         public Vector3 dir = -Vector3.up;
  432.         [SerializeField]
  433.         public float power = 9.81f;
  434.         public float GetGravityPower() { return power; }
  435.     }
  436.     #endregion // Gravity
  437.     #region MonoBehaviour
  438.     void Start() {
  439.         GroundNormal = Vector3.up;
  440.         EnsureRigidBody();
  441.         if(PlayerControlled) {
  442.             if (!(inputController is GroundedInputController)) {
  443.                 inputController = new GroundedInputController (inputController);
  444.             }
  445.             inputController.Start(this);
  446.             if(transform.tag == "Untagged" || transform.tag.Length == 0) { transform.tag = "Player"; }
  447.         }
  448.         CollideBox = GetComponent<Collider>();
  449.         if(CollideBox == null) { CollideBox = gameObject.AddComponent<CapsuleCollider>(); }
  450.         ExpectedHeightFromGround = CollideBox.bounds.extents.y;
  451.         if(CollideBox is CapsuleCollider) {
  452.             CapsuleCollider cc = CollideBox as CapsuleCollider;
  453.             ExpectedHorizontalRadius = cc.radius;
  454.         } else {
  455.             Vector3 ex = CollideBox.bounds.extents;
  456.             ExpectedHorizontalRadius = Mathf.Max(ex.x, ex.z);
  457.         }
  458.     }
  459.     /// <summary>where physics-related changes happen</summary>
  460.     void FixedUpdate() {
  461.         if(gravityApplication != GravityState.none) { StandLogic(); }
  462.         MoveLogic();
  463.         // keep track of where we are in relation to the parent platform object
  464.         if(gravityApplication != GravityState.none && transformOnPlatform && transformOnPlatform.parent != null &&
  465.             stickToGround == HowToDealWithGround.withPlaceholder) {
  466.             transformOnPlatform.position = transform.position;
  467.         }
  468.         UpdateStateIndicators ();
  469.     }
  470.     /// <summary>where visual-related updates should happen</summary>
  471.     void LateUpdate() {
  472.         // if on a platform, update position on the platform based on velocity
  473.         if(IsStableOnGround && transformOnPlatform != null &&
  474.             stickToGround == HowToDealWithGround.withPlaceholder) {
  475.             transform.position = transformOnPlatform.position + rb.velocity * Time.deltaTime;
  476.         }
  477.         if(PlayerControlled) { inputController.LateUpdate(this); }
  478.     }
  479.     void OnCollisionStay(Collision c) {
  480.         if(c.gameObject == StandingOnObject) {
  481.             if(!IsStableOnGround) { IsCollidingWithGround = true; } else { GroundNormal = c.contacts[0].normal; }
  482.         }
  483.     }
  484.     void OnCollisionExit(Collision c) {
  485.         if(c.gameObject == StandingOnObject) {
  486.             if(IsStableOnGround || IsCollidingWithGround) {
  487.                 IsCollidingWithGround = false;
  488.                 IsStableOnGround = false;
  489.             }
  490.         }
  491.     }
  492.     #endregion // MonoBehaviour
  493. }
  494.  
  495. /// a basic Mobile Entity controller (for MOBs, like seeking fireballs, or very basic enemies)
  496. public class MobileEntity : MonoBehaviour {
  497.     /// <summary>if being controlled by player, this value is constanly reset by user input. otherwise, useable as AI controls.</summary>
  498.     [HideInInspector]
  499.     public Vector3 MoveDirection, LookDirection;
  500.     [Tooltip("movement speed")]
  501.     public float MoveSpeed = 5;
  502.     [Tooltip("how quickly the PlayerControl transform rotates to match the intended direction, measured in degrees-per-second")]
  503.     public float TurnSpeed = 180;
  504.     [Tooltip("how quickly the PlayerControl reaches MoveSpeed. If less-than zero, jump straight to move speed.")]
  505.     public float Acceleration = -1;
  506.     [Tooltip("if true, mouse controls and WASD function to move around the player. Otherwise, uses Non Player Control settings")]
  507.     public bool PlayerControlled = true;
  508.     /// <summary>the player's RigidBody, which lets Unity do all the physics for us</summary>
  509.     [HideInInspector]
  510.     public Rigidbody rb { get; protected set; }
  511.  
  512.     /// <summary>cached variables required for a host of calculations</summary>
  513.     public float CurrentSpeed { get; protected set; }
  514.     public float BrakeDistance { get; protected set; }
  515.     [HideInInspector]
  516.     public bool IsBrakeOn = false;
  517.     protected bool brakesOnLastFrame = false;
  518.     public bool IsMovingIntentionally { get; protected set; }
  519.  
  520.     #region Camera Control
  521.     public InputController inputController;
  522.  
  523.     [System.Serializable]
  524.     public class InputController {
  525.         [Tooltip("Camera for the PlayerControl to use. Will automagically find one if not set.")]
  526.         public Camera myCamera;
  527.         /// <summary>the transform controlling where the camera should go. Might be different than myCamera if a VR headset is plugged in.</summary>
  528.         [HideInInspector]
  529.         public Transform camHandle;
  530.         /// <summary>how the 3D camera should move with the player.</summary>
  531.         [Tooltip("how the 3D camera should move with the player\n"+
  532.             "* Fixed Camera: other code should control the camera\n"+
  533.             "* Lock To Player: follow player with current offset\n"+
  534.             "* Rotate 3rd Person: 3rd person, scrollwheel zoom\n"+
  535.             "* Lock-and-Rotate-with-RMB: like the Unity editor Scene view")]
  536.         public ControlStyle controlMode = ControlStyle.lockAndRotateWithRMB;
  537.         public enum ControlStyle { fixedCamera, lockToPlayer, rotate3rdPerson, lockAndRotateWithRMB }
  538.         /// <summary>how far away the camera should be from the player</summary>
  539.         protected Vector3 cameraOffset;
  540.         [Tooltip("how far the camera should be from the PlayerControl transform")]
  541.         public float cameraDistance;
  542.         public float horizontalMouselookSpeed = 5, verticalMouselookSpeed = 5;
  543.         [Tooltip("If true, a raycast is sent to make sure the camera doesn't clip through solid objects.")]
  544.         public bool cameraWontClip = true;
  545.         [Tooltip("if true, automatically bring velocity to zero if there is no user-input")]
  546.         public bool AutoSlowdown = true;
  547.         [Tooltip("Axis used for moving (see: Edit->Project Settings->Input")]
  548.         public string forwardAxisName = "Vertical", rightAxisName = "Horizontal";
  549.         [Tooltip("Axis used for turning (see: Edit->Project Settings->Input")]
  550.         public string turnHorizontalAxisName = "Mouse X", turnVerticalAxisName = "Mouse Y";
  551.         public string zoomInAndOutAxisName = "Mouse ScrollWheel";
  552.         [Tooltip("how far the eye-focus (where the camera rests) should be above the PlayerControl transform")]
  553.         public float eyeHeight = 0.125f;
  554.         [HideInInspector]
  555.         public float currentPitch { get; protected set; }
  556.         /// <summary>keeps the camera from looking too far (only effective with gravity)</summary>
  557.         [Tooltip("keeps the camera from looking too far (only effective with gravity)")]
  558.         public float maxCameraPitch = 90, minCameraPitch = -90;
  559.         /// <summary>'up' direction for the player, used to orient the camera when there is gravity</summary>
  560.         [HideInInspector]
  561.         public Vector3 cameraUp = Vector3.up;
  562.         /// <summary>the vertical tilt of the camera</summary>
  563.         public Vector3 CameraCenter(Transform t) { return (eyeHeight == 0)? t.position : t.position + t.up * eyeHeight; }
  564.  
  565.         public void Copy(InputController c) {
  566.             myCamera = c.myCamera;
  567.             camHandle = c.camHandle;
  568.             controlMode = c.controlMode;
  569.             cameraDistance = c.cameraDistance;
  570.             horizontalMouselookSpeed = c.horizontalMouselookSpeed;
  571.             verticalMouselookSpeed = c.verticalMouselookSpeed;
  572.             cameraWontClip = c.cameraWontClip;
  573.         }
  574.         public virtual void UpdateCameraAngles(float dx, float dy) {
  575.             // simplistic gravity-less rotation
  576.             camHandle.Rotate(Vector3.up, dx);
  577.             camHandle.Rotate(Vector3.right, -dy);
  578.         }
  579.         protected void EnsureCamera(MobileEntity p) {
  580.             if (!myCamera) { // make sure there is a camera to control!
  581.                 myCamera = Camera.main;
  582.                 if (myCamera == null) {
  583.                     myCamera = (new GameObject ("<main camera>")).AddComponent<Camera> ();
  584.                     myCamera.tag = "MainCamera";
  585.                 }
  586.             } else {
  587.                 cameraOffset = camHandle.position - p.transform.position;
  588.                 cameraDistance = cameraOffset.magnitude;
  589.             }
  590.             if(UnityEngine.VR.VRDevice.isPresent) {
  591.                 camHandle = (new GameObject("<camera handle>")).transform;
  592.                 myCamera.transform.position = Vector3.zero;
  593.                 myCamera.transform.SetParent(camHandle);
  594.             } else {
  595.                 camHandle = myCamera.transform;
  596.             }
  597.             UpdateCamera (p, true);
  598.         }
  599.         public virtual void Start(MobileEntity p) {
  600.             EnsureCamera (p);
  601.         }
  602.         public bool ChangeCameraDistanceBasedOnScrollWheel() {
  603.             float scroll = Input.GetAxis(zoomInAndOutAxisName);
  604.             if(scroll != 0) {
  605.                 cameraDistance -= scroll * 10;
  606.                 cameraDistance = Mathf.Max(0, cameraDistance);
  607.                 if(cameraDistance > 0 && cameraOffset != Vector3.zero) {
  608.                     cameraOffset = cameraOffset.normalized * cameraDistance;
  609.                 }
  610.                 return true;
  611.             }
  612.             return false;
  613.         }
  614.         public virtual void LateUpdate(MobileEntity me) {
  615.             bool mustUpdateCamera = ChangeCameraDistanceBasedOnScrollWheel();
  616.             UpdateCamera (me, mustUpdateCamera);
  617.         }
  618.         public virtual void UpdateCamera(MobileEntity me, bool mustUpdate) {
  619.             if(controlMode == ControlStyle.fixedCamera && !mustUpdate) { return; }
  620.             bool updatingWithMouseInput = (controlMode == ControlStyle.rotate3rdPerson) ||
  621.                 (controlMode == ControlStyle.lockAndRotateWithRMB && Input.GetMouseButton(1));
  622.             // camera rotation
  623.             if (updatingWithMouseInput) {
  624.                 // get the rotations that the user input is indicating
  625.                 UpdateCameraAngles (Input.GetAxis (turnHorizontalAxisName) * horizontalMouselookSpeed, Input.GetAxis (turnVerticalAxisName) * verticalMouselookSpeed);
  626.             } else if (mustUpdate) {
  627.                 UpdateCameraAngles (0, 0);
  628.             }
  629.             me.LookDirection = camHandle.forward;
  630.             Vector3 eyeFocus = CameraCenter(me.transform);
  631.             // move the camera to be looking at the player's eyes/head, ideally with no geometry in the way
  632.             RaycastHit rh;
  633.             float calculatedDistForCamera = cameraDistance;
  634.             if(cameraWontClip && Physics.SphereCast(eyeFocus, myCamera.nearClipPlane, -camHandle.forward, out rh, cameraDistance)) {
  635.                 calculatedDistForCamera = rh.distance;
  636.             }
  637.             if(calculatedDistForCamera != 0) { cameraOffset = -myCamera.transform.forward * calculatedDistForCamera; }
  638.             Vector3 nextLocation = eyeFocus + ((cameraDistance > 0) ? cameraOffset : Vector3.zero);
  639.             camHandle.position = nextLocation;
  640.         }
  641.         public virtual Vector3 UpdateMoveDirectionBasedOnInput(MobileEntity me) {
  642.             float inputF = Input.GetAxis (forwardAxisName), inputR = Input.GetAxis (rightAxisName);
  643.             Vector3 MoveDirection = default(Vector3);
  644.             if (inputF != 0 || inputR != 0) {
  645.                 Transform t = myCamera.transform;
  646.                 MoveDirection = (inputR * t.right) + (inputF * t.forward);
  647.                 MoveDirection.Normalize ();
  648.             } else if (AutoSlowdown) {
  649.                 me.IsBrakeOn = true;
  650.             }
  651.             return MoveDirection;
  652.         }
  653.         public virtual void GetMoveVectors(Vector3 upVector, out Vector3 forward, out Vector3 right) {
  654.             Transform importantT = myCamera.transform;
  655.             right = importantT.right; forward = importantT.forward;
  656.         }
  657.     }
  658.     #endregion // Camera Control
  659.     #region movement and directional control
  660.     public bool IsBraking() { return IsBrakeOn || brakesOnLastFrame; }
  661.     public virtual void MoveLogic() {
  662.         if (PlayerControlled) { MoveDirection = inputController.UpdateMoveDirectionBasedOnInput(this); }
  663.         IsMovingIntentionally = MoveDirection != Vector3.zero;
  664.         if (IsBrakeOn) {
  665.             ApplyBrakes (Acceleration);
  666.         } else {
  667.             ApplyMove (Acceleration);
  668.         }
  669.         if (transform.forward != LookDirection && TurnSpeed != 0) { TurnToFace(LookDirection, Vector3.zero); }  // turn body as needed
  670.     }
  671.  
  672.     protected bool TurnToFace(Vector3 forward, Vector3 up) {
  673.         if((forward != Vector3.zero && transform.forward != forward) || (up != transform.up && up != Vector3.zero)) {
  674.             if(up == Vector3.zero) {
  675.                 Vector3 r = (forward == transform.right) ? -transform.forward : ((forward == -transform.right) ? transform.forward : transform.right);
  676.                 up = Vector3.Cross(forward, r);
  677.             }
  678.             Quaternion target = Quaternion.LookRotation(forward, up);
  679.             Quaternion q = Quaternion.RotateTowards(transform.rotation, target, TurnSpeed * Time.deltaTime);
  680.             transform.rotation = q;
  681.             return true;
  682.         }
  683.         return false;
  684.     }
  685.     protected virtual void EnforceSpeedLimit() {
  686.         float actualSpeed = rb.velocity.magnitude;
  687.         if(actualSpeed > MoveSpeed) {
  688.             rb.velocity = rb.velocity.normalized*MoveSpeed;
  689.         }
  690.     }
  691.     public virtual void ApplyBrakes(float deceleration) {
  692.         if (deceleration > 0) {
  693.             float amountToMove = deceleration * Time.deltaTime;
  694.             MoveDirection = -rb.velocity.normalized;
  695.             float actualSpeed = rb.velocity.magnitude;
  696.             if (actualSpeed > Acceleration * amountToMove) {
  697.                 rb.velocity += MoveDirection * amountToMove;
  698.                 return;
  699.             }
  700.         }
  701.         rb.velocity = Vector3.zero;
  702.     }
  703.     /// <summary>Applies AccelerationDirection to the velocity. if brakesOn, slows things down.</summary>
  704.     protected virtual void ApplyMove(float acceleration) {
  705.         if (acceleration > 0) {
  706.             float amountToMove = acceleration * Time.deltaTime;
  707.             rb.velocity += MoveDirection * amountToMove;
  708.             EnforceSpeedLimit ();
  709.         } else {
  710.             rb.velocity = MoveDirection * MoveSpeed;
  711.         }
  712.     }
  713.     public static float CalculateBrakeDistance(float speed, float acceleration) { return (speed * speed) / (2 * acceleration); }
  714.     #endregion // movement and directional control
  715.     #region Steering Behaviors
  716.     protected Vector3 SeekMath(Vector3 directionToLookToward) {
  717.         Vector3 desiredVelocity = directionToLookToward * MoveSpeed;
  718.         Vector3 desiredChangeToVelocity = desiredVelocity - rb.velocity;
  719.         float desiredChange = desiredChangeToVelocity.magnitude;
  720.         if (desiredChange < Time.deltaTime * Acceleration) { return directionToLookToward; }
  721.         Vector3 steerDirection = desiredChangeToVelocity.normalized;
  722.         return steerDirection;
  723.     }
  724.     /// <summary>used by the Grounded controller, to filter positions into ground-space (for better tracking on curved surfaces)</summary>
  725.     protected virtual Vector3 CalculatePositionForAutomaticMovement(Vector3 position) { return position; }
  726.     /// <summary>called during a FixedUpdate process to give this agent simple AI movement</summary>
  727.     public void CalculateSeek(Vector3 target, out Vector3 directionToMoveToward, ref Vector3 directionToLookToward) {
  728.         Vector3 delta = target - transform.position;
  729.         if (delta == Vector3.zero) { directionToMoveToward = Vector3.zero; return; }
  730.         float desiredDistance = delta.magnitude;
  731.         directionToLookToward = delta / desiredDistance;
  732.         directionToMoveToward = (Acceleration > 0)?SeekMath (directionToLookToward):directionToLookToward;
  733.     }
  734.     /// <summary>called during a FixedUpdate process to give this agent simple AI movement</summary>
  735.     public void CalculateArrive (Vector3 target, out Vector3 directionToMoveToward, ref Vector3 directionToLookToward) {
  736.         Vector3 delta = target - transform.position;
  737.         if (delta == Vector3.zero) { directionToMoveToward = Vector3.zero; return; }
  738.         float desiredDistance = delta.magnitude;
  739.         directionToLookToward = delta / desiredDistance;
  740.         if (desiredDistance > 1.0f / 1024 && desiredDistance > BrakeDistance) {
  741.             directionToMoveToward = SeekMath (directionToLookToward);
  742.             return;
  743.         }
  744.         directionToMoveToward = -directionToLookToward;
  745.         IsBrakeOn = true;
  746.     }
  747.     /// <summary>call this during a FixedUpdate process to give this agent simple AI movement</summary>
  748.     public void Seek(Vector3 target) {
  749.         CalculateSeek (CalculatePositionForAutomaticMovement(target), out MoveDirection, ref LookDirection);
  750.     }
  751.     /// <summary>call this during a FixedUpdate process to give this agent simple AI movement</summary>
  752.     public void Flee(Vector3 target) {
  753.         CalculateSeek (CalculatePositionForAutomaticMovement(target), out MoveDirection, ref LookDirection);
  754.         MoveDirection *= -1;
  755.     }
  756.     /// <summary>call this during a FixedUpdate process to give this agent simple AI movement</summary>
  757.     public void Arrive(Vector3 target) {
  758.         CalculateArrive (CalculatePositionForAutomaticMovement(target), out MoveDirection, ref LookDirection);
  759.     }
  760.     /// <summary>call this during a FixedUpdate process to give this agent simple AI movement</summary>
  761.     public void RandomWalk(float weight = 0, Vector3 weightedTowardPoint = default(Vector3)) {
  762.         if (weight != 0) {
  763.             Vector3 dir = (CalculatePositionForAutomaticMovement (weightedTowardPoint) - transform.position).normalized;
  764.             dir = (dir * weight) + (Random.onUnitSphere * (1 - weight));
  765.             Vector3 p = CalculatePositionForAutomaticMovement (transform.position + transform.forward + dir);
  766.             Vector3 delta = p - transform.position;
  767.             MoveDirection = LookDirection = delta.normalized;
  768.         } else {
  769.             Vector3 p = CalculatePositionForAutomaticMovement (transform.position + transform.forward + Random.onUnitSphere);
  770.             Vector3 delta = p - transform.position;
  771.             MoveDirection = LookDirection = delta.normalized;
  772.         }
  773.     }
  774.     #endregion // Steering Behaviors
  775.     #region MonoBehaviour
  776.     void Start() {
  777.         rb = GetComponent<Rigidbody>();
  778.         if(!rb) { rb = gameObject.AddComponent<Rigidbody>(); }
  779.         rb.useGravity = false; rb.freezeRotation = true;
  780.         if(PlayerControlled) {
  781.             if (inputController == null) inputController = new InputController ();
  782.             inputController.Start(this);
  783.             if(transform.tag == "Untagged" || transform.tag.Length == 0) { transform.tag = "Player"; }
  784.         }
  785.     }
  786.     public void UpdateStateIndicators() {
  787.         CurrentSpeed = rb.velocity.magnitude;
  788.         BrakeDistance = (Acceleration > 0)?CalculateBrakeDistance (CurrentSpeed,Acceleration/rb.mass)-(CurrentSpeed*Time.deltaTime):0;
  789.         if (IsBrakeOn) {
  790.             brakesOnLastFrame = true;
  791.             IsBrakeOn = false;
  792.         } else {
  793.             brakesOnLastFrame = false;
  794.         }
  795.     }
  796.     /// <summary>where physics-related changes happen</summary>
  797.     void FixedUpdate() {
  798.         MoveLogic();
  799.         UpdateStateIndicators ();
  800.     }
  801.     /// <summary>where visual-related updates should happen</summary>
  802.     void LateUpdate() { if(PlayerControlled) { inputController.LateUpdate(this); } }
  803.     #endregion MonoBehaviour
  804. }
RAW Paste Data
Top