< 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:48
Coverable lines:229
Total lines:526
Line coverage:79% (181 of 229)
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%
Jump()0%12300%
ResetGround()0%2.022083.33%
CheckGround()0%12.789064%
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 System.Collections;
 6using Cinemachine;
 7using UnityEngine.SceneManagement;
 8
 9public class DCLCharacterController : MonoBehaviour
 10{
 011    public static DCLCharacterController i { get; private set; }
 12
 13    [Header("Movement")]
 32614    public float minimumYPosition = 1f;
 15
 32616    public float groundCheckExtraDistance = 0.25f;
 32617    public float gravity = -55f;
 32618    public float jumpForce = 12f;
 32619    public float movementSpeed = 8f;
 32620    public float runningSpeedMultiplier = 2f;
 21
 22    public DCLCharacterPosition characterPosition;
 23
 24    [Header("Collisions")]
 25    public LayerMask groundLayers;
 26
 27    [System.NonSerialized]
 28    public bool initialPositionAlreadySet = false;
 29
 30    [System.NonSerialized]
 32631    public bool characterAlwaysEnabled = true;
 32
 33    [System.NonSerialized]
 34    public CharacterController characterController;
 35
 36    FreeMovementController freeMovementController;
 37
 38    new Collider collider;
 39
 32640    float deltaTime = 0.032f;
 32641    float deltaTimeCap = 0.032f; // 32 milliseconds = 30FPS, 16 millisecodns = 60FPS
 42    float lastUngroundedTime = 0f;
 43    float lastJumpButtonPressedTime = 0f;
 44    float lastMovementReportTime;
 45    float originalGravity;
 46    Vector3 lastLocalGroundPosition;
 32647    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
 5873    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
 397295    private Vector3Variable cameraForward => CommonScriptableObjects.cameraForward;
 2996    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    {
 122107        if (i != null)
 108        {
 17109            Destroy(gameObject);
 17110            return;
 111        }
 112
 105113        i = this;
 105114        originalGravity = gravity;
 115
 105116        SubscribeToInput();
 105117        CommonScriptableObjects.playerUnityPosition.Set(Vector3.zero);
 105118        CommonScriptableObjects.playerWorldPosition.Set(Vector3.zero);
 105119        CommonScriptableObjects.playerCoords.Set(Vector2Int.zero);
 105120        CommonScriptableObjects.playerUnityEulerAngles.Set(Vector3.zero);
 121
 105122        characterPosition = new DCLCharacterPosition();
 105123        characterController = GetComponent<CharacterController>();
 105124        freeMovementController = GetComponent<FreeMovementController>();
 105125        collider = GetComponent<Collider>();
 126
 105127        CommonScriptableObjects.worldOffset.OnChange += OnWorldReposition;
 128
 105129        lastPosition = transform.position;
 105130        transform.parent = null;
 131
 105132        CommonScriptableObjects.rendererState.OnChange += OnRenderingStateChanged;
 105133        OnRenderingStateChanged(CommonScriptableObjects.rendererState.Get(), false);
 134
 105135        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
 105140        avatarReference = new DCL.Models.DecentralandEntity { gameObject = avatarGameObject };
 105141        firstPersonCameraReference = new DCL.Models.DecentralandEntity { gameObject = firstPersonCameraGameObject };
 105142    }
 143
 144    private void SubscribeToInput()
 145    {
 105146        jumpStartedDelegate = (action) =>
 147        {
 0148            lastJumpButtonPressedTime = Time.time;
 0149            jumpButtonPressed = true;
 0150        };
 105151        jumpFinishedDelegate = (action) => jumpButtonPressed = false;
 105152        jumpAction.OnStarted += jumpStartedDelegate;
 105153        jumpAction.OnFinished += jumpFinishedDelegate;
 154
 105155        walkStartedDelegate = (action) => isWalking = true;
 105156        walkFinishedDelegate = (action) => isWalking = false;
 105157        sprintAction.OnStarted += walkStartedDelegate;
 105158        sprintAction.OnFinished += walkFinishedDelegate;
 105159    }
 160
 161    void OnDestroy()
 162    {
 121163        CommonScriptableObjects.worldOffset.OnChange -= OnWorldReposition;
 121164        jumpAction.OnStarted -= jumpStartedDelegate;
 121165        jumpAction.OnFinished -= jumpFinishedDelegate;
 121166        sprintAction.OnStarted -= walkStartedDelegate;
 121167        sprintAction.OnFinished -= walkFinishedDelegate;
 121168        CommonScriptableObjects.rendererState.OnChange -= OnRenderingStateChanged;
 121169    }
 170
 171    void OnWorldReposition(Vector3 current, Vector3 previous)
 172    {
 3173        Vector3 oldPos = this.transform.position;
 3174        this.transform.position = characterPosition.unityPosition; //CommonScriptableObjects.playerUnityPosition;
 175
 3176        if (CinemachineCore.Instance.BrainCount > 0)
 177        {
 3178            CinemachineCore.Instance.GetActiveBrain(0).ActiveVirtualCamera?.OnTargetObjectWarped(transform, transform.po
 179        }
 3180    }
 181
 182    public void SetPosition(Vector3 newPosition)
 183    {
 184        // failsafe in case something teleports the player below ground collisions
 13672185        if (newPosition.y < minimumYPosition)
 186        {
 38187            newPosition.y = minimumYPosition + 2f;
 188        }
 189
 13672190        lastPosition = characterPosition.worldPosition;
 13672191        characterPosition.worldPosition = newPosition;
 13672192        transform.position = characterPosition.unityPosition;
 13672193        Environment.i.platform.physicsSyncController.MarkDirty();
 194
 13672195        CommonScriptableObjects.playerUnityPosition.Set(characterPosition.unityPosition);
 13672196        CommonScriptableObjects.playerWorldPosition.Set(characterPosition.worldPosition);
 13672197        CommonScriptableObjects.playerCoords.Set(Utils.WorldToGridPosition(characterPosition.worldPosition));
 198
 13672199        if (Moved(lastPosition))
 200        {
 3050201            if (Moved(lastPosition, useThreshold: true))
 3031202                ReportMovement();
 203
 3050204            OnCharacterMoved?.Invoke(characterPosition);
 205
 3050206            float distance = Vector3.Distance(characterPosition.worldPosition, lastPosition) - movingPlatformSpeed;
 207
 3050208            if (distance > 0f && isGrounded)
 2558209                OnMoved?.Invoke(distance);
 210        }
 211
 13672212        lastPosition = transform.position;
 13672213    }
 214
 215    public void Teleport(string teleportPayload)
 216    {
 37217        ResetGround();
 218
 37219        var payload = Utils.FromJsonWithNulls<Vector3>(teleportPayload);
 220
 37221        var newPosition = new Vector3(payload.x, payload.y, payload.z);
 37222        SetPosition(newPosition);
 223
 37224        if (OnPositionSet != null)
 225        {
 0226            OnPositionSet.Invoke(characterPosition);
 227        }
 228
 37229        DataStore.i.player.lastTeleportPosition.Set(newPosition, true);
 230
 37231        if (!initialPositionAlreadySet)
 232        {
 8233            initialPositionAlreadySet = true;
 234        }
 37235    }
 236
 237    [System.Obsolete("SetPosition is deprecated, please use Teleport instead.", true)]
 0238    public void SetPosition(string positionVector) { Teleport(positionVector); }
 239
 640240    public void SetEnabled(bool enabled) { this.enabled = enabled; }
 241
 242    bool Moved(Vector3 previousPosition, bool useThreshold = false)
 243    {
 16722244        if (useThreshold)
 3050245            return Vector3.Distance(characterPosition.worldPosition, previousPosition) > 0.001f;
 246        else
 13672247            return characterPosition.worldPosition != previousPosition;
 248    }
 249
 250    internal void LateUpdate()
 251    {
 13613252        deltaTime = Mathf.Min(deltaTimeCap, Time.deltaTime);
 253
 13613254        if (transform.position.y < minimumYPosition)
 255        {
 0256            SetPosition(characterPosition.worldPosition);
 0257            return;
 258        }
 259
 13613260        if (freeMovementController.IsActive())
 261        {
 0262            velocity = freeMovementController.CalculateMovement();
 0263        }
 264        else
 265        {
 13613266            velocity.x = 0f;
 13613267            velocity.z = 0f;
 13613268            velocity.y += gravity * deltaTime;
 269
 13613270            bool previouslyGrounded = isGrounded;
 271
 13613272            if (!isJumping || velocity.y <= 0f)
 13613273                CheckGround();
 274
 13613275            if (isGrounded)
 276            {
 12602277                isJumping = false;
 12602278                velocity.y = gravity * deltaTime; // to avoid accumulating gravity in velocity.y while grounded
 12602279            }
 1011280            else if (previouslyGrounded && !isJumping)
 281            {
 23282                lastUngroundedTime = Time.time;
 283            }
 284
 13613285            if (Utils.isCursorLocked && characterForward.HasValue())
 286            {
 287                // Horizontal movement
 29288                var speed = movementSpeed * (isWalking ? runningSpeedMultiplier : 1f);
 289
 29290                transform.forward = characterForward.Get().Value;
 291
 29292                var xzPlaneForward = Vector3.Scale(cameraForward.Get(), new Vector3(1, 0, 1));
 29293                var xzPlaneRight = Vector3.Scale(cameraRight.Get(), new Vector3(1, 0, 1));
 294
 29295                Vector3 forwardTarget = Vector3.zero;
 296
 29297                if (characterYAxis.GetValue() > 0)
 0298                    forwardTarget += xzPlaneForward;
 29299                if (characterYAxis.GetValue() < 0)
 0300                    forwardTarget -= xzPlaneForward;
 301
 29302                if (characterXAxis.GetValue() > 0)
 0303                    forwardTarget += xzPlaneRight;
 29304                if (characterXAxis.GetValue() < 0)
 0305                    forwardTarget -= xzPlaneRight;
 306
 307
 29308                forwardTarget.Normalize();
 29309                velocity += forwardTarget * speed;
 29310                CommonScriptableObjects.playerUnityEulerAngles.Set(transform.eulerAngles);
 311            }
 312
 13613313            bool jumpButtonPressedWithGraceTime = jumpButtonPressed && (Time.time - lastJumpButtonPressedTime < 0.15f);
 314
 13613315            if (jumpButtonPressedWithGraceTime) // almost-grounded jump button press allowed time
 316            {
 0317                bool justLeftGround = (Time.time - lastUngroundedTime) < 0.1f;
 318
 0319                if (isGrounded || justLeftGround) // just-left-ground jump allowed time
 320                {
 0321                    Jump();
 322                }
 323            }
 324
 325            //NOTE(Mordi): Detecting when the character hits the ground (for landing-SFX)
 13613326            if (isGrounded && !previouslyGrounded && (Time.time - lastUngroundedTime) > 0.4f)
 327            {
 107328                OnHitGround?.Invoke();
 329            }
 330        }
 331
 13613332        if (characterController.enabled)
 333        {
 334            //NOTE(Brian): Transform has to be in sync before the Move call, otherwise this call
 335            //             will reset the character controller to its previous position.
 13451336            Environment.i.platform.physicsSyncController.Sync();
 13451337            characterController.Move(velocity * deltaTime);
 338        }
 339
 13613340        SetPosition(PositionUtils.UnityToWorldPosition(transform.position));
 341
 13613342        if ((DCLTime.realtimeSinceStartup - lastMovementReportTime) > PlayerSettings.POSITION_REPORTING_DELAY)
 343        {
 912344            ReportMovement();
 345        }
 346
 13613347        if (isOnMovingPlatform)
 348        {
 0349            lastLocalGroundPosition = groundTransform.InverseTransformPoint(transform.position);
 350        }
 351
 13613352        OnUpdateFinish?.Invoke(deltaTime);
 13613353    }
 354
 355    void Jump()
 356    {
 0357        if (isJumping)
 0358            return;
 359
 0360        isJumping = true;
 0361        isGrounded = false;
 362
 0363        ResetGround();
 364
 0365        velocity.y = jumpForce;
 366        //cameraTargetProbe.damping.y = dampingOnAir;
 367
 0368        OnJump?.Invoke();
 0369    }
 370
 371    public void ResetGround()
 372    {
 2182373        if (isOnMovingPlatform)
 0374            CommonScriptableObjects.playerIsOnMovingPlatform.Set(false);
 375
 2182376        isOnMovingPlatform = false;
 2182377        groundTransform = null;
 2182378        movingPlatformSpeed = 0;
 2182379    }
 380
 381    void CheckGround()
 382    {
 13613383        if (groundTransform == null)
 1134384            ResetGround();
 385
 13613386        if (isOnMovingPlatform)
 387        {
 0388            Physics.SyncTransforms();
 389            //NOTE(Brian): This should move the character with the moving platform
 0390            Vector3 newGroundWorldPos = groundTransform.TransformPoint(lastLocalGroundPosition);
 0391            movingPlatformSpeed = Vector3.Distance(newGroundWorldPos, transform.position);
 0392            transform.position = newGroundWorldPos;
 393        }
 394
 13613395        Transform transformHit = CastGroundCheckingRays();
 396
 13613397        if (transformHit != null)
 398        {
 12602399            if (groundTransform == transformHit)
 400            {
 12443401                bool groundHasMoved = (transformHit.position != groundLastPosition || transformHit.rotation != groundLas
 402
 12443403                if (!characterPosition.RepositionedWorldLastFrame()
 404                    && groundHasMoved)
 405                {
 0406                    isOnMovingPlatform = true;
 0407                    CommonScriptableObjects.playerIsOnMovingPlatform.Set(true);
 0408                    Physics.SyncTransforms();
 0409                    lastLocalGroundPosition = groundTransform.InverseTransformPoint(transform.position);
 410                }
 0411            }
 412            else
 413            {
 159414                groundTransform = transformHit;
 415            }
 159416        }
 417        else
 418        {
 1011419            ResetGround();
 420        }
 421
 13613422        if (groundTransform != null)
 423        {
 12602424            groundLastPosition = groundTransform.position;
 12602425            groundLastRotation = groundTransform.rotation;
 426        }
 427
 13613428        isGrounded = groundTransform != null && groundTransform.gameObject.activeInHierarchy;
 13613429    }
 430
 431    public Transform CastGroundCheckingRays()
 432    {
 433        RaycastHit hitInfo;
 434
 13613435        var result = CastGroundCheckingRays(transform, collider, groundCheckExtraDistance, 0.9f, groundLayers, out hitIn
 436
 13613437        if ( result )
 438        {
 12602439            return hitInfo.transform;
 440        }
 441
 1011442        return null;
 443    }
 444
 445    public bool CastGroundCheckingRays(float extraDistance, float scale, out RaycastHit hitInfo)
 446    {
 7447        return CastGroundCheckingRays(transform, collider, extraDistance, scale, groundLayers, out hitInfo);
 448    }
 449
 450    public bool CastGroundCheckingRay(float extraDistance, out RaycastHit hitInfo)
 451    {
 0452        Bounds bounds = collider.bounds;
 0453        float rayMagnitude = (bounds.extents.y + extraDistance);
 0454        bool test = CastGroundCheckingRay(transform.position, out hitInfo, rayMagnitude, groundLayers);
 0455        return test;
 456    }
 457
 458    // We secuentially cast rays in 4 directions (only if the previous one didn't hit anything)
 459    public static bool CastGroundCheckingRays(Transform transform, Collider collider, float extraDistance, float scale, 
 460    {
 13620461        Bounds bounds = collider.bounds;
 462
 13620463        float rayMagnitude = (bounds.extents.y + extraDistance);
 13620464        float originScale = scale * bounds.extents.x;
 465
 13620466        if (!CastGroundCheckingRay(Vector3.zero, out hitInfo, rayMagnitude, groundLayers) // center
 467            && !CastGroundCheckingRay( transform.position + transform.forward * originScale, out hitInfo, rayMagnitude, 
 468            && !CastGroundCheckingRay( transform.position + transform.right * originScale, out hitInfo, rayMagnitude, gr
 469            && !CastGroundCheckingRay( transform.position + -transform.forward * originScale, out hitInfo, rayMagnitude,
 470            && !CastGroundCheckingRay( transform.position + -transform.right * originScale, out hitInfo, rayMagnitude, g
 471        {
 1011472            return false;
 473        }
 474
 475        // At this point there is a guaranteed hit, so this is not null
 12609476        return true;
 477    }
 478
 479    public static bool CastGroundCheckingRay(Vector3 origin, out RaycastHit hitInfo, float rayMagnitude, int groundLayer
 480    {
 30273481        var ray = new Ray();
 30273482        ray.origin = origin;
 30273483        ray.direction = Vector3.down * rayMagnitude;
 484
 30273485        var result = Physics.Raycast(ray, out hitInfo, rayMagnitude, groundLayers);
 486
 487#if UNITY_EDITOR
 30273488        if ( result )
 12609489            Debug.DrawLine(ray.origin, hitInfo.point, Color.green);
 490        else
 17664491            Debug.DrawRay(ray.origin, ray.direction, Color.red);
 492#endif
 493
 17664494        return result;
 495    }
 496
 497    void ReportMovement()
 498    {
 3943499        float height = 0.875f;
 500
 3943501        var reportPosition = characterPosition.worldPosition + (Vector3.up * height);
 3943502        var compositeRotation = Quaternion.LookRotation(cameraForward.Get());
 3943503        var playerHeight = height + (characterController.height / 2);
 504
 505        //NOTE(Brian): We have to wait for a Teleport before sending the ReportPosition, because if not ReportPosition e
 506        //             When the spawn point is being selected / scenes being prepared to be sent and the Kernel gets cra
 507
 508        //             The race conditions that can arise from not having this flag can result in:
 509        //                  - Scenes not being sent for loading, making ActivateRenderer never being sent, only in WSS m
 510        //                  - Random teleports to 0,0 or other positions that shouldn't happen.
 3943511        if (initialPositionAlreadySet)
 483512            DCL.Interface.WebInterface.ReportPosition(reportPosition, compositeRotation, playerHeight);
 513
 3943514        lastMovementReportTime = DCLTime.realtimeSinceStartup;
 3943515    }
 516
 517    public void PauseGravity()
 518    {
 60519        gravity = 0f;
 60520        velocity.y = 0f;
 60521    }
 522
 0523    public void ResumeGravity() { gravity = originalGravity; }
 524
 640525    void OnRenderingStateChanged(bool isEnable, bool prevState) { SetEnabled(isEnable); }
 526}