Retro Game Internals: Contra Collision Detection

Collision Detection

There are two kinds of collisions that can happen in a game of Contra: object vs. object collisions, and object vs. level collisions. Each is handled by completely different code and detected using completely different data, so they are basically two separate systems.

Object vs. Object Collision

In the first post of this series, I mentioned that the game keeps track of three different kinds of objects: players, bullets from players, and enemies. Collision detection is one of the areas where this separation comes into play. Instead of checking for collisions between any and all of these objects, the game only checks collisions between the groups that will react to each other. Players never interact with other players or their bullets, and enemies never interact with other enemies, so the only collisions that matter are those between players and enemies, and those between player bullets and enemies.

Given that enemies are always involved in the object/object collisions that matter, collision detection between objects is done as part of updating all of the enemies. Each frame, the game loops over each active enemy and runs its particular update logic. After an enemy updates, the collision system kicks in and checks that enemy for collisions with players, and then collisions with player bullets. Each enemy has a pair of flags that can be used to indicate to the collision system whether either or both of these kinds of collisions are enabled for that enemy. This is how some enemies, like the bodies of turrets, are able to be shot by the player (by allowing collisions with player bullets) but not able kill you if you walk into them (by not allowing collisions with players.) Beyond these flags, no special logic is used to narrow down the list of potential collisions for an enemy. Every enemy is simply checked against every player and every bullet.

Checking for a collision between a given enemy and a given player is based on a point vs. rectangle test. The point that represents the player’s current position is checked against the rectangle that represents the current hit box that the enemy is using. If the point is inside the rectangle at the moment the check is performed then a collision has occurred. At first glance this seems a little strange. You need the player to be able to be shot in the head as well as the feet, but only the point that represents the player’s current position (which is usually fairly close the the center of the player sprite) is checked for collision with the enemy hit box. The key to making the system work is that the enemy hit boxes are not just tight bounds around the enemy sprites themselves, but rather representations of the area where collisions would occur if we were doing a more traditional rectangle vs. rectangle test. Put another way, the hit boxes that enemies use represent the space where the player position would have to be to make the player sprite overlap the enemy sprite.

collide_obj

You can see this process in action in the image above. In each of the three sections, the player’s position is indicated by a single green pixel in the center of a green circle. The pink rectangle represents the hit box that the white enemy bullet is using to check for collisions against the player’s position. Notice how the enemy bullet uses a different hit box depending on what the player is doing. This is how the game effectively changes where the player is vulnerable without actually having a hit box around the player to work with. Each enemy has a version of their hit box to use against a player in the water, a jumping player, a player on the ground, a standing/running player, and a player bullet. The particular type of player bullet is not taken into account when picking a hit box to test against so there is no difference between the different guns you can get from a bullet collision perspective.

Object vs. Level Collision

Collisions between objects and the level itself are not handled by a system in the way that collisions among objects are. Instead, level collisions are tested for directly wherever they are needed. If a player needs to know if he has run off the end of a platform then the code that updates a running player will query for a collision at the player’s feet every frame until it finds that there is nothing there. Most enemies don’t need to know about collisions with the level at all, but those that do will similarly check for them at specific times as needed. The only kind of query that is supported is checking a point against the level collision map that we’ve talked about before. The collision query returns the collision code for tile under the point that was checked (empty, solid, water or platform) and then the caller will do whatever it needs to in response to the different results.

3D Collision

Collisions in the pseudo-3D base levels work almost exactly the same as in the 2D levels but with certain constraints to make the effect hold up in fake 3D. Enemies are not allowed to collide with the player if their screen space Y position is too small (i.e. too far toward the top of the screen which means too far back “into” the screen.) This works because players are known to always be at the bottom of the screen during these levels. Player bullets are timed and only allowed to collide with most enemies once they have been on screen long enough to have reached the “back” of the room. For enemies that can be shot at any Z position within the room (like the rolling grenades that you have to blow up before they roll down and kill you) the timer on your bullets is not used. Instead, a flag is checked to see if your bullet was fired while you were lying on the ground, and if so, normal 2D collision detection is allowed to continue as usual. This works only because you are detecting a collision between two objects that are both known to be on the ground where Y position maps directly to Z position (i.e. if two things look like they are colliding in 2D, then you know they are colliding in 3D as well, but only along the ground.) With these constraints in place, the normal, flat 2D collision detection system can be used to detect collisions between objects even in a pseudo-3D environment.

