Levelwechsel Code

Acknex benutzt ein einfaches System, um Level zu laden; eine einzige Zeile Code erlaubt es, von einem Level ins Nächste zu wechseln. Wenn Sie das ausprobiert haben, werden Sie feststellen, dass der neue Level die Waffen, Items, etc. löscht, die der Spieler mit sich führte, daher müssen diese neu erstellt werden. Es gibt nur zwei goldene Regeln:
1) Globale Variablen bleiben erhalten
2) Eine Funktion läuft nach dem Laden eines neuen Levels weiter, wenn diese Codezeile darin steht:

me = null;

Was sind globale Variablen? Es sind einfach normale Variablen, die in der WDL-Datei definiert sind und zwar AUSSERHALB jeglicher Funktionen:

var number_of_mice;

Diese Variablen behalten ihre Werte auch nach einer level_load Anweisung, also können Sie in ihnen die verbleibende Munition, die Anzahl der Leben etc. speichern diese Daten werden einen Levelwechsel überstehen.

Was hat es mit diesen Funktionen auf sich? Wenn Sie ein neues Level laden, werden alle Entities des alten Levels entfernt (klingt logisch). Das bedeutet, dass die Actions und Functions zu diesen Entities Fehlermeldungen geben würden, weil ihre zugeordnete Entity fort ist. My = null sorgt dafür, dass die Funktion zur Engine sagt: Hey, ich stehe alleine hier! Ich habe keine Entity, die zu mir gehört, also laß mich weiterlaufen!

Ich benutze die Standard office.wmp als erstes Level und eine leicht modifizerte Version (mit einer riesigen 2) als zweites Level. Im ersten Level liegen zwei Waffen und eine Entity, die den Levelwechsel auslöst, sehen wir uns die Waffen zuerst an:

action pistol
{
my.__rotate = on; // rotates before being picked up
my.__repeat = off; // disable auto fire
my.__silent = on; // don't show any pickup message
my._offs_x = 30; // position of the pistol
my._offs_y = -10; // on x, y, z
my._offs_z = 15;
my._ammotype = 1.20; // uses ammo type 1, comes with 20 bullets
my._weaponnumber = 1; // press "1" to select this weapon
my._bulletspeed = 1000.01; // speed.recoil
my._firetime = 8; // time to reload
my._firemode = damage_shoot + hit_hole + gunfx_brass + gunfx_animate + 0.10; // hit holes on the walls, damage = 10 points / shot
gun(); // call the real gun action
}

action machine_gun
{
my.__rotate = on; // rotates before being picked up
my.__repeat = on; // auto fire
my.__silent = on; // don't show any pickup message
my._offs_x = 35; // position of the gun
my._offs_y = 10; // on x, y, z
my._offs_z = 15;
my._ammotype = 2.99; // uses ammo type 2, comes with 99 bullets
my._weaponnumber = 2; // press "2" to select this weapon
my._bulletspeed = 5000.01; // speed.recoil
my._firetime = 2; // time to reload
my._firemode = damage_shoot + hit_hole + gunfx_brass + gunfx_animate + 0.20; // hit holes on the walls, damage = 20 points / shot
gun(); // call the real gun action
}

Ich habe eine Pistole mit 20 Kugeln und ein Maschinengewehr mit 99 Kugeln im Level plaziert. Drücken Sie die Tasten 1 und 2, um die jeweilige Waffe anzuwählen, nachdem Sie sie aufgehoben haben. Die Entity, die den Levelwechsel auslöst, hat folgenden Code:

action level_12
{
my.enable_impact = on;
my.event = change_levels_12;
}

function change_levels_12
{
wait (1);
if (you != player) {return;}
me = null;
level_load (level2_wmb);
sleep (1);
restore_12();
}

Die Entity reagiert auf Impact Events, wenn eine andere Entity mit ihr kollidiert, wird die Funktion change_levels_12 aufgerufen. Wenn die Entity, die die Kollision verursacht hat (you) nicht der Spieler ist, dann wechseln wir das Level nicht; andernfalls setzen wir me auf null, um sicherzugehen, dass die Funktion die level_load Anweisung überstehen wird, warten eine Sekunde und rufen dann die Funktion restore_12() auf, die alle Items wiederherstellen soll, die aus Level 1 in Level 2 mitgenommen wurden.

