Entrar
PT
games/satisfying-bounce / src / Scene / TunnelScene.php · commit 8bdd6e4
<?phpnamespace App\Scene;use App\Audio\Scale;use App\Render\Gfx;use JumPHP\Renderer\Renderer;/** * An endless tunnel of squares coming at you, each one twisted a little more than the one before and coloured a step further round the * wheel: the picture seems to spin and pull you in while nothing moves but the size of the squares. Every square that passes a line * near the middle plays the next note of a 12-note phrase (D minor pentatonic) and lights up. The film is exactly one or more periods * long (6 s: 12 squares go by), so it loops with no seam. */final class TunnelScene extends VideoScene{    public const SECONDS = 24.0;    protected const LOOPS = true;    public const PERIOD = 6.0;    public const RINGS = 12;    private const RATIO = 1.3;    private const R_MIN = 7.0;    private float $flashed = -1.0;    /** @var array<int, float> square number (mod 12) => flash */    private array $flash = [];    protected function melodyNotes(): string    {        return 'D4';    }    protected function headingText(): string    {        return 'DO NOT STARE TOO LONG';    }    protected function hookText(): string    {        return 'THE TUNNEL NEVER ENDS';    }    protected function captionText(): string    {        return $this->t('DEPTH {n}', ['n' => (int)floor($this->time / self::PERIOD * self::RINGS)]);    }    protected function build(): void    {        $this->flash = [];        $this->flashed = -1.0;    }    /** how many squares have gone by the middle line (a float: its fraction moves them) */    public function depth(): float    {        return $this->time / self::PERIOD * self::RINGS;    }    protected function tick(float $dt): void    {        $d = $this->depth();        $n = (int)floor($d);        if ($n > $this->flashed) {            $this->flashed = $n;            if ($n > 0 || $d > 0.0) {                $i = $n % self::RINGS;                $this->flash[$i] = 1.0;                $this->play(Scale::freq(Scale::MINOR_PENTA, 4 + [0, 2, 4, 2, 5, 4, 2, 0, 1, 3, 2, 4][$i], 62), 'marimba', 0.5, 0.3, 0.0);                if ($i % 4 === 0) {                    $this->pad([Scale::freq(Scale::MINOR_PENTA, 0, 50), Scale::freq(Scale::MINOR_PENTA, 2, 50)], 3.2, 0.06);                }                $this->pulse = 1.0;                $this->tint = Gfx::hsv($i / self::RINGS);            }        }        foreach ($this->flash as $i => $f) {            $this->flash[$i] = max(0.0, $f - $dt * 2.2);        }    }    protected function drawWorld(Renderer $r): void    {        $d = $this->depth();        $base = floor($d);        $frac = $d - $base;        $cx = $r->getWidth() / 2;        $cy = 262.0;        // squares from the nearest (big) to the farthest (small); square j (0 = the one at the middle line) has radius R_MIN * ratio^(count - j - frac)        $count = 17;        for ($j = -3; $j < $count; $j++) {            $g = (int)($base + $j);                         // the number of this square: its colour and twist            $rad = self::R_MIN * self::RATIO ** ($count - $j - $frac - 3);            if ($rad < 3 || $rad > 420) {                continue;            }            $gi = (($g % self::RINGS) + self::RINGS) % self::RINGS;            $hue = $gi / self::RINGS;            $angle = $g * M_PI / 2 / self::RINGS + $this->time / self::PERIOD * M_PI / 2;            $light = 0.55 + 0.45 * ($rad / 120 > 1 ? 1 : $rad / 120);            $f = $this->flash[$gi] ?? 0.0;            $rgb = Gfx::mix(Gfx::hsv($hue + 0.55, 0.62, $light), [255, 255, 255], $f * 0.7);            $thick = max(1.0, $rad * 0.085) + $f * 1.5;            $this->frame($r, $cx, $cy, $rad, $angle, $thick, $rgb);        }        Gfx::glow($r, $cx, $cy, 4, [255, 255, 255], 0.8);        Gfx::disc($r, $cx, $cy, 2.2, [255, 255, 255]);    }    /** a hollow square: four bars */    private function frame(Renderer $r, float $cx, float $cy, float $rad, float $angle, float $thick, array $rgb): void    {        $pts = [];        for ($i = 0; $i < 4; $i++) {            $a = $angle + M_PI / 4 + $i * M_PI / 2;            $pts[] = [$cx + cos($a) * $rad * M_SQRT2, $cy + sin($a) * $rad * M_SQRT2];        }        for ($i = 0; $i < 4; $i++) {            [$x1, $y1] = $pts[$i];            [$x2, $y2] = $pts[($i + 1) % 4];            if (max($x1, $x2) < -10 || min($x1, $x2) > 280 || max($y1, $y2) < -10 || min($y1, $y2) > 490) {                continue;            }            Gfx::bar($r, $x1, $y1, $x2, $y2, $thick, $rgb);        }    }}