Player Physics
The code that controls the low level player movement in Contra is very simple compared to what’s possible with a modern physics engine. At the same time, I’ve always enjoyed the super responsive feel that a lot of older games have compared to games with more physically realistic controls. The data associated with low level motion in Contra is basically just a 2D position and velocity. Both are represented in 8.8 fixed point format with units of pixels, and pixels / frame respectively. Each frame, the game determines what the current velocity should be for a player and then simply adds that velocity into the player’s position. Horizontal velocity is always reset to 0 at the beginning of a frame so there is never any continuous acceleration in the X direction (so no ice physics are possible in Contra.) Enemy characters in the game don’t even get their own dedicated memory to keep track of velocity because a lot of them don’t move. Those enemies that do move usually just manually add some constant amount to their current position each frame. In the rare case that an enemy does need more complex movement, they implement their own version of what they need to do, not sharing any physics code with the player characters.
In general, there is no physics system that is always running to manage the movement of the players. Instead, certain calculations related to player movement are performed explicitly each frame depending on what exactly the player is doing at the time. For example, when the player is running along the ground, the play control code is setting the player’s horizontal velocity and checking for collisions at the player’s feet to see if you should start falling. However, the player’s vertical velocity is not being updated or even added into the player’s Y position at all. Also, collisions are not being checked for above the player’s head because the player is known to not be moving up while running. This differs from the more general approach to physical simulation where gravity would be constantly pulling the player down into the ground and a reaction would be constantly moving the player back up to resolve the collision. Similarly, while jumping, collisions are only checked for above the player’s head while the player is moving up during the first part of the jump. Once the peak of the jump is reached and the player starts moving down, the code switches to checking for collisions below the player to see if you’ve landed yet. Also while jumping, vertical acceleration is applied by manually adding a fixed value into the player’s Y velocity each frame. There is no general acceleration variable that is always being added into the player’s velocity but that happens to only get a non-zero value during a jump. There are no wasted collision checks and no wasted math is performed to calculate the movement of the player. Only the relevant details are processed explicitly each frame and this contributes to a very tight feeling control scheme without much emergence of behavior.
Player States
At the next higher play control level above the low level physics code, it’s common to define a set of possible states that the player can be in and then update the player each frame according to the current state. Contra effectively implements this kind of system although it doesn’t actually use a single value to keep track of the current state of the player. Instead, players have multiple groups of flags that indicate what kind of state the player is currently in. The major groups are the jumping flags, falling flags and water flags. When the player is on the ground all of these flags are clear. If you press A to jump, the jumping flag is set and some other jump specific flags are updated according to which direction you’re trying to move and which direction you were facing when you left the ground. A similar set of flags exists for falling which is the state you go into when you run off the edge of a platform or jump down through the ground. The water flags are used only in the first level when you enter the water and start swimming. These flags are never used at the same time (i.e. you’ll never find both the jumping and falling flags set at the same time) so I’m not sure why a single state variable wasn’t used instead. It’s common for play control functions to start off by testing the various flags and then jumping off to specific parts of their code depending on which flag they find set.
Control Details
Running in Contra is instant on / instant off unlike some other games like Super Mario Bros. that impart the player character with some momentum. As mentioned earlier, this is a result of the horizontal velocity of the player being reset to zero at the start of each frame and then updated to the desired value depending on what the player is doing. If the player is running on the ground and you stop pressing left or right, then the horizontal velocity remains set to zero and the character instantly stops moving forward.
Jumping and falling have a slightly different behavior where once you start moving forward or backward, you will continue to move in that direction until you either collide with something or press the opposite direction to move the other way instead. Once you’re moving horizontally in the air, you aren’t able to stop moving horizontally until you land. This is similar to the spinning jump in Metroid and differs from games like Mega Man where you will stop moving in the air if you let go of your button input. Contra’s jump behavior is implemented by a pair of flags that are part of the jumping flags as described earlier. Pressing left or right during a jump will cause the corresponding left or right jump flag to turn on, but releasing the buttons does not cause the flags to turn off. Then, your horizontal velocity during a jump is set based on the current value of the flags and not directly from your current button input.
Contra also allows you to jump down through the ground to lower platforms by holding down on the controller and pressing the jump button. This same mechanic is also used in games like Chip ‘n Dale Rescue Rangers on the NES, but is absent from games with similar “one way” platforms (platforms that you can pass through while jumping up from below, but that you land on when falling down onto them) like Super Mario Bros. 2. In Contra, when you press A while holding down on the controller, the player’s falling flag is set instead of the jumping flag. In order to allow you to pass through the ground, the game records the location on screen that is 20 pixels (a little more than one full collision tile) below your current Y position. Then, as you fall down, the collision check that is normally performed at the player’s feet when in the falling state is skipped while the player’s current Y position is still above that recorded value.
Up Next
The next post will be the final installment in my series on Contra. I’ll cover a bunch of miscellaneous details that I think are interesting but don’t fit in anywhere else. If there’s anything that I haven’t covered so far that you’d like to know about how the game works let me know on twitter @allan_blomquist or leave a comment below!
(Prev – This is part 6 of a 7 part series – Next)
Tags: Allan Blomquist, Retro Game, Retro Game Internals, Tomorrow Corporation
I can’t remember, did you already do a post on the bosses? I don’t know if there’s anything interesting there, but if there is I’d like to hear about it.
I had considered doing a post about bosses because they certainly are a cool part of the game but I’m not sure there’s all that much to say about them from a game systems point of view. They’re mostly just enemies like everything else with slightly more interesting behavior. They do require additional texture data to be loaded into memory because there isn’t enough room to fit everything for the level, the normal enemies and the bosses all at once. There is a small system in place to stream in new data for the boss as you make your way through the second to last screen in the level, but I think that’s more of a low level plumbing kind of thing that wouldn’t be as generally interesting.
It seems like horizontal input is ignored for a quarter or a second after a fall… for example, dropping down through a platform you aren’t able to alter your x-position immediately. Likewise, after walking off a ledge you can turn backwards (and your sprite will be displayed that way) without actually correcting your x-position in the opposite direction. Is this something you saw in the code? Why do you think they made this choice?
Yes, when you start to fall the game marks the minimum Y position that you must fall to before it starts checking for collisions again as mentioned in the post. In addition to not checking for collisions during that time, it is also not checking your left/right movement input which is why you can’t change your horizontal momentum for a bit. There doesn’t appear to be any technical reason why it couldn’t, you can use the game genie code AEOSZNYA to modify the code to never ignore the left/right input and it seems to work just fine. I would guess that it was just a design decision along the lines of how they don’t let you ever stop moving in X once you’re in the air, only flip your direction of movement back and forth.