Advertisement
Guest User

Super Character Controller

a guest
Oct 21st, 2018
73
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 20.43 KB | None | 0 0
  1. // External release version 1.1.0
  2.  
  3. using UnityEngine;
  4. using System;
  5. using System.Linq;
  6. using System.Collections.Generic;
  7.  
  8. /// <summary>
  9. /// Custom character controller, to be used by attaching the component to an object
  10. /// and writing scripts attached to the same object that recieve the "SuperUpdate" message
  11. /// </summary>
  12. public class SuperCharacterController : MonoBehaviour
  13. {
  14.     [SerializeField]
  15.     public Vector3 debugMove = Vector3.zero;
  16.  
  17.     [SerializeField]
  18.     bool fixedTimeStep;
  19.  
  20.     [SerializeField]
  21.     int fixedUpdatesPerSecond;
  22.  
  23.     [SerializeField]
  24.     bool debugSpheres;
  25.  
  26.     [SerializeField]
  27.     bool debugPushbackMesssages;
  28.  
  29.     /// <summary>
  30.     /// Describes the Transform of the object we are standing on as well as it's CollisionType, as well
  31.     /// as how far the ground is below us and what angle it is in relation to the controller.
  32.     /// </summary>
  33.     [SerializeField]
  34.     public struct Ground
  35.     {
  36.         public RaycastHit Hit;
  37.         public RaycastHit NearHit;
  38.         public RaycastHit FarHit;
  39.         public SuperCollisionType CollisionType;
  40.         public Transform Transform;
  41.  
  42.         public Ground(RaycastHit hit, RaycastHit nearHit, RaycastHit farHit, SuperCollisionType superCollisionType, Transform hitTransform)
  43.         {
  44.             Hit = hit;
  45.             NearHit = nearHit;
  46.             FarHit = farHit;
  47.             CollisionType = superCollisionType;
  48.             Transform = hitTransform;
  49.         }
  50.     }
  51.  
  52.     [SerializeField]
  53.     CollisionSphere[] spheres =
  54.         new CollisionSphere[3] {
  55.             new CollisionSphere(0.5f, true, false),
  56.             new CollisionSphere(1.0f, false, false),
  57.             new CollisionSphere(1.5f, false, true),
  58.         };
  59.  
  60.     public LayerMask Walkable;
  61.  
  62.     [SerializeField]
  63.     Collider OwnCollider;
  64.  
  65.     public float radius = 0.5f;
  66.  
  67.     public float deltaTime { get; private set; }
  68.     public Ground currentGround { get; private set; }
  69.     public CollisionSphere feet { get; private set; }
  70.     public CollisionSphere head { get; private set; }
  71.     public float height { get { return Vector3.Distance(OffsetPosition(head.Offset), OffsetPosition(feet.Offset)); } }
  72.     public Vector3 up { get { return transform.up; } }
  73.     public Vector3 down { get { return -transform.up; } }
  74.     public List<SuperCollision> collisionData { get; private set; }
  75.     public Transform currentlyClampedTo { get; set; }
  76.  
  77.     private Vector3 initialPosition;
  78.     private Vector3 groundOffset;
  79.     private bool clamping = true;
  80.     private bool slopeLimiting = true;
  81.  
  82.     private List<Collider> ignoredColliders;
  83.     private List<IgnoredCollider> ignoredColliderStack;
  84.  
  85.     private const float Tolerance = 0.05f;
  86.     private const float TinyTolerance = 0.01f;
  87.     private const string TemporaryLayer = "TempCast";
  88.     private int TemporaryLayerIndex;
  89.     private float fixedDeltaTime;
  90.  
  91.     public float speed = 26f;
  92.     public float torque;
  93.     public float maxVelocidade;
  94.     public float velocidadeAtual;
  95.     private float maxVelocity;
  96.  
  97.     public float jumpSpeed = 8f;
  98.     public float friction = 1f;
  99.     private Vector3 currentVelocity = Vector3.zero;
  100.     private float velocityY = 0f;
  101.     private SuperCharacterController character;
  102.  
  103.     public void Awake()
  104.     {
  105.         collisionData = new List<SuperCollision>();
  106.  
  107.         TemporaryLayerIndex = LayerMask.NameToLayer(TemporaryLayer);
  108.  
  109.         ignoredColliders = new List<Collider>();
  110.         ignoredColliderStack = new List<IgnoredCollider>();
  111.  
  112.         currentlyClampedTo = null;
  113.  
  114.         fixedDeltaTime = 1.0f / fixedUpdatesPerSecond;
  115.  
  116.         if (OwnCollider)
  117.             IgnoreCollider(OwnCollider);
  118.  
  119.         foreach (var sphere in spheres)
  120.         {
  121.             if (sphere.IsFeet)
  122.                 feet = sphere;
  123.  
  124.             if (sphere.IsHead)
  125.                 head = sphere;
  126.         }
  127.  
  128.         if (feet == null)
  129.             Debug.LogError("[SuperCharacterController] Feet not found on controller");
  130.  
  131.         if (head == null)
  132.             Debug.LogError("[SuperCharacterController] Head not found on controller");
  133.            
  134.         gameObject.SendMessage("SuperStart", SendMessageOptions.DontRequireReceiver);
  135.     }
  136.  
  137.     void Start()
  138.     {
  139.        // maxVelocity = maxVelocidade * 3.6f;
  140.     }
  141.  
  142.     void Update()
  143.     {
  144.         // If we are using a fixed timestep, ensure we run the main update loop
  145.         // a sufficient number of times based on the Time.deltaTime
  146.  
  147.         if (!fixedTimeStep)
  148.         {
  149.             deltaTime = Time.deltaTime;
  150.  
  151.             SingleUpdate();
  152.             return;
  153.         }
  154.         else
  155.         {
  156.             float delta = Time.deltaTime;
  157.  
  158.             while (delta > fixedDeltaTime)
  159.             {
  160.                 deltaTime = fixedDeltaTime;
  161.  
  162.                 SingleUpdate();
  163.  
  164.                 delta -= fixedDeltaTime;
  165.             }
  166.  
  167.             if (delta > 0f)
  168.             {
  169.                 deltaTime = delta;
  170.  
  171.                 SingleUpdate();
  172.             }
  173.         }
  174.  
  175.         if (!character)
  176.         {
  177.             // get the CharacterController only the first time:
  178.             character = GetComponent<SuperCharacterController>();
  179.             // get the direction from the controls:
  180.             Vector3 dir = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
  181.             // calculate the desired velocity:
  182.             Vector3 vel = transform.TransformDirection(dir) * speed;
  183.  
  184.             // here's where the magic happens:
  185.             currentVelocity = Vector3.Lerp(currentVelocity, vel, friction * Time.deltaTime);
  186.  
  187.             // apply gravity and jump after the friction!
  188.             if(character.clamping)
  189.             {
  190.                 velocityY = 0;
  191.             }
  192.             if (Input.GetButtonDown("Jump"))
  193.             {
  194.                 velocityY = jumpSpeed;
  195.                 velocityY -= Time.deltaTime;
  196.                 currentVelocity.y = velocityY;
  197.                 currentVelocity = (currentVelocity * Time.deltaTime);
  198.             }
  199.         }
  200.     }
  201.  
  202.     private void OnTriggerStay(Collider other) {
  203.  
  204.         if (other.gameObject.tag == "IceFloor")
  205.         {
  206.             Debug.Log("Gelo!");
  207.  
  208.             if (Input.GetKey("w"))
  209.             {
  210.                 float moveHorizontal = Input.GetAxis("Horizontal");
  211.                 float moveVertical = Input.GetAxis("Vertical");
  212.  
  213.                 Vector3 movement = new Vector3(moveHorizontal, 0.0f, moveVertical);
  214.  
  215.                 GetComponent<Collider>().material.dynamicFriction = 0.1f;
  216.                 GetComponent<Rigidbody>().AddForce(movement * speed * 2 * Time.deltaTime);
  217.  
  218.                 //Mathf.Clamp(GetComponent<Rigidbody>().velocity.x, 0, 3);
  219.                 //gameObject.GetComponent<Rigidbody>().velocity *= 0.99f;
  220.  
  221.                 //velocidadeAtual = Mathf.Abs(gameObject.GetComponent<Rigidbody>().velocity.sqrMagnitude / 3.6f);
  222.  
  223.             }
  224.         }
  225.  
  226.         else if (other.gameObject.tag == "IceFloor")
  227.         {
  228.             Debug.Log("Gelo!");
  229.  
  230.             if (Input.GetKey("s"))
  231.             {
  232.                 float moveHorizontal = Input.GetAxis("Horizontal");
  233.                 float moveVertical = Input.GetAxis("Vertical");
  234.  
  235.                 Vector3 movement = new Vector3(moveHorizontal, 0.0f, moveVertical);
  236.  
  237.                 GetComponent<Collider>().material.dynamicFriction = 0.5f;
  238.                 GetComponent<Rigidbody>().AddForce(movement * speed / 2 * Time.deltaTime);
  239.  
  240.                 //gameObject.GetComponent<Rigidbody>().velocity *= 0.33f;
  241.  
  242.                 //velocidadeAtual = Mathf.Abs(gameObject.GetComponent<Rigidbody>().velocity.sqrMagnitude / 3.6f);
  243.             }
  244.         }
  245.     }
  246.  
  247.     public void antiUpwarp(){
  248.         Walkable = 0;
  249.     }
  250.  
  251.     void SingleUpdate()
  252.     {
  253.         // Check if we are clamped to an object implicity or explicity
  254.         bool isClamping = clamping || currentlyClampedTo != null;
  255.         Transform clampedTo = currentlyClampedTo != null ? currentlyClampedTo : currentGround.Transform;
  256.  
  257.         // Move our controller if clamped object moved in the previous frame
  258.         if (isClamping && groundOffset != Vector3.zero && clampedTo != null)
  259.             transform.position = clampedTo.position + groundOffset;
  260.  
  261.         initialPosition = transform.position;
  262.  
  263.         ProbeGroundRecursive();
  264.  
  265.         transform.position += debugMove * deltaTime;
  266.  
  267.         gameObject.SendMessage("SuperUpdate", SendMessageOptions.DontRequireReceiver);
  268.  
  269.         Pushback();
  270.  
  271.         ProbeGroundRecursive();
  272.  
  273.         if (slopeLimiting)
  274.             SlopeLimit();
  275.  
  276.         ProbeGroundRecursive();
  277.  
  278.         if (clamping)
  279.             ClampToGround();
  280.  
  281.         isClamping = clamping || currentlyClampedTo != null;
  282.         clampedTo = currentlyClampedTo != null ? currentlyClampedTo : currentGround.Transform;
  283.  
  284.         if (isClamping)
  285.             groundOffset = transform.position - clampedTo.position;
  286.  
  287.     }
  288.  
  289.     /// <summary>
  290.     /// Prevents the player from walking up slopes of a larger angle than the object's SlopeLimit.
  291.     /// NOTE: Since ProbeGroundRecursive ignores any slopes greater than StandAngle, the controller
  292.     /// will not be slope limited against these slopes.
  293.     /// </summary>
  294.     /// <returns>True if the controller attemped to ascend a too steep slope and had their movement limited</returns>
  295.     bool SlopeLimit()
  296.     {
  297.         Vector3 n = currentGround.Hit.normal;
  298.         float a = Vector3.Angle(n, up);
  299.  
  300.         if (a > currentGround.CollisionType.SlopeLimit)
  301.         {
  302.             Vector3 absoluteMoveDirection = Math3d.ProjectVectorOnPlane(n, transform.position - initialPosition);
  303.  
  304.             // Retrieve a vector pointing down the slope
  305.             Vector3 r = Vector3.Cross(n, down);
  306.             Vector3 v = Vector3.Cross(r, n);
  307.  
  308.             float angle = Vector3.Angle(absoluteMoveDirection, v);
  309.  
  310.             if (angle <= 90.0f)
  311.                 return false;
  312.  
  313.             // Calculate where to place the controller on the slope, or at the bottom, based on the desired movement distance
  314.             Vector3 resolvedPosition = Math3d.ProjectPointOnLine(initialPosition, r, transform.position);
  315.             Vector3 direction = Math3d.ProjectVectorOnPlane(n, resolvedPosition - transform.position);
  316.  
  317.             RaycastHit hit;
  318.  
  319.             // Check if our path to our resolved position is blocked by any colliders
  320.             if (Physics.CapsuleCast(OffsetPosition(feet.Offset), OffsetPosition(head.Offset), radius, direction.normalized, out hit, direction.magnitude, Walkable))
  321.             {
  322.                 transform.position += v.normalized * hit.distance;
  323.             }
  324.             else
  325.             {
  326.                 transform.position += direction;
  327.             }
  328.  
  329.             return true;
  330.         }
  331.  
  332.         return false;
  333.     }
  334.  
  335.     void ClampToGround()
  336.     {
  337.         float d = currentGround.Hit.distance;
  338.         transform.position -= up * d;
  339.     }
  340.  
  341.     public void EnableClamping()
  342.     {
  343.         clamping = true;
  344.     }
  345.  
  346.     public void DisableClamping()
  347.     {
  348.         clamping = false;
  349.     }
  350.  
  351.     public void EnableSlopeLimit()
  352.     {
  353.         slopeLimiting = true;
  354.     }
  355.  
  356.     public void DisableSlopeLimit()
  357.     {
  358.         slopeLimiting = false;
  359.     }
  360.  
  361.     public bool IsClamping()
  362.     {
  363.         return clamping;
  364.     }
  365.  
  366.     /// <summary>
  367.     /// SphereCasts directly below the controller recurisvely until it either finds no ground, or a ground
  368.     /// at an angle less than StandAngle
  369.     /// </summary>
  370.     void ProbeGroundRecursive()
  371.     {
  372.         ProbeGroundRecursive(OffsetPosition(feet.Offset), 0);
  373.     }
  374.  
  375.     void ProbeGroundRecursive(Vector3 origin, float distanceTraveled)
  376.     {
  377.         PushIgnoredColliders();
  378.  
  379.         // Add a small amount of Tolerance before casting downwards
  380.         Vector3 o = origin + (up * Tolerance);
  381.        
  382.         RaycastHit hit;
  383.  
  384.         if (Physics.SphereCast(o, radius, down, out hit, Mathf.Infinity, Walkable))
  385.         {
  386.             var wall = hit.collider.gameObject.GetComponent<SuperCollisionType>();
  387.  
  388.             if (wall == null)
  389.             {
  390.                 // TODO: just use some derived default values?
  391.                 Debug.LogError("[SuperCharacterComponent]: Object on SuperCharacterController walkable layer does not have SuperCollisionType component attached");
  392.             }
  393.  
  394.             Vector3 newOrigin = o + down * (hit.distance + TinyTolerance + Tolerance);
  395.  
  396.             hit.distance = Mathf.Clamp(hit.distance - Tolerance, 0, Mathf.Infinity);
  397.  
  398.             hit.distance += distanceTraveled;
  399.  
  400.             // If the StandAngle is not satisfactory, adjust our origin to be slightly below where we last hit
  401.             // and SphereCast again
  402.             if (Vector3.Angle(hit.normal, up) > wall.StandAngle)
  403.             {
  404.                 PopIgnoredColliders();
  405.  
  406.                 ProbeGroundRecursive(newOrigin, hit.distance + TinyTolerance);
  407.                 return;
  408.             }
  409.            
  410.             // Because when SphereCast hits an edge on a surface it returns the interpolation of the two normals of the
  411.             // two triangles joined to that edge, we need to retrieve the actual normal of both of the triangles
  412.             Vector3 toCenter = Math3d.ProjectVectorOnPlane(up, (transform.position - hit.point).normalized * TinyTolerance);
  413.  
  414.             if (toCenter == Vector3.zero)
  415.             {
  416.                 currentGround = new Ground(hit, hit, hit, wall, hit.transform);
  417.                 PopIgnoredColliders();
  418.  
  419.                 return;
  420.             }
  421.            
  422.             Vector3 awayFromCenter = Quaternion.AngleAxis(-80.0f, Vector3.Cross(toCenter, up)) * -toCenter;
  423.  
  424.             Vector3 nearPoint = hit.point + toCenter + (up * TinyTolerance);
  425.             Vector3 farPoint = hit.point + (awayFromCenter * 3);
  426.  
  427.             RaycastHit nearHit;
  428.             RaycastHit farHit;
  429.  
  430.             // Retrieve the normal of the point nearest to the center of the base of the controller
  431.             Physics.Raycast(nearPoint, down, out nearHit, Mathf.Infinity, Walkable);
  432.             // Retrieve the normal of the point furthest to the center of the base of the controller
  433.             Physics.Raycast(farPoint, down, out farHit, Mathf.Infinity, Walkable);
  434.  
  435.             currentGround = new Ground(hit, nearHit, farHit, wall, hit.transform);
  436.         }
  437.         else
  438.         {
  439.             // Debug.LogError("[SuperCharacterComponent]: No ground was found below the player; player has escaped level");
  440.         }
  441.  
  442.         PopIgnoredColliders();
  443.     }
  444.  
  445.     /// <summary>
  446.     /// Check if any of the CollisionSpheres are colliding with any walkable objects in the world.
  447.     /// If they are, apply a proper pushback and retrieve the collision data
  448.     /// </summary>
  449.     void Pushback()
  450.     {
  451.         PushIgnoredColliders();
  452.  
  453.         collisionData.Clear();
  454.  
  455.         foreach (var sphere in spheres)
  456.         {
  457.             foreach (Collider col in Physics.OverlapSphere(OffsetPosition(sphere.Offset), radius, Walkable))
  458.             {
  459.                 Vector3 position = OffsetPosition(sphere.Offset);
  460.                 Vector3 contactPoint = SuperCollider.ClosestPointOnSurface(col, position, radius);
  461.  
  462.                 if (contactPoint != Vector3.zero)
  463.                 {
  464.                     if (debugPushbackMesssages)
  465.                         DebugDraw.DrawMarker(contactPoint, 2.0f, Color.cyan, 0.0f, false);
  466.  
  467.                     Vector3 v = contactPoint - position;
  468.  
  469.                     if (v != Vector3.zero)
  470.                     {
  471.                         // Cache the collider's layer so that we can cast against it
  472.                         int layer = col.gameObject.layer;
  473.  
  474.                         col.gameObject.layer = TemporaryLayerIndex;
  475.  
  476.                         // Check which side of the normal we are on
  477.                         bool facingNormal = Physics.SphereCast(new Ray(position, v.normalized), TinyTolerance, v.magnitude + TinyTolerance, 1 << TemporaryLayerIndex);
  478.  
  479.                         col.gameObject.layer = layer;
  480.  
  481.                         // Orient and scale our vector based on which side of the normal we are situated
  482.                         if (facingNormal)
  483.                         {
  484.                             if (Vector3.Distance(position, contactPoint) < radius)
  485.                             {
  486.                                 v = v.normalized * (radius - v.magnitude) * -1;
  487.                             }
  488.                             else
  489.                             {
  490.                                 // A previously resolved collision has had a side effect that moved us outside this collider
  491.                                 continue;
  492.                             }
  493.                         }
  494.                         else
  495.                         {
  496.                             v = v.normalized * (radius + v.magnitude);
  497.                         }
  498.  
  499.                         transform.position += v;
  500.  
  501.                         col.gameObject.layer = TemporaryLayerIndex;
  502.  
  503.                         // Retrieve the surface normal of the collided point
  504.                         RaycastHit normalHit;
  505.  
  506.                         Physics.SphereCast(new Ray(position + v, contactPoint - (position + v)), TinyTolerance, out normalHit, 1 << TemporaryLayerIndex);
  507.  
  508.                         col.gameObject.layer = layer;
  509.  
  510.                         // Our collision affected the collider; add it to the collision data
  511.                         var collision = new SuperCollision()
  512.                         {
  513.                             collisionSphere = sphere,
  514.                             superCollisionType = col.gameObject.GetComponent<SuperCollisionType>(),
  515.                             gameObject = col.gameObject,
  516.                             point = contactPoint,
  517.                             normal = normalHit.normal
  518.                         };
  519.  
  520.                         collisionData.Add(collision);
  521.                     }
  522.                 }
  523.             }
  524.         }
  525.        
  526.         PopIgnoredColliders();
  527.     }
  528.  
  529.     protected struct IgnoredCollider
  530.     {
  531.         public Collider collider;
  532.         public int layer;
  533.  
  534.         public IgnoredCollider(Collider collider, int layer)
  535.         {
  536.             this.collider = collider;
  537.             this.layer = layer;
  538.         }
  539.     }
  540.  
  541.     private void PushIgnoredColliders()
  542.     {
  543.         ignoredColliderStack.Clear();
  544.  
  545.         for (int i = 0; i < ignoredColliders.Count; i++)
  546.         {
  547.             Collider col = ignoredColliders[i];
  548.             ignoredColliderStack.Add(new IgnoredCollider(col, col.gameObject.layer));
  549.             col.gameObject.layer = TemporaryLayerIndex;
  550.         }
  551.     }
  552.  
  553.     private void PopIgnoredColliders()
  554.     {
  555.         for (int i = 0; i < ignoredColliderStack.Count; i++)
  556.         {
  557.             IgnoredCollider ic = ignoredColliderStack[i];
  558.             ic.collider.gameObject.layer = ic.layer;
  559.         }
  560.  
  561.         ignoredColliderStack.Clear();
  562.     }
  563.  
  564.     void OnDrawGizmos()
  565.     {
  566.         if (debugSpheres)
  567.         {
  568.             if (spheres != null)
  569.             {
  570.                 foreach (var sphere in spheres)
  571.                 {
  572.                     Gizmos.color = sphere.IsFeet ? Color.green : (sphere.IsHead ? Color.yellow : Color.cyan);
  573.                     Gizmos.DrawWireSphere(OffsetPosition(sphere.Offset), radius);
  574.                 }
  575.             }
  576.         }
  577.     }
  578.  
  579.     public Vector3 OffsetPosition(float y)
  580.     {
  581.         Vector3 p;
  582.  
  583.         p = transform.position;
  584.  
  585.         p += up * y;
  586.  
  587.         return p;
  588.     }
  589.  
  590.     public bool BelowHead(Vector3 point)
  591.     {
  592.         return Vector3.Angle(point - OffsetPosition(head.Offset), up) > 89.0f;
  593.     }
  594.  
  595.     public bool AboveFeet(Vector3 point)
  596.     {
  597.         return Vector3.Angle(point - OffsetPosition(feet.Offset), down) > 89.0f;
  598.     }
  599.  
  600.     public void IgnoreCollider(Collider col)
  601.     {
  602.         ignoredColliders.Add(col);
  603.     }
  604.  
  605.     public void RemoveIgnoredCollider(Collider col)
  606.     {
  607.         ignoredColliders.Remove(col);
  608.     }
  609.  
  610.     public void ClearIgnoredColliders()
  611.     {
  612.         ignoredColliders.Clear();
  613.     }
  614. }
  615.  
  616. [Serializable]
  617. public class CollisionSphere
  618. {
  619.     public float Offset;
  620.     public bool IsFeet;
  621.     public bool IsHead;
  622.  
  623.     public CollisionSphere(float offset, bool isFeet, bool isHead)
  624.     {
  625.         Offset = offset;
  626.         IsFeet = isFeet;
  627.         this.IsHead = isHead;
  628.     }
  629. }
  630.  
  631. public struct SuperCollision
  632. {
  633.     public CollisionSphere collisionSphere;
  634.     public SuperCollisionType superCollisionType;
  635.     public GameObject gameObject;
  636.     public Vector3 point;
  637.     public Vector3 normal;
  638. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement