Celeste and TowerFall Physics

  • All colliders are axis-aligned bounding boxes (AABBs)
  • All collider positions, widths, and heights are integer numbers
  • Except for special circumstances, Actors and Solids will never overlap
  • Solids do not interact with other Solids

Actor Basics

Let’s start off assuming that Solids will never move — how can Actors move and ensure they never overlap Solids? Actors have a very simple core API with two functions:

public void MoveX(float, Action); 
public void MoveY(float, Action);
public void MoveX(float amount, Action onCollide) 
{
xRemainder += amount;
int move = Round(xRemainder);
if (move != 0)
{
xRemainder -= move;
int sign = Sign(move);
while (move != 0)
{
if (!collideAt(solids, Position + new Vector2(sign, 0))
{
//There is no Solid immediately beside us
Position.X += sign;
move -= sign;
}
else
{
//Hit a solid!
if (onCollide != null)
onCollide();
break;
}
}
}
}

Moving Solids Prep

This is where things get tricky. I’ve seen and written many, many flawed implementations of moving solids throughout the years. Some games from my teenage years have truly atrocious moving platform physics, but I eventually arrived at this solution.

public void Move(float, float);
public virtual bool IsRiding(Solid solid); 
public virtual void Squish();

The Heavy Lifting

When a Solid interacts with an Actor, it can do so in two different ways: carrying or pushing. An Actor is carried if it is riding that Solid, but it is pushed if the Solid’s movement results in them overlapping. It’s important to note that pushing takes priority over carrying — that is, if a Solid simultaneously pushes and carries an Actor, it counts as a push.

public void Move(float x, float y) 
{
xRemainder += x;
yRemainder += y;
int moveX = Round(xRemainder);
int moveY = Round(yRemainder);
if (moveX != 0 || moveY != 0)
{
//Loop through every Actor in the Level, add it to
//a list if actor.IsRiding(this) is true
List riding = GetAllRidingActors();
//Make this Solid non-collidable for Actors,
//so that Actors moved by it do not get stuck on it
Collidable = false;
if (moveX != 0)
{
xRemainder -= moveX;
Position.X += moveX;
if (moveX > 0)
{
foreach (Actor actor in Level.AllActors)
{
if (overlapCheck(actor))
{
//Push right
actor.MoveX(this.Right — actor.Left, actor.Squish);
}
else if (riding.Contains(actor))
{
//Carry right
actor.MoveX(moveX, null);
}
}
}
else
{
foreach (Actor actor in Level.AllActors)
{
if (overlapCheck(actor))
{
//Push left
actor.MoveX(this.Left — actor.Right, actor.Squish);
}
else if (riding.Contains(actor))
{
//Carry left
actor.MoveX(moveX, null);
}
}
}
}
if (moveY != 0)
{
//Do y-axis movement

}
//Re-enable collisions for this Solid
Collidable = true;
}
}

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Maddy Thorson

Maddy Thorson

Makes games ❤ Celeste, TowerFall, Give Up Robot, Runman, Untitled Story, Jumper, etc