In the previous newsletter, I explained how the engines work and how to draw and animate objects on the Vectrex.
This Newsletter will use all this but now we start to create a real game level that’s quite close to what we have in the final game after.
Create a Level
We could easily write the entire game with code but it would create a lot of effort and it will not be very flexible if we change the levels or add more levels to the game. Therefore I created a simple way to describe levels in text files.
The Level File
I will later in the series add scripting to create and modify levels, but for now, I start with a simple text file that fits our needs for Vexxon. That’s also the only issue it has. It’s not flexible enough for any kind of game. But for now, it’s exactly what we need. You can open the level file (which is in the assets
folder and called level1.txt
) with any text editor. At first, there are some definitions that define the scoring and gameplay and look like this:
# This is a comment
B 100
F 200 1 10
S 200 100 80 70 0
The syntax is simple. The first character defines the type and the rest of the line defines the parameters separated by a space.
#:
Acomment is not used in the code but is helpful to add a note or any informationB:
Number of bullets to fire at the start (ex. 100). The more bullets you give the more easy the game is to playF:
Fuel at the start, l/sec used, and fill-up when hitting a petrol silo. This has also a big impact on how easy the game will be. For level 1 it’s probably a good idea to give a lot of fuel, so the user don’t have to hit petrol tanks. Later we can make that more difficult.S:
The score you get when hitting a flying jet, a jet on the runway, a tank, a rocket base, and a petrol silo
That allows us to fine-tune the gameplay without changing the code.
After those lines, plenty of lines are following which describe the level itself
0 ..........
X ----------
Y ----------
Z ----------
1 ...j......
0 ....J.....
1 .........t
1 .ff.......
1 .........t
2 ....@.....
While this looks a bit cryptic, the structure is quite similar to the one above. But instead of defining the scoring, it defines the level design itself. Every line starts with a code that defines the walls, followed by some codes that define the enemies. Every line defines a block and all blocks together define the entire level. You can create levels of any size with this. Also, it’s important to know that the level is created bottom-up, which means the last line in the level file is the one you see at first. This allows you to “see” the level and how it looks already in the text file. Like this section where you see a bunch of jets flying in a diamond-shaped form :)
0 ....J.....
0 ...J.J....
0 ..J...J...
0 .J.....J..
0 ..........
0 .J..J..J..
0 ...JJJ....
0 ....J.....
Lines that start with a number between 0 and 2 define sections that just have borders, but no walls.
0
=No Border,1
=Border,2
=End level.Lines that start with a number between
3
and9
define sections that have a border and solid walls, while the difference is in the height of the wallsThen we also have special walls that are slightly different.
X
defines a fly-trough wall,Y
a wall that you can only pass on the left side, andZ
one that you only can pass on the right side
With that, you already would have a nice Zaxxon alike level but so far no objects (like enemies)
This is done with the codes between positions 3 and 12 in a line
. and - are just used to improve visualization in the text files and or not used in the code
J:
a flying jet that fires also bullets at the playerj:
A jet that is still on the runway. The more you shoot, the fewer jets are coming later and fight against yout:
A tank that fires bullets at the playerr:
Rockets that fly vertically and try to hit the playerf:
Petrol silos that you must destroy to get more fuel@:
This is a nonvisible object that indicates the end of the level
That’s all we need so far to define a level in Vexxon :)
But to make all that work we also need some code in the game that reads the file and create the game elements described in this level file. This, I want to look at next.
Add the Code
The engine provides code that reads the level file in an internal structure that we easily can process to include the game elements we included earlier in the game engines (for now just cubes).
Load the file itself is simple, and we do that in vexxon_target_init
using level_load_number(1)
(which loads a file with the name level1.txt
). After that, we loop (level_has_more_lines()
) to the loaded structure line by line ( level_get_current_line(&line)
) and create a game section (_create_section(line) with it.
if (!level_load_number(1)) {
return;
}
...
while (level_has_more_lines()) {
level_get_current_line(&line);
_create_section(line);
_distance += 10;
}
Next, we must implement the function that creates a section in the function _create_section()
:
switch (line->border) {
// No border
case 0:
break;
// Has border
case 1:
_add_left_border_cube(3);
_add_right_border_cube(0);
break;
// End level border
case 2:
_add_left_border_cube(1);
_add_right_border_cube(1);
break;
// Has border: height
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
case 9:
rblog_num1(" Add wall with height: ", height);
_add_left_border_cube(MAX_BORDER_HEIGHT);
_add_right_border_cube(height+1);
_add_bottom_wall(height);
break;
case 10:
rblog(" Add fly trough wall (X)");
_add_left_border_cube(MAX_BORDER_HEIGHT);
_add_right_border_cube(MAX_BORDER_HEIGHT);
_add_bottom_wall(2);
_add_top_wall(2);
break;
case 11:
rblog(" Add left open wall (Y)");
_add_left_border_cube(MAX_BORDER_HEIGHT);
_add_right_border_cube(MAX_BORDER_HEIGHT);
_add_right_wall();
break;
case 12:
rblog(" Add right open wall (Z)");
_add_left_border_cube(MAX_BORDER_HEIGHT);
_add_right_border_cube(MAX_BORDER_HEIGHT);
_add_left_wall();
break;
}
I have already described in the section about the text file how this works. Here we parse only the first code of each line to create the different border and wall types. Should be more or less self-explanatory, right?
The difference in the functions we call here is just what blocks they draw, in which color, position, and size. More interesting are the next lines
for (int i = 0; i < line->object_count; i++) {
_add_level_object(&line->objects[i]);
}
We loop here also over the sesame lines, but this time I use the information to create the game objects.
void _add_level_object(level_obj* obj) {
switch (obj->type) {
case LEVEL_OBJECT_JET_FLYING:
_add_flying_jet(obj);
break;
case LEVEL_OBJECT_JET_STANDING:
_add_jet(obj);
break;
case LEVEL_OBJECT_TANK:
_add_tank(obj);
break;
case LEVEL_OBJECT_ROCKET:
_add_rocket(obj);
break;
case LEVEL_OBJECT_FUELSILO:
_add_fuel_silo(obj);
break;
case LEVEL_OBJECT_END:
_add_end_level(obj);
break;
}
}
It also contains at first a switch statement that checks the different possible game objects we have (see the description in the chapter about the level file. The function which gets called here is then responsible to create the individual game objects. As an example let’s take a look at _add_flying_jet(obj)
which creates a flying jet in the game:
void _add_flying_jet(level_obj* obj) {
int number = game_object_add(GAME_OBJECT_CUBE);
int x = LEFT_BORDER+2*obj->x-1;
game_object_set_pos(number, x, 0, _distance);
game_object_set_color(number, COLOR_JET);
game_object_set_scl(number, 1, 1, 2);
game_object_set_speed(number, 0, 0, SPEED_ENEMY_JET);
}
Here you see again the advantage of the game engine we build before. To create an actual game object we just use game_object_add()
, defines its position with game_object_set_pos()
and set a color with game_object_set_color()
(brightness on the Vectrex), the scale (size) of this object with game_object_set_scl()
and the speed game_object_set_speed()
, if it’s a game object that moves in the game, like the jet. That’s all needed and mostly the sesame for every kind of game object we want to create.
Now you see why using a level file is a good idea. We just have to create a bit of code, and a level file and we have a fully customizable game :)
Not bad what progress have we made by just adding a level file, right?
Try out
You find the source code as always here on GitHub.
But there is something important we miss so far. Have you tried to run it on the Vectrex? Then you probably see the problem immediately. What looks good on a modern PC with a raster display is a big issue on the Vectrex with its Vector display and the limited lines we can draw per cycle.
In easy words… we draw too many lines and will just see some flickering when using it on the PiTrex.
But how we can reduce the number of lines? Well, there are many potential things we still can do.
We don’t draw the entire level at once; which makes anyway no sense because we don’t see all
We might change the camera position, so we see fewer objects and therefore lines
We don’t draw hidden lines of an object at all. This needs some more complex math, which we just do later
That’s we are doing in next Sideletter. We will optimise at least point 1 and 2 and save topic 3 for later (when we optimize the engine)
I hope you liked today’s Newsletter. Any idea for a great level? You have now all the tools needed to created one!