< Summary

Class:AvatarAnimatorLegacy
Assembly:AvatarShape
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Avatar/AvatarAnimatorLegacy.cs
Covered lines:18
Uncovered lines:134
Coverable lines:152
Total lines:379
Line coverage:11.8% (18 of 152)
Covered branches:0
Total branches:0

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
AvatarAnimatorLegacy()0%110100%
Start()0%110100%
OnPoolGet()0%330100%
OnPoolRelease()0%12300%
Update()0%110100%
Update(...)0%8.744033.33%
UpdateInterface()0%42600%
State_Init(...)0%6200%
State_Ground(...)0%30500%
State_Air(...)0%12300%
State_Expression(...)0%72800%
SetExpressionValues(...)0%38.97013.33%
Reset()0%6200%
SetIdleFrame()0%2100%
PrepareLocomotionAnims(...)0%12300%
Prepare(...)0%6200%
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;
 7
 8public class AvatarAnimatorLegacy : MonoBehaviour, IPoolLifecycleHandler, IAnimator
 9{
 10    const float IDLE_TRANSITION_TIME = 0.2f;
 11    const float STRAFE_TRANSITION_TIME = 0.25f;
 12    const float RUN_TRANSITION_TIME = 0.15f;
 13    const float WALK_TRANSITION_TIME = 0.15f;
 14    const float JUMP_TRANSITION_TIME = 0.01f;
 15    const float FALL_TRANSITION_TIME = 0.5f;
 16    const float EXPRESSION_TRANSITION_TIME = 0.2f;
 17
 18    const float AIR_EXIT_TRANSITION_TIME = 0.2f;
 19    const float GROUND_BLENDTREE_TRANSITION_TIME = 0.15f;
 20
 21    const float RUN_SPEED_THRESHOLD = 0.05f;
 22    const float WALK_SPEED_THRESHOLD = 0.03f;
 23
 24    const float ELEVATION_OFFSET = 0.6f;
 25    const float RAY_OFFSET_LENGTH = 3.0f;
 26
 27    const float MAX_VELOCITY = 6.25f;
 28
 29    // Time it takes to determine if a character is grounded when vertical velocity is 0
 30    const float FORCE_GROUND_TIME = 0.05f;
 31
 32    // Minimum vertical speed used to consider whether an avatar is on air
 33    const float MIN_VERTICAL_SPEED_AIR = 0.025f;
 34
 35
 36    [System.Serializable]
 37    public class AvatarLocomotion
 38    {
 39        public AnimationClip idle;
 40        public AnimationClip walk;
 41        public AnimationClip run;
 42        public AnimationClip jump;
 43        public AnimationClip fall;
 44    }
 45
 46    [System.Serializable]
 47    public class BlackBoard
 48    {
 49        public float walkSpeedFactor;
 50        public float runSpeedFactor;
 51        public float movementSpeed;
 52        public float verticalSpeed;
 53        public bool isGrounded;
 54        public string expressionTriggerId;
 55        public long expressionTriggerTimestamp;
 56        public float deltaTime;
 57    }
 58
 59    [SerializeField] internal AvatarLocomotion femaleLocomotions;
 60    [SerializeField] internal AvatarLocomotion maleLocomotions;
 61    AvatarLocomotion currentLocomotions;
 62
 63    public new Animation animation;
 64    public BlackBoard blackboard;
 65    public Transform target;
 66
 104067    [SerializeField] float runMinSpeed = 6f;
 104068    [SerializeField] float walkMinSpeed = 0.1f;
 69
 70    internal System.Action<BlackBoard> currentState;
 71
 72    Vector3 lastPosition;
 73    bool isOwnPlayer = false;
 74    private AvatarAnimationEventHandler animEventHandler;
 75
 76    private float lastOnAirTime = 0;
 77
 112278    public void Start() { OnPoolGet(); }
 79
 80    public void OnPoolGet()
 81    {
 56182        if (DCLCharacterController.i != null)
 83        {
 56184            isOwnPlayer = DCLCharacterController.i.transform == transform.parent;
 85
 86            // NOTE: disable MonoBehaviour's update to use DCLCharacterController event instead
 56187            this.enabled = !isOwnPlayer;
 88
 56189            if (isOwnPlayer)
 90            {
 55991                DCLCharacterController.i.OnUpdateFinish += Update;
 92            }
 93        }
 94
 56195        currentState = State_Init;
 56196    }
 97
 98    public void OnPoolRelease()
 99    {
 0100        if (isOwnPlayer && DCLCharacterController.i)
 101        {
 0102            DCLCharacterController.i.OnUpdateFinish -= Update;
 103        }
 0104    }
 105
 4106    void Update() { Update(Time.deltaTime); }
 107
 108    void Update(float deltaTime)
 109    {
 4694110        if (target == null || animation == null)
 4694111            return;
 112
 0113        blackboard.deltaTime = deltaTime;
 0114        UpdateInterface();
 0115        currentState?.Invoke(blackboard);
 0116    }
 117
 118    void UpdateInterface()
 119    {
 0120        Vector3 velocityTargetPosition = target.position;
 0121        Vector3 flattenedVelocity = velocityTargetPosition - lastPosition;
 122
 123        //NOTE(Brian): Vertical speed
 0124        float verticalVelocity = flattenedVelocity.y;
 125
 126        //NOTE(Kinerius): if we have more or less than zero we consider that we are either jumping or falling
 0127        if (Mathf.Abs(verticalVelocity) > MIN_VERTICAL_SPEED_AIR)
 128        {
 0129            lastOnAirTime = Time.time;
 130        }
 131
 0132        blackboard.verticalSpeed = verticalVelocity;
 133
 0134        flattenedVelocity.y = 0;
 135
 0136        if (isOwnPlayer)
 0137            blackboard.movementSpeed = flattenedVelocity.magnitude - DCLCharacterController.i.movingPlatformSpeed;
 138        else
 0139            blackboard.movementSpeed = flattenedVelocity.magnitude;
 140
 0141        Vector3 rayOffset = Vector3.up * RAY_OFFSET_LENGTH;
 142
 143        //NOTE(Kinerius): This check is just for the playing character, it uses a combination of collision flags and ray
 0144        bool isGroundedByCharacterController = isOwnPlayer && DCLCharacterController.i.isGrounded;
 145
 146        //NOTE(Kinerius): This check is for interpolated avatars (the other players) as we dont have a Character Control
 147        //                this check is cheap and fast but not precise
 0148        bool isGroundedByVelocity = !isOwnPlayer && Time.time - lastOnAirTime > FORCE_GROUND_TIME;
 149
 150        //NOTE(Kinerius): This additional check is both for the player and interpolated avatars, we cast an additional r
 0151        bool isGroundedByRaycast = Physics.Raycast(target.transform.position + rayOffset,
 152            Vector3.down,
 153            RAY_OFFSET_LENGTH - ELEVATION_OFFSET,
 154            DCLCharacterController.i.groundLayers);
 155
 0156        blackboard.isGrounded = isGroundedByCharacterController || isGroundedByVelocity || isGroundedByRaycast;
 157#if UNITY_EDITOR
 0158        Debug.DrawRay(target.transform.position + rayOffset, Vector3.down * (RAY_OFFSET_LENGTH - ELEVATION_OFFSET), blac
 159#endif
 160
 0161        lastPosition = velocityTargetPosition;
 0162    }
 163
 164    void State_Init(BlackBoard bb)
 165    {
 0166        if (bb.isGrounded == true)
 167        {
 0168            currentState = State_Ground;
 0169        }
 170        else
 171        {
 0172            currentState = State_Air;
 173        }
 0174    }
 175
 176    void State_Ground(BlackBoard bb)
 177    {
 0178        if (bb.deltaTime <= 0)
 179        {
 0180            Debug.LogError("deltaTime should be > 0", gameObject);
 0181            return;
 182        }
 183
 0184        animation[currentLocomotions.run.name].normalizedSpeed = bb.movementSpeed / bb.deltaTime * bb.runSpeedFactor;
 0185        animation[currentLocomotions.walk.name].normalizedSpeed = bb.movementSpeed / bb.deltaTime * bb.walkSpeedFactor;
 186
 0187        float movementSpeed = bb.movementSpeed / bb.deltaTime;
 188
 0189        if (movementSpeed > runMinSpeed)
 190        {
 0191            animation.CrossFade(currentLocomotions.run.name, RUN_TRANSITION_TIME);
 0192        }
 0193        else if (movementSpeed > walkMinSpeed)
 194        {
 0195            animation.CrossFade(currentLocomotions.walk.name, WALK_TRANSITION_TIME);
 0196        }
 197        else
 198        {
 0199            animation.CrossFade(currentLocomotions.idle.name, IDLE_TRANSITION_TIME);
 200        }
 201
 0202        if (!bb.isGrounded)
 203        {
 0204            currentState = State_Air;
 0205            Update(bb.deltaTime);
 206        }
 0207    }
 208
 209    void State_Air(BlackBoard bb)
 210    {
 0211        if (bb.verticalSpeed > 0)
 212        {
 0213            animation.CrossFade(currentLocomotions.jump.name, JUMP_TRANSITION_TIME, PlayMode.StopAll);
 0214        }
 215        else
 216        {
 0217            animation.CrossFade(currentLocomotions.fall.name, FALL_TRANSITION_TIME, PlayMode.StopAll);
 218        }
 219
 0220        if (bb.isGrounded)
 221        {
 0222            animation.Blend(currentLocomotions.jump.name, 0, AIR_EXIT_TRANSITION_TIME);
 0223            animation.Blend(currentLocomotions.fall.name, 0, AIR_EXIT_TRANSITION_TIME);
 0224            currentState = State_Ground;
 0225            Update(bb.deltaTime);
 226        }
 0227    }
 228
 229    internal void State_Expression(BlackBoard bb)
 230    {
 0231        var animationInfo = animation[bb.expressionTriggerId];
 0232        animation.CrossFade(bb.expressionTriggerId, EXPRESSION_TRANSITION_TIME, PlayMode.StopAll);
 233        bool mustExit;
 234
 235        //Introduced the isMoving variable that is true if there is user input, substituted the old Math.Abs(bb.movement
 0236        if (isOwnPlayer)
 0237            mustExit = DCLCharacterController.i.isMovingByUserInput || animationInfo.length - animationInfo.time < EXPRE
 238        else
 0239            mustExit = Math.Abs(bb.movementSpeed) > 0.07f || animationInfo.length - animationInfo.time < EXPRESSION_TRAN
 240
 0241        if (mustExit)
 242        {
 0243            animation.Blend(bb.expressionTriggerId, 0, EXPRESSION_TRANSITION_TIME);
 0244            bb.expressionTriggerId = null;
 0245            if (!bb.isGrounded)
 0246                currentState = State_Air;
 247            else
 0248                currentState = State_Ground;
 249
 0250            Update(bb.deltaTime);
 0251        }
 252        else
 253        {
 0254            animation.Blend(bb.expressionTriggerId, 1, EXPRESSION_TRANSITION_TIME / 2f);
 255        }
 0256    }
 257
 258    public void SetExpressionValues(string expressionTriggerId, long expressionTriggerTimestamp)
 259    {
 2260        if (animation == null)
 2261            return;
 262
 0263        if (string.IsNullOrEmpty(expressionTriggerId))
 0264            return;
 265
 0266        if (animation.GetClip(expressionTriggerId) == null)
 0267            return;
 268
 0269        var mustTriggerAnimation = !string.IsNullOrEmpty(expressionTriggerId) && blackboard.expressionTriggerTimestamp !
 0270        blackboard.expressionTriggerId = expressionTriggerId;
 0271        blackboard.expressionTriggerTimestamp = expressionTriggerTimestamp;
 272
 0273        if (mustTriggerAnimation)
 274        {
 0275            if (!string.IsNullOrEmpty(expressionTriggerId))
 276            {
 0277                animation.Stop(expressionTriggerId);
 278            }
 0279            currentState = State_Expression;
 0280            Update();
 281        }
 0282    }
 283
 284    public void Reset()
 285    {
 0286        if (animation == null)
 0287            return;
 288
 289        //It will set the animation to the first frame, but due to the nature of the script and its Update. It wont stop
 0290        animation.Stop();
 0291    }
 292
 0293    public void SetIdleFrame() { animation.Play(currentLocomotions.idle.name); }
 294
 295    public void PrepareLocomotionAnims(string bodyshapeId)
 296    {
 0297        if (bodyshapeId.Contains(WearableLiterals.BodyShapes.MALE))
 298        {
 0299            currentLocomotions = maleLocomotions;
 0300        }
 0301        else if (bodyshapeId.Contains(WearableLiterals.BodyShapes.FEMALE))
 302        {
 0303            currentLocomotions = femaleLocomotions;
 304        }
 305
 0306        EquipEmote(currentLocomotions.idle.name, currentLocomotions.idle);
 0307        EquipEmote(currentLocomotions.walk.name, currentLocomotions.walk);
 0308        EquipEmote(currentLocomotions.run.name, currentLocomotions.run);
 0309        EquipEmote(currentLocomotions.jump.name, currentLocomotions.jump);
 0310        EquipEmote(currentLocomotions.fall.name, currentLocomotions.fall);
 0311    }
 312
 313    // AvatarSystem entry points
 314    public bool Prepare(string bodyshapeId, GameObject container)
 315    {
 0316        if (!container.transform.TryFindChildRecursively("Armature", out Transform armature))
 317        {
 0318            Debug.LogError($"Couldn't find Armature for AnimatorLegacy in path: {transform.GetHierarchyPath()}");
 0319            return false;
 320        }
 0321        Transform armatureParent = armature.parent;
 0322        animation = armatureParent.gameObject.GetOrCreateComponent<Animation>();
 0323        armatureParent.gameObject.GetOrCreateComponent<StickerAnimationListener>();
 324
 0325        PrepareLocomotionAnims(bodyshapeId);
 0326        SetIdleFrame();
 0327        animation.Sample();
 0328        InitializeAvatarAudioAndParticleHandlers(animation);
 0329        return true;
 330    }
 331
 4332    public void PlayEmote(string emoteId, long timestamps) { SetExpressionValues(emoteId, timestamps); }
 333
 334    public void EquipEmote(string emoteId, AnimationClip clip)
 335    {
 0336        if (animation == null)
 0337            return;
 338
 0339        if (animation.GetClip(emoteId) != null)
 0340            animation.RemoveClip(emoteId);
 0341        animation.AddClip(clip, emoteId);
 0342    }
 343
 344    public void UnequipEmote(string emoteId)
 345    {
 0346        if (animation == null)
 0347            return;
 348
 0349        if (animation.GetClip(emoteId) == null)
 0350            return;
 0351        animation.RemoveClip(emoteId);
 0352    }
 353
 354    private void InitializeAvatarAudioAndParticleHandlers(Animation createdAnimation)
 355    {
 356        //NOTE(Mordi): Adds handler for animation events, and passes in the audioContainer for the avatar
 0357        AvatarAnimationEventHandler animationEventHandler = createdAnimation.gameObject.GetOrCreateComponent<AvatarAnima
 0358        AudioContainer audioContainer = transform.GetComponentInChildren<AudioContainer>();
 0359        if (audioContainer != null)
 360        {
 0361            animationEventHandler.Init(audioContainer);
 362
 363            //NOTE(Mordi): If this is a remote avatar, pass the animation component so we can keep track of whether it i
 0364            AvatarAudioHandlerRemote audioHandlerRemote = audioContainer.GetComponent<AvatarAudioHandlerRemote>();
 0365            if (audioHandlerRemote != null)
 366            {
 0367                audioHandlerRemote.Init(createdAnimation.gameObject);
 368            }
 369        }
 370
 0371        animEventHandler = animationEventHandler;
 0372    }
 373
 374    private void OnDestroy()
 375    {
 1025376        if (animEventHandler != null)
 0377            Destroy(animEventHandler);
 1025378    }
 379}