Pong

Alles begann mit Pong! Ok, es gab einige Text Adventures davor, aber dies ist das erste (wie ich es nenne) “richtige” Spiel. Ich wollte es nachprogrammieren, weil ich nostalgisch bin (schnüff), aber auch um Ihnen zu demonstrieren, wie man 2D Grafik und Kollisionserkennung in 2D mit dem 3DGS realisiert.

Starten wir mit einigen Panel Definitionen und einem Bild, welches die Panels in Aktion zeigt:

panel main_pan // the main panel
{
bmap = main_pcx;
layer = 10;
pos_x = 0;
pos_y = 0;
digits = 330, 40, 2, digital_font, 1, player_score;
digits = 395, 40, 2, digital_font, 1, computer_score;
flags = refresh, d3d, visible;
}

panel player_pan
{
bmap = player_pcx;
layer = 11;
pos_x = 60;
pos_y = 250;
flags = overlay, refresh, d3d, visible;
}

panel computer_pan
{
bmap = computer_pcx;
layer = 11;
pos_x = 715;
pos_y = 250;
flags = overlay, refresh, d3d, visible;
}

panel ball_pan
{
bmap = ball_pcx;
layer = 11;
pos_x = 504;
pos_y = 344;
flags = overlay, refresh, d3d, visible;
}

Dieses Mal habe ich eine lange Main Funktion; die gute Nachricht ist, dass dies die einzige Funktion des ganzen Spiels ist und wir werden sie Zeile für Zeile durchgehen.

function main()
{
fps_max = 80; // limit the frame rate to keep the game playable on faster computers
level_load (dummy_wmb);
wait (2);
speed_x = 3 - 6 * (sys_seconds % 2);
speed_y = 3 - random(6);

Wir limitieren die Framerate auf 80, damit das Spiel auch auf einem 5GHz noch genausoschnell läuft. Wir laden ein Dummy Level, warten darauf, dass dieser geladen ist und erzeugen dann 2 Zufallswerte:
1) Die Richtung des Balls am Anfang (zum Spieler oder zum Computer) = -3 oder 3, je nach sys_seconds (gerade oder ungerade)
2) Die senkrechte Geschwindigkeit des Balls = -3...3.

