The Very Basics of Platforming Physics Using Thomas Was Alone as a Visual Cue

Thomas Was Alone (2012) is an award winning puzzle based platformer with an art style which can be described as simplistic and functional, or perhaps inspired by Bauhaus modernism or even De Stijl neoplasticism. Player avatars are brightly coloured rectangles whose only animation is in a jump action. Character is provided in narration, with minimal animation of the playable rectangles which are devoid of features beyond their uniform colour and rigid shape. The playing field is black and made up of rectangular shapes, varied enough to provide platforming challenges, but again without any detailed textures – simply: black. Beyond some particle effects, and patterned backgrounds, it’s fair to say Thomas Was Alone is particularly minimal in its presentation.

Its for this reason, I feel this particular indie game could well become the next Hello World of games programming student projects in the same way Tetris or Asteroids is. The game was originally made in Flash and ported to Unity a couple of years later, and while TWA is a complete and polished game, its simplified presentation allows for a student of any language or engine to learn many aspects of game design, including menu building, input challenges, audio programming and visual effects to name a few.

In this post, I want to introduce the concepts of character movement in a two dimensional space using example clips taken from Thomas Was Alone, and show how to recreate the fundamental verbs of a platformer: move and jump, along with some basic collision detection. This isn’t a comprehensive tutorial on platformer physics, nor a competent example, but an exploration into the underpinning code and how minimalist design removes artistic distraction from the design of actions in code.

Thomas is a little red rectangle. As already mentioned, there are seven playable characters to consider, all of which are rectangles but of different colours. To reduce the amount of code needed, an object can be used to define the rectangle basics. At this point we need to think what a rectangle is: a four sided shape with a height and width, and in this case, a colour.

var rectGeneric = function(height, width, colour) {
  this.height = height;
  this.width = width;
  this.colour = colour;
}

