animations = [ 'idle' => ['flags' => [], 'coords' => self::frames([$big ? self::BIG : self::SMALL])], 'pop' => ['flags' => [], 'coords' => self::frames([self::SMALL])], ]; $this->setAnimation('idle'); } public function isBreathable(): bool { return $this->big && $this->popTimer === 0 && $this->active; } /** Breathed by Sonic: shows the small "pop" sprite for 16 frames then vanishes. */ public function pop(): void { $this->popTimer = 16; $this->setAnimation('pop'); } /** Hitbox of the big bubble (10x12). */ public function intersects(array $rect, int $shrink = 0): bool { $x = $this->x + 3; $y = $this->y + 2; return $x < $rect['x'] + $rect['width'] && $x + 10 > $rect['x'] && $y < $rect['y'] + $rect['height'] && $y + 12 > $rect['y']; } public function think(float $deltaTime, ?float $waterLine): void { $this->clock += $deltaTime; while ($this->clock >= self::FRAME && $this->active) { $this->clock -= self::FRAME; if ($this->popTimer > 0) { if (--$this->popTimer === 0) { $this->active = false; } continue; } if ($this->frames % 16 === 0) { $this->vx = (mt_rand(0, 63) < 32 ? 0x20 : -0x20) / 256; } $this->frames++; $this->x += $this->vx; $this->y -= ($this->big ? 0x68 : 0x60) / 256; if ($waterLine !== null && $this->y <= $waterLine) { $this->active = false; } } } }