Cycle dense clouds with changing wind
This commit is contained in:
parent
cc4011ff25
commit
96b042a4c2
@ -44,6 +44,9 @@ const PLAYER_SMOKE_ENGINE_OFFSET = 17;
|
|||||||
const ROLLING_CLOUD_COUNT = 34;
|
const ROLLING_CLOUD_COUNT = 34;
|
||||||
const ROLLING_CLOUD_WRAP_MARGIN = 180;
|
const ROLLING_CLOUD_WRAP_MARGIN = 180;
|
||||||
const DENSE_CLOUD_MIN_COUNT = 8;
|
const DENSE_CLOUD_MIN_COUNT = 8;
|
||||||
|
const DENSE_CLOUD_MIN_SCALE = 2.2;
|
||||||
|
const DENSE_CLOUD_MAX_SCALE = 3.9;
|
||||||
|
const DENSE_CLOUD_CONCEALMENT_RADIUS_SCALE = 82;
|
||||||
const CLOUD_FIRE_SUPPRESSION_CONCEALMENT = 0.55;
|
const CLOUD_FIRE_SUPPRESSION_CONCEALMENT = 0.55;
|
||||||
const CLOUD_CLOSE_ENGAGE_RANGE = 180;
|
const CLOUD_CLOSE_ENGAGE_RANGE = 180;
|
||||||
const AUTO_RESET_MS = 2200;
|
const AUTO_RESET_MS = 2200;
|
||||||
@ -210,15 +213,15 @@ class GameScene extends Phaser.Scene {
|
|||||||
const denseCloud = i < DENSE_CLOUD_MIN_COUNT || Phaser.Math.Between(0, 5) === 0;
|
const denseCloud = i < DENSE_CLOUD_MIN_COUNT || Phaser.Math.Between(0, 5) === 0;
|
||||||
const foreground = denseCloud || Phaser.Math.Between(0, 4) === 0;
|
const foreground = denseCloud || Phaser.Math.Between(0, 4) === 0;
|
||||||
const baseScale = denseCloud
|
const baseScale = denseCloud
|
||||||
? Phaser.Math.FloatBetween(1.55, 2.85)
|
? Phaser.Math.FloatBetween(DENSE_CLOUD_MIN_SCALE, DENSE_CLOUD_MAX_SCALE)
|
||||||
: Phaser.Math.FloatBetween(0.85, 2.35);
|
: Phaser.Math.FloatBetween(0.85, 2.35);
|
||||||
const alphaMin = denseCloud
|
const alphaMin = denseCloud
|
||||||
? Phaser.Math.FloatBetween(0.34, 0.46)
|
? Phaser.Math.FloatBetween(0.4, 0.52)
|
||||||
: foreground
|
: foreground
|
||||||
? Phaser.Math.FloatBetween(0.08, 0.13)
|
? Phaser.Math.FloatBetween(0.08, 0.13)
|
||||||
: Phaser.Math.FloatBetween(0.025, 0.07);
|
: Phaser.Math.FloatBetween(0.025, 0.07);
|
||||||
const alphaMax = denseCloud
|
const alphaMax = denseCloud
|
||||||
? Phaser.Math.FloatBetween(0.64, 0.82)
|
? Phaser.Math.FloatBetween(0.72, 0.9)
|
||||||
: foreground
|
: foreground
|
||||||
? Phaser.Math.FloatBetween(0.18, 0.3)
|
? Phaser.Math.FloatBetween(0.18, 0.3)
|
||||||
: Phaser.Math.FloatBetween(0.08, 0.16);
|
: Phaser.Math.FloatBetween(0.08, 0.16);
|
||||||
@ -243,11 +246,18 @@ class GameScene extends Phaser.Scene {
|
|||||||
cloud.setData('alphaRate', Phaser.Math.FloatBetween(0.00018, 0.00048));
|
cloud.setData('alphaRate', Phaser.Math.FloatBetween(0.00018, 0.00048));
|
||||||
cloud.setData('rollPhase', Phaser.Math.FloatBetween(0, Math.PI * 2));
|
cloud.setData('rollPhase', Phaser.Math.FloatBetween(0, Math.PI * 2));
|
||||||
cloud.setData('driftScale', Phaser.Math.FloatBetween(0.55, 1.35));
|
cloud.setData('driftScale', Phaser.Math.FloatBetween(0.55, 1.35));
|
||||||
cloud.setData('concealmentRadius', denseCloud ? baseScale * 58 : 0);
|
cloud.setData(
|
||||||
|
'concealmentRadius',
|
||||||
|
denseCloud ? baseScale * DENSE_CLOUD_CONCEALMENT_RADIUS_SCALE : 0,
|
||||||
|
);
|
||||||
cloud.setData(
|
cloud.setData(
|
||||||
'concealmentStrength',
|
'concealmentStrength',
|
||||||
denseCloud ? Phaser.Math.FloatBetween(0.82, 0.98) : 0,
|
denseCloud ? Phaser.Math.FloatBetween(0.82, 0.98) : 0,
|
||||||
);
|
);
|
||||||
|
cloud.setData('presence', denseCloud ? Phaser.Math.FloatBetween(0.25, 1) : 1);
|
||||||
|
cloud.setData('weatherCycleMs', Phaser.Math.Between(18000, 36000));
|
||||||
|
cloud.setData('weatherPhase', Phaser.Math.FloatBetween(0, Math.PI * 2));
|
||||||
|
cloud.setData('weatherCycleIndex', -1);
|
||||||
|
|
||||||
this.rollingClouds.push(cloud);
|
this.rollingClouds.push(cloud);
|
||||||
}
|
}
|
||||||
@ -274,7 +284,28 @@ class GameScene extends Phaser.Scene {
|
|||||||
const alphaMin = cloud.getData('alphaMin') || 0.04;
|
const alphaMin = cloud.getData('alphaMin') || 0.04;
|
||||||
const alphaMax = cloud.getData('alphaMax') || 0.14;
|
const alphaMax = cloud.getData('alphaMax') || 0.14;
|
||||||
const visibility = 0.5 + 0.5 * Math.sin(now * rate + phase);
|
const visibility = 0.5 + 0.5 * Math.sin(now * rate + phase);
|
||||||
cloud.setAlpha(Phaser.Math.Linear(alphaMin, alphaMax, visibility));
|
|
||||||
|
let presence = 1;
|
||||||
|
if (cloud.getData('denseCloud')) {
|
||||||
|
const weatherCycleMs = cloud.getData('weatherCycleMs') || 26000;
|
||||||
|
const weatherPhase = cloud.getData('weatherPhase') || 0;
|
||||||
|
const cyclePosition = (now % weatherCycleMs) / weatherCycleMs;
|
||||||
|
const weatherWave = 0.5 + 0.5 * Math.sin(cyclePosition * Math.PI * 2 + weatherPhase);
|
||||||
|
const windPresence = clamp((wind.strength - 5) / 18, 0, 1);
|
||||||
|
const targetPresence = clamp(weatherWave * 0.72 + windPresence * 0.38 - 0.12, 0, 1);
|
||||||
|
const previousPresence = cloud.getData('presence') ?? targetPresence;
|
||||||
|
presence = Phaser.Math.Linear(previousPresence, targetPresence, Math.min(1, dt * 0.7));
|
||||||
|
cloud.setData('presence', presence);
|
||||||
|
|
||||||
|
const cycleIndex = Math.floor(now / weatherCycleMs);
|
||||||
|
const previousCycleIndex = cloud.getData('weatherCycleIndex');
|
||||||
|
if (presence < 0.06 && cycleIndex !== previousCycleIndex) {
|
||||||
|
this.reseedDenseCloudUpwind(cloud, wind);
|
||||||
|
cloud.setData('weatherCycleIndex', cycleIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cloud.setAlpha(Phaser.Math.Linear(alphaMin, alphaMax, visibility) * presence);
|
||||||
|
|
||||||
const baseScale = cloud.getData('baseScale') || 1;
|
const baseScale = cloud.getData('baseScale') || 1;
|
||||||
const rollPhase = cloud.getData('rollPhase') || 0;
|
const rollPhase = cloud.getData('rollPhase') || 0;
|
||||||
@ -289,6 +320,24 @@ class GameScene extends Phaser.Scene {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
reseedDenseCloudUpwind(cloud, wind) {
|
||||||
|
const magnitude = Math.max(1, Math.hypot(wind?.x || 0, wind?.y || 0));
|
||||||
|
const windX = (wind?.x || 1) / magnitude;
|
||||||
|
const windY = (wind?.y || 0) / magnitude;
|
||||||
|
const margin = ROLLING_CLOUD_WRAP_MARGIN;
|
||||||
|
const crossOffset = Phaser.Math.Between(-Math.round(WORLD_SIZE * 0.45), Math.round(WORLD_SIZE * 0.45));
|
||||||
|
const centerX = WORLD_SIZE / 2;
|
||||||
|
const centerY = WORLD_SIZE / 2;
|
||||||
|
const sideX = -windY;
|
||||||
|
const sideY = windX;
|
||||||
|
|
||||||
|
cloud.x = centerX - windX * (WORLD_SIZE / 2 + margin) + sideX * crossOffset;
|
||||||
|
cloud.y = centerY - windY * (WORLD_SIZE / 2 + margin) + sideY * crossOffset;
|
||||||
|
cloud.setData('driftScale', Phaser.Math.FloatBetween(0.65, 1.45));
|
||||||
|
cloud.setData('weatherPhase', Phaser.Math.FloatBetween(0, Math.PI * 2));
|
||||||
|
}
|
||||||
|
|
||||||
cloudConcealmentAt(x, y) {
|
cloudConcealmentAt(x, y) {
|
||||||
if (!this.rollingClouds?.length) return 0;
|
if (!this.rollingClouds?.length) return 0;
|
||||||
|
|
||||||
@ -304,7 +353,8 @@ class GameScene extends Phaser.Scene {
|
|||||||
|
|
||||||
const radialStrength = 1 - (distance / radius);
|
const radialStrength = 1 - (distance / radius);
|
||||||
const strength = cloud.getData('concealmentStrength') || 0;
|
const strength = cloud.getData('concealmentStrength') || 0;
|
||||||
concealment = Math.max(concealment, radialStrength * strength);
|
const presence = cloud.getData('presence') ?? 1;
|
||||||
|
concealment = Math.max(concealment, radialStrength * strength * presence);
|
||||||
}
|
}
|
||||||
|
|
||||||
return clamp(concealment, 0, 1);
|
return clamp(concealment, 0, 1);
|
||||||
|
|||||||
@ -260,21 +260,23 @@ test('environment physics adds wind, soft island collision, turbulence, and proj
|
|||||||
assert.match(source, /windSpeed: this\.environmentWind\?\.strength/);
|
assert.match(source, /windSpeed: this\.environmentWind\?\.strength/);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('rolling clouds include dense concealment banks that drift with wind', async () => {
|
test('rolling clouds include large dense banks that phase in and out with the wind', async () => {
|
||||||
const source = await read('src/game-v4.js');
|
const source = await read('src/game-v4.js');
|
||||||
|
|
||||||
assert.match(source, /const ROLLING_CLOUD_COUNT = 34/);
|
assert.match(source, /const ROLLING_CLOUD_COUNT = 34/);
|
||||||
assert.match(source, /const DENSE_CLOUD_MIN_COUNT = 8/);
|
assert.match(source, /const DENSE_CLOUD_MIN_COUNT = 8/);
|
||||||
assert.match(source, /this\.createRollingClouds\(\)/);
|
assert.match(source, /const DENSE_CLOUD_MIN_SCALE = 2\.2/);
|
||||||
assert.match(source, /const denseCloud = i < DENSE_CLOUD_MIN_COUNT/);
|
assert.match(source, /const DENSE_CLOUD_MAX_SCALE = 3\.9/);
|
||||||
assert.match(source, /setDepth\(denseCloud \? 8 : foreground \? 7 : 2\)/);
|
assert.match(source, /const DENSE_CLOUD_CONCEALMENT_RADIUS_SCALE = 82/);
|
||||||
assert.match(source, /Phaser\.BlendModes\.NORMAL : Phaser\.BlendModes\.SCREEN/);
|
assert.match(source, /Phaser\.Math\.FloatBetween\(DENSE_CLOUD_MIN_SCALE, DENSE_CLOUD_MAX_SCALE\)/);
|
||||||
assert.match(source, /cloud\.setData\('concealmentRadius', denseCloud \? baseScale \* 58 : 0\)/);
|
assert.match(source, /cloud\.setData\(\s*'concealmentRadius',[\s\S]*?DENSE_CLOUD_CONCEALMENT_RADIUS_SCALE/);
|
||||||
assert.match(source, /cloud\.setData\(\s*'concealmentStrength'/);
|
assert.match(source, /cloud\.setData\('weatherCycleMs', Phaser\.Math\.Between\(18000, 36000\)\)/);
|
||||||
assert.match(source, /this\.updateRollingClouds\(deltaMs\)/);
|
assert.match(source, /const windPresence = clamp\(\(wind\.strength - 5\) \/ 18, 0, 1\)/);
|
||||||
assert.match(source, /const wind = environmentWindAt\(\{/);
|
assert.match(source, /const targetPresence = clamp\(weatherWave \* 0\.72 \+ windPresence \* 0\.38 - 0\.12, 0, 1\)/);
|
||||||
assert.match(source, /cloud\.x \+= wind\.x \* driftScale \* dt/);
|
assert.match(source, /cloud\.setAlpha\(Phaser\.Math\.Linear\(alphaMin, alphaMax, visibility\) \* presence\)/);
|
||||||
assert.match(source, /cloud\.setAlpha\(Phaser\.Math\.Linear\(alphaMin, alphaMax, visibility\)\)/);
|
assert.match(source, /this\.reseedDenseCloudUpwind\(cloud, wind\)/);
|
||||||
|
assert.match(source, /cloud\.x = centerX - windX \* \(WORLD_SIZE \/ 2 \+ margin\)/);
|
||||||
|
assert.match(source, /cloud\.y = centerY - windY \* \(WORLD_SIZE \/ 2 \+ margin\)/);
|
||||||
assert.doesNotMatch(source, /const clouds = this\.add\.graphics\(\)/);
|
assert.doesNotMatch(source, /const clouds = this\.add\.graphics\(\)/);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -288,6 +290,8 @@ test('dense clouds conceal ships and suppress long-range pirate fire', async ()
|
|||||||
assert.match(source, /this\.player\.setAlpha\(Phaser\.Math\.Linear\(1, 0\.3, playerConcealment\)\)/);
|
assert.match(source, /this\.player\.setAlpha\(Phaser\.Math\.Linear\(1, 0\.3, playerConcealment\)\)/);
|
||||||
assert.match(source, /pirate\.setAlpha\(Phaser\.Math\.Linear\(1, 0\.36, concealment\)\)/);
|
assert.match(source, /pirate\.setAlpha\(Phaser\.Math\.Linear\(1, 0\.36, concealment\)\)/);
|
||||||
assert.match(source, /this\.player\.setData\('cloudConcealment', playerConcealment\)/);
|
assert.match(source, /this\.player\.setData\('cloudConcealment', playerConcealment\)/);
|
||||||
|
assert.match(source, /const presence = cloud\.getData\('presence'\) \?\? 1/);
|
||||||
|
assert.match(source, /radialStrength \* strength \* presence/);
|
||||||
assert.match(source, /const targetObscured = playerConcealment >= CLOUD_FIRE_SUPPRESSION_CONCEALMENT/);
|
assert.match(source, /const targetObscured = playerConcealment >= CLOUD_FIRE_SUPPRESSION_CONCEALMENT/);
|
||||||
assert.match(source, /distance > CLOUD_CLOSE_ENGAGE_RANGE/);
|
assert.match(source, /distance > CLOUD_CLOSE_ENGAGE_RANGE/);
|
||||||
assert.match(source, /if \(!targetObscured && distance < broadsideRange/);
|
assert.match(source, /if \(!targetObscured && distance < broadsideRange/);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user