Retro Game Internals: Contra Base Enemies

Static Base Enemies

The enemies in the pseudo-3D “base” levels in Contra are spawned by a slightly different system compared to the other kinds of levels. Base levels are also divided up into screens, and each screen also gets its own list of enemies that it needs to spawn, but the details of the list are different.

baseenemies

The screens in the base levels do not scroll, so each enemy in the screen enemy list is spawned the moment you enter the screen. Each enemy list starts off with a count of how many targets need to be destroyed on that screen in order to lower the electric barrier and allow you to move forward. The game actually has a bit of unused logic to lower the barrier right away if there are no targets even though there are no screens in the final game that are set up this way. The rest of the enemy list consists of a series of records for each enemy that needs to be spawned. The first piece of data in each record gives the X and Y position for where the enemy should spawn on screen. The second piece of data in each record gives the enemy type to spawn, and the third and final piece is an opaque creation context value just like the one in the enemy lists for the other kinds of levels.

At the moment that you enter a new screen, all of the enemies on that screen’s enemy list are spawned and the list is never referenced again. These enemies are mostly just the stationary things on the back wall like the targets and guns that shoot at you. All of the other enemies that run onto the screen while you’re playing are not spawned through the screen’s enemy list. Instead, they are handled by a different system.

Sequenced Base Enemies

One type of object that is a part of the enemy list for every screen in a base level isn’t actually a visible enemy, but an entity whose job it is to manage all of the enemies that run in from the sides of the screen as you play. This spawner entity processes its own completely separate enemy list for each screen that tells it which enemies to spawn and when. These enemy lists also contain a series of records, one for each enemy that needs to be spawned. The first piece of data in each record determines which kind of enemy to spawn and gives the creation context value for that enemy. The second piece of data gives a delay time to wait before spawning the next enemy. There is also a flag associated with each record that tells if that record is the final one for that screen. Once the final record has been processed, the spawner entity loops back to the first record and begins repeating the same pattern of enemies over and over. Once the whole pattern has been run through 7 times on a single screen, the spawner entity destroys itself so no more enemies will be spawned. This is what triggers the targets on the walls to begin shooting bullets at the player.

Power-up Enemies

On some of the screens in base levels you encounter a red jumping enemy that gives you a power-up when killed. The appearance of these enemies is mostly just specified by the secondary enemy list that the enemy spawner follows, but with the additional rules that only 1 of these red enemies can spawn on any given screen, and you can not spawn a red enemy during the first pass through the secondary enemy list. The creation context data for the jumping enemy in the spawner’s enemy list determines whether or not it can be one that carries a power-up, and if so, what kind of power-up it will drop when killed by the player. The 1 enemy per screen rule applies to spawning the enemy, not killing it, so if you miss the red enemy the first time you won’t get another one again on that screen.

Retro Game Internals: Contra Random Enemies

Random Enemy Spawns

In addition to the enemies that are specifically set up to spawn at certain places in a level, there are also enemies that appear somewhat randomly. These enemies are the running soldiers that are spawned every so often at the edges of the screen as you play through the game. The system that manages these randomly spawned enemies is surprisingly complex given the seemingly simple job it has to do. You can see in the image below the enemies we’re talking about in this post. All of the running soldiers on this screen have been randomly spawned while the player was standing still.

rand_spawn

