< Summary

Class:DCLCharacterController
Assembly:MainScripts
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Controllers/CharacterController/DCLCharacterController.cs
Covered lines:182
Uncovered lines:60
Coverable lines:242
Total lines:549
Line coverage:75.2% (182 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%35.4627077.36%
SaveLateUpdateGroundTransforms()0%6200%
Jump()0%12300%
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%220100%
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")]
 61312    public float minimumYPosition = 1f;
 13
 61314    public float groundCheckExtraDistance = 0.25f;
 61315    public float gravity = -55f;
 61316    public float jumpForce = 12f;
 61317    public float movementSpeed = 8f;
 61318    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]
 61329    public bool characterAlwaysEnabled = true;
 30
 31    [System.NonSerialized]
 32    public CharacterController characterController;
 33
 34    FreeMovementController freeMovementController;
 35
 36    new Collider collider;
 37
 61338    float deltaTime = 0.032f;
 61339    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
 61349    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
 1275    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
 828197    private Vector3Variable cameraForward => CommonScriptableObjects.cameraForward;
 698    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    {
 610108        if (i != null)
 109        {
 24110            Destroy(gameObject);
 24111            return;
 112        }
 113
 586114        i = this;
 586115        originalGravity = gravity;
 116
 586117        SubscribeToInput();
 586118        CommonScriptableObjects.playerUnityPosition.Set(Vector3.zero);
 586119        CommonScriptableObjects.playerWorldPosition.Set(Vector3.zero);
 586120        CommonScriptableObjects.playerCoords.Set(Vector2Int.zero);
 586121        CommonScriptableObjects.playerUnityEulerAngles.Set(Vector3.zero);
 122
 586123        characterPosition = new DCLCharacterPosition();
 586124        characterController = GetComponent<CharacterController>();
 586125        freeMovementController = GetComponent<FreeMovementController>();
 586126        collider = GetComponent<Collider>();
 127
 586128        CommonScriptableObjects.worldOffset.OnChange += OnWorldReposition;
 129
 586130        lastPosition = transform.position;
 586131        transform.parent = null;
 132
 586133        CommonScriptableObjects.rendererState.OnChange += OnRenderingStateChanged;
 586134        OnRenderingStateChanged(CommonScriptableObjects.rendererState.Get(), false);
 135
 586136        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
 586141        avatarReference = new DCL.Models.DecentralandEntity { gameObject = avatarGameObject };
 586142        firstPersonCameraReference = new DCL.Models.DecentralandEntity { gameObject = firstPersonCameraGameObject };
 143
 586144    }
 145
 146    private void SubscribeToInput()
 147    {
 586148        jumpStartedDelegate = (action) =>
 149        {
 0150            lastJumpButtonPressedTime = Time.time;
 0151            jumpButtonPressed = true;
 0152        };
 586153        jumpFinishedDelegate = (action) => jumpButtonPressed = false;
 586154        jumpAction.OnStarted += jumpStartedDelegate;
 586155        jumpAction.OnFinished += jumpFinishedDelegate;
 156
 586157        walkStartedDelegate = (action) => isWalking = true;
 586158        walkFinishedDelegate = (action) => isWalking = false;
 586159        sprintAction.OnStarted += walkStartedDelegate;
 586160        sprintAction.OnFinished += walkFinishedDelegate;
 586161    }
 162
 163    void OnDestroy()
 164    {
 610165        CommonScriptableObjects.worldOffset.OnChange -= OnWorldReposition;
 610166        jumpAction.OnStarted -= jumpStartedDelegate;
 610167        jumpAction.OnFinished -= jumpFinishedDelegate;
 610168        sprintAction.OnStarted -= walkStartedDelegate;
 610169        sprintAction.OnFinished -= walkFinishedDelegate;
 610170        CommonScriptableObjects.rendererState.OnChange -= OnRenderingStateChanged;
 610171    }
 172
 173    void OnWorldReposition(Vector3 current, Vector3 previous)
 174    {
 2175        Vector3 oldPos = this.transform.position;
 2176        this.transform.position = characterPosition.unityPosition; //CommonScriptableObjects.playerUnityPosition;
 177
 2178        if (CinemachineCore.Instance.BrainCount > 0)
 179        {
 2180            CinemachineCore.Instance.GetActiveBrain(0).ActiveVirtualCamera?.OnTargetObjectWarped(transform, transform.po
 181        }
 2182    }
 183
 184    public void SetPosition(Vector3 newPosition)
 185    {
 186        // failsafe in case something teleports the player below ground collisions
 8712187        if (newPosition.y < minimumYPosition)
 188        {
 741189            newPosition.y = minimumYPosition + 2f;
 190        }
 191
 8712192        lastPosition = characterPosition.worldPosition;
 8712193        characterPosition.worldPosition = newPosition;
 8712194        transform.position = characterPosition.unityPosition;
 8712195        Environment.i.platform.physicsSyncController.MarkDirty();
 196
 8712197        CommonScriptableObjects.playerUnityPosition.Set(characterPosition.unityPosition);
 8712198        CommonScriptableObjects.playerWorldPosition.Set(characterPosition.worldPosition);
 8712199        CommonScriptableObjects.playerCoords.Set(Utils.WorldToGridPosition(characterPosition.worldPosition));
 200
 8712201        if (Moved(lastPosition))
 202        {
 8244203            if (Moved(lastPosition, useThreshold: true))
 8244204                ReportMovement();
 205
 8244206            OnCharacterMoved?.Invoke(characterPosition);
 207
 8244208            float distance = Vector3.Distance(characterPosition.worldPosition, lastPosition) - movingPlatformSpeed;
 209
 8244210            if (distance > 0f && isGrounded)
 112211                OnMoved?.Invoke(distance);
 212        }
 213
 8712214        lastPosition = transform.position;
 8712215    }
 216
 217    public void Teleport(string teleportPayload)
 218    {
 38219        ResetGround();
 220
 38221        var payload = Utils.FromJsonWithNulls<Vector3>(teleportPayload);
 222
 38223        var newPosition = new Vector3(payload.x, payload.y, payload.z);
 38224        SetPosition(newPosition);
 225
 38226        if (OnPositionSet != null)
 227        {
 0228            OnPositionSet.Invoke(characterPosition);
 229        }
 230
 38231        DataStore.i.player.lastTeleportPosition.Set(newPosition, true);
 232
 38233        if (!initialPositionAlreadySet)
 234        {
 35235            initialPositionAlreadySet = true;
 236        }
 38237    }
 238
 239    [System.Obsolete("SetPosition is deprecated, please use Teleport instead.", true)]
 0240    public void SetPosition(string positionVector) { Teleport(positionVector); }
 241
 1176242    public void SetEnabled(bool enabled) { this.enabled = enabled; }
 243
 244    bool Moved(Vector3 previousPosition, bool useThreshold = false)
 245    {
 16956246        if (useThreshold)
 8244247            return Vector3.Distance(characterPosition.worldPosition, previousPosition) > 0.001f;
 248        else
 8712249            return characterPosition.worldPosition != previousPosition;
 250    }
 251
 252    internal void LateUpdate()
 253    {
 8674254        deltaTime = Mathf.Min(deltaTimeCap, Time.deltaTime);
 255
 8674256        if (transform.position.y < minimumYPosition)
 257        {
 0258            SetPosition(characterPosition.worldPosition);
 0259            return;
 260        }
 261
 8674262        if (freeMovementController.IsActive())
 263        {
 0264            velocity = freeMovementController.CalculateMovement();
 0265        }
 266        else
 267        {
 8674268            velocity.x = 0f;
 8674269            velocity.z = 0f;
 8674270            velocity.y += gravity * deltaTime;
 271
 8674272            bool previouslyGrounded = isGrounded;
 273
 8674274            if (!isJumping || velocity.y <= 0f)
 8674275                CheckGround();
 276
 8674277            if (isGrounded)
 278            {
 150279                isJumping = false;
 150280                velocity.y = gravity * deltaTime; // to avoid accumulating gravity in velocity.y while grounded
 150281            }
 8524282            else if (previouslyGrounded && !isJumping)
 283            {
 30284                lastUngroundedTime = Time.time;
 285            }
 286
 8674287            if (Utils.isCursorLocked && characterForward.HasValue())
 288            {
 289                // Horizontal movement
 6290                var speed = movementSpeed * (isWalking ? runningSpeedMultiplier : 1f);
 291
 6292                transform.forward = characterForward.Get().Value;
 293
 6294                var xzPlaneForward = Vector3.Scale(cameraForward.Get(), new Vector3(1, 0, 1));
 6295                var xzPlaneRight = Vector3.Scale(cameraRight.Get(), new Vector3(1, 0, 1));
 296
 6297                Vector3 forwardTarget = Vector3.zero;
 298
 6299                if (characterYAxis.GetValue() > 0)
 0300                    forwardTarget += xzPlaneForward;
 6301                if (characterYAxis.GetValue() < 0)
 0302                    forwardTarget -= xzPlaneForward;
 303
 6304                if (characterXAxis.GetValue() > 0)
 0305                    forwardTarget += xzPlaneRight;
 6306                if (characterXAxis.GetValue() < 0)
 0307                    forwardTarget -= xzPlaneRight;
 308
 309
 6310                forwardTarget.Normalize();
 6311                velocity += forwardTarget * speed;
 6312                CommonScriptableObjects.playerUnityEulerAngles.Set(transform.eulerAngles);
 313            }
 314
 8674315            bool jumpButtonPressedWithGraceTime = jumpButtonPressed && (Time.time - lastJumpButtonPressedTime < 0.15f);
 316
 8674317            if (jumpButtonPressedWithGraceTime) // almost-grounded jump button press allowed time
 318            {
 0319                bool justLeftGround = (Time.time - lastUngroundedTime) < 0.1f;
 320
 0321                if (isGrounded || justLeftGround) // just-left-ground jump allowed time
 322                {
 0323                    Jump();
 324                }
 325            }
 326
 327            //NOTE(Mordi): Detecting when the character hits the ground (for landing-SFX)
 8674328            if (isGrounded && !previouslyGrounded && (Time.time - lastUngroundedTime) > 0.4f)
 329            {
 67330                OnHitGround?.Invoke();
 331            }
 332        }
 333
 8674334        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.
 8674338            Environment.i.platform.physicsSyncController.Sync();
 8674339            characterController.Move(velocity * deltaTime);
 340        }
 341
 8674342        SetPosition(PositionUtils.UnityToWorldPosition(transform.position));
 343
 8674344        if ((DCLTime.realtimeSinceStartup - lastMovementReportTime) > PlayerSettings.POSITION_REPORTING_DELAY)
 345        {
 31346            ReportMovement();
 347        }
 348
 8674349        if (isOnMovingPlatform)
 350        {
 0351            SaveLateUpdateGroundTransforms();
 352        }
 353
 8674354        OnUpdateFinish?.Invoke(deltaTime);
 8089355    }
 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    {
 0369        if (isJumping)
 0370            return;
 371
 0372        isJumping = true;
 0373        isGrounded = false;
 374
 0375        ResetGround();
 376
 0377        velocity.y = jumpForce;
 378        //cameraTargetProbe.damping.y = dampingOnAir;
 379
 0380        OnJump?.Invoke();
 0381    }
 382
 383    public void ResetGround()
 384    {
 17127385        if (isOnMovingPlatform)
 0386            CommonScriptableObjects.playerIsOnMovingPlatform.Set(false);
 387
 17127388        isOnMovingPlatform = false;
 17127389        groundTransform = null;
 17127390        movingPlatformSpeed = 0;
 17127391    }
 392
 393    void CheckGround()
 394    {
 8674395        if (groundTransform == null)
 8565396            ResetGround();
 397
 8674398        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
 8674417        Transform transformHit = CastGroundCheckingRays();
 418
 8674419        if (transformHit != null)
 420        {
 150421            if (groundTransform == transformHit)
 422            {
 76423                bool groundHasMoved = (transformHit.position != groundLastPosition || transformHit.rotation != groundLas
 424
 76425                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            {
 74439                groundTransform = transformHit;
 74440                CommonScriptableObjects.movingPlatformRotationDelta.Set(Quaternion.identity);
 441            }
 74442        }
 443        else
 444        {
 8524445            ResetGround();
 446        }
 447
 8674448        if (groundTransform != null)
 449        {
 150450            groundLastPosition = groundTransform.position;
 150451            groundLastRotation = groundTransform.rotation;
 452        }
 453
 8674454        isGrounded = groundTransform != null && groundTransform.gameObject.activeInHierarchy;
 8674455    }
 456
 457    public Transform CastGroundCheckingRays()
 458    {
 459        RaycastHit hitInfo;
 460
 8674461        var result = CastGroundCheckingRays(transform, collider, groundCheckExtraDistance, 0.9f, groundLayers, out hitIn
 462
 8674463        if ( result )
 464        {
 150465            return hitInfo.transform;
 466        }
 467
 8524468        return null;
 469    }
 470
 1859471    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    {
 10533484        Bounds bounds = collider.bounds;
 485
 10533486        float rayMagnitude = (bounds.extents.y + extraDistance);
 10533487        float originScale = scale * bounds.extents.x;
 488
 10533489        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        {
 10383495            return false;
 496        }
 497
 498        // At this point there is a guaranteed hit, so this is not null
 150499        return true;
 500    }
 501
 502    public static bool CastGroundCheckingRay(Vector3 origin, out RaycastHit hitInfo, float rayMagnitude, int groundLayer
 503    {
 52065504        var ray = new Ray();
 52065505        ray.origin = origin;
 52065506        ray.direction = Vector3.down * rayMagnitude;
 507
 52065508        var result = Physics.Raycast(ray, out hitInfo, rayMagnitude, groundLayers);
 509
 510#if UNITY_EDITOR
 52065511        if ( result )
 150512            Debug.DrawLine(ray.origin, hitInfo.point, Color.green);
 513        else
 51915514            Debug.DrawRay(ray.origin, ray.direction, Color.red);
 515#endif
 516
 51915517        return result;
 518    }
 519
 520    void ReportMovement()
 521    {
 8275522        float height = 0.875f;
 523
 8275524        var reportPosition = characterPosition.worldPosition + (Vector3.up * height);
 8275525        var compositeRotation = Quaternion.LookRotation(cameraForward.Get());
 8275526        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.
 8275534        if (initialPositionAlreadySet)
 37535            DCL.Interface.WebInterface.ReportPosition(reportPosition, compositeRotation, playerHeight);
 536
 8275537        lastMovementReportTime = DCLTime.realtimeSinceStartup;
 8275538    }
 539
 540    public void PauseGravity()
 541    {
 38542        gravity = 0f;
 38543        velocity.y = 0f;
 38544    }
 545
 0546    public void ResumeGravity() { gravity = originalGravity; }
 547
 1176548    void OnRenderingStateChanged(bool isEnable, bool prevState) { SetEnabled(isEnable); }
 549}