RU

Урок 2 — Персонаж, который двигается и анимируется

Всё, что движется, — это сущности (src/Entity/), которые сцены создают через new. У сущности есть позиция ($this->x, $this->y), скорость (vx, vy), размер, направление (facing) и состояние (state). Трейты добавляют возможности: Sprite (анимация), Gravity (падение), TileCollision (знать, касаемся ли мы земли).

Спрайт, нарисованный на PHP

Картинка не нужна: createTextureFromPixels() создаёт текстуру из строк текста. Каждый символ — пиксель, . — прозрачный.

private const SHEET = [            // 3 кадра 12 × 14: стоит, идёт, прыгает
    '...####........####........####.....',
    '..######......######......######....',
    '..##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.