The heart of the random spawning system is a timer that counts down from some initial value to zero over and over. Each time it reaches zero, there is a chance for an enemy to spawn. Each level has a certain interval that the timer uses and some levels disable random enemy spawns by specifying an interval of zero (the base levels and the final level for example.) There are 2 modifications that are applied on top of the default interval specified by the level to arrive at the final interval that will be used . First, the interval is reduced by roughly 20% for each time that you’ve finished the game. This means that after the game kicks you back to the first level after you beat it, the random enemies spawn more frequently, and even more so if you play all the way through again and again. The other factor that reduces the timer interval is which gun you currently have. Guns are classified according to how good the game considers them to be with P being the worst, F being better, M and L sharing the next best rating and S (of course) being the best. The random spawn timer’s interval will be reduced by about 3% for each rating point (0-3) that your current gun has. The image above is from a 4th play through of the game while the player has the spread gun – the screen shot doesn’t do justice to the amount of guys constantly running onto the screen.

The rate at which the timer counts down is also variable depending on whether or not the screen is currently scrolling forward. If the countdown rate were 1.0 when you are standing still, it would be 0.75 when you are running forward. This has the effect of generating fewer random spawns while you’re moving.  I guess the developers wanted to compensate for the fact that you’ll also be running into the non-randomly spawned enemies while scrolling.

When the timer reaches zero, there is a chance for spawning to happen. Which side of the screen the enemy will come from is usually completely random. The one specific exception is when you are in the first level, and you are in your first play through of the game, and there have been fewer than 30 enemies randomly spawned on the current screen, then the enemies will always spawn on the right side of the screen. This makes it a little easier for brand new players who are just starting out. The Y position for the new enemy to spawn at is chosen in 1 of 3 different ways depending on how many frames have gone by since the game started. One quarter of the time the game looks for a platform to spawn on starting at the top of the screen and searching down. One quarter of the time it starts at the bottom of the screen and searches up. The other half of the time, the game tries to use a random player’s current Y position as the starting Y position for an upward search. There is actually a bug in this logic however that causes the search to start at the very top of the screen half of the time if there is only 1 player alive in the game. This bug doesn’t really harm anything though since the only effect might be that a randomly spawned enemy appears on a lower part of the screen than it should have.

Once a candidate position to spawn an enemy at is found, there are a few more checks to see if spawning should happen. If the candidate position is too close to the top or bottom of the screen then the spawn is rejected (unless you’re in the waterfall level where spawns are allowed to be close to the top of the screen.) If you’re on the very first screen of any level, then the spawn is rejected. Another hard coded test is if you’re on the last few screens of the snowfield level, then spawns from the left side of the screen are always rejected. This test kicks in when you get to the very last snowy platform of the level with the trees at the bottom, bombs being tossed out at you and the 1 guy shooting the stationary gun at you. Why such a specific test is in the game is anyone’s guess. Maybe one of the developers thought it was too hard to deal with so many threats at once (although almost the exact same situation happens at the very beginning of the level and spawns from the left are allowed there.)

One other hard coded test is there to make things a little easier on your first time through the game. If there have been fewer than 30 randomly spawned enemies on the current screen, and you are on your first play through, and a player is standing too close to the side of the screen that an enemy is about to spawn from, then the spawn is rejected. This prevents cheap deaths from enemies randomly spawning right on top of you.

If all of these tests have passed, there is still one more thing that needs to be checked before an enemy can actually spawn. Every single screen in the game has a unique value associated with it that controls various aspects of the randomly spawned enemies on that screen. One thing that an individual screen can control is an extra chance that any given spawn will be rejected. Screens can either always allow spawns, always reject spawns, randomly reject spawns 50% of the time, or randomly reject spawns 75% of the time. If this final test passes then it’s finally time to spawn something.

There are 2 different kinds of spawns that can happen once the game decides that it wants to spawn something. If you are not in the waterfall level, and the screen is currently scrolling, then you have a 25% chance to spawn a group of 3 running soldiers. These soldiers are always configured to never shoot bullets at you. In what might be another bug or might have been on purpose, the game uses what is effectively uninitialized memory to configure the behavior of each of the soldiers with respect to what they do when they hit the edge of the platform that they’re running on (whether they always jump off or if they’re allowed to turn around.) If this was a bug instead of just trying to get random looking behavior then it is again a pretty minor one.

