PL

SONIC PHP

autor: davidnurdin · Platformówki
5,0 (1)
▶ Graj 💬 Opinie Kod źródłowy 🕘 Wersje Edytuj GitHub
. / src / Entities / Objects / Platform.php · commit a20d00e
<?phpnamespace App\Entities\Objects;use App\Entities\GameObject;use App\Gfx\Gfx;/** * Platforms Sonic can stand on. Each kind follows its ROM routine, one step per original frame (60Hz): *  $0B doObjectCode_platform              sinks 16px (1/2 px/frame) while stood on, rises back (1/4 px/frame) *  $0C doObjectCode_platform_weight       falls 24 frames after being stood on (gravity 1/4 px/frame²) *  $0F doObjectCode_platform_leftRight    1px/frame to the right for 160 frames, then back *  $27/$28 doObjectCode_platform_fall*    Jungle logs going down the waterfall (1/2 px/frame), back on top every 768/792 frames *  $29 doObjectCode_platform_roll         Jungle floating log: bobs, rolls with Sonic at half his speed *  $38 doObjectCode_platform_flyingRight  Sky Base: follows the wave, flies right (1/2 px/frame) while stood on *  $3B doObjectCode_platform_flyingUpDown up and down, ±1/16 px/frame² up to 2px/frame, 160 frames period *  $45 doObjectCode_platform_float        Labyrinth: rises once stood on (see updateFloat) */class Platform extends GameObject{    public const SPRITE_GREEN_HILL = [8, 8, 32, 16];    public const SPRITE_LOG = [104, 32, 32, 16];    public const SPRITE_SKY = [176, 56, 32, 16];    private const FRAME = 1 / 60;    protected float $startX;    protected float $startY;    protected string $kind;    protected bool $falling = false;    protected float $deltaX = 0;    protected float $carryX = 0;    protected float $deltaY = 0;    // stood on since the last frame (set by touch())    protected bool $touched = false;    protected bool $stood = false;    protected float $speedY = 0;    protected int $counter = 0;    protected int $direction = 1;    protected float $clock = 0;    protected int $frames = 0;    // $29: Sonic's speed (px/s), the distance rolled, the X Sonic is held at    protected float $playerSpeed = 0;    protected float $rolled = 0;    protected ?float $pinX = null;    // $38: camera wave offset ($D2A1)    protected float $wave = 0;    public function __construct(Gfx $sdl, int $objectType, string $typeName, int $tileX, int $tileY, array $sprite, ?string $sheet = null)    {        parent::__construct($sdl, $objectType, $typeName, 0, 0, $sprite[2], $sprite[3]);        $this->kind = match ($objectType) {            0x0B => 'sink',            0x0C => 'weight',            0x0F => 'leftRight',            0x27, 0x28 => 'fallLog',            0x29 => 'roll',            0x38 => 'flyRight',            0x3B => 'upDown',            0x45 => 'float',            default => 'none',        };        if ($sheet !== null) {            $this->tilesetName = $sheet;        }        $frames = $this->kind === 'roll' ? RomSprites::LOG_ROLL : [$sprite];        foreach ($frames as $i => $frame) {            $this->animations["f$i"] = ['flags' => [], 'coords' => self::frames([$frame])];        }        $this->setAnimation('f0');        // The ROM layouts start with an empty row: the sprite is 16px under the object, +1 line on the SMS.        $this->x = $tileX * 32;        $this->y = $tileY * 32 + 17;        if ($this->kind === 'float') {            $this->y = $tileY * 32 + 15;   // Y - 1, sprite on the second row        } elseif ($this->kind === 'roll') {            $this->y -= 24;                // Y - 24 on the first frame        }        $this->startX = $this->x;        $this->startY = $this->y;    }    public function update(float $deltaTime): void    {        if (!$this->active) return;        $oldX = $this->x;        $oldY = $this->y;        $this->pinX = null;        $this->clock += $deltaTime;        while ($this->clock >= self::FRAME) {            $this->clock -= self::FRAME;            $this->stood = $this->touched;            $this->touched = false;            $this->tick();            $this->frames++;        }        $this->deltaX = $this->x - $oldX;        $this->carryX = $this->deltaX;        $this->deltaY = $this->y - $oldY;        if ($this->kind === 'roll' && $this->stood) {            // _7ee6: Sonic is held on the log (X = log X - 4 for his 24px wide object)            $this->pinX = $this->x + $this->width / 2;        }    }    private function tick(): void    {        switch ($this->kind) {            case 'sink':                $offset = $this->y - $this->startY;                if ($this->stood) {                    $this->y = $offset < 16 ? min($this->startY + 16, $this->y + 0.5) : $this->y;                } elseif ($offset > 0) {                    $this->y = max($this->startY, $this->y - 0.25);                }                break;            case 'weight':                if ($this->stood) $this->falling = true;                if ($this->falling && ++$this->counter >= 0x18) {                    $this->speedY += 0x40 / 256;                }                $this->y += $this->speedY;                // removed once below the screen                if ($this->y > $this->startY + 480) {                    $this->active = false;                    $this->visible = false;                }                break;            case 'leftRight':                if (++$this->counter >= 0xA0) {                    $this->counter = 0;                    $this->direction = -$this->direction;                }                $this->x += $this->direction;                break;            case 'fallLog':                $this->y += 0.5;                if (($this->frames & 3) === 0 && ++$this->counter >= ($this->objectType === 0x27 ? 0xC0 : 0xC6)) {                    $this->counter = 0;                    $this->y = $this->startY;                }                break;            case 'roll':                $this->y += $this->counter < 0x14 ? 0.25 : -0.25;                $this->counter = ($this->counter + 1) % 0x28;                $frame = 0;                if ($this->stood && $this->playerSpeed != 0) {                    $speed = $this->playerSpeed / 60;   // px/frame                    if (!$this->blockedAhead($speed > 0)) {                        $this->x += $speed / 2;                    }                    $this->rolled = fmod($this->rolled + abs($speed), 9);                    $frame = intdiv((int)$this->rolled, 3);                }                $this->setAnimation("f$frame");                break;            case 'flyRight':                if ($this->stood) $this->startX += 0.5;                $this->x = $this->startX;                $this->y = $this->startY + $this->wave;                break;            case 'upDown':                $this->speedY += $this->counter < 0x50 ? 1 / 16 : -1 / 16;                $this->speedY = max(-2, min(2, $this->speedY));                $this->y += $this->speedY;                $this->counter = ($this->counter + 1) % 0xA0;                break;            case 'float':                $this->tickFloat();                break;        }    }    /** $29: the log stops when the block in front of it (X+18 or X-2, at the object Y) is solid. */    private function blockedAhead(bool $right): bool    {        if (!$this->level) return false;        $x = $right ? $this->x + 0x12 : $this->x - 2;        $block = $this->level->getTile((int)floor($x / 32), (int)floor(($this->y - 17) / 32));        $solidity = $block === null ? null : ($this->level->getSolidity()[$block] ?? null);        return $solidity !== null && (ord($solidity) & 0x3F) !== 0;    }    /**     * doObjectCode_platform_float: once stood on, accelerates upwards by 8/256 px/frame² up to 1px/frame and     * stops when the block under it has the solidity $1E. Goes back home when Sonic is far away (off-screen).     */    private function tickFloat(): void    {        if ($this->stood) $this->falling = true;   // "touched" once        if (!$this->falling || !$this->level) return;        $block = $this->level->getTile((int)floor(($this->x + 16) / 32), (int)floor(($this->y + 16) / 32));        $solidity = $this->level->getSolidity()[$block] ?? null;        if ($solidity !== null && (ord($solidity) & 0x3F) === 0x1E) {            $this->speedY = 0;            return;        }        $this->speedY = max(-1, $this->speedY - 8 / 256);        $this->y += $this->speedY;    }    public function render(float $cameraX, float $cameraY): void    {        parent::render($cameraX, $cameraY);        if ($this->kind === 'flyRight') {            // _b388: the propeller on the left side, a new frame every frame            $frame = RomSprites::PROPELLER[$this->frames & 3];            $src = new \SDL_Rect;            [$src->x, $src->y, $src->w, $src->h] = $frame;            $dst = new \SDL_Rect;            $dst->x = (int)round($this->x - 8 - $cameraX);            $dst->y = (int)round($this->y - 8 + ($frame[3] === 16 ? 4 : 0) - $cameraY);            $dst->w = $frame[2];            $dst->h = $frame[3];            \SDL_RenderCopy($this->sdl->getRenderer()->getRenderer(), $this->getTexture()['texture'], $src, $dst);        }    }    /** Called by the GameObjectManager while Sonic stands on the platform. */    public function touch(): void    {        $this->touched = true;    }    public function setPlayerSpeed(float $vx): void    {        $this->playerSpeed = $vx;    }    public function setWave(float $wave): void    {        $this->wave = $wave;    }    /** Centre X Sonic is held at ($29 floating log), null when he moves freely. */    public function getPinX(): ?float    {        return $this->pinX;    }    public function isFloating(): bool    {        return $this->kind === 'float';    }    /** Stood on: a weight falls, a floating platform rises (online: stood on on another machine). */    public function isFalling(): bool    {        return $this->falling;    }    public function startFalling(): void    {        if ($this->kind === 'weight' || $this->kind === 'float') $this->falling = true;    }    /** Back to the spawn point (the original does it when the platform leaves the screen). */    public function resetPosition(): void    {        $this->x = $this->startX;        $this->y = $this->startY;        $this->falling = false;        $this->speedY = 0;    }    public function isPlayerOnTop(array $playerRect, float $playerVy): bool    {        if (!$this->active || $playerVy < 0) return false;        $feet = $playerRect['y'] + $playerRect['height'];        return $feet >= $this->y - 4 - max(0, $this->deltaY)            && $feet <= $this->y + 10 + max(0, $this->deltaY)            && $playerRect['x'] + $playerRect['width'] > $this->x + 2            && $playerRect['x'] < $this->x + $this->width - 2;    }    public function getDeltaX(): float { return $this->deltaX; }    /**     * Horizontal move of the last update, handed out once: Sonic's collision runs several steps per frame,     * he must be carried by the platform once, not once per step (he slid along it).     */    public function takeDeltaX(): float    {        $dx = $this->carryX;        $this->carryX = 0;        return $dx;    }    public function getDeltaY(): float { return $this->deltaY; }}