Files
2nd/10_Wiki/Topics/AI_and_ML/Combat-System-and-Bullet-Interaction-Pipeline.md
T
Antigravity Agent f8b21af4be Wiki cleanup: error-doc removal, dedup merge, link normalization
10_Wiki/Topics 대규모 정리:
- 오류 캡처/미완성 stub 문서 227개 제거
- 교차폴더 중복 43클러스터 병합 (63파일 → redirect)
- 링크명 정규화: 깨진 링크 수정·redirect 직결·개념 매핑 ~2,400건
- 카테고리 MOC 6개 신규 생성
- Graph 섹션 미해결 related-keyword 링크 10,058건 제거

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 23:52:15 +09:00

8.8 KiB
Raw Blame History

id, title, category, status, canonical_id, aliases, duplicate_of, source_trust_level, confidence_score, verification_status, tags, raw_sources, last_reinforced, github_commit, tech_stack
id title category status canonical_id aliases duplicate_of source_trust_level confidence_score verification_status tags raw_sources last_reinforced github_commit tech_stack
wiki-2026-0508-combat-system-bullet-pipeline Combat System & Bullet Interaction Pipeline 10_Wiki/Topics verified self
bullet hell
combat system
object pool
broad-phase
narrow-phase
collision detection
homing missile
none B 0.85 applied
game-design
combat
collision-detection
object-pool
spatial-partition
bullet-hell
performance
2026-05-10 pending
language framework
TypeScript / C++ 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)

매 hit feedback

  • 매 visual: hitstop, screen shake, particle.
  • 매 audio: 매 spatial.
  • 매 game feel 의 make-or-break.

💻 패턴

Object pool

class BulletPool {
  private pool: Bullet[] = [];
  private active: Set<Bullet> = 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)

class SpatialGrid {
  private cells = new Map<string, Bullet[]>();
  
  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)

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

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)

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)

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)

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)

// 매 [[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

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

🤖 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.

🧪 검증 / 중복

🕓 Changelog

날짜 변경
2026-05-08 Phase 1
2026-05-10 Manual cleanup — Actor-Proxy + grid + 매 pool / homing / spiral / hitstop / GPU code