< Summary

Class:DCLCharacterController
Assembly:MainScripts
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Controllers/CharacterController/DCLCharacterController.cs
Covered lines:195
Uncovered lines:47
Coverable lines:242
Total lines:549
Line coverage:80.5% (195 of 242)
Covered branches:0
Total branches:0

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
DCLCharacterController()0%110100%
Awake()0%44095.83%
SubscribeToInput()0%110100%
OnDestroy()0%110100%
OnWorldReposition(...)0%330100%
SetPosition(...)0%880100%
Teleport(...)0%3.013090%
SetPosition(...)0%2100%
SetEnabled(...)0%110100%
Moved(...)0%220100%
LateUpdate()0%30.5727083.02%
SaveLateUpdateGroundTransforms()0%6200%
Jump()0%3.023087.5%
ResetGround()0%2.022083.33%
CheckGround()0%21.3910051.52%
CastGroundCheckingRays()0%220100%
CastGroundCheckingRays(...)0%110100%
CastGroundCheckingRay(...)0%2100%
CastGroundCheckingRays(...)0%660100%
CastGroundCheckingRay(...)0%220100%
ReportMovement()0%330100%
PauseGravity()0%110100%
ResumeGravity()0%2100%
OnRenderingStateChanged(...)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{
 09    public static DCLCharacterController i { get; private set; }
 10
 11    [Header("Movement")]
 37112    public float minimumYPosition = 1f;
 13
 37114    public float groundCheckExtraDistance = 0.25f;
 37115    public float gravity = -55f;
 37116    public float jumpForce = 12f;
 37117    public float movementSpeed = 8f;
 37118    public float runningSpeedMultiplier = 2f;
 19
 20    public DCLCharacterPosition characterPosition;
 21
 22    [Header("Collisions")]
 23    public LayerMask groundLayers;
 24
 25    [System.NonSerialized]
 26    public bool initialPositionAlreadySet = false;
 27
 28    [System.NonSerialized]
 37129    public bool characterAlwaysEnabled = true;
 30
 31    [System.NonSerialized]
 32    public CharacterController characterController;
 33
 34    FreeMovementController freeMovementController;
 35
 36    new Collider collider;
 37
 37138    float deltaTime = 0.032f;
 37139    float deltaTimeCap = 0.032f; // 32 milliseconds = 30FPS, 16 millisecodns = 60FPS
 40    float lastUngroundedTime = 0f;
 41    float lastJumpButtonPressedTime = 0f;
 42    float lastMovementReportTime;
 43    float originalGravity;
 44    Vector3 lastLocalGroundPosition;
 45
 46    Vector3 lastCharacterRotation;
 47    Vector3 lastGlobalCharacterRotation;
 48
 37149    Vector3 velocity = Vector3.zero;
 50
 051    public bool isWalking { get; private set; } = false;
 052    public bool isJumping { get; private set; } = false;
 053    public bool isGrounded { get; private set; }
 054    public bool isOnMovingPlatform { get; private set; }
 55
 56    internal Transform groundTransform;
 57
 58    Vector3 lastPosition;
 59    Vector3 groundLastPosition;
 60    Quaternion groundLastRotation;
 61    bool jumpButtonPressed = false;
 62
 63    [Header("InputActions")]
 64    public InputAction_Hold jumpAction;
 65
 66    public InputAction_Hold sprintAction;
 67
 68    public Vector3 moveVelocity;
 69
 70    private InputAction_Hold.Started jumpStartedDelegate;
 71    private InputAction_Hold.Finished jumpFinishedDelegate;
 72    private InputAction_Hold.Started walkStartedDelegate;
 73    private InputAction_Hold.Finished walkFinishedDelegate;
 74
 939275    private Vector3NullableVariable characterForward => CommonScriptableObjects.characterForward;
 76
 77    public static System.Action<DCLCharacterPosition> OnCharacterMoved;
 78    public static System.Action<DCLCharacterPosition> OnPositionSet;
 79    public event System.Action<float> OnUpdateFinish;
 80
 81    // Will allow the game objects to be set, and create the DecentralandEntity manually during the Awake
 082    public DCL.Models.IDCLEntity avatarReference { get; private set; }
 083    public DCL.Models.IDCLEntity firstPersonCameraReference { get; private set; }
 84
 85    [SerializeField]
 86    private GameObject avatarGameObject;
 87
 88    [SerializeField]
 89    private GameObject firstPersonCameraGameObject;
 90
 91    [SerializeField]
 92    private InputAction_Measurable characterYAxis;
 93
 94    [SerializeField]
 95    private InputAction_Measurable characterXAxis;
 96
 12697    private Vector3Variable cameraForward => CommonScriptableObjects.cameraForward;
 12698    private Vector3Variable cameraRight => CommonScriptableObjects.cameraRight;
 99
 100    [System.NonSerialized]
 101    public float movingPlatformSpeed;
 102    public event System.Action OnJump;
 103    public event System.Action OnHitGround;
 104    public event System.Action<float> OnMoved;
 105
 106    void Awake()
 107    {
 145108        if (i != null)
 109        {
 22110            Destroy(gameObject);
 22111            return;
 112        }
 113
 123114        i = this;
 123115        originalGravity = gravity;
 116
 123117        SubscribeToInput();
 123118        CommonScriptableObjects.playerUnityPosition.Set(Vector3.zero);
 123119        CommonScriptableObjects.playerWorldPosition.Set(Vector3.zero);
 123120        CommonScriptableObjects.playerCoords.Set(Vector2Int.zero);
 123121        CommonScriptableObjects.playerUnityEulerAngles.Set(Vector3.zero);
 122
 123123        characterPosition = new DCLCharacterPosition();
 123124        characterController = GetComponent<CharacterController>();
 123125        freeMovementController = GetComponent<FreeMovementController>();
 123126        collider = GetComponent<Collider>();
 127
 123128        CommonScriptableObjects.worldOffset.OnChange += OnWorldReposition;
 129
 123130        lastPosition = transform.position;
 123131        transform.parent = null;
 132
 123133        CommonScriptableObjects.rendererState.OnChange += OnRenderingStateChanged;
 123134        OnRenderingStateChanged(CommonScriptableObjects.rendererState.Get(), false);
 135
 123136        if (avatarGameObject == null || firstPersonCameraGameObject == null)
 137        {
 0138            throw new System.Exception("Both the avatar and first person camera game objects must be set.");
 139        }
 140
 123141        avatarReference = new DCL.Models.DecentralandEntity { gameObject = avatarGameObject };
 123142        firstPersonCameraReference = new DCL.Models.DecentralandEntity { gameObject = firstPersonCameraGameObject };
 143
 123144    }
 145
 146    private void SubscribeToInput()
 147    {
 123148        jumpStartedDelegate = (action) =>
 149        {
 1150            lastJumpButtonPressedTime = Time.time;
 1151            jumpButtonPressed = true;
 1152        };
 124153        jumpFinishedDelegate = (action) => jumpButtonPressed = false;
 123154        jumpAction.OnStarted += jumpStartedDelegate;
 123155        jumpAction.OnFinished += jumpFinishedDelegate;
 156
 123157        walkStartedDelegate = (action) => isWalking = true;
 123158        walkFinishedDelegate = (action) => isWalking = false;
 123159        sprintAction.OnStarted += walkStartedDelegate;
 123160        sprintAction.OnFinished += walkFinishedDelegate;
 123161    }
 162
 163    void OnDestroy()
 164    {
 144165        CommonScriptableObjects.worldOffset.OnChange -= OnWorldReposition;
 144166        jumpAction.OnStarted -= jumpStartedDelegate;
 144167        jumpAction.OnFinished -= jumpFinishedDelegate;
 144168        sprintAction.OnStarted -= walkStartedDelegate;
 144169        sprintAction.OnFinished -= walkFinishedDelegate;
 144170        CommonScriptableObjects.rendererState.OnChange -= OnRenderingStateChanged;
 144171    }
 172
 173    void OnWorldReposition(Vector3 current, Vector3 previous)
 174    {
 5175        Vector3 oldPos = this.transform.position;
 5176        this.transform.position = characterPosition.unityPosition; //CommonScriptableObjects.playerUnityPosition;
 177
 5178        if (CinemachineCore.Instance.BrainCount > 0)
 179        {
 5180            CinemachineCore.Instance.GetActiveBrain(0).ActiveVirtualCamera?.OnTargetObjectWarped(transform, transform.po
 181        }
 5182    }
 183
 184    public void SetPosition(Vector3 newPosition)
 185    {
 186        // failsafe in case something teleports the player below ground collisions
 18188187        if (newPosition.y < minimumYPosition)
 188        {
 39189            newPosition.y = minimumYPosition + 2f;
 190        }
 191
 18188192        lastPosition = characterPosition.worldPosition;
 18188193        characterPosition.worldPosition = newPosition;
 18188194        transform.position = characterPosition.unityPosition;
 18188195        Environment.i.platform.physicsSyncController.MarkDirty();
 196
 18188197        CommonScriptableObjects.playerUnityPosition.Set(characterPosition.unityPosition);
 18188198        CommonScriptableObjects.playerWorldPosition.Set(characterPosition.worldPosition);
 18188199        CommonScriptableObjects.playerCoords.Set(Utils.WorldToGridPosition(characterPosition.worldPosition));
 200
 18188201        if (Moved(lastPosition))
 202        {
 3284203            if (Moved(lastPosition, useThreshold: true))
 3273204                ReportMovement();
 205
 3284206            OnCharacterMoved?.Invoke(characterPosition);
 207
 3284208            float distance = Vector3.Distance(characterPosition.worldPosition, lastPosition) - movingPlatformSpeed;
 209
 3284210            if (distance > 0f && isGrounded)
 2419211                OnMoved?.Invoke(distance);
 212        }
 213
 18188214        lastPosition = transform.position;
 18188215    }
 216
 217    public void Teleport(string teleportPayload)
 218    {
 52219        ResetGround();
 220
 52221        var payload = Utils.FromJsonWithNulls<Vector3>(teleportPayload);
 222
 52223        var newPosition = new Vector3(payload.x, payload.y, payload.z);
 52224        SetPosition(newPosition);
 225
 52226        if (OnPositionSet != null)
 227        {
 0228            OnPositionSet.Invoke(characterPosition);
 229        }
 230
 52231        DataStore.i.player.lastTeleportPosition.Set(newPosition, true);
 232
 52233        if (!initialPositionAlreadySet)
 234        {
 22235            initialPositionAlreadySet = true;
 236        }
 52237    }
 238
 239    [System.Obsolete("SetPosition is deprecated, please use Teleport instead.", true)]
 0240    public void SetPosition(string positionVector) { Teleport(positionVector); }
 241
 752242    public void SetEnabled(bool enabled) { this.enabled = enabled; }
 243
 244    bool Moved(Vector3 previousPosition, bool useThreshold = false)
 245    {
 21472246        if (useThreshold)
 3284247            return Vector3.Distance(characterPosition.worldPosition, previousPosition) > 0.001f;
 248        else
 18188249            return characterPosition.worldPosition != previousPosition;
 250    }
 251
 252    internal void LateUpdate()
 253    {
 18102254        deltaTime = Mathf.Min(deltaTimeCap, Time.deltaTime);
 255
 18102256        if (transform.position.y < minimumYPosition)
 257        {
 0258            SetPosition(characterPosition.worldPosition);
 0259            return;
 260        }
 261
 18102262        if (freeMovementController.IsActive())
 263        {
 0264            velocity = freeMovementController.CalculateMovement();
 0265        }
 266        else
 267        {
 18102268            velocity.x = 0f;
 18102269            velocity.z = 0f;
 18102270            velocity.y += gravity * deltaTime;
 271
 18102272            bool previouslyGrounded = isGrounded;
 273
 18102274            if (!isJumping || velocity.y <= 0f)
 18066275                CheckGround();
 276
 18102277            if (isGrounded)
 278            {
 16747279                isJumping = false;
 16747280                velocity.y = gravity * deltaTime; // to avoid accumulating gravity in velocity.y while grounded
 16747281            }
 1355282            else if (previouslyGrounded && !isJumping)
 283            {
 24284                lastUngroundedTime = Time.time;
 285            }
 286
 18102287            if (Utils.isCursorLocked && characterForward.HasValue())
 288            {
 289                // Horizontal movement
 126290                var speed = movementSpeed * (isWalking ? runningSpeedMultiplier : 1f);
 291
 126292                transform.forward = characterForward.Get().Value;
 293
 126294                var xzPlaneForward = Vector3.Scale(cameraForward.Get(), new Vector3(1, 0, 1));
 126295                var xzPlaneRight = Vector3.Scale(cameraRight.Get(), new Vector3(1, 0, 1));
 296
 126297                Vector3 forwardTarget = Vector3.zero;
 298
 126299                if (characterYAxis.GetValue() > 0)
 0300                    forwardTarget += xzPlaneForward;
 126301                if (characterYAxis.GetValue() < 0)
 0302                    forwardTarget -= xzPlaneForward;
 303
 126304                if (characterXAxis.GetValue() > 0)
 0305                    forwardTarget += xzPlaneRight;
 126306                if (characterXAxis.GetValue() < 0)
 0307                    forwardTarget -= xzPlaneRight;
 308
 309
 126310                forwardTarget.Normalize();
 126311                velocity += forwardTarget * speed;
 126312                CommonScriptableObjects.playerUnityEulerAngles.Set(transform.eulerAngles);
 313            }
 314
 18102315            bool jumpButtonPressedWithGraceTime = jumpButtonPressed && (Time.time - lastJumpButtonPressedTime < 0.15f);
 316
 18102317            if (jumpButtonPressedWithGraceTime) // almost-grounded jump button press allowed time
 318            {
 25319                bool justLeftGround = (Time.time - lastUngroundedTime) < 0.1f;
 320
 25321                if (isGrounded || justLeftGround) // just-left-ground jump allowed time
 322                {
 1323                    Jump();
 324                }
 325            }
 326
 327            //NOTE(Mordi): Detecting when the character hits the ground (for landing-SFX)
 18102328            if (isGrounded && !previouslyGrounded && (Time.time - lastUngroundedTime) > 0.4f)
 329            {
 117330                OnHitGround?.Invoke();
 331            }
 332        }
 333
 18102334        if (characterController.enabled)
 335        {
 336            //NOTE(Brian): Transform has to be in sync before the Move call, otherwise this call
 337            //             will reset the character controller to its previous position.
 17946338            Environment.i.platform.physicsSyncController.Sync();
 17946339            characterController.Move(velocity * deltaTime);
 340        }
 341
 18102342        SetPosition(PositionUtils.UnityToWorldPosition(transform.position));
 343
 18102344        if ((DCLTime.realtimeSinceStartup - lastMovementReportTime) > PlayerSettings.POSITION_REPORTING_DELAY)
 345        {
 1297346            ReportMovement();
 347        }
 348
 18102349        if (isOnMovingPlatform)
 350        {
 0351            SaveLateUpdateGroundTransforms();
 352        }
 353
 18102354        OnUpdateFinish?.Invoke(deltaTime);
 18102355    }
 356    private void SaveLateUpdateGroundTransforms()
 357    {
 0358        lastLocalGroundPosition = groundTransform.InverseTransformPoint(transform.position);
 359
 0360        if (CommonScriptableObjects.characterForward.HasValue())
 361        {
 0362            lastCharacterRotation = groundTransform.InverseTransformDirection(CommonScriptableObjects.characterForward.G
 0363            lastGlobalCharacterRotation = CommonScriptableObjects.characterForward.Get().Value;
 364        }
 0365    }
 366
 367    void Jump()
 368    {
 1369        if (isJumping)
 0370            return;
 371
 1372        isJumping = true;
 1373        isGrounded = false;
 374
 1375        ResetGround();
 376
 1377        velocity.y = jumpForce;
 378        //cameraTargetProbe.damping.y = dampingOnAir;
 379
 1380        OnJump?.Invoke();
 1381    }
 382
 383    public void ResetGround()
 384    {
 2829385        if (isOnMovingPlatform)
 0386            CommonScriptableObjects.playerIsOnMovingPlatform.Set(false);
 387
 2829388        isOnMovingPlatform = false;
 2829389        groundTransform = null;
 2829390        movingPlatformSpeed = 0;
 2829391    }
 392
 393    void CheckGround()
 394    {
 18066395        if (groundTransform == null)
 1457396            ResetGround();
 397
 18066398        if (isOnMovingPlatform)
 399        {
 0400            Physics.SyncTransforms();
 401            //NOTE(Brian): This should move the character with the moving platform
 0402            Vector3 newGroundWorldPos = groundTransform.TransformPoint(lastLocalGroundPosition);
 0403            movingPlatformSpeed = Vector3.Distance(newGroundWorldPos, transform.position);
 0404            transform.position = newGroundWorldPos;
 405
 0406            Vector3 newCharacterForward = groundTransform.TransformDirection(lastCharacterRotation);
 0407            Vector3 lastFrameDifference = Vector3.zero;
 0408            if (CommonScriptableObjects.characterForward.HasValue())
 409            {
 0410                lastFrameDifference = CommonScriptableObjects.characterForward.Get().Value - lastGlobalCharacterRotation
 411            }
 412            //NOTE(Kinerius) CameraStateTPS rotates the character between frames so we add the difference.
 413            //               if we dont do this, the character wont rotate when moving, only when the platform rotates
 0414            CommonScriptableObjects.characterForward.Set(newCharacterForward + lastFrameDifference);
 415        }
 416
 18066417        Transform transformHit = CastGroundCheckingRays();
 418
 18066419        if (transformHit != null)
 420        {
 16747421            if (groundTransform == transformHit)
 422            {
 16553423                bool groundHasMoved = (transformHit.position != groundLastPosition || transformHit.rotation != groundLas
 424
 16553425                if (!characterPosition.RepositionedWorldLastFrame()
 426                    && groundHasMoved)
 427                {
 0428                    isOnMovingPlatform = true;
 0429                    CommonScriptableObjects.playerIsOnMovingPlatform.Set(true);
 0430                    Physics.SyncTransforms();
 0431                    SaveLateUpdateGroundTransforms();
 432
 0433                    Quaternion deltaRotation = groundTransform.rotation * Quaternion.Inverse(groundLastRotation);
 0434                    CommonScriptableObjects.movingPlatformRotationDelta.Set(deltaRotation);
 435                }
 0436            }
 437            else
 438            {
 194439                groundTransform = transformHit;
 194440                CommonScriptableObjects.movingPlatformRotationDelta.Set(Quaternion.identity);
 441            }
 194442        }
 443        else
 444        {
 1319445            ResetGround();
 446        }
 447
 18066448        if (groundTransform != null)
 449        {
 16747450            groundLastPosition = groundTransform.position;
 16747451            groundLastRotation = groundTransform.rotation;
 452        }
 453
 18066454        isGrounded = groundTransform != null && groundTransform.gameObject.activeInHierarchy;
 18066455    }
 456
 457    public Transform CastGroundCheckingRays()
 458    {
 459        RaycastHit hitInfo;
 460
 18066461        var result = CastGroundCheckingRays(transform, collider, groundCheckExtraDistance, 0.9f, groundLayers, out hitIn
 462
 18066463        if ( result )
 464        {
 16747465            return hitInfo.transform;
 466        }
 467
 1319468        return null;
 469    }
 470
 7471    public bool CastGroundCheckingRays(float extraDistance, float scale, out RaycastHit hitInfo) { return CastGroundChec
 472
 473    public bool CastGroundCheckingRay(float extraDistance, out RaycastHit hitInfo)
 474    {
 0475        Bounds bounds = collider.bounds;
 0476        float rayMagnitude = (bounds.extents.y + extraDistance);
 0477        bool test = CastGroundCheckingRay(transform.position, out hitInfo, rayMagnitude, groundLayers);
 0478        return test;
 479    }
 480
 481    // We secuentially cast rays in 4 directions (only if the previous one didn't hit anything)
 482    public static bool CastGroundCheckingRays(Transform transform, Collider collider, float extraDistance, float scale, 
 483    {
 18073484        Bounds bounds = collider.bounds;
 485
 18073486        float rayMagnitude = (bounds.extents.y + extraDistance);
 18073487        float originScale = scale * bounds.extents.x;
 488
 18073489        if (!CastGroundCheckingRay(transform.position, out hitInfo, rayMagnitude, groundLayers) // center
 490            && !CastGroundCheckingRay( transform.position + transform.forward * originScale, out hitInfo, rayMagnitude, 
 491            && !CastGroundCheckingRay( transform.position + transform.right * originScale, out hitInfo, rayMagnitude, gr
 492            && !CastGroundCheckingRay( transform.position + -transform.forward * originScale, out hitInfo, rayMagnitude,
 493            && !CastGroundCheckingRay( transform.position + -transform.right * originScale, out hitInfo, rayMagnitude, g
 494        {
 1319495            return false;
 496        }
 497
 498        // At this point there is a guaranteed hit, so this is not null
 16754499        return true;
 500    }
 501
 502    public static bool CastGroundCheckingRay(Vector3 origin, out RaycastHit hitInfo, float rayMagnitude, int groundLayer
 503    {
 23349504        var ray = new Ray();
 23349505        ray.origin = origin;
 23349506        ray.direction = Vector3.down * rayMagnitude;
 507
 23349508        var result = Physics.Raycast(ray, out hitInfo, rayMagnitude, groundLayers);
 509
 510#if UNITY_EDITOR
 23349511        if ( result )
 16754512            Debug.DrawLine(ray.origin, hitInfo.point, Color.green);
 513        else
 6595514            Debug.DrawRay(ray.origin, ray.direction, Color.red);
 515#endif
 516
 6595517        return result;
 518    }
 519
 520    void ReportMovement()
 521    {
 4570522        float height = 0.875f;
 523
 4570524        var reportPosition = characterPosition.worldPosition + (Vector3.up * height);
 4570525        var compositeRotation = Quaternion.LookRotation(characterForward.HasValue() ? characterForward.Get().Value : cam
 4570526        var playerHeight = height + (characterController.height / 2);
 527
 528        //NOTE(Brian): We have to wait for a Teleport before sending the ReportPosition, because if not ReportPosition e
 529        //             When the spawn point is being selected / scenes being prepared to be sent and the Kernel gets cra
 530
 531        //             The race conditions that can arise from not having this flag can result in:
 532        //                  - Scenes not being sent for loading, making ActivateRenderer never being sent, only in WSS m
 533        //                  - Random teleports to 0,0 or other positions that shouldn't happen.
 4570534        if (initialPositionAlreadySet)
 577535            DCL.Interface.WebInterface.ReportPosition(reportPosition, compositeRotation, playerHeight);
 536
 4570537        lastMovementReportTime = DCLTime.realtimeSinceStartup;
 4570538    }
 539
 540    public void PauseGravity()
 541    {
 75542        gravity = 0f;
 75543        velocity.y = 0f;
 75544    }
 545
 0546    public void ResumeGravity() { gravity = originalGravity; }
 547
 752548    void OnRenderingStateChanged(bool isEnable, bool prevState) { SetEnabled(isEnable); }
 549}