Controlling Avatar Animations

# About the Course

Let's look at three ways to control avatar animations. In this course, we'll try to control the avatar animation with the function you want.

# Control with ActionStateChangedEvent

An avatar consists of a Body and Face within AvatarRoot. Body is the avatar's torso and Face is its face. You can set what animation to play in the avatar's Body and Face with [ActionStateChangedEvent](/apiReference/Events/ActionStateChangedEvent).

  * Face controls the avatar's face animation, the facial expression.
  * Body controls the avatar's torso action and parts action.

  
Let's take a look at the properties of **ActionStateChangedEvent**. 

[{"Property":"CoreActionName","Description":"The name of the avatar's torso action."},{"Property":"PartsActionName","Description":"The name of each part action. Actions of avatar's equipped items except for face accessories."},{"Property":"PlayRate","Description":"It's the play speed."},{"Property":"PlayType","Description":"It's the play type."},{"Property":"StartFrameIndex","Description":"It's the starting frame of the animation."},{"Property":"EndFrameIndex","Description":"It's the ending frame of the animation."}]

## ActionName

ActionStateChangedEvent determines which animation to play with ActionName. Below is the list of ActionName.

[{"ActionName1":"stand1","ActionName2":"swingO1","ActionName3":"stabO1","ActionName4":"shoot1"},{"ActionName1":"stand2","ActionName2":"swingO2","ActionName3":"stabO2","ActionName4":"shoot2"},{"ActionName1":"walk1","ActionName2":"swingO3","ActionName3":"stabOF","ActionName4":"shootF"},{"ActionName1":"walk2","ActionName2":"swingOF","ActionName3":"stabT1","ActionName4":"nan"},{"ActionName1":"alert","ActionName2":"swingT1","ActionName3":"stabT2","ActionName4":"nan"},{"ActionName1":"prone","ActionName2":"swingT2","ActionName3":"stabTF","ActionName4":"nan"},{"ActionName1":"proneStab","ActionName2":"swingT3","ActionName3":"nan","ActionName4":"nan"},{"ActionName1":"jump","ActionName2":"swingTF","ActionName3":"nan","ActionName4":"nan"},{"ActionName1":"fly","ActionName2":"swingP1","ActionName3":"nan","ActionName4":"nan"},{"ActionName1":"sit","ActionName2":"swingP2","ActionName3":"nan","ActionName4":"nan"},{"ActionName1":"ladder","ActionName2":"swingPF","ActionName3":"nan","ActionName4":"nan"},{"ActionName1":"rope","ActionName2":"nan","ActionName3":"nan","ActionName4":"nan"},{"ActionName1":"dead","ActionName2":"nan","ActionName3":"nan","ActionName4":"nan"},{"ActionName1":"heal","ActionName2":"nan","ActionName3":"nan","ActionName4":"nan"},{"ActionName1":"blink","ActionName2":"nan","ActionName3":"nan","ActionName4":"nan"}]

You can preview the animations of each action in AvatarItem Editor Maker as follows.

  1. Download Avatar_Cap_A1.psd in the [Open Back Cap](/docs/?postId=684) guide. 

> Tip. You can use any example resource file (psd) under the sub-documents in the [Creating Avatar Item](/ko/docs/?postId=582) guide. You don't have to use Avatar_Cap_A1.psd.

  2. Click Window - AvatarItem Editor in the navigation menu of the Maker.   

  3. Select the downloaded Avatar_Cap_A1.psd after clicking the [Select PSD File] button.   

  4. After a while, the psd file is applied. Choose the desired name in the ActionName list. You can check the selected animation. [gif](16667653519861a35fd34874248b9a83f0b983525f3bc)



You can utilize ActionName with CoreActionName and PartsActionName. As described above, CoreActionName is avatar's bare body action and PartsActionName are the actions of avatar's equipped items except for face accessories. If you look at the avatar's movements with different settings of CoreActionName and PartsActionName it's easy to understand. For example, look at the image below. The avatar's torso is walking as the CoreActionName is set to walk1, but the clothes and shoes stand still as the PartsActionName is set to stand1. [gif](1666850718269278086006fbe4b198a9c492c952bd8c4)