If Thomas is 30 pixels wide and 50 tall, and is a specific shade of red (rgb(174, 67, 58) or in hexadecimal: #ae433a), we would create Thomas using the rectGeneric object as such:

var thomas = new rectGeneric(50,30,#ae433a);

Thomas exists, but cannot yet do anything. What can the real Thomas do? Move left and right, and jump. Moving left and right along a flat surface is x-axis movement, whereas moving up and down is y-axis movement. Upward movement in this side scrolling, two dimensional game is jumping, downward movement is falling. Movement can occur in two axes at the same time: jumping to the right for example.

It’s also important to think that to move, an object must first have a position. If we have a big empty box, or world, to move around in, say 1000px square, we can apply cartesian coordinates to the world and position an object anywhere within it. Such as x:0, y:0 would be the top left corner, x:1000, y:1000 the bottom right and x:250, y:250 would be in the upper left half of the world.

As all the rectangles can move left and right, let’s apply this new ability to the rectGeneric object. For now, assume we have an event listener for keyboard presses that knows when a particular key is pressed. To tie a user action (keypress) to an onscreen action, we need to update the object.

var rectGeneric = function(height, width, colour) {
  // height/width/colour parameters still exist here 
  var x = 0;
  var y = 0;

  this.update = function() {
    if(isKeyPressed(left)) { x--; }
    if(isKeyPressed(right)) { x++; }
  }
}

What the above code will do is subtract 1 from the x position if the left key is pressed, then add 1 to the x position if the right key is pressed. If both keys are pressed at the same time, the effect would be zero difference in movement.

If x and y are points on the 1000,1000 world, the movement is effectively one pixel per keypress. Assuming the key is held down, and the update is running hundreds or thousands of times per second, it wouldn’t take long for Thomas to move from 0,0 to 1000,0, in fact, it would probably be impossible to see. To counter this effect, update cycles are often governed by a delta time variable, but I’m excluding that from this article. Instead, let’s assume the code we have written moves Thomas relatively slowly, one pixel per second perhaps. A quick fix would be to add a speed dec/increment greater than one:

var rectGeneric = function(height, width, colour) {
  // height/width/colour parameters still exist here 
  // x, y
  var speed = 5;

  this.update = function() {
    if(isKeyPressed(left)) { x -= speed; }
    if(isKeyPressed(right)) { x += speed; }
  }
}

Movement doesn’t have to be at a linear speed; I may be imagining it, but Thomas feels “sticky” moving from a standing start to top speed and I’d think there was a slight accumulation of acceleration, at least in the first few frames of movement, to provide “game feel.” Rather than simply moving when told, there is slight inertia to the character’s momentum, making it feel weighty, or to put it another way: giving the rectangle physicality.

Unlike say, Sonic the Hedgehog, where acceleration is prolonged over continued forward movement, any acceleration in TWA (imagined or not) is very short from a standing start into full movement. There are two variables to consider: acceleration and top speed. Once Thomas has accelerated enough, he hits his top speed and cannot accelerate any faster.

var rectGeneric = function(height, width, colour) {
  // height/width/colour parameters still exist here 
  // x, y
  var move = 0;
  var currentSpeedX = 0;
  this.acceleration = 0.3;
  this.topSpeed = 5;

  this.update = function() {
    // Replace speed with a movement direction, negative or positive
    if(isKeyPressed(left)) { move = -1; }
    if(isKeyPressed(right)) { move = 1; }

    // Increase speed by acceleration if a direction is pressed
    if(isKeyPressed(left) || isKeyPressed(right)) { 
      this.speedX += this.acceleration; 
      // Limit the speed to the defined top speed
      if(this.currentSpeedX > this.topSpeed) {
        this.currentSpeedX = this.topSpeed;
      }
    }

    // Update the x position by the direction and speed of motion
    x += move * currentSpeedX;
  }
}

Remembering that x and y are coordinates in the world where Thomas will be, we are updating the x value each time we call the update function. We update it by the distance travelled in an update cycle, which is the cumulative acceleration value, speedX and can be from 0 to 5 (the value of topSpeed) in increments of 0.3 (the value of acceleration). It will take seventeen update calls to reach top speed, but 17 * 0.3 is actually 5.1, which is greater than the topSpeed, so we cap currentSpeedX at topSpeed before updating x to prevent any unexpected acceleration.

You may have noticed another difference between this and the original x++ code. Move is set when a key is pressed and never reset unless a different key is pressed, in which case the direction is simply reversed. The current implementation would continue to move Thomas in the last direction pressed, forever. In Thomas Was Alone, letting go of a direction key immediately kills momentum in that direction, so this is a simple fix.

var rectGeneric = function(height, width, colour) {
  // all code remains here

  this.update = function() {
    // Update movement direction: none, negative or positive
    xMove = 0;
    if(isKeyPressed(left)) { xMove = -1; }
    if(isKeyPressed(right)) { xMove = 1; }
    
    // all code below remains the same
  }
}

We reset the move value to 0 at the beginning of every update, and restate the direction based on current keypress prior to any updating of x position. An alternative would be to add friction as a parameter, as some games do, but TWA lacks friction and air resistance, opting for complete cessation of x-axis movement with release of the movement keys.

One last thing before adding in that y-movement. In TWA, as in all platform games, levels are designed around jumping, and primary obstacles are walls. Interaction with a wall is another design decision: should the character collide with a wall and rebound from it, or be slowed down as they continue through the wall, breaking through it, or even halt all momentum in an almost comedic splat against the immovable wall? Thomas Was Alone opts for the latter, again providing a clear example of collision detection.

Characters in TWA cannot go through walls or each other, they are impassable objects. We need to define an area around the character where if anything potentially intersects – or collides – with that area, the intersection is prevented. TWA is neatly designed around rectangles, including the level layouts, so we have a straightforward problem to solve: stopping one rectangle overlapping another.

There are many approaches to collision detection and which you choose will depend on your character models or designs, the world they exist in and many other interactions made in your game. Bounding boxes are a good, basic concept to start with, and are basically testing each edge of a list of boxes to detect any overlapping. For our purposes, we will make the playable area out of boxes held in a list as well; I doubt TWA was created this way, but it’s a simple solution which allows us to implement one function of collision detection which can be used for the level as well as for other characters.

  this.update = function() {
    // previous code 

    var nextPos = x + (xMove * currentSpeedX);    
    rectangles.forEach(rect => { 
      // Detect a collision of the next position against the xPosition of the other rectangle
      // collision detection along x axis (do the corners of two rectangles overlap?)
      if(nextXPos+this.w >= rect.xPosition && nextXPos < rect.xPosition+rect.w) {
          // collision response: don't allow the nextXPos to be used
          nextXPos = this.xPosition;
      }       
    });
    // final step: set the drawable xPosition as the updated nextXPos (calculated or identical to previous xPosition)
    this.xPosition = nextXPos;
  }
Imagine the yellow boxes overlap with the blue box to understand the descriptions on the right.

The illustration should provide a better context to the two conditions in the if statement. nextXPos is the x position of the left side of the rectangle, so to know our blue square overlaps with a yellow one, we add the width of the blue rectangle to its nextXPos; if this value is greater than the yellow square’s xPosition (again, its left face), we can mark that as an overlap. However, the blue square could be entirely to the right of the yellow one and the first condition would still be true, so we need to limit the trueness of the condition, to create a bound of the yellow square. To do this, we simply flip the scenario: is the left face (nextXPos) of the blue square to the left of the yellow square’s right face (it’s xPosition + it’s width)? Again, this condition on its own could be true without a visible overlap, so the two conditions must both be true (&&) for a collision to be occurring.

We have only considered the x-axis here. If we implemented a jump, the blue box would still collide with the left face of a yellow box even if it was trying to jump over it. So we add the same principle but for the yPosition and height.

if(nextXPos+this.w >= rect.xPosition && nextXPos < rect.xPosition+rect.w &&
   this.yPosition-this.h <= rect.yPosition && this.yPosition > rect.yPosition-rect.h) {
  // 
}

Before we learn to jump, we should implement gravity – what goes up must come down, after all. Thomas Was Alone’s levels sometimes open with the characters in an elevated y-position from which they will fall to the floor once the level becomes playable. This is a combination of y-axis movement and collision detection: with nothing colliding with their bottom edge, the rectangles will continue to fall or, to put it another way, increase their y-position infinitely.

A gravity parameter works in the same way as acceleration, however unlike acceleration, gravity is constantly applied to the yPosition value regardless of user input.

var gravity = 0.4;

this.update() {
  this.yPosition += gravity;
  rectangles.forEach(rect => { 
    if(this.yPosition >= rect.yPosition-rect.h && 
       this.xPosition+this.w >= rect.xPosition && 
       this.xPosition < rect.xPosition+rect.w) {
      this.yPosition = rect.yPosition-rect.h;
    } 
   });
  //
}

This code should look very similar to our x-axis collision detection, because we’ve jumped right into adding gravity and checking for a floor. Does the bottom of our rectangle, collide with another rectangle’s top? If so, we want to halt the yPosition there.

Gravity itself is a value which “feels” right for the game. Increase the value to make objects fall faster, or decrease for a slower effect. Whatever the value, it must be overcome when beginning a jump.

Now we know how to fall, we just need to learn how to go up. In TWA, different characters can jump to various heights, in much the same way they have varying speeds when moving left and right. Jump speed isn’t really a varying thing, but we can apply speed to a jump, just like accumulated gravity is the speed of a fall.

We need to detect if the character is currently on the ground or not, and if not, it shouldn’t be able to jump. Adding a yMove variable and having it set to 0 (on the ground) or 1 (not), provides us with a simple check prior to incrementing any Y speed.

Consider the jump: a maximum height or apex must be reached then gravity applied to bring the character back down. We already have gravity working, so let’s add the following code beneath the xMove code.

if(isKeyPressed(kbSpace) === true) { 
  if(yMove === 0) {
    currentSpeedY = -this.apex; 
    if(currentSpeedY >= topSpeedY) { 
      currentSpeedY = topSpeedY; 
    } 
  }
}

If the space bar is pressed to trigger a jump, check the player is on a flat surface, set the Y speed to the negative of the apex (y axis increases top to bottom, so a jump upwards is actually a reduction of the y value). Cap the speed in the same way we cap the x speed. Et voilá, our generic rectangle can now jump.

Already we have the bare minimum: left and right movement, a jump with gravity, and simple collision detection.

Different rectangles have different properties such as jump height, speed, and size. This is the beginning of understanding where the generic player code can be reused by multiple characters, and tweaked to give a unique feel for each playable character.

Because of how we setup the genericCharacter object, all we need to do here is plug in different values at the instance creator.

var thomas = new genericCharacter("T", 50, 30, "#ae433a", 5, 100, 500, 9);
var chris = new genericCharacter("C", 35, 35, "#ae7239", 2, 150, 500, 4);
var john = new genericCharacter("J", 125, 25, "#b29e39", 7, 200, 500, 15);
var claire = new genericCharacter("A", 125, 125, "#223e5f", 2, 250, 500, 4);
var laura = new genericCharacter("L", 25, 125, "#a0354f", 5, 400, 500, 0);

We can now control thomas, a red 50 x 30 pixel rectangle with a top speed of 5 and a jump height of 9. We start Thomas in position 100 (x) 500 (y). Notice that Chris is a smaller rectangle with a different colour, speed, starting position and jump height, yet we’ve used the same genericCharacter code and he can be controlled separately to Thomas.

I’ve prepared a slightly buggy but working example of movement, collision and jumping. You can view and modify it at JSFiddle, here: https://jsfiddle.net/unnecessarywriting/0p7t1hnd/7/ The code used in this post comes from earlier revisions of this code, so if you’ve been following the post and think the code doesn’t work, I urge you to look at the fiddle for a working demo to build on.

Physics can be fleshed out a lot more than how I’ve approached the problem here. Take a look at the fantastic Sonic Physics Guides on Sonic Retro, and this very thorough guide to collision detection in 2D Platformers by Rodrigo Monteiro.

Adding unique abilities needn’t require duplicating code either. One character in Thomas Was Alone has a double jump, allowing an additional jump while in mid-air. This could be a genericCharacter function with a boolean flag allowing (or not) characters to use the double jump. Likewise, a gravity direction flag could enable reversed gravity characters, like the inverted Thomas character, James.

More customised functions which alter collision response or movement may require overriding the update function completely, but still the genericCharacter object would be the first port of call for any new character. This is how Sonic’s supporting cast can fly or climb where Sonic cannot, but they still only require a single generic character object. It is also how ROM hackers can add in third party characters and their functionality into games which don’t feature those avatars or skills.

Understanding where animation is just artistic flourish and that code is governing the character behaviour is necessary for any game programmer. Thomas Was Alone’s unique art style abstracts animation to emphasise the concept the characters are living inside a computer simulation, but is a case of art imitating life in that the same abstraction helps developers understand the concepts being utilised.

If Mario 1-1 is used as the de facto introduction to video game tutorials and level design for those developers, Thomas Was Alone should be the de facto introduction to physics for the software devs. By removing all distractions in visual stimulation, varied concepts can be broken down into small repeatable actions which can be built upon to demonstrate a complex system of physics without having to say a word. After all, it’s no good having great level design if there isn’t a character which feels perfect to play in it with.


Thomas Was Alone, 2012, PC / Mac, [Trailer], Metacritic: 77

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s