top of page
Writer's pictureTom Stephenson

2D Procedural Animation In Unity

Updated: Mar 6, 2021

I recently took part in the Ludum Dare 46 and attempted to tackle procedural movement as part of my game, The Dark Cave. This post explores how I did that and how you can use it in your games.


Check it out on Browser and Windows Desktop here.


Moving The Body


Before we move the legs we need to get the body moving.


Create a new script and attach it to the main parent-transform of your creature/character.


We only want to move the body on the X-axis as we'll be setting up the height later, which is why set the target y position to the current position of the body.


void Update()
{
    transform.position = Vector2.MoveTowards(transform.position, new Vector2(currentTarget.transform.position.x, transform.position.y), speed * Time.deltaTime);
}

Rigging The Legs And Inverse Kinematics


Unity has made it very easy to create bone rigs and add inverse kinematics (IK). By using IK we ensure all other joints move correctly when you move the feet, which makes our lives a lot easier.


Using the Unity's rigging tools is a whole blog post in itself. Luckily Brackeys have made a fantastic video on 2D skeletal animation and IK, which I followed followed during the jam.


Once you have rigged your legs and set up IK, you should be able to move the feet and the rest of the leg will move nicely with it.



One Step At A Time


Keeping The Legs In One Spot

Currently our creature's feet follow the body, which we don't want to happen.

To solve this we need to lock the feet to the ground.


Create a new script which controls the leg. You will need to attach this script to each leg you add. In the script we create a target position for the current spot we want the foot to remain and constantly move the foot towards that position in Update().


Transform currentTarget;

void Update() 
{     
foot.transform.position = Vector2.MoveTowards(foot.transform.position, currentTarget.position, speed * Time.deltaTime); 
}

Now the feet should remain planted on one spot, even if we move the body.



Setting The New Target Point and Ground Detection

Next we need to work out where we want each leg to step. Create a new empty gameobject for each leg and make them child objects of the body. This ensures they move with the body. These will be our desired target points (shown in red).



The new Target points move with the body so we don't need to set their X position. However, we do need these points to snap to the ground. We can achieve this by performing a raycast above the target point down towards the ground, then setting the Y position of the target point to the hit Y value.


In my raycast I also check for the ground layer so that the creature does not step on other objects - this is optional.


float desiredYPosition;

void Update()
{
// Cast a ray
    RaycastHit2D hit = Physics2D.Raycast(new             
    Vector2(desiredTarget.position.x, transform.position.y + 5),               
    -Vector2.up, 12f, LayerMask.GetMask("Ground"));
 
 // If we hit a collider, set the desiredYPosition to the hit Y point.        
 if (hit.collider != null)
        {
            desiredYPosition = hit.point.y;
        }
        else
        {
            desiredYPosition = transform.position.y;
        }

    desiredTarget.position = new Vector2(desiredTarget.position.x,     
    desiredYPosition);
}

The final result should have the desired target points moving with the body and snapping to the ground.



Take A Step (Or A Skate?)

Our creature is now ready to take his first step! Well at this point the movement will look more like skating.


Check the distance between the currentTarget (the positon we use to hold the foot in place) and the new Desired Target. If the distance threshold is met, it's time for our creature to take a step.


Set the currentTarget position to the DesiredTarget's position and the foot will move towards the currentTarget's position.


float dist;

dist = Vector2.Distance(currentTarget.position, desiredTarget.position);

if (dist > 3)
   {
      currentTarget.position = desiredTarget.position;
   }

This should result in the creature taking step-like slides towards as it moves with each step landing on the ground.



Step Height and Animation Curves

Scooting around is fun, but lets add some height to the steps.


There's a few ways you can approach this, but I like to offset the step height using Animation Curves as they're visual and give you a good level of control over how the steps rise and fall.


First we create an animation curve and a float to handle time at the top of our script.

We then check the distance between the foot and the current target.


If the distance is greater than 0.1f we need to move the foot towards the currentTarget's position and add current height of the animation curve to the y position of the foot.


We set the timer using Time.delta time so that it scrolls through the animtion curve in real-time and that's how we get our step height.


If the distance is less than 0.1f we just clamp the foot to the currentTarget's position.


public Transform foot;
Transform currentTarget;  
float timer;
public AnimationCurve yCurve;

void Update() 
{  
    float dist = Vector2.Distance(foot.position, currentTarget);
        
        // Move the foot
        if (dist > 0.1f)
        {
            timer = 0;
            // Increase curve timer
            timer += Time.deltaTime;
            // Move towards desired target position
           foot.position = Vector2.MoveTowards(foot.position, new Vector2(currentTarget.x, currentTarget.y + yCurve.Evaluate(timer)), speed * Time.deltaTime);
        
        }
        // Clamp the foot        
        else
        {
            foot.position = currentTarget.position;
        }
}

Currently our step won't look any different and the legs will still be gliding across the floor.

We need to set up the AnimationCurve in the inspector. Click on the box next to your yCurve animation curve and the curve editor window should appear.



At the bottom of the window are a few curve presets. I use the flat line preset as a starting point (first option), but you can use whatever you like.


- Left click on a key point and move the handle to change the curve direction of that point.

- You can add new key points anywhere on the line by right-clicking and clicking 'Add Key'.

- Click and drag a key point to reposition.


The X-axis of the graph determines the amount of time and the Y axis is the height of our step. Play around with differing shapes, speeds and heights until you have something you like.


I wanted my creature to feel like it was stalking the player and trying to be quiet, so I left the step height quite low for mine.



It's Alive!! - Closing Notes and Next Steps


Our creature should now be walking properly, with its feet connecting to the ground regardless of how bumpy the terrain is.


There's plenty you can do to expand on this:


  • Use animation curves to make the body bob up and down whilst idle.

  • Add layer masks to your ground detection rays so that the creature only walks on certain surfaces, or maybe allow it to walk on everything and cause havoc!

  • Add extra raycast checks infront of and behind the creature to see if the ground stops to prevent it from walking off ledges.

  • Move the rear DesiredTarget points in closer to the creatures body to give it a more creepy sense of movement.

These are just a few examples of what you can do with procedural animation. I really enjoyed experimenting with movement during the LudumDare and it's certainly something I want to experiment with more and I can't wait to see what you make.


I'd love to keep the conversation going on Twitter and see your creatures and creations!


I'm Tom and you can follow me on twitter @DeveloperTom1


Use the hashtag #proccreature when sharing your creation and hopefully we can start something fun!


Thanks for reading and happy developing!

9,972 views2 comments

Recent Posts

See All

2 comentários


ji sung Choi
ji sung Choi
24 de out.

As for the code under "Step Height and Animation Curves" the timer shouldn't reset under the if(dist<0.1f) statement, it should reset under the else statement. Otherwise very good starting point for 2DPA, thanks!

Curtir

Manish Varshney
Manish Varshney
06 de mai. de 2021

Tom, believe me, you are such a horrible explainer.

Curtir
bottom of page