19 KiB
19 KiB
Update: Aggregate Combat Worker Path
- Large-battle detached aggregate combat now tries to run through
src/game/combat/aggregateCombatWorker.jsbefore falling back to the synchronous aggregate path. - The main thread sends Transferable TypedArrays for detached model ids, position, HP, team key, movement speed, DPS, and frost state; Phaser objects and team/skin references stay on the main thread.
- Worker results are applied only when the match id still matches and the model is still detached, preventing stale async results from overwriting visible/detail fighters.
- Stale worker ids whose models have already been unregistered are skipped before reading model fields.
- The main thread still performs
killFighterModel()for worker-reported deaths so split-on-death, kill rewards, death stats, scoreboard updates, and match completion stay on the existing authoritative path.
Update: Magic Attack Effect Pooling
spawnSpellEffect()now acquires instant-spell visual sprites from a small per-texture pool and returns them when their attack animation completes.- Pooled spell effects are reset on reuse for texture frame, position, scale, depth, alpha, rotation, flip, active/visible state, and animation-complete listeners.
clearCombatObjects()now disposes throughdisposeCombatObject(), allowing active pooled spell effects to be returned during match cleanup while non-pooled projectiles, labels, heal effects, and world effects keep their destroy path.- Only magic/instant-spell visuals were pooled here; projectile hit objects and meteor/world-effect objects remain on their existing lifecycle.
Update: Squad-Based Detached Combat
- Large-battle detached models are grouped into transient squads by arena cell, team id, and
PERFORMANCE.LARGE_BATTLE_AGGREGATE_SQUAD_SIZE. - Squad AI does nearest-opposing-squad movement and group DPS resolution, then writes surviving members back into deterministic spiral slots around the squad center.
- This removes per-frame movement/target AI for thousands of offscreen models; individual
updateFighterModel()stays reserved for attached/detail fighters in the rolling camera window. - In large battles the combat target spatial index is built from attached/detail models, not the full model list, so visible individual AI no longer reintroduces an 8,000-model target scan.
Update: Aggregate Detached Combat
updateAggregateDetachedCombat()handles large-battle detached model-only fighters as coarse cell groups instead of invoking fullupdateFighterModel()AI for each offscreen model.- Every-frame work for detached models is now simple movement toward the nearest enemy aggregate cell; target scanning, attack windup, projectile scheduling, and animation locks are reserved for attached/detail fighters.
- Aggregate damage is computed from group attack DPS on a throttled interval and applied to real
FighterModelHP, so deaths, kill rewards, split-on-death, death stats, and winner checks remain tied to the existing combat state. - Aggregate kills pass
silentLog: trueto the model death path to avoid large offscreen death batches flooding the DOM kill log.
Update: Large Battle Combat Frame Throttles
prepareCombatFrame()now syncs model positions fromfighterByModelIdonly, so detached/offscreen sprite records are not scanned just to no-op position sync.- Large battles reuse the target spatial index for
PERFORMANCE.LARGE_BATTLE_TARGET_INDEX_REFRESH_MSinstead of rebuilding the full 8,000-model grid every frame. - The defensive model-index audit now runs once per second instead of every frame.
Update: Null-Safe Model Target Cache
resolveTargetEnemyModel()now clears staletargetModelIdvalues when the cached model can no longer be resolved or is no longer a living enemy.isValidEnemyTargetModel()now null-checks both attacker and candidate models before reading team ids, preventing a removed/dead cached target from crashing the update loop.
Update: Combat With Detached Render Sprites
prepareCombatFrame()now syncs model position only from attached sprites; detached sprites are skipped so model-only movement remains authoritative.fighterForModelId()may now returnnullfor living fighters outside the rolling-window detail set, which intentionally routes movement, attacks, damage, and death through the model-only fallback.- The target spatial index still builds from
scene.fighterModels; itslivingFighterscompatibility list now represents attached render sprites only, whilelivingModelsremains the full combat list. - Model-only death asks
ArenaScene.removeDetachedFighterProxyForModel()to remove the parked fighter entry, andlivingFighterProxyCount()prevents any remaining dead entries from being re-registered.
Update: Model-Only Combat Fallback
updateFighterModel()no longer requires an attached Phaser sprite to keep a living fighter model moving and fighting.- If a render sprite exists, movement, animation, projectiles, and death presentation keep using the existing Sprite/Arcade path.
- If no render sprite exists, movement updates model
x/ydirectly, attacks schedule delayed model hits, damage writes to model HP, and death unregisters the model fromArenaSceneindexes immediately. - Projectile and instant-spell model-only attacks preserve windup/effect/travel timing, but skip visual projectile/spell objects.
- Kill reward and split-on-death can now run from model state, so offscreen sprite detachment does not stop combat resolution.
Update: Model-Based Targeting And Spatial Index
ArenaScene.update()now iteratesscene.fighterModelsand callsupdateFighterModel()instead of driving combat directly from the sprite array.prepareCombatFrame()still syncs active sprite positions into models, but the target spatial index is built from model records and stores model entries in each grid cell.- Target caching moved to
model.targetModelId; validation checks model liveness and team identity before resolving the render sprite throughscene.fighterForModelId(). combatTargetIndexnow exposeslivingModelsas the primary model list while keepinglivingFightersas an attached-sprite compatibility list.- Attack execution, animation, projectiles, and HUD-facing effects still use sprites when they exist; detached participants resolve through the model-only path.
Update: FighterModel Position Sync In Combat
prepareCombatFrame()now syncs each sprite's current render position into itsFighterModelbefore building the target spatial index.- Dormant/offscreen fighters keep advancing model
x/ythroughfighterAdapter.moveFighterToward()while visible fighters continue to use Arcade movement and sync back into the model on the next combat frame. - Target-grid cell placement and nearest-enemy lookup use model position helpers, keeping the combat path ready for a future model-first update loop.
- Attack execution still resolves a fighter sprite from
targetModelIdfor movement, animation, and hit visuals. Removing that render dependency is a later migration step.
Update: Fighter Adapter In Combat
combat.jsno longer owns fighter render/body helpers locally. It imports fighter position, distance, movement, detail visibility, animation, body-disable, and arena-clamp helpers fromfighterAdapter.js.- Visible fighters still move through Arcade physics, while dormant fighters are advanced by the adapter with JS
x/ymath and arena clamping. - Target selection and camera/world-effect hit points now use adapter position helpers so disabled Arcade bodies do not leave stale centers behind.
- Ranged attacks still render projectiles only when both attacker and defender are detailed; dormant participation resolves through delayed data hits.
Update: Dormant Fighter Combat Simulation
updateFighterModel()acceptsdeltaand manually advances dormant fighters with disabled Arcade bodies using JS position math.- Visible fighters still use
scene.physics.moveToObject()so nearby/on-screen motion keeps the existing Arcade movement behavior. - Attack/hurt animation locks are applied only to detailed fighters. Dormant fighters rely on cooldowns and delayed hit timers instead of animation-complete events.
- Projectile attacks involving dormant fighters resolve as delayed data hits and skip Phaser projectile object creation.
- Hit-point, camera, and world-effect helpers treat disabled bodies as stale and use fighter
x/yinstead.
Update: Projectile And Target Grid Optimization
- Projectile hit detection now relies on
projectilePathHitsDefender()only; it no longer creates one Arcade overlap collider per projectile because the path check already covers fast projectile travel against the defender hit area. - Projectile path/hit-area geometry is reused through module-level scratch objects to avoid repeated
Line/Rectangleallocation during projectile updates. - The per-frame target spatial index now stores cells in a numeric array, avoiding string cell keys and
Mapwrites during every combat frame. clearCombatObjects()also clearsscene.combatTargetIndexso match resets and LOD passes do not briefly reuse stale living-fighter lists.
Update: Dense-Area Meteor Barrage
worldEffects.jsaggregates living fighters on the arena tile grid and uses a summed-area scan to select theWORLD_EFFECT.AREA_TILESsquare with the highest population.- That selected square is a warning/focus area. Each fire or frost event schedules
WORLD_EFFECT.IMPACT_COUNT_MINtoIMPACT_COUNT_MAXsmaller strikes inside it. - Tune the large warning visibility with
WORLD_EFFECT.WARNING_DURATION_MS, actual damage/frost footprints withWORLD_EFFECT.IMPACT_AREA_TILES, sprite size withWORLD_EFFECT.IMPACT_VISUAL_SCALE, strike spacing withWORLD_EFFECT.IMPACT_STAGGER_MS, and per-strike variation withWORLD_EFFECT.SIZE_SCALE_VARIANCE. WORLD_EFFECT.INTERVALsets the delay before the first barrage; subsequent normal barrages useWORLD_EFFECT.REPEAT_INTERVAL, withSUDDEN_DEATH.INTERVAL_MStaking over once sudden death is active.- Meteor impact shake uses the same size multiplier, scaling from
WORLD_EFFECT.METEOR_SHAKE_DURATION_MSandWORLD_EFFECT.METEOR_SHAKE_INTENSITY.
Update: Large Battle Targeting
combat.jsnow prepares a per-frame target spatial index throughprepareCombatFrame(scene).resolveTargetEnemy()keeps valid cached targets until their scan interval expires, then immediately looks up a fresh nearest enemy through the spatial grid.- Nearest enemy lookup searches grid cells outward from the fighter's current cell, with full-array scanning kept only as a fallback when no frame index exists.
- Large-battle corpse cleanup uses
PERFORMANCE.LARGE_BATTLE_FIGHTER_THRESHOLDandPERFORMANCE.LARGE_BATTLE_DEAD_DESPAWN_DELAY_MSfromsrc/constants.js.
Update: Team Shadow Animations And Frost Tint
- Dead fighters keep their death animation/corpse state at initial opacity, then fade out until
combat.jsremoves them fromscene.fightersand destroys the sprite. - Tune that fade/despawn lifetime with
FIGHTER.DEAD_DESPAWN_DELAY_MSand the final alpha withFIGHTER.DEAD_DESPAWN_ALPHAinsrc/constants.js. - Fighter action playback now goes through
fighterAdapter.playFighterAction()/playFighterActionIfNeeded(), which resolve animation keys withensureFighterTeamAnimation()so every action can use the team-shadow baked texture generated from the original spritesheet. - The adapter compares against the team-shadow animation key before replaying an action. This avoids switching back to the original non-team-colored spritesheet when fighters move, attack, take damage, or die.
- Frost stun remains a body tint effect in
worldEffects.js. Since team identity is baked into the floor shadow pixels, there is noteamMarkertint state to update or restore. - The removed
teamMarkerdisplay object means death handling no longer needs to hide or destroy a separate marker. HUD cleanup only owns pooled health-bar objects.
Update: Focused Combat Effects In Large Battles
combat.jsexposes supplemental combat visuals only while a large battle is inside the temporary meteor camera-focus window.- Outside that window, large battles skip critical labels, instant-spell sprites, kill-heal sprites, and kill-growth tweens while retaining the underlying damage and reward calculations.
- World-effect meteor/frost visuals remain visible, and projectile objects remain enabled because projectiles currently participate in hit detection.
- Projectile objects should keep calling
projectilePathHitsDefender()for collision checks instead of adding per-projectile Arcade overlap colliders.
Context: Combat System
1. 모듈별 상세 역할 (src/game/combat/)
combat.js: 전투 AI, 피해 계산, 처치 보상 등 핵심 전투 로직을 담당합니다.fighterStats.js에서 해석한 역할별 수치로 이동, 공격, 투사체 발사 등을 처리합니다.combatSettings.js: 전투 속도 배율 등 런타임 전투 설정을 관리합니다.arenaFinalCombatEffects.js: 최종 교전 시 슬로우 모션 등 연출 효과를 담당합니다. 수학적인 이징(easing) 함수와 물리 시간 배율 계산을 포함합니다.worldEffects.js: 실제 전투에서 설정 주기마다 생존자 밀집 구역을 탐색하고 화염/냉기 소형 메테오 포격을 실행하며, 대각선 낙하 연출, 개별 탄착 판정, 냉기 동결과 감속 구역 수명주기를 처리합니다.
2. 주요 로직 구현 세부 사항
전투 AI 및 유닛 동작
updateFighterModel(): 가장 가까운 적 모델을 찾아 이동하거나 공격하는 유닛 AI의 핵심입니다.applyHit(): 일반 공격 피해량은 공격자의melee/ranged/magic프로필 피해량 범위에서 계산하고, 치명타 적중은Critical!표기와 즉시 처치를 처리합니다.- 역할별 기본값:
src/constants.js의FIGHTER_TYPE_STATS에서 체력, 이동속도, 사거리, 공격 쿨다운, 피해량, 치명타 확률, 발동 지연을 독립적으로 조절합니다. 투사체 속도는ranged, 효과 적중 지연은magic프로필에 포함됩니다. projectilePathHitsDefender(): 투사체가 대상을 스쳐 지나가지 않도록 궤적(Line)과 히트박스(Rectangle) 겹침 검사를 수행합니다.
처치 보상 및 성장
applyKillReward(): 처치한 캐릭터의 체력 회복(현재 체력 30%), 크기 증가, 공격속도/이동속도 배율 증가를 처리합니다. 누적 배율은KILL_GROWTH_MAX_MULTIPLIER로 제한합니다.clampFighterInsideArena(): 처치 성장 중 커진 캐릭터가 전장 바깥으로 나가지 않도록 위치를 보정합니다.
월드 이펙트
- 발동 규칙: 프리뷰가 아닌 실제 전투에서 시작 후 첫 포격은
WORLD_EFFECT.INTERVAL이 지난 뒤 발생하고, 이후 일반 포격은WORLD_EFFECT.REPEAT_INTERVAL간격으로 발생합니다. 각 포격은AREA_TILES크기의 모든 후보 구역을 타일 누적합으로 평가해, 생존 캐릭터가 가장 많이 모인 범위를 선택합니다. 같은 밀도의 후보가 여러 개일 때만 그 후보 사이에서 무작위로 고릅니다. - 포격 판정: 선택된 큰 범위는 경고 표시와 카메라 포커스 대상으로 사용되고, 내부에 투하되는 작은 탄착 영역만 피해, 기절, 냉기 감속을 처리합니다. 이 때문에 넓은 밀집지대를 위협하면서도 영역 전체를 즉시 동일 피해로 덮지 않습니다.
- 서든 데스 (Sudden Death):
- 조건: 매치 시작 후
WORLD_EFFECT.SUDDEN_DEATH.TRIGGER_MS시간이 경과하면 서든 데스 상태에 진입합니다 (활성화 시). - 효과: 메테오 투하 주기가
SUDDEN_DEATH.INTERVAL_MS로 단축되며,FORCE_FROST설정 시 빙결 효과를 가진 냉기 메테오가 집중적으로 생성됩니다. - 목적: 장기전을 방지하고 전장에 무작위 변수를 극대화하여 물량 중심 팀에게 리스크를 부여합니다.
- 조건: 매치 시작 후
- 낙하 방향과 크기: 대상이 전장 좌측 반면(2, 3사분면)이면 화살표가 좌상단에서 우하단으로, 우측 반면(1, 4사분면)이면 좌우 반전되어 우상단에서 좌하단으로 이동합니다. 스프라이트를 45도로 기울이고 전용 시각 배율을 사용해 전역 마법 규모로 표현합니다.
- 화염 메테오:
world_Effect.png의 소형 탄착 애니메이션 3~4개가 밀집 경고 구역 내부로 순차 낙하합니다. 각 탄착은 크기에 따른 화면 흔들림과 개별 영역 고정 피해를 적용합니다. 환경 피해로 인한 사망은 킬 보상을 지급하지 않지만 사망 통계와 승패 판정에는 반영됩니다. - 냉기 메테오:
world_Effect_2.png의 소형 탄착 애니메이션들이 같은 방식으로 낙하합니다. 개별 탄착 피해를 입고 생존한 대상은 얼음색으로 바뀐 채 설정 시간 동안 기절하며, 각 탄착점에 남는 냉각지대 안에서는 공격속도와 이동속도 감속 배율을 적용합니다.
최종교전 슬로우모션
COMBAT.FINAL_SLOW_MOTION_ENABLED가 활성화된 경우:
- 최종교전 상태에서 공격 모션이 시작될 때 전역 time scale을 낮춥니다.
- 진입/유지/복귀 속도 램프(Ease)를 적용합니다.
- Arcade Physics는 timeScale 방향이 반대라 물리 이동에는 역수 배율을 적용합니다.
3. 유지보수 규칙
- 처치 성장 상한:
src/constants.js의KILL_GROWTH_MAX_MULTIPLIER를 수정합니다. - 공격력 조정:
src/constants.js의FIGHTER_TYPE_STATS.<type>.damageMin/damageMax를 수정합니다. - 월드 이펙트 및 서든 데스 조정:
src/constants.js의WORLD_EFFECT.METEOR_DAMAGE와WORLD_EFFECT.FROST_DAMAGE로 피해량을 조정합니다.SUDDEN_DEATH.ENABLED로 서든 데스 활성화 여부를 결정하며,TRIGGER_MS(시작 시간),INTERVAL_MS(주기),FORCE_FROST(냉기 고정) 설정을 변경할 수 있습니다.INTERVAL은 첫 포격까지의 대기 시간,REPEAT_INTERVAL은 이후 일반 포격 주기입니다.AREA_TILES는 밀집도를 검색하고 경고로 표시할 큰 구역이며,WARNING_DURATION_MS는 그 경고 표시 시간입니다.IMPACT_AREA_TILES,IMPACT_COUNT_MIN/IMPACT_COUNT_MAX,IMPACT_STAGGER_MS,IMPACT_VISUAL_SCALE는 내부 소형 포격의 판정 범위, 발수, 간격, 시각 크기를 조정합니다.WORLD_EFFECT.FROST_STUN_DURATION/FROST_STUN_TINT로 동결 시간과 표시 색상을 조정합니다.- 나머지
WORLD_EFFECT.*값으로 발동 주기, 범위, 냉각 지속시간과 감속 정도를 수정하며, 메테오 착탄 위치 포커싱은CAMERA.METEOR_FOCUS_ENABLED에서 켜고 끕니다.
- 특수 규칙: 캐릭터별 특수 공격 방식은
fighterManifest.js의combat설정을 확인합니다.