<?phpnamespace App\Scene;use App\Engine\Art;use App\Engine\Screen;use App\Engine\SoftRenderer;use App\Engine\Palette;use App\Game\Campaign;use App\Game\Graphics;use App\Game\HdView;use App\Game\SoundBank;use JumPHP\Kernel\Platform;use JumPHP\Renderer\Renderer;use JumPHP\Ui\MenuScene;/** * The title: the first level seen by a slowly turning camera (the real renderer), the logo, the main menu. * After a while without a key, the demo plays. */final class TitleScene extends MenuScene{ protected string $title = ''; protected float $menuTop = 0.55; /** seconds without a key: the demo starts after a while (the camera has its own clock: the engine's) */ private float $idle = 0.0; private ?SoftRenderer $view = null; private ?Screen $screen = null; private ?Art $art = null; private ?\App\Engine\Map\Level $level = null; /** @var mixed the logo's SDL texture (HD mode) */ private mixed $logo = null; public function enter(array $params = []): void { if (isset($params['graphics'])) { $this->engine->options->set('graphics', $params['graphics']); } parent::enter($params); $this->art ??= Art::get($this->engine->config->path('assets/data/art.pak')); $this->view ??= new SoftRenderer($this->art, 320, 200); $this->screen ??= new Screen($this->engine); $this->level ??= Campaign::level($this->engine->config->path('assets/maps/m1.json'), 'm1'); SoundBank::load($this->sound(), $this->engine->config); $this->idle = 0.0; } protected function items(): array { $items = [ ['NEW GAME', null, fn () => $this->scenes()->push(SkillScene::class)], ]; if ($this->engine->gl()->isAvailable()) { $toggle = function (): void { Graphics::toggle($this->engine); $this->refresh(); }; $items[] = ['GRAPHICS', fn () => Graphics::mode($this->engine) === 'hd' ? 'HD' : 'RETRO', $toggle, fn (int $dir) => $toggle()]; } $items = [ ...$items, ['OPTIONS', null, fn () => $this->scenes()->push(OptionsScene::class)], ['HOW TO PLAY', null, fn () => $this->scenes()->push(HelpScene::class)], ]; if (Platform::isDesktop()) { $items[] = ['QUIT', null, fn () => $this->engine->quit()]; } return $items; } /** @return array<int, array> the rows of the main menu (tests) */ public function menuItems(): array { return $this->items(); } public function update(float $dt): void { $this->idle += $dt; $a = $this->actions(); if ($a->isPressed('up') || $a->isPressed('down') || $a->isPressed('left') || $a->isPressed('right') || $a->isPressed('confirm')) { $this->idle = 0.0; } if ($this->idle > 30 && $this->scenes()->current() === $this) { $this->scenes()->switchTo(PlayScene::class, ['demo' => true, 'level' => 'm1']); return; } parent::update($dt); } public function exit(): void { Graphics::retro($this->engine); } protected function renderBackground(Renderer $r): void { $start = $this->level->things[0]; foreach ($this->level->things as $t) { if ($t['type'] === 'player') { $start = $t; } } $sector = $this->level->sectorAt($start['x'], $start['y']); // the engine's clock: the camera keeps turning while sub-menus come and go $angle = $start['angle'] + sin($this->engine->time() * 0.25) * 1.2; $z = $sector->floor + 48; $logo = $this->art->hud['LOGO']; $hd = Graphics::hd($this->engine); if ($hd !== null) { $r->clear([255, 0, 255]); $hd->setTint([0, 0, 0, 0.35]); $hd->render($this->level, $start['x'], $start['y'], $z, $angle, [], HdView::levelLights($this->level, $start['x'], $start['y']), null); // the logo in 2D, over the 3D $this->logo ??= $this->logoTexture($logo); $r->copy($this->logo, null, (320 - $logo['w']) / 2, 24, $logo['w'], $logo['h']); } else { $this->view->extraLight = 0; $this->view->render($this->level, $start['x'], $start['y'], $z, $angle); $this->view->blit($logo, (int)((320 - $logo['w']) / 2), 24); $this->screen->setTint([0, 0, 0, 0.45]); $this->screen->present($r, $this->view->fb, 320, 200, '', 0); } $this->font()->draw($r, 'A RETRO FPS WRITTEN IN PHP - JUMPHP', $this->width() / 2, 66, 1, [255, 190, 120], 'center', [0, 0, 0]); $this->font()->draw($r, 'ENGINE PORTED FROM THE 1993 GPL SOURCE', $this->width() / 2, 190, 1, [150, 150, 170], 'center', [0, 0, 0]); } /** The logo as an SDL texture (palette picture, transparent pixels kept). */ private function logoTexture(array $logo) { $img = imagecreatetruecolor($logo['w'], $logo['h']); imagealphablending($img, false); imagesavealpha($img, true); $rgb = Palette::rgb(); $clear = imagecolorallocatealpha($img, 0, 0, 0, 127); for ($y = 0; $y < $logo['h']; $y++) { for ($x = 0; $x < $logo['w']; $x++) { $i = ord($logo['px'][$y * $logo['w'] + $x]); imagesetpixel($img, $x, $y, $i === Palette::TRANSPARENT ? $clear : imagecolorallocatealpha($img, ...[...$rgb[$i], 0])); } } $this->sdl()->createTextureFromGd('iron-logo', $img); return $this->sdl()->getTexture('iron-logo')['texture']; }}