The other kind of spawn that you get if the conditions for the first kind aren’t met is a single running soldier that gets configured slightly differently. In this case, the normal random number generator is used to randomly configure the jumping behavior of the soldier. These soldiers are also assigned a shooting behavior based on another screen specific value. The idea behind this process was that each screen selects one of a small number of pre-made groups of behaviors with each group having a different mix of non-shooting, high shooting and low shooting behaviors. Then, the specific shooting behavior of each soldier that is spawned is picked at random from within the group that the screen selects. However, there is another bug in the game where one specific screen in the hangar level specifies the 8th behavior group when only 7 behavior groups exist. This causes the game to assign soldiers on that screen a garbage behavior value (the actual value comes from part of the pointers to the screen enemy lists that we’ve talked about before) resulting in various non-fatal side effects that mostly result in the soldiers instantly turning around and running off the screen.

Up Next

Who knew spawning some random enemies was so complicated? The next post will cover the final piece of the enemy spawning puzzle which is how enemies work in the “base” levels of the game. As always, feedback is welcome @allan_blomquist or comment below.

Retro Game Internals: Contra Levels

Tile Map

Each level in the game is made up of a series of independent screens. The image below shows an overview of the first level of the game divided up into the 13 screens that comprise the stage.

screens

Each screen is built from a collection of non-overlapping super tiles which each take up 32×32 pixels worth of screen space. A screen is always made of exactly 8 super tiles across and 7 from top to bottom. This results in a final screen size of 256×224 pixels. The native resolution of the NES is 256×240 pixels so Contra leaves some of the screen space unused rather than trying to deal with splitting super tiles in half to perfectly fill up the display. It was fairly common at the time for console games to not care what they were displaying at the very top and bottom of the screen because it often wasn’t visible on CRT TVs.

The image below shows the 7th screen of the first level of Contra divided up into the 56 super tiles that make up the background.

supertiles

Each super tile is in turn made up of 4×4 regular tiles which are 8×8 pixels each in size. These regular tiles are what the NES hardware actually uses to display background maps so all games need to break their maps down into 8×8 pixel tiles at some point.

Collision Data

The collision information for each screen is derived from the final layout of the regular tiles as opposed to being explicitly authored as a separate map. The resulting collision map is made up of 16×16 pixel blocks of the screen. This means that there are 2×2 regular tiles that make up each block of collision data, but when deriving the collision type for each block, only the upper left regular tile is considered.

Each level defines 4 ranges of tile indices with all of the tiles in each range having 1 of the 4 different collision types. For example in the first level, tiles 0×01 to 0×05 have the collision type for floors that you can land on when falling but pass through when moving left, up and right. Tiles 0×06 to 0xF8 have the empty space collision type. Tiles 0xF9 to 0xFE have the water collision type and tile 0xFF has the completely solid collision type.

The image below shows a part of the first level of the game with the resulting collision map overlaid. Blocks with collision type water are colored blue and blocks with collision that allows you to walk on them but also move through them are colored green. Empty space collision blocks are black and completely solid collision blocks are not used in this section of the level.

collision

Enemies

Each screen has a list of the enemies that need to be spawned into the world as you scroll through it. The list is presorted according to the distance you need to have scrolled the screen in order to spawn the enemy. For example, the first enemy in the first level has a scroll value of 16, so once you start playing and move far enough to the right to scroll the screen 16 pixels, that first enemy is spawned. Because the list is sorted by scroll distance and the screen can only scroll in 1 direction, there is always exactly 1 candidate for the next enemy to be spawned.

