Tutorial: Roter Baron#

In diesem Kapitel erstellen wir Schritt für Schritt einen Side-Scrolling-Shooter.

The techniques for creating parallax backgrounds, managing speed and movement, and generating enemies are widely used in games. After seeing them here, you should be able to use them in your own projects.

  • Basierend auf: https://github.com/kantel/pygamezero/tree/master/tappyplane

  • Lizenz: Namensnennung-Nicht kommerziell-Weitergabe unter gleichen Bedingungen 4.0 International

  • Prerequisite: Knowledge in handling class.

Step 1: Create the basic framework#

Create a basic framework: You need a world where actors can be placed.

Deine letzte Zeile muss world.run() sein.

from miniworlds import World, Actor, timer, Text
world = World(800, 480)

// dein Code hier

world.run()

Ordner vorbereiten#

You need to place images for backgrounds, players, and opponents in the images directory within your code directory.

my_code
|
|--images
|----planered1.png
|----background.png
|----groundgrass.png
|----shipbeige.png
|----shipblue.png
|----shipgreen.png
|----shippink.png
|----shipyellow.png

(Die Bilder findest du in diesem Repository: miniworlds-cookbook - red baron)

Create backgrounds#

Mit dem folgenden Code kannst du zwei Hintergründe generieren, die einen endlos scrollenden Effekt erzeugen.

Create two backgrounds that fill the entire screen side by side:

back0 = Actor()
back0.add_costume("background")
back0.size = world.width, world.height
back1 = Actor(world.width, 0)
back1.size = world.width, world.height
back1.add_costume("background")
backs = [back0, back1]

Jetzt animieren wir die Hintergründe:

  • Both backgrounds move constantly from right to left.

  • When a background leaves the left edge of the screen, it is moved to the right.

@world.register
def act(self):
    for back in backs:
        back.x -= 1
        if back.x <= -world.width:
            back.x = world.width
    for ground in grounds:
        ground.x -= 2
        if ground.x <= -world.width:
            ground.x = world.width

This creates an endlessly scrolling background.

Schritt 2: Flugzeug-Klasse erstellen#

Create airplane class#

Create a Plane class as a template for your player:

class Plane(Actor):
    def on_setup(self):
        self.add_costume("planered1")

Create instance of the airplane class#

Create an instance of this class at the end of your code, before world.run():

plane = Plane(100, world.height / 2)

Physik hinzufügen#

Jetzt fügen wir der Flugzeug-Klasse Physik hinzu. Modifiziere die on_setup()-Methode der Klasse:

    def on_setup(self):
        self.add_costume("planered1")
        self.gravity = 0.1
        self.velocity_y = 0
  • velocity_y describes the current speed of the aircraft in the y-direction.

  • gravity repräsentiert die Schwerkraft, die die Geschwindigkeit des Flugzeugs beeinflusst.

Physik simulieren#

The physics is simulated in the act() method of the class:

    def act(self):
        self.velocity_y += self.gravity
        self.velocity_y *= 0.9  # Reibung
        self.y += self.velocity_y

This adds the speed to the y-coordinates of the aircraft. Gravity continuously decreases the speed, while friction smooths the movement.

Add force on key press#

Use the on_key_down event to apply an upward force to the actor:

    def on_key_down_w(self):
        self.velocity_y -= 5

Step 3: Add opponent#

Importiere randint und choice, um zufällig Gegner zu generieren:

from random import randint, choice

Create Opponent Class#

Add an opponent class as a template:

class Enemy(Actor):
    
    def on_setup(self):
        self.add_costume(choice(enemyships))

    def reset(self):
        self.x = randint(world.width + 50, world.width + 500)
        self.y = randint(25, world.height - 85)

The reset() method randomly sets the opponent’s position within a specific range.

Add opponent to the world#

Create multiple instances of the enemy class with a loop and add them to the world:

enemies = []
for _ in range(10):
    enemy = Enemy()
    enemy.reset()
    enemies.append(enemy)

Move opponent#

Modify the on_setup() method of the enemy class:

def on_setup(self):
    self.add_costume(choice(enemyships))
    self.speed = -1.5

