G2A Many GEOs
SHARE
TWEET

PlayerControl.cs

mvaganov Oct 12th, 2016 (edited) 157 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
Ledger Nano X - The secure hardware wallet
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
Top