The second piece of data in each list entry is the type of enemy to create, along with a count of how many of those enemies to spawn. Then, for each of those count of enemies, there is a generic creation context value and a value that gives the position to spawn the enemy on screen. The creation context has no meaning to the spawning system itself, but will be interpreted later by the enemy’s own update logic. It is used, for example, to configure which kind of power-up a flying power-up balloon will drop, and to control certain aspects of the behavior of the running soldiers. The spawn position value gives only half of the information required to locate the new enemy on screen. For levels that scroll horizontally, the position value will give the y position of the enemy, and the x position will always be the far right side of the screen. If the level scrolls vertically then the spawn position value is the x position of the enemy and the y position is always at the top of the screen. Enemies that seem to spawn from the trailing edge of the screen have logic built in to their enemy specific update logic to warp themselves to the opposite side of the screen as their first action.

The variable length enemies list is terminated by a byte with a magic value and then the game will wait for you to scroll onto the next screen to begin looking for more enemies to spawn from that screen’s enemies list. Enemies are in charge of destroying themselves when they have scrolled off of the screen, there is no system that does it automatically.

Retro Game Internals

History

When I was a kid I played a lot of NES games. As I started learning how to program games myself, I always wondered what kind of magic it must take to make these games that I loved so much. Even once I had learned enough to be able to think about how I would approach programming any of these games, I still imagined that the REAL games must be so much more advanced than any lame design that I could come up with. I seemed doomed to forever wonder about something I had no means of ever finding out.

A number of years later I had gotten really interested in console game emulation and had been working on an emulator to play NES games on my Playstation 1. Part of the process of developing that emulator was to become gruelingly familiar with the machine code of NES games so that I could diagnose problems that the emulator had with various games and fix it. At some point it hit me that the thing that made my favorite NES games tick was not some kind of abstract magic, but the very concrete sequence of numbers that I had been staring at every day for months. I was so excited – how long could it possibly take to study these hundreds of thousands of numbers and finally find out how these NES games were programmed? Turns out the answer was quite a long time, but luckily one thing that I had in spades when I was younger was free time.

So began my hobby of reverse engineering old games which I’ve worked at with varying levels of commitment over the years. Today I’m starting a series of blog posts in which I want to document what I’ve learned about the games, from a game programmer’s point of view. I’m going to try to focus on how the game systems work at the game/engine level and not at the hardware level (so more like how does this game decide which things it wants to draw on this frame as opposed to how do sprites work on the NES.) I’ll also try to throw in any tidbits about the games that I think are interesting like things that weren’t obvious to me just from playing the game casually or instances of bugs in the game’s logic.

Contra Introduction

The first game that I’m going to write about is Contra on the NES. The remainder of this post will be a short overview of the objects and data that exist in a game of Contra and then subsequent posts will go into more detail about each system the game uses to manipulate its model of the world. The basic model that the game maintains is made up of:

  • Player characters
  • Player bullets
  • Enemies and other objects
  • Level data

The player characters are the main heavyweight objects of the game and have lots and lots of code that explicitly deals with them as you might expect. The bullets from player characters are treated differently from every other kind of object in the game for reasons that are probably performance related. We’ll see later on how the player bullets being separated out from the other game objects makes various things the game has to do more efficient. The final class of objects are the enemies which are managed by a simple entity system. This includes the enemies themselves along with enemy bullets and explosions, but also a couple of friendly things like flying power-up balloons and power-ups sitting on the ground. I’ll use the term enemies to refer to all of these things just to have a more descriptive term for them than “objects.” The key feature is that there is code in the game that deals with them abstractly as opposed to the player characters and player bullets which are always manipulated by specialized code.

The tile based level data is the final major part of the game simulation. Contra features standard horizontal levels along with a vertical level and pseudo-3D levels. Levels only support moving forward through them, never backwards. The game maintains a double buffer of tiles that hold collision and visual data for the current screen in the first buffer, while it builds the next screen in the second buffer. We’ll see later on how the level data is organized, how it is updated as you make your way through the levels and how enemies are spawned from it.

Up Next

I have a rough outline of where I’d like to go from here with this series but it’s still very much a work in progress. Topics I have ear marked to talk about include data representations, enemy behavior, scrolling, collision detection, random number generation, player control and other assorted things. I’d love to get feedback about what people might be interested in going forward. You can leave a comment below or reach me on twitter @allan_blomquist