Dieses Mal habe ich eine kleine Map erstellt, wo der Spieler mit einem Skelett fechten kann. Obwohl der Code sicher noch verbessert werden kann, enthält er doch wertvolle Informationen, wie man Nahkampfwaffen (Schwerter, Messer, Fäuste, etc.) implementiert. Ich mag keine komplizierten main Funktionen:
function
main()
{
level_load (sword_combat_wmb);
wait (2);
clip_size = 0;
on_d = null;
fps_max = 40;
}
Wir werden den Spieler mit den WASD Tasten bewegen, also mußte ich das Debug Panel deaktivieren, das sonst durch Druck auf die D Taste erscheint. Die Player action lautet:
action
player_fight
{
player = me;
player.healthpoints = 100;
while (player.healthpoints > 0)
{
camera.x = player.x - 200 * cos(player.pan);
camera.y = player.y - 200 * sin(player.pan);
camera.z = player.z + 200;
camera.pan = player.pan;
camera.tilt = -30;
Diese Zeilen bedeuten: ich bin der Spieler, ich habe 100 Health Punkte (healthpoints ist der Name für skill18 in dem Beispiel). Solange diese Punktzahl größer ist als 0, läuft die Schleife. Die folgenden Zeilen plazieren die Kamera 200 Quants hinter dem Spieler.
my.pan += 4 * (key_a - key_d) * time - 20 * mouse_force.x * time;
player_distance.x = 10 * (key_w - key_s) * time;
player_distance.y = 0;
player_distance.z = 0;
Der Spieler kann sich durch die A und D Tasten oder mit Hilfe der Maus drehen. Er kann sich vorwärts und rückwärts bewegen, wenn er W bzw. S drückt. player_distance ist eine Variable, die in der ent_move Anweisung Verwendung findet.
if ((key_w == 1) || (key_s == 1))
{
ent_cycle("walk", my.skill20);
my.skill20 += 4 * time;
if (my.skill20 > 100) {my.skill20 = 0;}
}
else
{
ent_cycle("stand", my.skill21);
my.skill21 += 2 * time;
if (my.skill21 > 100) {my.skill21 = 0;}
}
ent_move(player_distance, nullvector);
Wenn der Spieler läuft (W oder S sind gedrückt), dann spielen wir die "walk" Animation. Falls W und S nicht gedrückt sind, dann steht der Spieler, also spielen wir seine "stand" Animation. Schließlich wird player_distance genutzt, um die Bewegung auszuführen.
if (mouse_left == 1)
{
while (my.skill22 < 100)
{
ent_vertex(my.sword_tip, 315); // get the value in Med
ent_vertex(my.sword_base, 293); // get the value in Med
trace_mode = ignore_me + ignore_passable;
trace (my.sword_base, my.sword_tip);
if (result != 0)
{
effect (particle_sparks, 10, target, normal);
if (you != null) {you.healthpoints -= 6 * time;}
ent_playsound (my, sword_snd, 50);
}
ent_cycle("attack", my.skill22);
my.skill22 += 8 * time;
wait (1);
}
while (mouse_left == 1) {wait (1);}
}
wait (1);
}
Falls die linke Maustaste gedrückt wird, gelangen wir in die Angriffs-Schleife. Diese Schleife läuft, bis der Spieler sein letztes "Attack" Frame erreicht hat (dies wird in skill22 gespeichert). Wir speichern auch Schwertgriff und Schwertspitze (andere Namen für skill12 und skill15) und führen einen trace zwischen den Positionen aus. Falls dies einen Wert != 0 liefert (der Spieler hat etwas getroffen), dann generieren wir einige Partikel an dieser Stelle, indem wir den vordefinierten "target" Vektor nutzen. Falls der Spieler eine Entity (also keine Wand oder ähnliches) getroffen hat, dann wird Health von dieser abgezogen und ein Sound wird abgespielt. Der Spieler läuft durch seine Attack Animation und nach Loslassen der Maustaste kehrt alles wieder in den normalen Zustand zurück.
while (my.skill23 < 90)
{
ent_cycle("death", my.skill23);
my.skill23 += 3 * time;
wait (1);
}
my.passable = on;
}
An diesem Punkt im Code un ist der Spieler tot (Health 0), spielt seine "death" Animation und wird passable. Dadurch kann das Schwert des Gegners ihn nicht mehr treffen, denn das trace ist auf ignore_passable gesetzt.
Die Action für den Gegner ist ähnlich, ich habe den Pointer "enemy" verwendet, aber falls Sie mehrere Gegner in Ihrem Level haben möchten, ersetzen Sie "enemy" einfach durch "my".
action
enemy_fight
{
enemy = me;
enemy.healthpoints = 100;
while (my.healthpoints > 0)
{
if (vec_dist (my.x, player.x) < 200 && player.healthpoints >
0)
{
vec_set(temp, player.x);
vec_sub(temp, my.x);
vec_to_angle(my.pan, temp);
my.tilt = 0;
enemy_distance.x = 5 * time;
enemy_distance.y = 0;
enemy_distance.z = 0;
ent_move(enemy_distance, nullvector);
ent_cycle("walk", my.skill19);
my.skill19 += 5 * time;
if (my.skill19 > 100) {my.skill19 = 0;}
Auch der Gegner hat 100 Healthpunkte. Falls der Spieler lebt und näher als 200 Quants ist, dann dreht sich der Gegner in seine Richtung und beginnt, ihn zu verfolgen. Dabei wird seine "walk" Animation abgespielt.
if (vec_dist (my.x, player.x) < 50)
{
while (my.skill20 < 100)
{
ent_vertex(my.sword_tip, 291); // get the value in Med
ent_vertex(my.sword_base, 306); // get the value in Med
trace_mode = ignore_me + ignore_passable;
trace (my.sword_base, my.sword_tip);
if (result != 0)
{
effect (particle_blood, 2, target, normal);
if (you != null) {you.healthpoints -= 4 * time;}
ent_playsound (my, sword_snd, 50);
}
ent_cycle("attack", my.skill20);
my.skill20 += 5 * time;
wait (1);
}
waitt (6); // slows down the enemy and reduces the number of traces per
second
}
}
Wenn der Spieler näher als 50 Quants ist, dann greift der Gegner an (ähnlicher Code, andere Partikelfunktion). Der Gegner verursacht weniger Schaden - der Spieler wird trotzdem einen schwere Stand haben. :)
else // the player is farther than 200 quants away
{
ent_cycle("stand", my.skill21); // play stand frames animation
my.skill21 += 2 * time; // "stand" animation speed
if (my.skill21 > 100) {my.skill21 = 0;} // loop animation
}
wait (1);
}
while (my.skill22 < 80) // the enemy is dead
{
ent_cycle("death", my.skill22); // play death frames animation
my.skill22 += 1 * time; // "death" animation speed
wait (1);
}
my.passable = on; // the corpse can't be hit by the sword from now on
}
Wenn der Spieler weiter als 200 Quants entfernt ist, geht der Gegner zurück in seine "stand" Animation. Im Tod teilt der Gegner den Code mit dem Spieler; die Funktionen, die Partikel erzeugen (Blut des Spielers und Blut des Skeletts) sind sich sehr ähnlich:
function
particle_sparks()
{
temp.x = random(2) - 1;
temp.y = random(2) - 1;
temp.z = random(1) - 1.5;
vec_add (my.vel_x, temp);
my.alpha = 30 + random(50);
my.bmap = spark_map;
my.size = 10;
my.flare = on;
my.bright = on;
my.move = on;
my.lifespan = 20;
my.function = fade_particle;
}
function
particle_blood()
{
temp.x = random(2) - 1;
temp.y = random(2) - 1;
temp.z = random(1) - 1.5;
vec_add (my.vel_x, temp);
my.alpha = 70 + random(30);
my.bmap = blood_map;
my.size = 6;
my.flare = on;
my.bright = on;
my.move = on;
my.lifespan = 20;
my.function = fade_particle;
}
function
fade_particle()
{
my.alpha -= 5 * time;
if (my.alpha < 0) {my.lifespan = 0;}
}
Die Partikel werden nahe dem Trefferpunkt erzeugt und fallen nach unten (temp.z < 0). Werfen wir nun einen Blick auf das Panel und den Text:
string health_str = "Player Health: Enemy Health:";
panel
health_panel
{
pos_x = 0;
pos_y = 0;
digits = 120, 575, 4, swc_font, 1, player.healthpoints;
digits = 550, 575, 4, swc_font, 1, enemy.healthpoints;
flags = refresh, visible;
}
text
health_text // displays the text
{
pos_x = 0;
pos_y = 550;
font = swc_font;
string = health_str;
flags = visible;
}
Wie man sieht verwende ich ein einzelnes Panel mit 2 Ziffern, um die Werte darzustellen und einen Text mit einem längeren String, um Texte vom Spieler und vom Gegner darzustellen. Und das ist auch der Grund, weshalb ich mit einem Pointer auf den Gegner arbeite: ich möchte auch seinen Health Wert darstellen können.
Was
geschieht nun wenn das Skelett das Schwert des Spielers trifft? Das
Schwert und der Spieler sind in meinem Beispiel ein einziges Model, also
wird trotzdem Gesundheit vom Spieler abgezogen. Dies läßt sich
vermeiden, indem der Spieler ein seperates Schwert Model erhält, welches
an seine Hand attached wird, oder indem man den Trefferpunkt ermittelt
und mit den Schwertkoordinaten vergleicht, die man mit ent_vertex erhält.
Wald
Dieser Ausschnitt wird einen Wald in einem quadratischen Areal erstellen. Dies sollte Ihnen einiges an Arbeit ersparen und noch besser, Sie werden ein anderes Waldstück haben, wann immer Sie das Level starten.
define num_trees = 100;
var
max_x = 1000;
var
min_x = -1000;
var
max_y = 1000;
var
min_y = -1000;
Der Wald besteht aus 100 Bäumen, in meinem Beispiel werden diese in einem rechteckigen Areal erzeugt (-1000 ... 1000 Quants in x-Richtung und -1000 ... 1000 Quants in y-Richtung, aber Sie können andere Werte verwenden).
Der Wald wird erstellt, indem man diese Funktion von der main aufruft:
function
generate_forest()
{
randomize(); // always generate a different forest
while (tree_index < num_trees)
{
tree_pos.x = sign(min_x) * random(abs(min_x)) + sign(max_x) * random(abs(max_x));
tree_pos.y = sign(min_y) * random(abs(min_y)) + sign(max_y) * random(abs(max_y));
tree_pos.z = 1000; // 3000 quants above the floor level
tree_index += 1;
if (random(1) > 0.5)
{
ent_create (tree1_mdl, tree_pos, create_tree);
}
else
{
ent_create (tree2_mdl, tree_pos, create_tree);
}
ifdef fun;
waitt (8);
endif;
}
}
Als erstes rufen wir die vordefinierte Funktion randomize() auf. Dies stellt sicher, dass wir einen anderen Wald erhalten, wann immer wir das Level starten. Zufallszahlen sind nicht wirklich "zufällig"; normalerweise werden sie bei jedem Start durch eine komplizierte Funktion erzeugt, aber jedes Mal erhält man dieselbe "zufällige" Nummer beim ersten Start. Randomize sorgt nun dafür, dass random nicht immer die erste Zahl liefert, sondern wirklich eine Zufällige.
Solange noch nicht alle Bäume erzeugt wurden (tree_index < num_trees), erzeugen wir eine zufällige Baumposition innerhalb der rechteckigen Zone, 1000 Quants über dem Boden (falls der Boden den z Wert 0 hat). Dann addieren wir 1 auf den tree_index, d.h. wir haben einen neuen Baum erzeugt und je nach Wert der Zufallszahl verwenden wir einen der zwei Bäume, die für diesen Wald benutzt werden.
Falls wir nun die Map folgendermaßen aufrufen: "office.wdl - d fun", dann plazieren wir zwei Bäume pro Sekunde, so dass wir bequem zusehen können, wie sie erstellt und plaziert werden. Falls die Map ohne "-d fun" gestartet wird, dann werden die Bäume mit großer Geschwindigkeit erzeugt - eigentlich das, was wir wollen.
Die Funktion, die für jeden Baum abläuft ist Folgende:
function
create_tree()
{
wait (1);
my.pan = random(360);
vec_set (temp, my.pos);
temp.z -= 3000;
trace_mode = ignore_me + ignore_sprites + ignore_models + use_box;
my.z -= trace (my.pos, temp) + 20;
}
Wie Sie sehen, hat jeder Baum einen zufälligen Winkel (pan). Wir tracen 3000 Quants nach unten und verringern die z-Koordinate, bis der Baum auf dem Boden "Wurzeln schlägt". Dabei plazieren wir ihn 20 Quants tiefer, damit er selbst auf einem schrägen Boden noch in der Erde steckt.
Stellen
Sie sicher, dass Sie Low Poly Models oder (noch besser) Sprites für
die Bäume verwenden. Falls jeder Baum aus 500 Polygongen besteht und
Sie einen Wald aus 500 Bäumen erstellen, werden Sie keine 3D Karte
finden, die 250000 Polygone flüssig darstellen kann.