Dancing Crab mit P5.js

P5.js
Spieleprogrammierung
Autor:in

Jörg Kantel

Veröffentlichungsdatum

10. Mai 2023

Yeah, I did it! Ich habe mein erstes, eigenes Spiel in P5.js programmiert. Eigentlich habe ich »nur« meine kleine, rote und mit Luftblasen tanzende Krabbe von Python (Trinket) nach JavaScript (P5.js) portiert. Es war einerseits einfacher, als ich dachte, andererseits gab es doch einige Probleme, an denen ich zu knabbern hatte, die aber größtenteils meiner fehlenden JavaScript-Erfahrung zuzuschreiben waren.

Die größten Probleme verursachten jedoch nicht die ungewohnte Syntax (an for (let item of items) statt for item in items oder this. statt self. hatte ich mich schnell gewöhnt) oder die geschweiften Klammerns, sondern die Semikolon (;) am Ende jeder Zeile. Wie oft ich mich verfluchte, weil ich diese mal wieder vergessen hatte, kann ich gar nicht zählen.

Die wichtigste Änderung gegenüber der Trinket-Python-Version ist, daß die Krabbe nun nicht mehr mit der rechten oder linken Maustaste, sondern mit den rechten und linken Pfeiltasten gesteuert wird. Damit diese nicht auch noch an den Browser durchgereicht werden, habe ich sie am Ende der Funktionen keyPressed() und keyReleased() jeweils mit einem return false abgefangen und neutralisiert.

Und die Krabbe zappelt nun nicht mehr ununterbrochen, sondern tänzelt nur noch, wenn sie sich bewegt.

Außerdem kann in der derzeitigen Version die Krabbe noch nicht sterben und das Spiel beendet werden, da dann ein Reload der gesamten Seite erforderlich wäre, um ein neues Spiel zu starten. (In einer – geplanten – späteren Version werde ich verschiedene Spiel-Statii mit einem Restart-Screen einbauen, aber jetzt ist das erst einmal nur ein proof of concept.) Zwar wird eine Kollision der Krabbe mit einer roten Blase mit einem »GAME OVER« kommentiert (nur bei eingeschalteter JavaScript-Konsole sichtbar), doch ansonsten geht das Spiel ungerührt weiter.

Ansonsten sind sich JavaScript in der ES6-Variante und Python sehr ähnlich. Wer sich überzeugen will, kann ja den Quellcode meiner P5.js-Version mit der Original-Python-Version vergleichen:

// Dancing Crab
// Jörg Kantel 2023
const windowWidth = 640;
const windowHeight = 416;
const fps = 60;
const numBubbles = 50;
const numEnemies = 5;

let bg;
let crab;
let crabImages = [];
let bubbles = [];
let bubbleImages = [];
let enemyBubbles = [];
let enemyImage;

class Crab {

  constructor() {
    this.w = 64;
    this.h = 64;
    this.r = 32;
    this.x = width/2 - this.w/2;
    this.y = height/2 + 100;
    this.img = crabImages[0];
    this.dir = "none";
    this.speed = 5;
    this.animationCount = 0;
    this.score = 0;
  }

  update() {
    // Bewegung
    if (this.dir == "none") {
      this.x += 0;
    } else if (this.dir == "right") {
      if (this.x <= width - this.w - 5) {
        this.x += this.speed;
      }
    } else if (this.dir == "left") {
      if (this.x >= 2) {
        this.x -= this.speed;
      }
    }
    // Animation
    this.animationCount += 1;
    if (this.dir == "none") {
      this.img = crabImages[0];
    } else {
    if (this.animationCount >= 10) {
      this.animationCount = 0;
    }
    if (this.animationCount <= 5) {
      this.img = crabImages[0];
    } else {
      this.img = crabImages[1];
    }
  }
  }

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

class Bubble {

  constructor() {
    this.reset();
    this.speed = 2;
  }

  reset() {
    let dia = int(random(0, 2))
    this.img = bubbleImages[dia];
    this.r = this.img.width/2;
    this.x = int(random(width));
    this.y = int(random(-2*height, -50));
  }

  isCircleCollision(other) {
    let distance = dist(this.x, this.y, other.x, other.y);
    if (distance < this.r + other.r) {
      return true;
    }
    return false;
  }

  update() {
    this.y += this.speed;
    if (this.y > height + 50) {
      this.reset();
    }
  }

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

class EnemyBubble extends Bubble {

  constructor() {
    super();
    this.reset();
    this.r = 15;
    this.speed = 3;
    this.img = enemyImage;
  }

    reset() {
      this.x = int(random(width));
      this.y = int(random(-2*height, -50));
    }
  }

function preload() {
  bg = loadImage("images/background.png");
  crabImages[0] = loadImage("images/crab1.png");
  crabImages[1] = loadImage("images/crab2.png");
  for (let i = 0; i < 3; i++) {
    bubbleImages[i] = loadImage("images/bubbleblue" + str(i) + ".png"); 
  }
  enemyImage = loadImage("images/bubblere1.png");
}

function setup() {
  myCanvas = createCanvas(windowWidth, windowHeight);
  myCanvas.parent("crab01");
  frameRate(fps);
  for (let i = 0; i < numBubbles; i++) {
    bubbles[i] = new Bubble;
  }
  for (let j = 0; j < numEnemies; j++) {
    enemyBubbles[j] = new EnemyBubble;
  }
  crab = new Crab();
}

function draw() {
  background(49, 197, 244);    // Hellblau
  image(bg, 0, 0);
  for (let bubble of bubbles) {
    bubble.update();
    if (bubble.isCircleCollision(crab)) {
      bubble.reset();
      crab.score += 1;
      // console.log(crab.score);
    }
    bubble.display();
  }
  for (enemyBubble of enemyBubbles) {
    enemyBubble.update();
    if (enemyBubble.isCircleCollision(crab)) {
      console.log("GAME OVER");
      // crab.x = 2000;
      // crab.y = 2000;
    }
    enemyBubble.display();
  }
  crab.update();
  crab.display();
}

function keyPressed() {
  if (keyCode === LEFT_ARROW) {
    crab.dir = "left";
  } else if (keyCode === RIGHT_ARROW) {
    crab.dir = "right";
  }
  return false;
}

function keyReleased() {
  crab.dir = "none";
  return false;
}

Wie gewohnt gibt es den Quellcode mit allen Assets in meinem GitHub-Repositorium. Dazu hier auch noch die Credits: Die Impementierung wurde inspiriert von Heiko Fehrs »Let’s Code Python«, Bonn (Rheinwerk-Verlag) 2019, Seiten 247ff. Die Bilder der Krabbe sind von Nitin Chowdary, der sie unter der CC0 auf OpenGameArt.org veröffentlicht hatte. Ebenfalls von OpenGameArt.org sind die Luftblasen (CC-BY 3.0) des Users mit dem schönen Screen-Namen HorrorPen. Und der Bildhintergrund stammt wie so oft aus dem schier unerschöpflichen, freien (CC0) Fundus von Kenny.nl.

Das Spielen mit P5.js hat mir sehr viel Spaß bereitet. Es wird daher vermutlich in Zukunft mehr davon geben. Macht Euch auf einiges gefaßt, ich habe Blut geleckt. Still digging!