< 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")]
 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
 25475    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
 488797    private Vector3Variable cameraForward => CommonScriptableObjects.cameraForward;
 12798    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
 18220187        if (newPosition.y < minimumYPosition)
 188        {
 39189            newPosition.y = minimumYPosition + 2f;
 190        }
 191
 18220192        lastPosition = characterPosition.worldPosition;
 18220193        characterPosition.worldPosition = newPosition;
 18220194        transform.position = characterPosition.unityPosition;
 18220195        Environment.i.platform.physicsSyncController.MarkDirty();
 196
 18220197        CommonScriptableObjects.playerUnityPosition.Set(characterPosition.unityPosition);
 18220198        CommonScriptableObjects.playerWorldPosition.Set(characterPosition.worldPosition);
 18220199        CommonScriptableObjects.playerCoords.Set(Utils.WorldToGridPosition(characterPosition.worldPosition));
 200
 18220201        if (Moved(lastPosition))
 202        {
 3485203            if (Moved(lastPosition, useThreshold: true))
 3471204                ReportMovement();
 205
 3485206            OnCharacterMoved?.Invoke(characterPosition);
 207
 3485208            float distance = Vector3.Distance(characterPosition.worldPosition, lastPosition) - movingPlatformSpeed;
 209
 3485210            if (distance > 0f && isGrounded)
 2624211                OnMoved?.Invoke(distance);
 212        }
 213
 18220214        lastPosition = transform.position;
 18220215    }
 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    {
 21705246        if (useThreshold)
 3485247            return Vector3.Distance(characterPosition.worldPosition, previousPosition) > 0.001f;
 248        else
 18220249            return characterPosition.worldPosition != previousPosition;
 250    }
 251
 252    internal void LateUpdate()
 253    {
 18134254        deltaTime = Mathf.Min(deltaTimeCap, Time.deltaTime);
 255
 18134256        if (transform.position.y < minimumYPosition)
 257        {
 0258            SetPosition(characterPosition.worldPosition);
 0259            return;
 260        }
 261
 18134262        if (freeMovementController.IsActive())
 263        {
 0264            velocity = freeMovementController.CalculateMovement();
 0265        }
 266        else
 267        {
 18134268            velocity.x = 0f;
 18134269            velocity.z = 0f;
 18134270            velocity.y += gravity * deltaTime;
 271
 18134272            bool previouslyGrounded = isGrounded;
 273
 18134274            if (!isJumping || velocity.y <= 0f)
 18097275                CheckGround();
 276
 18134277            if (isGrounded)
 278            {
 16788279                isJumping = false;
 16788280                velocity.y = gravity * deltaTime; // to avoid accumulating gravity in velocity.y while grounded
 16788281            }
 1346282            else if (previouslyGrounded && !isJumping)
 283            {
 23284                lastUngroundedTime = Time.time;
 285            }
 286
 18134287            if (Utils.isCursorLocked && characterForward.HasValue())
 288            {
 289                // Horizontal movement
 127290                var speed = movementSpeed * (isWalking ? runningSpeedMultiplier : 1f);
 291
 127292                transform.forward = characterForward.Get().Value;
 293
 127294                var xzPlaneForward = Vector3.Scale(cameraForward.Get(), new Vector3(1, 0, 1));
 127295                var xzPlaneRight = Vector3.Scale(cameraRight.Get(), new Vector3(1, 0, 1));
 296
 127297                Vector3 forwardTarget = Vector3.zero;
 298
 127299                if (characterYAxis.GetValue() > 0)
 0300                    forwardTarget += xzPlaneForward;
 127301                if (characterYAxis.GetValue() < 0)
 0302                    forwardTarget -= xzPlaneForward;
 303
 127304                if (characterXAxis.GetValue() > 0)
 0305                    forwardTarget += xzPlaneRight;
 127306                if (characterXAxis.GetValue() < 0)
 0307                    forwardTarget -= xzPlaneRight;
 308
 309
 127310                forwardTarget.Normalize();
 127311                velocity += forwardTarget * speed;
 127312                CommonScriptableObjects.playerUnityEulerAngles.Set(transform.eulerAngles);
 313            }
 314
 18134315            bool jumpButtonPressedWithGraceTime = jumpButtonPressed && (Time.time - lastJumpButtonPressedTime < 0.15f);
 316
 18134317            if (jumpButtonPressedWithGraceTime) // almost-grounded jump button press allowed time
 318            {
 26319                bool justLeftGround = (Time.time - lastUngroundedTime) < 0.1f;
 320
 26321                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)
 18134328            if (isGrounded && !previouslyGrounded && (Time.time - lastUngroundedTime) > 0.4f)
 329            {
 116330                OnHitGround?.Invoke();
 331            }
 332        }
 333
 18134334        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.
 17976338            Environment.i.platform.physicsSyncController.Sync();
 17976339            characterController.Move(velocity * deltaTime);
 340        }
 341
 18134342        SetPosition(PositionUtils.UnityToWorldPosition(transform.position));
 343
 18134344        if ((DCLTime.realtimeSinceStartup - lastMovementReportTime) > PlayerSettings.POSITION_REPORTING_DELAY)
 345        {
 1289346            ReportMovement();
 347        }
 348
 18134349        if (isOnMovingPlatform)
 350        {
 0351            SaveLateUpdateGroundTransforms();
 352        }
 353
 18134354        OnUpdateFinish?.Invoke(deltaTime);
 18134355    }
 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    {
 2808385        if (isOnMovingPlatform)
 0386            CommonScriptableObjects.playerIsOnMovingPlatform.Set(false);
 387
 2808388        isOnMovingPlatform = false;
 2808389        groundTransform = null;
 2808390        movingPlatformSpeed = 0;
 2808391    }
 392
 393    void CheckGround()
 394    {
 18097395        if (groundTransform == null)
 1446396            ResetGround();
 397
 18097398        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
 18097417        Transform transformHit = CastGroundCheckingRays();
 418
 18097419        if (transformHit != null)
 420        {
 16788421            if (groundTransform == transformHit)
 422            {
 16604423                bool groundHasMoved = (transformHit.position != groundLastPosition || transformHit.rotation != groundLas
 424
 16604425                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            {
 184439                groundTransform = transformHit;
 184440                CommonScriptableObjects.movingPlatformRotationDelta.Set(Quaternion.identity);
 441            }
 184442        }
 443        else
 444        {
 1309445            ResetGround();
 446        }
 447
 18097448        if (groundTransform != null)
 449        {
 16788450            groundLastPosition = groundTransform.position;
 16788451            groundLastRotation = groundTransform.rotation;
 452        }
 453
 18097454        isGrounded = groundTransform != null && groundTransform.gameObject.activeInHierarchy;
 18097455    }
 456
 457    public Transform CastGroundCheckingRays()
 458    {
 459        RaycastHit hitInfo;
 460
 18097461        var result = CastGroundCheckingRays(transform, collider, groundCheckExtraDistance, 0.9f, groundLayers, out hitIn
 462
 18097463        if ( result )
 464        {
 16788465            return hitInfo.transform;
 466        }
 467
 1309468        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    {
 18104484        Bounds bounds = collider.bounds;
 485
 18104486        float rayMagnitude = (bounds.extents.y + extraDistance);
 18104487        float originScale = scale * bounds.extents.x;
 488
 18104489        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        {
 1309495            return false;
 496        }
 497
 498        // At this point there is a guaranteed hit, so this is not null
 16795499        return true;
 500    }
 501
 502    public static bool CastGroundCheckingRay(Vector3 origin, out RaycastHit hitInfo, float rayMagnitude, int groundLayer
 503    {
 23340504        var ray = new Ray();
 23340505        ray.origin = origin;
 23340506        ray.direction = Vector3.down * rayMagnitude;
 507
 23340508        var result = Physics.Raycast(ray, out hitInfo, rayMagnitude, groundLayers);
 509
 510#if UNITY_EDITOR
 23340511        if ( result )
 16795512            Debug.DrawLine(ray.origin, hitInfo.point, Color.green);
 513        else
 6545514            Debug.DrawRay(ray.origin, ray.direction, Color.red);
 515#endif
 516
 6545517        return result;
 518    }
 519
 520    void ReportMovement()
 521    {
 4760522        float height = 0.875f;
 523
 4760524        var reportPosition = characterPosition.worldPosition + (Vector3.up * height);
 4760525        var compositeRotation = Quaternion.LookRotation(cameraForward.Get());
 4760526        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.
 4760534        if (initialPositionAlreadySet)
 617535            DCL.Interface.WebInterface.ReportPosition(reportPosition, compositeRotation, playerHeight);
 536
 4760537        lastMovementReportTime = DCLTime.realtimeSinceStartup;
 4760538    }
 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}