How to Create Bombard
From codeTank
Let's create a game I like to call Bombard. Open up Brain Damage (minimum version 1.1.1), and let's get started.
Contents |
Think of the Game
The first step is always to plan. It's important to have a general idea of what you're going to create. Depending on the size of your game, it depends on how much planning you'll have to do. For really large games, this step will take quite a while. For simple arcade games, the planning stage is usually "I want my game to act like XXX", where XXX is a game you've played in the past.
For Bombard, I want to create a game where the user controls a box with their mouse. I want a bunch of boxes to fly around the screen, randomly. The object of the game will be for the user to maneuver their box to avoid collisions with the random boxes zooming all over the place. The longer they can survive, the higher their score. Easy peasy, lemon squeezy!
Get Something Running
The second step is to just get something up and running. For this game, we'll need a window. Here's some basic window code to get you started:
sh: highlight: command not found
You need to specify a language like this: <source lang="html">...</source>
Supported languages for syntax highlighting:
(error loading support language list)Pretty simple. Run the program. All it does is create a window.
window.create will create our window. If we don't provide it with any parameters, it will just make a default window. It will return the window object, and store it in the variable wnd. We'll want to keep track of the wnd variable, because that's probably how we'll access our window in the future.
The loop at the end has an important role. Imagine if we created our window, and then the program ended. Well... the window would get created... but then the program would end. And it would destroy our window. How can we avoid that?
Well, we need to loop around in a circle.
window.getcount will return the number of windows that are open. If we create a window, then window.getcount() will return 1 (because there is 1 window). In our loop, we basically want to keep going in circles while we have some windows active.
So, while window.getcount() > 0 will mean it keeps looping around as long as we have a window on the screen. Once the user clicks the X button on the window, window.getcount() will return 0. And since 0 isn't greater than 0, it will exit the loop - and exit our script.
But what does window.pumpmessages do? Well, all windows that are on the screen will receive messages. These messages will eventually turn into events, which we can capture later. But we need to push these messages along. In order to do that, window.pumpmessages will sit around and wait until a message has been generated. Once a message is generated, it will pump the message to the appropriate window. In our game, we only have one window open, so all the messages will go to our main window.
Configure the Window
Now that we have a window, we should configure it so it looks more like a game.
For example, we should definitely set the width, height, and title of the window. This way we'll know exactly how big our game arena is, because we have specified it explicitly. And a title is just nice, to let the player know what they're playing.
sh: highlight: command not found
You need to specify a language like this: <source lang="html">...</source>
Supported languages for syntax highlighting:
(error loading support language list)That's better :-). Now our game is taking a little bit more form. But there is still some more configuring to do...
Changing the Default Background Color
For me, the background color of a window is gray. It might be different for you. We should make it one universal color for everyone - something that is nice. How about a sky blue?
To change the background color, we need to process an event - onerasebkgnd. This event is fired every time the window needs to erase it's background. If we don't specify the event, then it'll just erase it with the default gray color.
Let's change that:
sh: highlight: command not found
You need to specify a language like this: <source lang="html">...</source>
Supported languages for syntax highlighting:
(error loading support language list)When the event onerasebkgnd is fired, it will pass two parameters: the window, and the paint object. This is always how it behaves. We use the paint object in order to paint to the screen.
Since pnt is an object, we use a colon instead of a period to call functions in it. We want to erase the background, so we just draw a solid rectangle over the entire window, using pnt:rectangle.
Run the program. Did our background color change?
No?! What the hell?!?! Run it again. Still no!?
What is the problem?
Well, I'll tell you exactly what's happening. The event is getting fired, and the blue rectangle is being drawn. But immediately after that, the default color is drawn over it. How do we stop the default color from being drawn over our hard work?
return false from the event. That will tell the window to not erase the background with the default color.
Try this, and run it again:
sh: highlight: command not found
You need to specify a language like this: <source lang="html">...</source>
Supported languages for syntax highlighting:
(error loading support language list)Run it.
You'll notice that the background color has changed. However, the blue is a little bit off. A little too dark, in my opinion. We can change that by changing our parameters to the pnt:rectangle function call.
The first two parameters is the upper left corner. The next two are the width and height of our rectangle. The fifth parameter is the border color - we set this to false because we don't want to draw a border. The last parameter is the fill color. We set this to rgb(100, 100, 255).
rgb is a simple function that creates a color, given a Red, Green, and Blue value. Each value can be between 0 (void of color) to 255 (maximum color). We set our light blue to rgb(100, 100, 255), but we need it to be a little lighter. The blue channel is already maxed out at 255 - to make it lighter, we just need to increase the red and green values. Increase them to 200, by changing the statement to: rgb(200, 200, 255).
Run it again. Now the background color is a nice sky blue :-).
Using Variables
Before we continue, we should notice something.
What if we ever want to change our background color? Well, we can just find that spot in the code, and change the values. Not too hard I suppose.
But what if we want to change our window size? Well, all we have to do is change the width and height parameters. Er... but wait. We also have to change pnt:rectangle parameters too, because they use the width and height as well.
In order to keep all this straight, sometimes it's a good idea to create a table at the start of your script that will configure all the little things like that. When you make a game, you should reference this configuration table, and never hard-code in values.
You don't have to go too crazy with it, but we might realistically want to change the window width and height at some point, or the background color. To make this easier, let's make a configuration table now - before we get too far into our game, and it's a pain in the ass.
sh: highlight: command not found
You need to specify a language like this: <source lang="html">...</source>
Supported languages for syntax highlighting:
(error loading support language list)That's better. Run the program - it should do the exact same thing as last time. The important difference here is that we've just made our job easier, in the long run. By creating a config table (you can call it whatever you want, I just like to use the name config), we have given our script a central location to change some values.
Take notice how we don't hard-code the values anywhere EXCEPT in the config table. As our program grows, this table can serve as a central location to store configuration variables.
Adding the Player's Box
Now that we have a nicely configured window, we need to start building the game. Let's start by just drawing a block in the center of the screen.
Whenever you draw something (other than the background color), you'll want to process the onpaint event. This is where all the drawing happens. Eventually we will be adding all our boxes into this event - but let's start simple and just add one box:
sh: highlight: command not found
You need to specify a language like this: <source lang="html">...</source>
Supported languages for syntax highlighting:
(error loading support language list)That's nice. Now we have a red box, with a black border.
But wait... maybe we should move some of those values into the config table? This is going to be our player... so what values should we be storing in our config table?
Well, the 200, 200 is the upper-left corner of where we draw. This isn't a configuration value - it's going to change during the game, as the player moves the mouse. So we shouldn't stick that in config. Perhaps we should stick that in it's own variable.
But the 15, 15 is the size of the player. We should probably stick that in config. What about the colors? Sure, we can store those too. So let's do this:
sh: highlight: command not found
You need to specify a language like this: <source lang="html">...</source>
Supported languages for syntax highlighting:
(error loading support language list)Perfect! Now if only we could make the box move...
Making the Player Move
When the user moves the mouse, it should make our box move. This might be a little tricky, and it takes some math. We'll start simple and move slowly.
The first thing we need to do is capture the onmousemove event. The onmousemove event is fired when the mouse is moved around the window. Every time the mouse position changes, a new onmousemove event is fired. Sounds great!
When the mouse moves, we need to store the new mouse coordinates into the player table. That will make sure the player's position moves WITH the mouse. Let's start with this:
sh: highlight: command not found
You need to specify a language like this: <source lang="html">...</source>
Supported languages for syntax highlighting:
(error loading support language list)Alright... so if we run our program, the box should move around, right?
Try it out. Run the new script, and move the mouse around. Does the red box move? No?! NO!?!?! Arrggh! Why not?
Well, the reason the red box doesn't move is because a window will only paint itself when it thinks it needs to. For example, if you minimize the window, and then restore it - the window needs to be painted again. So it will call the onpaint event. But if the window is just sitting there, it doesn't know that it should be re-painted. It thinks everything is still good to go.
In order to tell the window it needs to be re-painted, we use the function window.invalidate on the window object. window.invalidate tells the window to "invalidate" the painting region - which basically just means re-draw it.
When do we need to invalidate? Well, every time the player position changes. So we should invalidate our window at the end of the onmousemove event.
sh: highlight: command not found
You need to specify a language like this: <source lang="html">...</source>
Supported languages for syntax highlighting:
(error loading support language list)We use the colon instead of the period, because we are calling invalidate on the wnd object. We are telling that specific window to invalidate itself.
Now run the program. It works! When you move your mouse around on the screen, the red rectangle moves with you! Hurray!
There is one slight annoyance though. The upper-left corner of the rectangle moves where the mouse pointer is. Really, it should be the center of the rectangle moves with the mouse pointer. This will require some math.
Basically, we want to take the current behavior, but offset the player position by half it's size. Think about that for a second. Play around with the current program, and understand that our goal is to offset the red rectangle ever-so-slightly. We want our pointer to be in the middle of the red rectangle, instead of the upper-left corner. So we need to offset the player's position by half it's size - in order to get the center.
Here is the modified code. Think about the math until you can understand exactly why this works:
sh: highlight: command not found
You need to specify a language like this: <source lang="html">...</source>
Supported languages for syntax highlighting:
(error loading support language list)Now run the script.
Much better. Now when the player moves around, the pointer is "attached" to the center of the rectangle, instead of it's upper-left corner. Hurray!
Please take special notice: we are using the config.player_size value here. Remember, that if the configuration of the player's size changes, then the "center offset" will change. So you must recognize that the idea of centering the cursor on the player's rectangle actually depends on the player's size.
This reminds us why it's great to use a configuration table. If at a later time, we want to change the player's size, then the onpaint event will still work, and so will the onmousemove event. As the game grows, it would have become harder and harder to track these things without a central config table. But as long as we keep thinking about it the entire time, then we can write code that takes the configuration into account.
Review Part 1
At this point, let's review what we've done so far.
First, we just got some code up and running. A basic window, and that's it. That gave us our starting point, to which we could add more stuff to it.
Then we configured out window so it looked a little more customized. We changed the dimensions, the title bar, and the background color.
It was at this point where we realized we should probably store some hard-coded values in a configuration table instead. This way if we later decided to change something, we wouldn't have to search our code and make the changes manually - we could just flip a switch in the configuration table, and our code would adapt to it.
Then we added a player rectangle (which required adding more configuration variables), and attached it to the mouse. And lastly, we made sure the pointer was attached to the CENTER of the player rectangle, instead of the upper-left corner, by offsetting it's position according to the player's size.
The current state of the code is this:
sh: highlight: command not found
You need to specify a language like this: <source lang="html">...</source>
Supported languages for syntax highlighting:
(error loading support language list)Adding an Enemy Box
Alright - we have a player box that is attached to the mouse. Now we need to add an enemy box that moves around on it's own. We'll start with just one enemy box, and then try to expand it so there are multiple enemy boxes on the screen.
Let's start the same way we did the player box. Just by drawing a rectangle on the screen.
sh: highlight: command not found
You need to specify a language like this: <source lang="html">...</source>
Supported languages for syntax highlighting:
(error loading support language list)Ok, that works. Now we have something to build off of.
Which part is configuration, and which part should be it's own separate variable? Well, the position of the rectangle is a variable that changes as the game is played - so that should be it's own separate variable. The size of the rectangle could be configuration... but that would mean that all enemies are the same size. Instead, let's make that a variable too. That way all enemy boxes have different sizes. The fill color can be a configuration value though, because every enemy box will be the same color. Let's remove the hard-coded values:
sh: highlight: command not found
You need to specify a language like this: <source lang="html">...</source>
Supported languages for syntax highlighting:
(error loading support language list)Making the Enemy Move
Now for a challenging part. We want the enemy box to move on it's own. How can we do this?
Well, the idea is that the enemy.x and enemy.y values have to change... and then the window has to be re-drawn with wnd:invalidate(). But where do we put this code?
The Main Loop
There are a couple different answers, but in my opinion, the best place to stick the code is at the very end of the file, inside the main loop:
sh: highlight: command not found
You need to specify a language like this: <source lang="html">...</source>
Supported languages for syntax highlighting:
(error loading support language list)You might ask: why?! Seems a little strange at first. But let's think about what the entire program is doing...
When you run the program, it starts at the top, and executes each line. If you remember the very beginning of this tutorial, we had to keep our window alive, so we created that loop at the end of the file so we wouldn't exit right away - in this loop, we have to pump messages to the window.
So when we are playing our game, what's actually happening is that it's just spinning in circles, in that loop. When we call window.pumpmessages(), it will pump the messages, and turn them into events - which our event functions pick up. For example, when the mouse is moved over the window, a mousemove message is generated. window.pumpmessages() will convert that message into an event, onmousemove, and run our event function.
window.pumpmessages
How does window.pumpmessages work? What it does is sit around, waiting for a message. Once a message is generated, it pumps it to the event handler. Then it exists. When it exists, our program will loop around (if there are still windows open), and execute window.pumpmessages again. So basically: we are just looping in circles, pumping the messages.
The important thing to realize is that the majority of time, our program is just sitting around, waiting for a message. This waiting happens INSIDE of window.pumpmessages.
Just to see this happening, let's change our code slightly. This won't be a permanent change that will stay in our game - we're just going to perform this change to understand how window.pumpmessages works:
sh: highlight: command not found
You need to specify a language like this: <source lang="html">...</source>
Supported languages for syntax highlighting:
(error loading support language list)Run the program, and be sure to look at the Output window in Brain Damage.
When you move your mouse over the window, the message count will increase quickly. But if you keep your mouse still, then nothing will happen. That's because no messages are being generated - nothing is happening! Once you move your mouse (or do anything else, like click a button, or type a letter), then a message is generated, and our message_count increases.
Play around with that for a while, until you understand that: when nothing is happening, our program is just waiting inside of window.pumpmessags for something TO happen.
Idle Time
Now here is a question: if we want the enemy boxes to move on their own, can we afford to be stuck in window.pumpmessages until an event occurs?
The answer is no. We must only call window.pumpmessages when we KNOW there is a message. And when there isn't a message, then we need to be moving our enemy boxes. We can't afford to just chill inside of window.pumpmessages, waiting around for an event.
What we're going to do is transform our main loop into one that processes it's idle time. The way it currently works, our idle time is spent waiting inside of window.pumpmessages. Instead, we want to use our idle time. Here is our new loop:
sh: highlight: command not found
You need to specify a language like this: <source lang="html">...</source>
Supported languages for syntax highlighting:
(error loading support language list)Run the program. You'll notice that it behaves exactly like it did before. The important difference is that we are no longer WAITING around for a message. As you can see by the code, we only execute window.pumpmessages if window.hasmessages is true. If the window doesn't have any messages, then we execute our idle code (which right now doesn't do anything).
Our game is the exact same as before, except now we can do something during our idle time, instead of waiting for a message inside of window.pumpmessages. It's important to understand this concept, because it's used in all game programming.
Moving the Enemy during Idle Time
Now that we process our idle time, we can move our enemy during that time.
Remember our original idea: we know to move the enemy, we need to change enemy.x and enemy.y, and then call wnd:invalidate to re-draw the screen. Now we can do this in the idle section!
Here's a good start:
sh: highlight: command not found
You need to specify a language like this: <source lang="html">...</source>
Supported languages for syntax highlighting:
(error loading support language list)Run that and see how it looks.
You'll notice that the enemy box does move on it's own... but it looks crappy. It flickers. Why does this happen?
Dealing with Flicker
This flicker is caused because the program is running very quickly - we are calling wnd:invalidate() during our idle time, which means that maybe 100 times a second, we are erasing the background, and painting our objects. Well... that's what we're seeing.
wnd:invalidate() will generate an erasebkgnd message, and a paint message, back to back. Those messages are converted into the events, onerasebkgnd and onpaint, by our window.pumpmessages call. This happens maybe 100 times a second - erase, draw, erase, draw, erase, draw, etc. So about half the time, our window has been erased, and the other half, it has rectangles inside of it. This creates flicker.
How can we fix this?
There are many solutions. One simple solution is to increase the amount of time the drawn rectangles appear on the screen. Right now we spend 50% of the time erased, and 50% of the time painted. What if we could bump our paint time up a little bit? If 10% of the time we are erased, and 90% of the time we are painted, then there will be less flicker. There will still be flicker - but it will be very little.
In order to implement that strategy, we find in our code where we just finished painting everything, and force the program to wait a little bit. Where did we finish painting? Well, right at the end of the onpaint event.
Try this:
sh: highlight: command not found
You need to specify a language like this: <source lang="html">...</source>
Supported languages for syntax highlighting:
(error loading support language list)Run the program now.
The flicker is gone (mostly)! The enemy box doesn't move as quick as it used to, because we are slowing our entire script down using sleep. sleep is a pretty simple function - it just waits around for however many seconds you tell it to. So, we told it to wait around for 0.001 seconds.
This is a very slight delay, but because computers are so fast, you can really tell the difference. By putting this sleep command at the end of the onpaint, we are delaying the program right after we just drew everything. This reduces flicker, because now the majority of the time is spent after everything is on the screen.
This is just a simple solution to fix flicker. There are far more complicated techniques, that are usually better than this solution. The main complaint about this solution is that the program is just waiting around, doing nothing, inside of sleep. If our game was big and complicated, we could be using that time to actually do something. But since our game is very simple, we will use this method, because it gets the job done with one line of code.