while ((computer_score != 15) && (player_score != 15))
{
if (key_cuu == 1) {player_pan.pos_y -= 8;}
if (key_cud == 1) {player_pan.pos_y += 8;}
player_pan.pos_y = min(470, player_pan.pos_y);
player_pan.pos_y = max(35, player_pan.pos_y);

Das Spiel läuft, solange die Punktzahl des Spielers oder Computers unter 15 liegt. Wenn die Pfeil Oben Taste gedrückt wird (cuu), wird player_pan aufwärts bewegt, denn die y Koordinate wird kleiner und kleiner (bis 35 erreicht wird). Die Pfeil unten Taste bewegt das Panel nach unten (der y Wert kann bis 470 ansteigen).

ball_pan.pos_x += speed_x;
ball_pan.pos_y += speed_y;

Der Ball ändert seine Position je nach speed_x und speed_y. Hier ist ein Bild der Bewegung:

Wie sie Wissen ändert der Ball gleichzeitig seine x und y Position, so sieht also die Bewegung aus:

if (ball_pan.pos_y > 555)
{
speed_y = -3 - random(3);
snd_play (beep1_wav, 50, 0);
}
if (ball_pan.pos_y < 32)
{
speed_y = 3 + random(3);
snd_play (beep1_wav, 50, 0);
}

Falls die y Koordinate unter 32 oder über 555 geht, prallt der Ball ab, indem seine Geschwindigkeit geändert wird und beep1_wav ertönt.

if (ball_pan.pos_x > 740) // player scores!
{
snd_play (goal_wav, 70, 0);
speed_x = -3 - random(3);
player_score += 1;
ball_pan.pos_x = 740;
}

Falls ball_pan.pos_x > 740, hat der Spieler einen Punkt erzielt! Wir spielen den goal_wav Sound ab, ändern die Geschwindigkeit des Balles, um ihn wieder Richtung Spieler zu schieben, erhöhen die Punktzahl des Spielers und bewegen den Ball auf 740 Pixel auf der x-Achse. Warum dies?

Nun, der Ball könnte 741, 742, 743, ... passieren, ehe er wieder zurückgepraööt ist und dies würde mehr als 1 Tor mit einem Schuß registrieren. Die Lösung ist es, den Ball von Hand aus der Torzone zu bewegen und die Geschwindigkeit vom Vorzeichen her zu ändern. Auf diese Weise wird nur ein Tor registriert.

if (ball_pan.pos_x < 40) // computer scores!
{
snd_play (goal_wav, 70, 0);
speed_x = 3 + random(3);
computer_score += 1;
ball_pan.pos_x = 40;
}

Wenn der Computer ein Tor schießt, geschieht dasselbe.

if ((ball_pan.pos_y > player_pan.pos_y - 12) && (ball_pan.pos_y < player_pan.pos_y + 96) && (ball_pan.pos_x > 60) && (ball_pan.pos_x < 72))
{
// the player has blocked the ball
snd_play (beep2_wav, 70, 0);
speed_x = 3 + random(3);
speed_y = 3 - random(3);
}

Wenn der Spieler den Ball abgewehrt hat, spielen wir den beep2_wav Sound und lassen den Ball abprallen?

Wer möchte einen perfekten Computergegner? Ich weiß, dass ich keinen will.

if ((ball_pan.pos_y > 100) && (ball_pan.pos_y < 490))
{
computer_pan.pos_y = ball_pan.pos_y - 42;
}

Wir möchten, dass der Computer auch Fehler macht, daher begrenzen wir seine Bewegung auf der y-Achse auf 100 bis 490 Pixel. Auf diese Weise haben Sie eine faire Chance ihn zu besiegen. Beachten Sie, dass der Computer einen Pffset von 42 Pixeln hat, wenn er dem Ball folgt, auf diese Weise trifft er ihn nach Möglichkeit mit der Mitte seiner Spielfigur (falls er ihn trifft) :)

if ((ball_pan.pos_y > computer_pan.pos_y - 12) && (ball_pan.pos_y < computer_pan.pos_y + 96) && (ball_pan.pos_x > 703) && (ball_pan.pos_x < 715))
{
// the computer has blocked the ball
snd_play (beep2_wav, 70, 0);
speed_x = -3 - random(3);
speed_y = 3 - random(3);
}

Hat der Computer geblockt, prallt der Ball zurück und ein Geräusch ertönt.

Gut, dies ist nicht die Anfängerecke, aber wie funktioniert das Kollisionssystem? Woher weiß man, dass der Ball zurückgeworfen wurde?
Ich werde erklären, wie es für dne Computer funktioniert, dieselbe Methode findet beim Spieler Anwendung. Die traurige Wahrheit ist, dass es keine “echte” Kollisionserkennung in 2D gibt, es muß daher vorgetäuscht werden. Schauen wir uns ein Beispiel an:

Könnten Sie sagen, dass der Ball treffen wird? Sieht so aus, oder? Erstens ist die Höhe des Balls gleich der Höhe des Computers. Ich bin sogar sicher, dass der Ball treffen würde, wenn er in irgendeiner der folgenden Positionen ist:

