< Summary

Class:DCLCharacterController
Assembly:CharacterController
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Controllers/CharacterController/DCLCharacterController.cs
Covered lines:198
Uncovered lines:57
Coverable lines:255
Total lines:583
Line coverage:77.6% (198 of 255)
Covered branches:0
Total branches:0

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
DCLCharacterController()0%110100%
Awake()0%44096.3%
SubscribeToInput()0%110100%
OnDestroy()0%110100%
OnWorldReposition(...)0%330100%
SetPosition(...)0%990100%
Teleport(...)0%110100%
Teleport(...)0%3.033085.71%
SetPosition(...)0%2100%
SetEnabled(...)0%110100%
Moved(...)0%220100%
LateUpdate()0%42.8629074.55%
SaveLateUpdateGroundTransforms()0%6200%
Jump()0%12300%
ResetGround()0%2.022083.33%
CheckGround()0%48.3815047.06%
CastGroundCheckingRays()0%220100%
CastGroundCheckingRays(...)0%220100%
CastGroundCheckingRay(...)0%2100%
CastGroundCheckingRays(...)0%660100%
CastGroundCheckingRay(...)0%220100%
ReportMovement()0%330100%
PauseGravity()0%110100%
ResumeGravity()0%2100%
OnRenderingStateChanged(...)0%110100%
IsLastCollisionGround()0%110100%

File(s)

/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Controllers/CharacterController/DCLCharacterController.cs

