Previous: If - Else branching

Using the keyboard

If you can't control a game, you could as well watch a movie. The good news is that lite-C allows you to control what's happening in the level using the keyboard, the mouse, the joystick and simple script instructions. If you want to use other hardware devices with the engine (a light gun, for example) you can use the SDK to develop an interface for them using C++ or Delphi.

Lite-C can do several things when we press a key on our keyboard:

1) Lite-C can set a predefined variable to 1 when we press a key on the keyboard. The good news is that these predefined variables have meaningful names, like this:

key_a, key_b, key_shift, key_space, key_pause, key_f1, etc.

You can check if (key_a == 1) and if this is true, you know for sure that the player has pressed the "A" key.

2) Lite-C can generate a scan code. That's a simple number, but its value depends on what key you have pressed. You can check the value of the scan code to see what key was pressed, using key_lastpressed. Read the Lite-C manual to find more information about these keywords, including a table with all the scan codes.

This method is used in some rare cases, like redefining the keys. You can find a few examples that use scan codes in the Acknex User Magazine (AUM).

3) Lite-C is able to run a specific function when we press a key on the keyboard, if we have attached a function to a certain key. I have used an example in one of the previous workshops, the one that talks about pointers:

function move_up()
{
    wizard.z += 5;
}

...
on_u = move_up;
...

This example runs a previously defined function named "move_up" when we press the "U" key (it doesn't matter if caps lock is on or off). Don't forget that the function name must be given without parenthesis.

Important tip

I encourage you to read my AUMs if you want to learn even more about lite-C; the code inside those articles is explained step by step. Oh, and don't copy and paste code from the magazines because it is simplified a little - there are links to the fully working, downloadable lite-C files in all the magazines.

Let's get to work! Start Lite-C, run script13 and you will see all three keyboard methods listed above in action (the nice sky texture was created by Mighty Pete). Press W or S to tilt the camera up and down; press A and D to change the pan angle of the camera. Press 1 to make the sky brighter and press 2 to reduce the sky light. Press R to restore the initial camera angles. Once again, you can toggle the window mode / full screen mode using the Alt + Enter key combination.

I'm sure that you won't be too surprised when you will hear that all these things were created using a small script file:

////////////////////////////////////////////////////////////////////
#include <acknex.h>
#include <default.c>
////////////////////////////////////////////////////////////////////
ENTITY* my_sky;
   
function bright_lights()
{
   vec_add(my_sky.blue,vector(10, 10, 10));
}
   
function dim_lights()
{
   vec_add(my_sky.blue,vector(-10, -10, -10));
}
   
function main()
{
   on_1 = bright_lights;
   on_2 = dim_lights;
   level_load ("");
   my_sky = ent_createlayer("blood_gorge+6.tga", SKY | CUBE | SHOW, 0);
   while (1)
   {
      if (key_w) camera.tilt += 2 * time_step; 
      if (key_s) camera.tilt -= 2 * time_step; 
      if (key_a) camera.pan += 2 * time_step;
      if (key_d) camera.pan -= 2 * time_step;
      if (19 == key_lastpressed) // if the [R] key is pressed
      {
         camera.pan = 0; // restore the initial pan
         camera.tilt = 0; // and tilt angles
         vec_set(my_sky.blue,vector(128,128,128));    //   set a medium sky brightness
      }
      wait (1);
   }
}   

First of all, let discuss the method that was used to create the nice looking sky:

level_load (" ");
my_sky = ent_createlayer("blood_gorge+6.tga",SKY | CUBE | SHOW, 0);

We are loading a level just like we did in the 10th workshop, but this time it's a completely empty level, as indicated by the empty name string that's used by the level_load function. The following line of code creates a sky; ent_createlayer is an instruction that creates a (2D) layered entity, which means that we could put several entities on different layers, on top of each other. Our example places the sky on the first layer (layer = 0). Sky entities are layered entities, and because a layered entity can also live in a 2D world, we can create them without needing a "real" 3D level.

As you might have guessed, "blood_gorge+6.tga" is the name of the six-sided sky bitmap which can be found inside the workshop13 folder. SKY, CUBE and SHOW are flags indicating that the entity is a visible sky cube. You can find the full list of sky flags in the Lite-C manual.

The advanced programmers would say that ent_createlayer returns a pointer to the created entity. We've defined the ENTITY* pointer named "sky" at the beginning of the script; ent_createlayer will create the sky entity, and our "my_sky = ent_createlayer("blood_gorge+6.tga",SKY | CUBE | SHOW,0);" line of code will tell the engine that our newly created sky will be named my_sky, get it?

Time to discuss the content of the while loop:

while (1)
{
    if (key_w) camera.tilt += 2 * time_step;
    if (key_s) camera.tilt -= 2 * time_step;
    if (key_a) camera.pan += 2 * time_step;
    if (key_d) camera.pan -= 2 * time_step;
    if (19 == key_lastpressed) // if the [R] key is pressed
    {
        camera.pan = 0; // restore the initial pan
        camera.tilt = 0; // and tilt angles
        vec_set (my_sky.blue, vector(128,128,128)); // set a medium sky brightness
    }
    wait (1);
}


If we press the [
W] key, the code increases the camera's tilt angle by adding 2 * time_step to it (you remember time_step from the 10th workshop, don't you?). If we press the [S] key, the code decreases the tilt angle. Pressing [A] increases camera.pan and [D] decreases camera.pan.

