< Summary

Class:DCLCharacterController
Assembly:CharacterController
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Controllers/CharacterController/DCLCharacterController.cs
Covered lines:191
Uncovered lines:62
Coverable lines:253
Total lines:572
Line coverage:75.4% (191 of 253)
Covered branches:0
Total branches:0

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
DCLCharacterController()0%110100%
Awake()0%44096.15%
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%38.9829077.19%
SaveLateUpdateGroundTransforms()0%6200%
Jump()0%12300%
ResetGround()0%2.022083.33%
CheckGround()0%28.4112051.52%
CastGroundCheckingRays()0%220100%
CastGroundCheckingRays(...)0%2.152066.67%
CastGroundCheckingRay(...)0%2100%
CastGroundCheckingRays(...)0%660100%
CastGroundCheckingRay(...)0%220100%
ReportMovement()0%330100%
PauseGravity()0%110100%
ResumeGravity()0%2100%
OnRenderingStateChanged(...)0%110100%
IsLastCollisionGround()0%2100%

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    private const float CONTROLLER_DRIFT_OFFSET = 0.15f;
 12
 13    [Header("Movement")]
 59614    public float minimumYPosition = 1f;
 15
 59616    public float groundCheckExtraDistance = 0.1f;
 59617    public float gravity = -55f;
 59618    public float jumpForce = 12f;
 59619    public float movementSpeed = 8f;
 59620    public float runningSpeedMultiplier = 2f;
 21
 22    public DCLCharacterPosition characterPosition;
 23
 24    [Header("Collisions")]
 25    public LayerMask groundLayers;
 26
 27    [Header("Additional Camera Layers")]
 28    public LayerMask cameraLayers;
 29
 30    [System.NonSerialized]
 31    public bool initialPositionAlreadySet = false;
 32
 33    [System.NonSerialized]
 59634    public bool characterAlwaysEnabled = true;
 35
 36    [System.NonSerialized]
 37    public CharacterController characterController;
 38
 39    FreeMovementController freeMovementController;
 40
 41    new Collider collider;
 42
 43    float lastUngroundedTime = 0f;
 44    float lastJumpButtonPressedTime = 0f;
 45    float lastMovementReportTime;
 46    float originalGravity;
 47    Vector3 lastLocalGroundPosition;
 48
 49    Vector3 lastCharacterRotation;
 50    Vector3 lastGlobalCharacterRotation;
 51
 59652    Vector3 velocity = Vector3.zero;
 53
 054    public bool isWalking { get; private set; } = false;
 055    public bool isMovingByUserInput { get; private set; } = false;
 056    public bool isJumping { get; private set; } = false;
 057    public bool isGrounded { get; private set; }
 058    public bool isOnMovingPlatform { get; private set; }
 59
 60    internal Transform groundTransform;
 61
 62    Vector3 lastPosition;
 63    Vector3 groundLastPosition;
 64    Quaternion groundLastRotation;
 65    bool jumpButtonPressed = false;
 66
 67    [Header("InputActions")]
 68    public InputAction_Hold jumpAction;
 69
 70    public InputAction_Hold sprintAction;
 71
 72    public Vector3 moveVelocity;
 73
 74    private InputAction_Hold.Started jumpStartedDelegate;
 75    private InputAction_Hold.Finished jumpFinishedDelegate;
 76    private InputAction_Hold.Started walkStartedDelegate;
 77    private InputAction_Hold.Finished walkFinishedDelegate;
 78
 1613679    private Vector3NullableVariable characterForward => CommonScriptableObjects.characterForward;
 80
 81    public static System.Action<DCLCharacterPosition> OnCharacterMoved;
 82    public static System.Action<DCLCharacterPosition> OnPositionSet;
 83    public event System.Action<float> OnUpdateFinish;
 84
 85    public GameObject avatarGameObject;
 86    public GameObject firstPersonCameraGameObject;
 87
 88    [SerializeField]
 89    private InputAction_Measurable characterYAxis;
 90
 91    [SerializeField]
 92    private InputAction_Measurable characterXAxis;
 93
 1013194    private Vector3Variable cameraForward => CommonScriptableObjects.cameraForward;
 318895    private Vector3Variable cameraRight => CommonScriptableObjects.cameraRight;
 96
 97    [System.NonSerialized]
 98    public float movingPlatformSpeed;
 99    private CollisionFlags lastCharacterControllerCollision;
 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    {
 583107        if (i != null)
 108        {
 23109            Destroy(gameObject);
 23110            return;
 111        }
 112
 560113        i = this;
 560114        originalGravity = gravity;
 115
 560116        SubscribeToInput();
 560117        CommonScriptableObjects.playerUnityPosition.Set(Vector3.zero);
 560118        CommonScriptableObjects.playerWorldPosition.Set(Vector3.zero);
 560119        CommonScriptableObjects.playerCoords.Set(Vector2Int.zero);
 560120        DataStore.i.player.playerWorldPosition.Set(Vector2Int.zero);
 560121        CommonScriptableObjects.playerUnityEulerAngles.Set(Vector3.zero);
 122
 560123        characterPosition = new DCLCharacterPosition();
 560124        characterController = GetComponent<CharacterController>();
 560125        freeMovementController = GetComponent<FreeMovementController>();
 560126        collider = GetComponent<Collider>();
 127
 560128        CommonScriptableObjects.worldOffset.OnChange += OnWorldReposition;
 129
 560130        lastPosition = transform.position;
 560131        transform.parent = null;
 132
 560133        CommonScriptableObjects.rendererState.OnChange += OnRenderingStateChanged;
 560134        OnRenderingStateChanged(CommonScriptableObjects.rendererState.Get(), false);
 135
 560136        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
 560141        var worldData = DataStore.i.Get<DataStore_World>();
 560142        worldData.avatarTransform.Set(avatarGameObject.transform);
 560143        worldData.fpsTransform.Set(firstPersonCameraGameObject.transform);
 560144    }
 145
 146    private void SubscribeToInput()
 147    {
 560148        jumpStartedDelegate = (action) =>
 149        {
 0150            lastJumpButtonPressedTime = Time.time;
 0151            jumpButtonPressed = true;
 0152        };
 560153        jumpFinishedDelegate = (action) => jumpButtonPressed = false;
 560154        jumpAction.OnStarted += jumpStartedDelegate;
 560155        jumpAction.OnFinished += jumpFinishedDelegate;
 156
 560157        walkStartedDelegate = (action) => isWalking = true;
 560158        walkFinishedDelegate = (action) => isWalking = false;
 560159        sprintAction.OnStarted += walkStartedDelegate;
 560160        sprintAction.OnFinished += walkFinishedDelegate;
 560161    }
 162
 163    void OnDestroy()
 164    {
 583165        CommonScriptableObjects.worldOffset.OnChange -= OnWorldReposition;
 583166        jumpAction.OnStarted -= jumpStartedDelegate;
 583167        jumpAction.OnFinished -= jumpFinishedDelegate;
 583168        sprintAction.OnStarted -= walkStartedDelegate;
 583169        sprintAction.OnFinished -= walkFinishedDelegate;
 583170        CommonScriptableObjects.rendererState.OnChange -= OnRenderingStateChanged;
 583171        i = null;
 583172    }
 173
 174    void OnWorldReposition(Vector3 current, Vector3 previous)
 175    {
 2176        Vector3 oldPos = this.transform.position;
 2177        this.transform.position = characterPosition.unityPosition; //CommonScriptableObjects.playerUnityPosition;
 178
 2179        if (CinemachineCore.Instance.BrainCount > 0)
 180        {
 2181            CinemachineCore.Instance.GetActiveBrain(0).ActiveVirtualCamera?.OnTargetObjectWarped(transform, transform.po
 182        }
 2183    }
 184
 185    public void SetPosition(Vector3 newPosition)
 186    {
 187        // failsafe in case something teleports the player below ground collisions
 5291188        if (newPosition.y < minimumYPosition)
 189        {
 154190            newPosition.y = minimumYPosition + 2f;
 191        }
 192
 5291193        lastPosition = characterPosition.worldPosition;
 5291194        characterPosition.worldPosition = newPosition;
 5291195        transform.position = characterPosition.unityPosition;
 5291196        Environment.i.platform.physicsSyncController?.MarkDirty();
 197
 5291198        CommonScriptableObjects.playerUnityPosition.Set(characterPosition.unityPosition);
 5291199        CommonScriptableObjects.playerWorldPosition.Set(characterPosition.worldPosition);
 5291200        Vector2Int playerPosition = Utils.WorldToGridPosition(characterPosition.worldPosition);
 5291201        CommonScriptableObjects.playerCoords.Set(playerPosition);
 5291202        DataStore.i.player.playerWorldPosition.Set(playerPosition);
 5291203        DataStore.i.player.playerUnityPosition.Set(characterPosition.unityPosition);
 204
 5291205        if (Moved(lastPosition))
 206        {
 4847207            if (Moved(lastPosition, useThreshold: true))
 4847208                ReportMovement();
 209
 4847210            OnCharacterMoved?.Invoke(characterPosition);
 211
 4847212            float distance = Vector3.Distance(characterPosition.worldPosition, lastPosition) - movingPlatformSpeed;
 213
 4847214            if (distance > 0f && isGrounded)
 24215                OnMoved?.Invoke(distance);
 216        }
 217
 5291218        lastPosition = transform.position;
 5291219    }
 220
 221    public void Teleport(string teleportPayload)
 222    {
 39223        ResetGround();
 224
 39225        var payload = Utils.FromJsonWithNulls<Vector3>(teleportPayload);
 226
 39227        var newPosition = new Vector3(payload.x, payload.y, payload.z);
 39228        SetPosition(newPosition);
 229
 39230        if (OnPositionSet != null)
 231        {
 0232            OnPositionSet.Invoke(characterPosition);
 233        }
 234
 39235        DataStore.i.player.lastTeleportPosition.Set(newPosition, true);
 236
 39237        if (!initialPositionAlreadySet)
 238        {
 36239            initialPositionAlreadySet = true;
 240        }
 39241    }
 242
 243    [System.Obsolete("SetPosition is deprecated, please use Teleport instead.", true)]
 0244    public void SetPosition(string positionVector) { Teleport(positionVector); }
 245
 2242246    public void SetEnabled(bool enabled) { this.enabled = enabled; }
 247
 248    bool Moved(Vector3 previousPosition, bool useThreshold = false)
 249    {
 10138250        if (useThreshold)
 4847251            return Vector3.Distance(characterPosition.worldPosition, previousPosition) > 0.001f;
 252        else
 5291253            return characterPosition.worldPosition != previousPosition;
 254    }
 255
 256    internal void LateUpdate()
 257    {
 5253258        if(!DataStore.i.player.canPlayerMove.Get())
 2259            return;
 260
 5251261        if (transform.position.y < minimumYPosition)
 262        {
 0263            SetPosition(characterPosition.worldPosition);
 0264            return;
 265        }
 266
 5251267        if (freeMovementController.IsActive())
 268        {
 0269            velocity = freeMovementController.CalculateMovement();
 0270        }
 271        else
 272        {
 5251273            velocity.x = 0f;
 5251274            velocity.z = 0f;
 5251275            velocity.y += gravity * Time.deltaTime;
 276
 5251277            bool previouslyGrounded = isGrounded;
 278
 5251279            if (!isJumping || velocity.y <= 0f)
 5251280                CheckGround();
 281
 5251282            if (isGrounded)
 283            {
 33284                isJumping = false;
 33285                velocity.y = gravity * Time.deltaTime; // to avoid accumulating gravity in velocity.y while grounded
 33286            }
 5218287            else if (previouslyGrounded && !isJumping)
 288            {
 3289                lastUngroundedTime = Time.time;
 290            }
 291
 5251292            if (characterForward.HasValue())
 293            {
 294                // Horizontal movement
 3188295                var speed = movementSpeed * (isWalking ? runningSpeedMultiplier : 1f);
 296
 3188297                transform.forward = characterForward.Get().Value;
 298
 3188299                var xzPlaneForward = Vector3.Scale(cameraForward.Get(), new Vector3(1, 0, 1));
 3188300                var xzPlaneRight = Vector3.Scale(cameraRight.Get(), new Vector3(1, 0, 1));
 301
 3188302                Vector3 forwardTarget = Vector3.zero;
 303
 3188304                if (characterYAxis.GetValue() > CONTROLLER_DRIFT_OFFSET)
 0305                    forwardTarget += xzPlaneForward;
 3188306                if (characterYAxis.GetValue() < -CONTROLLER_DRIFT_OFFSET)
 0307                    forwardTarget -= xzPlaneForward;
 308
 3188309                if (characterXAxis.GetValue() > CONTROLLER_DRIFT_OFFSET)
 0310                    forwardTarget += xzPlaneRight;
 3188311                if (characterXAxis.GetValue() < -CONTROLLER_DRIFT_OFFSET)
 0312                    forwardTarget -= xzPlaneRight;
 313
 3188314                if (forwardTarget.Equals(Vector3.zero))
 3188315                    isMovingByUserInput = false;
 316                else
 0317                    isMovingByUserInput = true;
 318
 319
 3188320                forwardTarget.Normalize();
 3188321                velocity += forwardTarget * speed;
 3188322                CommonScriptableObjects.playerUnityEulerAngles.Set(transform.eulerAngles);
 323            }
 324
 5251325            bool jumpButtonPressedWithGraceTime = jumpButtonPressed && (Time.time - lastJumpButtonPressedTime < 0.15f);
 326
 5251327            if (jumpButtonPressedWithGraceTime) // almost-grounded jump button press allowed time
 328            {
 0329                bool justLeftGround = (Time.time - lastUngroundedTime) < 0.1f;
 330
 0331                if (isGrounded || justLeftGround) // just-left-ground jump allowed time
 332                {
 0333                    Jump();
 334                }
 335            }
 336
 337            //NOTE(Mordi): Detecting when the character hits the ground (for landing-SFX)
 5251338            if (isGrounded && !previouslyGrounded && (Time.time - lastUngroundedTime) > 0.4f)
 339            {
 19340                OnHitGround?.Invoke();
 341            }
 342        }
 343
 5251344        if (characterController.enabled)
 345        {
 346            //NOTE(Brian): Transform has to be in sync before the Move call, otherwise this call
 347            //             will reset the character controller to its previous position.
 5251348            Environment.i.platform.physicsSyncController?.Sync();
 5251349            lastCharacterControllerCollision = characterController.Move(velocity * Time.deltaTime);
 350        }
 351
 5251352        SetPosition(PositionUtils.UnityToWorldPosition(transform.position));
 353
 5251354        if ((DCLTime.realtimeSinceStartup - lastMovementReportTime) > PlayerSettings.POSITION_REPORTING_DELAY)
 355        {
 33356            ReportMovement();
 357        }
 358
 5251359        if (isOnMovingPlatform)
 360        {
 0361            SaveLateUpdateGroundTransforms();
 362        }
 5251363        OnUpdateFinish?.Invoke(Time.deltaTime);
 4692364    }
 365
 366    private void SaveLateUpdateGroundTransforms()
 367    {
 0368        lastLocalGroundPosition = groundTransform.InverseTransformPoint(transform.position);
 369
 0370        if (CommonScriptableObjects.characterForward.HasValue())
 371        {
 0372            lastCharacterRotation = groundTransform.InverseTransformDirection(CommonScriptableObjects.characterForward.G
 0373            lastGlobalCharacterRotation = CommonScriptableObjects.characterForward.Get().Value;
 374        }
 0375    }
 376
 377    void Jump()
 378    {
 0379        if (isJumping)
 0380            return;
 381
 0382        isJumping = true;
 0383        isGrounded = false;
 384
 0385        ResetGround();
 386
 0387        velocity.y = jumpForce;
 388        //cameraTargetProbe.damping.y = dampingOnAir;
 389
 0390        OnJump?.Invoke();
 0391    }
 392
 393    public void ResetGround()
 394    {
 10498395        if (isOnMovingPlatform)
 0396            CommonScriptableObjects.playerIsOnMovingPlatform.Set(false);
 397
 10498398        isOnMovingPlatform = false;
 10498399        groundTransform = null;
 10498400        movingPlatformSpeed = 0;
 10498401    }
 402
 403    void CheckGround()
 404    {
 5251405        if (groundTransform == null)
 5238406            ResetGround();
 407
 5251408        if (isOnMovingPlatform)
 409        {
 0410            Physics.SyncTransforms();
 411            //NOTE(Brian): This should move the character with the moving platform
 0412            Vector3 newGroundWorldPos = groundTransform.TransformPoint(lastLocalGroundPosition);
 0413            movingPlatformSpeed = Vector3.Distance(newGroundWorldPos, transform.position);
 0414            transform.position = newGroundWorldPos;
 415
 0416            Vector3 newCharacterForward = groundTransform.TransformDirection(lastCharacterRotation);
 0417            Vector3 lastFrameDifference = Vector3.zero;
 0418            if (CommonScriptableObjects.characterForward.HasValue())
 419            {
 0420                lastFrameDifference = CommonScriptableObjects.characterForward.Get().Value - lastGlobalCharacterRotation
 421            }
 422
 423            //NOTE(Kinerius) CameraStateTPS rotates the character between frames so we add the difference.
 424            //               if we dont do this, the character wont rotate when moving, only when the platform rotates
 0425            CommonScriptableObjects.characterForward.Set(newCharacterForward + lastFrameDifference);
 426        }
 427
 5251428        Transform transformHit = CastGroundCheckingRays();
 429
 5251430        if (transformHit != null)
 431        {
 30432            if (groundTransform == transformHit)
 433            {
 12434                bool groundHasMoved = (transformHit.position != groundLastPosition || transformHit.rotation != groundLas
 435
 12436                if (!characterPosition.RepositionedWorldLastFrame()
 437                    && groundHasMoved)
 438                {
 0439                    isOnMovingPlatform = true;
 0440                    CommonScriptableObjects.playerIsOnMovingPlatform.Set(true);
 0441                    Physics.SyncTransforms();
 0442                    SaveLateUpdateGroundTransforms();
 443
 0444                    Quaternion deltaRotation = groundTransform.rotation * Quaternion.Inverse(groundLastRotation);
 0445                    CommonScriptableObjects.movingPlatformRotationDelta.Set(deltaRotation);
 446                }
 0447            }
 448            else
 449            {
 18450                groundTransform = transformHit;
 18451                CommonScriptableObjects.movingPlatformRotationDelta.Set(Quaternion.identity);
 452            }
 18453        }
 454        else
 455        {
 5221456            ResetGround();
 457        }
 458
 5251459        if (groundTransform != null)
 460        {
 30461            groundLastPosition = groundTransform.position;
 30462            groundLastRotation = groundTransform.rotation;
 463        }
 464
 5251465        isGrounded = IsLastCollisionGround() || groundTransform != null && groundTransform.gameObject.activeInHierarchy;
 5251466    }
 467
 468    public Transform CastGroundCheckingRays()
 469    {
 470        RaycastHit hitInfo;
 471
 5251472        var result = CastGroundCheckingRays(transform, collider, groundCheckExtraDistance, 0.9f, groundLayers, out hitIn
 473
 5251474        if ( result )
 475        {
 30476            return hitInfo.transform;
 477        }
 478
 5221479        return null;
 480    }
 481
 482    public bool CastGroundCheckingRays(float extraDistance, float scale, out RaycastHit hitInfo)
 483    {
 2064484        if (CastGroundCheckingRays(transform, collider, extraDistance, scale, groundLayers | cameraLayers , out hitInfo)
 0485            return true;
 486
 2064487        return IsLastCollisionGround();
 488    }
 489
 490    public bool CastGroundCheckingRay(float extraDistance, out RaycastHit hitInfo)
 491    {
 0492        Bounds bounds = collider.bounds;
 0493        float rayMagnitude = (bounds.extents.y + extraDistance);
 0494        bool test = CastGroundCheckingRay(transform.position, out hitInfo, rayMagnitude, groundLayers);
 0495        return IsLastCollisionGround() || test;
 496    }
 497
 498    // We secuentially cast rays in 4 directions (only if the previous one didn't hit anything)
 499    public static bool CastGroundCheckingRays(Transform transform, Collider collider, float extraDistance, float scale, 
 500    {
 7315501        Bounds bounds = collider.bounds;
 502
 7315503        float rayMagnitude = (bounds.extents.y + extraDistance);
 7315504        float originScale = scale * bounds.extents.x;
 505
 7315506        if (!CastGroundCheckingRay(transform.position, out hitInfo, rayMagnitude, groundLayers) // center
 507            && !CastGroundCheckingRay( transform.position + transform.forward * originScale, out hitInfo, rayMagnitude, 
 508            && !CastGroundCheckingRay( transform.position + transform.right * originScale, out hitInfo, rayMagnitude, gr
 509            && !CastGroundCheckingRay( transform.position + -transform.forward * originScale, out hitInfo, rayMagnitude,
 510            && !CastGroundCheckingRay( transform.position + -transform.right * originScale, out hitInfo, rayMagnitude, g
 511        {
 7285512            return false;
 513        }
 514
 515        // At this point there is a guaranteed hit, so this is not null
 30516        return true;
 517    }
 518
 519    public static bool CastGroundCheckingRay(Vector3 origin, out RaycastHit hitInfo, float rayMagnitude, int groundLayer
 520    {
 36455521        var ray = new Ray();
 36455522        ray.origin = origin;
 36455523        ray.direction = Vector3.down * rayMagnitude;
 524
 36455525        var result = Physics.Raycast(ray, out hitInfo, rayMagnitude, groundLayers);
 526
 527#if UNITY_EDITOR
 36455528        if ( result )
 30529            Debug.DrawLine(ray.origin, hitInfo.point, Color.green);
 530        else
 36425531            Debug.DrawRay(ray.origin, ray.direction, Color.red);
 532#endif
 533
 36425534        return result;
 535    }
 536
 537    void ReportMovement()
 538    {
 4880539        float height = 0.875f;
 540
 4880541        var reportPosition = characterPosition.worldPosition + (Vector3.up * height);
 4880542        var compositeRotation = Quaternion.LookRotation(characterForward.HasValue() ? characterForward.Get().Value : cam
 4880543        var playerHeight = height + (characterController.height / 2);
 4880544        var cameraRotation = Quaternion.LookRotation(cameraForward.Get());
 545
 546        //NOTE(Brian): We have to wait for a Teleport before sending the ReportPosition, because if not ReportPosition e
 547        //             When the spawn point is being selected / scenes being prepared to be sent and the Kernel gets cra
 548
 549        //             The race conditions that can arise from not having this flag can result in:
 550        //                  - Scenes not being sent for loading, making ActivateRenderer never being sent, only in WSS m
 551        //                  - Random teleports to 0,0 or other positions that shouldn't happen.
 4880552        if (initialPositionAlreadySet)
 40553            DCL.Interface.WebInterface.ReportPosition(reportPosition, compositeRotation, playerHeight, cameraRotation);
 554
 4880555        lastMovementReportTime = DCLTime.realtimeSinceStartup;
 4880556    }
 557
 558    public void PauseGravity()
 559    {
 39560        gravity = 0f;
 39561        velocity.y = 0f;
 39562    }
 563
 0564    public void ResumeGravity() { gravity = originalGravity; }
 565
 2242566    void OnRenderingStateChanged(bool isEnable, bool prevState) { SetEnabled(isEnable); }
 567
 568    bool IsLastCollisionGround()
 569    {
 0570        return (lastCharacterControllerCollision & CollisionFlags.Below) != 0;
 571    }
 572}