Generic filters
Exact matches only
Search in title
Filter by Custom Post Type
Search in project

Triggering Animations and States

There are two types of animations that can be played, short term ones such as waving or pointing, and long term ones (known as states) that exist until they are canceled. These two types are quite different in how they are handled by Atavism, and to add more complexity, there are two different animation systems Unity uses as well. If you haven’t already, it is recommended you read up on Working with Animations.

To simplify this guide, I will cover the triggering of the animation first for both short term and long term animations, then cover how to actually play the animation using both Legacy and Mecanim animation systems.

 

Triggering the Animation

An example copy of a script is available in Atavism project in Assets/Scripts directory. Script is attached to the Scripts.prefab in the AtavismObjects folder.

Looking at that file you will see the Update() function some input checks. These will run one of the functions when the matching key is pressed.

 

Short Term

Short term animations are done via the Coordinated Effect system. This is because they do not need to be saved to the character, they only need to be shown to other players nearby while the animation plays. In the example script there is the following function:

  public void PlayWave() {
	Dictionary<string, object> props = new Dictionary<string, object> ();
	props.Add ("coordEffect", "PlayWaveAnimation"); // Put name of Coord Effect Prefab to play here
	props.Add ("hasTarget", false);
	NetworkAPI.SendExtensionMessage (ClientAPI.GetPlayerOid(), false, "ao.PLAY_COORD_EFFECT", props);
    }

The code inside the function sends a command to the server telling it to play a coordinated effect with the name “PlayWaveAnimation”. The server will then send down a message to all players near the player who sent the command telling the client to play the specified Coordinated Effect. To play a different animation, just create a new Coordinated Effect and create a copy of that example function, but changing the name of the “coordEffect” property.

To create a new Coordinated Effect, go into the Resources/Content/CoordinatedEffects folder and duplicate one of the existing prefabs. I recommend duplicating the PlayWaveAnimation prefab as it is already set up with the correct component, all that needs done is changing the Animation Name and Length (and maybe delete the other components on there so it only has the Coord Animation one left).

 

Long Term

Long term animations are saved as properties on a player so that other players who log in later or walk up to this player will see the animation still being played. It can be useful to think of these as a state, such as the state of sitting down or lying down.

For this tutorial, I have decided to create a new player property called “currentAnim“. In the example file, there is the PlayLieDown function which will toggle between setting this to “lie” and “null” (as in no state). This is the function here:

  public void PlayLieDown() {
	// First check if the player is already lying down
	bool playerLyingDown = false;
	if (ClientAPI.GetPlayerObject().PropertyExists("currentAnim")) {
		if ((string)ClientAPI.GetPlayerObject().GetProperty("currentAnim") == "lie")
			playerLyingDown = true;
	}
	
	if (playerLyingDown) {
		// Player is lying down so reset anim back to normal
		NetworkAPI.SendTargetedCommand (ClientAPI.GetPlayerOid(), "/setStringProperty currentAnim null");
	} else {
		// Player is not lying down so set anim to lie
		NetworkAPI.SendTargetedCommand (ClientAPI.GetPlayerOid(), "/setStringProperty currentAnim lie");
	}
    }

To do this for other animations, the bare minimum is to replace the two instances of the world “lie” with whatever other animation state you want to use.

Now the other tricky part to this is to handle state interruptions. That is to say, what happens if the player moves while lying down? Should it automatically clear the state back to null? This will be covered in the sections below.

 

Playing the Animations

Legacy

The Legacy system is much harder to add new animations to as it requires code changes to make it work. When selecting a mob prefab that uses the “Atavism Legacy Animation Mob Controller” you will see a list of animation properties such as “Idle Animation” and “Walk Animation”. That mob controller file will need to be edited to include new properties for your new animations, then some code added to work out when those animations should be played.

Open up the AtavismLegacyAnimationMobController3D.cs file and near the top of the file add new lines for your new animations after line 15:

  public AnimationClip mountAnimation;

for example, for the new Wave and Lie animations you would put:

  public AnimationClip waveAnimation;
  public AnimationClip lieAnimation;

From here the code will be a little bit different based on short term or long term animations.

 
Short Term

To get short term animations to play the PlayAnimation() function needs a few lines to convert an animation name from the Coordinated Effect to the AnimationClip added just above. In the PlayAnimation() function you will see these lines:

  if (animationName == "attack_normal" || animationName == "Attack") {
      overrideAnimation = unarmedAttackedAnimation;
  }

To add a new animation, such as the wave one from above, add in the following code string after it:

  else if (animationName == "Waving") {
      overrideAnimation = waveAnimation;
  }

My function now looks like this:

I got the “Waving” from the Animation Name property on the PlayWaveAnimation Coordinated Effect shown above. The “waveAnimation” is from the new variable added just a few lines earlier in this page.

 
Long Term

As we are using a new player property (called “currentAnim“) a new property handler will need to be added to detect when that property has changed. There are plenty of examples of property handlers littered throughout the Atavism code, so I stole one from the MobController3D.cs and modified it to pick up our animation. The following code needs to be added…

First create a new function after the PlayAnimation function. Here’s my example:

  public void HandleCurrentAnim (object sender, PropertyChangeEventArgs args)
    {
	// Get the value of the currentAnim property
	string currentAnim = (string)AtavismClient.Instance.WorldManager.GetObjectNode(oid).GetProperty("currentAnim");
	// Check if the value is "null", if so, set the overrideAnimationExpires time to -1 to reset it
	if (currentAnim == "null") {
		overrideAnimationExpires = -1;
	} else {
		// Otherwise call the PlayAnimation function with a very large amount of time
		PlayAnimation(currentAnim, float.MaxValue);
	}
    }

Note you will need to follow the steps in the “Short Term” section just above to convert the animation name to an Animation object.

Finally, this property handler function needs to be registered so it will run when the currentAnim property changes. This can be done by adding a few lines of code to the ObjectNodeReady() function.

  GetComponent<AtavismNode>().RegisterObjectPropertyChangeHandler("currentAnim", HandleCurrentAnim);
  // If the currentAnim property already exists, run the handler now
  if (GetComponent<AtavismNode>().PropertyExists("currentAnim")) {
      HandleCurrentAnim(null, null);
  }
  

So it will now look like:

Note that you will need to now add in your AnimationClips to the Animation and AtavismLegacyMobController3D components on your mob prefab.

 
Clearing CurrentAnim when Moving

At the end of the Update() function you can do a check to see if the players currentAnim is not set to null and if they are also moving. If those two requirements are met, you can set the currentAnim back to null to cancel it. Here’s what I wrote up:

  if (isPlayer && controller.velocity.sqrMagnitude > 0.1 && ClientAPI.GetPlayerObject().PropertyExists("currentAnim")) {
      if ((string)ClientAPI.GetPlayerObject().GetProperty("currentAnim") != "null")
          NetworkAPI.SendTargetedCommand (ClientAPI.GetPlayerOid(), "/setStringProperty currentAnim null");
    }

To help you place it in the right spot, here is how mine looks: