< 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%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")]
 37012    public float minimumYPosition = 1f;
 13
 37014    public float groundCheckExtraDistance = 0.25f;
 37015    public float gravity = -55f;
 37016    public float jumpForce = 12f;
 37017    public float movementSpeed = 8f;
 37018    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]
 37029    public bool characterAlwaysEnabled = true;
 30
 31    [System.NonSerialized]
 32    public CharacterController characterController;
 33
 34    FreeMovementController freeMovementController;
 35
 36    new Collider collider;
 37
 37038    float deltaTime = 0.032f;
 37039    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
 37049    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
 8875    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
 469497    private Vector3Variable cameraForward => CommonScriptableObjects.cameraForward;
 4498    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
 20594187        if (newPosition.y < minimumYPosition)
 188        {
 39189            newPosition.y = minimumYPosition + 2f;
 190        }
 191
 20594192        lastPosition = characterPosition.worldPosition;
 20594193        characterPosition.worldPosition = newPosition;
 20594194        transform.position = characterPosition.unityPosition;
 20594195        Environment.i.platform.physicsSyncController.MarkDirty();
 196
 20594197        CommonScriptableObjects.playerUnityPosition.Set(characterPosition.unityPosition);
 20594198        CommonScriptableObjects.playerWorldPosition.Set(characterPosition.worldPosition);
 20594199        CommonScriptableObjects.playerCoords.Set(Utils.WorldToGridPosition(characterPosition.worldPosition));
 200
 20594201        if (Moved(lastPosition))
 202        {
 3323203            if (Moved(lastPosition, useThreshold: true))
 3307204                ReportMovement();
 205
 3323206            OnCharacterMoved?.Invoke(characterPosition);
 207
 3323208            float distance = Vector3.Distance(characterPosition.worldPosition, lastPosition) - movingPlatformSpeed;
 209
 3323210            if (distance > 0f && isGrounded)
 2529211                OnMoved?.Invoke(distance);
 212        }
 213
 20594214        lastPosition = transform.position;
 20594215    }
 216
 217    public void Teleport(string teleportPayload)
 218    {
 53219        ResetGround();
 220
 53221        var payload = Utils.FromJsonWithNulls<Vector3>(teleportPayload);
 222
 53223        var newPosition = new Vector3(payload.x, payload.y, payload.z);
 53224        SetPosition(newPosition);
 225
 53226        if (OnPositionSet != null)
 227        {
 0228            OnPositionSet.Invoke(characterPosition);
 229        }
 230
 53231        DataStore.i.player.lastTeleportPosition.Set(newPosition, true);
 232
 53233        if (!initialPositionAlreadySet)
 234        {
 23235            initialPositionAlreadySet = true;
 236        }
 53237    }
 238
 239    [System.Obsolete("SetPosition is deprecated, please use Teleport instead.", true)]
 0240    public void SetPosition(string positionVector) { Teleport(positionVector); }
 241
 748242    public void SetEnabled(bool enabled) { this.enabled = enabled; }
 243
 244    bool Moved(Vector3 previousPosition, bool useThreshold = false)
 245    {
 23917246        if (useThreshold)
 3323247            return Vector3.Distance(characterPosition.worldPosition, previousPosition) > 0.001f;
 248        else
 20594249            return characterPosition.worldPosition != previousPosition;
 250    }
 251
 252    internal void LateUpdate()
 253    {
 20505254        deltaTime = Mathf.Min(deltaTimeCap, Time.deltaTime);
 255
 20505256        if (transform.position.y < minimumYPosition)
 257        {
 0258            SetPosition(characterPosition.worldPosition);
 0259            return;
 260        }
 261
 20505262        if (freeMovementController.IsActive())
 263        {
 0264            velocity = freeMovementController.CalculateMovement();
 0265        }
 266        else
 267        {
 20505268            velocity.x = 0f;
 20505269            velocity.z = 0f;
 20505270            velocity.y += gravity * deltaTime;
 271
 20505272            bool previouslyGrounded = isGrounded;
 273
 20505274            if (!isJumping || velocity.y <= 0f)
 20466275                CheckGround();
 276
 20505277            if (isGrounded)
 278            {
 19207279                isJumping = false;
 19207280                velocity.y = gravity * deltaTime; // to avoid accumulating gravity in velocity.y while grounded
 19207281            }
 1298282            else if (previouslyGrounded && !isJumping)
 283            {
 22284                lastUngroundedTime = Time.time;
 285            }
 286
 20505287            if (Utils.isCursorLocked && characterForward.HasValue())
 288            {
 289                // Horizontal movement
 44290                var speed = movementSpeed * (isWalking ? runningSpeedMultiplier : 1f);
 291
 44292                transform.forward = characterForward.Get().Value;
 293
 44294                var xzPlaneForward = Vector3.Scale(cameraForward.Get(), new Vector3(1, 0, 1));
 44295                var xzPlaneRight = Vector3.Scale(cameraRight.Get(), new Vector3(1, 0, 1));
 296
 44297                Vector3 forwardTarget = Vector3.zero;
 298
 44299                if (characterYAxis.GetValue() > 0)
 0300                    forwardTarget += xzPlaneForward;
 44301                if (characterYAxis.GetValue() < 0)
 0302                    forwardTarget -= xzPlaneForward;
 303
 44304                if (characterXAxis.GetValue() > 0)
 0305                    forwardTarget += xzPlaneRight;
 44306                if (characterXAxis.GetValue() < 0)
 0307                    forwardTarget -= xzPlaneRight;
 308
 309
 44310                forwardTarget.Normalize();
 44311                velocity += forwardTarget * speed;
 44312                CommonScriptableObjects.playerUnityEulerAngles.Set(transform.eulerAngles);
 313            }
 314
 20505315            bool jumpButtonPressedWithGraceTime = jumpButtonPressed && (Time.time - lastJumpButtonPressedTime < 0.15f);
 316
 20505317            if (jumpButtonPressedWithGraceTime) // almost-grounded jump button press allowed time
 318            {
 27319                bool justLeftGround = (Time.time - lastUngroundedTime) < 0.1f;
 320
 27321                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)
 20505328            if (isGrounded && !previouslyGrounded && (Time.time - lastUngroundedTime) > 0.4f)
 329            {
 112330                OnHitGround?.Invoke();
 331            }
 332        }
 333
 20505334        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.
 20347338            Environment.i.platform.physicsSyncController.Sync();
 20347339            characterController.Move(velocity * deltaTime);
 340        }
 341
 20505342        SetPosition(PositionUtils.UnityToWorldPosition(transform.position));
 343
 20505344        if ((DCLTime.realtimeSinceStartup - lastMovementReportTime) > PlayerSettings.POSITION_REPORTING_DELAY)
 345        {
 1343346            ReportMovement();
 347        }
 348
 20505349        if (isOnMovingPlatform)
 350        {
 0351            SaveLateUpdateGroundTransforms();
 352        }
 353
 20505354        OnUpdateFinish?.Invoke(deltaTime);
 20505355    }
 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    {
 2705385        if (isOnMovingPlatform)
 0386            CommonScriptableObjects.playerIsOnMovingPlatform.Set(false);
 387
 2705388        isOnMovingPlatform = false;
 2705389        groundTransform = null;
 2705390        movingPlatformSpeed = 0;
 2705391    }
 392
 393    void CheckGround()
 394    {
 20466395        if (groundTransform == null)
 1392396            ResetGround();
 397
 20466398        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
 20466417        Transform transformHit = CastGroundCheckingRays();
 418
 20466419        if (transformHit != null)
 420        {
 19207421            if (groundTransform == transformHit)
 422            {
 19026423                bool groundHasMoved = (transformHit.position != groundLastPosition || transformHit.rotation != groundLas
 424
 19026425                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            {
 181439                groundTransform = transformHit;
 181440                CommonScriptableObjects.movingPlatformRotationDelta.Set(Quaternion.identity);
 441            }
 181442        }
 443        else
 444        {
 1259445            ResetGround();
 446        }
 447
 20466448        if (groundTransform != null)
 449        {
 19207450            groundLastPosition = groundTransform.position;
 19207451            groundLastRotation = groundTransform.rotation;
 452        }
 453
 20466454        isGrounded = groundTransform != null && groundTransform.gameObject.activeInHierarchy;
 20466455    }
 456
 457    public Transform CastGroundCheckingRays()
 458    {
 459        RaycastHit hitInfo;
 460
 20466461        var result = CastGroundCheckingRays(transform, collider, groundCheckExtraDistance, 0.9f, groundLayers, out hitIn
 462
 20466463        if ( result )
 464        {
 19207465            return hitInfo.transform;
 466        }
 467
 1259468        return null;
 469    }
 470
 8553471    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    {
 29019484        Bounds bounds = collider.bounds;
 485
 29019486        float rayMagnitude = (bounds.extents.y + extraDistance);
 29019487        float originScale = scale * bounds.extents.x;
 488
 29019489        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        {
 1259495            return false;
 496        }
 497
 498        // At this point there is a guaranteed hit, so this is not null
 27760499        return true;
 500    }
 501
 502    public static bool CastGroundCheckingRay(Vector3 origin, out RaycastHit hitInfo, float rayMagnitude, int groundLayer
 503    {
 34055504        var ray = new Ray();
 34055505        ray.origin = origin;
 34055506        ray.direction = Vector3.down * rayMagnitude;
 507
 34055508        var result = Physics.Raycast(ray, out hitInfo, rayMagnitude, groundLayers);
 509
 510#if UNITY_EDITOR
 34055511        if ( result )
 27760512            Debug.DrawLine(ray.origin, hitInfo.point, Color.green);
 513        else
 6295514            Debug.DrawRay(ray.origin, ray.direction, Color.red);
 515#endif
 516
 6295517        return result;
 518    }
 519
 520    void ReportMovement()
 521    {
 4650522        float height = 0.875f;
 523
 4650524        var reportPosition = characterPosition.worldPosition + (Vector3.up * height);
 4650525        var compositeRotation = Quaternion.LookRotation(cameraForward.Get());
 4650526        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.
 4650534        if (initialPositionAlreadySet)
 545535            DCL.Interface.WebInterface.ReportPosition(reportPosition, compositeRotation, playerHeight);
 536
 4650537        lastMovementReportTime = DCLTime.realtimeSinceStartup;
 4650538    }
 539
 540    public void PauseGravity()
 541    {
 76542        gravity = 0f;
 76543        velocity.y = 0f;
 76544    }
 545
 0546    public void ResumeGravity() { gravity = originalGravity; }
 547
 748548    void OnRenderingStateChanged(bool isEnable, bool prevState) { SetEnabled(isEnable); }
 549}