Beginner's corner

Top  Previous  Next

Feature of the month: remote cameras

This month I want to show you a "pro" feature that is extremely useful in action games, sport games, adventure games, strategy games... in fact, I think that any game could use a remote camera.
Acknex has improved a lot lately and this is one of the hot new additions to the engine: the ability to render a view on a sprite.

Practical use: display your football game on a big screen, keep an eye on your fortress' entrance, create rear view mirrors for your car and so much more!

First of all, we define a view:

view remote
{
    pos_x = 0;
    pos_y = 0;
    size_x = 128; // size of the view on x and y
    size_y = 64;
    layer = 5;
    flags = visible;
}

We must set its size on x and y to the size of the sprite (128 x 64 pixels in my example). I chose layer = 5 but you can choose any small value. This will be our virtual "screen" but we also need a camera to watch what's happening:

action camera1
{
    my.invisible = on;
    my.passable = on;
    remote.x = my.x;
    remote.y = my.y;
    remote.x = my.x;
    remote.pan = my.pan;
    remote.tilt = my.tilt;
    remote.roll = my.roll;
}

You can use any model for the camera: place it in Wed, set its position and angles and they will be transmitted to the remote view. This should be all... oh, wait, we need to show the image captured by the "screen" using a sprite:

action monitor
{
    my.oriented = on; // oriented sprite
    remote.bmap = bmap_for_entity (my, 0);
}

I am using an oriented sprite; the view will be rendered on "my" bitmap = the bitmap used for the sprite with this action attached to it.


Blood pool

This snippet will teach you make your enemies bleed as soon as they die. I have kept the code as simple as possible:

define number_of_hits skill1;

action enemy_blood
{
    var blood_coords; // local var
    my.enable_entity = on;
    my.enable_impact = on;
    my.event = enemy_hit;
    my.number_of_hits = 0;
    while(my.number_of_hits == 0) // standing
    {
         ent_cycle("stand", my.skill20); // play "stand" animation
         my.skill20 += 3 * time;
         my.skill20 %= 100; // loop
         wait (1);
    }

I have defined a local var named blood_coords. I know that you are too shy to ask but I will explain right here, right now what's with these local and global variables. A global variable is defined outside any action or function - it isn't enclosed by curly brackets. A global variable can be accessed from any other function or action. It is good to know that you can type ammo1 += 100 at the console and get 100 extra bullets for the weapon that uses ammo1, isn't it? A local variable is defined exactly like a global var, but its definition is placed inside the action or function, like the one in action enemy_blood. A local var is known only inside the action / function that has its definition inside it. If you would want to type blood_coords.x = 100; at the console it wouldn't work, get it? Local vars are just like entity skills: you can use them only within the action or function that has defined them. Let's get back to work now... The local var named blood_coords will be set to the origin of the blood spot. I could have used a global var but if you would have attached the same action to several models, every model could have changed blood_coords because this var would have been known to all the actions and functions. A local blood_coords var is recognized only inside its own action or function so the blood_coords value for one of the enemies can't interfere with other blood_coords value, used by another enemy.

The enemy is sensitive to impact; shoot it with a rocket to trigger its enemy_hit event. I have used number_of_hits as a name for skill1; as long as number_of_hits is zero, the enemy will play its "stand" animation in a loop

    // the enemy was hit by a rocket - it must die
    my.skill20 = 0;
    while(my.skill20 < 60) // one shot animation, 60% of "death" because the last "death" frame is damaged - open the model in Med to see for yourself
    {
         ent_cycle("death", my.skill20); // play "death" animation
         my.skill20 += 2 * time;
         wait (1);
    }
    my.event = null; // don't react to other events from now on
    // the enemy is dead here - time to create the blood pool
    vec_set(blood_coords, my.x);
    blood_coords.z -= 500;
    trace_mode = ignore_me + ignore_models + ignore_sprites;
    blood_coords.z = my.z - trace (my.x, blood_coords) + 2; // place the blood sprite 2 quants above the ground
    ent_create(blood_pcx, blood_coords, create_blood);
}

When number_of_hits is greater than zero, the enemy has to die; we play its death animation (only 60% of its death frames because the last death frame is damaged - open the model in Med to see it). We set the event to null, we trace 500 quants below the enemy to get the position of the floor, we set blood_coords.z two quants above the ground and we create the blood sprite using function create_blood:

function create_blood()
{
    my.passable = on;
    my.oriented = on;
    my.tilt = 90;
    my.flare = on;
    my.ambient = 100;
    my.bright = on;
    while (my.scale_x < 5)
    {
         my.scale_x += 0.05 * time;
         my.scale_y = my.scale_x;
         wait (1);
    }
}

The blood sprite is passable, oriented and has its flare and bright flags set. The sprite will increase its size on x and y five times.

The function that triggers all this scary stuff is:

function enemy_hit()
{
    if (event_type == event_entity || event_type == event_impact)
    {
         if (you == player) // collided with the player?
         {
              return; // do nothing
         }
         else // hit by a rocket
         {
              my.number_of_hits += 1;
         }
    }
    // add other events here
}

If the enemy has collided with an entity, it checks to see if that entity is the player. If the enemy was hit by a rocket, number_of_hits will be increased and the enemy will die.