diff --git a/Assets/Plugins.meta b/Assets/Plugins.meta
new file mode 100644
index 0000000..d9bb659
--- /dev/null
+++ b/Assets/Plugins.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: f5f3393f99206534a9f7c642a8fba028
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/KinematicCharacterController.meta b/Assets/Plugins/KinematicCharacterController.meta
new file mode 100644
index 0000000..65500f4
--- /dev/null
+++ b/Assets/Plugins/KinematicCharacterController.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 80ae4a9ddbd64d846a0b7de1a75c9f83
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/KinematicCharacterController/Core.meta b/Assets/Plugins/KinematicCharacterController/Core.meta
new file mode 100644
index 0000000..fe4fdf5
--- /dev/null
+++ b/Assets/Plugins/KinematicCharacterController/Core.meta
@@ -0,0 +1,9 @@
+fileFormatVersion: 2
+guid: c8854cb84deea8146b0cc7833ff338fd
+folderAsset: yes
+timeCreated: 1499134826
+licenseType: Store
+DefaultImporter:
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/KinematicCharacterController/Core/Editor.meta b/Assets/Plugins/KinematicCharacterController/Core/Editor.meta
new file mode 100644
index 0000000..965efb3
--- /dev/null
+++ b/Assets/Plugins/KinematicCharacterController/Core/Editor.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: 220667f043f505a4eb52d0208e14818e
+folderAsset: yes
+timeCreated: 1504492780
+licenseType: Store
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/KinematicCharacterController/Core/Editor/KCC.Editor.asmdef b/Assets/Plugins/KinematicCharacterController/Core/Editor/KCC.Editor.asmdef
new file mode 100644
index 0000000..089a778
--- /dev/null
+++ b/Assets/Plugins/KinematicCharacterController/Core/Editor/KCC.Editor.asmdef
@@ -0,0 +1,17 @@
+{
+ "name": "KCC.Editor",
+ "references": [
+ "GUID:1bf68a6e05395544c9eb0c8d1ab94588"
+ ],
+ "includePlatforms": [
+ "Editor"
+ ],
+ "excludePlatforms": [],
+ "allowUnsafeCode": false,
+ "overrideReferences": false,
+ "precompiledReferences": [],
+ "autoReferenced": true,
+ "defineConstraints": [],
+ "versionDefines": [],
+ "noEngineReferences": false
+}
\ No newline at end of file
diff --git a/Assets/Plugins/KinematicCharacterController/Core/Editor/KCC.Editor.asmdef.meta b/Assets/Plugins/KinematicCharacterController/Core/Editor/KCC.Editor.asmdef.meta
new file mode 100644
index 0000000..96c148d
--- /dev/null
+++ b/Assets/Plugins/KinematicCharacterController/Core/Editor/KCC.Editor.asmdef.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 8b7c931d99dfb09428a98748ecfd6169
+AssemblyDefinitionImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/KinematicCharacterController/Core/Editor/KinematicCharacterMotorEditor.cs b/Assets/Plugins/KinematicCharacterController/Core/Editor/KinematicCharacterMotorEditor.cs
new file mode 100644
index 0000000..12eb811
--- /dev/null
+++ b/Assets/Plugins/KinematicCharacterController/Core/Editor/KinematicCharacterMotorEditor.cs
@@ -0,0 +1,28 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using UnityEditor;
+
+namespace KinematicCharacterController
+{
+ [CustomEditor(typeof(KinematicCharacterMotor))]
+ public class KinematicCharacterMotorEditor : Editor
+ {
+ protected virtual void OnSceneGUI()
+ {
+ KinematicCharacterMotor motor = (target as KinematicCharacterMotor);
+ if (motor)
+ {
+ Vector3 characterBottom = motor.transform.position + (motor.Capsule.center + (-Vector3.up * (motor.Capsule.height * 0.5f)));
+
+ Handles.color = Color.yellow;
+ Handles.CircleHandleCap(
+ 0,
+ characterBottom + (motor.transform.up * motor.MaxStepHeight),
+ Quaternion.LookRotation(motor.transform.up, motor.transform.forward),
+ motor.Capsule.radius + 0.1f,
+ EventType.Repaint);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/Plugins/KinematicCharacterController/Core/Editor/KinematicCharacterMotorEditor.cs.meta b/Assets/Plugins/KinematicCharacterController/Core/Editor/KinematicCharacterMotorEditor.cs.meta
new file mode 100644
index 0000000..8a2539c
--- /dev/null
+++ b/Assets/Plugins/KinematicCharacterController/Core/Editor/KinematicCharacterMotorEditor.cs.meta
@@ -0,0 +1,13 @@
+fileFormatVersion: 2
+guid: 7db3ed6d74cbf2b489cab5e1586bed7a
+timeCreated: 1518400757
+licenseType: Store
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/KinematicCharacterController/Core/Editor/ReadOnlyPropertyDrawer.cs b/Assets/Plugins/KinematicCharacterController/Core/Editor/ReadOnlyPropertyDrawer.cs
new file mode 100644
index 0000000..5a1aa7c
--- /dev/null
+++ b/Assets/Plugins/KinematicCharacterController/Core/Editor/ReadOnlyPropertyDrawer.cs
@@ -0,0 +1,21 @@
+using UnityEngine;
+using UnityEditor;
+
+namespace KinematicCharacterController
+{
+ [CustomPropertyDrawer(typeof(ReadOnlyAttribute))]
+ public class ReadOnlyPropertyDrawer : PropertyDrawer
+ {
+ public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
+ {
+ return EditorGUI.GetPropertyHeight(property, label, true);
+ }
+
+ public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
+ {
+ GUI.enabled = false;
+ EditorGUI.PropertyField(position, property, label, true);
+ GUI.enabled = true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/Plugins/KinematicCharacterController/Core/Editor/ReadOnlyPropertyDrawer.cs.meta b/Assets/Plugins/KinematicCharacterController/Core/Editor/ReadOnlyPropertyDrawer.cs.meta
new file mode 100644
index 0000000..3363fc6
--- /dev/null
+++ b/Assets/Plugins/KinematicCharacterController/Core/Editor/ReadOnlyPropertyDrawer.cs.meta
@@ -0,0 +1,13 @@
+fileFormatVersion: 2
+guid: 0d4351ab88853824b85a8f458928d825
+timeCreated: 1504492796
+licenseType: Store
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/KinematicCharacterController/Core/ICharacterController.cs b/Assets/Plugins/KinematicCharacterController/Core/ICharacterController.cs
new file mode 100644
index 0000000..4ae54ac
--- /dev/null
+++ b/Assets/Plugins/KinematicCharacterController/Core/ICharacterController.cs
@@ -0,0 +1,50 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace KinematicCharacterController
+{
+ public interface ICharacterController
+ {
+ ///
+ /// This is called when the motor wants to know what its rotation should be right now
+ ///
+ void UpdateRotation(ref Quaternion currentRotation, float deltaTime);
+ ///
+ /// This is called when the motor wants to know what its velocity should be right now
+ ///
+ void UpdateVelocity(ref Vector3 currentVelocity, float deltaTime);
+ ///
+ /// This is called before the motor does anything
+ ///
+ void BeforeCharacterUpdate(float deltaTime);
+ ///
+ /// This is called after the motor has finished its ground probing, but before PhysicsMover/Velocity/etc.... handling
+ ///
+ void PostGroundingUpdate(float deltaTime);
+ ///
+ /// This is called after the motor has finished everything in its update
+ ///
+ void AfterCharacterUpdate(float deltaTime);
+ ///
+ /// This is called after when the motor wants to know if the collider can be collided with (or if we just go through it)
+ ///
+ bool IsColliderValidForCollisions(Collider coll);
+ ///
+ /// This is called when the motor's ground probing detects a ground hit
+ ///
+ void OnGroundHit(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, ref HitStabilityReport hitStabilityReport);
+ ///
+ /// This is called when the motor's movement logic detects a hit
+ ///
+ void OnMovementHit(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, ref HitStabilityReport hitStabilityReport);
+ ///
+ /// This is called after every move hit, to give you an opportunity to modify the HitStabilityReport to your liking
+ ///
+ void ProcessHitStabilityReport(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, Vector3 atCharacterPosition, Quaternion atCharacterRotation, ref HitStabilityReport hitStabilityReport);
+ ///
+ /// This is called when the character detects discrete collisions (collisions that don't result from the motor's capsuleCasts when moving)
+ ///
+ void OnDiscreteCollisionDetected(Collider hitCollider);
+ }
+}
\ No newline at end of file
diff --git a/Assets/Plugins/KinematicCharacterController/Core/ICharacterController.cs.meta b/Assets/Plugins/KinematicCharacterController/Core/ICharacterController.cs.meta
new file mode 100644
index 0000000..e87f49a
--- /dev/null
+++ b/Assets/Plugins/KinematicCharacterController/Core/ICharacterController.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: f13aa252bd3bf9943b1d65e68d93dcfb
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/KinematicCharacterController/Core/IMoverController.cs b/Assets/Plugins/KinematicCharacterController/Core/IMoverController.cs
new file mode 100644
index 0000000..f582ec1
--- /dev/null
+++ b/Assets/Plugins/KinematicCharacterController/Core/IMoverController.cs
@@ -0,0 +1,14 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace KinematicCharacterController
+{
+ public interface IMoverController
+ {
+ ///
+ /// This is called to let you tell the PhysicsMover where it should be right now
+ ///
+ void UpdateMovement(out Vector3 goalPosition, out Quaternion goalRotation, float deltaTime);
+ }
+}
\ No newline at end of file
diff --git a/Assets/Plugins/KinematicCharacterController/Core/IMoverController.cs.meta b/Assets/Plugins/KinematicCharacterController/Core/IMoverController.cs.meta
new file mode 100644
index 0000000..553f65e
--- /dev/null
+++ b/Assets/Plugins/KinematicCharacterController/Core/IMoverController.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 8ae7eceec45f1be4c8450edc46c0ad36
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/KinematicCharacterController/Core/KCC.asmdef b/Assets/Plugins/KinematicCharacterController/Core/KCC.asmdef
new file mode 100644
index 0000000..2dbd7e7
--- /dev/null
+++ b/Assets/Plugins/KinematicCharacterController/Core/KCC.asmdef
@@ -0,0 +1,3 @@
+{
+ "name": "KCC"
+}
diff --git a/Assets/Plugins/KinematicCharacterController/Core/KCC.asmdef.meta b/Assets/Plugins/KinematicCharacterController/Core/KCC.asmdef.meta
new file mode 100644
index 0000000..2de9d19
--- /dev/null
+++ b/Assets/Plugins/KinematicCharacterController/Core/KCC.asmdef.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 1bf68a6e05395544c9eb0c8d1ab94588
+AssemblyDefinitionImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/KinematicCharacterController/Core/KCCSettings.cs b/Assets/Plugins/KinematicCharacterController/Core/KCCSettings.cs
new file mode 100644
index 0000000..23e6848
--- /dev/null
+++ b/Assets/Plugins/KinematicCharacterController/Core/KCCSettings.cs
@@ -0,0 +1,33 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace KinematicCharacterController
+{
+ [CreateAssetMenu]
+ public class KCCSettings : ScriptableObject
+ {
+ ///
+ /// Determines if the system simulates automatically.
+ /// If true, the simulation is done on FixedUpdate
+ ///
+ [Tooltip("Determines if the system simulates automatically. If true, the simulation is done on FixedUpdate")]
+ public bool AutoSimulation = true;
+ ///
+ /// Should interpolation of characters and PhysicsMovers be handled
+ ///
+ [Tooltip("Should interpolation of characters and PhysicsMovers be handled")]
+ public bool Interpolate = true;
+ ///
+
+ /// Initial capacity of the system's list of Motors (will resize automatically if needed, but setting a high initial capacity can help preventing GC allocs)
+ ///
+ [Tooltip("Initial capacity of the system's list of Motors (will resize automatically if needed, but setting a high initial capacity can help preventing GC allocs)")]
+ public int MotorsListInitialCapacity = 100;
+ ///
+ /// Initial capacity of the system's list of Movers (will resize automatically if needed, but setting a high initial capacity can help preventing GC allocs)
+ ///
+ [Tooltip("Initial capacity of the system's list of Movers (will resize automatically if needed, but setting a high initial capacity can help preventing GC allocs)")]
+ public int MoversListInitialCapacity = 100;
+ }
+}
\ No newline at end of file
diff --git a/Assets/Plugins/KinematicCharacterController/Core/KCCSettings.cs.meta b/Assets/Plugins/KinematicCharacterController/Core/KCCSettings.cs.meta
new file mode 100644
index 0000000..daa4144
--- /dev/null
+++ b/Assets/Plugins/KinematicCharacterController/Core/KCCSettings.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 2b43d8459fe4812488b2c47ac60e41c3
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/KinematicCharacterController/Core/KinematicCharacterMotor.cs b/Assets/Plugins/KinematicCharacterController/Core/KinematicCharacterMotor.cs
new file mode 100644
index 0000000..bd04d3a
--- /dev/null
+++ b/Assets/Plugins/KinematicCharacterController/Core/KinematicCharacterMotor.cs
@@ -0,0 +1,2689 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace KinematicCharacterController
+{
+ public enum RigidbodyInteractionType
+ {
+ None,
+ Kinematic,
+ SimulatedDynamic
+ }
+
+ public enum StepHandlingMethod
+ {
+ None,
+ Standard,
+ Extra
+ }
+
+ public enum MovementSweepState
+ {
+ Initial,
+ AfterFirstHit,
+ FoundBlockingCrease,
+ FoundBlockingCorner,
+ }
+
+ ///
+ /// Represents the entire state of a character motor that is pertinent for simulation.
+ /// Use this to save state or revert to past state
+ ///
+ [System.Serializable]
+ public struct KinematicCharacterMotorState
+ {
+ public Vector3 Position;
+ public Quaternion Rotation;
+ public Vector3 BaseVelocity;
+
+ public bool MustUnground;
+ public float MustUngroundTime;
+ public bool LastMovementIterationFoundAnyGround;
+ public CharacterTransientGroundingReport GroundingStatus;
+
+ public Rigidbody AttachedRigidbody;
+ public Vector3 AttachedRigidbodyVelocity;
+ }
+
+ ///
+ /// Describes an overlap between the character capsule and another collider
+ ///
+ public struct OverlapResult
+ {
+ public Vector3 Normal;
+ public Collider Collider;
+
+ public OverlapResult(Vector3 normal, Collider collider)
+ {
+ Normal = normal;
+ Collider = collider;
+ }
+ }
+
+ ///
+ /// Contains all the information for the motor's grounding status
+ ///
+ public struct CharacterGroundingReport
+ {
+ public bool FoundAnyGround;
+ public bool IsStableOnGround;
+ public bool SnappingPrevented;
+ public Vector3 GroundNormal;
+ public Vector3 InnerGroundNormal;
+ public Vector3 OuterGroundNormal;
+
+ public Collider GroundCollider;
+ public Vector3 GroundPoint;
+
+ public void CopyFrom(CharacterTransientGroundingReport transientGroundingReport)
+ {
+ FoundAnyGround = transientGroundingReport.FoundAnyGround;
+ IsStableOnGround = transientGroundingReport.IsStableOnGround;
+ SnappingPrevented = transientGroundingReport.SnappingPrevented;
+ GroundNormal = transientGroundingReport.GroundNormal;
+ InnerGroundNormal = transientGroundingReport.InnerGroundNormal;
+ OuterGroundNormal = transientGroundingReport.OuterGroundNormal;
+
+ GroundCollider = null;
+ GroundPoint = Vector3.zero;
+ }
+ }
+
+ ///
+ /// Contains the simulation-relevant information for the motor's grounding status
+ ///
+ public struct CharacterTransientGroundingReport
+ {
+ public bool FoundAnyGround;
+ public bool IsStableOnGround;
+ public bool SnappingPrevented;
+ public Vector3 GroundNormal;
+ public Vector3 InnerGroundNormal;
+ public Vector3 OuterGroundNormal;
+
+ public void CopyFrom(CharacterGroundingReport groundingReport)
+ {
+ FoundAnyGround = groundingReport.FoundAnyGround;
+ IsStableOnGround = groundingReport.IsStableOnGround;
+ SnappingPrevented = groundingReport.SnappingPrevented;
+ GroundNormal = groundingReport.GroundNormal;
+ InnerGroundNormal = groundingReport.InnerGroundNormal;
+ OuterGroundNormal = groundingReport.OuterGroundNormal;
+ }
+ }
+
+ ///
+ /// Contains all the information from a hit stability evaluation
+ ///
+ public struct HitStabilityReport
+ {
+ public bool IsStable;
+
+ public bool FoundInnerNormal;
+ public Vector3 InnerNormal;
+ public bool FoundOuterNormal;
+ public Vector3 OuterNormal;
+
+ public bool ValidStepDetected;
+ public Collider SteppedCollider;
+
+ public bool LedgeDetected;
+ public bool IsOnEmptySideOfLedge;
+ public float DistanceFromLedge;
+ public bool IsMovingTowardsEmptySideOfLedge;
+ public Vector3 LedgeGroundNormal;
+ public Vector3 LedgeRightDirection;
+ public Vector3 LedgeFacingDirection;
+ }
+
+ ///
+ /// Contains the information of hit rigidbodies during the movement phase, so they can be processed afterwards
+ ///
+ public struct RigidbodyProjectionHit
+ {
+ public Rigidbody Rigidbody;
+ public Vector3 HitPoint;
+ public Vector3 EffectiveHitNormal;
+ public Vector3 HitVelocity;
+ public bool StableOnHit;
+ }
+
+ ///
+ /// Component that manages character collisions and movement solving
+ ///
+ [RequireComponent(typeof(CapsuleCollider))]
+ public class KinematicCharacterMotor : MonoBehaviour
+ {
+#pragma warning disable 0414
+ [Header("Components")]
+ ///
+ /// The capsule collider of this motor
+ ///
+ [ReadOnly]
+ public CapsuleCollider Capsule;
+
+ [Header("Capsule Settings")]
+ ///
+ /// Radius of the character's capsule
+ ///
+ [SerializeField]
+ [Tooltip("Radius of the Character Capsule")]
+ private float CapsuleRadius = 0.5f;
+ ///
+ /// Height of the character's capsule
+ ///
+ [SerializeField]
+ [Tooltip("Height of the Character Capsule")]
+ private float CapsuleHeight = 2f;
+ ///
+ /// Local y position of the character's capsule center
+ ///
+ [SerializeField]
+ [Tooltip("Height of the Character Capsule")]
+ private float CapsuleYOffset = 1f;
+ ///
+ /// Physics material of the character's capsule
+ ///
+ [SerializeField]
+ [Tooltip("Physics material of the Character Capsule (Does not affect character movement. Only affects things colliding with it)")]
+#pragma warning disable 0649
+ private PhysicMaterial CapsulePhysicsMaterial;
+#pragma warning restore 0649
+
+
+ [Header("Grounding settings")]
+ ///
+ /// Increases the range of ground detection, to allow snapping to ground at very high speeds
+ ///
+ [Tooltip("Increases the range of ground detection, to allow snapping to ground at very high speeds")]
+ public float GroundDetectionExtraDistance = 0f;
+ ///
+ /// Maximum slope angle on which the character can be stable
+ ///
+ [Range(0f, 89f)]
+ [Tooltip("Maximum slope angle on which the character can be stable")]
+ public float MaxStableSlopeAngle = 60f;
+ ///
+ /// Which layers can the character be considered stable on
+ ///
+ [Tooltip("Which layers can the character be considered stable on")]
+ public LayerMask StableGroundLayers = -1;
+ ///
+ /// Notifies the Character Controller when discrete collisions are detected
+ ///
+ [Tooltip("Notifies the Character Controller when discrete collisions are detected")]
+ public bool DiscreteCollisionEvents = false;
+
+
+ [Header("Step settings")]
+ ///
+ /// Handles properly detecting grounding status on steps, but has a performance cost.
+ ///
+ [Tooltip("Handles properly detecting grounding status on steps, but has a performance cost.")]
+ public StepHandlingMethod StepHandling = StepHandlingMethod.Standard;
+ ///
+ /// Maximum height of a step which the character can climb
+ ///
+ [Tooltip("Maximum height of a step which the character can climb")]
+ public float MaxStepHeight = 0.5f;
+ ///
+ /// Can the character step up obstacles even if it is not currently stable?
+ ///
+ [Tooltip("Can the character step up obstacles even if it is not currently stable?")]
+ public bool AllowSteppingWithoutStableGrounding = false;
+ ///
+ /// Minimum length of a step that the character can step on (used in Extra stepping method. Use this to let the character step on steps that are smaller that its radius
+ ///
+ [Tooltip("Minimum length of a step that the character can step on (used in Extra stepping method). Use this to let the character step on steps that are smaller that its radius")]
+ public float MinRequiredStepDepth = 0.1f;
+
+
+ [Header("Ledge settings")]
+ ///
+ /// Handles properly detecting ledge information and grounding status, but has a performance cost.
+ ///
+ [Tooltip("Handles properly detecting ledge information and grounding status, but has a performance cost.")]
+ public bool LedgeAndDenivelationHandling = true;
+ ///
+ /// The distance from the capsule central axis at which the character can stand on a ledge and still be stable
+ ///
+ [Tooltip("The distance from the capsule central axis at which the character can stand on a ledge and still be stable")]
+ public float MaxStableDistanceFromLedge = 0.5f;
+ ///
+ /// Prevents snapping to ground on ledges beyond a certain velocity
+ ///
+ [Tooltip("Prevents snapping to ground on ledges beyond a certain velocity")]
+ public float MaxVelocityForLedgeSnap = 0f;
+ ///
+ /// The maximun downward slope angle change that the character can be subjected to and still be snapping to the ground
+ ///
+ [Tooltip("The maximun downward slope angle change that the character can be subjected to and still be snapping to the ground")]
+ [Range(1f, 180f)]
+ public float MaxStableDenivelationAngle = 180f;
+
+
+ [Header("Rigidbody interaction settings")]
+ ///
+ /// Handles properly being pushed by and standing on PhysicsMovers or dynamic rigidbodies. Also handles pushing dynamic rigidbodies
+ ///
+ [Tooltip("Handles properly being pushed by and standing on PhysicsMovers or dynamic rigidbodies. Also handles pushing dynamic rigidbodies")]
+ public bool InteractiveRigidbodyHandling = true;
+ ///
+ /// How the character interacts with non-kinematic rigidbodies. \"Kinematic\" mode means the character pushes the rigidbodies with infinite force (as a kinematic body would). \"SimulatedDynamic\" pushes the rigidbodies with a simulated mass value.
+ ///
+ [Tooltip("How the character interacts with non-kinematic rigidbodies. \"Kinematic\" mode means the character pushes the rigidbodies with infinite force (as a kinematic body would). \"SimulatedDynamic\" pushes the rigidbodies with a simulated mass value.")]
+ public RigidbodyInteractionType RigidbodyInteractionType;
+ [Tooltip("Mass used for pushing bodies")]
+ public float SimulatedCharacterMass = 1f;
+ ///
+ /// Determines if the character preserves moving platform velocities when de-grounding from them
+ ///
+ [Tooltip("Determines if the character preserves moving platform velocities when de-grounding from them")]
+ public bool PreserveAttachedRigidbodyMomentum = true;
+
+
+ [Header("Constraints settings")]
+ ///
+ /// Determines if the character's movement uses the planar constraint
+ ///
+ [Tooltip("Determines if the character's movement uses the planar constraint")]
+ public bool HasPlanarConstraint = false;
+ ///
+ /// Defines the plane that the character's movement is constrained on, if HasMovementConstraintPlane is active
+ ///
+ [Tooltip("Defines the plane that the character's movement is constrained on, if HasMovementConstraintPlane is active")]
+ public Vector3 PlanarConstraintAxis = Vector3.forward;
+
+ [Header("Other settings")]
+ ///
+ /// How many times can we sweep for movement per update
+ ///
+ [Tooltip("How many times can we sweep for movement per update")]
+ public int MaxMovementIterations = 5;
+ ///
+ /// How many times can we check for decollision per update
+ ///
+ [Tooltip("How many times can we check for decollision per update")]
+ public int MaxDecollisionIterations = 1;
+ ///
+ /// Checks for overlaps before casting movement, making sure all collisions are detected even when already intersecting geometry (has a performance cost, but provides safety against tunneling through colliders)
+ ///
+ [Tooltip("Checks for overlaps before casting movement, making sure all collisions are detected even when already intersecting geometry (has a performance cost, but provides safety against tunneling through colliders)")]
+ public bool CheckMovementInitialOverlaps = true;
+ ///
+ /// Sets the velocity to zero if exceed max movement iterations
+ ///
+ [Tooltip("Sets the velocity to zero if exceed max movement iterations")]
+ public bool KillVelocityWhenExceedMaxMovementIterations = true;
+ ///
+ /// Sets the remaining movement to zero if exceed max movement iterations
+ ///
+ [Tooltip("Sets the remaining movement to zero if exceed max movement iterations")]
+ public bool KillRemainingMovementWhenExceedMaxMovementIterations = true;
+
+ ///
+ /// Contains the current grounding information
+ ///
+ [System.NonSerialized]
+ public CharacterGroundingReport GroundingStatus = new CharacterGroundingReport();
+ ///
+ /// Contains the previous grounding information
+ ///
+ [System.NonSerialized]
+ public CharacterTransientGroundingReport LastGroundingStatus = new CharacterTransientGroundingReport();
+ ///
+ /// Specifies the LayerMask that the character's movement algorithm can detect collisions with. By default, this uses the rigidbody's layer's collision matrix
+ ///
+ [System.NonSerialized]
+ public LayerMask CollidableLayers = -1;
+
+ ///
+ /// The Transform of the character motor
+ ///
+ public Transform Transform { get { return _transform; } }
+ private Transform _transform;
+ ///
+ /// The character's goal position in its movement calculations (always up-to-date during the character update phase)
+ ///
+ public Vector3 TransientPosition { get { return _transientPosition; } }
+ private Vector3 _transientPosition;
+ ///
+ /// The character's up direction (always up-to-date during the character update phase)
+ ///
+ public Vector3 CharacterUp { get { return _characterUp; } }
+ private Vector3 _characterUp;
+ ///
+ /// The character's forward direction (always up-to-date during the character update phase)
+ ///
+ public Vector3 CharacterForward { get { return _characterForward; } }
+ private Vector3 _characterForward;
+ ///
+ /// The character's right direction (always up-to-date during the character update phase)
+ ///
+ public Vector3 CharacterRight { get { return _characterRight; } }
+ private Vector3 _characterRight;
+ ///
+ /// The character's position before the movement calculations began
+ ///
+ public Vector3 InitialSimulationPosition { get { return _initialSimulationPosition; } }
+ private Vector3 _initialSimulationPosition;
+ ///
+ /// The character's rotation before the movement calculations began
+ ///
+ public Quaternion InitialSimulationRotation { get { return _initialSimulationRotation; } }
+ private Quaternion _initialSimulationRotation;
+ ///
+ /// Represents the Rigidbody to stay attached to
+ ///
+ public Rigidbody AttachedRigidbody { get { return _attachedRigidbody; } }
+ private Rigidbody _attachedRigidbody;
+ ///
+ /// Vector3 from the character transform position to the capsule center
+ ///
+ public Vector3 CharacterTransformToCapsuleCenter { get { return _characterTransformToCapsuleCenter; } }
+ private Vector3 _characterTransformToCapsuleCenter;
+ ///
+ /// Vector3 from the character transform position to the capsule bottom
+ ///
+ public Vector3 CharacterTransformToCapsuleBottom { get { return _characterTransformToCapsuleBottom; } }
+ private Vector3 _characterTransformToCapsuleBottom;
+ ///
+ /// Vector3 from the character transform position to the capsule top
+ ///
+ public Vector3 CharacterTransformToCapsuleTop { get { return _characterTransformToCapsuleTop; } }
+ private Vector3 _characterTransformToCapsuleTop;
+ ///
+ /// Vector3 from the character transform position to the capsule bottom hemi center
+ ///
+ public Vector3 CharacterTransformToCapsuleBottomHemi { get { return _characterTransformToCapsuleBottomHemi; } }
+ private Vector3 _characterTransformToCapsuleBottomHemi;
+ ///
+ /// Vector3 from the character transform position to the capsule top hemi center
+ ///
+ public Vector3 CharacterTransformToCapsuleTopHemi { get { return _characterTransformToCapsuleTopHemi; } }
+ private Vector3 _characterTransformToCapsuleTopHemi;
+ ///
+ /// The character's velocity resulting from standing on rigidbodies or PhysicsMover
+ ///
+ public Vector3 AttachedRigidbodyVelocity { get { return _attachedRigidbodyVelocity; } }
+ private Vector3 _attachedRigidbodyVelocity;
+ ///
+ /// The number of overlaps detected so far during character update (is reset at the beginning of the update)
+ ///
+ public int OverlapsCount { get { return _overlapsCount; } }
+ private int _overlapsCount;
+ ///
+ /// The overlaps detected so far during character update
+ ///
+ public OverlapResult[] Overlaps { get { return _overlaps; } }
+ private OverlapResult[] _overlaps = new OverlapResult[MaxRigidbodyOverlapsCount];
+
+ ///
+ /// The motor's assigned controller
+ ///
+ [NonSerialized]
+ public ICharacterController CharacterController;
+ ///
+ /// Did the motor's last swept collision detection find a ground?
+ ///
+ [NonSerialized]
+ public bool LastMovementIterationFoundAnyGround;
+ ///
+ /// Index of this motor in KinematicCharacterSystem arrays
+ ///
+ [NonSerialized]
+ public int IndexInCharacterSystem;
+ ///
+ /// Remembers initial position before all simulation are done
+ ///
+ [NonSerialized]
+ public Vector3 InitialTickPosition;
+ ///
+ /// Remembers initial rotation before all simulation are done
+ ///
+ [NonSerialized]
+ public Quaternion InitialTickRotation;
+ ///
+ /// Specifies a Rigidbody to stay attached to
+ ///
+ [NonSerialized]
+ public Rigidbody AttachedRigidbodyOverride;
+ ///
+ /// The character's velocity resulting from direct movement
+ ///
+ [NonSerialized]
+ public Vector3 BaseVelocity;
+
+ // Private
+ private RaycastHit[] _internalCharacterHits = new RaycastHit[MaxHitsBudget];
+ private Collider[] _internalProbedColliders = new Collider[MaxCollisionBudget];
+ private List _rigidbodiesPushedThisMove = new List(16);
+ private RigidbodyProjectionHit[] _internalRigidbodyProjectionHits = new RigidbodyProjectionHit[MaxRigidbodyOverlapsCount];
+ private Rigidbody _lastAttachedRigidbody;
+ private bool _solveMovementCollisions = true;
+ private bool _solveGrounding = true;
+ private bool _movePositionDirty = false;
+ private Vector3 _movePositionTarget = Vector3.zero;
+ private bool _moveRotationDirty = false;
+ private Quaternion _moveRotationTarget = Quaternion.identity;
+ private bool _lastSolvedOverlapNormalDirty = false;
+ private Vector3 _lastSolvedOverlapNormal = Vector3.forward;
+ private int _rigidbodyProjectionHitCount = 0;
+ private bool _isMovingFromAttachedRigidbody = false;
+ private bool _mustUnground = false;
+ private float _mustUngroundTimeCounter = 0f;
+ private Vector3 _cachedWorldUp = Vector3.up;
+ private Vector3 _cachedWorldForward = Vector3.forward;
+ private Vector3 _cachedWorldRight = Vector3.right;
+ private Vector3 _cachedZeroVector = Vector3.zero;
+
+ private Quaternion _transientRotation;
+ ///
+ /// The character's goal rotation in its movement calculations (always up-to-date during the character update phase)
+ ///
+ public Quaternion TransientRotation
+ {
+ get
+ {
+ return _transientRotation;
+ }
+ private set
+ {
+ _transientRotation = value;
+ _characterUp = _transientRotation * _cachedWorldUp;
+ _characterForward = _transientRotation * _cachedWorldForward;
+ _characterRight = _transientRotation * _cachedWorldRight;
+ }
+ }
+
+ ///
+ /// The character's total velocity, including velocity from standing on rigidbodies or PhysicsMover
+ ///
+ public Vector3 Velocity
+ {
+ get
+ {
+ return BaseVelocity + _attachedRigidbodyVelocity;
+ }
+ }
+
+ // Warning: Don't touch these constants unless you know exactly what you're doing!
+ public const int MaxHitsBudget = 16;
+ public const int MaxCollisionBudget = 16;
+ public const int MaxGroundingSweepIterations = 2;
+ public const int MaxSteppingSweepIterations = 3;
+ public const int MaxRigidbodyOverlapsCount = 16;
+ public const float CollisionOffset = 0.01f;
+ public const float GroundProbeReboundDistance = 0.02f;
+ public const float MinimumGroundProbingDistance = 0.005f;
+ public const float GroundProbingBackstepDistance = 0.1f;
+ public const float SweepProbingBackstepDistance = 0.002f;
+ public const float SecondaryProbesVertical = 0.02f;
+ public const float SecondaryProbesHorizontal = 0.001f;
+ public const float MinVelocityMagnitude = 0.01f;
+ public const float SteppingForwardDistance = 0.03f;
+ public const float MinDistanceForLedge = 0.05f;
+ public const float CorrelationForVerticalObstruction = 0.01f;
+ public const float ExtraSteppingForwardDistance = 0.01f;
+ public const float ExtraStepHeightPadding = 0.01f;
+#pragma warning restore 0414
+
+ private void OnEnable()
+ {
+ KinematicCharacterSystem.EnsureCreation();
+ KinematicCharacterSystem.RegisterCharacterMotor(this);
+ }
+
+ private void OnDisable()
+ {
+ KinematicCharacterSystem.UnregisterCharacterMotor(this);
+ }
+
+ private void Reset()
+ {
+ ValidateData();
+ }
+
+ private void OnValidate()
+ {
+ ValidateData();
+ }
+
+ [ContextMenu("Remove Component")]
+ private void HandleRemoveComponent()
+ {
+ CapsuleCollider tmpCapsule = gameObject.GetComponent();
+ DestroyImmediate(this);
+ DestroyImmediate(tmpCapsule);
+ }
+
+ ///
+ /// Handle validating all required values
+ ///
+ public void ValidateData()
+ {
+ Capsule = GetComponent();
+ CapsuleRadius = Mathf.Clamp(CapsuleRadius, 0f, CapsuleHeight * 0.5f);
+ Capsule.direction = 1;
+ Capsule.sharedMaterial = CapsulePhysicsMaterial;
+ SetCapsuleDimensions(CapsuleRadius, CapsuleHeight, CapsuleYOffset);
+
+ MaxStepHeight = Mathf.Clamp(MaxStepHeight, 0f, Mathf.Infinity);
+ MinRequiredStepDepth = Mathf.Clamp(MinRequiredStepDepth, 0f, CapsuleRadius);
+ MaxStableDistanceFromLedge = Mathf.Clamp(MaxStableDistanceFromLedge, 0f, CapsuleRadius);
+
+ transform.localScale = Vector3.one;
+
+#if UNITY_EDITOR
+ Capsule.hideFlags = HideFlags.NotEditable;
+ if (!Mathf.Approximately(transform.lossyScale.x, 1f) || !Mathf.Approximately(transform.lossyScale.y, 1f) || !Mathf.Approximately(transform.lossyScale.z, 1f))
+ {
+ Debug.LogError("Character's lossy scale is not (1,1,1). This is not allowed. Make sure the character's transform and all of its parents have a (1,1,1) scale.", this.gameObject);
+ }
+#endif
+ }
+
+ ///
+ /// Sets whether or not the capsule collider will detect collisions
+ ///
+ public void SetCapsuleCollisionsActivation(bool collisionsActive)
+ {
+ Capsule.isTrigger = !collisionsActive;
+ }
+
+ ///
+ /// Sets whether or not the motor will solve collisions when moving (or moved onto)
+ ///
+ public void SetMovementCollisionsSolvingActivation(bool movementCollisionsSolvingActive)
+ {
+ _solveMovementCollisions = movementCollisionsSolvingActive;
+ }
+
+ ///
+ /// Sets whether or not grounding will be evaluated for all hits
+ ///
+ public void SetGroundSolvingActivation(bool stabilitySolvingActive)
+ {
+ _solveGrounding = stabilitySolvingActive;
+ }
+
+ ///
+ /// Sets the character's position directly
+ ///
+ public void SetPosition(Vector3 position, bool bypassInterpolation = true)
+ {
+ _transform.position = position;
+ _initialSimulationPosition = position;
+ _transientPosition = position;
+
+ if (bypassInterpolation)
+ {
+ InitialTickPosition = position;
+ }
+ }
+
+ ///
+ /// Sets the character's rotation directly
+ ///
+ public void SetRotation(Quaternion rotation, bool bypassInterpolation = true)
+ {
+ _transform.rotation = rotation;
+ _initialSimulationRotation = rotation;
+ TransientRotation = rotation;
+
+ if (bypassInterpolation)
+ {
+ InitialTickRotation = rotation;
+ }
+ }
+
+ ///
+ /// Sets the character's position and rotation directly
+ ///
+ public void SetPositionAndRotation(Vector3 position, Quaternion rotation, bool bypassInterpolation = true)
+ {
+ _transform.SetPositionAndRotation(position, rotation);
+ _initialSimulationPosition = position;
+ _initialSimulationRotation = rotation;
+ _transientPosition = position;
+ TransientRotation = rotation;
+
+ if (bypassInterpolation)
+ {
+ InitialTickPosition = position;
+ InitialTickRotation = rotation;
+ }
+ }
+
+ ///
+ /// Moves the character position, taking all movement collision solving int account. The actual move is done the next time the motor updates are called
+ ///
+ public void MoveCharacter(Vector3 toPosition)
+ {
+ _movePositionDirty = true;
+ _movePositionTarget = toPosition;
+ }
+
+ ///
+ /// Moves the character rotation. The actual move is done the next time the motor updates are called
+ ///
+ public void RotateCharacter(Quaternion toRotation)
+ {
+ _moveRotationDirty = true;
+ _moveRotationTarget = toRotation;
+ }
+
+ ///
+ /// Returns all the state information of the motor that is pertinent for simulation
+ ///
+ public KinematicCharacterMotorState GetState()
+ {
+ KinematicCharacterMotorState state = new KinematicCharacterMotorState();
+
+ state.Position = _transientPosition;
+ state.Rotation = _transientRotation;
+
+ state.BaseVelocity = BaseVelocity;
+ state.AttachedRigidbodyVelocity = _attachedRigidbodyVelocity;
+
+ state.MustUnground = _mustUnground;
+ state.MustUngroundTime = _mustUngroundTimeCounter;
+ state.LastMovementIterationFoundAnyGround = LastMovementIterationFoundAnyGround;
+ state.GroundingStatus.CopyFrom(GroundingStatus);
+ state.AttachedRigidbody = _attachedRigidbody;
+
+ return state;
+ }
+
+ ///
+ /// Applies a motor state instantly
+ ///
+ public void ApplyState(KinematicCharacterMotorState state, bool bypassInterpolation = true)
+ {
+ SetPositionAndRotation(state.Position, state.Rotation, bypassInterpolation);
+
+ BaseVelocity = state.BaseVelocity;
+ _attachedRigidbodyVelocity = state.AttachedRigidbodyVelocity;
+
+ _mustUnground = state.MustUnground;
+ _mustUngroundTimeCounter = state.MustUngroundTime;
+ LastMovementIterationFoundAnyGround = state.LastMovementIterationFoundAnyGround;
+ GroundingStatus.CopyFrom(state.GroundingStatus);
+ _attachedRigidbody = state.AttachedRigidbody;
+ }
+
+ ///
+ /// Resizes capsule. ALso caches importand capsule size data
+ ///
+ public void SetCapsuleDimensions(float radius, float height, float yOffset)
+ {
+ height = Mathf.Max(height, (radius * 2f) + 0.01f); // Safety to prevent invalid capsule geometries
+
+ CapsuleRadius = radius;
+ CapsuleHeight = height;
+ CapsuleYOffset = yOffset;
+
+ Capsule.radius = CapsuleRadius;
+ Capsule.height = Mathf.Clamp(CapsuleHeight, CapsuleRadius * 2f, CapsuleHeight);
+ Capsule.center = new Vector3(0f, CapsuleYOffset, 0f);
+
+ _characterTransformToCapsuleCenter = Capsule.center;
+ _characterTransformToCapsuleBottom = Capsule.center + (-_cachedWorldUp * (Capsule.height * 0.5f));
+ _characterTransformToCapsuleTop = Capsule.center + (_cachedWorldUp * (Capsule.height * 0.5f));
+ _characterTransformToCapsuleBottomHemi = Capsule.center + (-_cachedWorldUp * (Capsule.height * 0.5f)) + (_cachedWorldUp * Capsule.radius);
+ _characterTransformToCapsuleTopHemi = Capsule.center + (_cachedWorldUp * (Capsule.height * 0.5f)) + (-_cachedWorldUp * Capsule.radius);
+ }
+
+ private void Awake()
+ {
+ _transform = this.transform;
+ ValidateData();
+
+ _transientPosition = _transform.position;
+ TransientRotation = _transform.rotation;
+
+ // Build CollidableLayers mask
+ CollidableLayers = 0;
+ for (int i = 0; i < 32; i++)
+ {
+ if (!Physics.GetIgnoreLayerCollision(this.gameObject.layer, i))
+ {
+ CollidableLayers |= (1 << i);
+ }
+ }
+
+ SetCapsuleDimensions(CapsuleRadius, CapsuleHeight, CapsuleYOffset);
+ }
+
+ ///
+ /// Update phase 1 is meant to be called after physics movers have calculated their velocities, but
+ /// before they have simulated their goal positions/rotations. It is responsible for:
+ /// - Initializing all values for update
+ /// - Handling MovePosition calls
+ /// - Solving initial collision overlaps
+ /// - Ground probing
+ /// - Handle detecting potential interactable rigidbodies
+ ///
+ public void UpdatePhase1(float deltaTime)
+ {
+ // NaN propagation safety stop
+ if (float.IsNaN(BaseVelocity.x) || float.IsNaN(BaseVelocity.y) || float.IsNaN(BaseVelocity.z))
+ {
+ BaseVelocity = Vector3.zero;
+ }
+ if (float.IsNaN(_attachedRigidbodyVelocity.x) || float.IsNaN(_attachedRigidbodyVelocity.y) || float.IsNaN(_attachedRigidbodyVelocity.z))
+ {
+ _attachedRigidbodyVelocity = Vector3.zero;
+ }
+
+#if UNITY_EDITOR
+ if (!Mathf.Approximately(_transform.lossyScale.x, 1f) || !Mathf.Approximately(_transform.lossyScale.y, 1f) || !Mathf.Approximately(_transform.lossyScale.z, 1f))
+ {
+ Debug.LogError("Character's lossy scale is not (1,1,1). This is not allowed. Make sure the character's transform and all of its parents have a (1,1,1) scale.", this.gameObject);
+ }
+#endif
+
+ _rigidbodiesPushedThisMove.Clear();
+
+ // Before update
+ CharacterController.BeforeCharacterUpdate(deltaTime);
+
+ _transientPosition = _transform.position;
+ TransientRotation = _transform.rotation;
+ _initialSimulationPosition = _transientPosition;
+ _initialSimulationRotation = _transientRotation;
+ _rigidbodyProjectionHitCount = 0;
+ _overlapsCount = 0;
+ _lastSolvedOverlapNormalDirty = false;
+
+ #region Handle Move Position
+ if (_movePositionDirty)
+ {
+ if (_solveMovementCollisions)
+ {
+ Vector3 tmpVelocity = GetVelocityFromMovement(_movePositionTarget - _transientPosition, deltaTime);
+ if (InternalCharacterMove(ref tmpVelocity, deltaTime))
+ {
+ if (InteractiveRigidbodyHandling)
+ {
+ ProcessVelocityForRigidbodyHits(ref tmpVelocity, deltaTime);
+ }
+ }
+ }
+ else
+ {
+ _transientPosition = _movePositionTarget;
+ }
+
+ _movePositionDirty = false;
+ }
+ #endregion
+
+ LastGroundingStatus.CopyFrom(GroundingStatus);
+ GroundingStatus = new CharacterGroundingReport();
+ GroundingStatus.GroundNormal = _characterUp;
+
+ if (_solveMovementCollisions)
+ {
+ #region Resolve initial overlaps
+ Vector3 resolutionDirection = _cachedWorldUp;
+ float resolutionDistance = 0f;
+ int iterationsMade = 0;
+ bool overlapSolved = false;
+ while (iterationsMade < MaxDecollisionIterations && !overlapSolved)
+ {
+ int nbOverlaps = CharacterCollisionsOverlap(_transientPosition, _transientRotation, _internalProbedColliders);
+
+ if (nbOverlaps > 0)
+ {
+ // Solve overlaps that aren't against dynamic rigidbodies or physics movers
+ for (int i = 0; i < nbOverlaps; i++)
+ {
+ if (GetInteractiveRigidbody(_internalProbedColliders[i]) == null)
+ {
+ // Process overlap
+ Transform overlappedTransform = _internalProbedColliders[i].GetComponent();
+ if (Physics.ComputePenetration(
+ Capsule,
+ _transientPosition,
+ _transientRotation,
+ _internalProbedColliders[i],
+ overlappedTransform.position,
+ overlappedTransform.rotation,
+ out resolutionDirection,
+ out resolutionDistance))
+ {
+ // Resolve along obstruction direction
+ HitStabilityReport mockReport = new HitStabilityReport();
+ mockReport.IsStable = IsStableOnNormal(resolutionDirection);
+ resolutionDirection = GetObstructionNormal(resolutionDirection, mockReport.IsStable);
+
+ // Solve overlap
+ Vector3 resolutionMovement = resolutionDirection * (resolutionDistance + CollisionOffset);
+ _transientPosition += resolutionMovement;
+
+ // Remember overlaps
+ if (_overlapsCount < _overlaps.Length)
+ {
+ _overlaps[_overlapsCount] = new OverlapResult(resolutionDirection, _internalProbedColliders[i]);
+ _overlapsCount++;
+ }
+
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ overlapSolved = true;
+ }
+
+ iterationsMade++;
+ }
+ #endregion
+ }
+
+ #region Ground Probing and Snapping
+ // Handle ungrounding
+ if (_solveGrounding)
+ {
+ if (MustUnground())
+ {
+ _transientPosition += _characterUp * (MinimumGroundProbingDistance * 1.5f);
+ }
+ else
+ {
+ // Choose the appropriate ground probing distance
+ float selectedGroundProbingDistance = MinimumGroundProbingDistance;
+ if (!LastGroundingStatus.SnappingPrevented && (LastGroundingStatus.IsStableOnGround || LastMovementIterationFoundAnyGround))
+ {
+ if (StepHandling != StepHandlingMethod.None)
+ {
+ selectedGroundProbingDistance = Mathf.Max(CapsuleRadius, MaxStepHeight);
+ }
+ else
+ {
+ selectedGroundProbingDistance = CapsuleRadius;
+ }
+
+ selectedGroundProbingDistance += GroundDetectionExtraDistance;
+ }
+
+ ProbeGround(ref _transientPosition, _transientRotation, selectedGroundProbingDistance, ref GroundingStatus);
+
+ if (!LastGroundingStatus.IsStableOnGround && GroundingStatus.IsStableOnGround)
+ {
+ // Handle stable landing
+ BaseVelocity = Vector3.ProjectOnPlane(BaseVelocity, CharacterUp);
+ BaseVelocity = GetDirectionTangentToSurface(BaseVelocity, GroundingStatus.GroundNormal) * BaseVelocity.magnitude;
+ }
+ }
+ }
+
+ LastMovementIterationFoundAnyGround = false;
+
+ if (_mustUngroundTimeCounter > 0f)
+ {
+ _mustUngroundTimeCounter -= deltaTime;
+ }
+ _mustUnground = false;
+ #endregion
+
+ if (_solveGrounding)
+ {
+ CharacterController.PostGroundingUpdate(deltaTime);
+ }
+
+ if (InteractiveRigidbodyHandling)
+ {
+ #region Interactive Rigidbody Handling
+ _lastAttachedRigidbody = _attachedRigidbody;
+ if (AttachedRigidbodyOverride)
+ {
+ _attachedRigidbody = AttachedRigidbodyOverride;
+ }
+ else
+ {
+ // Detect interactive rigidbodies from grounding
+ if (GroundingStatus.IsStableOnGround && GroundingStatus.GroundCollider.attachedRigidbody)
+ {
+ Rigidbody interactiveRigidbody = GetInteractiveRigidbody(GroundingStatus.GroundCollider);
+ if (interactiveRigidbody)
+ {
+ _attachedRigidbody = interactiveRigidbody;
+ }
+ }
+ else
+ {
+ _attachedRigidbody = null;
+ }
+ }
+
+ Vector3 tmpVelocityFromCurrentAttachedRigidbody = Vector3.zero;
+ Vector3 tmpAngularVelocityFromCurrentAttachedRigidbody = Vector3.zero;
+ if (_attachedRigidbody)
+ {
+ GetVelocityFromRigidbodyMovement(_attachedRigidbody, _transientPosition, deltaTime, out tmpVelocityFromCurrentAttachedRigidbody, out tmpAngularVelocityFromCurrentAttachedRigidbody);
+ }
+
+ // Conserve momentum when de-stabilized from an attached rigidbody
+ if (PreserveAttachedRigidbodyMomentum && _lastAttachedRigidbody != null && _attachedRigidbody != _lastAttachedRigidbody)
+ {
+ BaseVelocity += _attachedRigidbodyVelocity;
+ BaseVelocity -= tmpVelocityFromCurrentAttachedRigidbody;
+ }
+
+ // Process additionnal Velocity from attached rigidbody
+ _attachedRigidbodyVelocity = _cachedZeroVector;
+ if (_attachedRigidbody)
+ {
+ _attachedRigidbodyVelocity = tmpVelocityFromCurrentAttachedRigidbody;
+
+ // Rotation from attached rigidbody
+ Vector3 newForward = Vector3.ProjectOnPlane(Quaternion.Euler(Mathf.Rad2Deg * tmpAngularVelocityFromCurrentAttachedRigidbody * deltaTime) * _characterForward, _characterUp).normalized;
+ TransientRotation = Quaternion.LookRotation(newForward, _characterUp);
+ }
+
+ // Cancel out horizontal velocity upon landing on an attached rigidbody
+ if (GroundingStatus.GroundCollider &&
+ GroundingStatus.GroundCollider.attachedRigidbody &&
+ GroundingStatus.GroundCollider.attachedRigidbody == _attachedRigidbody &&
+ _attachedRigidbody != null &&
+ _lastAttachedRigidbody == null)
+ {
+ BaseVelocity -= Vector3.ProjectOnPlane(_attachedRigidbodyVelocity, _characterUp);
+ }
+
+ // Movement from Attached Rigidbody
+ if (_attachedRigidbodyVelocity.sqrMagnitude > 0f)
+ {
+ _isMovingFromAttachedRigidbody = true;
+
+ if (_solveMovementCollisions)
+ {
+ // Perform the move from rgdbdy velocity
+ InternalCharacterMove(ref _attachedRigidbodyVelocity, deltaTime);
+ }
+ else
+ {
+ _transientPosition += _attachedRigidbodyVelocity * deltaTime;
+ }
+
+ _isMovingFromAttachedRigidbody = false;
+ }
+ #endregion
+ }
+ }
+
+ ///
+ /// Update phase 2 is meant to be called after physics movers have simulated their goal positions/rotations.
+ /// At the end of this, the TransientPosition/Rotation values will be up-to-date with where the motor should be at the end of its move.
+ /// It is responsible for:
+ /// - Solving Rotation
+ /// - Handle MoveRotation calls
+ /// - Solving potential attached rigidbody overlaps
+ /// - Solving Velocity
+ /// - Applying planar constraint
+ ///
+ public void UpdatePhase2(float deltaTime)
+ {
+ // Handle rotation
+ CharacterController.UpdateRotation(ref _transientRotation, deltaTime);
+ TransientRotation = _transientRotation;
+
+ // Handle move rotation
+ if (_moveRotationDirty)
+ {
+ TransientRotation = _moveRotationTarget;
+ _moveRotationDirty = false;
+ }
+
+ if (_solveMovementCollisions && InteractiveRigidbodyHandling)
+ {
+ if (InteractiveRigidbodyHandling)
+ {
+ #region Solve potential attached rigidbody overlap
+ if (_attachedRigidbody)
+ {
+ float upwardsOffset = Capsule.radius;
+
+ RaycastHit closestHit;
+ if (CharacterGroundSweep(
+ _transientPosition + (_characterUp * upwardsOffset),
+ _transientRotation,
+ -_characterUp,
+ upwardsOffset,
+ out closestHit))
+ {
+ if (closestHit.collider.attachedRigidbody == _attachedRigidbody && IsStableOnNormal(closestHit.normal))
+ {
+ float distanceMovedUp = (upwardsOffset - closestHit.distance);
+ _transientPosition = _transientPosition + (_characterUp * distanceMovedUp) + (_characterUp * CollisionOffset);
+ }
+ }
+ }
+ #endregion
+ }
+
+ if (InteractiveRigidbodyHandling)
+ {
+ #region Resolve overlaps that could've been caused by rotation or physics movers simulation pushing the character
+ Vector3 resolutionDirection = _cachedWorldUp;
+ float resolutionDistance = 0f;
+ int iterationsMade = 0;
+ bool overlapSolved = false;
+ while (iterationsMade < MaxDecollisionIterations && !overlapSolved)
+ {
+ int nbOverlaps = CharacterCollisionsOverlap(_transientPosition, _transientRotation, _internalProbedColliders);
+ if (nbOverlaps > 0)
+ {
+ for (int i = 0; i < nbOverlaps; i++)
+ {
+ // Process overlap
+ Transform overlappedTransform = _internalProbedColliders[i].GetComponent();
+ if (Physics.ComputePenetration(
+ Capsule,
+ _transientPosition,
+ _transientRotation,
+ _internalProbedColliders[i],
+ overlappedTransform.position,
+ overlappedTransform.rotation,
+ out resolutionDirection,
+ out resolutionDistance))
+ {
+ // Resolve along obstruction direction
+ HitStabilityReport mockReport = new HitStabilityReport();
+ mockReport.IsStable = IsStableOnNormal(resolutionDirection);
+ resolutionDirection = GetObstructionNormal(resolutionDirection, mockReport.IsStable);
+
+ // Solve overlap
+ Vector3 resolutionMovement = resolutionDirection * (resolutionDistance + CollisionOffset);
+ _transientPosition += resolutionMovement;
+
+ // If interactiveRigidbody, register as rigidbody hit for velocity
+ if (InteractiveRigidbodyHandling)
+ {
+ Rigidbody probedRigidbody = GetInteractiveRigidbody(_internalProbedColliders[i]);
+ if (probedRigidbody != null)
+ {
+ HitStabilityReport tmpReport = new HitStabilityReport();
+ tmpReport.IsStable = IsStableOnNormal(resolutionDirection);
+ if (tmpReport.IsStable)
+ {
+ LastMovementIterationFoundAnyGround = tmpReport.IsStable;
+ }
+ if (probedRigidbody != _attachedRigidbody)
+ {
+ Vector3 characterCenter = _transientPosition + (_transientRotation * _characterTransformToCapsuleCenter);
+ Vector3 estimatedCollisionPoint = _transientPosition;
+
+
+ StoreRigidbodyHit(
+ probedRigidbody,
+ Velocity,
+ estimatedCollisionPoint,
+ resolutionDirection,
+ tmpReport);
+ }
+ }
+ }
+
+ // Remember overlaps
+ if (_overlapsCount < _overlaps.Length)
+ {
+ _overlaps[_overlapsCount] = new OverlapResult(resolutionDirection, _internalProbedColliders[i]);
+ _overlapsCount++;
+ }
+
+ break;
+ }
+ }
+ }
+ else
+ {
+ overlapSolved = true;
+ }
+
+ iterationsMade++;
+ }
+ #endregion
+ }
+ }
+
+ // Handle velocity
+ CharacterController.UpdateVelocity(ref BaseVelocity, deltaTime);
+
+ //this.CharacterController.UpdateVelocity(ref BaseVelocity, deltaTime);
+ if (BaseVelocity.magnitude < MinVelocityMagnitude)
+ {
+ BaseVelocity = Vector3.zero;
+ }
+
+ #region Calculate Character movement from base velocity
+ // Perform the move from base velocity
+ if (BaseVelocity.sqrMagnitude > 0f)
+ {
+ if (_solveMovementCollisions)
+ {
+ InternalCharacterMove(ref BaseVelocity, deltaTime);
+ }
+ else
+ {
+ _transientPosition += BaseVelocity * deltaTime;
+ }
+ }
+
+ // Process rigidbody hits/overlaps to affect velocity
+ if (InteractiveRigidbodyHandling)
+ {
+ ProcessVelocityForRigidbodyHits(ref BaseVelocity, deltaTime);
+ }
+ #endregion
+
+ // Handle planar constraint
+ if (HasPlanarConstraint)
+ {
+ _transientPosition = _initialSimulationPosition + Vector3.ProjectOnPlane(_transientPosition - _initialSimulationPosition, PlanarConstraintAxis.normalized);
+ }
+
+ // Discrete collision detection
+ if (DiscreteCollisionEvents)
+ {
+ int nbOverlaps = CharacterCollisionsOverlap(_transientPosition, _transientRotation, _internalProbedColliders, CollisionOffset * 2f);
+ for (int i = 0; i < nbOverlaps; i++)
+ {
+ CharacterController.OnDiscreteCollisionDetected(_internalProbedColliders[i]);
+ }
+ }
+
+ CharacterController.AfterCharacterUpdate(deltaTime);
+ }
+
+ ///
+ /// Determines if motor can be considered stable on given slope normal
+ ///
+ private bool IsStableOnNormal(Vector3 normal)
+ {
+ return Vector3.Angle(_characterUp, normal) <= MaxStableSlopeAngle;
+ }
+
+ ///
+ /// Determines if motor can be considered stable on given slope normal
+ ///
+ private bool IsStableWithSpecialCases(ref HitStabilityReport stabilityReport, Vector3 velocity)
+ {
+ if (LedgeAndDenivelationHandling)
+ {
+ if (stabilityReport.LedgeDetected)
+ {
+ if (stabilityReport.IsMovingTowardsEmptySideOfLedge)
+ {
+ // Max snap vel
+ Vector3 velocityOnLedgeNormal = Vector3.Project(velocity, stabilityReport.LedgeFacingDirection);
+ if (velocityOnLedgeNormal.magnitude >= MaxVelocityForLedgeSnap)
+ {
+ return false;
+ }
+ }
+
+ // Distance from ledge
+ if (stabilityReport.IsOnEmptySideOfLedge && stabilityReport.DistanceFromLedge > MaxStableDistanceFromLedge)
+ {
+ return false;
+ }
+ }
+
+ // "Launching" off of slopes of a certain denivelation angle
+ if (LastGroundingStatus.FoundAnyGround && stabilityReport.InnerNormal.sqrMagnitude != 0f && stabilityReport.OuterNormal.sqrMagnitude != 0f)
+ {
+ float denivelationAngle = Vector3.Angle(stabilityReport.InnerNormal, stabilityReport.OuterNormal);
+ if (denivelationAngle > MaxStableDenivelationAngle)
+ {
+ return false;
+ }
+ else
+ {
+ denivelationAngle = Vector3.Angle(LastGroundingStatus.InnerGroundNormal, stabilityReport.OuterNormal);
+ if (denivelationAngle > MaxStableDenivelationAngle)
+ {
+ return false;
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
+ ///
+ /// Probes for valid ground and midifies the input transientPosition if ground snapping occurs
+ ///
+ public void ProbeGround(ref Vector3 probingPosition, Quaternion atRotation, float probingDistance, ref CharacterGroundingReport groundingReport)
+ {
+ if (probingDistance < MinimumGroundProbingDistance)
+ {
+ probingDistance = MinimumGroundProbingDistance;
+ }
+
+ int groundSweepsMade = 0;
+ RaycastHit groundSweepHit = new RaycastHit();
+ bool groundSweepingIsOver = false;
+ Vector3 groundSweepPosition = probingPosition;
+ Vector3 groundSweepDirection = (atRotation * -_cachedWorldUp);
+ float groundProbeDistanceRemaining = probingDistance;
+ while (groundProbeDistanceRemaining > 0 && (groundSweepsMade <= MaxGroundingSweepIterations) && !groundSweepingIsOver)
+ {
+ // Sweep for ground detection
+ if (CharacterGroundSweep(
+ groundSweepPosition, // position
+ atRotation, // rotation
+ groundSweepDirection, // direction
+ groundProbeDistanceRemaining, // distance
+ out groundSweepHit)) // hit
+ {
+ Vector3 targetPosition = groundSweepPosition + (groundSweepDirection * groundSweepHit.distance);
+ HitStabilityReport groundHitStabilityReport = new HitStabilityReport();
+ EvaluateHitStability(groundSweepHit.collider, groundSweepHit.normal, groundSweepHit.point, targetPosition, _transientRotation, BaseVelocity, ref groundHitStabilityReport);
+
+ groundingReport.FoundAnyGround = true;
+ groundingReport.GroundNormal = groundSweepHit.normal;
+ groundingReport.InnerGroundNormal = groundHitStabilityReport.InnerNormal;
+ groundingReport.OuterGroundNormal = groundHitStabilityReport.OuterNormal;
+ groundingReport.GroundCollider = groundSweepHit.collider;
+ groundingReport.GroundPoint = groundSweepHit.point;
+ groundingReport.SnappingPrevented = false;
+
+ // Found stable ground
+ if (groundHitStabilityReport.IsStable)
+ {
+ // Find all scenarios where ground snapping should be canceled
+ groundingReport.SnappingPrevented = !IsStableWithSpecialCases(ref groundHitStabilityReport, BaseVelocity);
+
+ groundingReport.IsStableOnGround = true;
+
+ // Ground snapping
+ if (!groundingReport.SnappingPrevented)
+ {
+ probingPosition = groundSweepPosition + (groundSweepDirection * (groundSweepHit.distance - CollisionOffset));
+ }
+
+ CharacterController.OnGroundHit(groundSweepHit.collider, groundSweepHit.normal, groundSweepHit.point, ref groundHitStabilityReport);
+ groundSweepingIsOver = true;
+ }
+ else
+ {
+ // Calculate movement from this iteration and advance position
+ Vector3 sweepMovement = (groundSweepDirection * groundSweepHit.distance) + ((atRotation * _cachedWorldUp) * Mathf.Max(CollisionOffset, groundSweepHit.distance));
+ groundSweepPosition = groundSweepPosition + sweepMovement;
+
+ // Set remaining distance
+ groundProbeDistanceRemaining = Mathf.Min(GroundProbeReboundDistance, Mathf.Max(groundProbeDistanceRemaining - sweepMovement.magnitude, 0f));
+
+ // Reorient direction
+ groundSweepDirection = Vector3.ProjectOnPlane(groundSweepDirection, groundSweepHit.normal).normalized;
+ }
+ }
+ else
+ {
+ groundSweepingIsOver = true;
+ }
+
+ groundSweepsMade++;
+ }
+ }
+
+ ///
+ /// Forces the character to unground itself on its next grounding update
+ ///
+ public void ForceUnground(float time = 0.1f)
+ {
+ _mustUnground = true;
+ _mustUngroundTimeCounter = time;
+ }
+
+ public bool MustUnground()
+ {
+ return _mustUnground || _mustUngroundTimeCounter > 0f;
+ }
+
+ ///
+ /// Returns the direction adjusted to be tangent to a specified surface normal relatively to the character's up direction.
+ /// Useful for reorienting a direction on a slope without any lateral deviation in trajectory
+ ///
+ public Vector3 GetDirectionTangentToSurface(Vector3 direction, Vector3 surfaceNormal)
+ {
+ Vector3 directionRight = Vector3.Cross(direction, _characterUp);
+ return Vector3.Cross(surfaceNormal, directionRight).normalized;
+ }
+
+ ///
+ /// Moves the character's position by given movement while taking into account all physics simulation, step-handling and
+ /// velocity projection rules that affect the character motor
+ ///
+ /// Returns false if movement could not be solved until the end
+ private bool InternalCharacterMove(ref Vector3 transientVelocity, float deltaTime)
+ {
+ if (deltaTime <= 0f)
+ return false;
+
+ // Planar constraint
+ if (HasPlanarConstraint)
+ {
+ transientVelocity = Vector3.ProjectOnPlane(transientVelocity, PlanarConstraintAxis.normalized);
+ }
+
+ bool wasCompleted = true;
+ Vector3 remainingMovementDirection = transientVelocity.normalized;
+ float remainingMovementMagnitude = transientVelocity.magnitude * deltaTime;
+ Vector3 originalVelocityDirection = remainingMovementDirection;
+ int sweepsMade = 0;
+ bool hitSomethingThisSweepIteration = true;
+ Vector3 tmpMovedPosition = _transientPosition;
+ bool previousHitIsStable = false;
+ Vector3 previousVelocity = _cachedZeroVector;
+ Vector3 previousObstructionNormal = _cachedZeroVector;
+ MovementSweepState sweepState = MovementSweepState.Initial;
+
+ // Project movement against current overlaps before doing the sweeps
+ for (int i = 0; i < _overlapsCount; i++)
+ {
+ Vector3 overlapNormal = _overlaps[i].Normal;
+ if (Vector3.Dot(remainingMovementDirection, overlapNormal) < 0f)
+ {
+ bool stableOnHit = IsStableOnNormal(overlapNormal) && !MustUnground();
+ Vector3 velocityBeforeProjection = transientVelocity;
+ Vector3 obstructionNormal = GetObstructionNormal(overlapNormal, stableOnHit);
+
+ InternalHandleVelocityProjection(
+ stableOnHit,
+ overlapNormal,
+ obstructionNormal,
+ originalVelocityDirection,
+ ref sweepState,
+ previousHitIsStable,
+ previousVelocity,
+ previousObstructionNormal,
+ ref transientVelocity,
+ ref remainingMovementMagnitude,
+ ref remainingMovementDirection);
+
+ previousHitIsStable = stableOnHit;
+ previousVelocity = velocityBeforeProjection;
+ previousObstructionNormal = obstructionNormal;
+ }
+ }
+
+ // Sweep the desired movement to detect collisions
+ while (remainingMovementMagnitude > 0f &&
+ (sweepsMade <= MaxMovementIterations) &&
+ hitSomethingThisSweepIteration)
+ {
+ bool foundClosestHit = false;
+ Vector3 closestSweepHitPoint = default;
+ Vector3 closestSweepHitNormal = default;
+ float closestSweepHitDistance = 0f;
+ Collider closestSweepHitCollider = null;
+
+ if (CheckMovementInitialOverlaps)
+ {
+ int numOverlaps = CharacterCollisionsOverlap(
+ tmpMovedPosition,
+ _transientRotation,
+ _internalProbedColliders,
+ 0f,
+ false);
+ if (numOverlaps > 0)
+ {
+ closestSweepHitDistance = 0f;
+
+ float mostObstructingOverlapNormalDotProduct = 2f;
+
+ for (int i = 0; i < numOverlaps; i++)
+ {
+ Collider tmpCollider = _internalProbedColliders[i];
+
+ if (Physics.ComputePenetration(
+ Capsule,
+ tmpMovedPosition,
+ _transientRotation,
+ tmpCollider,
+ tmpCollider.transform.position,
+ tmpCollider.transform.rotation,
+ out Vector3 resolutionDirection,
+ out float resolutionDistance))
+ {
+ float dotProduct = Vector3.Dot(remainingMovementDirection, resolutionDirection);
+ if (dotProduct < 0f && dotProduct < mostObstructingOverlapNormalDotProduct)
+ {
+ mostObstructingOverlapNormalDotProduct = dotProduct;
+
+ closestSweepHitNormal = resolutionDirection;
+ closestSweepHitCollider = tmpCollider;
+ closestSweepHitPoint = tmpMovedPosition + (_transientRotation * CharacterTransformToCapsuleCenter) + (resolutionDirection * resolutionDistance);
+
+ foundClosestHit = true;
+ }
+ }
+ }
+ }
+ }
+
+ if (!foundClosestHit && CharacterCollisionsSweep(
+ tmpMovedPosition, // position
+ _transientRotation, // rotation
+ remainingMovementDirection, // direction
+ remainingMovementMagnitude + CollisionOffset, // distance
+ out RaycastHit closestSweepHit, // closest hit
+ _internalCharacterHits) // all hits
+ > 0)
+ {
+ closestSweepHitNormal = closestSweepHit.normal;
+ closestSweepHitDistance = closestSweepHit.distance;
+ closestSweepHitCollider = closestSweepHit.collider;
+ closestSweepHitPoint = closestSweepHit.point;
+
+ foundClosestHit = true;
+ }
+
+ if (foundClosestHit)
+ {
+ // Calculate movement from this iteration
+ Vector3 sweepMovement = (remainingMovementDirection * (Mathf.Max(0f, closestSweepHitDistance - CollisionOffset)));
+ tmpMovedPosition += sweepMovement;
+ remainingMovementMagnitude -= sweepMovement.magnitude;
+
+ // Evaluate if hit is stable
+ HitStabilityReport moveHitStabilityReport = new HitStabilityReport();
+ EvaluateHitStability(closestSweepHitCollider, closestSweepHitNormal, closestSweepHitPoint, tmpMovedPosition, _transientRotation, transientVelocity, ref moveHitStabilityReport);
+
+ // Handle stepping up steps points higher than bottom capsule radius
+ bool foundValidStepHit = false;
+ if (_solveGrounding && StepHandling != StepHandlingMethod.None && moveHitStabilityReport.ValidStepDetected)
+ {
+ float obstructionCorrelation = Mathf.Abs(Vector3.Dot(closestSweepHitNormal, _characterUp));
+ if (obstructionCorrelation <= CorrelationForVerticalObstruction)
+ {
+ Vector3 stepForwardDirection = Vector3.ProjectOnPlane(-closestSweepHitNormal, _characterUp).normalized;
+ Vector3 stepCastStartPoint = (tmpMovedPosition + (stepForwardDirection * SteppingForwardDistance)) +
+ (_characterUp * MaxStepHeight);
+
+ // Cast downward from the top of the stepping height
+ int nbStepHits = CharacterCollisionsSweep(
+ stepCastStartPoint, // position
+ _transientRotation, // rotation
+ -_characterUp, // direction
+ MaxStepHeight, // distance
+ out RaycastHit closestStepHit, // closest hit
+ _internalCharacterHits,
+ 0f,
+ true); // all hits
+
+ // Check for hit corresponding to stepped collider
+ for (int i = 0; i < nbStepHits; i++)
+ {
+ if (_internalCharacterHits[i].collider == moveHitStabilityReport.SteppedCollider)
+ {
+ Vector3 endStepPosition = stepCastStartPoint + (-_characterUp * (_internalCharacterHits[i].distance - CollisionOffset));
+ tmpMovedPosition = endStepPosition;
+ foundValidStepHit = true;
+
+ // Project velocity on ground normal at step
+ transientVelocity = Vector3.ProjectOnPlane(transientVelocity, CharacterUp);
+ remainingMovementDirection = transientVelocity.normalized;
+
+ break;
+ }
+ }
+ }
+ }
+
+ // Handle movement solving
+ if (!foundValidStepHit)
+ {
+ Vector3 obstructionNormal = GetObstructionNormal(closestSweepHitNormal, moveHitStabilityReport.IsStable);
+
+ // Movement hit callback
+ CharacterController.OnMovementHit(closestSweepHitCollider, closestSweepHitNormal, closestSweepHitPoint, ref moveHitStabilityReport);
+
+ // Handle remembering rigidbody hits
+ if (InteractiveRigidbodyHandling && closestSweepHitCollider.attachedRigidbody)
+ {
+ StoreRigidbodyHit(
+ closestSweepHitCollider.attachedRigidbody,
+ transientVelocity,
+ closestSweepHitPoint,
+ obstructionNormal,
+ moveHitStabilityReport);
+ }
+
+ bool stableOnHit = moveHitStabilityReport.IsStable && !MustUnground();
+ Vector3 velocityBeforeProj = transientVelocity;
+
+ // Project velocity for next iteration
+ InternalHandleVelocityProjection(
+ stableOnHit,
+ closestSweepHitNormal,
+ obstructionNormal,
+ originalVelocityDirection,
+ ref sweepState,
+ previousHitIsStable,
+ previousVelocity,
+ previousObstructionNormal,
+ ref transientVelocity,
+ ref remainingMovementMagnitude,
+ ref remainingMovementDirection);
+
+ previousHitIsStable = stableOnHit;
+ previousVelocity = velocityBeforeProj;
+ previousObstructionNormal = obstructionNormal;
+ }
+ }
+ // If we hit nothing...
+ else
+ {
+ hitSomethingThisSweepIteration = false;
+ }
+
+ // Safety for exceeding max sweeps allowed
+ sweepsMade++;
+ if (sweepsMade > MaxMovementIterations)
+ {
+ if (KillRemainingMovementWhenExceedMaxMovementIterations)
+ {
+ remainingMovementMagnitude = 0f;
+ }
+
+ if (KillVelocityWhenExceedMaxMovementIterations)
+ {
+ transientVelocity = Vector3.zero;
+ }
+ wasCompleted = false;
+ }
+ }
+
+ // Move position for the remainder of the movement
+ tmpMovedPosition += (remainingMovementDirection * remainingMovementMagnitude);
+ _transientPosition = tmpMovedPosition;
+
+ return wasCompleted;
+ }
+
+ ///
+ /// Gets the effective normal for movement obstruction depending on current grounding status
+ ///
+ private Vector3 GetObstructionNormal(Vector3 hitNormal, bool stableOnHit)
+ {
+ // Find hit/obstruction/offset normal
+ Vector3 obstructionNormal = hitNormal;
+ if (GroundingStatus.IsStableOnGround && !MustUnground() && !stableOnHit)
+ {
+ Vector3 obstructionLeftAlongGround = Vector3.Cross(GroundingStatus.GroundNormal, obstructionNormal).normalized;
+ obstructionNormal = Vector3.Cross(obstructionLeftAlongGround, _characterUp).normalized;
+ }
+
+ // Catch cases where cross product between parallel normals returned 0
+ if (obstructionNormal.sqrMagnitude == 0f)
+ {
+ obstructionNormal = hitNormal;
+ }
+
+ return obstructionNormal;
+ }
+
+ ///
+ /// Remembers a rigidbody hit for processing later
+ ///
+ private void StoreRigidbodyHit(Rigidbody hitRigidbody, Vector3 hitVelocity, Vector3 hitPoint, Vector3 obstructionNormal, HitStabilityReport hitStabilityReport)
+ {
+ if (_rigidbodyProjectionHitCount < _internalRigidbodyProjectionHits.Length)
+ {
+ if (!hitRigidbody.GetComponent())
+ {
+ RigidbodyProjectionHit rph = new RigidbodyProjectionHit();
+ rph.Rigidbody = hitRigidbody;
+ rph.HitPoint = hitPoint;
+ rph.EffectiveHitNormal = obstructionNormal;
+ rph.HitVelocity = hitVelocity;
+ rph.StableOnHit = hitStabilityReport.IsStable;
+
+ _internalRigidbodyProjectionHits[_rigidbodyProjectionHitCount] = rph;
+ _rigidbodyProjectionHitCount++;
+ }
+ }
+ }
+
+ public void SetTransientPosition(Vector3 newPos)
+ {
+ _transientPosition = newPos;
+ }
+
+ ///
+ /// Processes movement projection upon detecting a hit
+ ///
+ private void InternalHandleVelocityProjection(bool stableOnHit, Vector3 hitNormal, Vector3 obstructionNormal, Vector3 originalDirection,
+ ref MovementSweepState sweepState, bool previousHitIsStable, Vector3 previousVelocity, Vector3 previousObstructionNormal,
+ ref Vector3 transientVelocity, ref float remainingMovementMagnitude, ref Vector3 remainingMovementDirection)
+ {
+ if (transientVelocity.sqrMagnitude <= 0f)
+ {
+ return;
+ }
+
+ Vector3 velocityBeforeProjection = transientVelocity;
+
+ if (stableOnHit)
+ {
+ LastMovementIterationFoundAnyGround = true;
+ HandleVelocityProjection(ref transientVelocity, obstructionNormal, stableOnHit);
+ }
+ else
+ {
+ // Handle projection
+ if (sweepState == MovementSweepState.Initial)
+ {
+ HandleVelocityProjection(ref transientVelocity, obstructionNormal, stableOnHit);
+ sweepState = MovementSweepState.AfterFirstHit;
+ }
+ // Blocking crease handling
+ else if (sweepState == MovementSweepState.AfterFirstHit)
+ {
+ EvaluateCrease(
+ transientVelocity,
+ previousVelocity,
+ obstructionNormal,
+ previousObstructionNormal,
+ stableOnHit,
+ previousHitIsStable,
+ GroundingStatus.IsStableOnGround && !MustUnground(),
+ out bool foundCrease,
+ out Vector3 creaseDirection);
+
+ if (foundCrease)
+ {
+ if (GroundingStatus.IsStableOnGround && !MustUnground())
+ {
+ transientVelocity = Vector3.zero;
+ sweepState = MovementSweepState.FoundBlockingCorner;
+ }
+ else
+ {
+ transientVelocity = Vector3.Project(transientVelocity, creaseDirection);
+ sweepState = MovementSweepState.FoundBlockingCrease;
+ }
+ }
+ else
+ {
+ HandleVelocityProjection(ref transientVelocity, obstructionNormal, stableOnHit);
+ }
+ }
+ // Blocking corner handling
+ else if (sweepState == MovementSweepState.FoundBlockingCrease)
+ {
+ transientVelocity = Vector3.zero;
+ sweepState = MovementSweepState.FoundBlockingCorner;
+ }
+ }
+
+ if (HasPlanarConstraint)
+ {
+ transientVelocity = Vector3.ProjectOnPlane(transientVelocity, PlanarConstraintAxis.normalized);
+ }
+
+ float newVelocityFactor = transientVelocity.magnitude / velocityBeforeProjection.magnitude;
+ remainingMovementMagnitude *= newVelocityFactor;
+ remainingMovementDirection = transientVelocity.normalized;
+ }
+
+ private void EvaluateCrease(
+ Vector3 currentCharacterVelocity,
+ Vector3 previousCharacterVelocity,
+ Vector3 currentHitNormal,
+ Vector3 previousHitNormal,
+ bool currentHitIsStable,
+ bool previousHitIsStable,
+ bool characterIsStable,
+ out bool isValidCrease,
+ out Vector3 creaseDirection)
+ {
+ isValidCrease = false;
+ creaseDirection = default;
+
+ if (!characterIsStable || !currentHitIsStable || !previousHitIsStable)
+ {
+ Vector3 tmpBlockingCreaseDirection = Vector3.Cross(currentHitNormal, previousHitNormal).normalized;
+ float dotPlanes = Vector3.Dot(currentHitNormal, previousHitNormal);
+ bool isVelocityConstrainedByCrease = false;
+
+ // Avoid calculations if the two planes are the same
+ if (dotPlanes < 0.999f)
+ {
+ // TODO: can this whole part be made simpler? (with 2d projections, etc)
+ Vector3 normalAOnCreasePlane = Vector3.ProjectOnPlane(currentHitNormal, tmpBlockingCreaseDirection).normalized;
+ Vector3 normalBOnCreasePlane = Vector3.ProjectOnPlane(previousHitNormal, tmpBlockingCreaseDirection).normalized;
+ float dotPlanesOnCreasePlane = Vector3.Dot(normalAOnCreasePlane, normalBOnCreasePlane);
+
+ Vector3 enteringVelocityDirectionOnCreasePlane = Vector3.ProjectOnPlane(previousCharacterVelocity, tmpBlockingCreaseDirection).normalized;
+
+ if (dotPlanesOnCreasePlane <= (Vector3.Dot(-enteringVelocityDirectionOnCreasePlane, normalAOnCreasePlane) + 0.001f) &&
+ dotPlanesOnCreasePlane <= (Vector3.Dot(-enteringVelocityDirectionOnCreasePlane, normalBOnCreasePlane) + 0.001f))
+ {
+ isVelocityConstrainedByCrease = true;
+ }
+ }
+
+ if (isVelocityConstrainedByCrease)
+ {
+ // Flip crease direction to make it representative of the real direction our velocity would be projected to
+ if (Vector3.Dot(tmpBlockingCreaseDirection, currentCharacterVelocity) < 0f)
+ {
+ tmpBlockingCreaseDirection = -tmpBlockingCreaseDirection;
+ }
+
+ isValidCrease = true;
+ creaseDirection = tmpBlockingCreaseDirection;
+ }
+ }
+ }
+
+ ///
+ /// Allows you to override the way velocity is projected on an obstruction
+ ///
+ public virtual void HandleVelocityProjection(ref Vector3 velocity, Vector3 obstructionNormal, bool stableOnHit)
+ {
+ if (GroundingStatus.IsStableOnGround && !MustUnground())
+ {
+ // On stable slopes, simply reorient the movement without any loss
+ if (stableOnHit)
+ {
+ velocity = GetDirectionTangentToSurface(velocity, obstructionNormal) * velocity.magnitude;
+ }
+ // On blocking hits, project the movement on the obstruction while following the grounding plane
+ else
+ {
+ Vector3 obstructionRightAlongGround = Vector3.Cross(obstructionNormal, GroundingStatus.GroundNormal).normalized;
+ Vector3 obstructionUpAlongGround = Vector3.Cross(obstructionRightAlongGround, obstructionNormal).normalized;
+ velocity = GetDirectionTangentToSurface(velocity, obstructionUpAlongGround) * velocity.magnitude;
+ velocity = Vector3.ProjectOnPlane(velocity, obstructionNormal);
+ }
+ }
+ else
+ {
+ if (stableOnHit)
+ {
+ // Handle stable landing
+ velocity = Vector3.ProjectOnPlane(velocity, CharacterUp);
+ velocity = GetDirectionTangentToSurface(velocity, obstructionNormal) * velocity.magnitude;
+ }
+ // Handle generic obstruction
+ else
+ {
+ velocity = Vector3.ProjectOnPlane(velocity, obstructionNormal);
+ }
+ }
+ }
+
+ ///
+ /// Allows you to override the way hit rigidbodies are pushed / interacted with.
+ /// ProcessedVelocity is what must be modified if this interaction affects the character's velocity.
+ ///
+ public virtual void HandleSimulatedRigidbodyInteraction(ref Vector3 processedVelocity, RigidbodyProjectionHit hit, float deltaTime)
+ {
+ }
+
+ ///
+ /// Takes into account rigidbody hits for adding to the velocity
+ ///
+ private void ProcessVelocityForRigidbodyHits(ref Vector3 processedVelocity, float deltaTime)
+ {
+ for (int i = 0; i < _rigidbodyProjectionHitCount; i++)
+ {
+ RigidbodyProjectionHit bodyHit = _internalRigidbodyProjectionHits[i];
+
+ if (bodyHit.Rigidbody && !_rigidbodiesPushedThisMove.Contains(bodyHit.Rigidbody))
+ {
+ if (_internalRigidbodyProjectionHits[i].Rigidbody != _attachedRigidbody)
+ {
+ // Remember we hit this rigidbody
+ _rigidbodiesPushedThisMove.Add(bodyHit.Rigidbody);
+
+ float characterMass = SimulatedCharacterMass;
+ Vector3 characterVelocity = bodyHit.HitVelocity;
+
+ KinematicCharacterMotor hitCharacterMotor = bodyHit.Rigidbody.GetComponent();
+ bool hitBodyIsCharacter = hitCharacterMotor != null;
+ bool hitBodyIsDynamic = !bodyHit.Rigidbody.isKinematic;
+ float hitBodyMass = bodyHit.Rigidbody.mass;
+ float hitBodyMassAtPoint = bodyHit.Rigidbody.mass; // todo
+ Vector3 hitBodyVelocity = bodyHit.Rigidbody.velocity;
+ if (hitBodyIsCharacter)
+ {
+ hitBodyMass = hitCharacterMotor.SimulatedCharacterMass;
+ hitBodyMassAtPoint = hitCharacterMotor.SimulatedCharacterMass; // todo
+ hitBodyVelocity = hitCharacterMotor.BaseVelocity;
+ }
+ else if (!hitBodyIsDynamic)
+ {
+ PhysicsMover physicsMover = bodyHit.Rigidbody.GetComponent();
+ if(physicsMover)
+ {
+ hitBodyVelocity = physicsMover.Velocity;
+ }
+ }
+
+ // Calculate the ratio of the total mass that the character mass represents
+ float characterToBodyMassRatio = 1f;
+ {
+ if (characterMass + hitBodyMassAtPoint > 0f)
+ {
+ characterToBodyMassRatio = characterMass / (characterMass + hitBodyMassAtPoint);
+ }
+ else
+ {
+ characterToBodyMassRatio = 0.5f;
+ }
+
+ // Hitting a non-dynamic body
+ if (!hitBodyIsDynamic)
+ {
+ characterToBodyMassRatio = 0f;
+ }
+ // Emulate kinematic body interaction
+ else if (RigidbodyInteractionType == RigidbodyInteractionType.Kinematic && !hitBodyIsCharacter)
+ {
+ characterToBodyMassRatio = 1f;
+ }
+ }
+
+ ComputeCollisionResolutionForHitBody(
+ bodyHit.EffectiveHitNormal,
+ characterVelocity,
+ hitBodyVelocity,
+ characterToBodyMassRatio,
+ out Vector3 velocityChangeOnCharacter,
+ out Vector3 velocityChangeOnBody);
+
+ processedVelocity += velocityChangeOnCharacter;
+
+ if (hitBodyIsCharacter)
+ {
+ hitCharacterMotor.BaseVelocity += velocityChangeOnCharacter;
+ }
+ else if (hitBodyIsDynamic)
+ {
+ bodyHit.Rigidbody.AddForceAtPosition(velocityChangeOnBody, bodyHit.HitPoint, ForceMode.VelocityChange);
+ }
+
+ if (RigidbodyInteractionType == RigidbodyInteractionType.SimulatedDynamic)
+ {
+ HandleSimulatedRigidbodyInteraction(ref processedVelocity, bodyHit, deltaTime);
+ }
+ }
+ }
+ }
+
+ }
+
+ public void ComputeCollisionResolutionForHitBody(
+ Vector3 hitNormal,
+ Vector3 characterVelocity,
+ Vector3 bodyVelocity,
+ float characterToBodyMassRatio,
+ out Vector3 velocityChangeOnCharacter,
+ out Vector3 velocityChangeOnBody)
+ {
+ velocityChangeOnCharacter = default;
+ velocityChangeOnBody = default;
+
+ float bodyToCharacterMassRatio = 1f - characterToBodyMassRatio;
+ float characterVelocityMagnitudeOnHitNormal = Vector3.Dot(characterVelocity, hitNormal);
+ float bodyVelocityMagnitudeOnHitNormal = Vector3.Dot(bodyVelocity, hitNormal);
+
+ // if character velocity was going against the obstruction, restore the portion of the velocity that got projected during the movement phase
+ if (characterVelocityMagnitudeOnHitNormal < 0f)
+ {
+ Vector3 restoredCharacterVelocity = hitNormal * characterVelocityMagnitudeOnHitNormal;
+ velocityChangeOnCharacter += restoredCharacterVelocity;
+ }
+
+ // solve impulse velocities on both bodies, but only if the body velocity would be giving resistance to the character in any way
+ if (bodyVelocityMagnitudeOnHitNormal > characterVelocityMagnitudeOnHitNormal)
+ {
+ Vector3 relativeImpactVelocity = hitNormal * (bodyVelocityMagnitudeOnHitNormal - characterVelocityMagnitudeOnHitNormal);
+ velocityChangeOnCharacter += relativeImpactVelocity * bodyToCharacterMassRatio;
+ velocityChangeOnBody += -relativeImpactVelocity * characterToBodyMassRatio;
+ }
+ }
+
+ ///
+ /// Determines if the input collider is valid for collision processing
+ ///
+ /// Returns true if the collider is valid
+ private bool CheckIfColliderValidForCollisions(Collider coll)
+ {
+ // Ignore self
+ if (coll == Capsule)
+ {
+ return false;
+ }
+
+ if (!InternalIsColliderValidForCollisions(coll))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Determines if the input collider is valid for collision processing
+ ///
+ private bool InternalIsColliderValidForCollisions(Collider coll)
+ {
+ Rigidbody colliderAttachedRigidbody = coll.attachedRigidbody;
+ if (colliderAttachedRigidbody)
+ {
+ bool isRigidbodyKinematic = colliderAttachedRigidbody.isKinematic;
+
+ // If movement is made from AttachedRigidbody, ignore the AttachedRigidbody
+ if (_isMovingFromAttachedRigidbody && (!isRigidbodyKinematic || colliderAttachedRigidbody == _attachedRigidbody))
+ {
+ return false;
+ }
+
+ // don't collide with dynamic rigidbodies if our RigidbodyInteractionType is kinematic
+ if (RigidbodyInteractionType == RigidbodyInteractionType.Kinematic && !isRigidbodyKinematic)
+ {
+ // wake up rigidbody
+ if (coll.attachedRigidbody)
+ {
+ coll.attachedRigidbody.WakeUp();
+ }
+
+ return false;
+ }
+ }
+
+ // Custom checks
+ bool colliderValid = CharacterController.IsColliderValidForCollisions(coll);
+ if (!colliderValid)
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Determines if the motor is considered stable on a given hit
+ ///
+ public void EvaluateHitStability(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, Vector3 atCharacterPosition, Quaternion atCharacterRotation, Vector3 withCharacterVelocity, ref HitStabilityReport stabilityReport)
+ {
+ if (!_solveGrounding)
+ {
+ stabilityReport.IsStable = false;
+ return;
+ }
+
+ Vector3 atCharacterUp = atCharacterRotation * _cachedWorldUp;
+ Vector3 innerHitDirection = Vector3.ProjectOnPlane(hitNormal, atCharacterUp).normalized;
+
+ stabilityReport.IsStable = this.IsStableOnNormal(hitNormal);
+
+ stabilityReport.FoundInnerNormal = false;
+ stabilityReport.FoundOuterNormal = false;
+ stabilityReport.InnerNormal = hitNormal;
+ stabilityReport.OuterNormal = hitNormal;
+
+ // Ledge handling
+ if (LedgeAndDenivelationHandling)
+ {
+ float ledgeCheckHeight = MinDistanceForLedge;
+ if (StepHandling != StepHandlingMethod.None)
+ {
+ ledgeCheckHeight = MaxStepHeight;
+ }
+
+ bool isStableLedgeInner = false;
+ bool isStableLedgeOuter = false;
+
+ if (CharacterCollisionsRaycast(
+ hitPoint + (atCharacterUp * SecondaryProbesVertical) + (innerHitDirection * SecondaryProbesHorizontal),
+ -atCharacterUp,
+ ledgeCheckHeight + SecondaryProbesVertical,
+ out RaycastHit innerLedgeHit,
+ _internalCharacterHits) > 0)
+ {
+ Vector3 innerLedgeNormal = innerLedgeHit.normal;
+ stabilityReport.InnerNormal = innerLedgeNormal;
+ stabilityReport.FoundInnerNormal = true;
+ isStableLedgeInner = IsStableOnNormal(innerLedgeNormal);
+ }
+
+ if (CharacterCollisionsRaycast(
+ hitPoint + (atCharacterUp * SecondaryProbesVertical) + (-innerHitDirection * SecondaryProbesHorizontal),
+ -atCharacterUp,
+ ledgeCheckHeight + SecondaryProbesVertical,
+ out RaycastHit outerLedgeHit,
+ _internalCharacterHits) > 0)
+ {
+ Vector3 outerLedgeNormal = outerLedgeHit.normal;
+ stabilityReport.OuterNormal = outerLedgeNormal;
+ stabilityReport.FoundOuterNormal = true;
+ isStableLedgeOuter = IsStableOnNormal(outerLedgeNormal);
+ }
+
+ stabilityReport.LedgeDetected = (isStableLedgeInner != isStableLedgeOuter);
+ if (stabilityReport.LedgeDetected)
+ {
+ stabilityReport.IsOnEmptySideOfLedge = isStableLedgeOuter && !isStableLedgeInner;
+ stabilityReport.LedgeGroundNormal = isStableLedgeOuter ? stabilityReport.OuterNormal : stabilityReport.InnerNormal;
+ stabilityReport.LedgeRightDirection = Vector3.Cross(hitNormal, stabilityReport.LedgeGroundNormal).normalized;
+ stabilityReport.LedgeFacingDirection = Vector3.ProjectOnPlane(Vector3.Cross(stabilityReport.LedgeGroundNormal, stabilityReport.LedgeRightDirection), CharacterUp).normalized;
+ stabilityReport.DistanceFromLedge = Vector3.ProjectOnPlane((hitPoint - (atCharacterPosition + (atCharacterRotation * _characterTransformToCapsuleBottom))), atCharacterUp).magnitude;
+ stabilityReport.IsMovingTowardsEmptySideOfLedge = Vector3.Dot(withCharacterVelocity.normalized, stabilityReport.LedgeFacingDirection) > 0f;
+ }
+
+ if (stabilityReport.IsStable)
+ {
+ stabilityReport.IsStable = IsStableWithSpecialCases(ref stabilityReport, withCharacterVelocity);
+ }
+ }
+
+ // Step handling
+ if (StepHandling != StepHandlingMethod.None && !stabilityReport.IsStable)
+ {
+ // Stepping not supported on dynamic rigidbodies
+ Rigidbody hitRigidbody = hitCollider.attachedRigidbody;
+ if (!(hitRigidbody && !hitRigidbody.isKinematic))
+ {
+ DetectSteps(atCharacterPosition, atCharacterRotation, hitPoint, innerHitDirection, ref stabilityReport);
+
+ if (stabilityReport.ValidStepDetected)
+ {
+ stabilityReport.IsStable = true;
+ }
+ }
+ }
+
+ CharacterController.ProcessHitStabilityReport(hitCollider, hitNormal, hitPoint, atCharacterPosition, atCharacterRotation, ref stabilityReport);
+ }
+
+ private void DetectSteps(Vector3 characterPosition, Quaternion characterRotation, Vector3 hitPoint, Vector3 innerHitDirection, ref HitStabilityReport stabilityReport)
+ {
+ int nbStepHits = 0;
+ Collider tmpCollider;
+ RaycastHit outerStepHit;
+ Vector3 characterUp = characterRotation * _cachedWorldUp;
+ Vector3 verticalCharToHit = Vector3.Project((hitPoint - characterPosition), characterUp);
+ Vector3 horizontalCharToHitDirection = Vector3.ProjectOnPlane((hitPoint - characterPosition), characterUp).normalized;
+ Vector3 stepCheckStartPos = (hitPoint - verticalCharToHit) + (characterUp * MaxStepHeight) + (horizontalCharToHitDirection * CollisionOffset * 3f);
+
+ // Do outer step check with capsule cast on hit point
+ nbStepHits = CharacterCollisionsSweep(
+ stepCheckStartPos,
+ characterRotation,
+ -characterUp,
+ MaxStepHeight + CollisionOffset,
+ out outerStepHit,
+ _internalCharacterHits,
+ 0f,
+ true);
+
+ // Check for overlaps and obstructions at the hit position
+ if (CheckStepValidity(nbStepHits, characterPosition, characterRotation, innerHitDirection, stepCheckStartPos, out tmpCollider))
+ {
+ stabilityReport.ValidStepDetected = true;
+ stabilityReport.SteppedCollider = tmpCollider;
+ }
+
+ if (StepHandling == StepHandlingMethod.Extra && !stabilityReport.ValidStepDetected)
+ {
+ // Do min reach step check with capsule cast on hit point
+ stepCheckStartPos = characterPosition + (characterUp * MaxStepHeight) + (-innerHitDirection * MinRequiredStepDepth);
+ nbStepHits = CharacterCollisionsSweep(
+ stepCheckStartPos,
+ characterRotation,
+ -characterUp,
+ MaxStepHeight - CollisionOffset,
+ out outerStepHit,
+ _internalCharacterHits,
+ 0f,
+ true);
+
+ // Check for overlaps and obstructions at the hit position
+ if (CheckStepValidity(nbStepHits, characterPosition, characterRotation, innerHitDirection, stepCheckStartPos, out tmpCollider))
+ {
+ stabilityReport.ValidStepDetected = true;
+ stabilityReport.SteppedCollider = tmpCollider;
+ }
+ }
+ }
+
+ private bool CheckStepValidity(int nbStepHits, Vector3 characterPosition, Quaternion characterRotation, Vector3 innerHitDirection, Vector3 stepCheckStartPos, out Collider hitCollider)
+ {
+ hitCollider = null;
+ Vector3 characterUp = characterRotation * Vector3.up;
+
+ // Find the farthest valid hit for stepping
+ bool foundValidStepPosition = false;
+
+ while (nbStepHits > 0 && !foundValidStepPosition)
+ {
+ // Get farthest hit among the remaining hits
+ RaycastHit farthestHit = new RaycastHit();
+ float farthestDistance = 0f;
+ int farthestIndex = 0;
+ for (int i = 0; i < nbStepHits; i++)
+ {
+ float hitDistance = _internalCharacterHits[i].distance;
+ if (hitDistance > farthestDistance)
+ {
+ farthestDistance = hitDistance;
+ farthestHit = _internalCharacterHits[i];
+ farthestIndex = i;
+ }
+ }
+
+ Vector3 characterPositionAtHit = stepCheckStartPos + (-characterUp * (farthestHit.distance - CollisionOffset));
+
+ int atStepOverlaps = CharacterCollisionsOverlap(characterPositionAtHit, characterRotation, _internalProbedColliders);
+ if (atStepOverlaps <= 0)
+ {
+ // Check for outer hit slope normal stability at the step position
+ if (CharacterCollisionsRaycast(
+ farthestHit.point + (characterUp * SecondaryProbesVertical) + (-innerHitDirection * SecondaryProbesHorizontal),
+ -characterUp,
+ MaxStepHeight + SecondaryProbesVertical,
+ out RaycastHit outerSlopeHit,
+ _internalCharacterHits,
+ true) > 0)
+ {
+ if (IsStableOnNormal(outerSlopeHit.normal))
+ {
+ // Cast upward to detect any obstructions to moving there
+ if (CharacterCollisionsSweep(
+ characterPosition, // position
+ characterRotation, // rotation
+ characterUp, // direction
+ MaxStepHeight - farthestHit.distance, // distance
+ out RaycastHit tmpUpObstructionHit, // closest hit
+ _internalCharacterHits) // all hits
+ <= 0)
+ {
+ // Do inner step check...
+ bool innerStepValid = false;
+ RaycastHit innerStepHit;
+
+ if (AllowSteppingWithoutStableGrounding)
+ {
+ innerStepValid = true;
+ }
+ else
+ {
+ // At the capsule center at the step height
+ if (CharacterCollisionsRaycast(
+ characterPosition + Vector3.Project((characterPositionAtHit - characterPosition), characterUp),
+ -characterUp,
+ MaxStepHeight,
+ out innerStepHit,
+ _internalCharacterHits,
+ true) > 0)
+ {
+ if (IsStableOnNormal(innerStepHit.normal))
+ {
+ innerStepValid = true;
+ }
+ }
+ }
+
+ if (!innerStepValid)
+ {
+ // At inner step of the step point
+ if (CharacterCollisionsRaycast(
+ farthestHit.point + (innerHitDirection * SecondaryProbesHorizontal),
+ -characterUp,
+ MaxStepHeight,
+ out innerStepHit,
+ _internalCharacterHits,
+ true) > 0)
+ {
+ if (IsStableOnNormal(innerStepHit.normal))
+ {
+ innerStepValid = true;
+ }
+ }
+ }
+
+ // Final validation of step
+ if (innerStepValid)
+ {
+ hitCollider = farthestHit.collider;
+ foundValidStepPosition = true;
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ // Discard hit if not valid step
+ if (!foundValidStepPosition)
+ {
+ nbStepHits--;
+ if (farthestIndex < nbStepHits)
+ {
+ _internalCharacterHits[farthestIndex] = _internalCharacterHits[nbStepHits];
+ }
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// Get true linear velocity (taking into account rotational velocity) on a given point of a rigidbody
+ ///
+ public void GetVelocityFromRigidbodyMovement(Rigidbody interactiveRigidbody, Vector3 atPoint, float deltaTime, out Vector3 linearVelocity, out Vector3 angularVelocity)
+ {
+ if (deltaTime > 0f)
+ {
+ linearVelocity = interactiveRigidbody.velocity;
+ angularVelocity = interactiveRigidbody.angularVelocity;
+ if(interactiveRigidbody.isKinematic)
+ {
+ PhysicsMover physicsMover = interactiveRigidbody.GetComponent();
+ if (physicsMover)
+ {
+ linearVelocity = physicsMover.Velocity;
+ angularVelocity = physicsMover.AngularVelocity;
+ }
+ }
+
+ if (angularVelocity != Vector3.zero)
+ {
+ Vector3 centerOfRotation = interactiveRigidbody.transform.TransformPoint(interactiveRigidbody.centerOfMass);
+
+ Vector3 centerOfRotationToPoint = atPoint - centerOfRotation;
+ Quaternion rotationFromInteractiveRigidbody = Quaternion.Euler(Mathf.Rad2Deg * angularVelocity * deltaTime);
+ Vector3 finalPointPosition = centerOfRotation + (rotationFromInteractiveRigidbody * centerOfRotationToPoint);
+ linearVelocity += (finalPointPosition - atPoint) / deltaTime;
+ }
+ }
+ else
+ {
+ linearVelocity = default;
+ angularVelocity = default;
+ return;
+ }
+ }
+
+ ///
+ /// Determines if a collider has an attached interactive rigidbody
+ ///
+ private Rigidbody GetInteractiveRigidbody(Collider onCollider)
+ {
+ Rigidbody colliderAttachedRigidbody = onCollider.attachedRigidbody;
+ if (colliderAttachedRigidbody)
+ {
+ if (colliderAttachedRigidbody.gameObject.GetComponent())
+ {
+ return colliderAttachedRigidbody;
+ }
+
+ if (!colliderAttachedRigidbody.isKinematic)
+ {
+ return colliderAttachedRigidbody;
+ }
+ }
+ return null;
+ }
+
+ ///
+ /// Calculates the velocity required to move the character to the target position over a specific deltaTime.
+ /// Useful for when you wish to work with positions rather than velocities in the UpdateVelocity callback
+ ///
+ public Vector3 GetVelocityForMovePosition(Vector3 fromPosition, Vector3 toPosition, float deltaTime)
+ {
+ return GetVelocityFromMovement(toPosition - fromPosition, deltaTime);
+ }
+
+ public Vector3 GetVelocityFromMovement(Vector3 movement, float deltaTime)
+ {
+ if (deltaTime <= 0f)
+ return Vector3.zero;
+
+ return movement / deltaTime;
+ }
+
+ ///
+ /// Trims a vector to make it restricted against a plane
+ ///
+ private void RestrictVectorToPlane(ref Vector3 vector, Vector3 toPlane)
+ {
+ if (vector.x > 0 != toPlane.x > 0)
+ {
+ vector.x = 0;
+ }
+ if (vector.y > 0 != toPlane.y > 0)
+ {
+ vector.y = 0;
+ }
+ if (vector.z > 0 != toPlane.z > 0)
+ {
+ vector.z = 0;
+ }
+ }
+
+ ///
+ /// Detect if the character capsule is overlapping with anything collidable
+ ///
+ /// Returns number of overlaps
+ public int CharacterCollisionsOverlap(Vector3 position, Quaternion rotation, Collider[] overlappedColliders, float inflate = 0f, bool acceptOnlyStableGroundLayer = false)
+ {
+ int queryLayers = CollidableLayers;
+ if (acceptOnlyStableGroundLayer)
+ {
+ queryLayers = CollidableLayers & StableGroundLayers;
+ }
+
+ Vector3 bottom = position + (rotation * _characterTransformToCapsuleBottomHemi);
+ Vector3 top = position + (rotation * _characterTransformToCapsuleTopHemi);
+ if (inflate != 0f)
+ {
+ bottom += (rotation * Vector3.down * inflate);
+ top += (rotation * Vector3.up * inflate);
+ }
+
+ int nbHits = 0;
+ int nbUnfilteredHits = Physics.OverlapCapsuleNonAlloc(
+ bottom,
+ top,
+ Capsule.radius + inflate,
+ overlappedColliders,
+ queryLayers,
+ QueryTriggerInteraction.Ignore);
+
+ // Filter out invalid colliders
+ nbHits = nbUnfilteredHits;
+ for (int i = nbUnfilteredHits - 1; i >= 0; i--)
+ {
+ if (!CheckIfColliderValidForCollisions(overlappedColliders[i]))
+ {
+ nbHits--;
+ if (i < nbHits)
+ {
+ overlappedColliders[i] = overlappedColliders[nbHits];
+ }
+ }
+ }
+
+ return nbHits;
+ }
+
+ ///
+ /// Detect if the character capsule is overlapping with anything
+ ///
+ /// Returns number of overlaps
+ public int CharacterOverlap(Vector3 position, Quaternion rotation, Collider[] overlappedColliders, LayerMask layers, QueryTriggerInteraction triggerInteraction, float inflate = 0f)
+ {
+ Vector3 bottom = position + (rotation * _characterTransformToCapsuleBottomHemi);
+ Vector3 top = position + (rotation * _characterTransformToCapsuleTopHemi);
+ if (inflate != 0f)
+ {
+ bottom += (rotation * Vector3.down * inflate);
+ top += (rotation * Vector3.up * inflate);
+ }
+
+ int nbHits = 0;
+ int nbUnfilteredHits = Physics.OverlapCapsuleNonAlloc(
+ bottom,
+ top,
+ Capsule.radius + inflate,
+ overlappedColliders,
+ layers,
+ triggerInteraction);
+
+ // Filter out the character capsule itself
+ nbHits = nbUnfilteredHits;
+ for (int i = nbUnfilteredHits - 1; i >= 0; i--)
+ {
+ if (overlappedColliders[i] == Capsule)
+ {
+ nbHits--;
+ if (i < nbHits)
+ {
+ overlappedColliders[i] = overlappedColliders[nbHits];
+ }
+ }
+ }
+
+ return nbHits;
+ }
+
+ ///
+ /// Sweeps the capsule's volume to detect collision hits
+ ///
+ /// Returns the number of hits
+ public int CharacterCollisionsSweep(Vector3 position, Quaternion rotation, Vector3 direction, float distance, out RaycastHit closestHit, RaycastHit[] hits, float inflate = 0f, bool acceptOnlyStableGroundLayer = false)
+ {
+ int queryLayers = CollidableLayers;
+ if (acceptOnlyStableGroundLayer)
+ {
+ queryLayers = CollidableLayers & StableGroundLayers;
+ }
+
+ Vector3 bottom = position + (rotation * _characterTransformToCapsuleBottomHemi) - (direction * SweepProbingBackstepDistance);
+ Vector3 top = position + (rotation * _characterTransformToCapsuleTopHemi) - (direction * SweepProbingBackstepDistance);
+ if (inflate != 0f)
+ {
+ bottom += (rotation * Vector3.down * inflate);
+ top += (rotation * Vector3.up * inflate);
+ }
+
+ // Capsule cast
+ int nbHits = 0;
+ int nbUnfilteredHits = Physics.CapsuleCastNonAlloc(
+ bottom,
+ top,
+ Capsule.radius + inflate,
+ direction,
+ hits,
+ distance + SweepProbingBackstepDistance,
+ queryLayers,
+ QueryTriggerInteraction.Ignore);
+
+ // Hits filter
+ closestHit = new RaycastHit();
+ float closestDistance = Mathf.Infinity;
+ nbHits = nbUnfilteredHits;
+ for (int i = nbUnfilteredHits - 1; i >= 0; i--)
+ {
+ hits[i].distance -= SweepProbingBackstepDistance;
+
+ RaycastHit hit = hits[i];
+ float hitDistance = hit.distance;
+
+ // Filter out the invalid hits
+ if (hitDistance <= 0f || !CheckIfColliderValidForCollisions(hit.collider))
+ {
+ nbHits--;
+ if (i < nbHits)
+ {
+ hits[i] = hits[nbHits];
+ }
+ }
+ else
+ {
+ // Remember closest valid hit
+ if (hitDistance < closestDistance)
+ {
+ closestHit = hit;
+ closestDistance = hitDistance;
+ }
+ }
+ }
+
+ return nbHits;
+ }
+
+ ///
+ /// Sweeps the capsule's volume to detect hits
+ ///
+ /// Returns the number of hits
+ public int CharacterSweep(Vector3 position, Quaternion rotation, Vector3 direction, float distance, out RaycastHit closestHit, RaycastHit[] hits, LayerMask layers, QueryTriggerInteraction triggerInteraction, float inflate = 0f)
+ {
+ closestHit = new RaycastHit();
+
+ Vector3 bottom = position + (rotation * _characterTransformToCapsuleBottomHemi);
+ Vector3 top = position + (rotation * _characterTransformToCapsuleTopHemi);
+ if (inflate != 0f)
+ {
+ bottom += (rotation * Vector3.down * inflate);
+ top += (rotation * Vector3.up * inflate);
+ }
+
+ // Capsule cast
+ int nbHits = 0;
+ int nbUnfilteredHits = Physics.CapsuleCastNonAlloc(
+ bottom,
+ top,
+ Capsule.radius + inflate,
+ direction,
+ hits,
+ distance,
+ layers,
+ triggerInteraction);
+
+ // Hits filter
+ float closestDistance = Mathf.Infinity;
+ nbHits = nbUnfilteredHits;
+ for (int i = nbUnfilteredHits - 1; i >= 0; i--)
+ {
+ RaycastHit hit = hits[i];
+
+ // Filter out the character capsule
+ if (hit.distance <= 0f || hit.collider == Capsule)
+ {
+ nbHits--;
+ if (i < nbHits)
+ {
+ hits[i] = hits[nbHits];
+ }
+ }
+ else
+ {
+ // Remember closest valid hit
+ float hitDistance = hit.distance;
+ if (hitDistance < closestDistance)
+ {
+ closestHit = hit;
+ closestDistance = hitDistance;
+ }
+ }
+ }
+
+ return nbHits;
+ }
+
+ ///
+ /// Casts the character volume in the character's downward direction to detect ground
+ ///
+ /// Returns the number of hits
+ private bool CharacterGroundSweep(Vector3 position, Quaternion rotation, Vector3 direction, float distance, out RaycastHit closestHit)
+ {
+ closestHit = new RaycastHit();
+
+ // Capsule cast
+ int nbUnfilteredHits = Physics.CapsuleCastNonAlloc(
+ position + (rotation * _characterTransformToCapsuleBottomHemi) - (direction * GroundProbingBackstepDistance),
+ position + (rotation * _characterTransformToCapsuleTopHemi) - (direction * GroundProbingBackstepDistance),
+ Capsule.radius,
+ direction,
+ _internalCharacterHits,
+ distance + GroundProbingBackstepDistance,
+ CollidableLayers & StableGroundLayers,
+ QueryTriggerInteraction.Ignore);
+
+ // Hits filter
+ bool foundValidHit = false;
+ float closestDistance = Mathf.Infinity;
+ for (int i = 0; i < nbUnfilteredHits; i++)
+ {
+ RaycastHit hit = _internalCharacterHits[i];
+ float hitDistance = hit.distance;
+
+ // Find the closest valid hit
+ if (hitDistance > 0f && CheckIfColliderValidForCollisions(hit.collider))
+ {
+ if (hitDistance < closestDistance)
+ {
+ closestHit = hit;
+ closestHit.distance -= GroundProbingBackstepDistance;
+ closestDistance = hitDistance;
+
+ foundValidHit = true;
+ }
+ }
+ }
+
+ return foundValidHit;
+ }
+
+ ///
+ /// Raycasts to detect collision hits
+ ///
+ /// Returns the number of hits
+ public int CharacterCollisionsRaycast(Vector3 position, Vector3 direction, float distance, out RaycastHit closestHit, RaycastHit[] hits, bool acceptOnlyStableGroundLayer = false)
+ {
+ int queryLayers = CollidableLayers;
+ if (acceptOnlyStableGroundLayer)
+ {
+ queryLayers = CollidableLayers & StableGroundLayers;
+ }
+
+ // Raycast
+ int nbHits = 0;
+ int nbUnfilteredHits = Physics.RaycastNonAlloc(
+ position,
+ direction,
+ hits,
+ distance,
+ queryLayers,
+ QueryTriggerInteraction.Ignore);
+
+ // Hits filter
+ closestHit = new RaycastHit();
+ float closestDistance = Mathf.Infinity;
+ nbHits = nbUnfilteredHits;
+ for (int i = nbUnfilteredHits - 1; i >= 0; i--)
+ {
+ RaycastHit hit = hits[i];
+ float hitDistance = hit.distance;
+
+ // Filter out the invalid hits
+ if (hitDistance <= 0f ||
+ !CheckIfColliderValidForCollisions(hit.collider))
+ {
+ nbHits--;
+ if (i < nbHits)
+ {
+ hits[i] = hits[nbHits];
+ }
+ }
+ else
+ {
+ // Remember closest valid hit
+ if (hitDistance < closestDistance)
+ {
+ closestHit = hit;
+ closestDistance = hitDistance;
+ }
+ }
+ }
+
+ return nbHits;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/Plugins/KinematicCharacterController/Core/KinematicCharacterMotor.cs.meta b/Assets/Plugins/KinematicCharacterController/Core/KinematicCharacterMotor.cs.meta
new file mode 100644
index 0000000..fb3d392
--- /dev/null
+++ b/Assets/Plugins/KinematicCharacterController/Core/KinematicCharacterMotor.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 4d1bc5515e3ab954e80599c538834774
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/KinematicCharacterController/Core/KinematicCharacterSystem.cs b/Assets/Plugins/KinematicCharacterController/Core/KinematicCharacterSystem.cs
new file mode 100644
index 0000000..b838d14
--- /dev/null
+++ b/Assets/Plugins/KinematicCharacterController/Core/KinematicCharacterSystem.cs
@@ -0,0 +1,294 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using UnityEngine;
+
+namespace KinematicCharacterController
+{
+ ///
+ /// The system that manages the simulation of KinematicCharacterMotor and PhysicsMover
+ ///
+ [DefaultExecutionOrder(-100)]
+ public class KinematicCharacterSystem : MonoBehaviour
+ {
+ private static KinematicCharacterSystem _instance;
+
+ public static List CharacterMotors = new List();
+ public static List PhysicsMovers = new List();
+
+ private static float _lastCustomInterpolationStartTime = -1f;
+ private static float _lastCustomInterpolationDeltaTime = -1f;
+
+ public static KCCSettings Settings;
+
+ ///
+ /// Creates a KinematicCharacterSystem instance if there isn't already one
+ ///
+ public static void EnsureCreation()
+ {
+ if (_instance == null)
+ {
+ GameObject systemGameObject = new GameObject("KinematicCharacterSystem");
+ _instance = systemGameObject.AddComponent();
+
+ systemGameObject.hideFlags = HideFlags.NotEditable;
+ _instance.hideFlags = HideFlags.NotEditable;
+
+ Settings = ScriptableObject.CreateInstance();
+
+ GameObject.DontDestroyOnLoad(systemGameObject);
+ }
+ }
+
+ ///
+ /// Gets the KinematicCharacterSystem instance if any
+ ///
+ ///
+ public static KinematicCharacterSystem GetInstance()
+ {
+ return _instance;
+ }
+
+ ///
+ /// Sets the maximum capacity of the character motors list, to prevent allocations when adding characters
+ ///
+ ///
+ public static void SetCharacterMotorsCapacity(int capacity)
+ {
+ if (capacity < CharacterMotors.Count)
+ {
+ capacity = CharacterMotors.Count;
+ }
+ CharacterMotors.Capacity = capacity;
+ }
+
+ ///
+ /// Registers a KinematicCharacterMotor into the system
+ ///
+ public static void RegisterCharacterMotor(KinematicCharacterMotor motor)
+ {
+ CharacterMotors.Add(motor);
+ }
+
+ ///
+ /// Unregisters a KinematicCharacterMotor from the system
+ ///
+ public static void UnregisterCharacterMotor(KinematicCharacterMotor motor)
+ {
+ CharacterMotors.Remove(motor);
+ }
+
+ ///
+ /// Sets the maximum capacity of the physics movers list, to prevent allocations when adding movers
+ ///
+ ///
+ public static void SetPhysicsMoversCapacity(int capacity)
+ {
+ if (capacity < PhysicsMovers.Count)
+ {
+ capacity = PhysicsMovers.Count;
+ }
+ PhysicsMovers.Capacity = capacity;
+ }
+
+ ///
+ /// Registers a PhysicsMover into the system
+ ///
+ public static void RegisterPhysicsMover(PhysicsMover mover)
+ {
+ PhysicsMovers.Add(mover);
+
+ mover.Rigidbody.interpolation = RigidbodyInterpolation.None;
+ }
+
+ ///
+ /// Unregisters a PhysicsMover from the system
+ ///
+ public static void UnregisterPhysicsMover(PhysicsMover mover)
+ {
+ PhysicsMovers.Remove(mover);
+ }
+
+ // This is to prevent duplicating the singleton gameobject on script recompiles
+ private void OnDisable()
+ {
+ Destroy(this.gameObject);
+ }
+
+ private void Awake()
+ {
+ _instance = this;
+ }
+
+ private void FixedUpdate()
+ {
+ if (Settings.AutoSimulation)
+ {
+ float deltaTime = Time.deltaTime;
+
+ if (Settings.Interpolate)
+ {
+ PreSimulationInterpolationUpdate(deltaTime);
+ }
+
+ Simulate(deltaTime, CharacterMotors, PhysicsMovers);
+
+ if (Settings.Interpolate)
+ {
+ PostSimulationInterpolationUpdate(deltaTime);
+ }
+ }
+ }
+
+ private void LateUpdate()
+ {
+ if (Settings.Interpolate)
+ {
+ CustomInterpolationUpdate();
+ }
+ }
+
+ ///
+ /// Remembers the point to interpolate from for KinematicCharacterMotors and PhysicsMovers
+ ///
+ public static void PreSimulationInterpolationUpdate(float deltaTime)
+ {
+ // Save pre-simulation poses and place transform at transient pose
+ for (int i = 0; i < CharacterMotors.Count; i++)
+ {
+ KinematicCharacterMotor motor = CharacterMotors[i];
+
+ motor.InitialTickPosition = motor.TransientPosition;
+ motor.InitialTickRotation = motor.TransientRotation;
+
+ motor.Transform.SetPositionAndRotation(motor.TransientPosition, motor.TransientRotation);
+ }
+
+ for (int i = 0; i < PhysicsMovers.Count; i++)
+ {
+ PhysicsMover mover = PhysicsMovers[i];
+
+ mover.InitialTickPosition = mover.TransientPosition;
+ mover.InitialTickRotation = mover.TransientRotation;
+
+ mover.Transform.SetPositionAndRotation(mover.TransientPosition, mover.TransientRotation);
+ mover.Rigidbody.position = mover.TransientPosition;
+ mover.Rigidbody.rotation = mover.TransientRotation;
+ }
+ }
+
+ ///
+ /// Ticks characters and/or movers
+ ///
+ public static void Simulate(float deltaTime, List motors, List movers)
+ {
+ int characterMotorsCount = motors.Count;
+ int physicsMoversCount = movers.Count;
+
+#pragma warning disable 0162
+ // Update PhysicsMover velocities
+ for (int i = 0; i < physicsMoversCount; i++)
+ {
+ movers[i].VelocityUpdate(deltaTime);
+ }
+
+ // Character controller update phase 1
+ for (int i = 0; i < characterMotorsCount; i++)
+ {
+ motors[i].UpdatePhase1(deltaTime);
+ }
+
+ // Simulate PhysicsMover displacement
+ for (int i = 0; i < physicsMoversCount; i++)
+ {
+ PhysicsMover mover = movers[i];
+
+ mover.Transform.SetPositionAndRotation(mover.TransientPosition, mover.TransientRotation);
+ mover.Rigidbody.position = mover.TransientPosition;
+ mover.Rigidbody.rotation = mover.TransientRotation;
+ }
+
+ // Character controller update phase 2 and move
+ for (int i = 0; i < characterMotorsCount; i++)
+ {
+ KinematicCharacterMotor motor = motors[i];
+
+ motor.UpdatePhase2(deltaTime);
+
+ motor.Transform.SetPositionAndRotation(motor.TransientPosition, motor.TransientRotation);
+ }
+#pragma warning restore 0162
+ }
+
+ ///
+ /// Initiates the interpolation for KinematicCharacterMotors and PhysicsMovers
+ ///
+ public static void PostSimulationInterpolationUpdate(float deltaTime)
+ {
+ _lastCustomInterpolationStartTime = Time.time;
+ _lastCustomInterpolationDeltaTime = deltaTime;
+
+ // Return interpolated roots to their initial poses
+ for (int i = 0; i < CharacterMotors.Count; i++)
+ {
+ KinematicCharacterMotor motor = CharacterMotors[i];
+
+ motor.Transform.SetPositionAndRotation(motor.InitialTickPosition, motor.InitialTickRotation);
+ }
+
+ for (int i = 0; i < PhysicsMovers.Count; i++)
+ {
+ PhysicsMover mover = PhysicsMovers[i];
+
+ if (mover.MoveWithPhysics)
+ {
+ mover.Rigidbody.position = mover.InitialTickPosition;
+ mover.Rigidbody.rotation = mover.InitialTickRotation;
+
+ mover.Rigidbody.MovePosition(mover.TransientPosition);
+ mover.Rigidbody.MoveRotation(mover.TransientRotation);
+ }
+ else
+ {
+ mover.Rigidbody.position = (mover.TransientPosition);
+ mover.Rigidbody.rotation = (mover.TransientRotation);
+ }
+ }
+ }
+
+ ///
+ /// Handles per-frame interpolation
+ ///
+ private static void CustomInterpolationUpdate()
+ {
+ float interpolationFactor = Mathf.Clamp01((Time.time - _lastCustomInterpolationStartTime) / _lastCustomInterpolationDeltaTime);
+
+ // Handle characters interpolation
+ for (int i = 0; i < CharacterMotors.Count; i++)
+ {
+ KinematicCharacterMotor motor = CharacterMotors[i];
+
+ motor.Transform.SetPositionAndRotation(
+ Vector3.Lerp(motor.InitialTickPosition, motor.TransientPosition, interpolationFactor),
+ Quaternion.Slerp(motor.InitialTickRotation, motor.TransientRotation, interpolationFactor));
+ }
+
+ // Handle PhysicsMovers interpolation
+ for (int i = 0; i < PhysicsMovers.Count; i++)
+ {
+ PhysicsMover mover = PhysicsMovers[i];
+
+ mover.Transform.SetPositionAndRotation(
+ Vector3.Lerp(mover.InitialTickPosition, mover.TransientPosition, interpolationFactor),
+ Quaternion.Slerp(mover.InitialTickRotation, mover.TransientRotation, interpolationFactor));
+
+ Vector3 newPos = mover.Transform.position;
+ Quaternion newRot = mover.Transform.rotation;
+ mover.PositionDeltaFromInterpolation = newPos - mover.LatestInterpolationPosition;
+ mover.RotationDeltaFromInterpolation = Quaternion.Inverse(mover.LatestInterpolationRotation) * newRot;
+ mover.LatestInterpolationPosition = newPos;
+ mover.LatestInterpolationRotation = newRot;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/Plugins/KinematicCharacterController/Core/KinematicCharacterSystem.cs.meta b/Assets/Plugins/KinematicCharacterController/Core/KinematicCharacterSystem.cs.meta
new file mode 100644
index 0000000..2d1bcb3
--- /dev/null
+++ b/Assets/Plugins/KinematicCharacterController/Core/KinematicCharacterSystem.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: dbd80c9a62d41084aad50ae44255beb9
+timeCreated: 1500241576
+licenseType: Store
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/KinematicCharacterController/Core/PhysicsMover.cs b/Assets/Plugins/KinematicCharacterController/Core/PhysicsMover.cs
new file mode 100644
index 0000000..2af310c
--- /dev/null
+++ b/Assets/Plugins/KinematicCharacterController/Core/PhysicsMover.cs
@@ -0,0 +1,261 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace KinematicCharacterController
+{
+ ///
+ /// Represents the entire state of a PhysicsMover that is pertinent for simulation.
+ /// Use this to save state or revert to past state
+ ///
+ [System.Serializable]
+ public struct PhysicsMoverState
+ {
+ public Vector3 Position;
+ public Quaternion Rotation;
+ public Vector3 Velocity;
+ public Vector3 AngularVelocity;
+ }
+
+ ///
+ /// Component that manages the movement of moving kinematic rigidbodies for
+ /// proper interaction with characters
+ ///
+ [RequireComponent(typeof(Rigidbody))]
+ public class PhysicsMover : MonoBehaviour
+ {
+ ///
+ /// The mover's Rigidbody
+ ///
+ [ReadOnly]
+ public Rigidbody Rigidbody;
+
+ ///
+ /// Determines if the platform moves with rigidbody.MovePosition (when true), or with rigidbody.position (when false)
+ ///
+ public bool MoveWithPhysics = true;
+
+ ///
+ /// Index of this motor in KinematicCharacterSystem arrays
+ ///
+ [NonSerialized]
+ public IMoverController MoverController;
+ ///
+ /// Remembers latest position in interpolation
+ ///
+ [NonSerialized]
+ public Vector3 LatestInterpolationPosition;
+ ///
+ /// Remembers latest rotation in interpolation
+ ///
+ [NonSerialized]
+ public Quaternion LatestInterpolationRotation;
+ ///
+ /// The latest movement made by interpolation
+ ///
+ [NonSerialized]
+ public Vector3 PositionDeltaFromInterpolation;
+ ///
+ /// The latest rotation made by interpolation
+ ///
+ [NonSerialized]
+ public Quaternion RotationDeltaFromInterpolation;
+
+ ///
+ /// Index of this motor in KinematicCharacterSystem arrays
+ ///
+ public int IndexInCharacterSystem { get; set; }
+ ///
+ /// Remembers initial position before all simulation are done
+ ///
+ public Vector3 Velocity { get; protected set; }
+ ///
+ /// Remembers initial position before all simulation are done
+ ///
+ public Vector3 AngularVelocity { get; protected set; }
+ ///
+ /// Remembers initial position before all simulation are done
+ ///
+ public Vector3 InitialTickPosition { get; set; }
+ ///
+ /// Remembers initial rotation before all simulation are done
+ ///
+ public Quaternion InitialTickRotation { get; set; }
+
+ ///
+ /// The mover's Transform
+ ///
+ public Transform Transform { get; private set; }
+ ///
+ /// The character's position before the movement calculations began
+ ///
+ public Vector3 InitialSimulationPosition { get; private set; }
+ ///
+ /// The character's rotation before the movement calculations began
+ ///
+ public Quaternion InitialSimulationRotation { get; private set; }
+
+ private Vector3 _internalTransientPosition;
+
+ ///
+ /// The mover's rotation (always up-to-date during the character update phase)
+ ///
+ public Vector3 TransientPosition
+ {
+ get
+ {
+ return _internalTransientPosition;
+ }
+ private set
+ {
+ _internalTransientPosition = value;
+ }
+ }
+
+ private Quaternion _internalTransientRotation;
+ ///
+ /// The mover's rotation (always up-to-date during the character update phase)
+ ///
+ public Quaternion TransientRotation
+ {
+ get
+ {
+ return _internalTransientRotation;
+ }
+ private set
+ {
+ _internalTransientRotation = value;
+ }
+ }
+
+
+ private void Reset()
+ {
+ ValidateData();
+ }
+
+ private void OnValidate()
+ {
+ ValidateData();
+ }
+
+ ///
+ /// Handle validating all required values
+ ///
+ public void ValidateData()
+ {
+ Rigidbody = gameObject.GetComponent();
+
+ Rigidbody.centerOfMass = Vector3.zero;
+ Rigidbody.maxAngularVelocity = Mathf.Infinity;
+ Rigidbody.maxDepenetrationVelocity = Mathf.Infinity;
+ Rigidbody.isKinematic = true;
+ Rigidbody.interpolation = RigidbodyInterpolation.None;
+ }
+
+ private void OnEnable()
+ {
+ KinematicCharacterSystem.EnsureCreation();
+ KinematicCharacterSystem.RegisterPhysicsMover(this);
+ }
+
+ private void OnDisable()
+ {
+ KinematicCharacterSystem.UnregisterPhysicsMover(this);
+ }
+
+ private void Awake()
+ {
+ Transform = this.transform;
+ ValidateData();
+
+ TransientPosition = Rigidbody.position;
+ TransientRotation = Rigidbody.rotation;
+ InitialSimulationPosition = Rigidbody.position;
+ InitialSimulationRotation = Rigidbody.rotation;
+ LatestInterpolationPosition = Transform.position;
+ LatestInterpolationRotation = Transform.rotation;
+ }
+
+ ///
+ /// Sets the mover's position directly
+ ///
+ public void SetPosition(Vector3 position)
+ {
+ Transform.position = position;
+ Rigidbody.position = position;
+ InitialSimulationPosition = position;
+ TransientPosition = position;
+ }
+
+ ///
+ /// Sets the mover's rotation directly
+ ///
+ public void SetRotation(Quaternion rotation)
+ {
+ Transform.rotation = rotation;
+ Rigidbody.rotation = rotation;
+ InitialSimulationRotation = rotation;
+ TransientRotation = rotation;
+ }
+
+ ///
+ /// Sets the mover's position and rotation directly
+ ///
+ public void SetPositionAndRotation(Vector3 position, Quaternion rotation)
+ {
+ Transform.SetPositionAndRotation(position, rotation);
+ Rigidbody.position = position;
+ Rigidbody.rotation = rotation;
+ InitialSimulationPosition = position;
+ InitialSimulationRotation = rotation;
+ TransientPosition = position;
+ TransientRotation = rotation;
+ }
+
+ ///
+ /// Returns all the state information of the mover that is pertinent for simulation
+ ///
+ public PhysicsMoverState GetState()
+ {
+ PhysicsMoverState state = new PhysicsMoverState();
+
+ state.Position = TransientPosition;
+ state.Rotation = TransientRotation;
+ state.Velocity = Velocity;
+ state.AngularVelocity = AngularVelocity;
+
+ return state;
+ }
+
+ ///
+ /// Applies a mover state instantly
+ ///
+ public void ApplyState(PhysicsMoverState state)
+ {
+ SetPositionAndRotation(state.Position, state.Rotation);
+ Velocity = state.Velocity;
+ AngularVelocity = state.AngularVelocity;
+ }
+
+ ///
+ /// Caches velocity values based on deltatime and target position/rotations
+ ///
+ public void VelocityUpdate(float deltaTime)
+ {
+ InitialSimulationPosition = TransientPosition;
+ InitialSimulationRotation = TransientRotation;
+
+ MoverController.UpdateMovement(out _internalTransientPosition, out _internalTransientRotation, deltaTime);
+
+ if (deltaTime > 0f)
+ {
+ Velocity = (TransientPosition - InitialSimulationPosition) / deltaTime;
+
+ Quaternion rotationFromCurrentToGoal = TransientRotation * (Quaternion.Inverse(InitialSimulationRotation));
+ AngularVelocity = (Mathf.Deg2Rad * rotationFromCurrentToGoal.eulerAngles) / deltaTime;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/Plugins/KinematicCharacterController/Core/PhysicsMover.cs.meta b/Assets/Plugins/KinematicCharacterController/Core/PhysicsMover.cs.meta
new file mode 100644
index 0000000..22c397e
--- /dev/null
+++ b/Assets/Plugins/KinematicCharacterController/Core/PhysicsMover.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 075ccbe9837b0744985e09ba3f015b9b
+timeCreated: 1488089271
+licenseType: Store
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/KinematicCharacterController/Core/ReadOnlyAttribute.cs b/Assets/Plugins/KinematicCharacterController/Core/ReadOnlyAttribute.cs
new file mode 100644
index 0000000..918d460
--- /dev/null
+++ b/Assets/Plugins/KinematicCharacterController/Core/ReadOnlyAttribute.cs
@@ -0,0 +1,8 @@
+using UnityEngine;
+
+namespace KinematicCharacterController
+{
+ public class ReadOnlyAttribute : PropertyAttribute
+ {
+ }
+}
\ No newline at end of file
diff --git a/Assets/Plugins/KinematicCharacterController/Core/ReadOnlyAttribute.cs.meta b/Assets/Plugins/KinematicCharacterController/Core/ReadOnlyAttribute.cs.meta
new file mode 100644
index 0000000..f75dd61
--- /dev/null
+++ b/Assets/Plugins/KinematicCharacterController/Core/ReadOnlyAttribute.cs.meta
@@ -0,0 +1,13 @@
+fileFormatVersion: 2
+guid: 63e8828991cf67f42bfeb498686aad0b
+timeCreated: 1504493014
+licenseType: Store
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/KinematicCharacterController/ExampleCharacter.meta b/Assets/Plugins/KinematicCharacterController/ExampleCharacter.meta
new file mode 100644
index 0000000..0736ab8
--- /dev/null
+++ b/Assets/Plugins/KinematicCharacterController/ExampleCharacter.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: b8da84cc169c79b4894eefab60565279
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/KinematicCharacterController/ExampleCharacter/Materials.meta b/Assets/Plugins/KinematicCharacterController/ExampleCharacter/Materials.meta
new file mode 100644
index 0000000..6e28967
--- /dev/null
+++ b/Assets/Plugins/KinematicCharacterController/ExampleCharacter/Materials.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: f1cc217451b989d4db08290832cb9dc0
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/KinematicCharacterController/ExampleCharacter/Materials/White.mat b/Assets/Plugins/KinematicCharacterController/ExampleCharacter/Materials/White.mat
new file mode 100644
index 0000000..6805a97
--- /dev/null
+++ b/Assets/Plugins/KinematicCharacterController/ExampleCharacter/Materials/White.mat
@@ -0,0 +1,76 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!21 &2100000
+Material:
+ serializedVersion: 6
+ m_ObjectHideFlags: 0
+ m_PrefabParentObject: {fileID: 0}
+ m_PrefabInternal: {fileID: 0}
+ m_Name: White
+ m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0}
+ m_ShaderKeywords:
+ m_LightmapFlags: 4
+ m_EnableInstancingVariants: 0
+ m_DoubleSidedGI: 0
+ m_CustomRenderQueue: -1
+ stringTagMap: {}
+ disabledShaderPasses: []
+ m_SavedProperties:
+ serializedVersion: 3
+ m_TexEnvs:
+ - _BumpMap:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - _DetailAlbedoMap:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - _DetailMask:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - _DetailNormalMap:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - _EmissionMap:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 4, y: 4}
+ m_Offset: {x: 0, y: 0}
+ - _MainTex:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 4, y: 4}
+ m_Offset: {x: 0, y: 0}
+ - _MetallicGlossMap:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - _OcclusionMap:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - _ParallaxMap:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ m_Floats:
+ - _BumpScale: 0.5
+ - _Cutoff: 0.5
+ - _DetailNormalMapScale: 1
+ - _DstBlend: 0
+ - _GlossMapScale: 1
+ - _Glossiness: 0.661
+ - _GlossyReflections: 1
+ - _Metallic: 0
+ - _Mode: 0
+ - _OcclusionStrength: 1
+ - _Parallax: 0.02
+ - _SmoothnessTextureChannel: 0
+ - _SpecularHighlights: 1
+ - _SrcBlend: 1
+ - _UVSec: 0
+ - _ZWrite: 1
+ m_Colors:
+ - _Color: {r: 1, g: 1, b: 1, a: 1}
+ - _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
diff --git a/Assets/Plugins/KinematicCharacterController/ExampleCharacter/Materials/White.mat.meta b/Assets/Plugins/KinematicCharacterController/ExampleCharacter/Materials/White.mat.meta
new file mode 100644
index 0000000..deb2159
--- /dev/null
+++ b/Assets/Plugins/KinematicCharacterController/ExampleCharacter/Materials/White.mat.meta
@@ -0,0 +1,9 @@
+fileFormatVersion: 2
+guid: cf774b4c734b1d04e9fbf6d71e964591
+timeCreated: 1493180259
+licenseType: Store
+NativeFormatImporter:
+ mainObjectFileID: 2100000
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/KinematicCharacterController/ExampleCharacter/Prefabs.meta b/Assets/Plugins/KinematicCharacterController/ExampleCharacter/Prefabs.meta
new file mode 100644
index 0000000..7b5f9ad
--- /dev/null
+++ b/Assets/Plugins/KinematicCharacterController/ExampleCharacter/Prefabs.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 974715e4a94547d4189e0801712b2685
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/KinematicCharacterController/ExampleCharacter/Prefabs/ExampleCamera.prefab b/Assets/Plugins/KinematicCharacterController/ExampleCharacter/Prefabs/ExampleCamera.prefab
new file mode 100644
index 0000000..6683575
--- /dev/null
+++ b/Assets/Plugins/KinematicCharacterController/ExampleCharacter/Prefabs/ExampleCamera.prefab
@@ -0,0 +1,127 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!1 &1937979026721672
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 4353896453404184}
+ - component: {fileID: 20127477809793788}
+ - component: {fileID: 124551908420297706}
+ - component: {fileID: 81410469953920030}
+ - component: {fileID: 114014416615829144}
+ m_Layer: 0
+ m_Name: ExampleCamera
+ m_TagString: MainCamera
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!4 &4353896453404184
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1937979026721672}
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: -5}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_Children: []
+ m_Father: {fileID: 0}
+ m_RootOrder: 0
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!20 &20127477809793788
+Camera:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1937979026721672}
+ m_Enabled: 1
+ serializedVersion: 2
+ m_ClearFlags: 1
+ m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0}
+ m_projectionMatrixMode: 1
+ m_SensorSize: {x: 36, y: 24}
+ m_LensShift: {x: 0, y: 0}
+ m_GateFitMode: 2
+ m_FocalLength: 50
+ m_NormalizedViewPortRect:
+ serializedVersion: 2
+ x: 0
+ y: 0
+ width: 1
+ height: 1
+ near clip plane: 0.3
+ far clip plane: 1000
+ field of view: 75
+ orthographic: 0
+ orthographic size: 5
+ m_Depth: -1
+ m_CullingMask:
+ serializedVersion: 2
+ m_Bits: 4294967295
+ m_RenderingPath: -1
+ m_TargetTexture: {fileID: 0}
+ m_TargetDisplay: 0
+ m_TargetEye: 3
+ m_HDR: 1
+ m_AllowMSAA: 1
+ m_AllowDynamicResolution: 0
+ m_ForceIntoRT: 0
+ m_OcclusionCulling: 1
+ m_StereoConvergence: 10
+ m_StereoSeparation: 0.022
+--- !u!124 &124551908420297706
+Behaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1937979026721672}
+ m_Enabled: 1
+--- !u!81 &81410469953920030
+AudioListener:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1937979026721672}
+ m_Enabled: 1
+--- !u!114 &114014416615829144
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1937979026721672}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 24092a40b02616e479baeb940325e339, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ Camera: {fileID: 20127477809793788}
+ FollowPointFraming: {x: 0, y: 0}
+ FollowingSharpness: 10000
+ DefaultDistance: 6
+ MinDistance: 0
+ MaxDistance: 10
+ DistanceMovementSpeed: 5
+ DistanceMovementSharpness: 10
+ InvertX: 0
+ InvertY: 0
+ DefaultVerticalAngle: 20
+ MinVerticalAngle: -90
+ MaxVerticalAngle: 90
+ RotationSpeed: 1.3
+ RotationSharpness: 10000
+ ObstructionCheckRadius: 0.2
+ ObstructionLayers:
+ serializedVersion: 2
+ m_Bits: 4294965759
+ ObstructionSharpness: 10000
+ IgnoredColliders: []
diff --git a/Assets/Plugins/KinematicCharacterController/ExampleCharacter/Prefabs/ExampleCamera.prefab.meta b/Assets/Plugins/KinematicCharacterController/ExampleCharacter/Prefabs/ExampleCamera.prefab.meta
new file mode 100644
index 0000000..bee0535
--- /dev/null
+++ b/Assets/Plugins/KinematicCharacterController/ExampleCharacter/Prefabs/ExampleCamera.prefab.meta
@@ -0,0 +1,9 @@
+fileFormatVersion: 2
+guid: c75e91cf060bbf04e89204fa400606df
+timeCreated: 1499181144
+licenseType: Store
+NativeFormatImporter:
+ mainObjectFileID: 100100000
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/KinematicCharacterController/ExampleCharacter/Prefabs/ExampleCharacter.prefab b/Assets/Plugins/KinematicCharacterController/ExampleCharacter/Prefabs/ExampleCharacter.prefab
new file mode 100644
index 0000000..8de8f9c
--- /dev/null
+++ b/Assets/Plugins/KinematicCharacterController/ExampleCharacter/Prefabs/ExampleCharacter.prefab
@@ -0,0 +1,394 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!1 &43330285
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 43330286}
+ m_Layer: 0
+ m_Name: Root
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!4 &43330286
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 43330285}
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_Children:
+ - {fileID: 4036232530805280}
+ - {fileID: 4718917724592500}
+ m_Father: {fileID: 4898694727102280}
+ m_RootOrder: 0
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!1 &1072008595159340
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 4036232530805280}
+ m_Layer: 0
+ m_Name: Meshes
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!4 &4036232530805280
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1072008595159340}
+ m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_Children:
+ - {fileID: 4903610365175460}
+ m_Father: {fileID: 43330286}
+ m_RootOrder: 0
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!1 &1339159333115884
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 4903610365175460}
+ - component: {fileID: 33711778101729860}
+ - component: {fileID: 23310449951372792}
+ m_Layer: 0
+ m_Name: Capsule
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!4 &4903610365175460
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1339159333115884}
+ m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+ m_LocalPosition: {x: 0, y: 1, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_Children:
+ - {fileID: 4485742127011420}
+ m_Father: {fileID: 4036232530805280}
+ m_RootOrder: 0
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!33 &33711778101729860
+MeshFilter:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1339159333115884}
+ m_Mesh: {fileID: 10208, guid: 0000000000000000e000000000000000, type: 0}
+--- !u!23 &23310449951372792
+MeshRenderer:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1339159333115884}
+ m_Enabled: 1
+ m_CastShadows: 1
+ m_ReceiveShadows: 1
+ m_DynamicOccludee: 1
+ m_MotionVectors: 1
+ m_LightProbeUsage: 1
+ m_ReflectionProbeUsage: 1
+ m_RayTracingMode: 2
+ m_RenderingLayerMask: 4294967295
+ m_RendererPriority: 0
+ m_Materials:
+ - {fileID: 2100000, guid: cf774b4c734b1d04e9fbf6d71e964591, type: 2}
+ m_StaticBatchInfo:
+ firstSubMesh: 0
+ subMeshCount: 0
+ m_StaticBatchRoot: {fileID: 0}
+ m_ProbeAnchor: {fileID: 0}
+ m_LightProbeVolumeOverride: {fileID: 0}
+ m_ScaleInLightmap: 1
+ m_ReceiveGI: 1
+ m_PreserveUVs: 1
+ m_IgnoreNormalsForChartDetection: 0
+ m_ImportantGI: 0
+ m_StitchLightmapSeams: 0
+ m_SelectedEditorRenderState: 3
+ m_MinimumChartSize: 4
+ m_AutoUVMaxDistance: 0.5
+ m_AutoUVMaxAngle: 89
+ m_LightmapParameters: {fileID: 0}
+ m_SortingLayerID: 0
+ m_SortingLayer: 0
+ m_SortingOrder: 0
+--- !u!1 &1398904762131290
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 4898694727102280}
+ - component: {fileID: 136457490459165436}
+ - component: {fileID: 114565867274256808}
+ - component: {fileID: 114967586934651944}
+ - component: {fileID: 6177337246575538918}
+ m_Layer: 0
+ m_Name: ExampleCharacter
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!4 &4898694727102280
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1398904762131290}
+ m_LocalRotation: {x: 0, y: 0.7071068, z: 0, w: 0.70710677}
+ m_LocalPosition: {x: -40.14, y: 0, z: -29.24}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_Children:
+ - {fileID: 43330286}
+ m_Father: {fileID: 0}
+ m_RootOrder: 0
+ m_LocalEulerAnglesHint: {x: 0, y: 90.00001, z: 0}
+--- !u!136 &136457490459165436
+CapsuleCollider:
+ m_ObjectHideFlags: 8
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1398904762131290}
+ m_Material: {fileID: 0}
+ m_IsTrigger: 0
+ m_Enabled: 1
+ m_Radius: 0.5
+ m_Height: 2
+ m_Direction: 1
+ m_Center: {x: 0, y: 1, z: 0}
+--- !u!114 &114565867274256808
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1398904762131290}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 76020eee813ed7844bcea94c5d5ce76a, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ Motor: {fileID: 114967586934651944}
+ MaxStableMoveSpeed: 10
+ StableMovementSharpness: 15
+ OrientationSharpness: 10
+ OrientationMethod: 1
+ MaxAirMoveSpeed: 15
+ AirAccelerationSpeed: 18
+ Drag: 0.1
+ AllowJumpingWhenSliding: 0
+ JumpUpSpeed: 10
+ JumpScalableForwardSpeed: 10
+ JumpPreGroundingGraceTime: 0
+ JumpPostGroundingGraceTime: 0
+ IgnoredColliders:
+ - {fileID: 0}
+ - {fileID: 0}
+ BonusOrientationMethod: 0
+ BonusOrientationSharpness: 10
+ Gravity: {x: 0, y: -30, z: 0}
+ MeshRoot: {fileID: 4036232530805280}
+ CameraFollowPoint: {fileID: 4718917724592500}
+--- !u!114 &114967586934651944
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1398904762131290}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 4d1bc5515e3ab954e80599c538834774, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ Capsule: {fileID: 136457490459165436}
+ CapsuleRadius: 0.5
+ CapsuleHeight: 2
+ CapsuleYOffset: 1
+ CapsulePhysicsMaterial: {fileID: 0}
+ GroundDetectionExtraDistance: 0
+ MaxStableSlopeAngle: 60
+ StableGroundLayers:
+ serializedVersion: 2
+ m_Bits: 4294967295
+ DiscreteCollisionEvents: 0
+ StepHandling: 1
+ MaxStepHeight: 0.5
+ AllowSteppingWithoutStableGrounding: 0
+ MinRequiredStepDepth: 0.1
+ LedgeAndDenivelationHandling: 1
+ MaxStableDistanceFromLedge: 0.5
+ MaxVelocityForLedgeSnap: 0
+ MaxStableDenivelationAngle: 180
+ InteractiveRigidbodyHandling: 1
+ RigidbodyInteractionType: 0
+ SimulatedCharacterMass: 1
+ PreserveAttachedRigidbodyMomentum: 1
+ HasPlanarConstraint: 0
+ PlanarConstraintAxis: {x: 0, y: 0, z: 1}
+ MaxMovementIterations: 5
+ MaxDecollisionIterations: 1
+ CheckMovementInitialOverlaps: 1
+ KillVelocityWhenExceedMaxMovementIterations: 1
+ KillRemainingMovementWhenExceedMaxMovementIterations: 1
+--- !u!54 &6177337246575538918
+Rigidbody:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1398904762131290}
+ serializedVersion: 2
+ m_Mass: 1
+ m_Drag: 0
+ m_AngularDrag: 0.05
+ m_UseGravity: 0
+ m_IsKinematic: 1
+ m_Interpolate: 0
+ m_Constraints: 0
+ m_CollisionDetection: 0
+--- !u!1 &1758411822872852
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 4485742127011420}
+ - component: {fileID: 33111717300596878}
+ - component: {fileID: 23624829931232870}
+ m_Layer: 0
+ m_Name: Cylinder
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!4 &4485742127011420
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1758411822872852}
+ m_LocalRotation: {x: 0.70710677, y: -0, z: -0, w: 0.7071068}
+ m_LocalPosition: {x: 0, y: 0.44099998, z: 0.18199922}
+ m_LocalScale: {x: 0.88040376, y: 0.34093377, z: 0.51656276}
+ m_Children: []
+ m_Father: {fileID: 4903610365175460}
+ m_RootOrder: 0
+ m_LocalEulerAnglesHint: {x: 90.00001, y: 0, z: 0}
+--- !u!33 &33111717300596878
+MeshFilter:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1758411822872852}
+ m_Mesh: {fileID: 10206, guid: 0000000000000000e000000000000000, type: 0}
+--- !u!23 &23624829931232870
+MeshRenderer:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1758411822872852}
+ m_Enabled: 1
+ m_CastShadows: 1
+ m_ReceiveShadows: 1
+ m_DynamicOccludee: 1
+ m_MotionVectors: 1
+ m_LightProbeUsage: 1
+ m_ReflectionProbeUsage: 1
+ m_RayTracingMode: 2
+ m_RenderingLayerMask: 4294967295
+ m_RendererPriority: 0
+ m_Materials:
+ - {fileID: 2100000, guid: cf774b4c734b1d04e9fbf6d71e964591, type: 2}
+ m_StaticBatchInfo:
+ firstSubMesh: 0
+ subMeshCount: 0
+ m_StaticBatchRoot: {fileID: 0}
+ m_ProbeAnchor: {fileID: 0}
+ m_LightProbeVolumeOverride: {fileID: 0}
+ m_ScaleInLightmap: 1
+ m_ReceiveGI: 1
+ m_PreserveUVs: 1
+ m_IgnoreNormalsForChartDetection: 0
+ m_ImportantGI: 0
+ m_StitchLightmapSeams: 0
+ m_SelectedEditorRenderState: 3
+ m_MinimumChartSize: 4
+ m_AutoUVMaxDistance: 0.5
+ m_AutoUVMaxAngle: 89
+ m_LightmapParameters: {fileID: 0}
+ m_SortingLayerID: 0
+ m_SortingLayer: 0
+ m_SortingOrder: 0
+--- !u!1 &1854746833484672
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 4718917724592500}
+ m_Layer: 0
+ m_Name: CameraTarget
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!4 &4718917724592500
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1854746833484672}
+ m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+ m_LocalPosition: {x: 0, y: 1.43, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_Children: []
+ m_Father: {fileID: 43330286}
+ m_RootOrder: 1
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
diff --git a/Assets/Plugins/KinematicCharacterController/ExampleCharacter/Prefabs/ExampleCharacter.prefab.meta b/Assets/Plugins/KinematicCharacterController/ExampleCharacter/Prefabs/ExampleCharacter.prefab.meta
new file mode 100644
index 0000000..ba4ee11
--- /dev/null
+++ b/Assets/Plugins/KinematicCharacterController/ExampleCharacter/Prefabs/ExampleCharacter.prefab.meta
@@ -0,0 +1,9 @@
+fileFormatVersion: 2
+guid: d295472d4ce25404e9aed2627c34801f
+timeCreated: 1499181148
+licenseType: Store
+NativeFormatImporter:
+ mainObjectFileID: 100100000
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/KinematicCharacterController/ExampleCharacter/Scripts.meta b/Assets/Plugins/KinematicCharacterController/ExampleCharacter/Scripts.meta
new file mode 100644
index 0000000..7fb4034
--- /dev/null
+++ b/Assets/Plugins/KinematicCharacterController/ExampleCharacter/Scripts.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 9751300dc66704b4f87d358c2a60f913
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/KinematicCharacterController/ExampleCharacter/Scripts/ExampleCharacterCamera.cs b/Assets/Plugins/KinematicCharacterController/ExampleCharacter/Scripts/ExampleCharacterCamera.cs
new file mode 100644
index 0000000..44ded71
--- /dev/null
+++ b/Assets/Plugins/KinematicCharacterController/ExampleCharacter/Scripts/ExampleCharacterCamera.cs
@@ -0,0 +1,179 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace KinematicCharacterController.Examples
+{
+ public class ExampleCharacterCamera : MonoBehaviour
+ {
+ [Header("Framing")]
+ public Camera Camera;
+ public Vector2 FollowPointFraming = new Vector2(0f, 0f);
+ public float FollowingSharpness = 10000f;
+
+ [Header("Distance")]
+ public float DefaultDistance = 6f;
+ public float MinDistance = 0f;
+ public float MaxDistance = 10f;
+ public float DistanceMovementSpeed = 5f;
+ public float DistanceMovementSharpness = 10f;
+
+ [Header("Rotation")]
+ public bool InvertX = false;
+ public bool InvertY = false;
+ [Range(-90f, 90f)]
+ public float DefaultVerticalAngle = 20f;
+ [Range(-90f, 90f)]
+ public float MinVerticalAngle = -90f;
+ [Range(-90f, 90f)]
+ public float MaxVerticalAngle = 90f;
+ public float RotationSpeed = 1f;
+ public float RotationSharpness = 10000f;
+ public bool RotateWithPhysicsMover = false;
+
+ [Header("Obstruction")]
+ public float ObstructionCheckRadius = 0.2f;
+ public LayerMask ObstructionLayers = -1;
+ public float ObstructionSharpness = 10000f;
+ public List IgnoredColliders = new List();
+
+ public Transform Transform { get; private set; }
+ public Transform FollowTransform { get; private set; }
+
+ public Vector3 PlanarDirection { get; set; }
+ public float TargetDistance { get; set; }
+
+ private bool _distanceIsObstructed;
+ private float _currentDistance;
+ private float _targetVerticalAngle;
+ private RaycastHit _obstructionHit;
+ private int _obstructionCount;
+ private RaycastHit[] _obstructions = new RaycastHit[MaxObstructions];
+ private float _obstructionTime;
+ private Vector3 _currentFollowPosition;
+
+ private const int MaxObstructions = 32;
+
+ void OnValidate()
+ {
+ DefaultDistance = Mathf.Clamp(DefaultDistance, MinDistance, MaxDistance);
+ DefaultVerticalAngle = Mathf.Clamp(DefaultVerticalAngle, MinVerticalAngle, MaxVerticalAngle);
+ }
+
+ void Awake()
+ {
+ Transform = this.transform;
+
+ _currentDistance = DefaultDistance;
+ TargetDistance = _currentDistance;
+
+ _targetVerticalAngle = 0f;
+
+ PlanarDirection = Vector3.forward;
+ }
+
+ // Set the transform that the camera will orbit around
+ public void SetFollowTransform(Transform t)
+ {
+ FollowTransform = t;
+ PlanarDirection = FollowTransform.forward;
+ _currentFollowPosition = FollowTransform.position;
+ }
+
+ public void UpdateWithInput(float deltaTime, float zoomInput, Vector3 rotationInput)
+ {
+ if (FollowTransform)
+ {
+ if (InvertX)
+ {
+ rotationInput.x *= -1f;
+ }
+ if (InvertY)
+ {
+ rotationInput.y *= -1f;
+ }
+
+ // Process rotation input
+ Quaternion rotationFromInput = Quaternion.Euler(FollowTransform.up * (rotationInput.x * RotationSpeed));
+ PlanarDirection = rotationFromInput * PlanarDirection;
+ PlanarDirection = Vector3.Cross(FollowTransform.up, Vector3.Cross(PlanarDirection, FollowTransform.up));
+ Quaternion planarRot = Quaternion.LookRotation(PlanarDirection, FollowTransform.up);
+
+ _targetVerticalAngle -= (rotationInput.y * RotationSpeed);
+ _targetVerticalAngle = Mathf.Clamp(_targetVerticalAngle, MinVerticalAngle, MaxVerticalAngle);
+ Quaternion verticalRot = Quaternion.Euler(_targetVerticalAngle, 0, 0);
+ Quaternion targetRotation = Quaternion.Slerp(Transform.rotation, planarRot * verticalRot, 1f - Mathf.Exp(-RotationSharpness * deltaTime));
+
+ // Apply rotation
+ Transform.rotation = targetRotation;
+
+ // Process distance input
+ if (_distanceIsObstructed && Mathf.Abs(zoomInput) > 0f)
+ {
+ TargetDistance = _currentDistance;
+ }
+ TargetDistance += zoomInput * DistanceMovementSpeed;
+ TargetDistance = Mathf.Clamp(TargetDistance, MinDistance, MaxDistance);
+
+ // Find the smoothed follow position
+ _currentFollowPosition = Vector3.Lerp(_currentFollowPosition, FollowTransform.position, 1f - Mathf.Exp(-FollowingSharpness * deltaTime));
+
+ // Handle obstructions
+ {
+ RaycastHit closestHit = new RaycastHit();
+ closestHit.distance = Mathf.Infinity;
+ _obstructionCount = Physics.SphereCastNonAlloc(_currentFollowPosition, ObstructionCheckRadius, -Transform.forward, _obstructions, TargetDistance, ObstructionLayers, QueryTriggerInteraction.Ignore);
+ for (int i = 0; i < _obstructionCount; i++)
+ {
+ bool isIgnored = false;
+ for (int j = 0; j < IgnoredColliders.Count; j++)
+ {
+ if (IgnoredColliders[j] == _obstructions[i].collider)
+ {
+ isIgnored = true;
+ break;
+ }
+ }
+ for (int j = 0; j < IgnoredColliders.Count; j++)
+ {
+ if (IgnoredColliders[j] == _obstructions[i].collider)
+ {
+ isIgnored = true;
+ break;
+ }
+ }
+
+ if (!isIgnored && _obstructions[i].distance < closestHit.distance && _obstructions[i].distance > 0)
+ {
+ closestHit = _obstructions[i];
+ }
+ }
+
+ // If obstructions detecter
+ if (closestHit.distance < Mathf.Infinity)
+ {
+ _distanceIsObstructed = true;
+ _currentDistance = Mathf.Lerp(_currentDistance, closestHit.distance, 1 - Mathf.Exp(-ObstructionSharpness * deltaTime));
+ }
+ // If no obstruction
+ else
+ {
+ _distanceIsObstructed = false;
+ _currentDistance = Mathf.Lerp(_currentDistance, TargetDistance, 1 - Mathf.Exp(-DistanceMovementSharpness * deltaTime));
+ }
+ }
+
+ // Find the smoothed camera orbit position
+ Vector3 targetPosition = _currentFollowPosition - ((targetRotation * Vector3.forward) * _currentDistance);
+
+ // Handle framing
+ targetPosition += Transform.right * FollowPointFraming.x;
+ targetPosition += Transform.up * FollowPointFraming.y;
+
+ // Apply position
+ Transform.position = targetPosition;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/Plugins/KinematicCharacterController/ExampleCharacter/Scripts/ExampleCharacterCamera.cs.meta b/Assets/Plugins/KinematicCharacterController/ExampleCharacter/Scripts/ExampleCharacterCamera.cs.meta
new file mode 100644
index 0000000..64ae17e
--- /dev/null
+++ b/Assets/Plugins/KinematicCharacterController/ExampleCharacter/Scripts/ExampleCharacterCamera.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 24092a40b02616e479baeb940325e339
+timeCreated: 1485657083
+licenseType: Store
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/KinematicCharacterController/ExampleCharacter/Scripts/ExampleCharacterController.cs b/Assets/Plugins/KinematicCharacterController/ExampleCharacter/Scripts/ExampleCharacterController.cs
new file mode 100644
index 0000000..090780d
--- /dev/null
+++ b/Assets/Plugins/KinematicCharacterController/ExampleCharacter/Scripts/ExampleCharacterController.cs
@@ -0,0 +1,516 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using KinematicCharacterController;
+using System;
+
+namespace KinematicCharacterController.Examples
+{
+ public enum CharacterState
+ {
+ Default,
+ }
+
+ public enum OrientationMethod
+ {
+ TowardsCamera,
+ TowardsMovement,
+ }
+
+ public struct PlayerCharacterInputs
+ {
+ public float MoveAxisForward;
+ public float MoveAxisRight;
+ public Quaternion CameraRotation;
+ public bool JumpDown;
+ public bool CrouchDown;
+ public bool CrouchUp;
+ }
+
+ public struct AICharacterInputs
+ {
+ public Vector3 MoveVector;
+ public Vector3 LookVector;
+ }
+
+ public enum BonusOrientationMethod
+ {
+ None,
+ TowardsGravity,
+ TowardsGroundSlopeAndGravity,
+ }
+
+ public class ExampleCharacterController : MonoBehaviour, ICharacterController
+ {
+ public KinematicCharacterMotor Motor;
+
+ [Header("Stable Movement")]
+ public float MaxStableMoveSpeed = 10f;
+ public float StableMovementSharpness = 15f;
+ public float OrientationSharpness = 10f;
+ public OrientationMethod OrientationMethod = OrientationMethod.TowardsCamera;
+
+ [Header("Air Movement")]
+ public float MaxAirMoveSpeed = 15f;
+ public float AirAccelerationSpeed = 15f;
+ public float Drag = 0.1f;
+
+ [Header("Jumping")]
+ public bool AllowJumpingWhenSliding = false;
+ public float JumpUpSpeed = 10f;
+ public float JumpScalableForwardSpeed = 10f;
+ public float JumpPreGroundingGraceTime = 0f;
+ public float JumpPostGroundingGraceTime = 0f;
+
+ [Header("Misc")]
+ public List IgnoredColliders = new List();
+ public BonusOrientationMethod BonusOrientationMethod = BonusOrientationMethod.None;
+ public float BonusOrientationSharpness = 10f;
+ public Vector3 Gravity = new Vector3(0, -30f, 0);
+ public Transform MeshRoot;
+ public Transform CameraFollowPoint;
+ public float CrouchedCapsuleHeight = 1f;
+
+ public CharacterState CurrentCharacterState { get; private set; }
+
+ private Collider[] _probedColliders = new Collider[8];
+ private RaycastHit[] _probedHits = new RaycastHit[8];
+ private Vector3 _moveInputVector;
+ private Vector3 _lookInputVector;
+ private bool _jumpRequested = false;
+ private bool _jumpConsumed = false;
+ private bool _jumpedThisFrame = false;
+ private float _timeSinceJumpRequested = Mathf.Infinity;
+ private float _timeSinceLastAbleToJump = 0f;
+ private Vector3 _internalVelocityAdd = Vector3.zero;
+ private bool _shouldBeCrouching = false;
+ private bool _isCrouching = false;
+
+ private Vector3 lastInnerNormal = Vector3.zero;
+ private Vector3 lastOuterNormal = Vector3.zero;
+
+ private void Awake()
+ {
+ // Handle initial state
+ TransitionToState(CharacterState.Default);
+
+ // Assign the characterController to the motor
+ Motor.CharacterController = this;
+ }
+
+ ///
+ /// Handles movement state transitions and enter/exit callbacks
+ ///
+ public void TransitionToState(CharacterState newState)
+ {
+ CharacterState tmpInitialState = CurrentCharacterState;
+ OnStateExit(tmpInitialState, newState);
+ CurrentCharacterState = newState;
+ OnStateEnter(newState, tmpInitialState);
+ }
+
+ ///
+ /// Event when entering a state
+ ///
+ public void OnStateEnter(CharacterState state, CharacterState fromState)
+ {
+ switch (state)
+ {
+ case CharacterState.Default:
+ {
+ break;
+ }
+ }
+ }
+
+ ///
+ /// Event when exiting a state
+ ///
+ public void OnStateExit(CharacterState state, CharacterState toState)
+ {
+ switch (state)
+ {
+ case CharacterState.Default:
+ {
+ break;
+ }
+ }
+ }
+
+ ///
+ /// This is called every frame by ExamplePlayer in order to tell the character what its inputs are
+ ///
+ public void SetInputs(ref PlayerCharacterInputs inputs)
+ {
+ // Clamp input
+ Vector3 moveInputVector = Vector3.ClampMagnitude(new Vector3(inputs.MoveAxisRight, 0f, inputs.MoveAxisForward), 1f);
+
+ // Calculate camera direction and rotation on the character plane
+ Vector3 cameraPlanarDirection = Vector3.ProjectOnPlane(inputs.CameraRotation * Vector3.forward, Motor.CharacterUp).normalized;
+ if (cameraPlanarDirection.sqrMagnitude == 0f)
+ {
+ cameraPlanarDirection = Vector3.ProjectOnPlane(inputs.CameraRotation * Vector3.up, Motor.CharacterUp).normalized;
+ }
+ Quaternion cameraPlanarRotation = Quaternion.LookRotation(cameraPlanarDirection, Motor.CharacterUp);
+
+ switch (CurrentCharacterState)
+ {
+ case CharacterState.Default:
+ {
+ // Move and look inputs
+ _moveInputVector = cameraPlanarRotation * moveInputVector;
+
+ switch (OrientationMethod)
+ {
+ case OrientationMethod.TowardsCamera:
+ _lookInputVector = cameraPlanarDirection;
+ break;
+ case OrientationMethod.TowardsMovement:
+ _lookInputVector = _moveInputVector.normalized;
+ break;
+ }
+
+ // Jumping input
+ if (inputs.JumpDown)
+ {
+ _timeSinceJumpRequested = 0f;
+ _jumpRequested = true;
+ }
+
+ // Crouching input
+ if (inputs.CrouchDown)
+ {
+ _shouldBeCrouching = true;
+
+ if (!_isCrouching)
+ {
+ _isCrouching = true;
+ Motor.SetCapsuleDimensions(0.5f, CrouchedCapsuleHeight, CrouchedCapsuleHeight * 0.5f);
+ MeshRoot.localScale = new Vector3(1f, 0.5f, 1f);
+ }
+ }
+ else if (inputs.CrouchUp)
+ {
+ _shouldBeCrouching = false;
+ }
+
+ break;
+ }
+ }
+ }
+
+ ///
+ /// This is called every frame by the AI script in order to tell the character what its inputs are
+ ///
+ public void SetInputs(ref AICharacterInputs inputs)
+ {
+ _moveInputVector = inputs.MoveVector;
+ _lookInputVector = inputs.LookVector;
+ }
+
+ private Quaternion _tmpTransientRot;
+
+ ///
+ /// (Called by KinematicCharacterMotor during its update cycle)
+ /// This is called before the character begins its movement update
+ ///
+ public void BeforeCharacterUpdate(float deltaTime)
+ {
+ }
+
+ ///
+ /// (Called by KinematicCharacterMotor during its update cycle)
+ /// This is where you tell your character what its rotation should be right now.
+ /// This is the ONLY place where you should set the character's rotation
+ ///
+ public void UpdateRotation(ref Quaternion currentRotation, float deltaTime)
+ {
+ switch (CurrentCharacterState)
+ {
+ case CharacterState.Default:
+ {
+ if (_lookInputVector.sqrMagnitude > 0f && OrientationSharpness > 0f)
+ {
+ // Smoothly interpolate from current to target look direction
+ Vector3 smoothedLookInputDirection = Vector3.Slerp(Motor.CharacterForward, _lookInputVector, 1 - Mathf.Exp(-OrientationSharpness * deltaTime)).normalized;
+
+ // Set the current rotation (which will be used by the KinematicCharacterMotor)
+ currentRotation = Quaternion.LookRotation(smoothedLookInputDirection, Motor.CharacterUp);
+ }
+
+ Vector3 currentUp = (currentRotation * Vector3.up);
+ if (BonusOrientationMethod == BonusOrientationMethod.TowardsGravity)
+ {
+ // Rotate from current up to invert gravity
+ Vector3 smoothedGravityDir = Vector3.Slerp(currentUp, -Gravity.normalized, 1 - Mathf.Exp(-BonusOrientationSharpness * deltaTime));
+ currentRotation = Quaternion.FromToRotation(currentUp, smoothedGravityDir) * currentRotation;
+ }
+ else if (BonusOrientationMethod == BonusOrientationMethod.TowardsGroundSlopeAndGravity)
+ {
+ if (Motor.GroundingStatus.IsStableOnGround)
+ {
+ Vector3 initialCharacterBottomHemiCenter = Motor.TransientPosition + (currentUp * Motor.Capsule.radius);
+
+ Vector3 smoothedGroundNormal = Vector3.Slerp(Motor.CharacterUp, Motor.GroundingStatus.GroundNormal, 1 - Mathf.Exp(-BonusOrientationSharpness * deltaTime));
+ currentRotation = Quaternion.FromToRotation(currentUp, smoothedGroundNormal) * currentRotation;
+
+ // Move the position to create a rotation around the bottom hemi center instead of around the pivot
+ Motor.SetTransientPosition(initialCharacterBottomHemiCenter + (currentRotation * Vector3.down * Motor.Capsule.radius));
+ }
+ else
+ {
+ Vector3 smoothedGravityDir = Vector3.Slerp(currentUp, -Gravity.normalized, 1 - Mathf.Exp(-BonusOrientationSharpness * deltaTime));
+ currentRotation = Quaternion.FromToRotation(currentUp, smoothedGravityDir) * currentRotation;
+ }
+ }
+ else
+ {
+ Vector3 smoothedGravityDir = Vector3.Slerp(currentUp, Vector3.up, 1 - Mathf.Exp(-BonusOrientationSharpness * deltaTime));
+ currentRotation = Quaternion.FromToRotation(currentUp, smoothedGravityDir) * currentRotation;
+ }
+ break;
+ }
+ }
+ }
+
+ ///
+ /// (Called by KinematicCharacterMotor during its update cycle)
+ /// This is where you tell your character what its velocity should be right now.
+ /// This is the ONLY place where you can set the character's velocity
+ ///
+ public void UpdateVelocity(ref Vector3 currentVelocity, float deltaTime)
+ {
+ switch (CurrentCharacterState)
+ {
+ case CharacterState.Default:
+ {
+ // Ground movement
+ if (Motor.GroundingStatus.IsStableOnGround)
+ {
+ float currentVelocityMagnitude = currentVelocity.magnitude;
+
+ Vector3 effectiveGroundNormal = Motor.GroundingStatus.GroundNormal;
+
+ // Reorient velocity on slope
+ currentVelocity = Motor.GetDirectionTangentToSurface(currentVelocity, effectiveGroundNormal) * currentVelocityMagnitude;
+
+ // Calculate target velocity
+ Vector3 inputRight = Vector3.Cross(_moveInputVector, Motor.CharacterUp);
+ Vector3 reorientedInput = Vector3.Cross(effectiveGroundNormal, inputRight).normalized * _moveInputVector.magnitude;
+ Vector3 targetMovementVelocity = reorientedInput * MaxStableMoveSpeed;
+
+ // Smooth movement Velocity
+ currentVelocity = Vector3.Lerp(currentVelocity, targetMovementVelocity, 1f - Mathf.Exp(-StableMovementSharpness * deltaTime));
+ }
+ // Air movement
+ else
+ {
+ // Add move input
+ if (_moveInputVector.sqrMagnitude > 0f)
+ {
+ Vector3 addedVelocity = _moveInputVector * AirAccelerationSpeed * deltaTime;
+
+ Vector3 currentVelocityOnInputsPlane = Vector3.ProjectOnPlane(currentVelocity, Motor.CharacterUp);
+
+ // Limit air velocity from inputs
+ if (currentVelocityOnInputsPlane.magnitude < MaxAirMoveSpeed)
+ {
+ // clamp addedVel to make total vel not exceed max vel on inputs plane
+ Vector3 newTotal = Vector3.ClampMagnitude(currentVelocityOnInputsPlane + addedVelocity, MaxAirMoveSpeed);
+ addedVelocity = newTotal - currentVelocityOnInputsPlane;
+ }
+ else
+ {
+ // Make sure added vel doesn't go in the direction of the already-exceeding velocity
+ if (Vector3.Dot(currentVelocityOnInputsPlane, addedVelocity) > 0f)
+ {
+ addedVelocity = Vector3.ProjectOnPlane(addedVelocity, currentVelocityOnInputsPlane.normalized);
+ }
+ }
+
+ // Prevent air-climbing sloped walls
+ if (Motor.GroundingStatus.FoundAnyGround)
+ {
+ if (Vector3.Dot(currentVelocity + addedVelocity, addedVelocity) > 0f)
+ {
+ Vector3 perpenticularObstructionNormal = Vector3.Cross(Vector3.Cross(Motor.CharacterUp, Motor.GroundingStatus.GroundNormal), Motor.CharacterUp).normalized;
+ addedVelocity = Vector3.ProjectOnPlane(addedVelocity, perpenticularObstructionNormal);
+ }
+ }
+
+ // Apply added velocity
+ currentVelocity += addedVelocity;
+ }
+
+ // Gravity
+ currentVelocity += Gravity * deltaTime;
+
+ // Drag
+ currentVelocity *= (1f / (1f + (Drag * deltaTime)));
+ }
+
+ // Handle jumping
+ _jumpedThisFrame = false;
+ _timeSinceJumpRequested += deltaTime;
+ if (_jumpRequested)
+ {
+ // See if we actually are allowed to jump
+ if (!_jumpConsumed && ((AllowJumpingWhenSliding ? Motor.GroundingStatus.FoundAnyGround : Motor.GroundingStatus.IsStableOnGround) || _timeSinceLastAbleToJump <= JumpPostGroundingGraceTime))
+ {
+ // Calculate jump direction before ungrounding
+ Vector3 jumpDirection = Motor.CharacterUp;
+ if (Motor.GroundingStatus.FoundAnyGround && !Motor.GroundingStatus.IsStableOnGround)
+ {
+ jumpDirection = Motor.GroundingStatus.GroundNormal;
+ }
+
+ // Makes the character skip ground probing/snapping on its next update.
+ // If this line weren't here, the character would remain snapped to the ground when trying to jump. Try commenting this line out and see.
+ Motor.ForceUnground();
+
+ // Add to the return velocity and reset jump state
+ currentVelocity += (jumpDirection * JumpUpSpeed) - Vector3.Project(currentVelocity, Motor.CharacterUp);
+ currentVelocity += (_moveInputVector * JumpScalableForwardSpeed);
+ _jumpRequested = false;
+ _jumpConsumed = true;
+ _jumpedThisFrame = true;
+ }
+ }
+
+ // Take into account additive velocity
+ if (_internalVelocityAdd.sqrMagnitude > 0f)
+ {
+ currentVelocity += _internalVelocityAdd;
+ _internalVelocityAdd = Vector3.zero;
+ }
+ break;
+ }
+ }
+ }
+
+ ///
+ /// (Called by KinematicCharacterMotor during its update cycle)
+ /// This is called after the character has finished its movement update
+ ///
+ public void AfterCharacterUpdate(float deltaTime)
+ {
+ switch (CurrentCharacterState)
+ {
+ case CharacterState.Default:
+ {
+ // Handle jump-related values
+ {
+ // Handle jumping pre-ground grace period
+ if (_jumpRequested && _timeSinceJumpRequested > JumpPreGroundingGraceTime)
+ {
+ _jumpRequested = false;
+ }
+
+ if (AllowJumpingWhenSliding ? Motor.GroundingStatus.FoundAnyGround : Motor.GroundingStatus.IsStableOnGround)
+ {
+ // If we're on a ground surface, reset jumping values
+ if (!_jumpedThisFrame)
+ {
+ _jumpConsumed = false;
+ }
+ _timeSinceLastAbleToJump = 0f;
+ }
+ else
+ {
+ // Keep track of time since we were last able to jump (for grace period)
+ _timeSinceLastAbleToJump += deltaTime;
+ }
+ }
+
+ // Handle uncrouching
+ if (_isCrouching && !_shouldBeCrouching)
+ {
+ // Do an overlap test with the character's standing height to see if there are any obstructions
+ Motor.SetCapsuleDimensions(0.5f, 2f, 1f);
+ if (Motor.CharacterOverlap(
+ Motor.TransientPosition,
+ Motor.TransientRotation,
+ _probedColliders,
+ Motor.CollidableLayers,
+ QueryTriggerInteraction.Ignore) > 0)
+ {
+ // If obstructions, just stick to crouching dimensions
+ Motor.SetCapsuleDimensions(0.5f, CrouchedCapsuleHeight, CrouchedCapsuleHeight * 0.5f);
+ }
+ else
+ {
+ // If no obstructions, uncrouch
+ MeshRoot.localScale = new Vector3(1f, 1f, 1f);
+ _isCrouching = false;
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ public void PostGroundingUpdate(float deltaTime)
+ {
+ // Handle landing and leaving ground
+ if (Motor.GroundingStatus.IsStableOnGround && !Motor.LastGroundingStatus.IsStableOnGround)
+ {
+ OnLanded();
+ }
+ else if (!Motor.GroundingStatus.IsStableOnGround && Motor.LastGroundingStatus.IsStableOnGround)
+ {
+ OnLeaveStableGround();
+ }
+ }
+
+ public bool IsColliderValidForCollisions(Collider coll)
+ {
+ if (IgnoredColliders.Count == 0)
+ {
+ return true;
+ }
+
+ if (IgnoredColliders.Contains(coll))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ public void OnGroundHit(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, ref HitStabilityReport hitStabilityReport)
+ {
+ }
+
+ public void OnMovementHit(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, ref HitStabilityReport hitStabilityReport)
+ {
+ }
+
+ public void AddVelocity(Vector3 velocity)
+ {
+ switch (CurrentCharacterState)
+ {
+ case CharacterState.Default:
+ {
+ _internalVelocityAdd += velocity;
+ break;
+ }
+ }
+ }
+
+ public void ProcessHitStabilityReport(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, Vector3 atCharacterPosition, Quaternion atCharacterRotation, ref HitStabilityReport hitStabilityReport)
+ {
+ }
+
+ protected void OnLanded()
+ {
+ }
+
+ protected void OnLeaveStableGround()
+ {
+ }
+
+ public void OnDiscreteCollisionDetected(Collider hitCollider)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/Plugins/KinematicCharacterController/ExampleCharacter/Scripts/ExampleCharacterController.cs.meta b/Assets/Plugins/KinematicCharacterController/ExampleCharacter/Scripts/ExampleCharacterController.cs.meta
new file mode 100644
index 0000000..196499c
--- /dev/null
+++ b/Assets/Plugins/KinematicCharacterController/ExampleCharacter/Scripts/ExampleCharacterController.cs.meta
@@ -0,0 +1,13 @@
+fileFormatVersion: 2
+guid: 76020eee813ed7844bcea94c5d5ce76a
+timeCreated: 1503446428
+licenseType: Store
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/KinematicCharacterController/ExampleCharacter/Scripts/ExamplePlayer.cs b/Assets/Plugins/KinematicCharacterController/ExampleCharacter/Scripts/ExamplePlayer.cs
new file mode 100644
index 0000000..02c7f3b
--- /dev/null
+++ b/Assets/Plugins/KinematicCharacterController/ExampleCharacter/Scripts/ExamplePlayer.cs
@@ -0,0 +1,99 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using KinematicCharacterController;
+using KinematicCharacterController.Examples;
+
+namespace KinematicCharacterController.Examples
+{
+ public class ExamplePlayer : MonoBehaviour
+ {
+ public ExampleCharacterController Character;
+ public ExampleCharacterCamera CharacterCamera;
+
+ private const string MouseXInput = "Mouse X";
+ private const string MouseYInput = "Mouse Y";
+ private const string MouseScrollInput = "Mouse ScrollWheel";
+ private const string HorizontalInput = "Horizontal";
+ private const string VerticalInput = "Vertical";
+
+ private void Start()
+ {
+ Cursor.lockState = CursorLockMode.Locked;
+
+ // Tell camera to follow transform
+ CharacterCamera.SetFollowTransform(Character.CameraFollowPoint);
+
+ // Ignore the character's collider(s) for camera obstruction checks
+ CharacterCamera.IgnoredColliders.Clear();
+ CharacterCamera.IgnoredColliders.AddRange(Character.GetComponentsInChildren());
+ }
+
+ private void Update()
+ {
+ if (Input.GetMouseButtonDown(0))
+ {
+ Cursor.lockState = CursorLockMode.Locked;
+ }
+
+ HandleCharacterInput();
+ }
+
+ private void LateUpdate()
+ {
+ // Handle rotating the camera along with physics movers
+ if (CharacterCamera.RotateWithPhysicsMover && Character.Motor.AttachedRigidbody != null)
+ {
+ CharacterCamera.PlanarDirection = Character.Motor.AttachedRigidbody.GetComponent().RotationDeltaFromInterpolation * CharacterCamera.PlanarDirection;
+ CharacterCamera.PlanarDirection = Vector3.ProjectOnPlane(CharacterCamera.PlanarDirection, Character.Motor.CharacterUp).normalized;
+ }
+
+ HandleCameraInput();
+ }
+
+ private void HandleCameraInput()
+ {
+ // Create the look input vector for the camera
+ float mouseLookAxisUp = Input.GetAxisRaw(MouseYInput);
+ float mouseLookAxisRight = Input.GetAxisRaw(MouseXInput);
+ Vector3 lookInputVector = new Vector3(mouseLookAxisRight, mouseLookAxisUp, 0f);
+
+ // Prevent moving the camera while the cursor isn't locked
+ if (Cursor.lockState != CursorLockMode.Locked)
+ {
+ lookInputVector = Vector3.zero;
+ }
+
+ // Input for zooming the camera (disabled in WebGL because it can cause problems)
+ float scrollInput = -Input.GetAxis(MouseScrollInput);
+#if UNITY_WEBGL
+ scrollInput = 0f;
+#endif
+
+ // Apply inputs to the camera
+ CharacterCamera.UpdateWithInput(Time.deltaTime, scrollInput, lookInputVector);
+
+ // Handle toggling zoom level
+ if (Input.GetMouseButtonDown(1))
+ {
+ CharacterCamera.TargetDistance = (CharacterCamera.TargetDistance == 0f) ? CharacterCamera.DefaultDistance : 0f;
+ }
+ }
+
+ private void HandleCharacterInput()
+ {
+ PlayerCharacterInputs characterInputs = new PlayerCharacterInputs();
+
+ // Build the CharacterInputs struct
+ characterInputs.MoveAxisForward = Input.GetAxisRaw(VerticalInput);
+ characterInputs.MoveAxisRight = Input.GetAxisRaw(HorizontalInput);
+ characterInputs.CameraRotation = CharacterCamera.Transform.rotation;
+ characterInputs.JumpDown = Input.GetKeyDown(KeyCode.Space);
+ characterInputs.CrouchDown = Input.GetKeyDown(KeyCode.C);
+ characterInputs.CrouchUp = Input.GetKeyUp(KeyCode.C);
+
+ // Apply inputs to character
+ Character.SetInputs(ref characterInputs);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/Plugins/KinematicCharacterController/ExampleCharacter/Scripts/ExamplePlayer.cs.meta b/Assets/Plugins/KinematicCharacterController/ExampleCharacter/Scripts/ExamplePlayer.cs.meta
new file mode 100644
index 0000000..e09302d
--- /dev/null
+++ b/Assets/Plugins/KinematicCharacterController/ExampleCharacter/Scripts/ExamplePlayer.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 461cd396e3fc7cc4eb9c92bde05c1b9a
+timeCreated: 1485657184
+licenseType: Store
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant: