< Summary

Class:AvatarAnimatorLegacy
Assembly:AvatarShape
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Avatar/AvatarAnimatorLegacy.cs
Covered lines:13
Uncovered lines:160
Coverable lines:173
Total lines:446
Line coverage:7.5% (13 of 173)
Covered branches:0
Total branches:0

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
AvatarAnimatorLegacy()0%110100%
Start()0%110100%
Prepare(...)0%20400%
PrepareLocomotionAnims(...)0%12300%
OnEventHandlerUpdate()0%2100%
OnPoolGet()0%220100%
OnPoolRelease()0%20400%
OnUpdateWithDeltaTime(...)0%6200%
UpdateInterface()0%56700%
State_Init(...)0%6200%
State_Ground(...)0%30500%
CrossFadeTo(...)0%6200%
State_Air(...)0%12300%
State_Expression(...)0%72800%
SetExpressionValues(...)0%38.97013.33%
Reset()0%6200%
SetIdleFrame()0%2100%
PlayEmote(...)0%110100%
EquipEmote(...)0%12300%
UnequipEmote(...)0%12300%
InitializeAvatarAudioAndParticleHandlers(...)0%12300%
OnDestroy()0%2.152066.67%

File(s)

/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Avatar/AvatarAnimatorLegacy.cs

