Pyxel을 사용한 2D 게임 시작하기 (파트 7): 캐릭터 제어
Source: Dev.to
2D 게임 시작하기 with Pyxel – 파트 7: 캐릭터 제어
이번 파트에서는 플레이어가 직접 캐릭터를 움직일 수 있도록 입력 처리와 이동 로직을 구현합니다. 앞선 파트에서 만든 기본 화면에 키보드 입력을 연결하고, 캐릭터가 화면 안에서 부드럽게 움직이도록 만들겠습니다.
1. 키 입력 받기
Pyxel은 pyxel.btn() 과 pyxel.btnp() 함수를 제공하여 현재 키가 눌러져 있는지, 혹은 방금 눌렸는지를 확인할 수 있습니다.
| 함수 | 설명 |
|---|---|
pyxel.btn(key) | 지정한 키가 지속적으로 눌러져 있는지 반환 |
pyxel.btnp(key, hold=0, period=0) | 지정한 키가 새로 눌렸는지(한 번만) 반환. hold와 period 옵션으로 반복 입력을 제어 |
키 코드는 pyxel.KEY_LEFT, pyxel.KEY_RIGHT, pyxel.KEY_UP, pyxel.KEY_DOWN 등으로 접근합니다.
if pyxel.btn(pyxel.KEY_LEFT):
self.x -= self.speed
if pyxel.btn(pyxel.KEY_RIGHT):
self.x += self.speed
if pyxel.btn(pyxel.KEY_UP):
self.y -= self.speed
if pyxel.btn(pyxel.KEY_DOWN):
self.y += self.speed
2. 캐릭터 위치 업데이트
캐릭터의 좌표(x, y)와 이동 속도(speed)를 클래스 변수로 선언하고, update() 메서드 안에서 위의 입력 로직을 실행합니다. 화면 밖으로 나가지 않게 제한을 두는 것이 일반적입니다.
def update(self):
# 입력 처리
if pyxel.btn(pyxel.KEY_LEFT):
self.x = max(0, self.x - self.speed)
if pyxel.btn(pyxel.KEY_RIGHT):
self.x = min(pyxel.width - self.w, self.x + self.speed)
if pyxel.btn(pyxel.KEY_UP):
self.y = max(0, self.y - self.speed)
if pyxel.btn(pyxel.KEY_DOWN):
self.y = min(pyxel.height - self.h, self.y + self.speed)
max()와min()을 사용해 화면 경계를 벗어나지 않도록 합니다.self.w,self.h는 캐릭터 스프라이트의 가로·세로 크기입니다.
3. 애니메이션 추가 (선택 사항)
키 입력에 따라 스프라이트 인덱스를 바꾸면 간단한 걷는 애니메이션을 구현할 수 있습니다. 보통 self.frame 변수를 0~1 사이에서 토글하면서 두 개의 스프라이트를 교대로 그립니다.
self.frame = (self.frame + 1) % 2 # 0 ↔ 1 토글
pyxel.blt(self.x, self.y, 0, 0 + self.frame * 8, 0, 8, 8, colkey=0)
위 코드는 8×8 크기의 스프라이트 시트에서 첫 번째와 두 번째 프레임을 번갈아 그립니다.
4. 전체 예제
아래는 지금까지 설명한 내용을 모두 합친 최소 실행 가능한 예제입니다. 코드 블록 내부는 그대로 유지하고, 설명 부분만 한국어로 번역했습니다.
import pyxel
class Player:
def __init__(self, x, y):
self.x = x
self.y = y
self.w = 8
self.h = 8
self.speed = 2
self.frame = 0
def update(self):
if pyxel.btn(pyxel.KEY_LEFT):
self.x = max(0, self.x - self.speed)
if pyxel.btn(pyxel.KEY_RIGHT):
self.x = min(pyxel.width - self.w, self.x + self.speed)
if pyxel.btn(pyxel.KEY_UP):
self.y = max(0, self.y - self.speed)
if pyxel.btn(pyxel.KEY_DOWN):
self.y = min(pyxel.height - self.h, self.y + self.speed)
# 간단한 걷기 애니메이션
self.frame = (self.frame + 1) % 2
def draw(self):
pyxel.blt(self.x, self.y, 0, self.frame * 8, 0, 8, 8, colkey=0)
class App:
def __init__(self):
pyxel.init(160, 120, caption="Pyxel Character Control")
self.player = Player(80, 60)
pyxel.load("assets.pyxres") # 스프라이트 시트가 포함된 파일
pyxel.run(self.update, self.draw)
def update(self):
self.player.update()
def draw(self):
pyxel.cls(0)
self.player.draw()
App()
코드 설명
Player클래스는 캐릭터의 위치, 속도, 프레임 정보를 관리합니다.update()메서드에서 키 입력을 감지하고 좌표를 업데이트합니다.draw()메서드에서는 현재 프레임에 맞는 스프라이트를 화면에 그립니다.App클래스는 Pyxel 초기화, 메인 루프 연결, 그리고 전체 화면을 담당합니다.
5. 마무리
- 입력 처리와 경계 체크를 통해 기본적인 캐릭터 이동을 구현했습니다.
- 필요에 따라 점프, 공격, 다중 키 입력 등 복합적인 동작을 추가할 수 있습니다.
- 다음 파트에서는 맵 로딩과 충돌 처리를 다루어 보다 완성도 높은 2D 게임을 만들 예정입니다.
Tip:
pyxel.btnp()를 사용하면 키를 한 번만 인식하도록 할 수 있어, 점프와 같은 동작에 유용합니다.
이제 캐릭터를 자유롭게 움직일 수 있으니, 여러분만의 게임 로직을 추가해 보세요! 🚀
캐릭터 제어
이 장에서는 Space 키가 눌렸을 때를 감지하고 플레이어의 이동 방향을 좌 ↔ 우로 전환합니다. 이를 통해 보다 강한 직접 제어 감각을 제공합니다.
초기 이동 방향 설정
Game 생성자에서 함선의 초기 방향을 무작위로 선택합니다 (오른쪽 = 0° 또는 왼쪽 = 180°) 그리고 속도를 설정합니다.
# main.py – inside Game.__init__()
import random
# Randomly choose left or right
deg = 0 if random.random() < 0.5 else 180
self.ship.move(SHIP_SPD, deg)
Flip X 메서드
BaseSprite에 x‑축 속도를 반전시키는 flip_x() 메서드를 추가합니다.
# sprite.py – added to BaseSprite class
def flip_x(self):
"""Flip movement in the x direction"""
self.vx *= -1
flip_x()를 호출하면 방향이 바뀝니다:
- 오른쪽 → 왼쪽
- 왼쪽 → 오른쪽
키보드 입력 처리
Game 클래스에 control_ship() 메서드를 만듭니다. 이 메서드는 pyxel.btnp()를 사용해 Space 키가 정확히 눌린 순간을 감지하고 함선의 방향을 전환합니다.
# main.py – added to Game class
def control_ship(self):
"""Handle input"""
if pyxel.btnp(pyxel.KEY_SPACE):
self.ship.flip_x() # Reverse movement
Space 키를 누를 때마다 함선의 수평 이동이 토글됩니다.
화면 경계 래핑 로직
함선이 화면을 벗어나면 반대쪽으로 이동시킵니다. 재사용 가능한 overlap_spr() 메서드는 모든 스프라이트(예: 향후 추가될 소행성)에도 사용할 수 있습니다.
# main.py – added to Game class
def overlap_spr(self, spr):
"""Wrap sprite around screen edges"""
if spr.x < -spr.w:
spr.x = W
return
if W < spr.x:
spr.x = -spr.w
return
if spr.y < -spr.h:
spr.y = H
return
if H < spr.y:
spr.y = -spr.h
return
전체 코드
아래는 위에서 설명한 기능들의 전체 구현입니다.
# sprite.py
import pyxel
import math
import random
class BaseSprite:
def __init__(self, x, y, w=8, h=8):
self.x = x
self.y = y
self.w = w
self.h = h
self.vx = 0
self.vy = 0
def update(self):
self.x += self.vx
self.y += self.vy
def draw(self):
pass # Implemented in subclasses
def move(self, spd, deg):
rad = deg * math.pi / 180
self.vx = spd * math.cos(rad)
self.vy = spd * math.sin(rad)
def flip_x(self):
"""Flip movement in the x direction"""
self.vx *= -1
class ShipSprite(BaseSprite):
def __init__(self, x, y):
super().__init__(x, y)
def draw(self):
pyxel.blt(self.x, self.y, 0, 0, 0, self.w, self.h, 0)
# main.py
import pyxel
import random
import sprite
W, H = 160, 120
SHIP_SPD = 1.4 # Player speed
class Game:
def __init__(self):
self.score = 0
self.ship = sprite.ShipSprite(W / 2, H - 40)
# Random initial direction
deg = 0 if random.random() < 0.5 else 180
self.ship.move(SHIP_SPD, deg)
pyxel.init(W, H, title="Hello, Pyxel!!")
pyxel.load("shooter.pyxres")
pyxel.run(self.update, self.draw)
def update(self):
self.ship.update()
self.control_ship()
self.overlap_spr(self.ship)
def draw(self):
pyxel.cls(0)
pyxel.text(10, 10, f"SCORE:{self.score:04}", 12)
self.ship.draw()
def control_ship(self):
if pyxel.btnp(pyxel.KEY_SPACE):
self.ship.flip_x() # Reverse movement
def overlap_spr(self, spr):
if spr.x < -spr.w:
spr.x = W
return
if W < spr.x:
spr.x = -spr.w
return
if spr.y < -spr.h:
spr.y = H
return
if H < spr.y:
spr.y = -spr.h
return
def main():
Game()
if __name__ == "__main__":
main()
게임을 실행하면 함선이 수평으로 이동하는 것을 볼 수 있습니다; Space 키를 누르면 방향이 바뀌고, 화면 가장자리를 넘어가면 반대쪽으로 이동합니다.
*In the ne
다음 장에서는 “Let’s Spawn Asteroids.”*