Entrar
PT
games/satisfying-bounce / src / Scene / RainPondScene.php · commit 8bdd6e4
<?phpnamespace App\Scene;use App\Audio\Scale;use App\Render\Gfx;use JumPHP\Renderer\Renderer;/** * An ASMR pond: slow drops fall into the water, each one makes soft rings that spread, cross and fade, and a round drop sound of a * minor pentatonic scale (the further to the right, the higher). A low pad breathes underneath. Nothing hurries: the calm is the point. */final class RainPondScene extends VideoScene{    public const SECONDS = 24.0;    private const POND = [135.0, 290.0, 130.0, 118.0];     // centre x, y, radii    /** @var list<array{x: float, y: float, age: float, hue: float, big: float}> */    private array $ripples = [];    /** @var list<array{x: float, y: float, from: float, hit: float, hue: float}> */    private array $drops = [];    private float $next = 0.5;    private int $count = 0;    protected function melodyNotes(): string    {        return 'A3';    }    protected function headingText(): string    {        return 'RAIN ON A QUIET POND';    }    protected function hookText(): string    {        return 'BREATHE IN. LISTEN.';    }    protected function captionText(): string    {        return $this->t('DROPS {n}', ['n' => $this->count]);    }    public function dropCount(): int    {        return $this->count;    }    protected function build(): void    {        $this->world->reset($this->seed);        $this->ripples = [];        $this->drops = [];        $this->next = 0.5;        $this->count = 0;        $this->pad([Scale::freq(Scale::MINOR_PENTA, 0, 45), Scale::freq(Scale::MINOR_PENTA, 3, 45)], 6.0, 0.06);    }    protected function tick(float $dt): void    {        $rand = $this->world->random;        if ($this->time >= $this->next) {            $this->next = $this->time + $rand->between(0.45, 1.05);            [$cx, $cy, $rx, $ry] = self::POND;            $x = $cx + $rand->between(-0.62, 0.62) * $rx;            $y = $cy + $rand->between(-0.55, 0.55) * $ry;            $fall = sqrt(2 * ($y - 120) / 1100);            $this->drops[] = ['x' => $x, 'y' => $y, 'from' => $this->time, 'hit' => $this->time + $fall, 'hue' => 0.5 + 0.2 * ($x - $cx) / $rx];        }        foreach ($this->drops as $i => $d) {            if ($this->time >= $d['hit']) {                unset($this->drops[$i]);                $this->count++;                $this->ripples[] = ['x' => $d['x'], 'y' => $d['y'], 'age' => 0.0, 'hue' => $d['hue'], 'big' => 0.8 + $rand->between(0, 0.5)];                $deg = 3 + (int)floor(($d['x'] - (self::POND[0] - self::POND[2])) / (2 * self::POND[2]) * 8);                $this->play(Scale::freq(Scale::MINOR_PENTA, $deg, 57), 'drop', 0.9, 0.22 + $rand->between(0, 0.06), 0.1);                $this->fx->burst($d['x'], $d['y'], Gfx::hsv($d['hue'], 0.4), 4, 28);                $this->pulse = max($this->pulse, 0.25);                $this->tint = Gfx::hsv($d['hue'], 0.5);            }        }        $this->drops = array_values($this->drops);        foreach ($this->ripples as $i => &$rp) {            $rp['age'] += $dt;        }        unset($rp);        $this->ripples = array_values(array_filter($this->ripples, fn (array $rp) => $rp['age'] < 3.2));        if (count($this->ripples) > 12) {            array_shift($this->ripples);        }    }    protected function drawWorld(Renderer $r): void    {        [$cx, $cy, $rx, $ry] = self::POND;        Gfx::blob($r, $cx, $cy, $rx + 6, $ry + 5, 0.0, [34, 40, 86]);        Gfx::blob($r, $cx, $cy, $rx, $ry, 0.0, [12, 28, 60]);        Gfx::blob($r, $cx, $cy + 6, $rx * 0.8, $ry * 0.7, 0.0, [15, 36, 72]);        foreach ($this->ripples as $rp) {            foreach ([0.0, 0.32, 0.64] as $n => $delay) {                $a = $rp['age'] - $delay;                if ($a <= 0) {                    continue;                }                $rad = 6 + 44 * $a * $rp['big'] * (1 - 0.16 * $a);                $k = max(0.0, 1 - $a / 2.6);                $rgb = Gfx::mix([12, 28, 60], Gfx::hsv($rp['hue'], 0.45, 1.0), $k * (1 - $n * 0.25));                Gfx::arc($r, $rp['x'], $rp['y'], $rad, 0, 2 * M_PI, 2.2 - $n * 0.4, $rgb, 0.6);            }        }        foreach ($this->drops as $d) {                                        // a drop on its way down            $u = ($this->time - $d['from']) / max(0.01, $d['hit'] - $d['from']);            $y = 120 + ($d['y'] - 120) * $u * $u;            Gfx::blob($r, $d['x'], $y, 2.3, 4.2, M_PI / 2, Gfx::hsv($d['hue'], 0.3, 1.0));        }    }}