#LineLine coverage
 1using System;
 2using AvatarSystem;
 3using DCL;
 4using DCL.Components;
 5using DCL.Helpers;
 6using UnityEngine;
 7using Environment = DCL.Environment;
 8
 9public enum AvatarAnimation
 10{
 11    IDLE,
 12    RUN,
 13    WALK,
 14    EMOTE,
 15    JUMP,
 16    FALL,
 17}
 18
 19public class AvatarAnimatorLegacy : MonoBehaviour, IPoolLifecycleHandler, IAnimator
 20{
 21    const float IDLE_TRANSITION_TIME = 0.2f;
 22    const float STRAFE_TRANSITION_TIME = 0.25f;
 23    const float RUN_TRANSITION_TIME = 0.15f;
 24    const float WALK_TRANSITION_TIME = 0.15f;
 25    const float JUMP_TRANSITION_TIME = 0.01f;
 26    const float FALL_TRANSITION_TIME = 0.5f;
 27    const float EXPRESSION_TRANSITION_TIME = 0.2f;
 28
 29    const float AIR_EXIT_TRANSITION_TIME = 0.2f;
 30    const float GROUND_BLENDTREE_TRANSITION_TIME = 0.15f;
 31
 32    const float RUN_SPEED_THRESHOLD = 0.05f;
 33    const float WALK_SPEED_THRESHOLD = 0.03f;
 34
 35    const float ELEVATION_OFFSET = 0.6f;
 36    const float RAY_OFFSET_LENGTH = 3.0f;
 37
 38    const float MAX_VELOCITY = 6.25f;
 39
 40    // Time it takes to determine if a character is grounded when vertical velocity is 0
 41    const float FORCE_GROUND_TIME = 0.05f;
 42
 43    // Minimum vertical speed used to consider whether an avatar is on air
 44    const float MIN_VERTICAL_SPEED_AIR = 0.025f;
 45
 46    [System.Serializable]
 47    public class AvatarLocomotion
 48    {
 49        public AnimationClip idle;
 50        public AnimationClip walk;
 51        public AnimationClip run;
 52        public AnimationClip jump;
 53        public AnimationClip fall;
 54    }
 55
 56    [System.Serializable]
 57    public class BlackBoard
 58    {
 59        public float walkSpeedFactor;
 60        public float runSpeedFactor;
 61        public float movementSpeed;
 62        public float verticalSpeed;
 63        public bool isGrounded;
 64        public string expressionTriggerId;
 65        public long expressionTriggerTimestamp;
 66        public float deltaTime;
 67    }
 68
 69    [SerializeField] internal AvatarLocomotion femaleLocomotions;
 70    [SerializeField] internal AvatarLocomotion maleLocomotions;
 71    AvatarLocomotion currentLocomotions;
 72
 73    public new Animation animation;
 74    public BlackBoard blackboard;
 75    public Transform target;
 76
 105677    [SerializeField] float runMinSpeed = 6f;
 105678    [SerializeField] float walkMinSpeed = 0.1f;
 79
 80    internal System.Action<BlackBoard> currentState;
 81
 82    Vector3 lastPosition;
 83    bool isOwnPlayer = false;
 84    private AvatarAnimationEventHandler animEventHandler;
 85
 86    private float lastOnAirTime = 0;
 87
 88    private string runAnimationName;
 89    private string walkAnimationName;
 90    private string idleAnimationName;
 91    private string jumpAnimationName;
 92    private string fallAnimationName;
 93    private AvatarAnimation latestAnimation;
 94    private AnimationState runAnimationState;
 95    private AnimationState walkAnimationState;
 96    private bool isUpdateRegistered = false;
 97
 98    private Ray rayCache;
 99
 1138100    public void Start() { OnPoolGet(); }
 101
 102    // AvatarSystem entry points
 103    public bool Prepare(string bodyshapeId, GameObject container)
 104    {
 0105        if (!container.transform.TryFindChildRecursively("Armature", out Transform armature))
 106        {
 0107            Debug.LogError($"Couldn't find Armature for AnimatorLegacy in path: {transform.GetHierarchyPath()}");
 108
 0109            return false;
 110        }
 111
 0112        Transform armatureParent = armature.parent;
 0113        animation = armatureParent.gameObject.GetOrCreateComponent<Animation>();
 0114        armatureParent.gameObject.GetOrCreateComponent<StickerAnimationListener>();
 115
 0116        PrepareLocomotionAnims(bodyshapeId);
 0117        SetIdleFrame();
 0118        animation.Sample();
 0119        InitializeAvatarAudioAndParticleHandlers(animation);
 120
 121        // since the avatar can be updated when changing a wearable we shouldn't register to the update event twice
 0122        if (!isUpdateRegistered)
 123        {
 0124            isUpdateRegistered = true;
 125
 0126            if (isOwnPlayer)
 127            {
 0128                DCLCharacterController.i.OnUpdateFinish += OnUpdateWithDeltaTime;
 0129            }
 130            else
 131            {
 0132                Environment.i.platform.updateEventHandler.AddListener(IUpdateEventHandler.EventType.Update, OnEventHandl
 133            }
 134
 135        }
 136
 0137        return true;
 138    }
 139
 140    private void PrepareLocomotionAnims(string bodyshapeId)
 141    {
 0142        if (bodyshapeId.Contains(WearableLiterals.BodyShapes.MALE))
 143        {
 0144            currentLocomotions = maleLocomotions;
 0145        }
 0146        else if (bodyshapeId.Contains(WearableLiterals.BodyShapes.FEMALE))
 147        {
 0148            currentLocomotions = femaleLocomotions;
 149        }
 150
 0151        EquipEmote(currentLocomotions.idle.name, currentLocomotions.idle);
 0152        EquipEmote(currentLocomotions.walk.name, currentLocomotions.walk);
 0153        EquipEmote(currentLocomotions.run.name, currentLocomotions.run);
 0154        EquipEmote(currentLocomotions.jump.name, currentLocomotions.jump);
 0155        EquipEmote(currentLocomotions.fall.name, currentLocomotions.fall);
 156
 0157        idleAnimationName = currentLocomotions.idle.name;
 0158        walkAnimationName = currentLocomotions.walk.name;
 0159        runAnimationName = currentLocomotions.run.name;
 0160        jumpAnimationName = currentLocomotions.jump.name;
 0161        fallAnimationName = currentLocomotions.fall.name;
 162
 0163        runAnimationState = animation[runAnimationName];
 0164        walkAnimationState = animation[walkAnimationName];
 0165    }
 0166    private void OnEventHandlerUpdate() { OnUpdateWithDeltaTime(Time.deltaTime); }
 167
 168    public void OnPoolGet()
 169    {
 569170        if (DCLCharacterController.i != null)
 171        {
 569172            isOwnPlayer = DCLCharacterController.i.transform == transform.parent;
 173
 174            // NOTE: disable MonoBehaviour's update to use DCLCharacterController event instead
 569175            this.enabled = !isOwnPlayer;
 176        }
 177
 569178        currentState = State_Init;
 569179    }
 180
 181    public void OnPoolRelease()
 182    {
 0183        if (isUpdateRegistered)
 184        {
 0185            isUpdateRegistered = false;
 186
 0187            if (isOwnPlayer && DCLCharacterController.i)
 188            {
 0189                DCLCharacterController.i.OnUpdateFinish -= OnUpdateWithDeltaTime;
 0190            }
 191            else
 192            {
 0193                Environment.i.platform.updateEventHandler.RemoveListener(IUpdateEventHandler.EventType.Update, OnEventHa
 194            }
 195        }
 0196    }
 197
 198    void OnUpdateWithDeltaTime(float deltaTime)
 199    {
 0200        blackboard.deltaTime = deltaTime;
 0201        UpdateInterface();
 0202        currentState?.Invoke(blackboard);
 0203    }
 204
 205    void UpdateInterface()
 206    {
 0207        Vector3 velocityTargetPosition = target.position;
 0208        Vector3 flattenedVelocity = velocityTargetPosition - lastPosition;
 209
 210        //NOTE(Brian): Vertical speed
 0211        float verticalVelocity = flattenedVelocity.y;
 212
 213        //NOTE(Kinerius): if we have more or less than zero we consider that we are either jumping or falling
 0214        if (Mathf.Abs(verticalVelocity) > MIN_VERTICAL_SPEED_AIR)
 215        {
 0216            lastOnAirTime = Time.time;
 217        }
 218
 0219        blackboard.verticalSpeed = verticalVelocity;
 220
 0221        flattenedVelocity.y = 0;
 222
 0223        if (isOwnPlayer)
 0224            blackboard.movementSpeed = flattenedVelocity.magnitude - DCLCharacterController.i.movingPlatformSpeed;
 225        else
 0226            blackboard.movementSpeed = flattenedVelocity.magnitude;
 227
 0228        Vector3 rayOffset = Vector3.up * RAY_OFFSET_LENGTH;
 229
 230        //NOTE(Kinerius): This check is just for the playing character, it uses a combination of collision flags and ray
 0231        bool isGroundedByCharacterController = isOwnPlayer && DCLCharacterController.i.isGrounded;
 232
 233        //NOTE(Kinerius): This check is for interpolated avatars (the other players) as we dont have a Character Control
 234        //                this check is cheap and fast but not precise
 0235        bool isGroundedByVelocity = !isOwnPlayer && Time.time - lastOnAirTime > FORCE_GROUND_TIME;
 236
 237        //NOTE(Kinerius): This additional check is both for the player and interpolated avatars, we cast an additional r
 0238        bool isGroundedByRaycast = false;
 239
 0240        if (!isGroundedByCharacterController && !isGroundedByVelocity)
 241        {
 0242            rayCache.origin = velocityTargetPosition + rayOffset;
 0243            rayCache.direction = Vector3.down;
 244
 0245            isGroundedByRaycast = Physics.Raycast(rayCache,
 246                RAY_OFFSET_LENGTH - ELEVATION_OFFSET,
 247                DCLCharacterController.i.groundLayers);
 248
 249        }
 250
 0251        blackboard.isGrounded = isGroundedByCharacterController || isGroundedByVelocity || isGroundedByRaycast;
 252
 0253        lastPosition = velocityTargetPosition;
 0254    }
 255
 256    void State_Init(BlackBoard bb)
 257    {
 0258        if (bb.isGrounded)
 259        {
 0260            currentState = State_Ground;
 0261        }
 262        else
 263        {
 0264            currentState = State_Air;
 265        }
 0266    }
 267
 268    void State_Ground(BlackBoard bb)
 269    {
 0270        if (bb.deltaTime <= 0)
 0271            return;
 272
 0273        float movementSpeed = bb.movementSpeed / bb.deltaTime;
 274
 0275        runAnimationState.normalizedSpeed = movementSpeed * bb.runSpeedFactor;
 0276        walkAnimationState.normalizedSpeed = movementSpeed * bb.walkSpeedFactor;
 277
 0278        if (movementSpeed > runMinSpeed)
 279        {
 0280            CrossFadeTo(AvatarAnimation.RUN, runAnimationName, RUN_TRANSITION_TIME);
 0281        }
 0282        else if (movementSpeed > walkMinSpeed)
 283        {
 0284            CrossFadeTo(AvatarAnimation.WALK, walkAnimationName, WALK_TRANSITION_TIME);
 0285        }
 286        else
 287        {
 0288            CrossFadeTo(AvatarAnimation.IDLE, idleAnimationName, IDLE_TRANSITION_TIME);
 289        }
 290
 0291        if (!bb.isGrounded)
 292        {
 0293            currentState = State_Air;
 0294            OnUpdateWithDeltaTime(bb.deltaTime);
 295        }
 0296    }
 297    private void CrossFadeTo(AvatarAnimation avatarAnimation, string animationName, float runTransitionTime, PlayMode pl
 298    {
 0299        if (latestAnimation == avatarAnimation)
 0300            return;
 301
 0302        animation.CrossFade(animationName, runTransitionTime, playMode);
 0303        latestAnimation = avatarAnimation;
 0304    }
 305
 306    void State_Air(BlackBoard bb)
 307    {
 0308        if (bb.verticalSpeed > 0)
 309        {
 0310            CrossFadeTo(AvatarAnimation.JUMP, jumpAnimationName, JUMP_TRANSITION_TIME, PlayMode.StopAll);
 0311        }
 312        else
 313        {
 0314            CrossFadeTo(AvatarAnimation.FALL, fallAnimationName, FALL_TRANSITION_TIME, PlayMode.StopAll);
 315        }
 316
 0317        if (bb.isGrounded)
 318        {
 0319            animation.Blend(jumpAnimationName, 0, AIR_EXIT_TRANSITION_TIME);
 0320            animation.Blend(fallAnimationName, 0, AIR_EXIT_TRANSITION_TIME);
 0321            currentState = State_Ground;
 0322            OnUpdateWithDeltaTime(bb.deltaTime);
 323        }
 0324    }
 325
 326    internal void State_Expression(BlackBoard bb)
 327    {
 0328        var animationInfo = animation[bb.expressionTriggerId];
 0329        latestAnimation = AvatarAnimation.IDLE;
 0330        CrossFadeTo(AvatarAnimation.EMOTE, bb.expressionTriggerId, EXPRESSION_TRANSITION_TIME, PlayMode.StopAll);
 331        bool mustExit;
 332
 333        //Introduced the isMoving variable that is true if there is user input, substituted the old Math.Abs(bb.movement
 0334        if (isOwnPlayer)
 0335            mustExit = DCLCharacterController.i.isMovingByUserInput || animationInfo.length - animationInfo.time < EXPRE
 336        else
 0337            mustExit = Math.Abs(bb.movementSpeed) > 0.07f || animationInfo.length - animationInfo.time < EXPRESSION_TRAN
 338
 0339        if (mustExit)
 340        {
 0341            animation.Blend(bb.expressionTriggerId, 0, EXPRESSION_TRANSITION_TIME);
 0342            bb.expressionTriggerId = null;
 343
 0344            if (!bb.isGrounded)
 0345                currentState = State_Air;
 346            else
 0347                currentState = State_Ground;
 348
 0349            OnUpdateWithDeltaTime(bb.deltaTime);
 0350        }
 351        else
 352        {
 0353            animation.Blend(bb.expressionTriggerId, 1, EXPRESSION_TRANSITION_TIME / 2f);
 354        }
 0355    }
 356
 357    public void SetExpressionValues(string expressionTriggerId, long expressionTriggerTimestamp)
 358    {
 2359        if (animation == null)
 2360            return;
 361
 0362        if (string.IsNullOrEmpty(expressionTriggerId))
 0363            return;
 364
 0365        if (animation.GetClip(expressionTriggerId) == null)
 0366            return;
 367
 0368        var mustTriggerAnimation = !string.IsNullOrEmpty(expressionTriggerId) && blackboard.expressionTriggerTimestamp !
 0369        blackboard.expressionTriggerId = expressionTriggerId;
 0370        blackboard.expressionTriggerTimestamp = expressionTriggerTimestamp;
 371
 0372        if (mustTriggerAnimation)
 373        {
 0374            if (!string.IsNullOrEmpty(expressionTriggerId))
 375            {
 0376                animation.Stop(expressionTriggerId);
 377            }
 378
 0379            currentState = State_Expression;
 0380            OnUpdateWithDeltaTime(Time.deltaTime);
 381        }
 0382    }
 383
 384    public void Reset()
 385    {
 0386        if (animation == null)
 0387            return;
 388
 389        //It will set the animation to the first frame, but due to the nature of the script and its Update. It wont stop
 0390        animation.Stop();
 0391    }
 392
 0393    public void SetIdleFrame() { animation.Play(currentLocomotions.idle.name); }
 394
 4395    public void PlayEmote(string emoteId, long timestamps) { SetExpressionValues(emoteId, timestamps); }
 396
 397    public void EquipEmote(string emoteId, AnimationClip clip)
 398    {
 0399        if (animation == null)
 0400            return;
 401
 0402        if (animation.GetClip(emoteId) != null)
 0403            animation.RemoveClip(emoteId);
 404
 0405        animation.AddClip(clip, emoteId);
 0406    }
 407
 408    public void UnequipEmote(string emoteId)
 409    {
 0410        if (animation == null)
 0411            return;
 412
 0413        if (animation.GetClip(emoteId) == null)
 0414            return;
 415
 0416        animation.RemoveClip(emoteId);
 0417    }
 418
 419    private void InitializeAvatarAudioAndParticleHandlers(Animation createdAnimation)
 420    {
 421        //NOTE(Mordi): Adds handler for animation events, and passes in the audioContainer for the avatar
 0422        AvatarAnimationEventHandler animationEventHandler = createdAnimation.gameObject.GetOrCreateComponent<AvatarAnima
 0423        AudioContainer audioContainer = transform.GetComponentInChildren<AudioContainer>();
 424
 0425        if (audioContainer != null)
 426        {
 0427            animationEventHandler.Init(audioContainer);
 428
 429            //NOTE(Mordi): If this is a remote avatar, pass the animation component so we can keep track of whether it i
 0430            AvatarAudioHandlerRemote audioHandlerRemote = audioContainer.GetComponent<AvatarAudioHandlerRemote>();
 431
 0432            if (audioHandlerRemote != null)
 433            {
 0434                audioHandlerRemote.Init(createdAnimation.gameObject);
 435            }
 436        }
 437
 0438        animEventHandler = animationEventHandler;
 0439    }
 440
 441    private void OnDestroy()
 442    {
 1039443        if (animEventHandler != null)
 0444            Destroy(animEventHandler);
 1039445    }
 446}