Here’s a little bit of commentary for some of the features in the demo I posted yesterday:
Each game loop goes something like this:
1) Introduce any new enemies that are needed.
Currently the idea is to have a simple function CallEnemy(optional int enemy, optional int x, optional int y) which creates the specified enemy type at the specified location. If no location is specified, a random x value is chosen and y is set a bit above the top of the screen. If no enemy type is specified the function should choose a random enemy from a list of low-level enemies. At the moment there is only one enemy type, and the CallEnemy works by seeing if the number of enemies drawn (ie still alive) in the last game loop is less than a certain value, and calling another enemy if it is.
2) Calculate the desired horizontal movement of the player ship
This is based on what arrow keys are/aren’t pressed, and uses acceleration and maximum speed values. In other words, if a player continues to hold down an arrow key the ship’s speed will change by a higher and higher amount each time, until it reaches a maximum value. This gives a more realistic simulation, but I’m not sure how well it works in the context of this game where control is important. Might be useful for special instances (a specific level or section where the ship is hard to control?).
3) Calculate the desired vertical movement of the player ship.
Pretty much the same as above, but in the y direction.
4) If the player is firing, introduce a new bullet.
A single bullet is an instance of a Bullet structure, which is basically a container for values such as the x and y positions, the graphic to use for the bullet, the x and y speed of the bullet, the animation view to use for its explosion and how much damage it deals. It also contains a ‘life’ value that says how long the bullet has been around. The player has an array of (50?) bullets. This means there can be up to 50 player-fired bullets on-screen at any one time. In order to create a new bullet we have to go through this list of player bullets to try and find one that isn’t in use (its life = 0). If we succeed we can set this array slot up to be the new bullet. If not then… well… the player doesn’t fire anything. At the moment this can’t happen because the rate of fire combined with the speed of the bullets means bullets are disappearing off the top of the screen (and being ‘killed’ by setting life back to 0) before 50 have been shot.
5) Calculate the player’s horizontal position
This is a combination of the results of step 2 and any additional environmental effects such as drag. E.g. setting a drag value at a certain part of a stage could be used to simulate gravity or wind that would move the player in a certain direction unless they flew against it.
6) Calculate the player’s vertical position.
As above. Re-reading the code it’s possible that step 4 should actually happen after this, but at 40 frames a second I don’t think anyone would notice the difference.
7) Calculate the enemy movement, including them firing bullets.
I’m trying to implement some form of artificial intelligence for the enemies. Currently it works like this:
a) Each enemy has a variable pointing at an AI Routine, and a variable pointing at an AI Step.
b) When it needs to work out an enemy’s next movements, the script calls an AI function.
c) The AI function goes to the enemy’s routine, and then looks at the step they are currently on.
d) This step may modify one or more of the enemy’s values such as its speed. Enemies also have a target destination that they are moved towards, so the step may change this. The step may also check values, such as the enemy’s position, and make decisions based on this. Finally the step can call for the enemy to fire, by setting a boolean to true.
e) The step may alter the enemy’s AI Step value, so that a different step is run the next time. It could also potentially alter the AI Routine value, so that a completely different routine is run next time.
f) The script then leaves the AI function and moves the enemy according to the enemy’s values, whether or not they have been changed. Enemies are moved towards their target destinations (in both the x and y axis).
g) If called to fire the enemy creates a bullet similarly to the player in step 4. Enemies use the same Bullet structure as the player, but they have their own array to create bullets in. Each enemy has values telling the script where to create the bullet, what graphic to use, how much damage the bullet does etc. In other words each enemy can have different weapons, but the bullets for all weapons are stored in one array.
The current enemies show a very simple version of this. Their AI routine looks something like this:
Step 1) Set the target destination to be the left of the screen. Set the next step to be step 2.
Step 2) If the enemy has reached the target destination, set the next step to be step 3. Otherwise randomise a number and fire a bullet if that number is a certain value.
Step 3) Set the target destination to be the right of the screen. Set the next step to be step 4.
Step 4) If the enemy has reached the target destination, set the next step to be step 3. Otherwise randomise a number and fire a bullet if that number is a certain value.
Hopefully further down the line this can be used to program reasonably complex behaviour on an individual basis. A huge plus to this system is most of the code will be reused. There’s also the advantage that it’s in its separate global script, so it can grow without making the rest of the game’s code hard to read. I may add a AI Counter variable to the enemy type, so that for example a routine could be run a number of times, then changed to a different one.
8) Calculate movement of all player’s bullets, including whether they hit an enemy, and if so remove the bullet, damage/kill the enemy and create relevant explosions.
This is the major number cruncher, as every bullet has to be checked to see if it’s colliding with every enemy, which is potentially a lot of checking. Obviously this needs optimising. I don’t know how the professionals do it, but I’m using a series of if statements, called in the order that I think they’re most likely to be failed. In other words…
a) Is the bullet ‘alive’. If not, stop checking anything as it doesn’t exist on the game screen, and move on to the next bullet.
b) Is the enemy actually ‘alive’? If not, move on to checking the next enemy.
c) Is the bullet too far to the right of the enemy to hit them? If so, next enemy.
d) Is the bullet too far below the enemy to hit them? If so, next enemy.
e) Is the bullet too far to the left of the enemy? If so, next enemy.
f) Is the bullet too far to the right of the enemy? If so, next enemy.
If all of these checks pass the bullet has hit the enemy and we can move onto checking the next bullet (after destroying this one, causing the enemy to take damage/die and making an explosion).
9) Calculate movement of all enemy bullets, causing the player to take damage and create explosions if hit.
Less of a number cruncher because although there are potentially more enemy bullets, there’s only one area to check within. The player and enemies currently use hit-boxes as they are being drawn at real-time.
10) Calculate background movement.
This is actually the first bit of the project I made. The game has two backgrounds (which could potentially share the same graphic) and works on the assumption that each background graphic is at least as big as the game resolution. It starts by moving background 1, then checks to see if the top of background 1 is below the top of the screen. If it is, it draws background 2 above it to make sure there’s no gap. This continues, with both backgrounds moving down the screen. Eventually background 1 will fall completely off the bottom of the screen. When this happens, background 2 becomes the new background 1, transferring it’s current location and graphic. The game then picks a new background 2 image (at the moment this is from a random list of 3 or 4 graphics) and continues. As soon as background 1 starts coming off the top of the screen background 2 appears above it… and so on.
11) Draw player ship.
12) Draw enemy ships.
13) Draw player bullets.
14) Draw enemy bullets.
15) Draw special effects (currently means explosions).
16) Draw background.
All game graphics are drawn to the screen every frame using AGS’s drawing functions. There are no objects and only one invisible character who’s sole purpose is to sit in the one room so that AGS knows to show it. Graphics are drawn onto the backgrounds of one of 6 full-screen GUIs because:
a) I need something to draw onto.
b) Using GUIs is an easy way of doing layering – so the background is drawn behind the ship while bullets are drawn in front, regardless of when I actually draw them in the loop.
Maybe in the future I’ll convert it to use even less resources, and just draw on the room background.
17) Update status bar.
At the moment this GUI is just a health bar, but later it’ll probably contain other things (ammo, smart bombs?). The health bar is a button, and I’m clearing it to a red colour then using the draw rectangle function to drawn a green rectangle on top with the width determined by the player’s health.
And…?
Hopefully the above is the start of a good framework for producing a progressive, level based shoot-em-up game. I’ve tried to create it in such a way that lots of different elements of the game can be changed very easily, so that when it comes to actually programming a level it should be relatively simple and code light.
But whatever I end up doing with the project, the main point is it’s a fun programming challenge. I keep pushing AGS, and I keep being surprised by what it can do. All of the above takes place 40 times a second, yet I’m not seeing any drop in frame-rate at all. It’s truly an awesome tool.
P.S. If anyone wants a copy of the source code email me or PM me over at the AGS forums. It’s far from complete, and I can’t guarantee that the latest version is stable, but I’ve tried to keep it well commented as I’m going along.
Incredibly smooth and fun to play. Good scripting job!