Ok, wir stellen also sicher, dass der untere Teil des Balls unterhalb vom oberen Teil des Spielers ist (Ball #1). Vergessen Sie nicht, dass jedes Panel in der oberen linken Ecke den Ursprung hat, also sieht die erste Anweisung so aus:

if (ball_pan.pos_y > computer_pan.pos_y - 12)

Der Ball ist 12 x 12 Pixel groß. Und wie sähe das Ganze für Ball #7 aus? Falls der obere Teil des Balles oberhalb des unteren Teils vom Spieler ist, nicht wahr?

if (ball_pan.pos_y < computer_pan.pos_y + 96)

Dies ist der korrekte Code, da der Spieler 96 Pixel groß in y-Richtung ist.

Damit ist das Kollisionssystem für die y-Achse erklärt, sehen wir uns die x-Achse an.

Wie können wir sicherstellen, dass der Ball nicht durch den Spieler hindurchfliegt? Nun, wir müssen sicherstellen, dass die rechte Seite des Balls die linke Seite des Spielers berührt oder anders ausgedrückt:

if (ball_pan.pos_x > 703)

Denn der Computerspieler hat die x-Position von 715 Pixeln und der Ball ist 12 Pixel breit. Dann müssen wir nocht sicherstellen, dass der Ball den Spieler auch trifft und nicht den leeren Raum dahinter:

if (ball_pan.pos_x < 715).

Wenn ich die letzte Anweisung nicht benutzt hätte, dann sähe es aus, als wäre der Computerspieler “dick”, wie im Bild:

Kombinieren Sie die vier Abfragen oben und Sie erhalten das Kollisionssystem des Spiels.

Kraftfelder

Ich mag Kraftfelder! Sie sehen gut aus und sorgen für Adrenalin. Und es ist gar nicht schwer, sie zu erstellen!

Als ersten brauchen wir den Teil, der gut aussieht.

action force_field
{
var number_of_particles = my.skill1;
var particle_distance = my.skill2;
if (my.skill1 == 0) {number_of_particles = 600;}
if (my.skill2 == 0) {particle_distance = 0.2;}
vec_set (temp, my.pos);
while (number_of_particles > 0) // don't use a wait(1) here
{
temp.x += particle_distance * cos(my.pan);
temp.y += particle_distance * sin(my.pan);
effect(particle_barrier, 1, temp, normal);
number_of_particles -= 1;
}
}

Wir definieren zwei lokale Variablen: eine wird die Anzahl der Partikel speichern, die für das Kraftfeld benutzt werden (also die Länge) und der andere den Abstand zwischen zwei benachbarten Partikeln. Wenn Sie vergessen, diese Werte einzusetzen, werden skill1 und skill2 nach Definition auf 600 und 0.2 gesetzt. Wir speichern die Position der Kraftfeldentity in temp und beginnen in einer While Schleife (ohne wait) die Partikel zu generieren, bis alle da sind.

Sehen wir uns die Partikelfunktion an:

function particle_barrier()
{
my.bmap = redflare_pcx;
my.flare = on;
my.bright = on;
my.size = 10;
my.function = keep_particles;
}
function keep_particles()
{
my.lifespan = 10;
my.alpha = 30 + random(70);
}

Die einzige wichtige Sache hier ist die Tatsache, dass lifespan immer auf 10 gesetzt wird, damit die Partikel für immer “am Leben” bleiben. Außerdem wählen wir zufällige Alpha-Werte, um Laseaktivität zu zeigen. So weit, so gut, aber wie töten wir den Spieler?

Die einfachste Methode ist eine unsichtbare Entity an der Position des Kraftfeldes. Hier ist der Code:

action killer
{
my.invisible = on;
my.enable_impact = on;
my.enable_entity = on;
my.event = kill_them;
}

function kill_them()
{
wait (1);
if (you != null)
{
you._health = -10;
}
}

Die Entity ist einfach eine Map Entity, die auf Berührung mit anderen Entities reagiert. Wenn dies geschieht (you != null), wird der Health Wert der anderen Entity auf –10 gesetzt; sofortiger Tod.

Wie plaziert man ein solches Kraftfeld nun?
1) Stellen Sie eine kleine Entity in Ihr Level und geben Sie ihr die force_field Action für den Effekt. Dies wird der Partikelgenerator, stellen Sie also eine zweite, identisch aussehende Entity an das Ende des Partikelstrahls (ohne eine Action), damit es realistisch aussieht.
2) Erstellen Sie dann eine WMB Entity, die von den Ausmaßen her dem Kraftfeld entspricht, plazieren Sie sie in Ihrem Level und geben Sie ihr die Killer Action. Diese Entity sorgt für den Tod bei Berührung. Das

Beispiel Level kommt mit 3 Partikelgeneratoren (drei Kraftfelder) und einer großen Killer Entity.