Two-Minute Offense
Saturday, February 26th, 2011I really got into football this last season! What does a game developer do when they get really into something? Make a game about it!
[swfobj src="http://www.regularkid.com/tmo/TMO.swf" width="640" height="720"]
I always found the last two minutes of a close game to be the most exciting part. Why not turn that into a “micro” game? Enter “Two-Minute Offense”! The goal of the game is simple: It’s the last two minutes of the Super Bowl and you need a touchdown to win. One chance… that’s it! Can you do it?
The game was written in Flash using Flixel ( highly recommended!!! ) and created in about a month total time with about 6 days of actual development. I did something new for this game and kept a “development log”. Not necessarily a full “dev diary”… more like a change log sprinkled with my thoughts and comments on the game as it progressed. Check it out here!
It was pretty fun to create such a simple little game based on something as complex as a football game, so I figured it might be cool to do a quick write up here on how everything works.
Move Object
Everything in the game ( well… the ball and the players ) derives from a “Move Object” class. It’s a pretty simple object that only does one thing: move to a position. It uses very simple acceleration / velocity based movement. Every frame it simply calculates the vector from its current position to its destination position and simply accelerates in that direction until it reaches the desired position. Once there, it eases out it’s movement so they don’t stop on a dime. This “easing” logic is probably one of my favorite ( and most overused ) programming tricks:
val *= easePct;
Simply set easePct to some value ( I usually stay between 0.7 and 0.99 ) and viola! Instant movement smooting! In the case of the players, they ease out their velocity once they’re reached their destination at 0.9 per frame. As a side note, you can ease the entire movement of an object ( not used in this game ) with the following:
pos = dest – ( dest – pos )*easePct;
Formations
Offensive and defensive formations are defined using a “Formations.xml” file that lists all the possible formations by simply giving a player type such as “QB”, “WR”, “C”, etc. and an offset in yards from where the ball is on the line of scrimmage. So, for example, the center “C” always has an offset of (0, 0 ) since he’s right over the ball. The quarterback “QB” is usually something like ( 0, -2 ) or ( 0, -1 ) giving him a position just behind the center. Now all the code does is find the current formation in the XML structure, loop through all the player offsets and set the corresponding player object’s “Destination Position” ( remember it’s a “Move Object” ) to be the field position given the current line of scrimmage ( ie., ball position ) and the player offset. You can see this every time you pick a play and all the players move to their formation positions. Simple!
Player Control
When the ball is hiked, the player has control over the quarterback. Player control is really simple: it just overwrites any “Move Object” velocity / acceleration with values depending on keyboard input ( WASD ). This code happens for any player that is on offense and has the ball.
The Ball
The ball is ( like the players ) a Move Object. Most of the time, it’s offscreen and not being processed. When it is, there are 3 basic states it can be in: Hiking, Pitching ( for run plays ) and Passing ( for pass plays ). They all share the same basic logic: give the ball a destination position and let it move there until it hits a player or ( in the case of a pass ) hits the ground. In hiking and pitching, as soon as the ball hits it’s target player the ball goes back to being inactive and that player is now flagged as the “ball carrier”. In passing, the only differences are that 1) if the destination position is reached and there isn’t a player around, it’s flagged as an incomplete pass and 2) the ball is scaled up bigger as it gets closer to its peak ( which is just the middle of it’s movement vector towards the destination position ).
Plays
Plays are defined using another XML file ( Plays.xml ). There are a few miscellaneous values for every play, but the big guys are 1) the formation 2) the possible defensive plays to be used against it if it’s an offensive play ( more on this later ) and 3) a list of “player jobs”. The player jobs are the bread and butter of the game and what give the AI life! Every player is given a “job” at the start of a play and each job has it’s own specific AI. The great part about it is that there are actually only a handful of jobs that cover the entire game across all plays. Here’s the breakdown:
Pattern - A pattern job is used for receivers and runners and simply specifies a list of offsets from the player’s starting position that they run to. Runners will stop at their final offset position when they reach it. In the case of receivers, they’ll simply loop back and forth between the last and second to last points.
Tackle - This job simply tells the player to move towards the ball carrier ( and in some special cases the ball itself while it’s in the air ) and try to tackle them. If a player is tackled, they simply stop processing their AI for a random amount of time… as if they’re lying on the ground. The tackle job also has the option of telling a player to move to a specific position first and then start moving towards the ball carrier. This allows for blitzers, for example, to move to the outside first and then towards the player… otherwise they’d try to go straight through the offensive and defensive lines!
Block - This job tells the player to move to a position and stay there. If anyone bumps into them, they should stall that player for a random amount of time ( while they both play a funny little “blocking” animation ). Once that time is up ( which is between 0.5 and 2.5 seconds ), the blocker will tackle himself to get him to stop processing AI for a bit and let the other player continue on his way. In addition to the “Block” job, there are 2 other special jobs called “BlockLeft” and “BlockRight” which do the same thing as “Block” in addition to moving both players to the right or left. These are only used in the “QB Sneak” play to open up a gap for the QB to run through.
Cover - A cover job is really simple: move to a position offset from where the player starts and just stay there. They also are given a slight bit of movement so they’re not standing completely still at their position. This job is mainly used for line backers: they’ll drop back and “cover” a position on the field.
CoverPlayer - Even though it’s still very simple, this may be the most complicated job type. When the ball is snapped, this player finds the closest player on the other team and selects him as the player he’s going to “cover”. He also selects a random distance to stay away from that player. Throughout the rest of the play, the player simply tries to stick to the player he’s covering like glue at his specified distance. Think of a bubble of a random size being around the player he’s trying to cover and the job AI essentially keeps him as close to that player as possibly without entering the bubble. In addition to this AI, there is a small chance that this player may “blow his coverage” and fail to cover this player initially. All that happens here is the player uses the “tackle” job for a very small amount of time at the beginning of the play. After the time is up, he’ll resume normal “cover player” logic. What this does is basically stall this player for a bit and gives the receiver a chance to break away and be open. Having him use the normal “tackle” AI makes him run towards the QB ( since he’ll be the ball carrier ) for a brief moment gives the illusion that he screwed up his coverage.
Protect – The last job is the “protect” job and moves the player towards the closest player on the other team and will “block” them. This gives the illusion that your team members are helping out the ball carrier by blocking for them as they make their way up the field.
That’s it! These 6 jobs make up the entirety of the AI in the game! The great part is that even though they are all very simple and “dumb” AI, together they’re able to pull off some convincing AI!
So, the main play sequence is as follows:
1. User picks a play
2. All players are put into their formation position
3. The player hikes the ball
4. All players now process their job AI until the play is over ( incomplete pass, ball carrier tackled, out of bounds, etc. )
Special Case AI
When a play starts, all the players simply perform their job until the play is over. However, there are some special cases where a player’s job changes:
1. When a runner or receiver gets the ball ( or if the QB holds on to the ball and runs past the line of scrimmage ), all defensive players are put into the “tackle” job and all offensive players are put into the “protect” job.
2. If an interception occurs, all defensive players are put into the “protect” job and all offensive players are put into the “tackle” job.
3. If the defense play is a “blitz” play, all players in the “cover” or “cover player” job will be put immediately into the “tackle” job after the ball in thrown and will try to also move towards the ball itself. Without this logic, the receivers have it too easy on blitz plays since the middle of the field is so wide open. I found that allowing the remaining defenders to get a jump on the ball evens this out a bit.
Defensive AI
For every offensive play, there is a list of which defensive plays should be chosen from. The defense simply randomly selects from any of these plays. However, there is always one play that will work best against any particular offensive play. For example, the best defense against a “run left” is a “blitz left”. If a player selects the same play twice in a row, the defense AI will always select the best play. In addition to that, all players in the “coverplayer” job will choose a very tiny “bubble distance” so that they’ll stick much closer to the player they are covering to make it harder to throw to them. All of this is an attempt to force the player to choose a variety of plays and not continue to use the same one over and over.
Sounds
I couldn’t not mention the sounds! I used the amazing tool sfxr to generate all the 8-bit sounds. Easy to use and works great! If you want 8-bit sounds, this is all you need!
That pretty much sums up how the game works at a very high level! There are obviously many more pieces to it ( how the clock works, keeping track of downs, the play selection UI, win and lose conditions, etc. ), but they’re all pretty straight forward and probably boring to talk about, heh.
I hope you have fun with the game and enjoyed the write up on how everything works! If anyone has any questions or wants more specifics… just let me know! And if enough people show interest, I’ll get the code all packaged up to release as well!
Have fun!
No comments yet.