튜토리얼 2 — 움직이고 애니메이션하는 캐릭터
움직이는 것들은 엔티티(src/Entity/)이고, 장면이 new로 만들어요. 엔티티에는 위치($this->x, $this->y), 속도(vx, vy), 크기, 방향(facing), 상태(state)가 있어요. 트레이트로 기능을 더할 수 있어요. Sprite(애니메이션), Gravity(낙하), TileCollision(바닥에 닿았는지 알기)이 있어요.
PHP로 그린 스프라이트
이미지가 필요 없어요. createTextureFromPixels()가 텍스트 줄로 텍스처를 만들어 줘요. 문자 하나가 픽셀 하나이고, .은 투명이에요.
private const SHEET = [ // 12 × 14 크기 이미지 3장: 정지, 걷기, 점프
'...####........####........####.....',
'..######......######......######....',
'..##o#o#......##o#o#......##o#o#....',
// ...
];
public function __construct(Sdl $sdl, float $x, float $y)
{
$this->x = $x;
$this->y = $y;
$this->width = 10;
$this->height = 14;
if (!$sdl->hasTexture('player')) {
$sdl->createTextureFromPixels('player', self::SHEET, ['#' => '#ffd23f', 'o' => '#202020']);
}
$this->initSprite($sdl, 'player', 'idleRight');
}
실제 스프라이트 시트를 쓸 때는 $sdl->loadTexture('player', 'assets/sprites/player.png')를 사용해요.
애니메이션
애니메이션은 시트에서 가져온 이미지들의 목록이에요. 엔진은 <상태><방향> 이름의 애니메이션을 골라요. walkRight, idleLeft 같은 식이고, reverseWalkRight는 좌우를 뒤집은 이미지를 재사용해요.
protected array $animations = [
'idleRight' => ['coords' => [['x' => 0, 'y' => 0, 'w' => 12, 'h' => 14]]],
'walkRight' => ['coords' => [['x' => 12, 'y' => 0, 'w' => 12, 'h' => 14], ['x' => 0, 'y' => 0, 'w' => 12, 'h' => 14]], 'duration' => 0.12],
'jumpRight' => ['coords' => [['x' => 24, 'y' => 0, 'w' => 12, 'h' => 14]]],
'idleLeft' => 'reverseIdleRight',
'walkLeft' => 'reverseWalkRight',
'jumpLeft' => 'reverseJumpRight',
];
관성 있게 움직이기
use Sprite, Gravity, TileCollision {
Sprite::update as updateSprite;
Gravity::update as updateGravity;
}
public function control(int $move, bool $jumpPressed, bool $jumpHeld, float $dt): void
{
// 원하는 속도를 향해 부드럽게 가속해요
$this->vx = Math::approach($this->vx, $move * 110, 900 * $dt);
if ($move !== 0) {
$this->facing = $move < 0 ? 'left' : 'right';
}
if ($jumpPressed && $this->onGround) {
$this->vy = -290;
}
if (!$jumpHeld && $this->vy < -100) {
$this->vy = -100; // 버튼을 일찍 떼면 더 낮게 점프해요
}
}
public function update(float $dt): void
{
$this->updateGravity($dt);
$this->state = !$this->onGround ? 'jump' : (abs($this->vx) > 10 ? 'walk' : 'idle');
$this->pickAnimation(); // "<state><Facing>"을 골라요
$this->updateSprite($dt); // 다음 이미지
}
public function render(Renderer $r, float $camX = 0, float $camY = 0): void
{
$this->drawAt($r, $this->x - 1 - $camX, $this->y - $camY);
}
장면에서는 $player->control($this->actions()->axis('left', 'right'), $this->actions()->isPressed('jump'), $this->actions()->isHeld('jump'), $dt)를 호출한 뒤 $player->update($dt)를 호출해요. 벽과 바닥에 부딪히는 이동은 충돌 시스템의 일이에요. 튜토리얼 3에서 다뤄요.
이 코드는 JumPHP 스켈레톤(bin/jumphp new)의 src/Entity/Player.php와 같아요.