function restore_12()
{
while (player == null) {wait (1);}
if (weapon_1 == 1) // if the player has picked up weapon1
{
ent_create (weapon1_mdl, nullvector, pistol); // create the pistol again
}
if (weapon_2 == 1) // if the player has picked up weapon1
{
ent_create (weapon2_mdl, nullvector, machine_gun); // create the gun again
}
gun_loaded = 1; // make sure that it is loaded
gun_select(); // and select it
}

Diese Funktion wartet, dass der Spieler erstellt wird; wenn er Waffe 1 in Level 1 aufgehoben hat, wird diese erneut erzeugt und dieselbe Action wird aufgerufen (pistol), wenn der Spieler Waffe 2 in Level 1 aufgehoben hat, wird diese ebenfalls neu erstellt. Wir stellen sicher, dass die Waffe geladen ist und wählen dann die letzte Waffe aus, die in Level 1 benutzt wurde.

Ich habe einige Variablen verwendet, die in weapons.wdl definiert sind, um die Dinge zu vereinfachen. Wenn Sie Ihren Waffen / Inventar / etc. Code neu schreiben wollen, müssen Sie folgendes tun, um alle Items ins nächste Level zu bringen:
1) Setzen Sie eine bestimmte Variable auf 1, wenn Sie das Objekt aufgehoben haben (weapon_1 = 1 etc.)
2) Fügen Sie me = null in die Funktion ein, die das Level wechselt, BEVOR die level_load Anweisung ausgeführt wird
3) Warten Sie ein wenig (sleep(1) in meinem Beispiel, aber wait(2) würde es auch tun)
4) Rufen Sie die Funktion auf, welche die Items wiederherstellt
5) Diese Funktion prüft, welche Items aufgehoben wurden (weapon_1 = 0 oder 1?) und erstellt sie erneut, wenn die Variablen auf 1 stehen
6) Wiederholen Sie den Vorgang für all Ihre Levels (restore_23, restore_34, etc.)

Sichtweite

Haben Sie jemals Commandos gespielt? Dieses Spiel hat einen netten Effekt: Sie können die Sichtweite Ihrer Gegner sehen, also es ist zu erkennen, ob ein bestimmter Gegner Sie sehen kann oder nicht. Ich verwende die Engine schon lange, aber selbst ich bin beeindruckt, wie leicht es ist, einen solchen Effekt mit Acknex und C-Skript zu implementieren.

Ich verwende die Templates, deshalb definiere ich eine neue View, weil ich nicht zu viel Template Code modifizieren möchte:

view bird_view
{
layer = 15;
pos_x = 0;
pos_y = 0;
size_x = 640;
size_y = 480;
arc = 80;
flags = visible;
}

Ich habe eine Vogelperspektive in der Größe 640x480 Pixel erstellt. Wenn Ihr Spiel eine andere Auflösung hat, ändern Sie diese Werte. Ich habe einen praktischen Zoom Faktor gewählt (80), aber wenn Sie gern einen anderen hätten, ändern Sie einfach den Wert.

Der Feind ist die Standardwache, die auf ihrem Pfad Patroullie läuft. Diese Entity wird den Spieler und die vier... Lampen bemerken, die das Team darstellen. Die einizige Änderung für Ihre Feinde ist diese erste Codezeile in der Action für den Feind:

action patrol_prog
{
init_sight();
guard = me;
// watched = me;
..................
}

Wie Sie sehen, habe ich diese Zeile zur Action patrol_prog hinzugefügt. Ich habe die modifizierte office.wdl Datei zum Download Archiv gepackt. Schauen wir uns an, was die Funktion init_sight tut:

function init_sight()
{
LensFlare_Stop(); // diasble the lens flares if they are present
camera.visible = off;
clip_size = 0;
my._health = 100; // if needed
ent_create (cone_mdl, my.pos, move_cone);
bird_view.x = 0;
bird_view.y = 0;
bird_view.z = 1200;
bird_view.tilt = -90;

Wir schalten den Lens Flare Effekt ab, weil wir den in einer Sicht von oben nicht brauchen; wenn Sie Fehlermeldungen erhalten, dann benutzen Sie diesen Effekt gar nicht, in diesem Fall entfernen Sie die erste Zeile in der init_sight() Funktion. Wir verbergen die voreingestellte View (Camera) und stellen ein, dass alle Polygone unserer Models zu sehen sein sollen. Die Health der Wache wird auf 100 gesetzt (das brauchen wir später), erzeugen das Model für den Sichtkegel und geben diesem die Action move_cone.

Schließlich wird unsere Vogelperspektive 1200 Quants über dem Boden plaziert mit Blick nach unten wegen des Wertes vom Tilt Winkel.

while (my._health > 0)
{
my.skill40 = 40; // pan
my.skill41 = 180; // tilt
my.skill42 = 300; // range
scan_entity (my.x, my.skill40);
if ((result > 0) && (!snd_playing (alert_handle)))
{
alert_handle = snd_play (alert_wav, 50, 0);
}
wait (1);
}
}

