Wenn
Sie Stratego aus AUM 2 mochten, dann werden Sie Stratego 2 lieben, weil
es folgende neue Features enthält:
-
Echte 3D - Levels
-
mit der Maus auswählbare Einheiten
-
Mehrere gleichzeitig ausgewählte Einheiten (man zieht einen Rahmen
um die Einheiten)
-
Verschiedene Sounds für jede Einheit
Vorrausgesetzt wird, dass Sie sich mit dem Code von Stratego auskennen, sodass nicht geklärt werden muss, wie man Gebäude, Einheiten usw. erstellt. All dies ist in AUM 2 erklärt, hier konzentrieren wir uns nur auf das Wesentliche.
Stratego 2 ist ein Standalone-Projekt, sodass es seine eigene Hauptfunktion besitzt:
function
main()
{
level_load (stratego2_wmb);
wait (2); // wait for the level to be loaded
clip_size = 0; // show all the triangles for all the models
fps_max = 40; // lock the frame rate
camera.x = 0;
camera.y = 0;
camera.z = camera_height;
camera.tilt = -90;
camera.pan += 90;
mouse_mode = 2;
mouse_map = pointer_map;
mouse_pos.x = screen_size.x / 2;
mouse_pos.y = screen_size.y / 2;
move_camera();
multiple_selection();
while (index < 9)
{
temp.x = start_pos[index];
temp.y = start_pos[index + 1];
temp.z = start_pos[index + 2];
ent_create (guard_mdl, temp, init_unit);
index += 3;
}
}
Die Kamera schaut deshalb runter, weil der tilt = -90; ist (Vogelperspektive) und der pan ist deshalb mit 90° gesetzt, weil die Kamera somit in die richtige, gewünschte Richtung zeigt. Der Mauszeiger ist sichtbar und im Zentrum des Bildschirmes platziert und dann werden noch ein paar Funktionen initialisiert, über die wir noch reden werden. Drei Einheiten (guard.mdl) werden erstellt; die Startpositionen bei Spielbeginn werden hier gelesen:
var start_pos[9] = 200, 200, 500, -200, 200, 500, 0, -200, 500;
Der erste Guard ist bei xyz = 200, 200, 500, der zweite bei -200, 200, 500 (usw.) positioniert. Jeder Guard ruft die Funktion init_unit(); auf:
function
init_unit()
{
wait (1);
my.enable_click = on;
my.enable_entity = on;
my.enable_block = on;
my.event = move_unit;
vec_set (temp, my.pos);
temp.z -= 3000;
trace_mode = ignore_me + ignore_sprites + ignore_models + use_box;
my.z -= trace (my.pos, temp); // place the unit on the ground (it has been
spawned in the air)
my.target_x = 100000;
while (1)
{
while (my.target_x < 50000) {wait (1);}
if ((my.x > upleft_coords.x) && (my.x < upright_coords.x) &&
(my.y < upleft_coords.y) && (my.y > downleft_coords.y) &&
(my.unit_selected == 0))
{
my.unit_selected = 1;
move_unit();
}
else
{
if (my.unit_selected == 1)
{
vec_set (upleft_coords, nullvector); // reset selection coords
vec_set (upright_coords, nullvector);
vec_set (downleft_coords, nullvector);
vec_set (downright_coords, nullvector);
}
}
wait (1);
}
}
Die
Einheit reagiert auf die Maus, auf Kollisionen in der Level-Geometrie und
mit anderen Objekten. Der Guard wird in der Luft erstellt (der Z Wert war
im Array auf 500 gestellt, nicht?), sodass er nur noch auf den Boden bewegt
werden muss. Wir ziehen dafür das Ergebnis eines 3000 Quants - Trace
(abwärts!) vom ursprünglichen Z-Wert ab. My.target_x = 100000
zeigt nur an, dass die Einheit kein Ziel hat; der Wert 100000 ist einfach
nur ein Indikator, der dies anzeigt („außer Reichweite”).
Sie finden in Stratego 2 viel Code bezüglich my.target_x, my_unit_selected und weiteren, aber kein Grund zum Verzweifeln: dies sind Skills, die am Anfang der stratego.wdl definiert sind: z.B. ist my.target_x gleich skill12, es sieht halt besser aus.
Jede Einheit hat eine while(1) Schleife, die auch weiter läuft, wenn die Einheit etwas anderes macht; wenn der target_x einer Einheit größer als 50000 ist (100000 bedeutet „kein Ziel”, nicht?) und sie ist ausgewählt (nur sie oder mit anderen) durch ziehen eines Rahmens, dann wird die Einheit ausgewählt (my.unit_selected = 1) und sie bewegt sich in Richtung des Ziels, was jetzt ausgewählt ist. Die Koordinaten für das Auswählen werden dann zurückgesetzt, wenn die Einheit markiert wurde. Wenden wir uns nun etwas diesen Koordinaten zu:
function
multiple_selection()
{
while (1)
{
if (mouse_left == 1)
{
upper_left.visible = on;
upper_right.visible = on;
lower_left.visible = on;
lower_right.visible = on;
if (first_click == 0) // make sure that this "if" branch is executed only
once
{
first_click = 1;
upper_left.pos_x = pointer.x; // store panel's position
upper_left.pos_y = pointer.y;
}
lower_right.pos_x = pointer.x; // store panel's position
lower_right.pos_y = pointer.y;
lower_left.pos_x = upper_left.pos_x; // store panel's position
lower_left.pos_y = lower_right.pos_y;
upper_right.pos_x = lower_right.pos_x; // store panel's position
upper_right.pos_y = upper_left.pos_y;
}
else // finished multiple selection
{
upper_left.visible = off;
upper_right.visible = off;
lower_left.visible = off;
lower_right.visible = off;
first_click = 0;
upleft_coords.x = upper_left.pos_x; // project upper_left's panel coords
on the map
upleft_coords.y = upper_left.pos_y;
upleft_coords.z = camera_height;
vec_for_screen (upleft_coords, camera);
upright_coords.x = upper_right.pos_x; // project upper_left's panel coords
on the map
upright_coords.y = upper_right.pos_y;
upright_coords.z = camera_height;
vec_for_screen (upright_coords, camera);
downleft_coords.x = lower_left.pos_x; // project upper_left's panel coords
on the map
downleft_coords.y = lower_left.pos_y;
downleft_coords.z = camera_height;
vec_for_screen (downleft_coords, camera);
downright_coords.x = lower_right.pos_x; // project upper_left's panel coords
on the map
downright_coords.y = lower_right.pos_y;
downright_coords.z = camera_height;
vec_for_screen (downright_coords, camera);
}
wait (1);
}
}
Die Funktion multiple_selection() setzt die Koordinaten, die gebraucht werden, damit jede Einheit erkennt, ob sie ausgewählt ist oder nicht, indem ein Rahmen erstellt wird.
Wenn
man auf die Karte klickt (mouse_left = 1) erscheinen vier panels (upper_left,
upper_right, lower_left, lower_right). Diese da sind die kleinen grünen
auf dem Bild unten. Das erste Panel (upper_left) erscheint an der Stelle
des Mauszeigers und bleibt dort; die übrigen positionieren sich je
nach Maus - Bewegung um einen Rahmen beliebiger Breite/Höhe zu zeichnen.
Dafür benutzen wir ein „entity”-panel (quadratisch) und ändern
dessen Ausmaße über scale_x und scale_y.
Wenn die Maustasten nicht mehr aktiv sind und der Rahmen gezeichnet wurde, setzt der Code bei dem „else” fort: die Panels verschwinden und die Koordinaten der vier Ecken werden in 3D-Vektoren mithilfe von vec_for_screen, wie im Vild zu sehen, umgesetzt.
Hier haben wir einen virtuellen Rahmen erstellt, der die gleiche Größe wie der auf dem Bildschirm hat; der Code in init_unit prüft, ob sich die zugehörige Einheit in dem Rahmen befindet:
if
((my.x > upleft_coords.x) && (my.x < upright_coords.x) &&
(my.y < upleft_coords.y) && (my.y > downleft_coords.y) &&
(my.unit_selected == 0))
{
..............
}
In
meinem Beispiel sind alle Einheiten in diesem Rahmen platziert, sodass
alle markiert sind.
Hier ist nun die Funktion move_unit():
function
move_unit()
{
if (event_type == event_block || event_type == event_entity) // collision
with other entities or level geometry
{
if (abs(my.x - my.target_x) + abs(my.y - my.target_y) < 100) // close
to the target but other entities are already there
{
my.target_reached = 1; // the unit has reached the target
my.unit_selected = 0;
my.target_x += 100000; // move the target far away to get out of this "if"
branch
}
else
{
// caveman's path finding code - read more about it in Aum2
my.pan += 90 - random(180);
waitt (10); // wait a little
temp.x = my.target_x; // stored mouse pointer coordinates
temp.y = my.target_y;
temp.z = 0;
vec_sub (temp, my.x);
vec_to_angle (my.pan, temp); // rotate unit towards the target again
my.tilt = 0; // stand tall :)
}
}
if (event_type == event_click || my.unit_selected == 1)
{
my.destination = 0;
my.target_reached = 0; // target not available yet
ent_create (selected_pcx, my.pos, selected_unit);
my.selected_handle = snd_play (selected_snd, 70, 0);
snd_tune (my.selected_handle, 70, 80 + random(20), 0); // different "huh?"
voices
while (mouse_left == 1) {wait(1);} // wait for the mouse release
while (my.destination == 0) // as long as the target hasn't been set
{
if (mouse_left == 1)
{
mouse_map = pointerhigh_map;
my.ok_handle = snd_play (ok_snd, 70, 0); // store the sound handle in a
skill, no need to use a separate var
snd_tune (my.ok_handle, 70, 80 + random(50), 0); // different "ok" voices
waitt (4); // show the cross for 0.25 seconds
mouse_map = pointer_map;
my.destination = 1;
temp.x = mouse_pos.x;
temp.y = mouse_pos.y;
temp.z = camera_height;
vec_for_screen (temp, camera); // temp holds pointer's coords now
my.target_x = temp.x; // store pointer's coords before they get lost
my.target_y = temp.y;
vec_sub (temp, my.x);
vec_to_angle (my.pan, temp); // rotate the unit towards the target
my.tilt = 0; // we only need the correct "pan" angle, not tilt
my.fuel = 300; // maximum path length (fuel)
while (abs(my.x - my.target_x) + abs(my.y - my.target_y) > 3 &&
my.fuel > 0 && my.target_reached != 1) // stop near the target
{
my.unit_selected = 0;
vec_set (temp, my.x);
temp.z -= 3000;
trace_mode = ignore_me + ignore_sprites + ignore_models + use_box;
unit_speed.z = -trace (my.x, temp);
move_mode = ignore_you + ignore_passable;
ent_move (unit_speed, nullvector);
ent_cycle("walk", my.animation_frame); // play walk frames animation
my.animation_frame += 5 * time; // "walk" animation speed
if (my.animation_frame > 100) {my.animation_frame = 0;} // loop animation
my.fuel -= time; // burn fuel
wait (1);
}
my.target_reached = 1; // the unit has reached the target
my.target_x += 100000;
ent_cycle("stand", 0); // the unit stands still now
}
wait (1);
}
}
}
Diese Funktion wird aufgerufen, wenn auf die Einheit geklickt wurde (event_click ausgelöst) oder wenn my.unit_selected =1 ist (Rahmen-Auswahl). Wenn etwas davon passiert, wird ein grüner Sprite erstellt, der die Einheit markiert, ein Sound wie „ja?” wird mit einer zufälligen Frequenz gespielt (deshalb die Benutzung von snd_tune und random) und dann wird bis zum loslassen der linken Maustaste gewartet. Jetzt wird das Ziel angegeben: wenn wir irgendwo auf die Karte klicken, wechselt der Pointer nach pointerhigh_map (ein Kreuz), ein „ok!” Sound ertönt, und der Mauszeiger ist auf normal gesetzt. Eine weiterer vec_for_screen Befehl setzt die Maus Koordinaten in eine Levelposition um; dieser Vektor ist das Ziel der Einheiten! Die Guards drehen sich Richtung Ziel und bewegen sich solange dorthin bis die Distanz dahin < 3 ist. Wenn das Ziel unerreichbar für eine Einheit ist, stoppt sie nach einer bestimmten Zeit.
Hier wird nun die Benutzung von my.fuel erklärt: die Einheit startet mit fuel = 300 und verbrennt das Benzin während sie sich durch den Level bewegt. Wenn fuel < 0 ist, stoppt die Einheit abprupt.
Es wird weiterhin Trace benutzt, da die Einheiten die ganze Zeit auf dem Boden bleiben sollen. Die Einheiten tracen nur, wenn sie sich bewegen. Wenn die Einheit das Ziel erreicht hat, oder fuel < 0 ist, wird der target_x der Einheit auf eine Variable von 100000 gesetzt, was heißt, dass die Einheit kein Ziel hat, und das Flag target_reached wird gesetzt.
if (event_type == event_block || event_type == event_entity) // collision
with other entities or level geometry
{
if (abs(my.x - my.target_x) + abs(my.y - my.target_y) < 100) // close
to the target but other entities are already there
{
my.target_reached = 1; // the unit has reached the target
my.unit_selected = 0;
my.target_x += 100000; // move the target far away to get out of this "if"
branch
}
else
{
// caveman's path finding code - read more about it in Aum2
my.pan += 90 - random(180);
waitt (10); // wait a little
temp.x = my.target_x; // stored mouse pointer coordinates
temp.y = my.target_y;
temp.z = 0;
vec_sub (temp, my.x);
vec_to_angle (my.pan, temp); // rotate unit towards the target again
my.tilt = 0; // stand tall :)
}
}
Die Zeilen im Code der Funktion move_unit sind sehr nützlich, da sie auf eine Kollision mit anderen Einheiten oder des Levels reagieren. Wenn das Ziel sehr nahe ist und es eine Kollision gibt, ist es nahezu sicher, dass eine andere Einheit das Ziel schon erreicht hat; alle Einheiten haben dasselbe Ziel. Wenn nun eine zweite, dritte, etc. Einheit in der Nähe des Ziels ist und etwas berührt (z.B. eine andere Einheit!), dann bleibt sie stehen. Wenn die Einheit kollidiert ist, aber weit weg vom Ziel ist, wird die pan geändert, die Einheit geht etwas vorwärts, und es wird nochmal versucht, das Ziel zu finden.
function
selected_unit()
{
my.unlit = on; // shouldn't be affected by the lights in the level
my.ambient = 100; // make it bright
my.oriented = on;
my.passable = on;
my.tilt = 90;
while (you.target_reached == 0) // as long as the target hasn't been reached
{
vec_set (my.pos, you.pos); // move with the unit
wait (1);
}
ent_remove (me); // the target has been reached so the square has to disappear
}
Die Funktion selected_unit bewegt den grünen Sprite, der die ausgewählte Einheit umgibt, solange, bis die Einheit das Ziel erreicht hat.
Die letzte Funktion ist aus AUM2:
function
move_camera()
{
waitt (4); // wait for the level to load
level_marginx = 1000; // the map has 2000 quants on x (-1000...+1000)
level_marginy = 1000; // and on y (-1000...+1000)
while (1)
{
mouse_pos.x = pointer.x;
mouse_pos.y = pointer.y;
if (mouse_pos.x < 1 && camera.x > level_marginx * (-1)) {camera.x
-= 10 * time;}
if (mouse_pos.x > screen_size.x - 2 && camera.x < level_marginx)
{camera.x += 10 * time;}
if (mouse_pos.y > screen_size.y - 2 && camera.y > level_marginy
* (-1)) {camera.y -= 10 * time;}
if (mouse_pos.y < 1 && camera.y < level_marginy) {camera.y
+= 10 * time;}
wait (1);
}
}
Sie können die Level Ausdehnung mit level_marginx und level_marginy setzen und die Scroll-Geschwindigkeit anpassen, indem sie den Wert 10 ersetzen.
Stratego
1 und - 2 kann sicher dazu benutzt werden, um ein gutes Strategie-Spiel
zu entwickeln, das einzige was noch nicht richtig behandelt wurde ist z.B.
KI (Pathfinding) and Nahkampf. Starten Sie ihr Strategiespiel noch heute
- diese zusätzlichen Funktionen werde ich bestimmt noch in den nächsten
Monaten hinzufügen, keine Sorge! :)
Geschütztürme
Diese bösartigen Türme können Sie innerhalb von ein oder zwei Sekunden töten, da sie darauf trainiert worden sind, seit sie 5 Jahre alt sind. Sehen Sie sich die ihnen zugewiesene Aktion an:
action
turret
{
if (my.skill1 == 0) {my.skill1 = 1000;} // set the range with skill1, default
= 1000 quants
if (my.skill2 == 0) {my.skill2 = 180;} // default viewing angle in front
of the turret = 180
my.enable_impact = on;
my.event = destroy_turret;
while (player == null) {wait (1);}
while (my != null && player != null)
{
my.skill10 = abs(ang(player.pan) - ang(my.pan));
if (vec_dist (my.x, player.x) < my.skill1)
{
if ((my.skill10 > 180 - my.skill2 / 2) && (my.skill10 < 180
+ my.skill2 / 2))
{
vec_set (temp.x, player.x);
vec_sub (temp.x, my.x);
vec_to_angle (my.pan, temp);
ent_create (bullet_mdl, my.pos, move_bullet);
}
}
waitt (8); // fires 2 bullets a second
}
}
Die Türme haben eine einstellbare Reichweite und einen Sichtradius; Wenn sie vergessen skill1 und skill2 im Wed einzutragen, werden die Türme ihre Standardwerte verwenden. Wenn die Einheiten von einer Kugel getroffen werden, werden sie zerstört. Wir werden später darauf zurückkommen. Solange die Türme und der Spieler „leben”, berechnet der Turm den Abstand zum Player, dreht sich zu ihm und feuert 2 Kugeln pro Sekunde ab.
my.skill10
= abs(ang(player.pan) - ang(my.pan));
................................
if
((my.skill10 > 180 - my.skill2 / 2) && (my.skill10 < 180 + my.skill2
/ 2))
Diese zwei Zeilen stellen sicher, dass der Turm und der Spieler sich „sehen” können. Ich hätte ebensogut eine scan_entity Anweisung verwenden können. Die Türme können den Spiele „sehen”, wenn der Winkel zwischen dem Spieler und dem Turm im Bereich von 180 - skill2 / 2 .... 180 + skill2 / 2 liegt. Wenn wir die Standardwerte für skill2 verwenden, beträgt der Winkel, in dem der der Turm den Spieler bemerken wird, zwischen 90 und 270 Grad.
function
destroy_turret()
{
wait (1);
if (you == player) {return;} // can't be destroyed by running into it
snd_play (explode_snd, 70, 0);
ent_remove (me);
}
Wenn der Turm mit etwas zusammenstößt, wird er zerstört. Wir wollen dem Spieler nicht erlauben, den turm zu zerstören indem er einfach gegen ihn rennt, also passiert bei (you == player) nichts. Die Funktion, die die Kugel des Turmes bewegt, ist typisch und ich habe ihre Funktionsweise einige Male in voherigen Aums besprochen:
function
move_bullet()
{
wait (1);
my.enable_entity = on;
my.enable_block = on;
my.event = remove_bullet;
my.passable = on;
my.pan = you.pan;
bullet_speed.x = 100;
bullet_speed.y = 0;
bullet_speed.z = 0;
bullet_speed *= time;
while (my != null)
{
if (you == null) {return;}
if (vec_dist (my.x, you.x) < 100) // don't collide with the turret
{
my.passable = on;
}
else
{
my.passable = off;
}
ent_move (bullet_speed, nullvector);
wait (1);
}
}
Hier ist die fertige Funktion:
function
remove_bullet()
{
wait (1);
if (you == player) {player._health -= 10;}
ent_remove (me);
}
Sobald
die Kugel den Spieler trifft, werden dem Spieler 10 Lebenspunkte abgezogen
und sie verschwindet. Wenn die Kugel eine Wand trifft, wird sie verschwinden
ohne irgendwelchen Schaden anzurichten.