--- id: wiki-2026-0508-combat-system-bullet-pipeline title: Combat System & Bullet Interaction Pipeline category: 10_Wiki/Topics status: verified canonical_id: self aliases: [bullet hell, combat system, object pool, broad-phase, narrow-phase, collision detection, homing missile] duplicate_of: none source_trust_level: B confidence_score: 0.85 verification_status: applied tags: [game-design, combat, collision-detection, object-pool, spatial-partition, bullet-hell, performance] raw_sources: [] last_reinforced: 2026-05-10 github_commit: pending tech_stack: language: TypeScript / C++ framework: Game engine --- # Combat System & Bullet Pipeline ## 매 한 줄 > **"매 thousands of bullet 의 60 FPS"**. 매 object pool + 매 broad-phase (spatial grid / quadtree) + 매 narrow-phase (circle-circle / OBB). 매 modern: GPU bullet (compute shader). 매 bullet hell + STG + arena game 의 backbone. ## 매 핵심 ### 매 architecture - **CombatSystem**: 매 hub. - **BulletPool**: 매 reuse. - **SpatialPartition**: 매 broad-phase. - **CollisionResolver**: 매 narrow-phase. - **HomingController**. - **DamageEvent**. ### Object pooling - 매 instantiate / destroy 의 GC pressure. - 매 N pre-create + reuse. - 매 typical: 매 1000-10000 bullet pool. ### Spatial partitioning (broad-phase) #### Uniform Grid - 매 simple, 매 fast. - 매 bullet hell 의 typical. - 매 cell size = 매 max bullet radius × 2. #### Quadtree - 매 sparse 의 better. - 매 dynamic. #### Spatial Hash - 매 grid 의 hash version. - 매 unbounded space. #### BVH - 매 raytracing-style. - 매 complex shape. ### Narrow-phase #### Circle-Circle - 매 distance < r1 + r2. - 매 fastest. #### AABB - 매 axis-aligned box. - 매 bullet hell 의 enough. #### OBB - 매 oriented box. - 매 rotation 의 sensitive. #### SAT (Separating Axis Theorem) - 매 polygon-polygon. ### Homing missile - 매 target acquisition. - 매 angular velocity 의 limit. - 매 turn rate. ### Bullet pattern (danmaku) - 매 spiral, fan, ring, aimed, random spread. - 매 emitter 의 parameter. ### 매 modern (GPU bullet) - 매 compute shader 의 1M+ bullet. - 매 [[Compute Shader]] 참조. ### 매 hit feedback - 매 visual: hitstop, screen shake, particle. - 매 audio: 매 spatial. - 매 game feel 의 make-or-break. ## 💻 패턴 ### Object pool ```ts class BulletPool { private pool: Bullet[] = []; private active: Set = new Set(); constructor(size: number) { for (let i = 0; i < size; i++) { this.pool.push(new Bullet()); } } spawn(pos: Vec2, vel: Vec2): Bullet | null { const b = this.pool.pop(); if (!b) return null; // 매 pool exhausted b.reset(pos, vel); this.active.add(b); return b; } release(b: Bullet) { this.active.delete(b); this.pool.push(b); } } ``` ### Uniform Grid (broad-phase) ```ts class SpatialGrid { private cells = new Map(); constructor(private cellSize: number) {} private key(x: number, y: number): string { return `${Math.floor(x / this.cellSize)},${Math.floor(y / this.cellSize)}`; } rebuild(bullets: Bullet[]) { this.cells.clear(); for (const b of bullets) { const k = this.key(b.pos.x, b.pos.y); if (!this.cells.has(k)) this.cells.set(k, []); this.cells.get(k)!.push(b); } } nearby(pos: Vec2): Bullet[] { const result: Bullet[] = []; const cx = Math.floor(pos.x / this.cellSize); const cy = Math.floor(pos.y / this.cellSize); for (let dx = -1; dx <= 1; dx++) { for (let dy = -1; dy <= 1; dy++) { const k = `${cx + dx},${cy + dy}`; if (this.cells.has(k)) result.push(...this.cells.get(k)!); } } return result; } } ``` ### Circle-Circle (narrow) ```ts function circleCircle(a: Vec2, ra: number, b: Vec2, rb: number): boolean { const dx = a.x - b.x; const dy = a.y - b.y; const sumR = ra + rb; return dx * dx + dy * dy < sumR * sumR; // 매 sqrt 의 avoid } ``` ### Homing missile ```ts class HomingMissile { pos: Vec2; vel: Vec2; target: Vec2; maxTurnRate = 0.05; // 매 rad / frame update(dt: number) { const toTarget = this.target.sub(this.pos); const desiredAngle = Math.atan2(toTarget.y, toTarget.x); const currentAngle = Math.atan2(this.vel.y, this.vel.x); let diff = desiredAngle - currentAngle; while (diff > Math.PI) diff -= 2 * Math.PI; while (diff < -Math.PI) diff += 2 * Math.PI; const turn = clamp(diff, -this.maxTurnRate, this.maxTurnRate); const newAngle = currentAngle + turn; const speed = this.vel.length(); this.vel = new Vec2(Math.cos(newAngle) * speed, Math.sin(newAngle) * speed); this.pos = this.pos.add(this.vel.mul(dt)); } } ``` ### Danmaku pattern (spiral) ```ts class SpiralEmitter { angle = 0; fireRate = 0.05; // 매 sec between rotation = 0.1; // 매 rad / fire update(dt: number) { this.fireTimer += dt; while (this.fireTimer > this.fireRate) { this.fireTimer -= this.fireRate; const angle = this.angle; const vx = Math.cos(angle) * 200; const vy = Math.sin(angle) * 200; bulletPool.spawn(this.pos, new Vec2(vx, vy)); this.angle += this.rotation; } } } ``` ### Hitstop (game feel) ```ts class HitStop { remaining = 0; trigger(durationMs: number) { this.remaining = Math.max(this.remaining, durationMs); } shouldUpdateGame(dt: number): boolean { if (this.remaining > 0) { this.remaining -= dt; return false; // 매 freeze game logic } return true; } } // 매 trigger 매 hit onHit() { hitStop.trigger(50); // 매 50ms freeze — 매 impact feel screenShake.trigger(amplitude=5, duration=200); particleSystem.spawn(...); } ``` ### Bullet update loop (60 FPS) ```ts function gameLoop(dt: number) { // 매 1. spawn (emitter) for (const e of emitters) e.update(dt); // 매 2. update bullet positions for (const b of activeBullets) { b.pos = b.pos.add(b.vel.mul(dt)); if (b.pos.outside(screenBounds)) bulletPool.release(b); } // 매 3. broad-phase grid.rebuild([...activeBullets]); // 매 4. narrow-phase for (const player of players) { for (const b of grid.nearby(player.pos)) { if (circleCircle(player.pos, player.radius, b.pos, b.radius)) { player.takeHit(b); bulletPool.release(b); hitStop.trigger(50); } } } // 매 5. render render(activeBullets, players, enemies); } ``` ### GPU bullet (compute shader) ```js // 매 [[Compute Shader]] 참조 // 매 1M bullet 의 GPU 의 update + collision const computeShader = device.createComputePipeline({ compute: { module: bulletUpdateShader, entryPoint: 'main' }, }); // 매 매 frame const pass = encoder.beginComputePass(); pass.setPipeline(computeShader); pass.dispatchWorkgroups(Math.ceil(N_BULLETS / 64)); pass.end(); ``` ### Damage event ```ts class DamageEvent { constructor( public source: Bullet, public target: Entity, public amount: number, public type: DamageType, // 매 burst, sustain, area ) {} apply() { const reduced = this.target.applyResistance(this.amount, this.type); this.target.hp -= reduced; return reduced; } } ``` ## 🤔 결정 기준 | 상황 | Approach | |---|---| | 100s of bullet | Uniform Grid + circle | | 1000s | Uniform Grid + tight loop | | 100K+ | GPU compute shader | | Static obstacle | BVH / quadtree | | Polygon collision | SAT | | Homing | Angular velocity limit | | Game feel | Hitstop + shake + particle | **기본값**: Object pool + Uniform Grid + Circle-Circle + Hitstop. ## 🔗 Graph - 부모: [[Game-Design]] · [[Game-Engine]] - 변형: [[Object-Pool]] · [[Collision-Detection]] · [[Bullet-Hell]] - 응용: [[Compute Shader]] · [[Boss-Orchestration-and-Gimmick-Management]] · [[Combat Controls Update (Feb 2014)]] - Adjacent: [[Baiting]] · [[BioShock (2007)]] · [[Combined Arms (제병협동) 전술]] ## 🤖 LLM 활용 **언제**: 매 game design. 매 STG / bullet hell. 매 arena combat. 매 performance optimization. **언제 X**: 매 turn-based (different mechanic). 매 puzzle. ## ❌ 안티패턴 - **No object pool**: 매 GC stutter. - **N² collision check**: 매 100+ object 의 fail. - **Cell size 의 too small / large**: 매 inefficient. - **No hitstop**: 매 hits 의 weightless. - **Sub-frame collision miss**: 매 fast bullet 의 tunneling — 매 swept check. - **No bullet visual cleanup**: 매 visual clutter. ## 🧪 검증 / 중복 - Verified (Real-Time Collision Detection (Ericson), bullet hell game design). - 신뢰도 B. - Related: [[Compute Shader]] · [[Boss-Orchestration-and-Gimmick-Management]] · [[Game-Design]] · [[CSS Animations]] (game feel). ## 🕓 Changelog | 날짜 | 변경 | |---|---| | 2026-05-08 | Phase 1 | | 2026-05-10 | Manual cleanup — Actor-Proxy + grid + 매 pool / homing / spiral / hitstop / GPU code |