Solange die Wache am Leben ist, setzen wir einen Scansektor von 40 Grad horizontal, 180 Grad vertikal und eine Reichweite von 300 Quants.

Die Wache ist nun bereit zu scannen; wenn irgendetwas in seiner Reichweite entdeckt wird, dann löst dies ein Alarmgeräusch aus. An diesem Punkt haben wir also einen Feind, der nach anderen Entities scannen kann und ein Geräusch abspielt, wenn dies geschieht. Schauen wir uns an, wie der Sichtkegel funktioniert:

function move_cone()
{
proc_late();
my.passable = on;
my.albedo = 0;
my.transparent = on;
my.alpha = 20;
my.z += 25;

Die erste Codezeile stellt sicher, dass sich die Wache und der Kegel korrekt zueinander bewegen. Der Kegel selbst ist passable und transparent; sein z-Wert ist 25 Quants über dem z-Wert der Wache (dies ist die Augenhöhe für das Wachenmodel).

while (you._health > 0) // as long as the creator (the enemy) is alive
{
my.x = you.x;
my.y = you.y;
my.pan = you.pan;
my.tilt = you.tilt;
my.scale_x = you.skill42 / 10;
my.scale_y = 2 * you.skill42 * tan(you.skill40 / 2) / 10;
wait (1);
}
// the enemy is dead here -> destroy the cone
ent_remove (me);
}

Solange der Feind lebt, hängt der Kegel an ihm und übernimmt auch den Tilt Winkel. Die Ausdehnung des Models in x-Richtung ist durch skill42 / 10 gegeben. Wieso das?

Starten Sie MED und öffnen Sie cone.mdl:

Das Model für den Sichtkegel ist in Wahrheit ein Dreieck der Größe 10 Quants. Wenn wir eine Reichweite von 300 einstellen, müssen wir scale_x für das Model auf 30 setzen, denn dies gibt eine Größe von 30 * 10 = 300 Quants. Ich hätte auch ein Dreieck von der Größe 1 benutzen können, aber es ist nicht möglich für scale_x einen Wert größer als 100 anzugeben und so hätte dies die Reichweite des Scans auf 100 bechränkt. Daher dividieren wir skill42 noch durch 10. Die nächste Codezeile sieht noch furchteinflößender aus:

my.scale_y = 2 * you.skill42 * tan(you.skill40 / 2) / 10;

Hier ist ein Rat für die jüngeren Leser: lernt Mathematik zu lieben! Ich habe es gehaßt, als ich in der Oberstufe und später an der Uni war, aber wenn Sie sich ein Leben als Programmierer vorstellen können, müssen Sie eine Menge Mathematik können!

Anhand des Bildes können Sie sehen, wie ich auf die Formel kam; scale_y hängt von scale_x (skill42) und vom horizontalen Winkel, in dem gescannt wird ab (you.skill40).

Wenn der Gegner an Gesundheit verliert, verschwindet der Kegel. Wenn Sie die Demo sorgfältig testen, werden Sie feststellen, dass es mit dem Dreieck funktioniert, aber wenn Sie eine höhere Präzision wollen, sollten Sie ein Model verwenden, das mehr wie ein Kegel aussieht. Schauen Sie sich das Bild an, um zu sehen, wie es aussehen müßte.

Ich habe vergessen, Ihnen noch etwas mitzuteilen: die Entities, die gescannt werden sollen, müssen alle die enable_scan Flag gesetzt haben. Beim Spieler ist das der Fall und hier ist die Action, die zu den 4 Lampen gehört:

action scan_me
{
my.enable_scan = on;
// add your code below
}

Das ist alles. Natürlich können (und sollten) Sie noch mehr Code für Ihre Verbündeten hinzufügen, aber vergessen Sie nicht, die Flag zu setzen, sonst werden sie nicht bemerkt!