Extending Movements - A Worked Example

This article walks you through process of extending movements within the context of a sample problem.

 

Problem Statement

Create an endless runner style movement which switches direction when you hit a wall.

 

Stage 1 - Analysis

The first step of any implementation is to break down the problem, and think about potential solutions.

The behaviour being described is already quite similar to existing movements, the main difference being that we are taking away the option to change direction and instead replacing it with behaviour that applies only to the facing direction.

With that in mind and our task can be broken in to 3 steps:

  1. Extend digital ground movement so that it only runs in facing direction.
  2. Extend digital air movement so that only jumps in facing direction.
  3. Add some mechanism to switch facing direction when we hit a wall.

 

Stage 2 - Basic Implementation

Setup

To get started quickly lets take one of the existing samples to use as a basis. This enables us to focus on the code and not spend time setting up the scene, animations, etc.

It seems like the commandBro sample should suit this example, so do the following:

  1. Duplicate SampleScene-CommandBro
  2. Rename it Scene-CommandBroAsBounceRunner
  3. Open your new scene
  4. Find the Enemies object in the scene and disable it (lets sort out the movement before we add enemies).

 

 

Extending the digital ground movement

  1. Create a script called GroundMovement_DigitalInFacingDirection
  2. Edit the script and do the following:
    • Extend the GroundMovement_Digital class
    • Add the Info constants (these are used by the inspector when you click debug, and also required if we want to make our movement pluggable.

 


using UnityEngine
using System.Collections;
using PlatformerPro;

namespace PlatformerPro {

public class GroundMovement_DigitalInFacingDirection : GroundMovement_Digital
{
    /// <summary>
    /// Human readable name.
    /// </summary>
    private const string Name = "Digital/Run In Facing Direction";
    
    /// <summary>
    /// Human readable description.
    /// </summary>
    private const string Description = "Ground movement which runs only in the facing direction.";
    
    /// <summary>
    /// Static movement info used by the editor.
    /// </summary>
    new public static MovementInfo Info
    {
        get
        {
            return new MovementInfo(Name, Description);
        }
    }
}

}

 

We can now:

  1. Remove CommandBros ground movement
  2. Add our new movement

Note: this should be done by directly adding the script to the CommandBro-BaseMovements object,  we haven't yet made our script pluggable

If you set a speed in the box (e.g. 10) you should be able to run around the CommandBro sample as normal

 

Changing Behaviour - Run in Facing Direction

Most movement happens in the DoMove() function. Lets override with our own version of this function which uses the facing direction instead of the Input direction.

Character provides two options for getting facing direction: 

FacingDirection - Current facing direction, can be 0 for non direction.

LastFacedDirection - The last direction we were facing. Can only be 1 or -1.

We want to use the second option:

    override public void DoMove()
    {
        // Set frame speed - if friction is bigger than 2 we will slow the character down.
        float frameSpeed = speed;
        if (character.Friction > 2.0fframeSpeed *= (2.0f / character.Friction );
        // Snap to ground
        if (!character.rotateToSlopesSnapToGround ();
        // If facing right run right
        if (character.LastFacedDirection == 1)
        {
            character.SetVelocityX(character.IsGravityFlipped ? -frameSpeed : frameSpeed);
            character.Translate((character.IsGravityFlipped ? -frameSpeed : frameSpeed) * TimeManager.FrameTime0false);
        }
        // Else run left
        else
        {
            character.SetVelocityX(character.IsGravityFlipped ? frameSpeed : -frameSpeed);
            character.Translate((character.IsGravityFlipped ? frameSpeed : -frameSpeed) * TimeManager.FrameTime0false);
        }
    }

 

If you test at this point your character will move in facing direction but you  can still move the character with the arrow keys and the animation wont play correctly.

 

Overriding Animation and FacingDirection

We need to ensure we play the right animation, and we need to ensure that we don't allow the user to change the facing direction with the arrows keys. To do this we override the properties FacingDirection and AnimationState:

  • We will always return 0 for facing direction which means don't set a facing direction.
  • We will always return AnimationState.WALK for the animation.

 

    override public int FacingDirection
    {
        get { return 0; }
    }

 

override public PlatformerPro.AnimationState AnimationState
    {
        get { return PlatformerPro.AnimationState.WALK; }
    }

 

Bounce on Hit

We can now run in one direction, but we need to be able to reverse direction when we hit something. Instead of returning 0 to FacingDirection lets store our own copy of facing direction:

 

 

    protected int facingDirection;
    
override public int FacingDirection
    {
        get 
        { 
            return facingDirection;
        }
    }

Now lets change this value when we collide with something. In DoMove() we can use the helper method CheckSideCollisions() which returns true if a certain number of side collisions are reached:

 

 

   if (CheckSideCollisions (character1, facingDirection == 1 ? RaycastType.SIDE_RIGHT : RaycastType.SIDE_LEFT))
   {
    facingDirection *= -1;
   }

 

Notice how we only check the right side if we are facing right, and only the left side if facing left.

The last touch is to make sure we get a new non-zero facing direction when we gain control from another movement. We do this using the GainControl() method:

 

    override public void GainControl()
    {
        // Set to zero
        facingDirection = 0;
        // Now get last non-zero direction from the character
        facingDirection = character.LastFacedDirection;
    }

 

Updating the Air Movement

 

The changes for the AirMovement are pretty much the same as those for the ground movement. Note the following differences:

 

  • Instead of overriding DoMove() we override MoveInX(). Because almost all air movements more in both X and Y they have separate methods for each part (DoMove is still there but it just calls the other movements). 
  • There's no need to override the AnimationState. The jump animations will work as they are.
  • We need to override DoJump(). When a movement gains control through a special method such as DoJump() it does not call GainControl(). Our do jump method sets facing direction then calls the base behaviour.

 

Here's the whole class:

 

using UnityEngine;
using System.Collections;
using PlatformerPro;

namespace PlatformerPro {

public class AirMovement_DigitalInFacingDirection : AirMovement_Digital
{
    /// <summary>
    /// Direction we are facing.
    /// </summary>
    protected int facingDirection;

    /// <summary>
    /// Human readable name.
    /// </summary>
    private const string Name = "Digital/Jump In Facing Direction";
    
    /// <summary>
    /// Human readable description.
    /// </summary>
    private const string Description = "Air movement which moves only in the facing direction.";
    
    /// <summary>
    /// Static movement info used by the editor.
    /// </summary>
    new public static MovementInfo Info
    {
        get
        {
            return new MovementInfo(NameDescription);
        }
    }

    
    /// <summary>
    /// Does the X movement.
    /// </summary>
    override protected void MoveInX (float horizontalAxisint horizontalAxisDigitalButtonState runButton)
    {
        // Check side collissions if we find oneswitch facing direction
        if (CheckSideCollisions (character1character.LastFacedDirection == 1 ? RaycastType.SIDE_RIGHT : RaycastType.SIDE_LEFT))
        {
            facingDirection *= -1;
        }

        if (character.LastFacedDirection == 1)
        {
            character.SetVelocityX(character.IsGravityFlipped ? -airSpeed : airSpeed);
            character.Translate((character.IsGravityFlipped ? -airSpeed : airSpeed) * TimeManager.FrameTime0false);
        }
        else
        {
            character.SetVelocityX(character.IsGravityFlipped ? airSpeed : -airSpeed);
            character.Translate((character.IsGravityFlipped ? airSpeed : -airSpeed) * TimeManager.FrameTime0false);
        }
    }

    /// <summary>
    /// Called when the movement gets controlTypically used to do initialisation of velocity and the like.
    /// </summary>
    override public void GainControl()
    {
        // Set to zero
        facingDirection = 0;
        // Now get direction from the character
        facingDirection = character.LastFacedDirection;
        // Make sure we call the base
        base.GainControl ();
    }

    /// <summary>
    /// Do the jump.
    /// </summary>
    override public void DoJump()
    {
        // Set to zero
        facingDirection = 0;
        // Now get direction from the character
        facingDirection = character.LastFacedDirection;
        base.DoJump ();
    }

    /// <summary>
    /// Returns the direction the character is facing0 for none1 for right, -1 for left.
    /// This overriden version always returns the input direction.
    /// </summary>
    override public int FacingDirection
    {
        get 
        { 
            return facingDirection;
        }
    }
}
}

 

You can now replace the AirMovement on the character and set some values, we used the following:

 

The character will now run and jump in one direction only and then switch direction when it hits a wall.

 

Stage 3 - Making it Pluggable (Optional)

We have already given the class a unique name and description via a new MovementInfo to make pluggable the only thing we need to add is drawing an inspector. Because we haven't added any new variables we can reuse the AirMovement_Digital inspector like so:

    #if UNITY_EDITOR
        #region draw inspector

        /// <summary>
        /// Draws the inspector.
        /// </summary>
        new public static MovementVariable[] DrawInspector(MovementVariable[] movementData, ref bool showDetails, Character target)
        {
            return AirMovement_Digital.DrawInspector (movementData, ref showDetails, target);
        }

        #endregion
    #endif

 

 You can now select the movement from the drop down provided in AirMovement.

Have more questions? Submit a request

2 Comments

  • 0
    Avatar
    John Avery

    Note these samples will be included in v1.1.2 so if you work through them yourself remember to change the class names.

  • 0
    Avatar
    John Avery

    There are some good details related to this on the forums at: Evade Move

Please sign in to leave a comment.
Powered by Zendesk