The speed property specifies how many steps the opponent moves in the x-direction in each frame.

Add a act() method to simulate the movement:

def act(self):
    self.x += self.speed
    if self.x <= -self.width:
        self.reset()

Step 4: Add shooting#

Create a Bullet class to add the shooting function:

class Bullet(Actor):
    def on_setup(self):
        self.add_costume("laserred")
        self.x = plane.x
        self.y = plane.y
        self.speed = 25
        self.fire = False
    
    def act(self):
        self.x += self.speed

    def on_detecting_enemy(self, enemy):
        enemy.reset()
        
    def on_detecting_not_on_world(self):
        self.remove()

With the methods on_detecting_enemy and on_detecting_not_on_world, bullets can detect enemies and be removed when leaving the world.

Vollständiger Code:#

from miniworlds import World, Actor, timer, Text
from random import randint, choice

# based on https://github.com/kantel/pygamezero/tree/master/tappyplane


class RedBaronWorld(World):
    def on_setup(self):
        self.size = (800, 480)
        bottom_ground = self.height - 35
        nr_enemies = 10

        # Add backgrounds
        back0 = Actor(origin="topleft")
        back0.add_costume("background")
        back0.size = self.width, self.height
        back1 = Actor((self.width, 0), origin="topleft")
        back1.size = self.width, self.height
        back1.add_costume("background")
        self.backs = [back0, back1]

        ground0 = Actor((0, bottom_ground), origin="topleft")
        ground0.add_costume("groundgrass")
        ground0.width = self.width
        ground0.costume.is_scaled = True
        ground1 = Actor((self.width, bottom_ground), origin="topleft")
        ground1.add_costume("groundgrass")
        ground1.width = self.width
        ground1.costume.is_scaled = True
        self.grounds = [ground0, ground1]
        self.ground_level = self.height - 85
        self.plane = Plane((100, self.height / 2))

        enemies = []
        for _ in range(nr_enemies):
            enemy = Enemy()
            enemy.reset()
            enemies.append(enemy)

    def act(self):
        for back in self.backs:
            back.x -= 1
            if back.x <= -self.width:
                back.x = self.width
        for ground in self.grounds:
            ground.x -= 2
            if ground.x <= -self.width:
                ground.x = self.width
                
    def on_key_down_space(self):
        if not self.is_running:
            self.reset()
            self.run()


class Plane(Actor):
    def on_setup(self):
        self.add_costume("planered1")
        self.gravity = 0.1
        self.velocity_y = 0
        self.fire = False

    def act(self):
        self.velocity_y += self.gravity
        self.velocity_y *= 0.9  # friction
        self.y += self.velocity_y
        if self.y >= self.world.ground_level:
            self.y = self.world.ground_level
            self.velocity_y = 0
        if self.y <= 20:
            self.y = 20
            self.velocity_y = 0

    def on_key_down_w(self):
        self.velocity_y -= 5

    def on_key_down_d(self):
        if not self.fire:
            self.fire = True
            bullet = Bullet()

            @timer(frames=30)
            def downtime():
                self.fire = False

    def on_detecting_enemy(self, other):
        text = Text((self.world.width / 2, self.world.height / 2), "GAME OVER")
        text.color = (0, 0, 0)
        self.world.stop()


class Bullet(Actor):
    def on_setup(self):
        self.add_costume("laserred")
        self.x = self.world.plane.x
        self.y = self.world.plane.y
        self.speed = 25
        self.fire = False

    def act(self):
        self.x += self.speed

    def on_detecting_enemy(self, enemy):
        enemy.reset()

    def on_not_detecting_world(self):
        self.remove()


class Enemy(Actor):

    enemy_ships = ["shipbeige", "shipblue", "shipgreen", "shippink", "shipyellow"]

    def on_setup(self):
        self.add_costume(choice(self.enemy_ships))
        self.speed = -1.5

    def reset(self):
        self.x = randint(self.world.width + 50, self.world.width + 500)
        self.y = randint(25, self.world.ground_level)

    def act(self):
        self.x += self.speed
        if self.x <= -self.world.width:
            self.reset()


level1 = RedBaronWorld()
level1.run()