diff --git a/agent.md b/agent.md index 2d85314..0f6b638 100644 --- a/agent.md +++ b/agent.md @@ -1,3 +1,10 @@ +# Update: Variable Meteor Scale + +- Fire and frost world-effect meteors now randomize their size on each drop. +- `WORLD_EFFECT.SIZE_SCALE_VARIANCE` controls the per-drop random range around the base size. +- The same random multiplier is applied to both the damage/frost zone bounds and the falling/impact sprite scale. +- Meteor screen shake scales from the same size multiplier, with base values in `WORLD_EFFECT.METEOR_SHAKE_DURATION_MS` and `WORLD_EFFECT.METEOR_SHAKE_INTENSITY`. + # Update: Team Size Constants - The battle setup team-size limit is centralized in `SPAWN.MAX_TEAM_SIZE` inside `src/constants.js`. diff --git a/context/combat.md b/context/combat.md index bc3fd55..94ce159 100644 --- a/context/combat.md +++ b/context/combat.md @@ -1,3 +1,10 @@ +# Update: Variable Meteor Scale + +- `worldEffects.js` resolves a fresh size multiplier for every fire/frost meteor drop. +- Tune the base damage/frost zone with `WORLD_EFFECT.AREA_TILES`, the base sprite size with `WORLD_EFFECT.VISUAL_SCALE`, and the shared random spread with `WORLD_EFFECT.SIZE_SCALE_VARIANCE`. +- The same multiplier changes both the damage/frost zone bounds and the falling/impact sprite scale. +- Meteor impact shake uses the same size multiplier, scaling from `WORLD_EFFECT.METEOR_SHAKE_DURATION_MS` and `WORLD_EFFECT.METEOR_SHAKE_INTENSITY`. + # Update: Large Battle Targeting - `combat.js` now prepares a per-frame target spatial index through `prepareCombatFrame(scene)`. @@ -43,8 +50,8 @@ - **효과**: 메테오 투하 주기가 `SUDDEN_DEATH.INTERVAL_MS`로 단축되며, `FORCE_FROST` 설정 시 빙결 효과를 가진 냉기 메테오가 집중적으로 생성됩니다. - **목적**: 장기전을 방지하고 전장에 무작위 변수를 극대화하여 물량 중심 팀에게 리스크를 부여합니다. - **낙하 방향과 크기**: 대상이 전장 좌측 반면(2, 3사분면)이면 화살표가 좌상단에서 우하단으로, 우측 반면(1, 4사분면)이면 좌우 반전되어 우상단에서 좌하단으로 이동합니다. 스프라이트를 45도로 기울이고 전용 시각 배율을 사용해 전역 마법 규모로 표현합니다. -- **화염 메테오**: `world_Effect.png`의 7프레임 애니메이션이 낙하하면 화면 흔들림을 적용하고, 5x5 타일 영역의 생존자에게 고정 피해를 줍니다. 자동 관전 진입 전에는 `CAMERA.METEOR_FOCUS_ENABLED`가 켜져 있을 때 착탄 위치를 임시 포커싱합니다. 환경 피해로 인한 사망은 킬 보상을 지급하지 않지만 사망 통계와 승패 판정에는 반영됩니다. -- **냉기 메테오**: `world_Effect_2.png`의 7프레임 애니메이션으로 착탄을 표시하고, 자동 관전 진입 전에는 같은 설정에 따라 착탄 위치를 임시 포커싱합니다. 착탄 시 별도 조정 가능한 피해를 주며, 생존한 피격 대상은 캐릭터 본체와 실루엣이 얼음색으로 바뀐 채 2초 동안 기절합니다. 이후 남은 5x5 냉각지대 안에서는 공격속도와 이동속도 감속 배율을 적용하며, 영역을 벗어나거나 지속시간이 끝나면 배율을 복구합니다. +- **화염 메테오**: `world_Effect.png`의 7프레임 애니메이션이 낙하하면 크기에 따른 화면 흔들림을 적용하고, 5x5 타일 영역의 생존자에게 고정 피해를 줍니다. 자동 관전 진입 전에는 `CAMERA.METEOR_FOCUS_ENABLED`가 켜져 있을 때 착탄 위치를 임시 포커싱합니다. 환경 피해로 인한 사망은 킬 보상을 지급하지 않지만 사망 통계와 승패 판정에는 반영됩니다. +- **냉기 메테오**: `world_Effect_2.png`의 7프레임 애니메이션으로 착탄을 표시하고 크기에 따른 화면 흔들림을 적용하며, 자동 관전 진입 전에는 같은 설정에 따라 착탄 위치를 임시 포커싱합니다. 착탄 시 별도 조정 가능한 피해를 주며, 생존한 피격 대상은 캐릭터 본체와 실루엣이 얼음색으로 바뀐 채 2초 동안 기절합니다. 이후 남은 5x5 냉각지대 안에서는 공격속도와 이동속도 감속 배율을 적용하며, 영역을 벗어나거나 지속시간이 끝나면 배율을 복구합니다. ### 최종교전 슬로우모션 `COMBAT.FINAL_SLOW_MOTION_ENABLED`가 활성화된 경우: diff --git a/context/core.md b/context/core.md index 806e7cd..aa3aa87 100644 --- a/context/core.md +++ b/context/core.md @@ -1,5 +1,11 @@ # Context: Core & Infrastructure +# Update: Variable Meteor Scale + +- `WORLD_EFFECT.SIZE_SCALE_VARIANCE` randomizes each fire/frost meteor drop around the base size. +- The randomized size applies to both the world-effect damage/frost zone bounds from `WORLD_EFFECT.AREA_TILES` and the sprite scale from `WORLD_EFFECT.VISUAL_SCALE`. +- Meteor impact shake strength follows the same size multiplier, using `WORLD_EFFECT.METEOR_SHAKE_DURATION_MS` and `WORLD_EFFECT.METEOR_SHAKE_INTENSITY` as base values. + # Update: Team Size Constants - `SPAWN.MAX_TEAM_SIZE` is the single source of truth for the battle setup team-size maximum. @@ -42,6 +48,6 @@ - **물리 수치 조정**: 역할별 기본 체력/속도/사거리/공격 수치는 `src/constants.js`의 `FIGHTER_TYPE_STATS`에서 변경하고, 특정 스킨만 다르게 할 때는 `fighterManifest.js`의 `stats` 또는 `combat` 설정을 사용하십시오. - **처치 성장 상한 조정**: 처치 보상으로 캐릭터가 커지는 최대치와 공격/이동 배율 상한은 `src/constants.js`의 `KILL_GROWTH_MAX_MULTIPLIER`를 수정합니다. - **공격력 조정**: 역할별 기본 피해량은 `src/constants.js`의 `FIGHTER_TYPE_STATS..damageMin/damageMax`를 수정합니다. 캐릭터별 특수 공격 방식은 `fighterManifest.js`의 `combat` 설정을 우선 확인합니다. -- **월드 이펙트 조정**: `src/constants.js`의 `WORLD_EFFECT.INTERVAL`, `WORLD_EFFECT.FALL_TRAVEL_TILES`, `WORLD_EFFECT.VISUAL_SCALE`, `WORLD_EFFECT.DOMINANCE_TARGETING_MULTIPLIER`, `WORLD_EFFECT.METEOR_DAMAGE`, `WORLD_EFFECT.FROST_DAMAGE`, `WORLD_EFFECT.FROST_STUN_DURATION`, `WORLD_EFFECT.FROST_STUN_TINT`, `WORLD_EFFECT.FROST_DURATION`, `WORLD_EFFECT.FROST_SPEED_MULTIPLIER`를 수정합니다. 독주 표적 배율은 `0`이면 기존 생존 유닛 비례 추첨이며, `1`이면 구매 배수 지분보다 높은 생존 지분의 초과분을 표적 가중치로 반영합니다. 임시 메테오 카메라는 `CAMERA.METEOR_FOCUS_ENABLED`로 끌 수 있습니다. +- **월드 이펙트 조정**: `src/constants.js`의 `WORLD_EFFECT.INTERVAL`, `WORLD_EFFECT.AREA_TILES`, `WORLD_EFFECT.SIZE_SCALE_VARIANCE`, `WORLD_EFFECT.FALL_TRAVEL_TILES`, `WORLD_EFFECT.VISUAL_SCALE`, `WORLD_EFFECT.METEOR_SHAKE_DURATION_MS`, `WORLD_EFFECT.METEOR_SHAKE_INTENSITY`, `WORLD_EFFECT.DOMINANCE_TARGETING_MULTIPLIER`, `WORLD_EFFECT.METEOR_DAMAGE`, `WORLD_EFFECT.FROST_DAMAGE`, `WORLD_EFFECT.FROST_STUN_DURATION`, `WORLD_EFFECT.FROST_STUN_TINT`, `WORLD_EFFECT.FROST_DURATION`, `WORLD_EFFECT.FROST_SPEED_MULTIPLIER`를 수정합니다. 독주 표적 배율은 `0`이면 기존 생존 유닛 비례 추첨이며, `1`이면 구매 배수 지분보다 높은 생존 지분의 초과분을 표적 가중치로 반영합니다. 임시 메테오 카메라는 `CAMERA.METEOR_FOCUS_ENABLED`로 끌 수 있습니다. - **DOM 접근**: 성능을 위해 `ArenaScene`은 좌측 HUD badge 등 필요한 시점에만 최소한으로 DOM에 접근합니다. - **패키지 락 파일**: 이 프로젝트는 `package-lock.json`을 저장소에서 제외합니다. 의존성 변경 시 `package.json`을 기준으로 관리합니다. diff --git a/src/constants.js b/src/constants.js index 542c2ec..2989456 100644 --- a/src/constants.js +++ b/src/constants.js @@ -132,11 +132,14 @@ export const PROJECTILE = { export const WORLD_EFFECT = { INTERVAL: 4000, AREA_TILES: 15, + SIZE_SCALE_VARIANCE: 3, FRAMES: 7, FRAME_RATE: 14, FALL_DURATION: 920, FALL_TRAVEL_TILES: 8, VISUAL_SCALE: 50, + METEOR_SHAKE_DURATION_MS: 150, + METEOR_SHAKE_INTENSITY: 0.004, // 0 keeps target selection proportional to living units. // 1 adds pressure when a team's living share exceeds its paid spawn share. DOMINANCE_TARGETING_MULTIPLIER: 0.5, @@ -144,7 +147,7 @@ export const WORLD_EFFECT = { FROST_DAMAGE: 45, FROST_STUN_DURATION: 2000, FROST_STUN_TINT: 0x82e9ff, - FROST_DURATION: 20000, + FROST_DURATION: 2000, FROST_SPEED_MULTIPLIER: 0.55, SUDDEN_DEATH: { ENABLED: false, diff --git a/src/game/combat/worldEffects.js b/src/game/combat/worldEffects.js index b27d2d3..b87e5b9 100644 --- a/src/game/combat/worldEffects.js +++ b/src/game/combat/worldEffects.js @@ -125,7 +125,7 @@ function triggerWorldEffect(scene) { } const target = chooseWorldEffectTarget(livingFighters); - const zone = createEffectZone(target); + const zone = createEffectZone(target, resolveWorldEffectSizeScale()); // Sudden Death 상태이고 냉기 고정 설정이 되어있으면 무조건 냉기 메테오 if ((scene.isSuddenDeath && WORLD_EFFECT.SUDDEN_DEATH.FORCE_FROST) || Phaser.Math.Between(0, 1) === 0) { @@ -218,7 +218,7 @@ function spawnMeteor(scene, zone) { onImpact: () => { scene.tweens.killTweensOf(marker); marker.setAlpha(1); - scene.cameras.main.shake(150, 0.004); + applyMeteorImpactShake(scene, zone); resolveImpactDamage(scene, zone, WORLD_EFFECT.METEOR_DAMAGE); }, onAnimationComplete: () => { @@ -239,6 +239,7 @@ function spawnFrostZone(scene, zone) { disposeCombatObject(scene, marker); }, onImpact: () => { + applyMeteorImpactShake(scene, zone); resolveImpactDamage(scene, zone, WORLD_EFFECT.FROST_DAMAGE, (fighter) => { applyFrostStun(scene, fighter); }); @@ -261,7 +262,7 @@ function dropWorldEffectSprite( const sprite = scene.add .sprite(trajectory.startX, trajectory.startY, effectKey, 0) .setDepth(3) - .setScale(WORLD_EFFECT.VISUAL_SCALE) + .setScale(resolveWorldEffectVisualScale(zone)) .setFlipX(trajectory.flipX) .setAngle(trajectory.angle) .setAlpha(0.9); @@ -300,6 +301,40 @@ function worldEffectAnimationKey(effectKey) { return `${effectKey}-anim`; } +function resolveWorldEffectSizeScale() { + const variance = Math.max(0, Number(WORLD_EFFECT.SIZE_SCALE_VARIANCE) || 0); + + if (variance === 0) { + return 1; + } + + const minScale = Math.max(0.1, 1 - variance); + const maxScale = 1 + variance; + + return Phaser.Math.FloatBetween(minScale, maxScale); +} + +function resolveWorldEffectVisualScale(zone) { + const baseScale = Math.max(0.01, Number(WORLD_EFFECT.VISUAL_SCALE) || 1); + return baseScale * Math.max(0.1, Number(zone?.sizeScale) || 1); +} + +function applyMeteorImpactShake(scene, zone) { + const sizeScale = Math.max(0.1, Number(zone?.sizeScale) || 1); + const duration = Math.round( + Math.max(0, Number(WORLD_EFFECT.METEOR_SHAKE_DURATION_MS) || 0) + * Math.sqrt(sizeScale), + ); + const intensity = + Math.max(0, Number(WORLD_EFFECT.METEOR_SHAKE_INTENSITY) || 0) * sizeScale; + + if (duration <= 0 || intensity <= 0) { + return; + } + + scene.cameras.main.shake(duration, intensity); +} + function createFallTrajectory(zone) { const distance = ARENA.TILE_SIZE * WORLD_EFFECT.FALL_TRAVEL_TILES; const isLeftHalf = zone.centerX < ARENA.SIZE / 2; @@ -313,16 +348,22 @@ function createFallTrajectory(zone) { }; } -function createEffectZone(target) { - const size = ARENA.TILE_SIZE * WORLD_EFFECT.AREA_TILES; +function createEffectZone(target, sizeScale = 1) { + const areaTiles = Math.max( + 1, + (Number(WORLD_EFFECT.AREA_TILES) || 1) * Math.max(0.1, Number(sizeScale) || 1), + ); + const size = ARENA.TILE_SIZE * areaTiles; const centerX = target.body?.center.x ?? target.x; const centerY = target.body?.center.y ?? target.y; return { + areaTiles, bounds: new Phaser.Geom.Rectangle(centerX - size / 2, centerY - size / 2, size, size), centerX, centerY, marker: null, + sizeScale, }; } @@ -336,7 +377,7 @@ function createZoneMarker(scene, zone, color) { marker.strokeRect(x, y, width, height); marker.lineStyle(1, color, 0.34); - for (let index = 1; index < WORLD_EFFECT.AREA_TILES; index += 1) { + for (let index = 1; index < zone.areaTiles; index += 1) { const offset = index * ARENA.TILE_SIZE; marker.lineBetween(x + offset, y, x + offset, y + height); marker.lineBetween(x, y + offset, x + width, y + offset); diff --git a/todo.md b/todo.md index b40aae7..f3486fd 100644 --- a/todo.md +++ b/todo.md @@ -229,7 +229,7 @@ - **조치 사항**: - 생존 캐릭터가 30명 미만이거나 최종 2팀만 남으면 후반 자동 줌과 교전 중심 포커싱이 시작되도록 관전 조건을 확장. - 치명타의 `Critical!` 표기와 즉시 처치는 유지하면서 카메라 흔들림을 제거. - - 화염 메테오가 착탄할 때 화면 흔들림을 적용하고, 냉각지대 착탄과 피해 계산에서는 흔들림을 분리. + - 화염 메테오 착탄 화면 흔들림을 먼저 적용했으며, 이후 모든 메테오 착탄이 크기 기반 흔들림을 공유하도록 확장. 37. 자동 관전 이전 메테오 임시 포커싱 추가 (완료) - **조치 사항**: @@ -285,3 +285,9 @@ - Added `FIGHTER.DEAD_DESPAWN_DELAY_MS` in `src/constants.js` so corpse lifetime is easy to tune. - Updated `combat.js` to keep a dead fighter at initial opacity, fade it toward `FIGHTER.DEAD_DESPAWN_ALPHA`, then remove it from `scene.fighters` and destroy the sprite after the configured delay. - Kept death bookkeeping, kill rewards, split-on-death, and winner checks ahead of the despawn schedule. + +46. Variable meteor impact and visual scale (completed) +- **Changes**: + - Added `WORLD_EFFECT.SIZE_SCALE_VARIANCE` so each fire/frost meteor drop can pick a different size multiplier. + - Updated `worldEffects.js` to apply the same per-drop multiplier to both the damage/frost zone bounds and the falling/impact sprite scale. + - Added `WORLD_EFFECT.METEOR_SHAKE_DURATION_MS` and `WORLD_EFFECT.METEOR_SHAKE_INTENSITY`, then scaled fire/frost meteor camera shake from the meteor size multiplier.