In general, you should set CoreActionName and PartsActionName identically because it causes unnatural motion if they're different. Of course, sometimes they have to be set differently to appear natural, like the Death action, where you set the CoreActionName to dead and the PartsActionName to stand1. The creator can set CoreActionName and PartsActionName according to their own intentions.

## PlayRate

Control the play speed of the animation. The bigger the number is, the faster the play speed.

## PlayType

Set how to play the animation. PlayType includes OneTime, Loop, and ZigzagLoop.

[{"SpriteAnimClipPlayType":"OneTime","Description":"Play once."},{"SpriteAnimClipPlayType":"Loop","Description":"Play repeatedly in order."},{"SpriteAnimClipPlayType":"ZigzagLoop","Description":"Play repeatedly in order - in reverse order."}]

## Start/End Frame

You can check the total frames of each animation in AvatarItem Editor.

[png](16667657292657df3e93d54f8437cb7a709131c0ab531)

The creator can set the start frame (StartFrameIndex) and the end frame (EndFrameIndex) of the desired animation to play after checking the total frames. Note the following when setting frames.

  * The frame must be set to a positive number.
  * The start frame must be less than or equal to the end frame.
  * The end frame must be less than or equal to the total animation frame.
  * You can correct the frame value if it's incorrect.
  * The frame value is set with the default value if not entered.
  * The normal output of animations through the input of CoreActionName(torso action) and PartsActionName(parts action).   




## Example

You should send an event to the GetBodyEntity of the AvatarRendererComponent because ActionStateChangedEvent occurs through the avatar's body. The default settings are as follows.
    
    
    @Component
    script WalkActionInitializer extends Component
        @ExecSpace("ServerOnly")
        method void OnBeginPlay()
            local body = self.Entity.AvatarRendererComponent:GetBodyEntity()
            local event = ActionStateChangedEvent("walk1", "walk1", 1, SpriteAnimClipPlayType.Loop, 1, 2)
            body:SendEvent(event)
        end
    end
    

  
