Pyxel을 이용한 2D 게임 시작하기 (파트 15): 터널 회피 게임 2 (샘플)

발행: (2026년 1월 18일 오전 12:00 GMT+9)
5 min read
원문: Dev.to

Source: Dev.to

번역할 텍스트가 제공되지 않았습니다. 번역이 필요한 본문을 알려주시면 한국어로 번역해 드리겠습니다.

에셋

Pyxel Editor를 사용하여 배경 에셋을 만들고 리소스 파일을 다운로드하세요:

Download – 페이지 오른쪽 상단에 있는 Download 버튼을 클릭하여 파일을 받으세요.

전체 샘플 코드

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)

main.py

import pyxel
import math
import random
import sprite

# ----------------------------------------------------------------------
# Constants
# ----------------------------------------------------------------------
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)

    # ------------------------------------------------------------------
    # Core loop
    # ------------------------------------------------------------------
    def update(self):
        """Update processing"""

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

        # Control input
        self.control()

        # Skip the rest if we are not in play mode
        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```

> **Source:** ...

```python
.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, f"SCORE:{self.score:04}", 1)

    # ------------------------------------------------------------------
    # Helper methods
    # ------------------------------------------------------------------
    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

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

            # Lower tunnel
            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 (only in play mode)
        if self.game_mode == MODE_PLAY:
            self.player.jump()

def main():
    Game()

if __name__ == "__main__":
    main()
""" Main process """
Game()

if __name__ == "__main__":
    main()

게임을 실행하면 다음과 같이 보일 것입니다.

이 글을 읽어 주셔서 대단히 감사합니다.
이 시리즈가 게임 개발을 시작하는 데 도움이 되길 바랍니다.

(재미있으셨다면 👍을 눌러 주시면 정말 감사하겠습니다!)

Back to Blog

관련 글

더 보기 »