namespace Fusion.Addons.KCC { using UnityEngine; /// /// Main data structure used for movement calculations. Stores data which require rollback, network synchronization + various metadata. /// public sealed partial class KCCData { // PUBLIC MEMBERS /// /// Frame number, equals to Time.frameCount. /// public int Frame; /// /// Tick number, equals to Simulation.Tick or calculated fixed update frame count. /// public int Tick; /// /// Relative position of the time between two fixed times. Valid range is <0.0f, 1.0f> /// public float Alpha; /// /// Current time, equals to NetworkRunner.SimulationTime or NetworkRunner.SimulationRenderTime or variable if CCD is active. /// public float Time; /// /// Partial delta time, variable if CCD is active. Valid range is <0.0f, UpdateDeltaTime>, but can be altered. /// public float DeltaTime; /// /// Delta time of full update/tick (CCD independent). /// /// FixedUpdate => FixedUpdate /// FixedUpdate => Render /// Render => Render /// /// public float UpdateDeltaTime; /// /// Controls execution of the KCC. /// public bool IsActive = true; /// /// Base position, initialized with TargetPosition at the start of each KCC step. /// public Vector3 BasePosition; /// /// Desired position before depenetration and post-processing. /// public Vector3 DesiredPosition; /// /// Calculated or explicitly set position which is propagated to Transform. /// public Vector3 TargetPosition; /// /// Explicitly set look pitch rotation, this should propagate to camera rotation. /// public float LookPitch { get { return _lookPitch; } set { if (_lookPitch != value) { _lookPitch = value; _lookRotationCalculated = false; _lookDirectionCalculated = false; } } } /// /// Explicitly set look yaw rotation, this should propagate to camera and transform rotation. /// public float LookYaw { get { return _lookYaw; } set { if (_lookYaw != value) { _lookYaw = value; _lookRotationCalculated = false; _lookDirectionCalculated = false; _transformRotationCalculated = false; _transformDirectionCalculated = false; } } } /// /// Combination of LookPitch and LookYaw. /// public Quaternion LookRotation { get { if (_lookRotationCalculated == false) { _lookRotation = Quaternion.Euler(_lookPitch, _lookYaw, 0.0f); _lookRotationCalculated = true; } return _lookRotation; } } /// /// Calculated and cached look direction based on LookRotation. /// public Vector3 LookDirection { get { if (_lookDirectionCalculated == false) { _lookDirection = LookRotation * Vector3.forward; _lookDirectionCalculated = true; } return _lookDirection; } } /// /// Calculated and cached transform rotation based on Yaw look rotation. /// public Quaternion TransformRotation { get { if (_transformRotationCalculated == false) { _transformRotation = Quaternion.Euler(0.0f, _lookYaw, 0.0f); _transformRotationCalculated = true; } return _transformRotation; } } /// /// Calculated and cached transform direction based on TransformRotation. /// public Vector3 TransformDirection { get { if (_transformDirectionCalculated == false) { _transformDirection = TransformRotation * Vector3.forward; _transformDirectionCalculated = true; } return _transformDirection; } } /// /// Non-interpolated world space input direction - based on Keyboard / Joystick / NavMesh / ... /// public Vector3 InputDirection; /// /// One-time world space jump impulse based on input. /// public Vector3 JumpImpulse; /// /// Gravitational acceleration. /// public Vector3 Gravity; /// /// Maximum angle between KCC up direction and ground normal (depenetration vector) in degrees. Valid range is <0, 90>. Default is 75. /// public float MaxGroundAngle; /// /// Maximum angle between KCC up direction and wall surface (perpendicular to depenetration vector) in degrees. Valid range is <0, MaxGroundAngle> Default is 5. /// public float MaxWallAngle; /// /// Maximum angle between KCC up direction and hang surface (perpendicular to depenetration vector) in degrees. Valid range is <MaxWallAngle, 90> Default is 30. /// public float MaxHangAngle; /// /// Single Move/CCD step is split into multiple smaller sub-steps which results in higher overall depenetration quality. /// public int MaxPenetrationSteps; /// /// Velocity from external sources, one-time effect - reseted on the end of Move() call to prevent subsequent applications in render, ignoring Mass, example usage - jump pad. /// public Vector3 ExternalVelocity; /// /// Acceleration from external sources, continuous effect - value remains same for subsequent applications in render, ignoring Mass, example usage - escalator. /// public Vector3 ExternalAcceleration; /// /// Impulse from external sources, one-time effect - reseted on the end of Move() call to prevent subsequent applications in render, affected by Mass, example usage - explosion. /// public Vector3 ExternalImpulse; /// /// Force from external sources, continuous effect - value remains same for subsequent applications in render, affected by Mass, example usage - attractor. /// public Vector3 ExternalForce; /// /// Absolute position delta which is consumed by single move. It can also be set from ProcessPhysicsQuery and still consumed by currently executed move (useful for depenetration corrections). /// public Vector3 ExternalDelta; /// /// Speed used to calculate KinematicVelocity. /// public float KinematicSpeed; /// /// Calculated kinematic tangent, based on KinematicDirection and affected by other factors like ground normal, used to calculate KinematicVelocity. /// public Vector3 KinematicTangent; /// /// Desired kinematic direction, based on InputDirection and other factors, used to calculate KinematicVelocity. /// public Vector3 KinematicDirection; /// /// Velocity calculated from InputDirection, KinematicDirection, KinematicTangent and KinematicSpeed. /// public Vector3 KinematicVelocity; /// /// Velocity calculated from Gravity, ExternalVelocity, ExternalAcceleration, ExternalImpulse, ExternalForce and JumpImpulse. /// public Vector3 DynamicVelocity; /// /// Final calculated velocity used for position change, combined KinematicVelocity and DynamicVelocity. /// public Vector3 DesiredVelocity => KinematicVelocity + DynamicVelocity; /// /// Speed calculated from real position change. /// public float RealSpeed; /// /// Velocity calculated from real position change. /// public Vector3 RealVelocity; /// /// Counter in how many frames the JumpImpulse was applied in a row. /// Fixed update have 1 at max. In render update value higher than 1 indicates that jump happened in earlier render frame and should be processed next fixed update as well. /// public int JumpFrames; /// /// Flag that indicates KCC has jumped in current tick/frame. /// public bool HasJumped => JumpFrames == 1; /// /// Flag that indicates KCC has teleported in current tick. /// public bool HasTeleported; /// /// Flag that indicates KCC is touching a collider with normal angle lower than MaxGroundAngle. /// public bool IsGrounded; /// /// Same as IsGrounded previous tick or physics query. /// public bool WasGrounded; /// /// Indicates the KCC temporarily or permanently lost grounded state. /// public bool IsOnEdge => IsGrounded == false && WasGrounded == true; /// /// Indicates the KCC is stepping up. /// public bool IsSteppingUp; /// /// Same as IsSteppingUp previous tick or physics query. /// public bool WasSteppingUp; /// /// Indicates the KCC temporarily lost grounded state and is snapping to ground. /// public bool IsSnappingToGround; /// /// Same as IsSnappingToGround previous tick or physics query. /// public bool WasSnappingToGround; /// /// Combined normal of all touching colliders. Normals less distant from up direction have bigger impacton final normal. /// public Vector3 GroundNormal; /// /// Tangent to GroundNormal, can be calculated from DesiredVelocity or TargetRotation if GroundNormal and up direction is same. /// public Vector3 GroundTangent; /// /// Position of the KCC collider surface touching the ground collider. /// public Vector3 GroundPosition; /// /// Distance from ground. /// public float GroundDistance; /// /// Difference between ground normal and up direction in degrees. /// public float GroundAngle; /// /// Collection of networked collisions. Represents colliders the KCC interacts with. /// Only objects with NetworkObject component are stored for compatibility with local prediction. /// public readonly KCCCollisions Collisions = new KCCCollisions(); /// /// Collection of manually registered modifiers (for example processors) the KCC interacts with. /// Only objects with NetworkObject component are stored for compatibility with local prediction. /// public readonly KCCModifiers Modifiers = new KCCModifiers(); /// /// Collection of ignored colliders. /// Only objects with NetworkObject component are stored for compatibility with local prediction. /// public readonly KCCIgnores Ignores = new KCCIgnores(); /// /// Collection of colliders/triggers the KCC overlaps (radius + extent). /// This collection is not synchronized over the network! NetworkObject component is not needed, only local history is supported. /// public readonly KCCHits Hits = new KCCHits(); // PRIVATE MEMBERS private float _lookPitch; private float _lookYaw; private Quaternion _lookRotation; private bool _lookRotationCalculated; private Vector3 _lookDirection; private bool _lookDirectionCalculated; private Quaternion _transformRotation; private bool _transformRotationCalculated; private Vector3 _transformDirection; private bool _transformDirectionCalculated; // PUBLIC METHODS /// /// Returns the look rotation vector with selected axes. /// public Vector2 GetLookRotation(bool pitch = true, bool yaw = true) { Vector2 lookRotation = default; if (pitch == true) { lookRotation.x = _lookPitch; } if (yaw == true) { lookRotation.y = _lookYaw; } return lookRotation; } /// /// Add pitch and yaw look rotation. Resulting values are clamped to <-90, 90> (pitch) and <-180, 180> (yaw). /// Changes are not propagated to Transform component. /// public void AddLookRotation(float pitchDelta, float yawDelta) { if (pitchDelta != 0.0f) { LookPitch = Mathf.Clamp(LookPitch + pitchDelta, -90.0f, 90.0f); } if (yawDelta != 0.0f) { float lookYaw = LookYaw + yawDelta; while (lookYaw > 180.0f) { lookYaw -= 360.0f; } while (lookYaw < -180.0f) { lookYaw += 360.0f; } LookYaw = lookYaw; } } /// /// Add pitch and yaw look rotation. Resulting values are clamped to <minPitch, maxPitch> (pitch) and <-180, 180> (yaw). /// Changes are not propagated to Transform component. /// public void AddLookRotation(float pitchDelta, float yawDelta, float minPitch, float maxPitch) { if (pitchDelta != 0.0f) { if (minPitch < -90.0f) { minPitch = -90.0f; } if (maxPitch > 90.0f) { maxPitch = 90.0f; } if (maxPitch < minPitch) { maxPitch = minPitch; } LookPitch = Mathf.Clamp(LookPitch + pitchDelta, minPitch, maxPitch); } if (yawDelta != 0.0f) { float lookYaw = LookYaw + yawDelta; while (lookYaw > 180.0f) { lookYaw -= 360.0f; } while (lookYaw < -180.0f) { lookYaw += 360.0f; } LookYaw = lookYaw; } } /// /// Add pitch (x) and yaw (y) look rotation. Resulting values are clamped to <-90, 90> (pitch) and <-180, 180> (yaw). /// Changes are not propagated to Transform component. /// public void AddLookRotation(Vector2 lookRotationDelta) { AddLookRotation(lookRotationDelta.x, lookRotationDelta.y); } /// /// Add pitch (x) and yaw (y) look rotation. Resulting values are clamped to <minPitch, maxPitch> (pitch) and <-180, 180> (yaw). /// Changes are not propagated to Transform component. /// public void AddLookRotation(Vector2 lookRotationDelta, float minPitch, float maxPitch) { AddLookRotation(lookRotationDelta.x, lookRotationDelta.y, minPitch, maxPitch); } /// /// Set pitch and yaw look rotation. Values are clamped to <-90, 90> (pitch) and <-180, 180> (yaw). /// Changes are not propagated to Transform component. /// public void SetLookRotation(float pitch, float yaw) { KCCUtility.ClampLookRotationAngles(ref pitch, ref yaw); LookPitch = pitch; LookYaw = yaw; } /// /// Set pitch and yaw look rotation. Values are clamped to <minPitch, maxPitch> (pitch) and <-180, 180> (yaw). /// Changes are not propagated to Transform component. /// public void SetLookRotation(float pitch, float yaw, float minPitch, float maxPitch) { KCCUtility.ClampLookRotationAngles(ref pitch, ref yaw, minPitch, maxPitch); LookPitch = pitch; LookYaw = yaw; } /// /// Set pitch (x) and yaw (y) look rotation. Values are clamped to <-90, 90> (pitch) and <-180, 180> (yaw). /// Changes are not propagated to Transform component. /// public void SetLookRotation(Vector2 lookRotation) { SetLookRotation(lookRotation.x, lookRotation.y); } /// /// Set pitch (x) and yaw (y) look rotation. Values are clamped to <minPitch, maxPitch> (pitch) and <-180, 180> (yaw). /// Changes are not propagated to Transform component. /// public void SetLookRotation(Vector2 lookRotation, float minPitch, float maxPitch) { SetLookRotation(lookRotation.x, lookRotation.y, minPitch, maxPitch); } /// /// Set pitch and yaw look rotation. Roll is ignored (not supported). Values are clamped to <-90, 90> (pitch) and <-180, 180> (yaw). /// Changes are not propagated to Transform component. /// public void SetLookRotation(Quaternion lookRotation, bool preservePitch = false, bool preserveYaw = false) { KCCUtility.GetClampedLookRotationAngles(lookRotation, out float pitch, out float yaw); if (preservePitch == false) { LookPitch = pitch; } if (preserveYaw == false) { LookYaw = yaw; } } /// /// Clear all properties which should not propagate to next tick/frame if IsActive == false. /// public void ClearTransientProperties() { JumpFrames = default; JumpImpulse = default; ExternalVelocity = default; ExternalAcceleration = default; ExternalImpulse = default; ExternalForce = default; ClearUserTransientProperties(); } public void Clear() { ClearUserData(); Collisions.Clear(); Modifiers.Clear(); Ignores.Clear(); Hits.Clear(); } public void CopyFromOther(KCCData other) { Frame = other.Frame; Tick = other.Tick; Alpha = other.Alpha; Time = other.Time; DeltaTime = other.DeltaTime; UpdateDeltaTime = other.UpdateDeltaTime; IsActive = other.IsActive; BasePosition = other.BasePosition; DesiredPosition = other.DesiredPosition; TargetPosition = other.TargetPosition; LookPitch = other.LookPitch; LookYaw = other.LookYaw; InputDirection = other.InputDirection; JumpImpulse = other.JumpImpulse; Gravity = other.Gravity; MaxGroundAngle = other.MaxGroundAngle; MaxWallAngle = other.MaxWallAngle; MaxHangAngle = other.MaxHangAngle; MaxPenetrationSteps = other.MaxPenetrationSteps; ExternalVelocity = other.ExternalVelocity; ExternalAcceleration = other.ExternalAcceleration; ExternalImpulse = other.ExternalImpulse; ExternalForce = other.ExternalForce; ExternalDelta = other.ExternalDelta; KinematicSpeed = other.KinematicSpeed; KinematicTangent = other.KinematicTangent; KinematicDirection = other.KinematicDirection; KinematicVelocity = other.KinematicVelocity; DynamicVelocity = other.DynamicVelocity; RealSpeed = other.RealSpeed; RealVelocity = other.RealVelocity; JumpFrames = other.JumpFrames; HasTeleported = other.HasTeleported; IsGrounded = other.IsGrounded; WasGrounded = other.WasGrounded; IsSteppingUp = other.IsSteppingUp; WasSteppingUp = other.WasSteppingUp; IsSnappingToGround = other.IsSnappingToGround; WasSnappingToGround = other.WasSnappingToGround; GroundNormal = other.GroundNormal; GroundTangent = other.GroundTangent; GroundPosition = other.GroundPosition; GroundDistance = other.GroundDistance; GroundAngle = other.GroundAngle; Collisions.CopyFromOther(other.Collisions); Modifiers.CopyFromOther(other.Modifiers); Ignores.CopyFromOther(other.Ignores); Hits.CopyFromOther(other.Hits); CopyUserDataFromOther(other); } // PARTIAL METHODS partial void ClearUserTransientProperties(); partial void ClearUserData(); partial void CopyUserDataFromOther(KCCData other); } }