Some properties operate normally even when omitted. However, you must input **CoreActionName** and **PartsActionName**. ```lua @Component script InitialActionSetter extends Component @ExecSpace("ServerOnly") method void OnBeginPlay() local body = self.Entity.AvatarRendererComponent:GetBodyEntity() local event = ActionStateChangedEvent("walk1", "walk1") \-- You can omit the playback speed, playback type, start frame, and end frame. \-- It's the same meaning as ActionStateChangedEvent("walk1", "walk1", 1, SpriteAnimClipPlayType.Loop, 0, End Frame). body:SendEvent(event) end end ```   
If you enter the wrong frame value, it's automatically corrected. ```lua @Component script AvatarAnimationStateInitializer extends Component @ExecSpace("ServerOnly") method void OnBeginPlay() local body = self.Entity.AvatarRendererComponent:GetBodyEntity() local event = ActionStateChangedEvent("walk1", "walk1", 1, SpriteAnimClipPlayType.Loop, -1, 4) \-- The frame value should be a positive number. If you enter a negative number as startFrameIndex above, it's corrected to "0". \-- When endFrame is "3", if entering "4" for endFrameIndex, it would be corrected to "3". \-- It would be finally corrected to ActionStateChangedEvent("walk1", "walk1", 1, SpriteAnimClipPlayType.Loop, 0, 3). body:SendEvent(event) end end ```   
You can fine-tune multiple actions like the different modification between torso and parts animations respectively using **ActionStateChangedEvent**. However, there is also the hassle of having to specify an action each time. If you set frequently used animations like 'Stand' or 'Walk' like this every time, you will increase the amount of unnecessary repetitive code. Also, you need different animations for when your avatar is holding a one-handed weapon versus a two-handed weapon. You can get rid of these inconveniences by using **BodyActionStateChangeEvent**. 

# Control with BodyActionStateChangeEvent

[BodyActionStateChangeEvent](/apiReference/Events/BodyActionStateChangeEvent) is the event to literally change the bodyActionState. This event is convenient because you don't need to specify an animation for each weapon type separately. BodyActionStateChangeEvent controls animations with MapleAvatarBodyActionState. MapleAvatarBodyActionState predefines which torso action and parts action to use for each state and what the play speed and the play type are. It's easy to understand if you think of it as the animation presets used frequently are defined.   
MapleAvatarBodyActionState matches ActionStateChangedEvent as shown in the table below.

[{"MapleAvatarBodyActionState":"Stand","CoreActionName":"stand1","PartsActionName":"stand1","PlayRate":"1","PlayType":"ZigzagLoop"},{"MapleAvatarBodyActionState":"Stand","CoreActionName":"stand2","PartsActionName":"stand2","PlayRate":"1","PlayType":"ZigzagLoop"},{"MapleAvatarBodyActionState":"Walk","CoreActionName":"walk1","PartsActionName":"walk1","PlayRate":"1","PlayType":"Loop"},{"MapleAvatarBodyActionState":"Walk","CoreActionName":"walk2","PartsActionName":"walk2","PlayRate":"1","PlayType":"Loop"},{"MapleAvatarBodyActionState":"Attack","CoreActionName":"alert","PartsActionName":"alert","PlayRate":"1","PlayType":"Loop"},{"MapleAvatarBodyActionState":"Crouch","CoreActionName":"prone","PartsActionName":"prone","PlayRate":"1","PlayType":"Loop"},{"MapleAvatarBodyActionState":"Fall","CoreActionName":"jump","PartsActionName":"jump","PlayRate":"1","PlayType":"Loop"},{"MapleAvatarBodyActionState":"Sit","CoreActionName":"sit","PartsActionName":"sit","PlayRate":"1","PlayType":"Loop"},{"MapleAvatarBodyActionState":"Rope","CoreActionName":"rope","PartsActionName":"rope","PlayRate":"1","PlayType":"Loop"},{"MapleAvatarBodyActionState":"Ladder","CoreActionName":"ladder","PartsActionName":"ladder","PlayRate":"1","PlayType":"Loop"},{"MapleAvatarBodyActionState":"Dead","CoreActionName":"dead","PartsActionName":"stand1","PlayRate":"1","PlayType":"Loop"},{"MapleAvatarBodyActionState":"Blink","CoreActionName":"blink","PartsActionName":"blink","PlayRate":"1","PlayType":"Loop"},{"MapleAvatarBodyActionState":"Fly","CoreActionName":"fly","PartsActionName":"fly","PlayRate":"1","PlayType":"Loop"},{"MapleAvatarBodyActionState":"Hit","CoreActionName":"alert","PartsActionName":"alert","PlayRate":"1","PlayType":"ZigzagLoop"},{"MapleAvatarBodyActionState":"Alert","CoreActionName":"alert","PartsActionName":"alert","PlayRate":"1","PlayType":"ZigzagLoop"},{"MapleAvatarBodyActionState":"Heal","CoreActionName":"heal","PartsActionName":"heal","PlayRate":"1","PlayType":"Loop"},{"MapleAvatarBodyActionState":"Invalid","CoreActionName":"-","PartsActionName":"-","PlayRate":"-","PlayType":"-"}]

## Example

Let's look at an example where BodyActionStateChangeEvent calls the Fly state with MapleAvatarBodyActionState.
    
    
    @Component
    script BeginFlightAnimation extends Component
        @ExecSpace("ServerOnly")
        method void OnBeginPlay()
            local event = BodyActionStateChangeEvent()
            event.ActionState = MapleAvatarBodyActionState.Fly
            event.needResetAction = true
            event.startFrameIndex = 1
            event.endFrameIndex = 2
            self.Entity:SendEvent(event)
        end
    end
    

The event finally calls ActionStateChangedEvent as seen in the matching table above.
    
    
    @Component
    script InitialFlightState extends Component
        @ExecSpace("ServerOnly")
        method void OnBeginPlay()
            ActionStateChangedEvent("fly", "fly", 1, SpriteAnimClipPlayType.Loop, 1, 2)
        end
    end
    

Even if the wrong Start/End frame is entered in BodyActionStateChangeEvent, it can be corrected in ActionStateChangedEvent.

# Control with ActionSheet and StateComponent

It can also be cumbersome to change the animation by sending an event every time the state of a predefined or newly defined avatar changes. It would be very convenient if the animation would also change whenever the avatar state changes without sending an event. You can easily change animations by changing your avatar state at any time you want with [StateComponent](/apiReference/Components/StateComponent) after setting up an animation to be connected to the avatar state in ActionSheet of [AvatarStateAnimationComponent](/apiReference/Components/AvatarStateAnimationComponent).

The default ActionSheet provided by AvatarStateAnimationComponent is as follows. [png](1666838653260b4a969d3d22a438990ede8add75f3edb)

[{"Key":"IDLE","Value":"stand"},{"Key":"MOVE","Value":"walk"},{"Key":"ATTACK","Value":"attack"},{"Key":"HIT","Value":"hit"},{"Key":"CROUCH","Value":"crouch"},{"Key":"FALL","Value":"fall"},{"Key":"JUMP","Value":"fall"},{"Key":"CLIMB","Value":"rope"},{"Key":"LADDER","Value":"ladder"},{"Key":"DEAD","Value":"dead"},{"Key":"SIT","Value":"sit"}]

ActionSheetIn ,

  * Key means State.
  * Value means MapleAvatarBodyActionState. 

[{"ActionSheet Value":"stand","MapleAvatarBodyActionState":"Stand"},{"ActionSheet Value":"walk","MapleAvatarBodyActionState":"Walk"},{"ActionSheet Value":"attack","MapleAvatarBodyActionState":"Attack"},{"ActionSheet Value":"crouch","MapleAvatarBodyActionState":"Crouch"},{"ActionSheet Value":"fall","MapleAvatarBodyActionState":"Fall"},{"ActionSheet Value":"sit","MapleAvatarBodyActionState":"Sit"},{"ActionSheet Value":"rope","MapleAvatarBodyActionState":"Rope"},{"ActionSheet Value":"ladder","MapleAvatarBodyActionState":"Ladder"},{"ActionSheet Value":"dead","MapleAvatarBodyActionState":"Dead"},{"ActionSheet Value":"blink","MapleAvatarBodyActionState":"Blink"},{"ActionSheet Value":"fly","MapleAvatarBodyActionState":"Fly"},{"ActionSheet Value":"hit","MapleAvatarBodyActionState":"Hit"},{"ActionSheet Value":"alert","MapleAvatarBodyActionState":"Alert"}]

  
If an avatar reaches a specific **State**, it finds the **MapleAvatarBodyActionState** that matches the **Value** of the **State** in the **ActionSheet**. As you saw above, **MapleAvatarBodyActionState** predefines which torso and parts animation to play. Therefore, the avatar's animation will change according to the **State** changed with **StateComponent**.   
As such, **ActionSheet** conveniently and easily changes animations along with the state. For example, if you want to play the **walk** animation in the **IDLE** state, you just change the value of **Value** to **walk**. It's also easy for creators to add new states manually. 



> Learn more Refer to the [Easy Control of Avatar Animation with ActionSheet](/docs/?postId=545) guide to easily change animations using ActionSheet and StateComponent.

  
Changing animations with **ActionSheet** and **StateComponent** is easy, but it also has its drawbacks. * You cannot select the start and end frames of the animation. * If there is a defined default/user state change condition, the state can be changed automatically by this condition. Therefore, the state may change immediately before the desired animation is output. 

## Summary

A summary of the changing animation process is as follows. [png](1666767739753193f598641f64746982ab39b9808c9b4)

The table below summarizes the pros and cons of the three methods for controlling avatar's animation as seen above. Each method has clear pros and cons, so a creator just needs to choose the method that matches their intent.

[{"Controlling Avatar Animation":"ActionStateChangedEvent","Pros":"You can adjust the body and parts animations separately. You can set the play speed, play type, and animation start\/end frame in detail.","Cons":"It's cumbersome to set up frequently used actions like this every time.You need to specify a separate animation depending on the weapon."},{"Controlling Avatar Animation":"BodyActionStateChangeEvent","Pros":"You don't need to specify an animation depending on the weapon.You can set the start\/end frame of animations.","Cons":"You cannot adjust the body and parts animations separately. You cannot change state to undefined in MapleAvatarBodyActionState."},{"Controlling Avatar Animation":"ActionSheet and StateComponent","Pros":"You can simply change the animation for each avatar state in the ActionSheet of the property editor. You can easily add your avatar state.If you change the State at the desired timing, the animation changes accordingly with StateComponent.","Cons":"You cannot set the start\/end frame of animations.If the state is automatically changed by another state change condition, the desired animation may not be output."}]

# Animation Frame Event

Let's look at three events to tell the frame information of the playing animation. Let's check out an example to change to another animation with a specific frame using obtained frame information.

[{"Event":"[SpriteAnimPlayerChangeFrameEvent](\/apiReference\/Events\/SpriteAnimPlayerChangeFrameEvent)","Description":"It occurs when changing the sprite animation's frame."},{"Event":"[SpriteAnimPlayerStartFrameEvent](\/apiReference\/Events\/SpriteAnimPlayerStartFrameEvent)","Description":"It occurs when playing the first frame of sprite animation."},{"Event":"[SpriteAnimPlayerEndFrameEvent](\/apiReference\/Events\/SpriteAnimPlayerEndFrameEvent)","Description":"It occurs when playing the last frame of sprite animation."}]

Each event sends the frame number (FrameIndex), whether it's reverse playing (ReversePlaying), and total frame count (TotalFrameCount). The event is normally operated when connecting to avatar's Body or Face with ConnectEvent.

## Example

Let's see how to use FrameEvent in the following example code.

  1. Suppose you want to change to another animation when playing the second frame of an avatar's animation. If so, you can utilize SpriteAnimPlayerChangeFrameEvent. Add the `OnBeginPlay` function and connect the event to Body as described above. In case of cancellation of subscription, the handler of the connected event should have it as a property. 
         
         @Component
         script SpriteFrameEventHandler extends Component
         property any eventHandler = nil
         
         @ExecSpace("ClientOnly")
         method void OnBeginPlay()
                 local body = self.Entity.AvatarRendererComponent:GetBodyEntity()
         --         self.eventHandler = body:ConnectEvent("SpriteAnimPlayerChangeFrameEvent", self.ChangeFrameEvent)  -- [Commented: MLUA requires handler pattern]
                 -- Connect SpriteAnimPlayerChangeFrameEvent to the body. 
                 -- If SpriteAnimPlayerChangeFrameEvent occurs, call the ChangeFrameEvent function.
         end
         end
         



    
    
        Now, call the `ChangeFrameEvent` function when **SpriteAnimPlayerChangeFrameEvent** occurs.
          
    
    2. Write the `ChangeFrameEvent` function. 
    Set the **swingOF** animation to **Play 2-3 frames repeatedly** with a play speed **0.5** through **ActionStateChangedEvent** when changing the animation frame to 2.
        ```lua
    @Component
    script FrameActionTrigger extends Component
        @ExecSpace("ClientOnly")
        method void ChangeFrameEvent(any event)
        if event.FrameIndex == 2 then
            local body = self.Entity.AvatarRendererComponent:GetBodyEntity()
            local actionEvent = ActionStateChangedEvent("swingOF", "swingOF", 0.5, SpriteAnimClipPlayType.Loop, 2,3)
            body:SendEvent(actionEvent)
        end
        end
    end
    
    
    
    The code above works normally until the animation plays. However, since we are still subscribing to **SpriteAnimPlayerChangeFrameEvent**, as soon as the animation starts, the process will repeat again sending a new **ActionStateChangedEvent**. You will need to unsubscribe from **SpriteAnimPlayerChangeFrameEvent** as this will not give you the desired result.
      
    
    

  3. Unsubscribe from SpriteAnimPlayerChangeFrameEvent via the saved handler earlier. Let's modify the `ChangeFrameEvent` function. 
         
         @Component
         script FrameActionTrigger extends Component
         @ExecSpace("ClientOnly")
         method void ChangeFrameEvent(any event)
                 if event.FrameIndex == 2 then
                     local body = self.Entity.AvatarRendererComponent:GetBodyEntity()
         --             body:DisconnectEvent("SpriteAnimPlayerChangeFrameEvent", self.eventHandler)   -- [Commented: Use handler pattern instead]
                     -- Unsubscribe from SpriteAnimPlayerChangeFrameEvent
                     local actionEvent = ActionStateChangedEvent("swingOF", "swingOF", 0.5, SpriteAnimClipPlayType.Loop, 2,3)
                     body:SendEvent(actionEvent)
                 end
         end
         end
         



    
    
        Now after play, when the second frame of the first animation plays, it changes to the **swingOF** animation.
      
    
    **SpriteAnimPlayerStartFrameEvent** and **SpriteAnimPlayerEndFrameEvent** are the same. Let's create the desired animation using events according to the creator's implementation intention.
    
    > **Tip.**
    > The avatar animation is played through the client, so you need to control it with the client.