daily pastebin goal
69%
SHARE
TWEET

MovingEntity.cs

mvaganov Jun 14th, 2017 (edited) 239 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. using UnityEngine;
  2.  
  3. /// <summary>A custom Unity3D character controller, useful for player characters and AI characters
  4. /// Latest version at: https://pastebin.com/xFUD4tk2
  5. /// The complementary MOvingEntity_CameraInput script is at: https://pastebin.com/pC0Ddjsi </summary>
  6. /// <description>MIT License - TL;DR - This code is free, don't bother me about it!</description>
  7. /// <author email="mvaganov@hotmail.com">Michael Vaganov</author>
  8. public class MovingEntity : MovingEntityBase {
  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.     private bool wasStableOnGroundLastFrame;
  17.     public enum GravityState { none, useGravity, noFalling }
  18.     [Tooltip("if false, disables gravity's effects, and enables flying")]
  19.     public GravityState gravityApplication = GravityState.useGravity;
  20.     /// <summary>the ground-plane normal, used to determine which direction 'forward' is when it comes to movement on the ground</summary>
  21.     public enum HowToDealWithGround {none, withPlaceholder, withParenting}
  22.     [Tooltip("how the player should stay stable if the ground is moving\n"+
  23.         "* none: don't bother trying to be stable\n"+
  24.         "* withPlaceholder: placeholder transform keeps track\n"+
  25.         "* withParenting: player is parented to ground (not safe if ground is scaled)\n")]
  26.     public HowToDealWithGround stickToGround = HowToDealWithGround.withPlaceholder;
  27.     [HideInInspector]
  28.     public Vector3 GroundNormal { get; protected set; }
  29.     [HideInInspector]
  30.     /// <summary>What object is being stood on</summary>
  31.     public GameObject StandingOnObject { get; protected set; }
  32.     /// <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>
  33.     [HideInInspector]
  34.     public float StandAngle { get; protected set; }
  35.     [Tooltip("The maximum angle the player can move forward at while standing on the ground")]
  36.     public float MaxWalkAngle = 45;
  37.     /// <summary>if very far from ground, this value is infinity</summary>
  38.     [HideInInspector]
  39.     public float HeightFromGround { get; protected set; }
  40.     [HideInInspector]
  41.     public Collider CollideBox { get; protected set; }
  42.     /// <summary>expected distance from the center of the collider to the ground. auto-populates based on Box of Capsule collider</summary>
  43.     [HideInInspector]
  44.     public float ExpectedHeightFromGround;
  45.     /// <summary>expected distance from the center of the collider to a horizontal edge. auto-populates based on Box of Capsule collider</summary>
  46.     [HideInInspector]
  47.     public float ExpectedHorizontalRadius;
  48.     /// <summary>how much downward velocity the player is experiencing. non-zero when jumping or falling.</summary>
  49.     [HideInInspector]
  50.     public float VerticalVelocity { get; protected set; }
  51.  
  52.     public Vector3 GetUpOrientation() { return -gravity.dir; }
  53.     public Vector3 GetUpBodyDirection() { return GroundNormal; }
  54.     /// <summary>Used for temporary disabling of controls. Helpful for cutscenes/dialog</summary>
  55.     public void Toggle() { this.enabled = !this.enabled; }
  56.     public void RandomColor() { GetComponent<Renderer>().material.color = Random.ColorHSV(); }
  57.     /// <summary>Calculates correct look vectors given a general forward and an agreeable up</summary>
  58.     public static void CalculatePlanarMoveVectors(Vector3 generalForward, Vector3 upVector, out Vector3 forward, out Vector3 right) {
  59.         right = Vector3.Cross(upVector, generalForward).normalized;
  60.         forward = Vector3.Cross(right, upVector).normalized;
  61.     }
  62.     public float DistanceTo(Vector3 loc) {
  63.         return Vector3.Distance(transform.position, loc) - ExpectedHorizontalRadius;
  64.     }
  65.     public float DistanceTo(Transform mob) {
  66.         float otherRad = mob.transform.localScale.z;
  67.         Collider c = mob.GetComponent<Collider>();
  68.         if(c != null) { otherRad *= c.bounds.extents.z; }
  69.         return Vector3.Distance(transform.position, mob.transform.position) - (ExpectedHorizontalRadius + otherRad);
  70.     }
  71.     public Vector3 GetVelocity() { return rb.velocity; }
  72.     /// <summary>if a null pointer happens while trying to access a rigidbody variable, call this first.</summary>
  73.     public void EnsureRigidBody() {
  74.         if(rb == null) {
  75.             rb = GetComponent<Rigidbody>();
  76.             if(!rb) { rb = gameObject.AddComponent<Rigidbody>(); }
  77.             rb.useGravity = false; rb.freezeRotation = true;
  78.         }
  79.     }
  80.     #endregion // Public API
  81.     #region Core Controller Logic
  82.     /// <summary>used when the player controller needs to stay on a moving platform or vehicle</summary>
  83.     private Transform transformOnPlatform;
  84.     /// <summary>true if player is walking on ground that is too steep.</summary>
  85.     public bool tooSteep { get; protected set; }
  86.     [HideInInspector]
  87.     public bool AutomaticallyFollowPositionOnPlatform = true;
  88.     /// <summary>where the 'leg' raycast comes down from. changes if the leg can't find footing.</summary>
  89.     private Vector3 legOffset;
  90.     /// <summary>cache the data structure with the object, to improve performance</summary>
  91.     private RaycastHit rayDownHit = new RaycastHit();
  92.     /// <summary>useful to call in LateUpdate for camera</summary>
  93.     public void FollowPositionOnPlatform() {
  94.         // if on a platform, update position on the platform based on velocity
  95.         if(transformOnPlatform != null && stickToGround == HowToDealWithGround.withPlaceholder &&
  96.             (IsStableOnGround || wasStableOnGroundLastFrame)) {
  97.             transform.position = transformOnPlatform.position + rb.velocity * Time.deltaTime;
  98.         }
  99.     }
  100.     void StandLogic() {
  101.         // this might be better done in a LateUpdate for a camera, to make smoother motion
  102.         if(AutomaticallyFollowPositionOnPlatform) { FollowPositionOnPlatform(); }
  103.         wasStableOnGroundLastFrame = IsStableOnGround;
  104.         Vector3 hip = (legOffset!=Vector3.zero) ? transform.TransformPoint(legOffset) : transform.position;
  105.         const float epsilon = 0.0625f;
  106.         float lengthOfLeg = ExpectedHeightFromGround+ExpectedHorizontalRadius * 5;
  107.         // use a raycast to check if we're on the ground
  108.         CollideBox.enabled = false;
  109.         // if we're grounded enough (close enough to the ground)
  110.         if(Physics.Raycast(hip, /*gravity.dir*/ -transform.up, out rayDownHit, lengthOfLeg)) {
  111.             // record some important details about the space we're standing at
  112.             GroundNormal = rayDownHit.normal;
  113.             StandAngle = Vector3.Angle(GetUpOrientation(), GroundNormal);
  114.             tooSteep = (StandAngle > MaxWalkAngle);
  115.             HeightFromGround = rayDownHit.distance;
  116.             if(IsCollidingWithGround || HeightFromGround < ExpectedHeightFromGround + epsilon) {
  117.                 IsStableOnGround = true;
  118.                 StandingOnObject = rayDownHit.collider.gameObject;
  119.                 switch(stickToGround){
  120.                 case HowToDealWithGround.withPlaceholder:
  121.                     if (transformOnPlatform == null) {
  122.                         transformOnPlatform = (new GameObject ("<where " + name + " stands>")).transform;
  123.                     }
  124.                     transformOnPlatform.SetParent (StandingOnObject.transform);
  125.                     break;
  126.                 case HowToDealWithGround.withParenting:
  127.                     transform.SetParent (StandingOnObject.transform);
  128.                     break;
  129.                 }
  130.                 legOffset = Vector3.zero;
  131.                 float downwardV = Vector3.Dot(rb.velocity, gravity.dir); // check if we're falling.
  132.                 if(downwardV > 0) {
  133.                     rb.velocity = rb.velocity - downwardV * gravity.dir; // stop falling, we're on the ground.
  134.                 }
  135.                 if(HeightFromGround < ExpectedHeightFromGround - epsilon) {
  136.                     rb.velocity += GetUpOrientation() * epsilon; // if we're not standing tall enough, edge up a little.
  137.                 }
  138.             } else {
  139.                 IsStableOnGround = false;
  140.             }
  141.         } else {
  142.             HeightFromGround = float.PositiveInfinity;
  143.             IsStableOnGround = false;
  144.         }
  145.         if(!IsStableOnGround) { // if we're not grounded enough, mark it
  146.             IsStableOnGround = false;
  147.             StandingOnObject = null;
  148.             tooSteep = false;
  149.             switch(stickToGround){
  150.             case HowToDealWithGround.withPlaceholder:
  151.                 if (transformOnPlatform != null) {
  152.                     transformOnPlatform.SetParent (null);
  153.                 }
  154.                 break;
  155.             }
  156.             // if we couldn't find ground with our leg here, try another location.
  157.             legOffset = new Vector3(
  158.                 Random.Range(-1.0f, 1.0f) * ExpectedHorizontalRadius, 0,
  159.                 Random.Range(-1.0f, 1.0f) * ExpectedHorizontalRadius);
  160.         }
  161.         CollideBox.enabled = true;
  162.     }
  163.     public Vector3 NormalizeDirectionTo(Vector3 direction, Vector3 groundNormal) {
  164.         float amountOfVertical = Vector3.Dot (direction, groundNormal);
  165.         direction -= groundNormal * amountOfVertical;
  166.         if (direction != Vector3.zero) {  direction.Normalize (); }
  167.         return direction;
  168.     }
  169.     protected override void ApplyMove(float acceleration) {
  170.         // validate that the agent should be able to walk at this angle
  171.         float walkAngle = 90 - Vector3.Angle(GetUpOrientation(), rb.velocity);
  172.         if(walkAngle > MaxWalkAngle || StandAngle > MaxWalkAngle) { IsStableOnGround = false; }
  173.         // only apply gravity if the agent can't walk.
  174.         if(!IsStableOnGround) { rb.velocity += gravity.GetGravityPower() * gravity.dir * Time.deltaTime; }
  175.         VerticalVelocity = Vector3.Dot(rb.velocity, gravity.dir); // remember vertical velocity (for jumping!)
  176.         // prevent moving up hill if the hill is too steep (videogame logic!)
  177.         if(tooSteep && IsMovingIntentionally) {
  178.             Vector3 acrossSlope = Vector3.Cross(GroundNormal, -gravity.dir).normalized;
  179.             Vector3 intoSlope = Vector3.Cross(acrossSlope, -gravity.dir).normalized;
  180.             float upHillAmount = Vector3.Dot(MoveDirection, intoSlope);
  181.             if(upHillAmount > 0) {
  182.                 MoveDirection -= intoSlope * upHillAmount;
  183.                 MoveDirection.Normalize ();
  184.             }
  185.         }
  186.         base.ApplyMove(acceleration);
  187.         if(!IsStableOnGround) {
  188.             rb.velocity -= gravity.dir * Vector3.Dot(rb.velocity, gravity.dir); // subtract donward-force from move speed
  189.             rb.velocity += gravity.dir * VerticalVelocity; // re-apply vertical velocity from gravity/jumping
  190.         }
  191.     }
  192.     public override void MoveLogic() {
  193.         if (IsBrakeOn && (IsStableOnGround || gravityApplication != GravityState.useGravity)) {
  194.             ApplyBrakes (Acceleration);
  195.         }
  196.         IsMovingIntentionally = MoveDirection != Vector3.zero;
  197.         if(gravityApplication == GravityState.useGravity) {
  198.             if (IsMovingIntentionally) {
  199.                 // keeps motion aligned to ground, also makes it easy to put on the brakes
  200.                 MoveDirection = NormalizeDirectionTo (MoveDirection, GroundNormal);
  201.                 IsMovingIntentionally = MoveDirection != Vector3.zero;
  202.             }
  203.             ApplyMove (Acceleration);
  204.             jump.FixedUpdate(this);
  205.         } else if(!IsBrakeOn) {
  206.             base.ApplyMove(Acceleration);
  207.         }
  208.         // turn body as needed
  209.         if(AutomaticallyTurnToFaceLookDirection && TurnSpeed != 0) { UpdateFacing (); }
  210.     }
  211.     public override void UpdateFacing() {
  212.         Vector3 correctUp;      // TODO make this a member variable, and only assign here if it's zero? or just set it to ground normal?
  213.         if(!IsCollidingWithGround && !IsStableOnGround && float.IsInfinity(HeightFromGround)) {
  214.             correctUp = GetUpOrientation();
  215.         } else {
  216.             correctUp = (GroundNormal != Vector3.zero && gravityApplication != GravityState.none) ? GroundNormal:transform.up;
  217.         }
  218.         // turn IF needed
  219.         if ((LookDirection != Vector3.zero && LookDirection != transform.forward) || (correctUp != transform.up)) {
  220.             Vector3 correctRight, correctForward;
  221.             correctRight = Vector3.Cross (correctUp, LookDirection);
  222.             if (correctRight == Vector3.zero) {
  223.                 correctRight = Vector3.Cross (correctUp, (LookDirection != transform.forward)?transform.forward:transform.up);
  224.             }
  225.             correctForward = Vector3.Cross (correctRight, correctUp).normalized;
  226.             TurnToFace (correctForward, correctUp);
  227.         }
  228.     }
  229.     protected override void EnforceSpeedLimit() {
  230.         if(gravityApplication == GravityState.useGravity) {
  231.             float actualSpeed = Vector3.Dot(MoveDirection, rb.velocity);
  232.             if(actualSpeed > MoveSpeed) {
  233.                 rb.velocity -= MoveDirection * actualSpeed;
  234.                 rb.velocity += MoveDirection * MoveSpeed;
  235.             }
  236.         } else {
  237.             base.EnforceSpeedLimit();
  238.         }
  239.     }
  240.     #endregion // Core Controller Logic
  241.     #region Steering Behaviors
  242.     protected override Vector3 CalculatePositionForAutomaticMovement(Vector3 position) {
  243.         if (gravityApplication == GravityState.useGravity && IsStableOnGround) {
  244.             Vector3 delta = position - transform.position;
  245.             float notOnGround = Vector3.Dot (GroundNormal, delta);
  246.             delta -= GroundNormal * notOnGround;
  247.             if (delta == Vector3.zero) {
  248.                 return Vector3.zero;
  249.             }
  250.             position = transform.position + delta;
  251.         }
  252.         return position;
  253.     }
  254.     #endregion // Steering Behaviors
  255.     #region Jumping
  256.     public Jumping jump = new Jumping();
  257.  
  258.     [System.Serializable]
  259.     public class Jumping {
  260.         public float minJumpHeight = 0.25f, maxJumpHeight = 1;
  261.         [Tooltip("How long the jump button must be pressed to jump the maximum height")]
  262.         public float fullJumpPressDuration = 0.5f;
  263.         [Tooltip("for double-jumping, put a 2 here. To eliminate jumping, put a 0 here.")]
  264.         public int maxJumps = 1;
  265.         [HideInInspector]
  266.         public float SecondsToPressJump;
  267.         protected float currentJumpVelocity, heightReached, heightReachedTotal, timeHeld, targetHeight;
  268.         protected bool impulseActive, inputHeld, peaked = false;
  269.         [Tooltip("if false, double jumps won't 'restart' a jump, just add jump velocity")]
  270.         private bool jumpStartResetsVerticalMotion = true;
  271.         public int jumpsSoFar { get; protected set; }
  272.         /// <returns>if this instance is trying to jump</returns>
  273.         public bool IsJumping() { return inputHeld; }
  274.         /// <summary>pretends to hold the jump button for the specified duration</summary>
  275.         /// <param name="jumpHeldDuration">Jump held duration.</param>
  276.         public void FixedUpdate(MovingEntity p) {
  277.             if (inputHeld = (SecondsToPressJump > 0)) { SecondsToPressJump -= Time.deltaTime; }
  278.             if(impulseActive && !inputHeld) { impulseActive = false; }
  279.             if(!inputHeld) return;
  280.             // check stable footing for the jump
  281.             if(p.IsStableOnGround) {
  282.                 jumpsSoFar = 0;
  283.                 heightReached = 0;
  284.                 currentJumpVelocity = 0;
  285.                 timeHeld = 0;
  286.             }
  287.             // calculate the jump
  288.             float gForce = p.gravity.GetGravityPower() * p.rb.mass;
  289.             Vector3 jump_force = Vector3.zero, jumpDirection = -p.gravity.dir;
  290.             // if the user wants to jump, and is allowed to jump again
  291.             if(!impulseActive && (jumpsSoFar < maxJumps)) {
  292.                 heightReached = 0;
  293.                 timeHeld = 0;
  294.                 jumpsSoFar++;
  295.                 targetHeight = minJumpHeight * p.rb.mass;
  296.                 float velocityRequiredToJump = Mathf.Sqrt(targetHeight * 2 * gForce);
  297.                 // cancel out current jump/fall forces
  298.                 if(jumpStartResetsVerticalMotion) {
  299.                     float motionInVerticalDirection = Vector3.Dot(jumpDirection, p.rb.velocity);
  300.                     jump_force -= (motionInVerticalDirection * jumpDirection) / Time.deltaTime;
  301.                 }
  302.                 // apply proper jump force
  303.                 currentJumpVelocity = velocityRequiredToJump;
  304.                 peaked = false;
  305.                 jump_force += (jumpDirection * currentJumpVelocity) / Time.deltaTime;
  306.                 impulseActive = true;
  307.             } else
  308.             // if a jump is happening      
  309.             if(currentJumpVelocity > 0) {
  310.                 // handle jump height: the longer you hold jump, the higher you jump
  311.                 if(inputHeld) {
  312.                     timeHeld += Time.deltaTime;
  313.                     if(timeHeld >= fullJumpPressDuration) {
  314.                         targetHeight = maxJumpHeight;
  315.                         timeHeld = fullJumpPressDuration;
  316.                     } else {
  317.                         targetHeight = minJumpHeight + ((maxJumpHeight-minJumpHeight) * timeHeld / fullJumpPressDuration);
  318.                         targetHeight *= p.rb.mass;
  319.                     }
  320.                     if(heightReached < targetHeight) {
  321.                         float requiredJumpVelocity = Mathf.Sqrt((targetHeight - heightReached) * 2 * gForce);
  322.                         float forceNeeded = requiredJumpVelocity - currentJumpVelocity;
  323.                         jump_force += (jumpDirection * forceNeeded) / Time.deltaTime;
  324.                         currentJumpVelocity = requiredJumpVelocity;
  325.                     }
  326.                 }
  327.             } else {
  328.                 impulseActive = false;
  329.             }
  330.             if(currentJumpVelocity > 0) {
  331.                 float moved = currentJumpVelocity * Time.deltaTime;
  332.                 heightReached += moved;
  333.                 heightReachedTotal += moved;
  334.                 currentJumpVelocity -= gForce * Time.deltaTime;
  335.             } else if(!peaked && !p.IsStableOnGround) {
  336.                 peaked = true;
  337.                 impulseActive = false;
  338.             }
  339.             p.rb.AddForce(jump_force);
  340.         }
  341.     }
  342.     #endregion // Jumping
  343.     #region Gravity
  344.     public Gravity gravity = new Gravity();
  345.  
  346.     [System.Serializable]
  347.     public class Gravity {
  348.         [Tooltip("'down' direction for the player, which pulls the player")]
  349.         public Vector3 dir = -Vector3.up;
  350.         [SerializeField]
  351.         public float power = 9.81f;
  352.         public float GetGravityPower() { return power; }
  353.     }
  354.     #endregion // Gravity
  355.     #region MonoBehaviour
  356.     void Start() {
  357.         GroundNormal = Vector3.up;
  358.         EnsureRigidBody();
  359.         CollideBox = GetComponent<Collider>();
  360.         if(CollideBox == null) { CollideBox = gameObject.AddComponent<CapsuleCollider>(); }
  361.         ExpectedHeightFromGround = CollideBox.bounds.extents.y;
  362.         if(CollideBox is CapsuleCollider) {
  363.             CapsuleCollider cc = CollideBox as CapsuleCollider;
  364.             ExpectedHorizontalRadius = cc.radius;
  365.         } else {
  366.             Vector3 ex = CollideBox.bounds.extents;
  367.             ExpectedHorizontalRadius = Mathf.Max(ex.x, ex.z);
  368.         }
  369.     }
  370.     /// <summary>where physics-related changes happen</summary>
  371.     void FixedUpdate() {
  372.         if(gravityApplication != GravityState.none) { StandLogic(); }
  373.         MoveLogic();
  374.         // keep track of where we are in relation to the parent platform object
  375.         if(gravityApplication != GravityState.none && transformOnPlatform && transformOnPlatform.parent != null &&
  376.             stickToGround == HowToDealWithGround.withPlaceholder) {
  377.             transformOnPlatform.position = transform.position;
  378.         }
  379.         UpdateStateIndicators ();
  380.     }
  381.     void OnCollisionStay(Collision c) {
  382.         if(c.gameObject == StandingOnObject) {
  383.             if(!IsStableOnGround) { IsCollidingWithGround = true; } else { GroundNormal = c.contacts[0].normal; }
  384.         }
  385.     }
  386.     void OnCollisionExit(Collision c) {
  387.         if(c.gameObject == StandingOnObject) {
  388.             if(IsStableOnGround || IsCollidingWithGround) {
  389.                 IsCollidingWithGround = false;
  390.                 IsStableOnGround = false;
  391.             }
  392.         }
  393.     }
  394.     #endregion // MonoBehaviour
  395. } // MovingEntity
  396.  
  397. /// a basic Mobile Entity controller (for MOBs, like seeking fireballs, or very basic enemies)
  398. public class MovingEntityBase : MonoBehaviour {
  399.     #region Public API
  400.     /// <summary>if being controlled by player, this value is constanly reset by user input. otherwise, useable as AI controls.</summary>
  401.     [HideInInspector]
  402.     public Vector3 MoveDirection, LookDirection;
  403.     [Tooltip("speed limit")]
  404.     public float MoveSpeed = 5;
  405.     [Tooltip("how quickly the PlayerControl transform rotates to match the intended direction, measured in degrees-per-second")]
  406.     public float TurnSpeed = 180;
  407.     [Tooltip("how quickly the PlayerControl reaches MoveSpeed. If less-than zero, jump straight to move speed.")]
  408.     public float Acceleration = -1;
  409.     /// <summary>the player's RigidBody, which lets Unity do all the physics for us</summary>
  410.     [HideInInspector]
  411.     public Rigidbody rb { get; protected set; }
  412.     /// <summary>cached variables required for a host of calculations in other scripts</summary>
  413.     public float CurrentSpeed { get; protected set; }
  414.     public float BrakeDistance { get; protected set; }
  415.     [HideInInspector]
  416.     public bool IsBrakeOn = false, AutomaticallyTurnToFaceLookDirection = true;
  417.     protected bool brakesOnLastFrame = false;
  418.     public bool IsMovingIntentionally { get; protected set; }
  419.  
  420.     public void Copy(MovingEntityBase toCopy) {
  421.         MoveSpeed = toCopy.MoveSpeed;
  422.         TurnSpeed = toCopy.TurnSpeed;
  423.         Acceleration = toCopy.Acceleration;
  424.     }
  425.     public void UpdateStateIndicators() {
  426.         CurrentSpeed = rb.velocity.magnitude;
  427.         BrakeDistance = (Acceleration > 0)?CalculateBrakeDistance (CurrentSpeed,Acceleration/rb.mass)-(CurrentSpeed*Time.deltaTime):0;
  428.         if (IsBrakeOn) {
  429.             brakesOnLastFrame = true;
  430.             IsBrakeOn = false;
  431.         } else {
  432.             brakesOnLastFrame = false;
  433.         }
  434.     }
  435.     #endregion // Public API
  436.     #regionCore Controller Logic
  437.     public bool IsBraking() { return IsBrakeOn || brakesOnLastFrame; }
  438.     public virtual void MoveLogic() {
  439.         IsMovingIntentionally = MoveDirection != Vector3.zero;
  440.         if (IsBrakeOn) {
  441.             ApplyBrakes (Acceleration);
  442.         } else {
  443.             ApplyMove (Acceleration);
  444.         }
  445.         if (AutomaticallyTurnToFaceLookDirection && transform.forward != LookDirection && TurnSpeed != 0)
  446.         { UpdateFacing (); }    // turn body as needed
  447.     }
  448.     public virtual void UpdateFacing() {
  449.         if((LookDirection != Vector3.zero && transform.forward != LookDirection)) {
  450.             Vector3 r = (LookDirection == transform.right) ? -transform.forward : ((LookDirection == -transform.right) ? transform.forward : transform.right);
  451.             Vector3 up = Vector3.Cross(LookDirection, r);
  452.             TurnToFace (LookDirection, up);
  453.         }
  454.     }
  455.     public void TurnToFace(Vector3 forward, Vector3 up) {
  456.         Quaternion target = Quaternion.LookRotation(forward, up);
  457.         if (TurnSpeed > 0) {
  458.             target = Quaternion.RotateTowards (transform.rotation, target, TurnSpeed * Time.deltaTime);
  459.         }
  460.         transform.rotation = target;
  461.     }
  462.     protected virtual void EnforceSpeedLimit() {
  463.         float actualSpeed = rb.velocity.magnitude;
  464.         if(actualSpeed > MoveSpeed) {
  465.             rb.velocity = rb.velocity.normalized*MoveSpeed;
  466.         }
  467.     }
  468.     public virtual void ApplyBrakes(float deceleration) {
  469.         if (deceleration > 0) {
  470.             float amountToMove = deceleration * Time.deltaTime;
  471.             MoveDirection = -rb.velocity.normalized;
  472.             float actualSpeed = rb.velocity.magnitude;
  473.             if (actualSpeed > Acceleration * amountToMove) {
  474.                 rb.velocity += MoveDirection * amountToMove;
  475.                 return;
  476.             }
  477.         }
  478.         rb.velocity = Vector3.zero;
  479.     }
  480.     /// <summary>Applies AccelerationDirection to the velocity. if brakesOn, slows things down.</summary>
  481.     protected virtual void ApplyMove(float acceleration) {
  482.         if (acceleration > 0) {
  483.             float amountToMove = acceleration * Time.deltaTime;
  484.             rb.velocity += MoveDirection * amountToMove;
  485.             EnforceSpeedLimit ();
  486.         } else {
  487.             rb.velocity = MoveDirection * MoveSpeed;
  488.         }
  489.     }
  490.     public static float CalculateBrakeDistance(float speed, float acceleration) { return (speed * speed) / (2 * acceleration); }
  491.     #endregion // Core Controller Logic
  492.     #region Steering Behaviors
  493.     protected Vector3 SeekMath(Vector3 directionToLookToward) {
  494.         Vector3 desiredVelocity = directionToLookToward * MoveSpeed;
  495.         Vector3 desiredChangeToVelocity = desiredVelocity - rb.velocity;
  496.         float desiredChange = desiredChangeToVelocity.magnitude;
  497.         if (desiredChange < Time.deltaTime * Acceleration) { return directionToLookToward; }
  498.         Vector3 steerDirection = desiredChangeToVelocity.normalized;
  499.         return steerDirection;
  500.     }
  501.     /// <summary>used by the Grounded controller, to filter positions into ground-space (for better tracking on curved surfaces)</summary>
  502.     protected virtual Vector3 CalculatePositionForAutomaticMovement(Vector3 position) { return position; }
  503.     /// <summary>called during a FixedUpdate process to give this agent simple AI movement</summary>
  504.     public void CalculateSeek(Vector3 target, out Vector3 directionToMoveToward, ref Vector3 directionToLookToward) {
  505.         Vector3 delta = target - transform.position;
  506.         if (delta == Vector3.zero) { directionToMoveToward = Vector3.zero; return; }
  507.         float desiredDistance = delta.magnitude;
  508.         directionToLookToward = delta / desiredDistance;
  509.         directionToMoveToward = (Acceleration > 0)?SeekMath (directionToLookToward):directionToLookToward;
  510.     }
  511.     /// <summary>called during a FixedUpdate process to give this agent simple AI movement</summary>
  512.     public void CalculateArrive (Vector3 target, out Vector3 directionToMoveToward, ref Vector3 directionToLookToward) {
  513.         Vector3 delta = target - transform.position;
  514.         if (delta == Vector3.zero) { directionToMoveToward = Vector3.zero; return; }
  515.         float desiredDistance = delta.magnitude;
  516.         directionToLookToward = delta / desiredDistance;
  517.         if (desiredDistance > 1.0f / 1024 && desiredDistance > BrakeDistance) {
  518.             directionToMoveToward = SeekMath (directionToLookToward);
  519.             return;
  520.         }
  521.         directionToMoveToward = -directionToLookToward;
  522.         IsBrakeOn = true;
  523.     }
  524.  
  525.     public enum DirectionMovementIsPossible { none, all, forwardOnly, forwardOrBackward, wasd }
  526.     /// <summary>Filters the direction.</summary>
  527.     /// <returns>The direction.</returns>
  528.     /// <param name="dir">Dir.</param>
  529.     /// <param name="dirs">Dirs.</param>
  530.     /// <param name="minimumMoveAlignment">Minimum move alignment. from 0 to 1, the minimum Dot product for moving</param>
  531.     public Vector3 FilterDirection(Vector3 dir, DirectionMovementIsPossible dirs, float minimumMoveAlignment = 0) {
  532.         if (dir == Vector3.zero || dirs == DirectionMovementIsPossible.none) return Vector3.zero;
  533.         float fdot = Vector3.Dot (dir, transform.forward);
  534.         switch (dirs) {
  535.         case DirectionMovementIsPossible.all: break;
  536.         case DirectionMovementIsPossible.forwardOnly:
  537.             if (fdot > 0 && (minimumMoveAlignment <= 0 || fdot > minimumMoveAlignment))
  538.             { dir = transform.forward; } else { dir = Vector3.zero; } break;
  539.         case DirectionMovementIsPossible.forwardOrBackward:
  540.             if (fdot != 0 && (minimumMoveAlignment <= 0 || Mathf.Abs(fdot) > minimumMoveAlignment))
  541.             { dir = transform.forward * Mathf.Sign(fdot); } else { dir = Vector3.zero; } break;
  542.         case DirectionMovementIsPossible.wasd: {
  543.                 float rdot = Vector3.Dot (dir, transform.right);
  544.                 if (Mathf.Abs (rdot) > Mathf.Abs (fdot)) {
  545.                     if(minimumMoveAlignment <= 0 || Mathf.Abs(rdot) > minimumMoveAlignment)
  546.                     { dir = transform.right * Mathf.Sign (rdot); } else { dir = Vector3.zero; }
  547.                 }
  548.                 else if(minimumMoveAlignment <= 0 || Mathf.Abs(fdot) > minimumMoveAlignment)
  549.                 { dir = transform.forward * Mathf.Sign(fdot); } else { dir = Vector3.zero; }
  550.             } break;
  551.         }
  552.         return dir;
  553.     }
  554.     /// <summary>call this during a FixedUpdate process to give this agent simple AI movement</summary>
  555.     public void Seek(Vector3 target, DirectionMovementIsPossible dirs = DirectionMovementIsPossible.all,
  556.         float minimumMoveAlignment = 1-1.0f/128, bool stopIfAlignmentIsntGoodEnough = true) {
  557.         CalculateSeek (CalculatePositionForAutomaticMovement(target), out MoveDirection, ref LookDirection);
  558.         if (dirs != DirectionMovementIsPossible.all) { MoveDirection = FilterDirection (MoveDirection, dirs, minimumMoveAlignment); }
  559.         if (stopIfAlignmentIsntGoodEnough && MoveDirection == Vector3.zero && Acceleration > 0) { IsBrakeOn = true; }
  560.     }
  561.     /// <summary>call this during a FixedUpdate process to give this agent simple AI movement</summary>
  562.     public void Flee(Vector3 target) {
  563.         CalculateSeek (CalculatePositionForAutomaticMovement(target), out MoveDirection, ref LookDirection);
  564.         MoveDirection *= -1;
  565.     }
  566.     /// <summary>call this during a FixedUpdate process to give this agent simple AI movement</summary>
  567.     public void Arrive(Vector3 target) {
  568.         CalculateArrive (CalculatePositionForAutomaticMovement(target), out MoveDirection, ref LookDirection);
  569.     }
  570.     /// <summary>call this during a FixedUpdate process to give this agent simple AI movement</summary>
  571.     public void RandomWalk(float weight = 0, Vector3 weightedTowardPoint = default(Vector3)) {
  572.         if (weight != 0) {
  573.             Vector3 dir = (CalculatePositionForAutomaticMovement (weightedTowardPoint) - transform.position).normalized;
  574.             dir = (dir * weight) + (Random.onUnitSphere * (1 - weight));
  575.             Vector3 p = CalculatePositionForAutomaticMovement (transform.position + transform.forward + dir);
  576.             Vector3 delta = p - transform.position;
  577.             MoveDirection = LookDirection = delta.normalized;
  578.         } else {
  579.             Vector3 p = CalculatePositionForAutomaticMovement (transform.position + transform.forward + Random.onUnitSphere);
  580.             Vector3 delta = p - transform.position;
  581.             MoveDirection = LookDirection = delta.normalized;
  582.         }
  583.     }
  584.     #endregion // Steering Behaviors
  585.     #region MonoBehaviour
  586.     void Start() {
  587.         rb = GetComponent<Rigidbody>();
  588.         if(!rb) { rb = gameObject.AddComponent<Rigidbody>(); }
  589.         rb.useGravity = false; rb.freezeRotation = true;
  590.     }
  591.     /// <summary>where physics-related changes happen</summary>
  592.     void FixedUpdate() {
  593.         MoveLogic();
  594.         UpdateStateIndicators ();
  595.     }
  596.     #endregion // MonoBehaviour
  597. } // MovingEntityBase
RAW Paste Data
Top