Advertisement
Guest User

Untitled

a guest
May 31st, 2016
341
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 31.51 KB | None | 0 0
  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. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement