Tutorial 5 - Idle Turns

Tutorial 5 - Idle Turn Setup

Elements Involved

  1. Animations and LMG for idle rotations.
  2. The code in the player.cpp will determine when the character should turn and send this as input to the graph.
  3. The Animation Graph setup to trigger the idle turns

Creating Idle Turn Animations and the LMG

Assets
The Idle Turn LMG needs three assets:

  • idle animation
  • left turn asset
  • right turn asset

Rules

  • The Turn Assets must have locator rotation, a good guideline is 90 degrees, but any angle is fine.
  • The Turn Assets must not have any tranlation on the locator however, only rotation.
  • The Idle Asset should not have any locator movement at all.
  • The Idle Asset has to be long enough, use between 30 and 40 frames as a guideline. The reason for this is that the code blends the turn asset with the idle to achieve the correct target angle. If the idle asset is too short, the animation will look sped up. Use the LMG Editor to preview and test whether you like the results.

LMG

  • The LMG Blend Code is IROT and the Caps Code IROT. You can set this up manually with the Locomotion Group Editor.
  • You cannot preview Idle Turn LMGs in the Character Editor, you will need to use the LMG Editor for this. This can also help you test whether your idle animation is too long, too short, or just right.
  • If you need an example lmg file, take a look at the SDK character's lmg file, it called "relaxed_IdleStepRotate_nw.lmg" and can be found inside Game\Animations\human_male\lmg_files.

Setting up Idle Turns in the Graph

Step 1: Creating DesiredTurnSpeed Input

  • Add a new Input (Menu Graph -> Add Input) to the graph and call it "DesiredTurnSpeed".

The Input should be of the type int and range from -1 to 1. The default value is 0, and the priority is 150 for the SDK graph. You can choose a different and maybe lower priority if you have your own custom graph or do not use IdleBreaks or "any value" state parameterizations. Below is a picture of the completely set up input.

Step 2: Creating Idle Turn State

  • Next create a new state through the graph menu. Place it in a view where your Idle Hub state is also placed.
    The new Idle Rotation state needs to have a link coming in from the idle hub, and a force-follow link going out. This is needed because the idle rotation lmg is not a looping lmg - it does exactly one step. The state must then be left and re-entered (in case there is still an angle deviation that needs to be covered).
  • A force-follow link can be created by holding ALT while dragging the link. Or right-click on the little nub on the link and select Edit in the popup menu and manually set a force-follow chance in the properties of the link.

The node details of the state are very similar to one of a oneshot or transition. It is important that the checkbox allowing the animation to be restarted is set active - this will allow the character to do multiple steps if he needs to (by re-entering the state).

  • Assign an animation to the state.
    Below is a picture of the node details of the idle turn state:

Step 3: Setting up Selection Criteria

The Selection Criteria of the Idle Turn state are very similar to the ones of the regular idle. You can set this up quickly by either creating this state as a clone from one of the idle states, or set them as a parent state. The only difference is the DesiredTurnSpeed, which must be set to a non-zero value. For zero values, the regular idle states should be selected.

  • To make this happen, add a parameterization "Dir" to the state and add the params "Left" and "Right".
  • Set the Selection Criteria to 1 and -1 for these two (it doesn't really matter which is which, unless you want to set up different animations for the two directions.

This Selection Criteria must be matched by the idle state(s) in your graph.

  • Set all other idle states up so that they only trigger when the DesiredTurnSpeed is exactly zero.

Step 4: Setting Movement Control Methods

Lastly the Movement Control Methods of all idle states need to be changed to even allow the animation and entity angles to deviate from another at all.

  • This is done by setting the movement control methods on all of these states to DecoupledCatchup for the horizontal movement - but then only allowing the angle to deviate, and not the position.
  • This is done in the Movement & Physics tab in the Node Details of each of these states.
  • Select "DecoupledCatchup" from the Dropdown box, and change the angle that is allowed to deviate to something around 90 degree.
  • You can leave the position deviation untouched or set it to something really small, like 0.01.

The default position deviation of -1 will use a hardcoded default value of 0.5 or in a debug build it will use the console variable value of: ac_animErrorMaxDistance.

Below is a picture on how the setup on each idle state and the idle turn state should look:

Code (optional)

You can modify the angle at which the idle turn is triggered here, or integrate the code, if you have your own player implementation.

Change TRIGGER_TURN_ANGLE if you want the idle turns to be triggered sooner or later.
The code to calculate whether or not an idle turn is needed can be found in Player.cpp inside the GameDLL:

if ((frameTime != 0.0f) && (m_inputDesiredTurnSpeed != IAnimationGraph::InputID(-1)))
{
  Quat offset = m_pAnimatedCharacter->GetAnimLocation().q.GetInverted() * pEnt->GetRotation();
  float angle = RAD2DEG(offset.GetRotZ());
  float angleMag = cry_fabsf(angle);

  const float TRIGGER_TURN_ANGLE = 35.0f;
  const float STOP_TURN_ANGLE = 0.05f;
  if ( angleMag > TRIGGER_TURN_ANGLE)
  {
    m_lastReqTurnSpeed = (float) __fsel(angle, 1.0f, -1.0f);
  }
  else if ( angleMag > STOP_TURN_ANGLE)
  {
    // Check to see if we have reached our target (when the turn speed is in the opposite direction to the angle diff)
    m_lastReqTurnSpeed = (float) __fsel(m_lastReqTurnSpeed*angle, m_lastReqTurnSpeed, 0.0f);
  }
  else
  {
    m_lastReqTurnSpeed = 0.0f;
  }
  GetAnimationGraphState()->SetInput(m_inputDesiredTurnSpeed, m_lastReqTurnSpeed);
}