SONIC PHP

par davidnurdin · Plates-formes
pas encore noté
▶ Jouer Sources Éditer GitHub
. / src / Gfx / Gfx.php · commit d114180
<?phpnamespace App\Gfx;use JumPHP\Kernel\Engine;use JumPHP\Renderer\Sdl;/** * The game's textures by name, loaded by JumPHP's Sdl (assets/ paths, colour keys, slices of 4064 px), with what * the game needs on top of it: *  - the blocks of the 35 levels are registered only (~200 MB once decoded) and loaded with their level *    (loadGroup / releaseGroupsExcept); several levels use the same files: a file is loaded once, whatever the *    names it has; *  - wide pictures come with their slices next to them (tileset1-0.png, tileset1-1.png...: cut once, not by GD *    at each start in the browser); *  - variants: getTextures('objects') gives 'objects-water' while setTextureVariant('-water') is on; *  - getRenderer() / getWindow(): the game view (View) on JumPHP's renderer. * * A texture is a list of slices ['texture' => SDL texture, 'width', 'height', 'x'] like JumPHP's. */final class Gfx{    private const SLICE = 4064;    private View $view;    /** @var array<string, array> textures by name (slices) */    private array $textures = [];    /** @var array<string, array{0: string, 1: ?array, 2: string}> registered textures: name => [path, colour key, group] */    private array $lazy = [];    /** @var array<string, array{0: array, 1: string[]}> files loaded for the registered names: path key => [slices, JumPHP names] */    private array $files = [];    private string $variant = '';    private array $fonts = [];    private string $assets;    public function __construct(private Sdl $sdl, private Engine $engine)    {        $this->view = new View($sdl->getRenderer(), $engine);        $this->assets = $engine->config->path('assets');    }    public function getRenderer(): View    {        return $this->view;    }    /** The view size (getWidth / getHeight in game pixels). */    public function getWindow(): View    {        return $this->view;    }    public function engine(): Engine    {        return $this->engine;    }    // --- registered textures (the levels' blocks) ----------------------------------------------------    public function registerTexture(string $name, string $path, string $group, ?array $transparentColor = null): void    {        $this->lazy[$name] = [$path, $transparentColor, $group];    }    /** Loads every texture of $group not loaded yet. */    public function loadGroup(string $group): void    {        foreach ($this->lazy as $name => [, , $g]) {            if ($g === $group && !isset($this->textures[$name])) $this->loadLazy($name);        }    }    /** Frees the registered textures of the other groups (their files, unless $group uses them too). */    public function releaseGroupsExcept(string $group): void    {        $keep = [];        foreach ($this->lazy as [$path, $key, $g]) {            if ($g === $group) $keep[self::pathKey($path, $key)] = true;        }        foreach ($this->lazy as $name => [$path, $key, $g]) {            if ($g === $group || !isset($this->textures[$name])) continue;            unset($this->textures[$name]);            $pathKey = self::pathKey($path, $key);            if (!isset($keep[$pathKey])) $this->freeFile($pathKey);        }    }    /** Forgets a registered texture, and frees its file when no other name uses it. */    public function releaseTexture(string $name): void    {        if (!isset($this->lazy[$name])) return;        [$path, $key] = $this->lazy[$name];        $pathKey = self::pathKey($path, $key);        unset($this->textures[$name], $this->lazy[$name]);        foreach ($this->lazy as [$otherPath, $otherKey]) {            if (self::pathKey($otherPath, $otherKey) === $pathKey) return;        }        $this->freeFile($pathKey);    }    /** Sizes of the slices of a texture ([['width' => , 'height' => ], ...]) without loading it. */    public function getTextureSizes(string $name): array    {        if (isset($this->textures[$name]) || !isset($this->lazy[$name])) return $this->getTextures($name);        $file = $this->file($this->lazy[$name][0]);        $size = @getimagesize($file);        if ($size === false) throw new \RuntimeException('Image not found: ' . $this->lazy[$name][0]);        [$width, $height] = $size;        $sizes = [];        for ($x = 0; $x < $width || $x === 0; $x += self::SLICE) {            $sizes[] = ['width' => min(self::SLICE, $width - $x), 'height' => $height];        }        return $sizes;    }    private function loadLazy(string $name): array    {        [$path, $key] = $this->lazy[$name];        $pathKey = self::pathKey($path, $key);        if (!isset($this->files[$pathKey])) {            $engineName = 'file:' . strtr($pathKey, '/|,', '___');            $this->files[$pathKey] = [$this->loadFile($engineName, $path, $key), $this->engineNames];        }        return $this->textures[$name] = $this->files[$pathKey][0];    }    private function freeFile(string $pathKey): void    {        foreach ($this->files[$pathKey][1] ?? [] as $engineName) $this->sdl->releaseTexture($engineName);        unset($this->files[$pathKey]);    }    private static function pathKey(string $path, ?array $key): string    {        return $path . ($key === null ? '' : '|' . implode(',', $key));    }    // --- textures loaded now --------------------------------------------------------------------------    /** $path: from assets/; $transparentColor: ['r' => , 'g' => , 'b' => ] becomes transparent. */    public function loadTexture(string $name, string $path, ?array $transparentColor = null): void    {        $this->removeTexture($name);        $this->textures[$name] = $this->loadFile($name, $path, $transparentColor);        $this->owned[$name] = $this->engineNames;    }    /** @var array<string, string[]> JumPHP names of the textures loaded by loadTexture() */    private array $owned = [];    /** Loads $name again from another file, freeing the texture it had. */    public function replaceTexture(string $name, string $path, ?array $transparentColor = null): void    {        if (isset($this->lazy[$name])) return;        $this->loadTexture($name, $path, $transparentColor);    }    /** Forgets a texture loaded by loadTexture(). */    public function removeTexture(string $name): void    {        if (isset($this->lazy[$name])) return;        foreach ($this->owned[$name] ?? [] as $engineName) $this->sdl->releaseTexture($engineName);        unset($this->textures[$name], $this->owned[$name]);    }    public function hasTexture(string $name): bool    {        return isset($this->textures[$name]) || isset($this->lazy[$name]);    }    public function getTextures(string $name): array    {        if ($this->variant !== '' && $this->hasTexture($name . $this->variant)) {            $name .= $this->variant;        }        if (isset($this->textures[$name])) return $this->textures[$name];        if (isset($this->lazy[$name])) return $this->loadLazy($name);        throw new \RuntimeException("Unknown texture '$name'");    }    /** Suffix tried first by getTextures() (e.g. "-water": the underwater palette below the water line). */    public function setTextureVariant(string $variant): void    {        $this->variant = $variant;    }    /** Loads "{name}-water" from "{path without .png}-water.png" when that file exists. */    public function loadTextureVariant(string $name, string $path, string $variant, ?array $transparentColor = null): void    {        $variantPath = substr($path, 0, -4) . $variant . '.png';        if (is_file($this->file($variantPath))) {            $this->loadTexture($name . $variant, $variantPath, $transparentColor);        }    }    /** @var string[] the JumPHP names of the last file loaded */    private array $engineNames = [];    /**     * Loads assets/$path with JumPHP's Sdl as $engineName. A picture wider than 4064 px (WebGL's limit) is     * loaded from its slices next to it ("name-0.png", "name-1.png"...) when they are there, else JumPHP cuts it.     */    private function loadFile(string $engineName, string $path, ?array $key): array    {        $file = $this->file($path);        if (!is_file($file)) throw new \RuntimeException('Image not found: ' . $path);        $rgb = $key === null ? null : [(int)$key['r'], (int)$key['g'], (int)$key['b']];        $size = @getimagesize($file);        $width = $size ? (int)$size[0] : 0;        $base = substr($file, 0, -4);        if ($width <= self::SLICE || !is_file($base . '-0.png')) {            $this->engineNames = [$engineName];            return $this->sdl->loadTexture($engineName, $file, $rgb);        }        $slices = [];        $this->engineNames = [];        for ($i = 0, $x = 0; $x < $width; $i++, $x += self::SLICE) {            $part = $engineName . '#' . $i;            $slice = $this->sdl->loadTexture($part, $base . '-' . $i . '.png', $rgb)[0];            $slice['x'] = $x;            $slices[] = $slice;            $this->engineNames[] = $part;        }        return $slices;    }    private function file(string $path): string    {        return $this->assets . '/' . $path;    }    // --- font of the debug view (F1): the digits of the TTF font -------------------------------------    public function loadFont(string $name, string $path): void    {        $file = $this->file($path);        if (!is_file($file)) throw new \RuntimeException("Font file not found: $path");        if (!function_exists('TTF_OpenFont')) return;        \TTF_Init();        $font = \TTF_OpenFont($file, 29);        if (!$font) return;        $color = new \SDL_Color(255, 0, 0, 0);        $glyphs = [];        foreach (str_split('0123456789ABCDEF') as $char) {            $surface = \TTF_RenderText_Solid($font, $char, $color);            if (!$surface) continue;            $glyphs[$char] = \SDL_CreateTextureFromSurface($this->view->getRenderer(), $surface);            \SDL_FreeSurface($surface);        }        $this->fonts[$name] = $glyphs;    }    public function getFont(string $name): array    {        return $this->fonts[$name] ?? [];    }}