In and out

The following snippet will show you how to connect indoor and outdoor levels. The game starts in an outdoor level and you can go inside a house which loads as a separate level. Of course that you can get outside the house, returning to the outdoor level in a position that is close to the door (not at the initial starting point). You should place a low detail house in the outdoor level; I have used the same house indoors and outdoors.

function main()
{
level_load (inandout_wmb);
}

action warlock
{
player = my;
while (1)
{
my.skill1 = 5 * (key_w - key_s) * time; // 5 = forward / backward movement speed
my.skill2 = 0;
my.pan += 5 * (key_a - key_d) * time; // 2 = rotation speed
vec_set (temp, my.x);
temp.z -= 3000;
trace_mode = ignore_you + ignore_passable + use_box;
my.skill3 = -trace (my.x, temp);
move_mode = ignore_passable;
ent_move (my.skill1, nullvector); // moves using skill1..3
if (key_w + key_s > 0) // one of these 2 keys is pressed?
{
ent_cycle("walk", my.skill46); // then animate the warlock!
my.skill46 += 10 * time; // "walk" animation speed
my.skill46 %= 100; // loop the animation
}
else // the player isn't moving?
{
ent_cycle("stand", 0); // then set the first "stand" animation frame for it
}
camera.x = my.x - 50 * cos(my.pan); // place the camera 50 quants
camera.y = my.y - 50 * sin(my.pan); // behind the player
camera.z = my.z + 50; // and higher
camera.pan = my.pan; // keep the same pan with the player
camera.tilt = -5; // and look a bit downwards
wait (1);
}
}

Function main simply loads the outdoor level. The action that is attached to the player allows it to move using the "W" and "S" keys and to rotate using the "A" and "D" keys. The movement code includes gravity. If one of the movement keys is pressed, the player is animated using its "walk" animation; otherwise, it switches to the first "stand" animation. The camera is placed 50 quants behind the player and 50 quants higher than player's origin, keeping the same pan with the player and looking a bit downwards.

action door_level1
{
while (player == null) {wait (1);} // wait until the player is created
while (vec_dist(player.x, my.x) > 70) {wait (1);} // wait until the player has come closer than 70 quants
while (key_any == on) {wait (1);} // wait until the player has released the keys
vec_set (player_coordinates.x, player.x);
level_load (houselevel_wmb); // load the house level
}

The action above is attached to the door in the outdoor level; it waits until the player is created, and then it waits until the player has come closer than 70 quants to it. As soon as this happens we wait until the player releases the keys, and then we store player's coordinates and we load the indoor level.

action door_level2 // attached to the door in the second level (indoors)
{
while (player == null) {wait (1);} // wait until the player is created
while (vec_dist(player.x, my.x) < 80) {wait (1);} // wait until the player has moved away from the door
while (vec_dist(player.x, my.x) > 70) {wait (1);} // wait until the player has come closer than 70 quants to the door
while (key_any == on) {wait (1);} // wait until the player has released the keys
my = null; // make sure that the lines after level_load are executed
level_load (inandout_wmb); // load the house level
wait (3);
player_coordinates.x -= 50; // make sure that the player appears (50 quants + its initial x) away from the door (hysteresis)
vec_set (player.x, player_coordinates.x);
player.pan = 180; // rotate the player in the proper direction (with its back to the door)
}

The second door makes the switch from indoors to outdoors; it waits until the player is created, and then it waits until the player has moved away from the door. We need to use this line because we wouldn't want the player to be teleported back and forth several times. The following line waits until the player approaches the door, wanting to get out of the house. We wait until all the keys are released, and then we use my = null in order to make sure that the lines that follow the level_load instructions are executed as well. We subtract 50 quants from the previously stored player_coordinates because we want to move the player a bit away from the door, and then we restore player's position and we set the proper pan angle for it.

Now you can go and create those huge worlds that mix indoor and outdoor levels without any problem. Oh, and if you want to be able to carry weapons, items, etc between the levels make sure to check my "true level changing" article from Aum28.

Letter wheel

This piece of code might come in handy for your high scores, when the player needs to type in its name using a fancy method.

var letter_number = 0; // 0 for "A", 1 for "B", ... , 25 for "Z"

text name_txt
{
font = adventure_font;
pos_x = 200;
pos_y = 560;
layer = 20;
string = name_str;
flags = visible;
}

function main()
{
level_load (lwheel_wmb);
wait (3);
camera.x = 40;
camera.y = -100;
camera.z = 2100;
camera.pan = 90;
camera.tilt = -90;
}

I'm using a regular text definition to display the text at the bottom of the screen and a simple main function to load the level and place the camera in a proper position.

action rotating_wheel
{
str_cpy(name_str, ""); // reset name_str;
while (1)
{
if (key_cur == on)
{
while (key_cur == on) {wait (1);}
my.pan += 360 / 26; // add the angle (sector) that is covered by a letter
letter_number += 1;
}
if (key_cul == on)
{
while (key_cul == on) {wait (1);}
my.pan -= 360 / 26; // subtract the angle (sector) that is covered by a letter
letter_number -= 1;
}
letter_number %= 26;
if ((key_enter == on) || (key_space == on)) // press space or enter to select a letter
{
while (key_enter + key_space > 0) {wait (1);} // wait until the keys are released
str_for_asc(temp_str, letter_number + 65); // add 65 to turn 0 into 65 ("A"), 1 into 66 ("B") and so on
str_cat (name_str, temp_str);
}
name_txt.pos_x = 400 - str_len(name_str) * name_txt.char_x / 2 ; // center the text on the screen
wait (1);
}
}

The wheel is a huge entity that has got the letters applied over it. We reset name_str, and then, if the player has pressed the right arrow key, we wait until he or she releases it and we rotate the wheel with an angle that is computed dividing a full rotation (360 degrees) by the number of sector that need to be covered (26). The last line inside the "if" branch increments letter_number, a variable that will give us the current letter. A similar sequence takes place if the player presses the left arrow key; however, letter_number is decremented this time.

The following line of code makes sure that letter_number is kept within the 0 ("A")... 26 ("Z") range. If the player has pressed space or enter, the current letter will be selected. We wait until the keys are released, and then we convert letter_number + 65 to a letter using str_for_asc. Why do we do that? Well, letter_number = 0 if "A" is selected, letter_number = 1 if "B" is selected, and so on. On the other hand, we know that "A" has an ascii code of 65, B has its ascii = 66 and so on; therefore, we must add 65 to our letter_number value in order to get the proper ascii codes for the letters.

We use str_cat to add the current letter to the name_str string, and then we center the text in the center of the screen using its length, as well as the width of the character in our bitmapped font. The video resolution is 800 pixels on the x axis, so all we need to do is to subtract half of the length of the text in pixels from 800 / 2 = 400 in order to center the text.