Guest User

Untitled

a guest
May 31st, 2016
281
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // External release version 2.0.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.     Vector3 debugMove = Vector3.zero;
  16.  
  17.     [SerializeField]
  18.     QueryTriggerInteraction triggerInteraction;
  19.  
  20.     [SerializeField]
  21.     bool fixedTimeStep;
  22.  
  23.     [SerializeField]
  24.     int fixedUpdatesPerSecond;
  25.  
  26.     [SerializeField]
  27.     bool clampToMovingGround;
  28.  
  29.     [SerializeField]
  30.     bool debugSpheres;
  31.  
  32.     [SerializeField]
  33.     bool debugGrounding;
  34.  
  35.     [SerializeField]
  36.     bool debugPushbackMesssages;
  37.  
  38.     /// <summary>
  39.     /// Describes the Transform of the object we are standing on as well as it's CollisionType, as well
  40.     /// as how far the ground is below us and what angle it is in relation to the controller.
  41.     /// </summary>
  42.     [SerializeField]
  43.     public struct Ground
  44.     {
  45.         public RaycastHit hit { get; set; }
  46.         public RaycastHit nearHit { get; set; }
  47.         public RaycastHit farHit { get; set; }
  48.         public RaycastHit secondaryHit { get; set; }
  49.         public SuperCollisionType collisionType { get; set; }
  50.         public Transform transform { get; set; }
  51.  
  52.         public Ground(RaycastHit hit, RaycastHit nearHit, RaycastHit farHit, RaycastHit secondaryHit, SuperCollisionType superCollisionType, Transform hitTransform)
  53.         {
  54.             this.hit = hit;
  55.             this.nearHit = nearHit;
  56.             this.farHit = farHit;
  57.             this.secondaryHit = secondaryHit;
  58.             this.collisionType = superCollisionType;
  59.             this.transform = hitTransform;
  60.         }
  61.     }
  62.  
  63.     [SerializeField]
  64.     CollisionSphere[] spheres =
  65.         new CollisionSphere[3] {
  66.             new CollisionSphere(0.5f, true, false),
  67.             new CollisionSphere(1.0f, false, false),
  68.             new CollisionSphere(1.5f, false, true),
  69.         };
  70.  
  71.     public LayerMask Walkable;
  72.  
  73.     [SerializeField]
  74.     Collider ownCollider;
  75.  
  76.     [SerializeField]
  77.     public float radius = 0.5f;
  78.  
  79.     public float deltaTime { get; private set; }
  80.     public SuperGround currentGround { get; private set; }
  81.     public CollisionSphere feet { get; private set; }
  82.     public CollisionSphere head { get; private set; }
  83.  
  84.     /// <summary>
  85.     /// Total height of the controller from the bottom of the feet to the top of the head
  86.     /// </summary>
  87.     public float height { get { return Vector3.Distance(SpherePosition(head), SpherePosition(feet)) + radius * 2; } }
  88.  
  89.     public Vector3 up { get { return transform.up; } }
  90.     public Vector3 down { get { return -transform.up; } }
  91.     public List<SuperCollision> collisionData { get; private set; }
  92.     public Transform currentlyClampedTo { get; set; }
  93.     public float heightScale { get; set; }
  94.     public float radiusScale { get; set; }
  95.     public bool manualUpdateOnly { get; set; }
  96.  
  97.     public delegate void UpdateDelegate();
  98.     public event UpdateDelegate AfterSingleUpdate;
  99.  
  100.     private Vector3 initialPosition;
  101.     private Vector3 groundOffset;
  102.     private Vector3 lastGroundPosition;
  103.     private bool clamping = true;
  104.     private bool slopeLimiting = true;
  105.  
  106.     private List<Collider> ignoredColliders;
  107.     private List<IgnoredCollider> ignoredColliderStack;
  108.  
  109.     private const float Tolerance = 0.05f;
  110.     private const float TinyTolerance = 0.01f;
  111.     private const string TemporaryLayer = "TempCast";
  112.     private const int MaxPushbackIterations = 2;
  113.     private int TemporaryLayerIndex;
  114.     private float fixedDeltaTime;
  115.  
  116.     private static SuperCollisionType defaultCollisionType;
  117.  
  118.     void Awake()
  119.     {
  120.         collisionData = new List<SuperCollision>();
  121.  
  122.         TemporaryLayerIndex = LayerMask.NameToLayer(TemporaryLayer);
  123.  
  124.         ignoredColliders = new List<Collider>();
  125.         ignoredColliderStack = new List<IgnoredCollider>();
  126.  
  127.         currentlyClampedTo = null;
  128.  
  129.         fixedDeltaTime = 1.0f / fixedUpdatesPerSecond;
  130.  
  131.         heightScale = 1.0f;
  132.  
  133.         if (ownCollider)
  134.             IgnoreCollider(ownCollider);
  135.  
  136.         foreach (var sphere in spheres)
  137.         {
  138.             if (sphere.isFeet)
  139.                 feet = sphere;
  140.  
  141.             if (sphere.isHead)
  142.                 head = sphere;
  143.         }
  144.  
  145.         if (feet == null)
  146.             Debug.LogError("[SuperCharacterController] Feet not found on controller");
  147.  
  148.         if (head == null)
  149.             Debug.LogError("[SuperCharacterController] Head not found on controller");
  150.  
  151.         if (defaultCollisionType == null)
  152.             defaultCollisionType = new GameObject("DefaultSuperCollisionType", typeof(SuperCollisionType)).GetComponent<SuperCollisionType>();
  153.  
  154.         currentGround = new SuperGround(Walkable, this, triggerInteraction);
  155.  
  156.         manualUpdateOnly = false;
  157.  
  158.         gameObject.SendMessage("SuperStart", SendMessageOptions.DontRequireReceiver);
  159.     }
  160.  
  161.     void Update()
  162.     {
  163.         // If we are using a fixed timestep, ensure we run the main update loop
  164.         // a sufficient number of times based on the Time.deltaTime
  165.         if (manualUpdateOnly)
  166.             return;
  167.  
  168.         if (!fixedTimeStep)
  169.         {
  170.             deltaTime = Time.deltaTime;
  171.  
  172.             SingleUpdate();
  173.             return;
  174.         }
  175.         else
  176.         {
  177.             float delta = Time.deltaTime;
  178.  
  179.             while (delta > fixedDeltaTime)
  180.             {
  181.                 deltaTime = fixedDeltaTime;
  182.  
  183.                 SingleUpdate();
  184.  
  185.                 delta -= fixedDeltaTime;
  186.             }
  187.  
  188.             if (delta > 0f)
  189.             {
  190.                 deltaTime = delta;
  191.  
  192.                 SingleUpdate();
  193.             }
  194.         }
  195.     }
  196.  
  197.     public void ManualUpdate(float deltaTime)
  198.     {
  199.         this.deltaTime = deltaTime;
  200.  
  201.         SingleUpdate();
  202.     }
  203.  
  204.     void SingleUpdate()
  205.     {
  206.         // Check if we are clamped to an object implicity or explicity
  207.         bool isClamping = clamping || currentlyClampedTo != null;
  208.         Transform clampedTo = currentlyClampedTo != null ? currentlyClampedTo : currentGround.transform;
  209.  
  210.         if (clampToMovingGround && isClamping && clampedTo != null && clampedTo.position - lastGroundPosition != Vector3.zero)
  211.             transform.position += clampedTo.position - lastGroundPosition;
  212.  
  213.         initialPosition = transform.position;
  214.  
  215.         ProbeGround(1);
  216.  
  217.         transform.position += debugMove * deltaTime;
  218.  
  219.         gameObject.SendMessage("SuperUpdate", SendMessageOptions.DontRequireReceiver);
  220.  
  221.         collisionData.Clear();
  222.  
  223.         RecursivePushback(0, MaxPushbackIterations);
  224.  
  225.         ProbeGround(2);
  226.  
  227.         if (slopeLimiting)
  228.             SlopeLimit();
  229.  
  230.         ProbeGround(3);
  231.  
  232.         if (clamping)
  233.             ClampToGround();
  234.  
  235.         isClamping = clamping || currentlyClampedTo != null;
  236.         clampedTo = currentlyClampedTo != null ? currentlyClampedTo : currentGround.transform;
  237.  
  238.         if (isClamping)
  239.             lastGroundPosition = clampedTo.position;
  240.  
  241.         if (debugGrounding)
  242.             currentGround.DebugGround(true, true, true, true, true);
  243.  
  244.         if (AfterSingleUpdate != null)
  245.             AfterSingleUpdate();
  246.     }
  247.  
  248.     void ProbeGround(int iter)
  249.     {
  250.         PushIgnoredColliders();
  251.         currentGround.ProbeGround(SpherePosition(feet), iter);
  252.         PopIgnoredColliders();
  253.     }
  254.  
  255.     /// <summary>
  256.     /// Prevents the player from walking up slopes of a larger angle than the object's SlopeLimit.
  257.     /// </summary>
  258.     /// <returns>True if the controller attemped to ascend a too steep slope and had their movement limited</returns>
  259.     bool SlopeLimit()
  260.     {
  261.         Vector3 n = currentGround.PrimaryNormal();
  262.         float a = Vector3.Angle(n, up);
  263.  
  264.         if (a > currentGround.superCollisionType.SlopeLimit)
  265.         {
  266.             Vector3 absoluteMoveDirection = Math3d.ProjectVectorOnPlane(n, transform.position - initialPosition);
  267.  
  268.             // Retrieve a vector pointing down the slope
  269.             Vector3 r = Vector3.Cross(n, down);
  270.             Vector3 v = Vector3.Cross(r, n);
  271.  
  272.             float angle = Vector3.Angle(absoluteMoveDirection, v);
  273.  
  274.             if (angle <= 90.0f)
  275.                 return false;
  276.  
  277.             // Calculate where to place the controller on the slope, or at the bottom, based on the desired movement distance
  278.             Vector3 resolvedPosition = Math3d.ProjectPointOnLine(initialPosition, r, transform.position);
  279.             Vector3 direction = Math3d.ProjectVectorOnPlane(n, resolvedPosition - transform.position);
  280.  
  281.             RaycastHit hit;
  282.  
  283.             // Check if our path to our resolved position is blocked by any colliders
  284.             if (Physics.CapsuleCast(SpherePosition(feet), SpherePosition(head), radius, direction.normalized, out hit, direction.magnitude, Walkable, triggerInteraction))
  285.             {
  286.                 transform.position += v.normalized * hit.distance;
  287.             }
  288.             else
  289.             {
  290.                 transform.position += direction;
  291.             }
  292.  
  293.             return true;
  294.         }
  295.  
  296.         return false;
  297.     }
  298.  
  299.     void ClampToGround()
  300.     {
  301.         float d = currentGround.Distance();
  302.         transform.position -= up * d;
  303.     }
  304.  
  305.     public void EnableClamping()
  306.     {
  307.         clamping = true;
  308.     }
  309.  
  310.     public void DisableClamping()
  311.     {
  312.         clamping = false;
  313.     }
  314.  
  315.     public void EnableSlopeLimit()
  316.     {
  317.         slopeLimiting = true;
  318.     }
  319.  
  320.     public void DisableSlopeLimit()
  321.     {
  322.         slopeLimiting = false;
  323.     }
  324.  
  325.     public bool IsClamping()
  326.     {
  327.         return clamping;
  328.     }
  329.  
  330.     /// <summary>
  331.     /// Check if any of the CollisionSpheres are colliding with any walkable objects in the world.
  332.     /// If they are, apply a proper pushback and retrieve the collision data
  333.     /// </summary>
  334.     void RecursivePushback(int depth, int maxDepth)
  335.     {
  336.         PushIgnoredColliders();
  337.  
  338.         bool contact = false;
  339.  
  340.         foreach (var sphere in spheres)
  341.         {
  342.             foreach (Collider col in Physics.OverlapSphere((SpherePosition(sphere)), radius, Walkable, triggerInteraction))
  343.             {
  344.                 Vector3 position = SpherePosition(sphere);
  345.                 Vector3 contactPoint = SuperCollider.ClosestPointOnSurface(col, position, radius);
  346.  
  347.                 if (contactPoint != Vector3.zero)
  348.                 {
  349.                     if (debugPushbackMesssages)
  350.                         DebugDraw.DrawMarker(contactPoint, 2.0f, Color.cyan, 0.0f, false);
  351.  
  352.                     Vector3 v = contactPoint - position;
  353.  
  354.                     if (v != Vector3.zero)
  355.                     {
  356.                         // Cache the collider's layer so that we can cast against it
  357.                         int layer = col.gameObject.layer;
  358.  
  359.                         col.gameObject.layer = TemporaryLayerIndex;
  360.  
  361.                         // Check which side of the normal we are on
  362.                         bool facingNormal = Physics.SphereCast(new Ray(position, v.normalized), TinyTolerance, v.magnitude + TinyTolerance, 1 << TemporaryLayerIndex);
  363.  
  364.                         col.gameObject.layer = layer;
  365.  
  366.                         // Orient and scale our vector based on which side of the normal we are situated
  367.                         if (facingNormal)
  368.                         {
  369.                             if (Vector3.Distance(position, contactPoint) < radius)
  370.                             {
  371.                                 v = v.normalized * (radius - v.magnitude) * -1;
  372.                             }
  373.                             else
  374.                             {
  375.                                 // A previously resolved collision has had a side effect that moved us outside this collider
  376.                                 continue;
  377.                             }
  378.                         }
  379.                         else
  380.                         {
  381.                             v = v.normalized * (radius + v.magnitude);
  382.                         }
  383.  
  384.                         contact = true;
  385.  
  386.                         transform.position += v;
  387.  
  388.                         col.gameObject.layer = TemporaryLayerIndex;
  389.  
  390.                         // Retrieve the surface normal of the collided point
  391.                         RaycastHit normalHit;
  392.  
  393.                         Physics.SphereCast(new Ray(position + v, contactPoint - (position + v)), TinyTolerance, out normalHit, 1 << TemporaryLayerIndex);
  394.  
  395.                         col.gameObject.layer = layer;
  396.  
  397.                         SuperCollisionType superColType = col.gameObject.GetComponent<SuperCollisionType>();
  398.  
  399.                         if (superColType == null)
  400.                             superColType = defaultCollisionType;
  401.  
  402.                         // Our collision affected the collider; add it to the collision data
  403.                         var collision = new SuperCollision()
  404.                         {
  405.                             collisionSphere = sphere,
  406.                             superCollisionType = superColType,
  407.                             gameObject = col.gameObject,
  408.                             point = contactPoint,
  409.                             normal = normalHit.normal
  410.                         };
  411.  
  412.                         collisionData.Add(collision);
  413.                     }
  414.                 }
  415.             }
  416.         }
  417.  
  418.         PopIgnoredColliders();
  419.  
  420.         if (depth < maxDepth && contact)
  421.         {
  422.             RecursivePushback(depth + 1, maxDepth);
  423.         }
  424.     }
  425.  
  426.     protected struct IgnoredCollider
  427.     {
  428.         public Collider collider;
  429.         public int layer;
  430.  
  431.         public IgnoredCollider(Collider collider, int layer)
  432.         {
  433.             this.collider = collider;
  434.             this.layer = layer;
  435.         }
  436.     }
  437.  
  438.     private void PushIgnoredColliders()
  439.     {
  440.         ignoredColliderStack.Clear();
  441.  
  442.         for (int i = 0; i < ignoredColliders.Count; i++)
  443.         {
  444.             Collider col = ignoredColliders[i];
  445.             ignoredColliderStack.Add(new IgnoredCollider(col, col.gameObject.layer));
  446.             col.gameObject.layer = TemporaryLayerIndex;
  447.         }
  448.     }
  449.  
  450.     private void PopIgnoredColliders()
  451.     {
  452.         for (int i = 0; i < ignoredColliderStack.Count; i++)
  453.         {
  454.             IgnoredCollider ic = ignoredColliderStack[i];
  455.             ic.collider.gameObject.layer = ic.layer;
  456.         }
  457.  
  458.         ignoredColliderStack.Clear();
  459.     }
  460.  
  461.     void OnDrawGizmos()
  462.     {
  463.         Gizmos.color = Color.red;
  464.  
  465.         if (debugSpheres)
  466.         {
  467.             if (spheres != null)
  468.             {
  469.                 if (heightScale == 0) heightScale = 1;
  470.  
  471.                 foreach (var sphere in spheres)
  472.                 {
  473.                     Gizmos.color = sphere.isFeet ? Color.green : (sphere.isHead ? Color.yellow : Color.cyan);
  474.                     Gizmos.DrawWireSphere(SpherePosition(sphere), radius);
  475.                 }
  476.             }
  477.         }
  478.     }
  479.  
  480.     public Vector3 SpherePosition(CollisionSphere sphere)
  481.     {
  482.         if (sphere.isFeet)
  483.             return transform.position + sphere.offset * up;
  484.         else
  485.             return transform.position + sphere.offset * up * heightScale;
  486.     }
  487.  
  488.     public bool PointBelowHead(Vector3 point)
  489.     {
  490.         return Vector3.Angle(point - SpherePosition(head), up) > 89.0f;
  491.     }
  492.  
  493.     public bool PointAboveFeet(Vector3 point)
  494.     {
  495.         return Vector3.Angle(point - SpherePosition(feet), down) > 89.0f;
  496.     }
  497.  
  498.     public void IgnoreCollider(Collider col)
  499.     {
  500.         ignoredColliders.Add(col);
  501.     }
  502.  
  503.     public void RemoveIgnoredCollider(Collider col)
  504.     {
  505.         ignoredColliders.Remove(col);
  506.     }
  507.  
  508.     public void ClearIgnoredColliders()
  509.     {
  510.         ignoredColliders.Clear();
  511.     }
  512.  
  513.     public class SuperGround
  514.     {
  515.         public SuperGround(LayerMask walkable, SuperCharacterController controller, QueryTriggerInteraction triggerInteraction)
  516.         {
  517.             this.walkable = walkable;
  518.             this.controller = controller;
  519.             this.triggerInteraction = triggerInteraction;
  520.         }
  521.  
  522.         private class GroundHit
  523.         {
  524.             public Vector3 point { get; private set; }
  525.             public Vector3 normal { get; private set; }
  526.             public float distance { get; private set; }
  527.  
  528.             public GroundHit(Vector3 point, Vector3 normal, float distance)
  529.             {
  530.                 this.point = point;
  531.                 this.normal = normal;
  532.                 this.distance = distance;
  533.             }
  534.         }
  535.  
  536.         private LayerMask walkable;
  537.         private SuperCharacterController controller;
  538.         private QueryTriggerInteraction triggerInteraction;
  539.  
  540.         private GroundHit primaryGround;
  541.         private GroundHit nearGround;
  542.         private GroundHit farGround;
  543.         private GroundHit stepGround;
  544.         private GroundHit flushGround;
  545.  
  546.         public SuperCollisionType superCollisionType { get; private set; }
  547.         public Transform transform { get; private set; }
  548.  
  549.         private const float groundingUpperBoundAngle = 60.0f;
  550.         private const float groundingMaxPercentFromCenter = 0.85f;
  551.         private const float groundingMinPercentFromcenter = 0.50f;
  552.  
  553.         /// <summary>
  554.         /// Scan the surface below us for ground. Follow up the initial scan with subsequent scans
  555.         /// designed to test what kind of surface we are standing above and handle different edge cases
  556.         /// </summary>
  557.         /// <param name="origin">Center of the sphere for the initial SphereCast</param>
  558.         /// <param name="iter">Debug tool to print out which ProbeGround iteration is being run (3 are run each frame for the controller)</param>
  559.         public void ProbeGround(Vector3 origin, int iter)
  560.         {
  561.             ResetGrounds();
  562.  
  563.             Vector3 up = controller.up;
  564.             Vector3 down = -up;
  565.  
  566.             Vector3 o = origin + (up * Tolerance);
  567.  
  568.             // Reduce our radius by Tolerance squared to avoid failing the SphereCast due to clipping with walls
  569.             float smallerRadius = controller.radius - (Tolerance * Tolerance);
  570.  
  571.             RaycastHit hit;
  572.  
  573.             if (Physics.SphereCast(o, smallerRadius, down, out hit, Mathf.Infinity, walkable, triggerInteraction))
  574.             {
  575.                 var superColType = hit.collider.gameObject.GetComponent<SuperCollisionType>();
  576.  
  577.                 if (superColType == null)
  578.                 {
  579.                     superColType = defaultCollisionType;
  580.                 }
  581.  
  582.                 superCollisionType = superColType;
  583.                 transform = hit.transform;
  584.  
  585.                 // By reducing the initial SphereCast's radius by Tolerance, our casted sphere no longer fits with
  586.                 // our controller's shape. Reconstruct the sphere cast with the proper radius
  587.                 SimulateSphereCast(hit.normal, out hit);
  588.  
  589.                 primaryGround = new GroundHit(hit.point, hit.normal, hit.distance);
  590.  
  591.                 // If we are standing on a perfectly flat surface, we cannot be either on an edge,
  592.                 // On a slope or stepping off a ledge
  593.                 if (Vector3.Distance(Math3d.ProjectPointOnPlane(controller.up, controller.transform.position, hit.point), controller.transform.position) < TinyTolerance)
  594.                 {
  595.                     return;
  596.                 }
  597.  
  598.                 // As we are standing on an edge, we need to retrieve the normals of the two
  599.                 // faces on either side of the edge and store them in nearHit and farHit
  600.  
  601.                 Vector3 toCenter = Math3d.ProjectVectorOnPlane(up, (controller.transform.position - hit.point).normalized * TinyTolerance);
  602.  
  603.                 Vector3 awayFromCenter = Quaternion.AngleAxis(-80.0f, Vector3.Cross(toCenter, up)) * -toCenter;
  604.  
  605.                 Vector3 nearPoint = hit.point + toCenter + (up * TinyTolerance);
  606.                 Vector3 farPoint = hit.point + (awayFromCenter * 3);
  607.  
  608.                 RaycastHit nearHit;
  609.                 RaycastHit farHit;
  610.  
  611.                 Physics.Raycast(nearPoint, down, out nearHit, Mathf.Infinity, walkable, triggerInteraction);
  612.                 Physics.Raycast(farPoint, down, out farHit, Mathf.Infinity, walkable, triggerInteraction);
  613.  
  614.                 nearGround = new GroundHit(nearHit.point, nearHit.normal, nearHit.distance);
  615.                 farGround = new GroundHit(farHit.point, farHit.normal, farHit.distance);
  616.  
  617.                 // If we are currently standing on ground that should be counted as a wall,
  618.                 // we are likely flush against it on the ground. Retrieve what we are standing on
  619.                 if (Vector3.Angle(hit.normal, up) > superColType.StandAngle)
  620.                 {
  621.                     // Retrieve a vector pointing down the slope
  622.                     Vector3 r = Vector3.Cross(hit.normal, down);
  623.                     Vector3 v = Vector3.Cross(r, hit.normal);
  624.  
  625.                     Vector3 flushOrigin = hit.point + hit.normal * TinyTolerance;
  626.  
  627.                     RaycastHit flushHit;
  628.  
  629.                     if (Physics.Raycast(flushOrigin, v, out flushHit, Mathf.Infinity, walkable, triggerInteraction))
  630.                     {
  631.                         RaycastHit sphereCastHit;
  632.  
  633.                         if (SimulateSphereCast(flushHit.normal, out sphereCastHit))
  634.                         {
  635.                             flushGround = new GroundHit(sphereCastHit.point, sphereCastHit.normal, sphereCastHit.distance);
  636.                         }
  637.                         else
  638.                         {
  639.                             // Uh oh
  640.                         }
  641.                     }
  642.                 }
  643.  
  644.                 // If we are currently standing on a ledge then the face nearest the center of the
  645.                 // controller should be steep enough to be counted as a wall. Retrieve the ground
  646.                 // it is connected to at it's base, if there exists any
  647.                 if (Vector3.Angle(nearHit.normal, up) > superColType.StandAngle || nearHit.distance > Tolerance)
  648.                 {
  649.                     var col = nearHit.collider.gameObject.GetComponent<SuperCollisionType>();
  650.  
  651.                     if (col == null)
  652.                     {
  653.                         col = defaultCollisionType;
  654.                     }
  655.  
  656.                     // We contacted the wall of the ledge, rather than the landing. Raycast down
  657.                     // the wall to retrieve the proper landing
  658.                     if (Vector3.Angle(nearHit.normal, up) > col.StandAngle)
  659.                     {
  660.                         // Retrieve a vector pointing down the slope
  661.                         Vector3 r = Vector3.Cross(nearHit.normal, down);
  662.                         Vector3 v = Vector3.Cross(r, nearHit.normal);
  663.  
  664.                         RaycastHit stepHit;
  665.  
  666.                         if (Physics.Raycast(nearPoint, v, out stepHit, Mathf.Infinity, walkable, triggerInteraction))
  667.                         {
  668.                             stepGround = new GroundHit(stepHit.point, stepHit.normal, stepHit.distance);
  669.                         }
  670.                     }
  671.                     else
  672.                     {
  673.                         stepGround = new GroundHit(nearHit.point, nearHit.normal, nearHit.distance);
  674.                     }
  675.                 }
  676.             }
  677.             // If the initial SphereCast fails, likely due to the controller clipping a wall,
  678.             // fallback to a raycast simulated to SphereCast data
  679.             else if (Physics.Raycast(o, down, out hit, Mathf.Infinity, walkable, triggerInteraction))
  680.             {
  681.                 var superColType = hit.collider.gameObject.GetComponent<SuperCollisionType>();
  682.  
  683.                 if (superColType == null)
  684.                 {
  685.                     superColType = defaultCollisionType;
  686.                 }
  687.  
  688.                 superCollisionType = superColType;
  689.                 transform = hit.transform;
  690.  
  691.                 RaycastHit sphereCastHit;
  692.  
  693.                 if (SimulateSphereCast(hit.normal, out sphereCastHit))
  694.                 {
  695.                     primaryGround = new GroundHit(sphereCastHit.point, sphereCastHit.normal, sphereCastHit.distance);
  696.                 }
  697.                 else
  698.                 {
  699.                     primaryGround = new GroundHit(hit.point, hit.normal, hit.distance);
  700.                 }
  701.             }
  702.             else
  703.             {
  704.                 Debug.LogError("[SuperCharacterComponent]: No ground was found below the player; player has escaped level");
  705.             }
  706.         }
  707.  
  708.         private void ResetGrounds()
  709.         {
  710.             primaryGround = null;
  711.             nearGround = null;
  712.             farGround = null;
  713.             flushGround = null;
  714.             stepGround = null;
  715.         }
  716.  
  717.         public bool IsGrounded(bool currentlyGrounded, float distance)
  718.         {
  719.             Vector3 n;
  720.             return IsGrounded(currentlyGrounded, distance, out n);
  721.         }
  722.  
  723.         public bool IsGrounded(bool currentlyGrounded, float distance, out Vector3 groundNormal)
  724.         {
  725.             groundNormal = Vector3.zero;
  726.  
  727.             if (primaryGround == null || primaryGround.distance > distance)
  728.             {
  729.                 return false;
  730.             }
  731.  
  732.             // Check if we are flush against a wall
  733.             if (farGround != null && Vector3.Angle(farGround.normal, controller.up) > superCollisionType.StandAngle)
  734.             {
  735.                 if (flushGround != null && Vector3.Angle(flushGround.normal, controller.up) < superCollisionType.StandAngle && flushGround.distance < distance)
  736.                 {
  737.                     groundNormal = flushGround.normal;
  738.                     return true;
  739.                 }
  740.  
  741.                 return false;
  742.             }
  743.  
  744.             // Check if we are at the edge of a ledge, or on a high angle slope
  745.             if (farGround != null && !OnSteadyGround(farGround.normal, primaryGround.point))
  746.             {
  747.                 // Check if we are walking onto steadier ground
  748.                 if (nearGround != null && nearGround.distance < distance && Vector3.Angle(nearGround.normal, controller.up) < superCollisionType.StandAngle && !OnSteadyGround(nearGround.normal, nearGround.point))
  749.                 {
  750.                     groundNormal = nearGround.normal;
  751.                     return true;
  752.                 }
  753.  
  754.                 // Check if we are on a step or stair
  755.                 if (stepGround != null && stepGround.distance < distance && Vector3.Angle(stepGround.normal, controller.up) < superCollisionType.StandAngle)
  756.                 {
  757.                     groundNormal = stepGround.normal;
  758.                     return true;
  759.                 }
  760.  
  761.                 return false;
  762.             }
  763.  
  764.  
  765.             if (farGround != null)
  766.             {
  767.                 groundNormal = farGround.normal;
  768.             }
  769.             else
  770.             {
  771.                 groundNormal = primaryGround.normal;
  772.             }
  773.  
  774.             return true;
  775.         }
  776.  
  777.         /// <summary>
  778.         /// To help the controller smoothly "fall" off surfaces and not hang on the edge of ledges,
  779.         /// check that the ground below us is "steady", or that the controller is not standing
  780.         /// on too extreme of a ledge
  781.         /// </summary>
  782.         /// <param name="normal">Normal of the surface to test against</param>
  783.         /// <param name="point">Point of contact with the surface</param>
  784.         /// <returns>True if the ground is steady</returns>
  785.         private bool OnSteadyGround(Vector3 normal, Vector3 point)
  786.         {
  787.             float angle = Vector3.Angle(normal, controller.up);
  788.  
  789.             float angleRatio = angle / groundingUpperBoundAngle;
  790.  
  791.             float distanceRatio = Mathf.Lerp(groundingMinPercentFromcenter, groundingMaxPercentFromCenter, angleRatio);
  792.  
  793.             Vector3 p = Math3d.ProjectPointOnPlane(controller.up, controller.transform.position, point);
  794.  
  795.             float distanceFromCenter = Vector3.Distance(p, controller.transform.position);
  796.  
  797.             return distanceFromCenter <= distanceRatio * controller.radius;
  798.         }
  799.  
  800.         public Vector3 PrimaryNormal()
  801.         {
  802.             return primaryGround.normal;
  803.         }
  804.  
  805.         public float Distance()
  806.         {
  807.             return primaryGround.distance;
  808.         }
  809.  
  810.         public void DebugGround(bool primary, bool near, bool far, bool flush, bool step)
  811.         {
  812.             if (primary && primaryGround != null)
  813.             {
  814.                 DebugDraw.DrawVector(primaryGround.point, primaryGround.normal, 2.0f, 1.0f, Color.yellow, 0, false);
  815.             }
  816.  
  817.             if (near && nearGround != null)
  818.             {
  819.                 DebugDraw.DrawVector(nearGround.point, nearGround.normal, 2.0f, 1.0f, Color.blue, 0, false);
  820.             }
  821.  
  822.             if (far && farGround != null)
  823.             {
  824.                 DebugDraw.DrawVector(farGround.point, farGround.normal, 2.0f, 1.0f, Color.red, 0, false);
  825.             }
  826.  
  827.             if (flush && flushGround != null)
  828.             {
  829.                 DebugDraw.DrawVector(flushGround.point, flushGround.normal, 2.0f, 1.0f, Color.cyan, 0, false);
  830.             }
  831.  
  832.             if (step && stepGround != null)
  833.             {
  834.                 DebugDraw.DrawVector(stepGround.point, stepGround.normal, 2.0f, 1.0f, Color.green, 0, false);
  835.             }
  836.         }
  837.  
  838.         /// <summary>
  839.         /// Provides raycast data based on where a SphereCast would contact the specified normal
  840.         /// Raycasting downwards from a point along the controller's bottom sphere, based on the provided
  841.         /// normal
  842.         /// </summary>
  843.         /// <param name="groundNormal">Normal of a triangle assumed to be directly below the controller</param>
  844.         /// <param name="hit">Simulated SphereCast data</param>
  845.         /// <returns>True if the raycast is successful</returns>
  846.         private bool SimulateSphereCast(Vector3 groundNormal, out RaycastHit hit)
  847.         {
  848.             float groundAngle = Vector3.Angle(groundNormal, controller.up) * Mathf.Deg2Rad;
  849.  
  850.             Vector3 secondaryOrigin = controller.transform.position + controller.up * Tolerance;
  851.  
  852.             if (!Mathf.Approximately(groundAngle, 0))
  853.             {
  854.                 float horizontal = Mathf.Sin(groundAngle) * controller.radius;
  855.                 float vertical = (1.0f - Mathf.Cos(groundAngle)) * controller.radius;
  856.  
  857.                 // Retrieve a vector pointing up the slope
  858.                 Vector3 r2 = Vector3.Cross(groundNormal, controller.down);
  859.                 Vector3 v2 = -Vector3.Cross(r2, groundNormal);
  860.  
  861.                 secondaryOrigin += Math3d.ProjectVectorOnPlane(controller.up, v2).normalized * horizontal + controller.up * vertical;
  862.             }
  863.            
  864.             if (Physics.Raycast(secondaryOrigin, controller.down, out hit, Mathf.Infinity, walkable, triggerInteraction))
  865.             {
  866.                 // Remove the tolerance from the distance travelled
  867.                 hit.distance -= Tolerance + TinyTolerance;
  868.  
  869.                 return true;
  870.             }
  871.             else
  872.             {
  873.                 return false;
  874.             }
  875.         }
  876.     }
  877. }
  878.  
  879. [Serializable]
  880. public class CollisionSphere
  881. {
  882.     public float offset;
  883.     public bool isFeet;
  884.     public bool isHead;
  885.  
  886.     public CollisionSphere(float offset, bool isFeet, bool isHead)
  887.     {
  888.         this.offset = offset;
  889.         this.isFeet = isFeet;
  890.         this.isHead = isHead;
  891.     }
  892. }
  893.  
  894. public struct SuperCollision
  895. {
  896.     public CollisionSphere collisionSphere;
  897.     public SuperCollisionType superCollisionType;
  898.     public GameObject gameObject;
  899.     public Vector3 point;
  900.     public Vector3 normal;
  901. }
RAW Paste Data

Adblocker detected! Please consider disabling it...

We've detected AdBlock Plus or some other adblocking software preventing Pastebin.com from fully loading.

We don't have any obnoxious sound, or popup ads, we actively block these annoying types of ads!

Please add Pastebin.com to your ad blocker whitelist or disable your adblocking software.

×