Beginner's corner

Top  Previous  Next

Splitball

Splitball (split screen ball game) uses one of Acknex's distinct features: the ability to have multiple views at the same time. Let's take a look at function set_cameras - it is called in main:

function set_cameras()
{
    camera.size_x = 400;
    camera.size_y = 600;
    camera.pos_x = 0;
    camera.pos_y = 0;
    camera.x = first.x;
    camera.y = first.y;
    camera.z = first.z;
    camera.tilt = -90;
    camera.pan = first.pan;
    camera.visible = on;

    camera2.size_x = 400;
    camera2.size_y = 600;
    camera2.pos_x = 400;
    camera2.pos_y = 0;
    camera2.x = second.x;
    camera2.y = second.y;
    camera2.z = second.z;
    camera2.tilt = -90;
    camera2.pan = second.pan;
    camera2.visible = on;
}

I have set the screen resolution to 800x600 so my views (named camera and camera2) have 400x600 pixels each. I have placed my (almost) famous arrow.mdl model in the level; its position (xyz) and orientation (pan) are transferred to the coresponding view. The arrows are named first and second (to be honest, this is the name of the synonyms associated to them using action player1eye and player2eye.

The players are using similar actions; let's take a look at one of them:

action player1
{
    my.push = 5;
    my.enable_block = on;
    while (1)
    {
         while (score1 == 9 || score2 == 9)
         {
              wait (2);
         }
         if (key_z == 1 && my.x > -270)
         {
              ent_move (move_left, nullvector);
         }
         if (key_x == 1 && my.x < 270)
         {
               ent_move (move_right, nullvector);
         }
         move_left.x = -25 * time;
         move_right.x = 25 * time;
         wait (1);
    }
}

Everything happens inside a while (1) loop; if the game is over (score1 = 9 or score2 = 9) the players can't move, otherwise player1 moves to the left ot right depending on the key that was pressed (z or x). Finally we compute the paddle movement speed every frame. It is true that we have limited the frame rate to 50 so these values don't change too much but on weak systems and / or in software mode the frame rate could go below 50 fps.

The wall that surrounds the field is a simple entity that has its skill11 set to 1; I did that to be able to test if the ball collides with one of the paddles or the wall.

The ball has the following action attached to it:

action ball
{
    ball_syn = me;
    my.skill10 = 1;
    my.enable_entity = on;
    my.event = change_direction;
    if (random(1) > 0.5)
    {
         my.pan = 45 + random(90);
    }
    else
    {
         my.pan = 225 + random(90);
    }
    while (1) 
    {
         ball_speed.x = 30 * time;
         ball_speed.y = 0;
         ball_speed.z = 0;
         ent_move (ball_speed, nullvector);
         wait (1);
    }
}

The ball has its skill10 set to 1 and it triggers its event (change_direction) if it collides with another entity (one of the players, the wall or the goal entity). When the game starts, the first ball can come towards player1 if random(1) > 0.5 or player2 if random(1) <= 0.5. This gives the players even chances at game start. I have choosed decent pan angles - take a look at the picture below:

The ball is moving all the time using a few lines of code - there's nothing special here.

Every time you score a goal, action goal (attached to the goal.wmb entity) triggers its event: function add_score:

action goal
{
    my.enable_impact = on;
    my.event = add_score;
}

function add_score()
{
    if (you.skill10 == 1)
    {
         if (score1 == 9 || score2 == 9) {return;}
         play_sound goal_snd, 50;
         if (my.flag1 == on)
         {
             score1 += 1;
             wait (1);
             ball_syn.x = 0;
             ball_syn.y = 0;
             ball_syn.pan = 225 + random(90);
         }
         if (my.flag2 == on)
         {
             score2 += 1;
             wait (1);
             ball_syn.x = 0;
             ball_syn.y = 0;
             ball_syn.pan = 45 + random(90);
         }
    }
}

First of all we check if the ball has collided with the goal.wmb entity (you.skill10 = 1); if the game is already over (score1 = 9 or score2 = 9) there's nothing to do, otherwise we play the goal_snd sound and we add the score for player1 or player2 (if flag1 or flag2 is set for goal.wmb in wed). If player1 scores, the ball will be placed at the center and it will start moving towards him (that's the right thing to do).

The game is restarted every time we reset the scores - this starts the while (1) loops for the players, allowing them to move again.

Password request code

This is the code for your npc (non-playing character). You come close to it, it asks you "What's the password?", you type the password in and if you know it, you can open certain doors or trigger any other actions or functions.

I'm using three strings: the first one displays the password request string, the second will store my keystrokes (ten characters, but you can increase the number) and the final string holds the predefined password.

string passreq_str = "What's the password?";
string pass_str[10];
string password_str = "geronimo";

I'm using a classical test definition to display the strings on the screen and a simple request_pass action that will be attached to my npc.

text pass_txt
{
    pos_x = 20;
    pos_y = 20;
    layer = 15;
    font standard_font;
}

action request_pass
{
    my.enable_impact = on;
    my.enable_push = on;
    my.event = ask_pass;
}

The interesting stuff happens in function ask_pass:

function ask_pass ()
{
    if (you == player)
    {
         if (pass_txt.visible == on) {return;}
         pass_txt.string = passreq_str;
         pass_txt.visible = on;
         waitt (32);
         pass_txt.string = pass_str;
         while (pass_txt.visible == on)
         {
              inkey pass_str;
              if(result == 13) 
              {
                   pass_txt.visible = off;
              }
         }
         if (str_cmpi (pass_str, password_str) == 1)
         {
               key1 = 1; 
               new_event();
         }
    }
}

This function will run only if the entity that collided with the npc is the player itself. If you have a decent computer, multiple events can run at the same time and you don't want that. We can use exclusive_global or my method:

if (pass_txt.visible == on) {return;}

If pass_text is visible already, the function stops so only one instance will run (the first one that makes the text visible). We display the passreq_str string for two seconds and then we wait for player input; this will happen as long as the player doesn't press Enter (result is 13 if Enter is pressed). When we have finished typing the passowrd and we press Enter, the text is made invisible and a simple str_cmpi instruction is performed. If pass_str == password_str (user input = predefined password) and we are using the templates, key1 = 1 means that we can open doors that can be unlocked if we have key1, key2 = 1 means that we can open doors that are using key2 and so on. We're also calling the new_event function that can be used to start other functions, etc.