#LineLine coverage
 1using DCL;
 2using DCL.Configuration;
 3using DCL.Helpers;
 4using UnityEngine;
 5using Cinemachine;
 6
 7public class DCLCharacterController : MonoBehaviour
 8{
 177319    public static DCLCharacterController i { get; private set; }
 10
 11    private const float CONTROLLER_DRIFT_OFFSET = 0.15f;
 12
 13    [Header("Movement")]
 42314    public float minimumYPosition = 1f;
 15
 42316    public float groundCheckExtraDistance = 0.1f;
 42317    public float gravity = -55f;
 42318    public float jumpForce = 12f;
 42319    public float movementSpeed = 8f;
 42320    public float runningSpeedMultiplier = 2f;
 21
 22    public DCLCharacterPosition characterPosition;
 23
 24    [Header("Collisions")]
 25    public LayerMask groundLayers;
 26
 27    [Header("Additional Camera Layers")]
 28    public LayerMask cameraLayers;
 29
 30    [System.NonSerialized]
 31    public bool initialPositionAlreadySet = false;
 32
 33    [System.NonSerialized]
 42334    public bool characterAlwaysEnabled = true;
 35
 36    [System.NonSerialized]
 37    public CharacterController characterController;
 38
 39    FreeMovementController freeMovementController;
 40
 41    new Collider collider;
 42
 43    float lastUngroundedTime = 0f;
 44    float lastJumpButtonPressedTime = 0f;
 45    float lastMovementReportTime;
 46    float originalGravity;
 47    Vector3 lastLocalGroundPosition;
 48
 49    Vector3 lastCharacterRotation;
 50    Vector3 lastGlobalCharacterRotation;
 51
 42352    Vector3 velocity = Vector3.zero;
 53
 3054    public bool isWalking { get; private set; } = false;
 3055    public bool isMovingByUserInput { get; private set; } = false;
 1559756    public bool isJumping { get; private set; } = false;
 7363957    public bool isGrounded { get; private set; }
 8740458    public bool isOnMovingPlatform { get; private set; }
 59
 60    internal Transform groundTransform;
 61
 62    Vector3 lastPosition;
 63    Vector3 groundLastPosition;
 64    Quaternion groundLastRotation;
 65    bool jumpButtonPressed = false;
 66
 67    [Header("InputActions")]
 68    public InputAction_Hold jumpAction;
 69
 70    public InputAction_Hold sprintAction;
 71
 72    public Vector3 moveVelocity;
 73
 74    private InputAction_Hold.Started jumpStartedDelegate;
 75    private InputAction_Hold.Finished jumpFinishedDelegate;
 76    private InputAction_Hold.Started walkStartedDelegate;
 77    private InputAction_Hold.Finished walkFinishedDelegate;
 78
 2882179    private Vector3NullableVariable characterForward => CommonScriptableObjects.characterForward;
 80
 81    public static System.Action<DCLCharacterPosition> OnCharacterMoved;
 82    public static System.Action<DCLCharacterPosition> OnPositionSet;
 83    public event System.Action<float> OnUpdateFinish;
 84
 85    public GameObject avatarGameObject;
 86    public GameObject firstPersonCameraGameObject;
 87
 88    [SerializeField]
 89    private InputAction_Measurable characterYAxis;
 90
 91    [SerializeField]
 92    private InputAction_Measurable characterXAxis;
 93
 2764694    private Vector3Variable cameraForward => CommonScriptableObjects.cameraForward;
 3095    private Vector3Variable cameraRight => CommonScriptableObjects.cameraRight;
 96
 42397    private readonly DataStore_Player dataStorePlayer = DataStore.i.player;
 98
 99    [System.NonSerialized]
 100    public float movingPlatformSpeed;
 101    private CollisionFlags lastCharacterControllerCollision;
 102
 103    public event System.Action OnJump;
 104    public event System.Action OnHitGround;
 105    public event System.Action<float> OnMoved;
 106
 107    void Awake()
 108    {
 405109        if (i != null)
 110        {
 23111            Destroy(gameObject);
 23112            return;
 113        }
 114
 382115        i = this;
 382116        originalGravity = gravity;
 117
 382118        SubscribeToInput();
 382119        CommonScriptableObjects.playerUnityPosition.Set(Vector3.zero);
 382120        dataStorePlayer.playerWorldPosition.Set(Vector3.zero);
 382121        CommonScriptableObjects.playerCoords.Set(Vector2Int.zero);
 382122        dataStorePlayer.playerGridPosition.Set(Vector2Int.zero);
 382123        CommonScriptableObjects.playerUnityEulerAngles.Set(Vector3.zero);
 124
 382125        characterPosition = new DCLCharacterPosition();
 382126        characterController = GetComponent<CharacterController>();
 382127        freeMovementController = GetComponent<FreeMovementController>();
 382128        collider = GetComponent<Collider>();
 129
 382130        CommonScriptableObjects.worldOffset.OnChange += OnWorldReposition;
 131
 382132        lastPosition = transform.position;
 382133        transform.parent = null;
 134
 382135        CommonScriptableObjects.rendererState.OnChange += OnRenderingStateChanged;
 382136        OnRenderingStateChanged(CommonScriptableObjects.rendererState.Get(), false);
 137
 382138        if (avatarGameObject == null || firstPersonCameraGameObject == null)
 139        {
 0140            throw new System.Exception("Both the avatar and first person camera game objects must be set.");
 141        }
 142
 382143        var worldData = DataStore.i.Get<DataStore_World>();
 382144        worldData.avatarTransform.Set(avatarGameObject.transform);
 382145        worldData.fpsTransform.Set(firstPersonCameraGameObject.transform);
 146
 382147        dataStorePlayer.lastTeleportPosition.OnChange += Teleport;
 382148    }
 149
 150    private void SubscribeToInput()
 151    {
 382152        jumpStartedDelegate = (action) =>
 153        {
 0154            lastJumpButtonPressedTime = Time.time;
 0155            jumpButtonPressed = true;
 0156        };
 382157        jumpFinishedDelegate = (action) => jumpButtonPressed = false;
 382158        jumpAction.OnStarted += jumpStartedDelegate;
 382159        jumpAction.OnFinished += jumpFinishedDelegate;
 160
 382161        walkStartedDelegate = (action) => isWalking = true;
 382162        walkFinishedDelegate = (action) => isWalking = false;
 382163        sprintAction.OnStarted += walkStartedDelegate;
 382164        sprintAction.OnFinished += walkFinishedDelegate;
 382165    }
 166
 167    void OnDestroy()
 168    {
 405169        CommonScriptableObjects.worldOffset.OnChange -= OnWorldReposition;
 405170        jumpAction.OnStarted -= jumpStartedDelegate;
 405171        jumpAction.OnFinished -= jumpFinishedDelegate;
 405172        sprintAction.OnStarted -= walkStartedDelegate;
 405173        sprintAction.OnFinished -= walkFinishedDelegate;
 405174        CommonScriptableObjects.rendererState.OnChange -= OnRenderingStateChanged;
 405175        dataStorePlayer.lastTeleportPosition.OnChange -= Teleport;
 405176        i = null;
 405177    }
 178
 179    void OnWorldReposition(Vector3 current, Vector3 previous)
 180    {
 2181        Vector3 oldPos = this.transform.position;
 2182        this.transform.position = characterPosition.unityPosition; //CommonScriptableObjects.playerUnityPosition;
 183
 2184        if (CinemachineCore.Instance.BrainCount > 0)
 185        {
 2186            CinemachineCore.Instance.GetActiveBrain(0).ActiveVirtualCamera?.OnTargetObjectWarped(transform, transform.po
 187        }
 2188    }
 189
 190    public void SetPosition(Vector3 newPosition)
 191    {
 192        // failsafe in case something teleports the player below ground collisions
 15007193        if (newPosition.y < minimumYPosition)
 194        {
 74195            newPosition.y = minimumYPosition + 2f;
 196        }
 197
 15007198        lastPosition = characterPosition.worldPosition;
 15007199        characterPosition.worldPosition = newPosition;
 15007200        transform.position = characterPosition.unityPosition;
 15007201        Environment.i.platform.physicsSyncController?.MarkDirty();
 202
 15007203        CommonScriptableObjects.playerUnityPosition.Set(characterPosition.unityPosition);
 15007204        dataStorePlayer.playerWorldPosition.Set(characterPosition.worldPosition);
 15007205        Vector2Int playerPosition = Utils.WorldToGridPosition(characterPosition.worldPosition);
 15007206        CommonScriptableObjects.playerCoords.Set(playerPosition);
 15007207        dataStorePlayer.playerGridPosition.Set(playerPosition);
 15007208        dataStorePlayer.playerUnityPosition.Set(characterPosition.unityPosition);
 209
 15007210        if (Moved(lastPosition))
 211        {
 13779212            if (Moved(lastPosition, useThreshold: true))
 13779213                ReportMovement();
 214
 13779215            OnCharacterMoved?.Invoke(characterPosition);
 216
 13779217            float distance = Vector3.Distance(characterPosition.worldPosition, lastPosition) - movingPlatformSpeed;
 218
 13779219            if (distance > 0f && isGrounded)
 6220                OnMoved?.Invoke(distance);
 221        }
 222
 15007223        lastPosition = transform.position;
 15007224    }
 225
 226    public void Teleport(string teleportPayload)
 227    {
 40228        var payload = Utils.FromJsonWithNulls<Vector3>(teleportPayload);
 40229        dataStorePlayer.lastTeleportPosition.Set(payload, notifyEvent: true);
 40230    }
 231
 232    private void Teleport(Vector3 newPosition, Vector3 prevPosition)
 233    {
 41234        ResetGround();
 235
 41236        SetPosition(newPosition);
 237
 41238        if (OnPositionSet != null)
 239        {
 0240            OnPositionSet.Invoke(characterPosition);
 241        }
 242
 41243        if (!initialPositionAlreadySet)
 244        {
 37245            initialPositionAlreadySet = true;
 246        }
 41247    }
 248
 249    [System.Obsolete("SetPosition is deprecated, please use Teleport instead.", true)]
 0250    public void SetPosition(string positionVector) { Teleport(positionVector); }
 251
 1526252    public void SetEnabled(bool enabled) { this.enabled = enabled; }
 253
 254    bool Moved(Vector3 previousPosition, bool useThreshold = false)
 255    {
 28786256        if (useThreshold)
 13779257            return Vector3.Distance(characterPosition.worldPosition, previousPosition) > 0.001f;
 258        else
 15007259            return characterPosition.worldPosition != previousPosition;
 260    }
 261
 262    internal void LateUpdate()
 263    {
 14965264        if(!dataStorePlayer.canPlayerMove.Get())
 0265            return;
 266
 14965267        if (transform.position.y < minimumYPosition)
 268        {
 0269            SetPosition(characterPosition.worldPosition);
 0270            return;
 271        }
 272
 14965273        if (freeMovementController.IsActive())
 274        {
 0275            velocity = freeMovementController.CalculateMovement();
 276        }
 277        else
 278        {
 14965279            velocity.x = 0f;
 14965280            velocity.z = 0f;
 14965281            velocity.y += gravity * Time.deltaTime;
 282
 14965283            bool previouslyGrounded = isGrounded;
 284
 14965285            if (!isJumping || velocity.y <= 0f)
 14965286                CheckGround();
 287
 14965288            if (isGrounded)
 289            {
 627290                isJumping = false;
 627291                velocity.y = gravity * Time.deltaTime; // to avoid accumulating gravity in velocity.y while grounded
 292            }
 14338293            else if (previouslyGrounded && !isJumping)
 294            {
 5295                lastUngroundedTime = Time.time;
 296            }
 297
 14965298            if (characterForward.HasValue())
 299            {
 300                // Horizontal movement
 30301                var speed = movementSpeed * (isWalking ? runningSpeedMultiplier : 1f);
 302
 30303                transform.forward = characterForward.Get().Value;
 304
 30305                var xzPlaneForward = Vector3.Scale(cameraForward.Get(), new Vector3(1, 0, 1));
 30306                var xzPlaneRight = Vector3.Scale(cameraRight.Get(), new Vector3(1, 0, 1));
 307
 30308                Vector3 forwardTarget = Vector3.zero;
 309
 30310                if (characterYAxis.GetValue() > CONTROLLER_DRIFT_OFFSET)
 0311                    forwardTarget += xzPlaneForward;
 30312                if (characterYAxis.GetValue() < -CONTROLLER_DRIFT_OFFSET)
 0313                    forwardTarget -= xzPlaneForward;
 314
 30315                if (characterXAxis.GetValue() > CONTROLLER_DRIFT_OFFSET)
 0316                    forwardTarget += xzPlaneRight;
 30317                if (characterXAxis.GetValue() < -CONTROLLER_DRIFT_OFFSET)
 0318                    forwardTarget -= xzPlaneRight;
 319
 30320                if (forwardTarget.Equals(Vector3.zero))
 30321                    isMovingByUserInput = false;
 322                else
 0323                    isMovingByUserInput = true;
 324
 325
 30326                forwardTarget.Normalize();
 30327                velocity += forwardTarget * speed;
 30328                CommonScriptableObjects.playerUnityEulerAngles.Set(transform.eulerAngles);
 329            }
 330
 14965331            bool jumpButtonPressedWithGraceTime = jumpButtonPressed && (Time.time - lastJumpButtonPressedTime < 0.15f);
 332
 14965333            if (jumpButtonPressedWithGraceTime) // almost-grounded jump button press allowed time
 334            {
 0335                bool justLeftGround = (Time.time - lastUngroundedTime) < 0.1f;
 336
 0337                if (isGrounded || justLeftGround) // just-left-ground jump allowed time
 338                {
 0339                    Jump();
 340                }
 341            }
 342
 343            //NOTE(Mordi): Detecting when the character hits the ground (for landing-SFX)
 14965344            if (isGrounded && !previouslyGrounded && (Time.time - lastUngroundedTime) > 0.4f)
 345            {
 21346                OnHitGround?.Invoke();
 347            }
 348        }
 349
 14965350        if (characterController.enabled)
 351        {
 352            //NOTE(Brian): Transform has to be in sync before the Move call, otherwise this call
 353            //             will reset the character controller to its previous position.
 14965354            Environment.i.platform.physicsSyncController?.Sync();
 14965355            lastCharacterControllerCollision = characterController.Move(velocity * Time.deltaTime);
 356        }
 357
 14965358        SetPosition(PositionUtils.UnityToWorldPosition(transform.position));
 359
 14965360        if ((DCLTime.realtimeSinceStartup - lastMovementReportTime) > PlayerSettings.POSITION_REPORTING_DELAY)
 361        {
 35362            ReportMovement();
 363        }
 364
 14965365        if (isOnMovingPlatform)
 366        {
 0367            SaveLateUpdateGroundTransforms();
 368        }
 14965369        OnUpdateFinish?.Invoke(Time.deltaTime);
 0370    }
 371
 372    private void SaveLateUpdateGroundTransforms()
 373    {
 0374        lastLocalGroundPosition = groundTransform.InverseTransformPoint(transform.position);
 375
 0376        if (CommonScriptableObjects.characterForward.HasValue())
 377        {
 0378            lastCharacterRotation = groundTransform.InverseTransformDirection(CommonScriptableObjects.characterForward.G
 0379            lastGlobalCharacterRotation = CommonScriptableObjects.characterForward.Get().Value;
 380        }
 0381    }
 382
 383    void Jump()
 384    {
 0385        if (isJumping)
 0386            return;
 387
 0388        isJumping = true;
 0389        isGrounded = false;
 390
 0391        ResetGround();
 392
 0393        velocity.y = jumpForce;
 394        //cameraTargetProbe.damping.y = dampingOnAir;
 395
 0396        OnJump?.Invoke();
 0397    }
 398
 399    public void ResetGround()
 400    {
 28737401        if (isOnMovingPlatform)
 0402            CommonScriptableObjects.playerIsOnMovingPlatform.Set(false);
 403
 28737404        isOnMovingPlatform = false;
 28737405        groundTransform = null;
 28737406        movingPlatformSpeed = 0;
 28737407    }
 408
 409    void CheckGround()
 410    {
 14965411        if (groundTransform == null)
 14357412            ResetGround();
 413
 14965414        if (isOnMovingPlatform)
 415        {
 0416            Physics.SyncTransforms();
 417            //NOTE(Brian): This should move the character with the moving platform
 0418            Vector3 newGroundWorldPos = groundTransform.TransformPoint(lastLocalGroundPosition);
 0419            movingPlatformSpeed = Vector3.Distance(newGroundWorldPos, transform.position);
 0420            transform.position = newGroundWorldPos;
 421
 0422            Vector3 newCharacterForward = groundTransform.TransformDirection(lastCharacterRotation);
 0423            Vector3 lastFrameDifference = Vector3.zero;
 0424            if (CommonScriptableObjects.characterForward.HasValue())
 425            {
 0426                lastFrameDifference = CommonScriptableObjects.characterForward.Get().Value - lastGlobalCharacterRotation
 427            }
 428
 429            //NOTE(Kinerius) CameraStateTPS rotates the character between frames so we add the difference.
 430            //               if we dont do this, the character wont rotate when moving, only when the platform rotates
 0431            var newForward = newCharacterForward + lastFrameDifference;
 432
 0433            if (newForward is { x: 0, y: 0, z: 0 })
 0434                newForward = Vector3.forward;
 435
 0436            CommonScriptableObjects.characterForward.Set(newForward);
 437        }
 438
 14965439        Transform transformHit = CastGroundCheckingRays();
 440
 14965441        if (transformHit != null)
 442        {
 626443            if (groundTransform == transformHit)
 444            {
 604445                bool groundHasMoved = (transformHit.position != groundLastPosition || transformHit.rotation != groundLas
 446
 604447                if (!characterPosition.RepositionedWorldLastFrame()
 448                    && groundHasMoved)
 449                {
 0450                    isOnMovingPlatform = true;
 0451                    CommonScriptableObjects.playerIsOnMovingPlatform.Set(true);
 0452                    Physics.SyncTransforms();
 0453                    SaveLateUpdateGroundTransforms();
 454
 0455                    Quaternion deltaRotation = groundTransform.rotation * Quaternion.Inverse(groundLastRotation);
 0456                    CommonScriptableObjects.movingPlatformRotationDelta.Set(deltaRotation);
 457                }
 458            }
 459            else
 460            {
 22461                groundTransform = transformHit;
 22462                CommonScriptableObjects.movingPlatformRotationDelta.Set(Quaternion.identity);
 463            }
 464        }
 465        else
 466        {
 14339467            ResetGround();
 468        }
 469
 14965470        if (groundTransform != null)
 471        {
 626472            groundLastPosition = groundTransform.position;
 626473            groundLastRotation = groundTransform.rotation;
 474        }
 475
 14965476        isGrounded = IsLastCollisionGround() || groundTransform != null && groundTransform.gameObject.activeInHierarchy;
 14965477    }
 478
 479    public Transform CastGroundCheckingRays()
 480    {
 481        RaycastHit hitInfo;
 482
 14965483        var result = CastGroundCheckingRays(transform, collider, groundCheckExtraDistance, 0.9f, groundLayers, out hitIn
 484
 14965485        if ( result )
 486        {
 626487            return hitInfo.transform;
 488        }
 489
 14339490        return null;
 491    }
 492
 493    public bool CastGroundCheckingRays(float extraDistance, float scale, out RaycastHit hitInfo)
 494    {
 14926495        if (CastGroundCheckingRays(transform, collider, extraDistance, scale, groundLayers | cameraLayers , out hitInfo)
 724496            return true;
 497
 14202498        return IsLastCollisionGround();
 499    }
 500
 501    public bool CastGroundCheckingRay(float extraDistance, out RaycastHit hitInfo)
 502    {
 0503        Bounds bounds = collider.bounds;
 0504        float rayMagnitude = (bounds.extents.y + extraDistance);
 0505        bool test = CastGroundCheckingRay(transform.position, out hitInfo, rayMagnitude, groundLayers);
 0506        return IsLastCollisionGround() || test;
 507    }
 508
 509    // We secuentially cast rays in 4 directions (only if the previous one didn't hit anything)
 510    public static bool CastGroundCheckingRays(Transform transform, Collider collider, float extraDistance, float scale, 
 511    {
 29891512        Bounds bounds = collider.bounds;
 513
 29891514        float rayMagnitude = (bounds.extents.y + extraDistance);
 29891515        float originScale = scale * bounds.extents.x;
 516
 29891517        if (!CastGroundCheckingRay(transform.position, out hitInfo, rayMagnitude, groundLayers) // center
 518            && !CastGroundCheckingRay( transform.position + transform.forward * originScale, out hitInfo, rayMagnitude, 
 519            && !CastGroundCheckingRay( transform.position + transform.right * originScale, out hitInfo, rayMagnitude, gr
 520            && !CastGroundCheckingRay( transform.position + -transform.forward * originScale, out hitInfo, rayMagnitude,
 521            && !CastGroundCheckingRay( transform.position + -transform.right * originScale, out hitInfo, rayMagnitude, g
 522        {
 28541523            return false;
 524        }
 525
 526        // At this point there is a guaranteed hit, so this is not null
 1350527        return true;
 528    }
 529
 530    public static bool CastGroundCheckingRay(Vector3 origin, out RaycastHit hitInfo, float rayMagnitude, int groundLayer
 531    {
 144055532        var ray = new Ray();
 144055533        ray.origin = origin;
 144055534        ray.direction = Vector3.down * rayMagnitude;
 535
 144055536        var result = Physics.Raycast(ray, out hitInfo, rayMagnitude, groundLayers);
 537
 538#if UNITY_EDITOR
 144055539        if ( result )
 1350540            Debug.DrawLine(ray.origin, hitInfo.point, Color.green);
 541        else
 142705542            Debug.DrawRay(ray.origin, ray.direction, Color.red);
 543#endif
 544
 142705545        return result;
 546    }
 547
 548    void ReportMovement()
 549    {
 13814550        float height = 0.875f;
 551
 13814552        var reportPosition = characterPosition.worldPosition + (Vector3.up * height);
 13814553        var compositeRotation = Quaternion.LookRotation(characterForward.HasValue() ? characterForward.Get().Value : cam
 13814554        var playerHeight = height + (characterController.height / 2);
 13814555        var cameraRotation = Quaternion.LookRotation(cameraForward.Get());
 556
 557        //NOTE(Brian): We have to wait for a Teleport before sending the ReportPosition, because if not ReportPosition e
 558        //             When the spawn point is being selected / scenes being prepared to be sent and the Kernel gets cra
 559
 560        //             The race conditions that can arise from not having this flag can result in:
 561        //                  - Scenes not being sent for loading, making ActivateRenderer never being sent, only in WSS m
 562        //                  - Random teleports to 0,0 or other positions that shouldn't happen.
 13814563        if (initialPositionAlreadySet)
 41564            DCL.Interface.WebInterface.ReportPosition(reportPosition, compositeRotation, playerHeight, cameraRotation);
 565
 13814566        lastMovementReportTime = DCLTime.realtimeSinceStartup;
 13814567    }
 568
 569    public void PauseGravity()
 570    {
 40571        gravity = 0f;
 40572        velocity.y = 0f;
 40573    }
 574
 0575    public void ResumeGravity() { gravity = originalGravity; }
 576
 1526577    void OnRenderingStateChanged(bool isEnable, bool prevState) { SetEnabled(isEnable); }
 578
 579    bool IsLastCollisionGround()
 580    {
 29167581        return (lastCharacterControllerCollision & CollisionFlags.Below) != 0;
 582    }
 583}