DEV Community

Kajiru
Kajiru

Posted on

Getting Started with 2D Games Using Pyxel (Part 15): Tunnel Dodge Game 2 (Sample)

Tunnel Dodge Game 2 (Sample)

In this article, we introduce a sample that displays a background using a tilemap.

(This article only introduces the sample code.)

1. Preparing the Assets

Create the background assets using the Pyxel Editor.

Download the resource file

(Click the Download button at the top right to download the file.)

Sample Code

The complete sample code is shown below.

# sprite.py
import pyxel
import math
import random

class BaseSprite:

    def __init__(self, x, y, w=8, h=8):
        """ Constructor """
        self.x = x
        self.y = y
        self.w = w
        self.h = h
        self.vx = 0
        self.vy = 0

    def update(self):
        """ Update processing """
        self.x += self.vx
        self.y += self.vy

    def draw(self):
        """ Draw processing (implemented in subclasses) """
        pass

    def move(self, spd, deg):
        """ Move by speed and angle """
        rad = deg * math.pi / 180
        self.vx = spd * math.cos(rad)  # Velocity on X axis
        self.vy = spd * math.sin(rad)  # Velocity on Y axis

    def intersects(self, other):
        """ Rectangle collision detection (AABB) """
        if other.x + other.w < self.x: return False
        if self.x + self.w < other.x: return False
        if other.y + other.h < self.y: return False
        if self.y + self.h < other.y: return False
        return True


class PlayerSprite(BaseSprite):

    def __init__(self, x, y):
        """ Constructor """
        super().__init__(x, y)
        self.gravity = 0.4    # Gravity
        self.jump_x = 1.0    # Jump X velocity
        self.jump_y = -3.4   # Jump Y velocity

    def update(self):
        """ Update processing """
        super().update()
        self.vy += self.gravity

    def draw(self):
        """ Draw processing """
        pyxel.blt(self.x, self.y, 0,
                  0, 16, self.w, self.h, 0)
        # Debug
        # pyxel.rectb(self.x, self.y, self.w, self.h, 3)

    def jump(self):
        """ Jump """
        self.vx = self.jump_x
        self.vy = self.jump_y


class TunnelSprite(BaseSprite):

    def __init__(self, x, y, length, top_flg=False):
        """ Constructor """
        super().__init__(x, y, 16, length * 8)
        self.length = length  # Tunnel length
        if top_flg:
            self.y -= self.h  # Upper tunnel

    def draw(self):
        """ Draw processing """
        # Top
        pyxel.blt(self.x, self.y, 0,
                  16, 16, self.w, 8, 0)

        # Middle
        for i in range(self.length - 2):
            y = self.y + (i + 1) * 8
            pyxel.blt(self.x, y, 0,
                      16, 24, self.w, 8, 0)

        # Bottom
        y = self.y + (self.length - 1) * 8
        pyxel.blt(self.x, y, 0,
                  16, 32, self.w, 8, 0)

        # Debug
        # pyxel.rectb(self.x, self.y, self.w, self.h, 3)
Enter fullscreen mode Exit fullscreen mode
# main.py
import pyxel
import math
import random
import sprite

W, H = 160, 120

START_X = W / 2 - 48
START_Y = H / 2 - 12

MODE_TITLE = "title"
MODE_PLAY = "play"
MODE_GAME_OVER = "game_over"

TUNNEL_TOTAL = 48


class Game:
    def __init__(self):
        """ Constructor """

        # Initialize score
        self.score = 0

        # Game mode
        self.game_mode = MODE_TITLE

        # Initialize player
        self.player = sprite.PlayerSprite(START_X, START_Y)

        # Initialize stage
        self.reset()

        # Start Pyxel
        pyxel.init(W, H, title="Hello, Pyxel!!")
        pyxel.load("flappy.pyxres")
        pyxel.run(self.update, self.draw)

    def update(self):
        """ Update processing """

        # Update score
        self.score = int(self.player.x - START_X)

        # Control input
        self.control()

        # Check play state
        if self.game_mode != MODE_PLAY:
            return

        # Update player
        self.player.update()

        # Update tunnels
        for tunnel in self.tunnels:
            tunnel.update()
            if tunnel.intersects(self.player):
                self.game_mode = MODE_GAME_OVER
                break

        # Falling check
        if H < self.player.y:
            self.game_mode = MODE_GAME_OVER

    def draw(self):
        """ Draw processing """
        pyxel.cls(6)

        # Tilemap background
        pyxel.bltm(0, 0, 0, 0, 0, 192, 128, 0)

        # Camera (set)
        pyxel.camera(self.player.x - START_X, 0)

        # Draw player
        self.player.draw()

        # Draw tunnels
        for tunnel in self.tunnels:
            tunnel.draw()

        # Camera (reset)
        pyxel.camera()

        # Messages
        if self.game_mode == MODE_TITLE:
            msg = "SPACE TO PLAY"
            pyxel.text(W / 2 - len(msg) * 2, H / 2, msg, 1)
        elif self.game_mode == MODE_GAME_OVER:
            msg = "GAME OVER"
            pyxel.text(W / 2 - len(msg) * 2, H / 2, msg, 1)

        # Draw score
        pyxel.text(10, 10,
                   "SCORE:{:04}".format(self.score), 1)

    def reset(self):
        """ Initialize stage """

        # Player position
        self.player.x = START_X
        self.player.y = START_Y

        # Tunnels
        self.tunnels = []
        for i in range(TUNNEL_TOTAL):
            pad_x = 42
            pad_y = random.randint(2, 3) * 8
            x = START_X + pad_x * i + 32
            y = H / 2 + random.randint(-2, 2) * 8

            t_top = sprite.TunnelSprite(x, y - pad_y, 10, True)
            self.tunnels.append(t_top)

            t_bottom = sprite.TunnelSprite(x, y + pad_y, 10)
            self.tunnels.append(t_bottom)

    def control(self):
        """ Control input """
        if not pyxel.btnp(pyxel.KEY_SPACE):
            return

        # Title -> Play
        if self.game_mode == MODE_TITLE:
            self.game_mode = MODE_PLAY

        # Game Over -> Title
        if self.game_mode == MODE_GAME_OVER:
            self.game_mode = MODE_TITLE
            self.reset()

        # Jump
        if self.game_mode == MODE_PLAY:
            self.player.jump()


def main():
    """ Main process """
    Game()


if __name__ == "__main__":
    main()
Enter fullscreen mode Exit fullscreen mode

Result

The game will look like this when running.

Conclusion

Thank you very much for reading this article.

I hope this series helps you get started with game development.

(If you enjoyed it, a 👍 would be greatly appreciated!)

Top comments (0)