Retro Platformer in P5.play – Stage 3

Spieleprogrammierung
P5.play
P5.js
Autor:in

Jörg Kantel

Veröffentlichungsdatum

31. Mai 2023

Meine noch sehr tastenden Versuche mit P5.play, der Spiele- und Physik-Engine für P5.js haben mir erste Erfolgserlebnisse beschert. Mit wenigen Zeilen Code ist es mir gelungen, einen kleinen Ninja-Frosch mittels Tastendruck über den Bildschirm hüpfen zu lassen. Denn P5.play bietet dafür eine Reihe sehr leistungsstarker Befehle an, die die Programmierung stark vereinfachen.

Das beginnt mit dem Preload der Datein, die die geneigte Programmiererin oder der geneigte Programmierer nicht einzeln aufrufen und hochladen muß, sondern dies kann (am sinnvollsten in der Funktion preload()) in einem Rutsch erledigt werden. Das sieht dann so aus:

function preload() {
  ninjaIdleAni = loadAni("data/ninjafrog/idle/ninjafrog_idle_00.png", 10);
  ninjaWalkAni = loadAni("data/ninjafrog/walk/ninjafrog_walk_00.png", 11);
}

P5.js kennt nämlich einen Befehl loadAni(), der ähnlich wie loadImage() funktioniert, aber nicht nur eine einzelne Datei, sondern eine ganze Sequenz von Dateien hochlädt und unter einem Variablennamen abspeichert. Die Dateien müssen dafür fortlaufend numeriert sein und der zweite Parameter im Aufruf gibt die letzte Dateinummer der Animation an, nicht die Anzahl der Dateien! Die erste (idle) Animation besteht daher aus 11 Einzelbildern, die von 00 bis 10 durchnumeriert sind.

Hat man die Bildchen erst einmal hochgeladen, kann man in setup() ein Sprite-Objekt anlegen und diesem Objekt dann die gewünschten Animationen zuweisen:

  ninja = new Sprite(width/2, 174);
  walkAni = ninja.addAni("walk", ninjaWalkAni);
  idleAni = ninja.addAni("idle", ninjaIdleAni);

Der erste Parameter ist so etwas wie ein Schlüssel, ein Name, unter dem die Animation in draw() dann aufgerufen wird.

P5.js erledigt viel unter der Haube. So startet das Programm per Default und ohne daß der Programmierer es separat programmieren muß, mit der letzten, mit sprite.addAni() zugewiesenen Animation, in meinem Fall also mit der "idle"-Animation.

Relativ neu dokumentiert ist der sprite.mirror.x- oder sprite.mirror.y-Befehl. Er spiegelt das Sprite jeweils um die x- respektive y-Achse, so daß dafür keine separate Bildsequenz geladen werden muß.

In draw() kommt dann die kb.pressing()-Methode zum Einsatz1. Hier gibt es die Besonderheit, daß die Parameter "up", "down", "left" und "right" sowohl auf die Pfeiltasten als auch auf die Tasten WASD reagieren2.

Die Steuerung des Ninja habe ich mit diesen Zeilen programmiert:

  if (kb.pressing("left")) {
    ninja.mirror.x = true;
    ninja.ani = "walk";
    if (ninja.x > 16) {
      ninja.direction = 180;
      ninja.speed = ninjaSpeed;
    } else {
      ninja.speed = 0;
    }
  } else if (kb.pressing("right")) {
    ninja.mirror.x = false;
    ninja.ani = "walk";
    if (ninja.x < width - 16) {
      ninja.direction = 0;
      ninja.speed = ninjaSpeed;
    } else {
    ninja.speed = 0;
    }
  } else {
    ninja.ani = "idle";
    ninja.speed = 0;
  }

Das war eigentlich schon alles. Probiert es aus: Mit den Pfeiltastem rechts und links oder den Tasten a und d könnt ihr den Ninja-Frosch durch das Bildschirmfenster hüpfen lassen:

Dafür, daß der Sketch gerade einmal aus siebzig Zeilen Code besteht, passiert schon eine ganze Menge. Wie immer für alle unter Euch, die gerne Quellcode lesen und/oder ihn nachvollziehen wollen, hier noch einmal alles kompakt:

// Retro Platformer Stage 2
// Mit P5.play und planck.js
// Assets: Pixel Adventure von Pixel Frog: https://pixelfrog-assets.itch.io/pixel-adventure-1

// Konstanten
const winwidth = 640;
const winheight = 240;
const ninjaSpeed = 1.0;

// Globale Variablen
let ninja;
let ninjaIdleAni;
let ninjaWalkAni;
let idleAni;
let walkAni;


// Funktionen
function displayGround() {
  push();
  noStroke();
  fill(150, 63, 62);  // Rotbraun
  rect(0, height - 50, width, height);
  pop();
}

// Hauptprogramm
function preload() {
  ninjaIdleAni = loadAni("data/ninjafrog/idle/ninjafrog_idle_00.png", 10);
  ninjaWalkAni = loadAni("data/ninjafrog/walk/ninjafrog_walk_00.png", 11);
}

function setup() {
  myCanvas = createCanvas(winwidth, winheight);
  myCanvas.parent("mysketch");
  ninja = new Sprite(width/2, 174);
  walkAni = ninja.addAni("walk", ninjaWalkAni);
  idleAni = ninja.addAni("idle", ninjaIdleAni);
  ninja.mirror.x = false;
  ninja.speed = 0;
  } // end setup()

function draw() {
  background(215, 189, 156);  // Ocker 
  displayGround();

  if (kb.pressing("left")) {
    ninja.mirror.x = true;
    ninja.ani = "walk";
    if (ninja.x > 16) {
      ninja.direction = 180;
      ninja.speed = ninjaSpeed;
    } else {
      ninja.speed = 0;
    }
  } else if (kb.pressing("right")) {
    ninja.mirror.x = false;
    ninja.ani = "walk";
    if (ninja.x < width - 16) {
      ninja.direction = 0;
      ninja.speed = ninjaSpeed;
    } else {
    ninja.speed = 0;
    }
  } else {
    ninja.ani = "idle";
    ninja.speed = 0;
  }
} // end draw()

Die Credits findet Ihr im Kopf des Quelltextes kommentiert und er ist auch wieder mit den verwendeten Assets in meinem GitHub-Repositorium zu finden.

Bisher habe ich nur an der Oberfläche von P5.play gekratzt. Der Spaß kommt jedoch erst, wenn ich auch noch die eingebaute Physik-Engine nutze. Darüber beim nächsten Mal mehr. Still digging!

Fußnoten

  1. Es gibt in dieser Gruppe die Befehle presses(), pressing(), holding() und released(). Der erste Befehl ist nach dem ersten Tastendruck verbraucht, der zweite Befehl reagiert, so lange die Taste gedrückt ist, beim dritten Kommande muß die Taste länger (per Default 12 Frames) gedrückt sein, und der letzte Befehl reagiert auf das Loslassen der Taste. Ähnliches gibt es für Interaktionen mit der Maus oder mit einem Game Controller. Hier empfiehlt sich auf jeden Fall ein Blick in die Dokumentation.↩︎

  2. Zusätzlich kann auch ein zweiter Sprite (über einen zweiten Spieler) mit den Tasten IJKL gesteuert werden. Wie man das bewerkstelligt und wie man internationale Tastaturen dafür anpaßt, verrät ebenfalls die Dokumentation.↩︎