Add rolling wind-driven clouds
This commit is contained in:
parent
f3eb0dfaaf
commit
82eef3f26e
@ -41,6 +41,8 @@ const MAX_PIRATES = 6;
|
||||
const HUD_PUBLISH_INTERVAL_MS = 50;
|
||||
const PLAYER_SMOKE_REAR_OFFSET = 48;
|
||||
const PLAYER_SMOKE_ENGINE_OFFSET = 17;
|
||||
const ROLLING_CLOUD_COUNT = 34;
|
||||
const ROLLING_CLOUD_WRAP_MARGIN = 180;
|
||||
const AUTO_RESET_MS = 2200;
|
||||
|
||||
const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
|
||||
@ -101,6 +103,7 @@ class GameScene extends Phaser.Scene {
|
||||
create() {
|
||||
this.physics.world.setBounds(0, 0, WORLD_SIZE, WORLD_SIZE);
|
||||
this.drawWorld();
|
||||
this.createRollingClouds();
|
||||
this.createAnimations();
|
||||
|
||||
this.player = this.physics.add.sprite(1200, 1550, 'player-airship', 0);
|
||||
@ -184,6 +187,90 @@ class GameScene extends Phaser.Scene {
|
||||
|
||||
|
||||
|
||||
|
||||
createRollingClouds() {
|
||||
if (!this.textures.exists('rolling-cloud')) {
|
||||
const cloudTexture = this.make.graphics({ x: 0, y: 0, add: false });
|
||||
cloudTexture.fillStyle(0xe7f0f4, 0.34);
|
||||
cloudTexture.fillEllipse(64, 42, 92, 46);
|
||||
cloudTexture.fillStyle(0xf5f8fa, 0.26);
|
||||
cloudTexture.fillCircle(42, 38, 27);
|
||||
cloudTexture.fillCircle(67, 29, 31);
|
||||
cloudTexture.fillCircle(88, 40, 25);
|
||||
cloudTexture.generateTexture('rolling-cloud', 128, 84);
|
||||
cloudTexture.destroy();
|
||||
}
|
||||
|
||||
this.rollingClouds = [];
|
||||
for (let i = 0; i < ROLLING_CLOUD_COUNT; i++) {
|
||||
const foreground = Phaser.Math.Between(0, 4) === 0;
|
||||
const baseScale = Phaser.Math.FloatBetween(0.85, 2.35);
|
||||
const alphaMin = foreground
|
||||
? Phaser.Math.FloatBetween(0.08, 0.13)
|
||||
: Phaser.Math.FloatBetween(0.025, 0.07);
|
||||
const alphaMax = foreground
|
||||
? Phaser.Math.FloatBetween(0.18, 0.3)
|
||||
: Phaser.Math.FloatBetween(0.08, 0.16);
|
||||
const cloud = this.add.image(
|
||||
Phaser.Math.Between(-ROLLING_CLOUD_WRAP_MARGIN, WORLD_SIZE + ROLLING_CLOUD_WRAP_MARGIN),
|
||||
Phaser.Math.Between(-ROLLING_CLOUD_WRAP_MARGIN, WORLD_SIZE + ROLLING_CLOUD_WRAP_MARGIN),
|
||||
'rolling-cloud',
|
||||
);
|
||||
|
||||
cloud
|
||||
.setDepth(foreground ? 7 : 2)
|
||||
.setScale(baseScale)
|
||||
.setAlpha(alphaMin)
|
||||
.setBlendMode(Phaser.BlendModes.SCREEN);
|
||||
|
||||
cloud.setData('foreground', foreground);
|
||||
cloud.setData('baseScale', baseScale);
|
||||
cloud.setData('alphaMin', alphaMin);
|
||||
cloud.setData('alphaMax', alphaMax);
|
||||
cloud.setData('alphaPhase', Phaser.Math.FloatBetween(0, Math.PI * 2));
|
||||
cloud.setData('alphaRate', Phaser.Math.FloatBetween(0.00018, 0.00048));
|
||||
cloud.setData('rollPhase', Phaser.Math.FloatBetween(0, Math.PI * 2));
|
||||
cloud.setData('driftScale', Phaser.Math.FloatBetween(0.55, 1.35));
|
||||
|
||||
this.rollingClouds.push(cloud);
|
||||
}
|
||||
}
|
||||
|
||||
updateRollingClouds(deltaMs) {
|
||||
if (!this.rollingClouds?.length) return;
|
||||
|
||||
const dt = Math.min(deltaMs / 1000, 0.05);
|
||||
const now = this.time?.now ?? 0;
|
||||
|
||||
for (const cloud of this.rollingClouds) {
|
||||
const wind = environmentWindAt({
|
||||
x: cloud.x,
|
||||
y: cloud.y,
|
||||
timeMs: now,
|
||||
});
|
||||
const driftScale = cloud.getData('driftScale') || 1;
|
||||
cloud.x += wind.x * driftScale * dt;
|
||||
cloud.y += wind.y * driftScale * dt;
|
||||
|
||||
const phase = cloud.getData('alphaPhase') || 0;
|
||||
const rate = cloud.getData('alphaRate') || 0.0003;
|
||||
const alphaMin = cloud.getData('alphaMin') || 0.04;
|
||||
const alphaMax = cloud.getData('alphaMax') || 0.14;
|
||||
const visibility = 0.5 + 0.5 * Math.sin(now * rate + phase);
|
||||
cloud.setAlpha(Phaser.Math.Linear(alphaMin, alphaMax, visibility));
|
||||
|
||||
const baseScale = cloud.getData('baseScale') || 1;
|
||||
const rollPhase = cloud.getData('rollPhase') || 0;
|
||||
const roll = 1 + Math.sin(now * 0.00022 + rollPhase) * 0.045;
|
||||
cloud.setScale(baseScale * roll, baseScale / roll);
|
||||
|
||||
if (cloud.x > WORLD_SIZE + ROLLING_CLOUD_WRAP_MARGIN) cloud.x = -ROLLING_CLOUD_WRAP_MARGIN;
|
||||
if (cloud.y > WORLD_SIZE + ROLLING_CLOUD_WRAP_MARGIN) cloud.y = -ROLLING_CLOUD_WRAP_MARGIN;
|
||||
if (cloud.x < -ROLLING_CLOUD_WRAP_MARGIN) cloud.x = WORLD_SIZE + ROLLING_CLOUD_WRAP_MARGIN;
|
||||
if (cloud.y < -ROLLING_CLOUD_WRAP_MARGIN) cloud.y = WORLD_SIZE + ROLLING_CLOUD_WRAP_MARGIN;
|
||||
}
|
||||
}
|
||||
|
||||
createEnvironmentPhysics() {
|
||||
if (!this.textures.exists('environment-collider')) {
|
||||
const colliderTexture = this.make.graphics({ x: 0, y: 0, add: false });
|
||||
@ -476,17 +563,6 @@ class GameScene extends Phaser.Scene {
|
||||
for (let x = 0; x <= WORLD_SIZE; x += 100) bg.lineBetween(x, 0, x, WORLD_SIZE);
|
||||
for (let y = 0; y <= WORLD_SIZE; y += 100) bg.lineBetween(0, y, WORLD_SIZE, y);
|
||||
|
||||
const clouds = this.add.graphics();
|
||||
for (let i = 0; i < 46; i++) {
|
||||
const x = Phaser.Math.Between(80, WORLD_SIZE - 80);
|
||||
const y = Phaser.Math.Between(80, WORLD_SIZE - 80);
|
||||
const r = Phaser.Math.Between(24, 62);
|
||||
clouds.fillStyle(0xbcd2df, Phaser.Math.FloatBetween(0.025, 0.08));
|
||||
clouds.fillCircle(x, y, r);
|
||||
clouds.fillCircle(x + r * 0.55, y + 8, r * 0.7);
|
||||
clouds.fillCircle(x - r * 0.45, y + 5, r * 0.6);
|
||||
}
|
||||
|
||||
const island = this.add.graphics();
|
||||
island.fillStyle(0x5d694e, 1);
|
||||
island.fillEllipse(430, 480, 230, 150);
|
||||
@ -541,6 +617,7 @@ class GameScene extends Phaser.Scene {
|
||||
|
||||
this.updatePlayer(dt, deltaMs);
|
||||
this.updatePlayerSmoke(deltaMs);
|
||||
this.updateRollingClouds(deltaMs);
|
||||
this.updatePirates(dt);
|
||||
this.updateProjectiles();
|
||||
this.drawWeaponArcs();
|
||||
|
||||
@ -249,6 +249,23 @@ test('environment physics adds wind, soft island collision, turbulence, and proj
|
||||
assert.match(source, /windSpeed: this\.environmentWind\?\.strength/);
|
||||
});
|
||||
|
||||
test('rolling clouds drift with wind and vary visibility over time', async () => {
|
||||
const source = await read('src/game-v4.js');
|
||||
|
||||
assert.match(source, /const ROLLING_CLOUD_COUNT = 34/);
|
||||
assert.match(source, /this\.createRollingClouds\(\)/);
|
||||
assert.match(source, /this\.updateRollingClouds\(deltaMs\)/);
|
||||
assert.match(source, /this\.add\.image\([\s\S]*?'rolling-cloud'/);
|
||||
assert.match(source, /setDepth\(foreground \? 7 : 2\)/);
|
||||
assert.match(source, /setBlendMode\(Phaser\.BlendModes\.SCREEN\)/);
|
||||
assert.match(source, /const wind = environmentWindAt\(\{/);
|
||||
assert.match(source, /cloud\.x \+= wind\.x \* driftScale \* dt/);
|
||||
assert.match(source, /const visibility = 0\.5 \+ 0\.5 \* Math\.sin/);
|
||||
assert.match(source, /cloud\.setAlpha\(Phaser\.Math\.Linear\(alphaMin, alphaMax, visibility\)\)/);
|
||||
assert.match(source, /cloud\.setScale\(baseScale \* roll, baseScale \/ roll\)/);
|
||||
assert.doesNotMatch(source, /const clouds = this\.add\.graphics\(\)/);
|
||||
});
|
||||
|
||||
test('mobile HUD skips hidden Phaser rendering and throttles DOM state publishing', async () => {
|
||||
const source = await read('src/game-v4.js');
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user