Every game is different.
If I pasted the zombie code from the Swamp Platformer, it would probably be useless for you.
I can't really paste code from Sengoku Jidai or JLA because they are big and complicated and I decided to be minimalist with the classes in those cases.
Here's a commented sidescroller example I threw together at some point. I seem to remember it having problems or something, but it gets the idea across.
#include "sound_pool.bgt"
#include "dynamic_menu.bgt"
// The above lines add data from external script files. Those two come with BGT.
/*
* Multi-line comments like this one will appear at the beginning of each major section of this file.
* You can search for /* (slash asterisk) to jump to the next section.
*/
// These variables can be referenced by any part of the program:
int level;
int lives;
double x; // The player's position.
double health; // The player's health.
double y; // The player's y position.
double recovery; // How long before the player can attack again.
bool has_key; // Whether or not the player has found the key.
// The following variables are all objects.
sound_pool pool; // The simplest way to play lots of sounds.
tts_voice voice; // For text-to-speech.
dynamic_menu menu;
// Let's set some constants for the map:
const uint8 clear=0, pit=1, fan=2, door=3, heal=4, key=5;
// The map itself is an array of integers.
uint8[] map; // We don't define a size yet; the map can change size as you advance in the game.
int[] slots; // Parallel to map, keeps track of the sound slots. Only so we can silence items that the player collects.
/* Enemy class.
* This class defines an enemy, and all of its properties and methods.
*/
class enemy {
double health; // How much damage is needed to kill this enemy.
double strength; // How much damage this enemy does to the player.
double speed; // How quickly this enemy moves.
double x; // Enemy position.
double recovery; // How long before this enemy can attack again.
double attack_speed; // When an enemy attacks, recovery will be set to this value.
bool flies; // If true, this enemy stays in the air.
bool dodges; // If true, this enemy is immune to pits and fans, otherwise it can die just as easily as the player.
int slot; // This could be a sound object, but it's simpler to reference the sound that the enemy makes in the sound pool.
// The constructor sets the properties randomly.
enemy() {
x=random(1, 10);
health=random(1, 10);
slot=-1; // -1 means it has not been matched to a sound.
speed=random(1, 5);
strength=random(1, 15);
recovery=0.0;
attack_speed=random(250, 1500);
// Set flies and dodges randomly:
int number=random(0, 5);
if(number==5) flies=true;
else flies=false;
number=random(0, 5);
if(number>3) dodges=true;
else dodges=false;
// The above could have been accomplished without using ifs, but this way should make more sense.
}
// This method updates the enemy's sound, if it has one.
void update_sound() {
// First, we make sure that the slot corresponds to a sound.
if(slot>=0) {
// Move the sound to the enemy's current position:
pool.update_sound_1d(slot, x);
}
else {
// The enemy should probably be making sound.
// The sound should depend on whether or not the enemy is flying.
string toplay="sounds/enemy.wav";
if(flies) toplay="sounds/flying.wav";
// Since the class contains a variable called x, we use two colons (::) to reference the player's position when playing the sound.
slot=pool.play_1d(toplay, ::x, x, true);
}
}
// This method moves the enemy, and attacks the player if in range.
// It takes a double as an argument, as otherwise there is no way for the method to know how much time has passed.
void step(double time)
{
// Save the player position as something more distinct.
// We use two colons (::) to distinguish the global variable x from the enemy property of the same name.
double target=::x;
if(recovery>0.0) {
recovery-=time;
}
else {
// Let's back up x. This will be useful later.
int backup_x=round(x, 0); // We also rounded it to the nearest integer.
// The enemy moves toward the player.
// Notice that in both cases, we divide time by 1000, to convert it to seconds.
if(x>target) {
x=x-(speed*time/1000.0);
}
else if(x<target) {
x=x+(speed*time/1000.0);
}
// Now we use backup_x to see if we should play a step sound.
// We only do this if the enemy is on the ground, and if the integer part of x changed, so we combine two conditions:
if((!flies)&&(backup_x!=round(x, 0))) {
// Add a random number in the middle of the filename, so we use different step sounds each time.
pool.play_1d("sounds/step" + random(1, 5) + ".wav", target, x, false);
}
// If the enemy is close enough to the player, attack.
if(absolute(x-target)<=1.0) {
// They must be at the same vertical position:
if((flies)||(y<=0.0)) {
pool.play_stationary("sounds/hit_player.wav", false);
hurt_player(strength);
}
else pool.play_1d("sounds/miss.wav", target, x, false);
recovery=attack_speed;
}
}
}
// This method tells us if this enemy is alive.
// We'll use this later to determine when to step enemies, and when to remove them.
bool alive() {
return health>0;
}
}
// This array contains the enemies in the game:
// We use an @ sign to indicate that we're using handles; otherwise, lots of things could go wrong.
enemy@[] enemies;
/*
* The main function is where the program starts.
*/
void main() {
// Lets begin by setting up the menu.
// Use the same tts we're already using.
menu.set_tts_object(voice);
// Add our options:
menu.add_item_tts("Start game");
menu.add_item_tts("Set voice rate");
menu.add_item_tts("Help");
menu.add_item_tts("exit");
menu.wrap=true;
// Now, create the window:
show_game_window("Simplest Side Scroller Ever!");
// This loop lasts forever; the only way to get out of it is by selecting exit from the menu.
while(true) {
int value=menu.run("Use arrow keys and enter to select an option.", true);
if(value==1) new_game();
else if(value==2) {
voice.speak_interrupt("Enter voice rate, -5 to 5");
string text=input_box("Voice rate", "Enter the desired voice rate (-5 to 5): ");
// Assume that if the input is empty, the user canceled.
if(text.is_empty()) voice.speak_interrupt("Canceled");
else voice.rate=string_to_number(text);
// Notice that the above does not guard against invalid values!
}
else if(value==3) {
voice.speak_interrupt("In this game, you must unlock doors to reach the next level. Enemies will try to stop you. Use the left and right arrow keys to move, up to jump, spacebar to attack, down to pick up items, and h to hear your health. Press any key to continue.");
// Let the player skip this message by pressing a key.
keys_pressed();
bool has_pressed=false;
while(!has_pressed) {
int[] keys=keys_pressed();
if(keys.length()>0) has_pressed=true;
}
voice.stop();
}
else {
voice.speak_interrupt("Thanks for playing.");
exit();
}
wait(5);
}
}
/*
* The new game function initializes all the values, and turns over control to the game itself.
*/
void new_game() {
level=1;
lives=3;
recovery=0;
x=0;
y=0;
start_level();
timer gametimer;
while(lives>0) {
// Let the player exit the game:
if(key_pressed(KEY_ESCAPE)) {
pool.destroy_all();
return;
}
// Checking health:
if(key_pressed(KEY_H)) voice.speak_interrupt("Health: " + round(health, 0));
// Only let the player do anything else while not recovering from an attack.
if(recovery>0) recovery-=gametimer.elapsed;
else {
// Backup an int version of x.
int backup_x=round(x, 0);
// We use 0.004 as our speed, because gametimer.elapsed is in milliseconds. This means we'll move roughly 4 tiles per second.
if((key_down(KEY_LEFT))&&(x>0.0)) x-=0.004*gametimer.elapsed;
else if((key_down(KEY_RIGHT))&&(x<map.length())) x+=0.004*gametimer.elapsed;
else if((key_pressed(KEY_UP))&&(y<=0.0)) {
y=1000.0;
pool.play_stationary("sounds/jump.wav", false);
}
else if((key_pressed(KEY_DOWN))&&(y<=0.0)) {
int value=map[backup_x];
if(value==heal) {
health+=random(10, 50);
if(health>100) health=100;
voice.speak_interrupt("Healed");
map[backup_x]=clear;
if(slots[backup_x]>=0) pool.destroy_sound(slots[backup_x]);
}
else if(value==key) {
has_key=true;
map[backup_x]=clear;
voice.speak_interrupt("You found the key!");
if(slots[backup_x]>=0) pool.destroy_sound(slots[backup_x]);
}
}// Get item.
else if(key_pressed(KEY_SPACE)) {
recovery=333;
pool.play_stationary("sounds/attack.wav", false);
// Check enemies for a hit.
for(uint i=0; i<enemies.length(); i++) {
if(absolute(backup_x-enemies[i].x)<=2.0) {
// Be sure that the vertical positions match!
if((enemies[i].flies)&&(y>0)) {
enemies[i].health--;
pool.play_1d("sounds/hit_flying.wav", x, enemies[i].x, false);
}
else if((y<=0)&&(!enemies[i].flies)) {
pool.play_1d("sounds/hit_enemy.wav", x, enemies[i].x, false);
}
}// In range.
}// Enemies loop.
}// Attacking.
if(round(x, 0)!=backup_x) pool.play_stationary("sounds/step" + random(1, 5) + ".wav", false);
if(x<0.0) {
x=0.0;
pool.play_1d("sounds/wall.wav", x, -1, false);
}
else if(round(x, 0)>=map.length()) {
x=map.length()-1;
pool.play_1d("sounds/wall.wav", x, x+1, false);
}
// Check for traps.
int value=map[round(x, 0)];
if((value==pit)&&(y<=0)) {
pool.destroy_all();
voice.speak_interrupt_wait("You fell in a pit.");
kill_player();
}
else if((value==fan)&&(y>200)) {
pool.destroy_all();
voice.speak_interrupt("You hit a deadly fan!");
kill_player();
}
else if((value==door)&&(has_key)) {
pool.destroy_all();
voice.speak_interrupt_wait("You have unlocked the door! Congratulations!");
level++;
start_level();
}
}
for(uint i=0; i<enemies.length(); i++) {
if(@(enemies[i])!=null) {
if(enemies[i].alive()) {
enemies[i].update_sound();
enemies[i].step(gametimer.elapsed);
}
else {
// The enemy is dead, remove it.
if(enemies[i].slot>=0) pool.destroy_sound(enemies[i].slot);
if(enemies[i].flies) pool.play_1d("sounds/kill_flyer.wav", x, enemies[i].x, false);
else pool.play_1d("sounds/kill_enemy.wav", x, enemies[i].x, false);
enemies.remove_at(i);
}// Dead enemy.
}
}
if(health<=0.0) kill_player();
else pool.update_listener_1d(x);
gametimer.restart(); gametimer.resume();
wait(5);
}
voice.speak_interrupt_wait("Game over. You reached level " + level + ".");
}
/*
* This function initializes the level.
*/
void start_level() {
// Stop sound.
pool.destroy_all();
voice.speak("Level " + level + ". " + lives + " lives remaining.");
map.resize(level*10);
enemies.resize(level*2);
slots.resize(level*10);
for(uint i=1; i<map.length(); i++) {
map[i]=clear;
int trap=random(0, 15);
if(trap==4) map[i]=pit;
else if(trap==2) map[i]=fan;
else if(trap==8) map[i]=heal;
}// Initialize map.
// Position the door and the key.
int door_x=random(0, map.length()-1);
int key_x=level*9;
// The key should appear randomly, but not at the same position as the door:
do {
key_x=random(0, map.length()-1);
} while(key_x==door_x);
map[door_x]=door;
map[key_x]=key;
// Reset player.
x=0;
if(health<10) health=100;
else if(health>100) health=100;
recovery=0;
y=0;
has_key=false;
// Add enemies.
for(uint i=0; i<enemies.length(); i++) {
enemy en;
en.x=random(2, map.length());
@(enemies[i])=en;
}
// BGT cleans up memory automatically, but this is a good place to run garbage collection.
// There shouldn't be any lag in a game like this, but taking advantage of garbage-collection opportunities is generally a good idea:
garbage_collect();
// Now, wait for the voice to finish speaking. We didn't call speak_wait, so that the game could do other things while sapi speaks.
while(voice.speaking) {
wait(50);
}
// Start sounds.
for(uint i=0; i<map.length(); i++) {
int slot=-1;
if(map[i]==pit) slots[i]=pool.play_1d("sounds/pit.wav", 0, i, true);
else if(map[i]==fan) pool.play_1d("sounds/fan.wav", 0, i, true);
else if(map[i]==heal) slot=pool.play_1d("sounds/heal.wav", 0, i, true);
else if(map[i]==door) slot=pool.play_1d("sounds/door.wav", 0, i, true);
else if(map[i]==key) slot=pool.play_1d("sounds/key.wav", 0, i, true);
slots[i]=slot;
}// Sounds.
}
/*
* Kill the player.
*
*/
void kill_player() {
pool.play_stationary("sounds/death.wav", false);
wait(250);
voice.speak_wait("You have been killed.");
lives--;
if(lives>0) start_level();
}
/*
* Decreases the player's health.
*/
void hurt_player(double dmg) {
health=health-dmg;
}