We have used the dot method quite a bit lately, so you should remember its general form: object.property. The camera (our view inside the 3D world) is an object too, so we can change its properties: angles, position, ambient, etc using the dot method. We are using a few of the advanced programmer's shortcuts here; when we compare two expressions, a non-zero variable is always considered to be true. This means that whenever we press the [W] key, (key_w) becomes true (is set to 1), so we can use something like "if (key_w)", which works fine, just like a "if (key_w == 1)". Another thing: we have written the camera instructions next to the if comparisons in order to make the code look shorter. You should also remember these simplified instructions:

camera.tilt += 2 * time_step;

which do exactly the same job with their bigger brothers:

camera.tilt = camera.tilt + 2 * time_step;

and so on. You can use the same shortcut for any other mathematical operator used by the engine. Sometimes in programs by advanced lite-C writers you can see another sort of shortcut:

    camera.tilt += (key_w - key_s) * 2 * time_step;
    camera.pan += (key_a - key_d) * 2 * time_step;

This does the same as our four if... lines above, and takes advantage of the fact that key_w becomes 1 when the [W] key is pressed. So, the expression (key_w - key_s) is 1 when [W] is pressed, is -1 when [S] is pressed, and is 0 when neither is pressed (or both). We can multiply this with 2*time_step and add the result directly to the angle, without needing if comparisons!

Let's take a look at the last part of the loop, which uses the scan code (the second keyboard method):

if (19 == key_lastpressed) // if "R" is pressed
{
    camera.pan = 0;
    camera.tilt = 0;
    vec_set (my_sky.blue, vector(128,128,128)); // medium brightness
}

The predefined variable named key_lastpressed stores the scan code for the last key that was pressed on the keyboard. Whenever you press [R], key_lastpressed will be set to 19, which is the scan code that corresponds to the "R" key. The "if" instruction tests if [R] was pressed (with the constant value placed on the left side, the way we've learned it in the last workshop) and if this is true, it resets the camera (its initial angles are pan = 0, tilt = 0).

The scan codes that are generated by the engine have nothing to do with the ASCII or ANSI codes, although they can be converted to ASCII / ANSI (don't bother to read the last phrase if you don't know anything about ASCII and ANSI codes). Tip: Check the reference manual ("key mapping") to see a table with all the scan codes for every key on the keyboard.

The last line of code inside the if branch sets the brightness of the sky to a medium value. We are using the dot method again, with our object being the sky entity this time. Every entity has a color that can be set or changed by playing with its blue, green, and red values. These values can range from 0 to 255, so by setting them at 128 we're getting a medium color and brightness. We are using the vec_set instruction here, just like in the 10th workshop, in order to set all three values in a single line of code. The blue value also serves as a blue-green-red color vector, just like the x value which was serving as a x-y-z position vector in workshop 10.

Finally, let's take a look at the two functions that run when we press the 1 or 2 key on the keyboard:

function bright_lights()
{
    vec_add (my_sky.blue, vector(10,10,10));
}

function dim_lights()
{
    vec_add (my_sky.blue, vector(-10,-10,-10));
}

...
on_1 = bright_lights;
on_2 = dim_lights;
...

The first function (bright_lights) increases the sky brightness by adding a value to its colors. vec_add works similar with vec_set; it just adds three values at once instead of setting them like vec_set. Our vec_add line of code:

vec_add (my_sky.blue, vector(10,10,10));

does the same as:

my_sky.blue += 10;
my_sky.green += 10;
my_sky.red += 10;

This piece of code had the same effect, but would be longer and run a little slower. The second function (dim_lights), which runs in a similar way, reduces the sky brightness by decreasing 10 from each sky color component (blue, green, red). Once again, you should note the missing parentheses for the two functions in the "on_1 = ..." and "on_2 = ..." lines from the main function. If we would have placed parentheses at the end of the function names, the functions bright_light and dim_light would have been executed, rather than assigned to the keys!

The keyboard method that works with scan codes isn't used too often. You should use the "on_x = do_something;" method (replace "x" with any other key) when you want to run a specific action or function that executes something and then stops running (opens a door, turns on the light, loads a game, adds two numbers, etc). Use the "if (key_x)" method and a while loop when you want to control an action or function that is supposed to run continuously (player's movement, the camera, a vehicle, a paddle, etc).

Next: Using the mouse