< Summary

Class:DCLCharacterController
Assembly:MainScripts
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Controllers/CharacterController/DCLCharacterController.cs
Covered lines:181
Uncovered lines:60
Coverable lines:241
Total lines:548
Line coverage:75.1% (181 of 241)
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%990100%
Teleport(...)0%3.013090%
SetPosition(...)0%2100%
SetEnabled(...)0%110100%
Moved(...)0%220100%
LateUpdate()0%35.9627076.92%
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")]
 59812    public float minimumYPosition = 1f;
 13
 59814    public float groundCheckExtraDistance = 0.25f;
 59815    public float gravity = -55f;
 59816    public float jumpForce = 12f;
 59817    public float movementSpeed = 8f;
 59818    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]
 59829    public bool characterAlwaysEnabled = true;
 30
 31    [System.NonSerialized]
 32    public CharacterController characterController;
 33
 34    FreeMovementController freeMovementController;
 35
 36    new Collider collider;
 37
 38    float lastUngroundedTime = 0f;
 39    float lastJumpButtonPressedTime = 0f;
 40    float lastMovementReportTime;
 41    float originalGravity;
 42    Vector3 lastLocalGroundPosition;
 43
 44    Vector3 lastCharacterRotation;
 45    Vector3 lastGlobalCharacterRotation;
 46
 59847    Vector3 velocity = Vector3.zero;
 48
 049    public bool isWalking { get; private set; } = false;
 050    public bool isJumping { get; private set; } = false;
 051    public bool isGrounded { get; private set; }
 052    public bool isOnMovingPlatform { get; private set; }
 53
 54    internal Transform groundTransform;
 55
 56    Vector3 lastPosition;
 57    Vector3 groundLastPosition;
 58    Quaternion groundLastRotation;
 59    bool jumpButtonPressed = false;
 60
 61    [Header("InputActions")]
 62    public InputAction_Hold jumpAction;
 63
 64    public InputAction_Hold sprintAction;
 65
 66    public Vector3 moveVelocity;
 67
 68    private InputAction_Hold.Started jumpStartedDelegate;
 69    private InputAction_Hold.Finished jumpFinishedDelegate;
 70    private InputAction_Hold.Started walkStartedDelegate;
 71    private InputAction_Hold.Finished walkFinishedDelegate;
 72
 889973    private Vector3NullableVariable characterForward => CommonScriptableObjects.characterForward;
 74
 75    public static System.Action<DCLCharacterPosition> OnCharacterMoved;
 76    public static System.Action<DCLCharacterPosition> OnPositionSet;
 77    public event System.Action<float> OnUpdateFinish;
 78
 79    // Will allow the game objects to be set, and create the DecentralandEntity manually during the Awake
 080    public DCL.Models.IDCLEntity avatarReference { get; private set; }
 081    public DCL.Models.IDCLEntity firstPersonCameraReference { get; private set; }
 82
 83    [SerializeField]
 84    private GameObject avatarGameObject;
 85
 86    [SerializeField]
 87    private GameObject firstPersonCameraGameObject;
 88
 89    [SerializeField]
 90    private InputAction_Measurable characterYAxis;
 91
 92    [SerializeField]
 93    private InputAction_Measurable characterXAxis;
 94
 1359595    private Vector3Variable cameraForward => CommonScriptableObjects.cameraForward;
 335196    private Vector3Variable cameraRight => CommonScriptableObjects.cameraRight;
 97
 98    [System.NonSerialized]
 99    public float movingPlatformSpeed;
 100
 101    public event System.Action OnJump;
 102    public event System.Action OnHitGround;
 103    public event System.Action<float> OnMoved;
 104
 105    void Awake()
 106    {
 597107        if (i != null)
 108        {
 24109            Destroy(gameObject);
 24110            return;
 111        }
 112
 573113        i = this;
 573114        originalGravity = gravity;
 115
 573116        SubscribeToInput();
 573117        CommonScriptableObjects.playerUnityPosition.Set(Vector3.zero);
 573118        CommonScriptableObjects.playerWorldPosition.Set(Vector3.zero);
 573119        CommonScriptableObjects.playerCoords.Set(Vector2Int.zero);
 573120        CommonScriptableObjects.playerUnityEulerAngles.Set(Vector3.zero);
 121
 573122        characterPosition = new DCLCharacterPosition();
 573123        characterController = GetComponent<CharacterController>();
 573124        freeMovementController = GetComponent<FreeMovementController>();
 573125        collider = GetComponent<Collider>();
 126
 573127        CommonScriptableObjects.worldOffset.OnChange += OnWorldReposition;
 128
 573129        lastPosition = transform.position;
 573130        transform.parent = null;
 131
 573132        CommonScriptableObjects.rendererState.OnChange += OnRenderingStateChanged;
 573133        OnRenderingStateChanged(CommonScriptableObjects.rendererState.Get(), false);
 134
 573135        if (avatarGameObject == null || firstPersonCameraGameObject == null)
 136        {
 0137            throw new System.Exception("Both the avatar and first person camera game objects must be set.");
 138        }
 139
 573140        avatarReference = new DCL.Models.DecentralandEntity { gameObject = avatarGameObject };
 573141        firstPersonCameraReference = new DCL.Models.DecentralandEntity { gameObject = firstPersonCameraGameObject };
 573142    }
 143
 144    private void SubscribeToInput()
 145    {
 573146        jumpStartedDelegate = (action) =>
 147        {
 0148            lastJumpButtonPressedTime = Time.time;
 0149            jumpButtonPressed = true;
 0150        };
 573151        jumpFinishedDelegate = (action) => jumpButtonPressed = false;
 573152        jumpAction.OnStarted += jumpStartedDelegate;
 573153        jumpAction.OnFinished += jumpFinishedDelegate;
 154
 573155        walkStartedDelegate = (action) => isWalking = true;
 573156        walkFinishedDelegate = (action) => isWalking = false;
 573157        sprintAction.OnStarted += walkStartedDelegate;
 573158        sprintAction.OnFinished += walkFinishedDelegate;
 573159    }
 160
 161    void OnDestroy()
 162    {
 597163        CommonScriptableObjects.worldOffset.OnChange -= OnWorldReposition;
 597164        jumpAction.OnStarted -= jumpStartedDelegate;
 597165        jumpAction.OnFinished -= jumpFinishedDelegate;
 597166        sprintAction.OnStarted -= walkStartedDelegate;
 597167        sprintAction.OnFinished -= walkFinishedDelegate;
 597168        CommonScriptableObjects.rendererState.OnChange -= OnRenderingStateChanged;
 597169        i = null;
 597170    }
 171
 172    void OnWorldReposition(Vector3 current, Vector3 previous)
 173    {
 2174        Vector3 oldPos = this.transform.position;
 2175        this.transform.position = characterPosition.unityPosition; //CommonScriptableObjects.playerUnityPosition;
 176
 2177        if (CinemachineCore.Instance.BrainCount > 0)
 178        {
 2179            CinemachineCore.Instance.GetActiveBrain(0).ActiveVirtualCamera?.OnTargetObjectWarped(transform, transform.po
 180        }
 2181    }
 182
 183    public void SetPosition(Vector3 newPosition)
 184    {
 185        // failsafe in case something teleports the player below ground collisions
 5588186        if (newPosition.y < minimumYPosition)
 187        {
 567188            newPosition.y = minimumYPosition + 2f;
 189        }
 190
 5588191        lastPosition = characterPosition.worldPosition;
 5588192        characterPosition.worldPosition = newPosition;
 5588193        transform.position = characterPosition.unityPosition;
 5588194        Environment.i.platform.physicsSyncController?.MarkDirty();
 195
 5588196        CommonScriptableObjects.playerUnityPosition.Set(characterPosition.unityPosition);
 5588197        CommonScriptableObjects.playerWorldPosition.Set(characterPosition.worldPosition);
 5588198        CommonScriptableObjects.playerCoords.Set(Utils.WorldToGridPosition(characterPosition.worldPosition));
 199
 5588200        if (Moved(lastPosition))
 201        {
 4992202            if (Moved(lastPosition, useThreshold: true))
 4992203                ReportMovement();
 204
 4992205            OnCharacterMoved?.Invoke(characterPosition);
 206
 4992207            float distance = Vector3.Distance(characterPosition.worldPosition, lastPosition) - movingPlatformSpeed;
 208
 4992209            if (distance > 0f && isGrounded)
 60210                OnMoved?.Invoke(distance);
 211        }
 212
 5588213        lastPosition = transform.position;
 5588214    }
 215
 216    public void Teleport(string teleportPayload)
 217    {
 39218        ResetGround();
 219
 39220        var payload = Utils.FromJsonWithNulls<Vector3>(teleportPayload);
 221
 39222        var newPosition = new Vector3(payload.x, payload.y, payload.z);
 39223        SetPosition(newPosition);
 224
 39225        if (OnPositionSet != null)
 226        {
 0227            OnPositionSet.Invoke(characterPosition);
 228        }
 229
 39230        DataStore.i.player.lastTeleportPosition.Set(newPosition, true);
 231
 39232        if (!initialPositionAlreadySet)
 233        {
 36234            initialPositionAlreadySet = true;
 235        }
 39236    }
 237
 238    [System.Obsolete("SetPosition is deprecated, please use Teleport instead.", true)]
 0239    public void SetPosition(string positionVector) { Teleport(positionVector); }
 240
 1150241    public void SetEnabled(bool enabled) { this.enabled = enabled; }
 242
 243    bool Moved(Vector3 previousPosition, bool useThreshold = false)
 244    {
 10580245        if (useThreshold)
 4992246            return Vector3.Distance(characterPosition.worldPosition, previousPosition) > 0.001f;
 247        else
 5588248            return characterPosition.worldPosition != previousPosition;
 249    }
 250
 251    internal void LateUpdate()
 252    {
 5548253        if (transform.position.y < minimumYPosition)
 254        {
 0255            SetPosition(characterPosition.worldPosition);
 0256            return;
 257        }
 258
 5548259        if (freeMovementController.IsActive())
 260        {
 0261            velocity = freeMovementController.CalculateMovement();
 0262        }
 263        else
 264        {
 5548265            velocity.x = 0f;
 5548266            velocity.z = 0f;
 5548267            velocity.y += gravity * Time.deltaTime;
 268
 5548269            bool previouslyGrounded = isGrounded;
 270
 5548271            if (!isJumping || velocity.y <= 0f)
 5548272                CheckGround();
 273
 5548274            if (isGrounded)
 275            {
 120276                isJumping = false;
 120277                velocity.y = gravity * Time.deltaTime; // to avoid accumulating gravity in velocity.y while grounded
 120278            }
 5428279            else if (previouslyGrounded && !isJumping)
 280            {
 26281                lastUngroundedTime = Time.time;
 282            }
 283
 5548284            if (characterForward.HasValue())
 285            {
 286                // Horizontal movement
 3351287                var speed = movementSpeed * (isWalking ? runningSpeedMultiplier : 1f);
 288
 3351289                transform.forward = characterForward.Get().Value;
 290
 3351291                var xzPlaneForward = Vector3.Scale(cameraForward.Get(), new Vector3(1, 0, 1));
 3351292                var xzPlaneRight = Vector3.Scale(cameraRight.Get(), new Vector3(1, 0, 1));
 293
 3351294                Vector3 forwardTarget = Vector3.zero;
 295
 3351296                if (characterYAxis.GetValue() > 0)
 0297                    forwardTarget += xzPlaneForward;
 3351298                if (characterYAxis.GetValue() < 0)
 0299                    forwardTarget -= xzPlaneForward;
 300
 3351301                if (characterXAxis.GetValue() > 0)
 0302                    forwardTarget += xzPlaneRight;
 3351303                if (characterXAxis.GetValue() < 0)
 0304                    forwardTarget -= xzPlaneRight;
 305
 306
 3351307                forwardTarget.Normalize();
 3351308                velocity += forwardTarget * speed;
 3351309                CommonScriptableObjects.playerUnityEulerAngles.Set(transform.eulerAngles);
 310            }
 311
 5548312            bool jumpButtonPressedWithGraceTime = jumpButtonPressed && (Time.time - lastJumpButtonPressedTime < 0.15f);
 313
 5548314            if (jumpButtonPressedWithGraceTime) // almost-grounded jump button press allowed time
 315            {
 0316                bool justLeftGround = (Time.time - lastUngroundedTime) < 0.1f;
 317
 0318                if (isGrounded || justLeftGround) // just-left-ground jump allowed time
 319                {
 0320                    Jump();
 321                }
 322            }
 323
 324            //NOTE(Mordi): Detecting when the character hits the ground (for landing-SFX)
 5548325            if (isGrounded && !previouslyGrounded && (Time.time - lastUngroundedTime) > 0.4f)
 326            {
 55327                OnHitGround?.Invoke();
 328            }
 329        }
 330
 5548331        if (characterController.enabled)
 332        {
 333            //NOTE(Brian): Transform has to be in sync before the Move call, otherwise this call
 334            //             will reset the character controller to its previous position.
 5548335            Environment.i.platform.physicsSyncController?.Sync();
 5548336            characterController.Move(velocity * Time.deltaTime);
 337        }
 338
 5548339        SetPosition(PositionUtils.UnityToWorldPosition(transform.position));
 340
 5548341        if ((DCLTime.realtimeSinceStartup - lastMovementReportTime) > PlayerSettings.POSITION_REPORTING_DELAY)
 342        {
 130343            ReportMovement();
 344        }
 345
 5548346        if (isOnMovingPlatform)
 347        {
 0348            SaveLateUpdateGroundTransforms();
 349        }
 5548350        OnUpdateFinish?.Invoke(Time.deltaTime);
 4976351    }
 352
 353    private void SaveLateUpdateGroundTransforms()
 354    {
 0355        lastLocalGroundPosition = groundTransform.InverseTransformPoint(transform.position);
 356
 0357        if (CommonScriptableObjects.characterForward.HasValue())
 358        {
 0359            lastCharacterRotation = groundTransform.InverseTransformDirection(CommonScriptableObjects.characterForward.G
 0360            lastGlobalCharacterRotation = CommonScriptableObjects.characterForward.Get().Value;
 361        }
 0362    }
 363
 364    void Jump()
 365    {
 0366        if (isJumping)
 0367            return;
 368
 0369        isJumping = true;
 0370        isGrounded = false;
 371
 0372        ResetGround();
 373
 0374        velocity.y = jumpForce;
 375        //cameraTargetProbe.damping.y = dampingOnAir;
 376
 0377        OnJump?.Invoke();
 0378    }
 379
 380    public void ResetGround()
 381    {
 10928382        if (isOnMovingPlatform)
 0383            CommonScriptableObjects.playerIsOnMovingPlatform.Set(false);
 384
 10928385        isOnMovingPlatform = false;
 10928386        groundTransform = null;
 10928387        movingPlatformSpeed = 0;
 10928388    }
 389
 390    void CheckGround()
 391    {
 5548392        if (groundTransform == null)
 5461393            ResetGround();
 394
 5548395        if (isOnMovingPlatform)
 396        {
 0397            Physics.SyncTransforms();
 398            //NOTE(Brian): This should move the character with the moving platform
 0399            Vector3 newGroundWorldPos = groundTransform.TransformPoint(lastLocalGroundPosition);
 0400            movingPlatformSpeed = Vector3.Distance(newGroundWorldPos, transform.position);
 0401            transform.position = newGroundWorldPos;
 402
 0403            Vector3 newCharacterForward = groundTransform.TransformDirection(lastCharacterRotation);
 0404            Vector3 lastFrameDifference = Vector3.zero;
 0405            if (CommonScriptableObjects.characterForward.HasValue())
 406            {
 0407                lastFrameDifference = CommonScriptableObjects.characterForward.Get().Value - lastGlobalCharacterRotation
 408            }
 409
 410            //NOTE(Kinerius) CameraStateTPS rotates the character between frames so we add the difference.
 411            //               if we dont do this, the character wont rotate when moving, only when the platform rotates
 0412            CommonScriptableObjects.characterForward.Set(newCharacterForward + lastFrameDifference);
 413        }
 414
 5548415        Transform transformHit = CastGroundCheckingRays();
 416
 5548417        if (transformHit != null)
 418        {
 120419            if (groundTransform == transformHit)
 420            {
 59421                bool groundHasMoved = (transformHit.position != groundLastPosition || transformHit.rotation != groundLas
 422
 59423                if (!characterPosition.RepositionedWorldLastFrame()
 424                    && groundHasMoved)
 425                {
 0426                    isOnMovingPlatform = true;
 0427                    CommonScriptableObjects.playerIsOnMovingPlatform.Set(true);
 0428                    Physics.SyncTransforms();
 0429                    SaveLateUpdateGroundTransforms();
 430
 0431                    Quaternion deltaRotation = groundTransform.rotation * Quaternion.Inverse(groundLastRotation);
 0432                    CommonScriptableObjects.movingPlatformRotationDelta.Set(deltaRotation);
 433                }
 0434            }
 435            else
 436            {
 61437                groundTransform = transformHit;
 61438                CommonScriptableObjects.movingPlatformRotationDelta.Set(Quaternion.identity);
 439            }
 61440        }
 441        else
 442        {
 5428443            ResetGround();
 444        }
 445
 5548446        if (groundTransform != null)
 447        {
 120448            groundLastPosition = groundTransform.position;
 120449            groundLastRotation = groundTransform.rotation;
 450        }
 451
 5548452        isGrounded = groundTransform != null && groundTransform.gameObject.activeInHierarchy;
 5548453    }
 454
 455    public Transform CastGroundCheckingRays()
 456    {
 457        RaycastHit hitInfo;
 458
 5548459        var result = CastGroundCheckingRays(transform, collider, groundCheckExtraDistance, 0.9f, groundLayers, out hitIn
 460
 5548461        if ( result )
 462        {
 120463            return hitInfo.transform;
 464        }
 465
 5428466        return null;
 467    }
 468
 2198469    public bool CastGroundCheckingRays(float extraDistance, float scale, out RaycastHit hitInfo) { return CastGroundChec
 470
 471    public bool CastGroundCheckingRay(float extraDistance, out RaycastHit hitInfo)
 472    {
 0473        Bounds bounds = collider.bounds;
 0474        float rayMagnitude = (bounds.extents.y + extraDistance);
 0475        bool test = CastGroundCheckingRay(transform.position, out hitInfo, rayMagnitude, groundLayers);
 0476        return test;
 477    }
 478
 479    // We secuentially cast rays in 4 directions (only if the previous one didn't hit anything)
 480    public static bool CastGroundCheckingRays(Transform transform, Collider collider, float extraDistance, float scale, 
 481    {
 7746482        Bounds bounds = collider.bounds;
 483
 7746484        float rayMagnitude = (bounds.extents.y + extraDistance);
 7746485        float originScale = scale * bounds.extents.x;
 486
 7746487        if (!CastGroundCheckingRay(transform.position, out hitInfo, rayMagnitude, groundLayers) // center
 488            && !CastGroundCheckingRay( transform.position + transform.forward * originScale, out hitInfo, rayMagnitude, 
 489            && !CastGroundCheckingRay( transform.position + transform.right * originScale, out hitInfo, rayMagnitude, gr
 490            && !CastGroundCheckingRay( transform.position + -transform.forward * originScale, out hitInfo, rayMagnitude,
 491            && !CastGroundCheckingRay( transform.position + -transform.right * originScale, out hitInfo, rayMagnitude, g
 492        {
 7626493            return false;
 494        }
 495
 496        // At this point there is a guaranteed hit, so this is not null
 120497        return true;
 498    }
 499
 500    public static bool CastGroundCheckingRay(Vector3 origin, out RaycastHit hitInfo, float rayMagnitude, int groundLayer
 501    {
 38252502        var ray = new Ray();
 38252503        ray.origin = origin;
 38252504        ray.direction = Vector3.down * rayMagnitude;
 505
 38252506        var result = Physics.Raycast(ray, out hitInfo, rayMagnitude, groundLayers);
 507
 508#if UNITY_EDITOR
 38252509        if ( result )
 120510            Debug.DrawLine(ray.origin, hitInfo.point, Color.green);
 511        else
 38132512            Debug.DrawRay(ray.origin, ray.direction, Color.red);
 513#endif
 514
 38132515        return result;
 516    }
 517
 518    void ReportMovement()
 519    {
 5122520        float height = 0.875f;
 521
 5122522        var reportPosition = characterPosition.worldPosition + (Vector3.up * height);
 5122523        var compositeRotation = Quaternion.LookRotation(cameraForward.Get());
 5122524        var playerHeight = height + (characterController.height / 2);
 5122525        var cameraRotation = Quaternion.LookRotation(cameraForward.Get());
 526
 527        //NOTE(Brian): We have to wait for a Teleport before sending the ReportPosition, because if not ReportPosition e
 528        //             When the spawn point is being selected / scenes being prepared to be sent and the Kernel gets cra
 529
 530        //             The race conditions that can arise from not having this flag can result in:
 531        //                  - Scenes not being sent for loading, making ActivateRenderer never being sent, only in WSS m
 532        //                  - Random teleports to 0,0 or other positions that shouldn't happen.
 5122533        if (initialPositionAlreadySet)
 74534            DCL.Interface.WebInterface.ReportPosition(reportPosition, compositeRotation, playerHeight, cameraRotation);
 535
 5122536        lastMovementReportTime = DCLTime.realtimeSinceStartup;
 5122537    }
 538
 539    public void PauseGravity()
 540    {
 39541        gravity = 0f;
 39542        velocity.y = 0f;
 39543    }
 544
 0545    public void ResumeGravity() { gravity = originalGravity; }
 546
 1150547    void OnRenderingStateChanged(bool isEnable, bool prevState) { SetEnabled(isEnable); }
 548}