¡Nuevo! En español. (Spanish translation by Andrés de Pedro)
GameDevLessons.com has migrated to HobbyGameDev.com - please update links!
Chris DeLeon's GameDevLessons.com Newsletter
Vol 5 - August 8, 2009
Hello!
I'm Chris DeLeon (about me), and thank you for joining me for my monthly videogame development newsletter, vol. 5. This series is one of the ways that I aim to help new game developers get started, while helping current game developers take their work in new directions.
Lately, Andrés de Pedro has been generously translating the GameDevLessons.com newsletters into Spanish. Boletín 1 and Boletín 2 are already completed, with more on the way soon. If you have any Spanish speaking friends that would be interested in these resources, please take a moment to pass links along!
II.) Beginner - Programming Fundamentals
Programming Fundamentals in Game Context
The If Statement (Conditional)
The Else and Else-If Statements
III.) Intermediate - Level Design
Rapid Prototyping Methods for Spatial Levels
IV.) Advanced - Knowing When to Hack
Videogame Development Pros Know When to Do It
V.) Up-and-Coming Developer Nominations
Off to the Right Start? Want Free Publicity?
Keep up with each new newsletter - free!
To read previous editions, or subscribe: http://gamedevlessons.com/?page=free
If you found this link someplace other than your e-mail inbox, and would like to be notified when the next edition is available, you can join my spam-free mailing list. It only takes a minute, I will never send out more than one e-mail each month (only to announce these newsletters), and it's easy to unsubscribe at any time.
The newsletters are not intended to be cumulative - so you shouldn't need to read all previous editions to understand this one - but the different topics in each may be helpful to you. In particular, if you're new to my newsletters, I recommend Newsletter Vol. 1 for its non-technical conceptual introduction to programming, as well as its links to free resources for image editing, audio work, 3D modeling, and other game development asset creation.
II.) Beginner - Programming Fundamentals
Programming Fundamentals in Game Context
The goal of this section is certainly not to immediately transform someone with no prior programming experience into a game programmer. That takes time and practice, reading and trying, experimenting and thinking, over a period of weeks (basics), months (intermediate), and years (expert). This is not intended to take the place of a good book on programming, or practice - it's here for warm up, review, or to complement other resources that are out there by providing a connection to how the most common code structures are used in videogame development.
My hope is that I can plant seeds of familiarity, and establish context of use for the various elements of programming. (Lost already, or a bit unclear on what programming is? Check out Newsletter Vol. 1!)
The following examples are given mostly in C code, taking advantage of a little bit of the flexibility given by a C++ compiler. C++, Objective-C, ActionScript 3, and Java use the same or nearly identical structures for the same functionality, and those are the major programming languages used to develop today's console, downloadable, mobile, and web videogames.
Finally, the text in this section introduces a lot of material, in a very short space for the amount presented. If you're new to programming, it will take more than one read, but there's a good chance that you'll pick up something new each time. Remember: programming is how every videogame on the market (Wii, online, mobile, 360, PS3, SNES, Atari, Arcade...), every piece of software you use (Office, Windows, FireFox...), and every electronic device works. There's some patience involved in understanding how all these things work. Stick with it - you'll be glad you did!
What it is:
Whitespace refers to the gaps between letters within program code, whether represented as tabs, spaces, or new-line breaks (as from the Enter key). Many modern programming languages treat all whitespace the same, meaning that whether you skip lines, indent a certain distance, put two spaces instead of one, or use tabs instead of spaces, the program code will work exactly the same way.
How it looks:
for(int i=0;i<5;i++){showNumber(i);doOtherThingToo();}
...works the same as, but is not as easy on human eyes as...
for(int i = 0; i < 5; i++) {
showNumber(i);
doOtherThingToo();
}
Example of a use in videogames:
When a program's source code doesn't compile correctly, especially for a beginning programmer, it's frequently due to a mismatch between braces (the { and } symbols). Effective use of indentation to indicate how many pairs of braces are around code helps keep track of where braces should close. All errors in programming code are reported by the compiler using line numbers, which are only useful in leading the programmer to the problem if the code is broken onto new lines after each individual instruction is ended by its semicolon.
What it is:
Text visible only to the human reader, that the computer ignores when it's time to generate ("compile") a program from the program code.
How it looks:
// Text after 2 slashes on the same line is a comment
/* ...and so is anything typed between the slash-star and the star slash. This type of comment, unlike the double-slash style, works on multiple lines. */
Example of a use in videogames:
If you program something at the start of a project, then return to that part of code weeks or months later, you may have trouble figuring out what you were intending when the original code was written. Does the collision detection algorithm assume two objects weren't already overlapping in the previous frame? Is there a limit to how many badguys the game's graphics code is intended to render at once? Comments are a perfect way to record this thinking alongside the code it's relevant to, to ensure that it's passed along to your future self, or anyone else that might wind up reading the code.
What it is:
It is up to the programmer to create names for most things used in a program's code - names to keep track of numbers that are being used differently, to refer to text phrases that serve different purposes, and names for different chunks of code to succinctly describe their functionality. The computer doesn't care what these names mean to people ("drawStuff", "ElvisPants", and "BV3_aUiP0" are all equally valid) - but their consistent usage in different parts of the program is how the programmer stores, changes, and checks values for different purposes.
Even though the computer can't tell the difference between a function for updating the screen called "UpdateScreen" and one called "DogsGetExcited", the former is a smarter choice because it reflects how the programmer is using the label. Note that whatever names you use inside your program are 100% invisible to the users and the outside world - they're just a way to stay more organized about the numbers you're shuffling around.
How it looks:
moveBadguys(); // good name for a code chunk
int playerPositionX; // clear name for a number
abc123(); // terribly unclear name
int r; // bad. radius? red value? radiation? roundness?
Example of a use in videogames:
If you're good with naming all the numbers, code sections, files, and other labels needed by your program, the code can become impressively clear to anyone browsing through it. That means less time is needed for writing extensive comments to explain exceptions or clarify meaning, and far less time will get drained into untangling confusion (or problems arising from confusion) based in poor name choices.
What it is:
With only very rare exceptions (ex. very advanced topics related to PS3, XBox 360, and high-end computer programming), program code executes only 1 instruction at a time, 1 step at a time, top-to-bottom just like English text is read. Every instruction goes on its own line, for sake of human readability, and must end with a semicolon, for sake of machine readability.
How it looks:
doThing(); // happens first
doOtherThing(); // then this
lastThingHappening(); // happens last
Example of a use in videogames:
moveByPlayerInput();
detectCollisions();
drawEverythingToScreen();
...is a smarter order than...
detectCollisions();
moveByPlayerInput();
drawEverythingToScreen();
...because in the second ordering, character positions aren't updated to account for collisions that could have happened by the player's input until after the current frame is drawn to the screen. That means the player way wind up seeing things overlapping one another, which could have been easily avoided by simply switching 2 lines of code!
What it is:
A mostly self-contained solution to a recurring problem. In its simplest form, it's simply a group of code that can be called from any other place in code, any number of times, with a one word label. More involved functions can accept input values for use in calculation, and/or can output new values reflecting the results of its processing. The parenthesis pair always show up after a function name when it's called; if it is a function that takes input values, those values would be specified between the parenthesis.
How it looks:
Declaration
void doThing(void) { // a function with no input/output
// ...code goes here...
// every time the function is called
// this code will execute, top-to-bottom,
// from the function's starting brace "{"
// to the function's ending brace "}"
}
Call
doThing(); // does the "code goes here" from its braces
doThing(); // does that again (we called it again)
Example of a use in videogames:
Grouping related code together into functions helps organize code into more readable sections A function called drawStuff() might contain calls to all other draw functions, such as those used for drawing the background, player, enemies, powerups, weapon projectiles, and health information, which in turn could call other functions that handle the details of how to display the necessary images in the necessary places. This approach enables you to separately take a detailed or big picture look at different parts of the program, breaking the functionality up into understandable chunks. It also centralizes code that's needed multiple times into 1 place, where it can be fixed or updated only once if something is found to be broken in its behavior.
What it is:
Technically, it takes the contents of another file and dumps them word for word into the file that features the include statement.
Practically, it determines which pre-made functionality you have access to using. It works this way because most of the files we include are ones that define pre-made functions for us to call in our programs - the include statement automatically injects those definitions to the top of our file. Programming languages come with many libraries prepared for things like displaying text, reading keyboard input, accessing network hardware, and performing comparisons/manipulations of text.
How it looks:
#include <stdio.h> // lets us write text on the screen
// "stdio" stands for "STanDard Input Output"
...later in the code...
printf("This will show up, thanks to stdio.h!");
Example of a use in videogames:
Additional libraries can be downloaded from the internet (like Allegro, SDL, or DirectX in this case) that enable you to make simple function calls to load image/audio files into memory, change the screen resolution, display images to the screen, play sound effects, and handle fluid keyboard/mouse/joystick input. People have earned their PhD's from finding faster ways to get a computer to draw a line - don't waste your time trying to re-invent the wheel, when generations of computer researchers have been tackling very hard problems so that you won't have to. Instead, find and include libraries that have their brilliant solutions inside them! This allows you to focus your programming on what your videogame does differently with those graphics, sounds, and input events, instead of how to simply get them to work.
What it is:
A Variable Declaration is a line of code that sets aside a named container for a value. It's up to you as the programmer to come up with a name that you think is suitable. That value is often a number, a letter, a series of letters ("string"), or object (group of numbers, strings, and/or other objects) - to be saved, updated, and checked within code.
How it looks:
int health; // Lets us store/change/check 1 number
int lives; // To store/change/check a different number
// int stand for INTeger (meaning: a whole number)
Example of a use in videogames:
Every distinct horizontal position - how far a player, badguy, powerup, or particle is from the left side of the screen - must be tracked with a distinct variable.
Every distinct vertical position, too.
The same goes for how much health each character with health has, how many characters are alive at once, how much magic power the player has left, which powerups the player has in inventory, what level the player is on... if it's something that has to be remembered even temporarily, and may not always be the same number, it needs a variable. You'll be declaring lots and lots of variables.
What it is:
A "type" is any form of variable. Some of the most common are int (integer, non-decimal number), float (floating point, refers to a number with accuracy finer than whole numbers), and char (character, i.e. letter).
How it looks:
int thisIsAWholeNumber;
float thisCanBeADecimalNumber;
char someonesFirstInitialCanBeSavedHere;
Example of a use in videogames:
The number of lives would be an integer, since it is counted in discrete units. A rocket projectile's speed and position would be floats, since for smooth motion with acceleration finer variation is needed than whole numbers allow. A player's initials on a high score screen would be stored as char.
What it is:
The equal sign in programming, unlike the equal sign in math classes, is not the same to both sides of an equation. In fact, it isn't used to write an equation - it's used to create a statement of assignment. Where there's a single equal sign, whatever is on the right side gets evaluated (added together, multiplied out, substituted in, etc.) then stored into the variable to the left of the equal sign. That is, a new value is assigned to that variable name.
How it looks:
int temporaryNumberStorage; // creates a variable label
temporaryNumberStorage = 5; // saves 5 to that variable
// read it as, "store 5 into temporaryNumberStorage"
// This next line is an example of what NOT to do:
5 = temporaryNumberStorage; // meaningless! BAD!
// Say, "store temporaryNumberStorage into 5"
// That sounds like nonsense, right?
// What does it mean to save anything into 5?
// Your computer doesn't know, either :)
...another example...
int num1;
int num2;
num1 = 10;
num2 = 35;
num1 = num1 + num2 - 2;
// num1 winds up 43 (from 10 + 35 - 2)
// num2 stays 35, because it's right of the =
Example of a use in videogames:
Setting a player's number of lives (assuming "int lives" has been declared earlier in the code):
lives = 7;
Subtracting 1 life:
lives = lives - 1;
Giving 2 extra lives:
lives = lives + 2;
Beware that the following line does not actually change the value of lives, since there is no equal sign acting as an assignment operator (=):
lives + 1; // Does not change anything! No = sign!
Since that previous method of adding or subtracting involves typing the variable name twice, there's a slightly faster way that programmers invented to mark that addition or subtraction that saves into a variable with the assignment operator (=):
Subtracting 1 life:
lives -= 1;
Giving 2 extra lives:
lives += 2;
You can write it either way. "Var = Var + num;" is the same as typing in "Var += num;" and that also works for multiplication (*), divison (/), and subtraction (-). Lastly, the addition or subtraction by 1 is so common in code that there's a way to do that without using the assignment operator:
Subtracting 1 life:
lives--;
Add 1 life:
lives++;
(That's where the programming language C++ got it's name. It's the same as the C programming language, but with stuff added on top.)
What it is:
A way to organize multiple variables together into a meaningfully named grouping. As with function names and variable names, the programmer invents these names, ideally choosing a name that reflects how the group of variables is intended to be used. In C, it shows up as a struct, which only contains a grouping of variables, but in C++ and most other more recent languages it can be class, which has the ability to also group functions, plus a few other benefits.
How it looks:
struct character {
int health;
int x, y;
};
character thePlayer;
character badGuy;
thePlayer.x // variable for the player's x position
badGuy.health // variable for the badguy's health value
Example of a use in videogames:
Objects - whether structs in C or classes in newer programming languages - are essential to keeping organized a project grows to include many different kinds of characters, environment pieces, data formats, and other conceptual groupings of variables. The example above is a very simple, fairly typical use, but could be expanded to account for each character's image, mood, momentum, rotation, animation frame, inventory, etc.
What it is:
An array is a way to store a sequence of variables, such that they share a common label (think street name) but different index numbers to distinguish them from one another (think house/building address).
How it looks:
// The number in [brackets] declares the array's size.
// Array addresses start at 0, so the highest number
// we can use is 1 less than the size of the array.
// Defining array size [3] gives us [0],[1], and [2].
int numbers[3]; // creates memory for 3 int variables
numbers[0] = 1000;
numbers[2] = 70; // acts as 3 separate numbers!
numbers[1] = numbers[2] + numbers[0];
// numbers[0] winds up as 1000
// numbers[1] winds up as 1070
// numbers[2] winds up as 70
// 3 separate numbers, 1 common label
Example of a use in videogames:
Any time there are many of something, and each something behaves the same way or based on similar rules, there is probably an array or similar data structure involved. Particle effects, enemy characters, units on the battlefield, projectiles, and so on are often tracked using arrays, utilizing The Object structure of a language to group each individual thing's variables together.
An array of letters ("characters" or chars) forms a "string", or word/phrase which can be used as a name, displayed on screen as text, etc.
The If Statement (Conditional)
What it is:
Checking whether one or more mathematical comparisons or functions evaluate to true, and only doing a section of code if it is. Valid comparisons include:
a < b (a is less than b?)
a > b (a is greater than b?)
a <= b (a is less than or equal to b?)
a >= b (a is greater than or equal to b?)
a != b (a is not equal to b?)
a == b (a is equal to b? Remember, 1 equal sign is assignment!)
Multiple statements can be combined via boolean logic
(a < b) && (b > c) meaning (a < b AND ALSO b > c?)
(a < b) || (b > c) meaning (EITHER a < b OR b > c?)
How it looks:
if(lives > 0) { // "if the player is alive"
// ...move and draw the player here...
} // otherwise code skips ahead to here
Example of a use in videogames:
Killing a character when it runs out of health, forcing the player to stay on screen if the x or y positions go outside an acceptable range, checking whether a gun needs to be reloaded before allowing it to initiate the reload animation/code, etc.
The Else and Else-If Statements
What it is:
Using a section of code only if the result of an if statement turned out false.
How it looks:
if(lives > 0) { // "is the player alive?"
// ...move and draw the player here...
} else if(continues > 0) { // "has continues?"
// ...go to the continue screen...
} else { // "if the player isn't"
// ...play sad music...
// ...go to game over screen...
}
// Note that you can have as many else-ifs as needed
// You can even just have an else right after an if
// The presence of any else or else-if is optional
// If there is an else, it must come last in the chain
Example of a use in videogames:
It's extremely common to use this type of series of evaluations in games for a computer controlled enemy to evaluate circumstances with a pre-determined priorty, to handle a series of circumstances that might cause a collision to detonate a projectile, or to handle menu item selection.
The switch-case sequence (not covered here, but easy to find on the internet) is an alternate construction available if the else-if chain is checking matches against consecutive integers (is it 1? otherwise, 2? otherwise, 3? etc.).
What it is:
It works a lot like an If Statement, except that when it closing brace is reached by the code, it checks the While condition again. When the while condition is still true, it goes through the code between its braces another time; if the while condition's comparison expression is false, then code continues after its closing brace.
How it looks:
while( gameHasBeenLost == 0 ) {
useMouseInput();
moveEnemies();
updateScreen();
if( health < 0 ) {
gameHasBeenLost = 1;
}
}
Example of a use in videogames:
Its most common usage is shown immediately above - the game's logic and screen updates occur constantly within a while loop, until something happens within the program (a key is pressed, a menu item is clicked, lives run out, etc,) causing its comparison expression (here "gameHasBeenLost IS EQUAL TO 0") to no longer be true.
Take heed that with any loop, the risk of an "infinite loop" - code that never stops executing - appears if nothing inside it happens that will turn its expression condition false. If your program is being run in an old terminal, and seems to be stuck, CTRL+C will bail from it. If all else fails, CTRL+ALT+DEL (PC Windows) or Force Quit (Mac) will easily and safely terminate a program that is trapped in an infinite loop.
Note that the above usage is no longer common in certain newer languages, particular ActionScript 3, which instead handles game logic best in the form of setting up a timer to call a particular function some number of times per second. (Not shown here.)
What it is:
The For Loop groups together a common and very useful pattern of programming: set up a variable to be used in a while loop, declare the comparison expression for the while loop to evaluate, and do something each time through the loop to affect the loop's variable. It's commonly demonstrated as an easy way to count from one number to another, but it can also be used to scan across every letter in a word, check every pixel of an image, or call a function on every badguy/item in the world.
How it looks:
for(int count=0; count<100; count++) {
// ...do something here...
// it will happen 100 times.
}
...that behaves exactly the same as this...
int count=0;
while(count<100) {
// ...do something here...
// it will happen 100 times.
count = count + 1; // Remember! Same as "count++;"
}
Example of a use in videogames:
Going through each badguy and updating their position, where each badguy is an object in an array, by calling a function on the badguy in the array position denoted by the for loop's counting variable.
To play, move your mouse to position the paddle, and click to reset the ball when it is off the screen. Press the Escape key to quit. The zip file includes pre-compiled binaries for both Mac (breakout-mac) and PC/Windows (breakout.exe) - just double click those to play. The instructions to recompile these programs on your own are below - so that you can experiment making changes to the code to see how it works.
Download Dev-C++ 5.0 beta 9.2 (4.9.9.2) (9.0 MB) with Mingw/GCC 3.4.2. After it's installed, inside Dev-C++ go to "Tools" then "Check for Updates/Packages", then under "Select devpak server" choose "devpaks.org Community Devpaks". Press the "Check for Updates" button, then check the boxes to install Allegro (the library for audio, input, and graphical functions) and Allegro Supplement (documentation and sample files). After that, open up the breakout.dev file I've included, which already has the Allegro library added to the Parameters section of Linker under Project Options (as "-lalleg"). Then use the menus to go to Execute, Compile & Run. Any time you make changes to the program source, you can update the game itself by repeating that last step to Compile & Run.
Download Xcode for Mac-only Development from Apple's website to get all developer's tools installed. Also download the Allegro source for Linux (I found that here). Double click that downloaded allegro tar.gz to turn it into a folder, then use the Terminal to 'cd' command your way into that new allegro-4.2.2 folder (need to learn the Basic Terminal Commands?), then type the following lines into the terminal to install Allegro:
chmod +x fix.sh
./fix.sh macosx
make
sudo make install
sudo make install-man
sudo make install-applehelp
sudo make install-framework
make
sudo make install
Followed by using the Terminal to navigate into the folder containing the Breakout game sample source. Then type in this command:
g++ main.cpp core.cpp -o breakout-mac `allegro-config --libs`
NOTE that the ` is a BACKTICK on the same key as tilde (~), NOT a single quote/apostrophe from the same key as the parenthesis (").
To edit the code, open main.cpp using any plain text editor (TextEdit, TextWrangler, DashCode...). To recompile the program to reflect changes made to main.cpp, repeat the step above for typing "g++ main.cpp...[and the rest]" in the Terminal.
If you're using Linux, I think you can figure this out. :)
Pro Tip Hint: check out the instructions for the Mac section.
III.) Intermediate - Level Design
Every decision in a level's design is a conscious act by the level designer.
Levels aren't made by placing walls; levels are made by planning. Once a level developer is accustomed to the toolset at hand, and the underlying game engine, emphasis shifts from placing "walls" to placing "rooms," and from placing "enemies" to designing "encounters."
Odds are, you've never played more than one published title that used the same underlying level design philosophy. What works in one game, given its AI, weapons set and player interface generally does not translate well to another title in the same genre. Goldeneye N64 levels make poor Doom levels; Doom levels make poor Unreal Tournament levels; Mario levels wouldn't work for Sonic and Sonic levels wouldn't work for Mario.
Why bother with level design philosophy at all? Why not just make maps that are different every time? Just like there are conflicting philosophies that don't work well between games, for every game there are some overriding points that can be kept in mind to make the most of the engine. Here we'll look at a few particularly different ways of thinking about level space, since by separating them out, we're better able to switch between them deliberately as best fits the current videogame project's needs.
Some games focus on environmental realism, to the point that most of the levels are designed to feel like they are almost incidental to the gameplay experience taking place there (ex. Hitman, Rainbow Six). For these games, most of rooms, hallways, and open areas feel like they were laid out without special emphasis for the player start, ammo/health boxes, or enemy placement locations. An architect by training is most likely to design this type of map, so we'll call it the "Architect's Design." It provides a strong sense of immersion when it's done well, since real buildings aren't laid out linearly for mission objectives, but it can make for awkward flow that confuses first time action players.
Other titles focus on flow of action. Halo is the unmatched example of this design strategy, using Architect's Design to compensate for the disarray of the battlefield. The player is rarely left wondering where to go next, since there are typically shots, yelling, and action taking place where he/she should go. We'll call this the "Fireman's Design," since it results in the player rushing from point to point to "put out fires." This requires a considerable amount of event scripting, and doesn't leave much of an opportunity for the player to rest. This risks hurting replay value by making interesting things happen predictably the second time around, but it can offer an extremely cinematic experience (ex. Medal of Honor, Call of Duty franchises).
Some games lure the player around via exploration. Tomb Raider and Descent both relied at least in part on this "Curiosity Lure" (the player's left thinking "maybe this pathway leads to the exit?"). Without careful attention to attractive landmarks in the distance, and clear visual distinction between different rooms, it can lend itself to arbitrary map layouts, leaving the player wandering in cycles through corners for the next area to search through. Tomb Raider, for the most part, succeeded in doing this well, whereas Descent (unfortunately) did it very poorly. In a more action-oriented game the wandering is made even worse by going frequently through areas well after the interesting part of that genre (the enemies!) have been eliminated.
The name of this technique comes from the old Brothers Grimm fairy tale, Hansel and Gretel, in which a young boy and girl leave a trail of breadcrumbs behind them to find their way back home. It's "reverse" because, in this method of level design, breadcrumbs are scattered everywhere by the game's designers, and the player finds their way by picking them all up.
id Software used Reverse Breadcrumb in Doom, but it has been around at least since Pac-Man, and appears to this day in modern FPS games. Reverse Breadcrumb laces corners, hallways, side rooms, and action areas with cheap, low-value items of cumulative worth. +1% health, +1% armor, and small ammo packs are ideal for this, but in some games it's easily defeated badguys that. Besides giving you as the level designer an instrument of instruction to show the player where to go, it provides an immediate visual test for the player to mark off areas he's/she's already visited. If there are no more items (or weak badguys) in a room with several branching hallway exits, the player only has to explore whichever hallways still have items to find new areas of the map.
This approach constantly rewards the player, and leads to most or all of a map's areas being explored in turn. Care needs to be taken in a map designed with Reverse Breadcrumb to minimize the depth of dead ends, to avoid the player winding up stranded without cheap items as hints.
Although Painkiller is among the few recent commercial games to exploit this design strategy, games like Robotron, Mega Man, and Zelda established its place in history. The concept behind "Arena Traps" is to have the player fight battle after battle in isolated architectures. This avoids player's using kill zones to take advantage of deterministic AI. It implies that the world has an overriding, malicious intelligence manipulating the player's environment, but that works so long as the story takes the player to an evil dungeon, a trapped temple, or alien den.
Jumps, keys, physics engine exploits, and remote switches or time trip wires dominate puzzle based games. It's rare to see an entirely puzzle based game anymore, but some degree of puzzle is more likely than ever to find its way into every game on the shelf. Prince of Persia had a few undisguised puzzles in the story, as did Quake II and Tomb Raider. Portal has more recently repopularized puzzles in the form of intricate navigation.
When the player is stringed from one location to the next, but they feel like it's their idea each time, then the level design is Disguised Linear. Done right, this describes a map that plays linear but doesn't feel linear. Max Payne and Half-Life are the best-selling games with this structure. It can significantly detract from replayability, and can also raise some issues of the designer having more fun than the player; if the player doesn't get to decide where he/she will go next, and instead the level designer does, who's really playing? On the plus side, it typically means the player won't get lost, and the emphasis of the gameplay is on action or platform/key puzzles rather than exploration.
A non-linear level structure (like one with a centralized hub and many hallways) can quickly be turned linear by use of keys, remote door switches, and other order-of-play constraining mechanisms. The workaround to avoid this problem is to take a Mega Man boss weapon approach, so that although the order of action is up to the player, there's an ideal order that the expert player will take to optimize level performance. For these types of unenforced Creatively Linear level designs to work, the player must be given clear clues or signs of what is to be found in different directions – perhaps by imposing architecture, detailed red warning patterns on the floor, or puddles of damaging green goo.
Most commercial games don't follow any one formula. The most interesting level design philosophies are those that manage to take the elements that work from a variety of design angles. Don't be afraid to experiment, but when you do so, you'll probably want to test it in a smaller map before you build it in as a crucial point of a larger map.
Invent some terms, like those used here, and categorize your thoughts into discrete concepts - it makes it easier to re-use the ones that turn out well, easier to avoid re-using the ones that turn out poorly, and easier to mix new combinations of what worked well to generate more intricate levels for later parts of the same game.
Rapid Prototyping Methods for Spatial Levels
The scientist's approach. Begin by drawing a few circles on a blank page to represent major rooms, areas, hubs or hallway intersections. Draw a few lines between these nodes, until everything is connected in at least 1 way. Next, put your mind to thinking about how you'll run the player through these loops – item lure? Strictly ordered key puzzles?
Advantage: Fast way to sketch out how your level can have complex interconnectedness, without holding yourself back to conventional orthogonal hallways or linear path cutting as you might be tempted to do in most level creation environments.
Disadvantage: Leaves a LOT of unanswered questions about the level's theme, and the layout of rooms. This prototyping definitely should be used only in conjunction with another method, such as Pipelining or Area Sketching.
The builder's approach. Begin on a piece of paper with a rectangle, pentagon, or a more interesting shape, like a giant hand (Doom, map E3M2 did this), and then recursively divide that space into rooms.
Advantage: Can create a more realistic building space (no wasted area between rooms or halls). Doing it freehand on paper then transforming it into your game's level format reduces the tendency to work strictly orthogonal or on-grid.
Disadvantage: Leads to a map that is potentially too visually cluttered. If the overriding shape is not broken carefully, it can also yield a map that feels too contrived.
The mechanic's approach. This can be done equally well in most level building tools as it can on graph paper, if you're sufficiently comfortable with its environment and tools. Basically, form the level out of hallways, placing important items and keys in intersections or corners. Go back and bloat certain areas of the pipe into room-sized combat areas, add a little decoration based on a given theme, and call it done.
Advantage: As fast, if not faster, than any other method, without creating trivially simple maps.
Disadvantage: Often results in an unrealistic environment, and it takes some practice and patience to identify which areas of the pipeline should be bloated.
The beginner's approach. Basically, steal from anything and everything you can. Copy areas and layouts out of buildings you've been in, attach them to movie sets you've admired, and connect them altogether with an order of events that worked in your favorite game. As long as you're mixing multiple sources, you can result in some fairly decent levels while learning from the pros along the way.
Advantage: Creates much better content than you could have on your own.
Disadvantage: Makes you feel dirty and guilty inside. Do it if you're looking for a way to learn as you go, but try to wean yourself of it a.s.a.p.
The architect's approach. This actually involves sketching out the area as if it really existed – where do pipes go? Electrical wires? How and why are different areas (locker rooms, restrooms, offices, closets) placed where they are in relation to everything else?
Advantage: This method leads to the most believable environments.
Disadvantage: Slow, slow, slow. Maybe try it out for some early maps, but in the end, most of the money, energy, and time should be spent tending to the content that the player directly sees and experiences. Strive to see if you can figure out some patterns or rules to follow to simulate the same effect without putting so much invisible work into it.
The artist's approach. This technique is very popular in the industry, and often shows up in collector edition released concept art. Make 3-D sketches of areas you'd like to fight in – from the player's perspective, or from a "security camera" ¾ angled perspective from a room's corner against the ceiling. How do obstacles fit together? From where will the enemies come at the player?
Advantage: Creates memorable areas, vastly more so than any other means of design.
Disadvantage: Requires a little art talent, and some random inspiration. Do this when you can, but don't expect it to work for every room of every level.
This is the game designer's approach. Unfortunately, it's also a lot less formulaic in how to perform it properly. Knowing some of the other prototyping methods are there to work from, and how to articulate the different aspects to team members, is essential to succeeding in hybrid level prototyping. As design schemes can be mixed, so too design prototyping methods can be mixed.
IV.) Advanced - Knowing When to Hack
"If everything seems under control, you're not going fast enough."
-Mario Andretti
The word "hacking" doesn't mean to programmers and software developers what it does to laymen. It used to mean committing computer crimes, but today that is more often referred to as cracking, or with an extra preposition added in the phrasing "hacking into" a computer. When people first started editing commercial videogames, like Command & Conquer or Doom, using homemade tools that the commercial game developers didn't officially endorse, that was what hacking referred to for awhile... but these days that's called "modding." So when we use hacking here, we're using it's most common usage among modern programmers: writing sloppy, on-the-fly, rushed code that's the bare minimum to accomplish the task at hand.
Hacked code is known for being difficult for anyone to understand other than the person that wrote it, for being inconvenient to update/maintain, and for generally not reflecting good habits and principles of scalable programming.
Any reputable Computer Science or Engineering education will develop a scornful disdain for hacking in every student that passes through its doors. Researchers and professors and systems professionals will frown upon the idea strongly. If the habits of hacking seem deeply ingrained in a potential employee, a recruiter or interviewer may find a person unemployable.
Videogame Development Pros Know When to Do It
Videogame development is not exactly like research. Nor is it like programming an operating system, compiler, telecommunications satellite, or missile defense system. Those projects have extremely strict requirements in terms of functionality, stability, maintainability, and code readability. The time and money that go into those serious projects is often tightly bound by business calculations, without much passion or love or creativity from team members pulling extra hours from their personal lives to cram more love and cool stuff in. If something goes wrong with that serious software, then someone's degree is at stake, or businesses will lose millions of dollars in missed business opportunities, or soldiers will be killed. If something goes terribly, terribly wrong in a videogame, the player may have to replay 20 minutes of something they hopefully enjoy doing anyway, and it'll lose a few points in reviews.
What this means is that there absolutely, positively, without a doubt, is a time and place for hacking in videogame development.
But when?
The original videogames in nearly every genre were only possible because of absurd amounts of awkward hacking. That includes the first videogame ever. That also includes Adventure, the first action-adventure videogame, from 1978 by Warren Robinett (PDF presentation slides, mirrored as PDF version of a PPT from Warren's site). It's true too for each of the major innovations in first-person shooters John Carmack created with id Software (Catacombs 3D, Wolfenstein, Doom, Quake), which were notoriously made possible through Carmack's willingness to write hard-to-read code when it was the only way at the time to render 3D-looking worlds on such early hardware. That was the only way due to factors relating not only to machine optimization, but also programmer communication overhead, budget limitations, and time potentially taken away from polishing other features of the games.
Beyond basic requirements, what can be done in a videogame is determined by tradeoffs, or opportunity cost. Opportunity cost is estimated based on time and staff required to make something possible. If it can be done by 1 person in 2 hours the "wrong" way (getting in the way of doing future things the "right" way), but would take 5 people 3 days to do it the "right" way (easier to update, more predictable effect on the rest of the system), then there are a few important considerations as to which path is preferred:
|
Are there times when hacking something in the fast way can come back to cause you trouble? Absolutely. Is it dangerous if overdone? Of course. Though wouldn't it be foolish to pretend like it isn't one of many options at your disposal, to be utilized when the risks are understood and can be reasonably mitigated? Yep.
Sometimes the race goes to the driver willing to do push their car a bit further, a bit faster, through spaces that are a bit tighter, than the other people on the track.
(But that option is only viable provided that the rest of the car is built well enough to survive being pushed that far.)
V.) Up-and-Coming Developer Nominations
Off to the Right Start? Want Free Publicity?
Beginning with next month's newsletter, I will begin recognizing and giving visibility to up-and-coming videogame developers.
Whether it's a 13-year-old that demonstrates maturity beyond his/her years in level design using specialized videogame building software, or an older moonlighting indie with a track record of cool work but little visibility, I want to help.
What is there to win?
|
Have someone in mind? Even if it's yourself? Simply take a few minutes to send me a short e-mail with the creator's name, website, a link to one or more of the creator's best games, a brief (3-10 sentence) description of the creator's accomplishments and (if known) a few words about the creator's goals to chris@gamedevlessons.com.
Chris DeLeon
PS I am writing this newsletter series to help people, and I want the contents to reach as many people as possible. Please pass along this link if you know someone that might find this useful: http://gamedevlessons.com/lessons/letter5.html Sending the link works better than copy/paste, since that way they'll see the latest updated version with Q&A or corrections.
PPS If you'd like to be kept up-to-date with these monthly mailings, simply subscribe to the GameDevLesson.com Monthly Newsletter: gamedevlessons.com/?page=free