Abenteuer P5.js (Teil 5): Octopussy

P5.js
Processing
Creative Coding
Spieleprogrammierung
Autor:in

Jörg Kantel

Veröffentlichungsdatum

29. Mai 2024

Heute möchte ich die ersten Schritte, die ich in den bisherigen Tutorials zu P5.js und dem P5.js-Webeditor veröffentlicht hatte, mit einer kleinen Fingerübung abrunden, die das bisher behandelte zusammenfasst.

Es geht um Octopussy, einer Freundin von Hörnchen, die mit ihrem Raumfisch von einer wichtigen Mission den Rückflug zu ihrem Heimatplaneten angetreten hat. Leider ist ihr Raumfisch ein wenig reparaturbedürftig und nur noch schwer zu steuern. Octopussy möchte aber auf keinen Fall die netten Käferpiloten, die ihr immer wieder entgegenkommen, rammen.

Deshalb muß ihr die Spielerin oder der Spieler helfen. Mit einem Mausklick wird Octopussys Raumfisch ein wenig angeliftet, während die Gravitationskraft es immer wieder nach unten sinken läßt1.

Der »endlos scrollende« Bildhintergrund ist genau wie beim ballonfliegenden Dachs in einer eigenen Klasse implementiert:

class Background {
  constructor(_x, _y) {
    this.x = _x;
    this.y = _y;
    this.im = bg;
    this.speed = 0.5;
  }

  update() {
    this.x -= this.speed;
    if (this.x <= -bgWidth) {
      this.x = bgWidth;
    }
  }

  display() {
    image(this.im, this.x, this.y);
  }
}

Der einzige Unterschied ist, daß dieses Mal zweimal das gleiche Bild aneinandergeklebt wird:

  backs[0] = new Background(0, 0);
  backs[1] = new Background(bgWidth, 0);

Ähnlich einfach ist die Klasse Beetle geraten, denn der Pilot in seinem Marienkäferraumschiff muß ja nur stur geradeaus von rechts nach links über den Bildschirm fliegen:

class Beetle {
  constructor() {
    this.w = 100;
    this.h = 110;
    this.im = beetleIm;
    this.reset();
  }

  reset() {
    this.x = random(width + 100, width + 400);
    this.y = random(150, height - 150);
    this.speed = random(0.5, 2);
  }

  update() {
    this.x -= this.speed;
    if (this.x <= -150) {
      this.reset();
    }
  }

  display() {
    image(this.im, this.x, this.y, this.w, this.h);
  }
}

Kaum mehr Gehirnschmalz verlangt auch die Klasse Octopussy, nur daß sie zusätzlich noch die Eigenschaften gravity und lift besitzt. Deren Werte habe ich durch Experimentieren herausgefunden2.

Neu ist die Methode up(). Sie wird aufgerufen, wenn die Spielerin oder der Spieler mit der Maus ins Fenster klicken, und sie hebt einfach den Raumfisch ein wenig an:

class Octopussy {
  constructor() {
    this.x = 30;
    this.y = 140;
    this.w = 120;
    this.h = 100;
    this.im = octoIm;

    this.gravity = 0.05;
    this.lift = -12;
    this.vel = 0;
  }

  up() {
    this.vel += this.lift;
  }

  update() {
    this.vel += this.gravity;
    this.vel *= 0.9;
    this.y += this.vel;
    // Check border
    if (this.y >= height - this.h) {
      this.y = height - this.h;
      this.vel = 0;
    } else if (this.y <= 0) {
      this.y = 0;
      this.vel = 0;
    }
  }

  display() {
    image(this.im, this.x, this.y, this.w, this.h);
  }
}

Außerdem achtet die update()-Methode noch darauf, daß Octopussy nicht oben oder unten aus dem Fenster verschwindet. Sie wird an den Bildrändern brutal abgestoppt.

Ich hätte ja gerne geschrieben, daß die Hauptdatei sketch.js wieder von erfrischender Kürze ist, und das ist sie eigentlich auch, oder?

const windowWidth = 640;
const windowHeight = 400;
const bgWidth = 2048;
let octopussy;
let octoIm;
let beetle;
let beetleIm;
let bg;
let backs = [];

function preload() {
  octoIm = loadImage("data/octopus.png");
  beetleIm = loadImage("data/beetleship.png");
  bg = loadImage("data/background.png");
}

function setup() {
  createCanvas(windowWidth, windowHeight);
  backs[0] = new Background(0, 0);
  backs[1] = new Background(bgWidth, 0);
  octopussy = new Octopussy();
  beetle = new Beetle();
}

function draw() {
  background(220);
  for (let back of backs) {
    back.update();
    back.display();
    beetle.update();
    octopussy.update();
    beetle.display();
    octopussy.display();
  }
}

function mousePressed() {
  octopussy.up();
}

Es ist eigentlich kein richtiges Spiel, weil es ohne Konsequenzen bleibt, wenn Octopussy den Beetle-Piloten rammt. Ich habe bewußt keine Kollisionserkennung implementiert, weil ich erst erkunden wollte, welche Methoden mir dafür die Bibliothek P5.play bereitstellt. Doch wer will, kann sich ja mal selber daran versuchen. Bei der Form der beiden Sprites bietet sich die Kollisionserkennung zwischen zwei Kreisen an, wie ich sie hier auch für P5.js schon einmal vorgestellt hatte. Bei einem Radius von etwa 40 Pixeln je Sprite dürfte eine einigermaßen realistisch wirkende Kollisionserkennung zustandekommen.

Wie immer gibt es zum Abschluß eine Übersicht aller bisher in dieser Reihe erschienenen Tutorials, damit Ihr nicht die Datenkrake bemühen müßt:

  1. Bouncing Faces: Drei Wege, mit P5.js zu spielen (GitHub)
  2. Luftballons im Wunderland: Erstes Abenteuer mit P5.js (GitHub)
  3. Der fliegende Dachs im Wunderland: Das Abenteuer P5.js geht weiter (GitHub)
  4. Abenteuer P5.js : Hallo Hörnchen! (GitHub)
  5. Abenteuer P5.js (Teil 5): Octopussy (GitHub)

Und last but not least die Credits: Das Hintergrundbild ist wieder von RubberDuck, der auch dieses auf OpenGameArt hochgeladen und in die Public Domain (CC0) entlassen hat. Die beiden Akteure wiederum entstammen der Sammlung SpaceCute von Daniel Cook (Lost Garden). Seine Lizenz (CC BY 3.0) verlangt ausdrücklich die Namensnennung des Urhebers. Dem bin ich hiermit gerne nachgekommen.

Fußnoten

  1. Wer jetzt fragt »Gravitation« und »unten« im Weltraum? Dem sei gesagt: Das ist mein Spiel und hier bestimme ich die Gesetze der Physik.↩︎

  2. Und das die Schubkraft, die Octopussys Raumfisch anhebt, negativ ist, liegt schlicht und einfach daran, daß die y-Achse auch bei JavaScript von Null (oben) nach height (unten) reicht.↩︎