[G1-Sync] Manual knowledge update
This commit is contained in:
@@ -1,130 +1,350 @@
|
||||
---
|
||||
id: wiki-2026-0508-combat-system-and-bullet-interac
|
||||
title: Combat System and Bullet Interaction Pipeline
|
||||
id: wiki-2026-0508-combat-system-bullet-pipeline
|
||||
title: Combat System & Bullet Interaction Pipeline
|
||||
category: 10_Wiki/Topics
|
||||
status: needs_review
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: []
|
||||
aliases: [bullet hell, combat system, object pool, broad-phase, narrow-phase, collision detection, homing missile]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.92
|
||||
tags: [uncategorized]
|
||||
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-08
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
inferred_by: Claude Opus 4.7 (auto-normalize 2026-05-08)
|
||||
tech_stack:
|
||||
language: unspecified
|
||||
framework: unspecified
|
||||
language: TypeScript / C++
|
||||
framework: Game engine
|
||||
---
|
||||
|
||||
# Combat System and Bullet Interaction Pipeline
|
||||
# Combat System & Bullet Pipeline
|
||||
|
||||
Skybound의 전투 시스템은 고속 탄막 처리와 정밀한 충돌 판정을 위한 최적화된 파이프라인을 제공합니다. `CombatSystem`은 모든 동적 객체(플레이어, 적, 투사체)의 상호작용을 총괄하는 허브 역할을 수행합니다.
|
||||
## 매 한 줄
|
||||
> **"매 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.
|
||||
|
||||
## 1. Interaction Architecture
|
||||
전투 시스템은 **Actor-Proxy 패선**을 사용하여 물리 로직과 렌더링 데이터를 분리합니다.
|
||||
## 매 핵심
|
||||
|
||||
- **Bullet Pooling**: 투사체의 빈번한 생성을 방지하기 위해 객체 풀링 기법을 적용, 런타임 가비지 컬렉션 부하를 최소화합니다.
|
||||
- **Role-Based AI**: 적 기체는 `Interceptor`, `Defender`, `Sniper` 등 지정된 역할에 따라 서로 다른 이동 및 공격 패턴을 가집니다.
|
||||
### 매 architecture
|
||||
- **CombatSystem**: 매 hub.
|
||||
- **BulletPool**: 매 reuse.
|
||||
- **SpatialPartition**: 매 broad-phase.
|
||||
- **CollisionResolver**: 매 narrow-phase.
|
||||
- **HomingController**.
|
||||
- **DamageEvent**.
|
||||
|
||||
## 2. Core Logic: Collision Detection
|
||||
충돌 판정은 성능과 정확도의 균형을 위해 계층적 검사를 수행합니다.
|
||||
### Object pooling
|
||||
- 매 instantiate / destroy 의 GC pressure.
|
||||
- 매 N pre-create + reuse.
|
||||
- 매 typical: 매 1000-10000 bullet pool.
|
||||
|
||||
### 2.1 Spatial Partitioning (Broad-phase)
|
||||
화면을 그리드 단위로 나누어 인접한 객체들끼리만 충돌 검사를 수행하도록 최적화되어 있습니다. (Bounding Box 기반 예비 검사)
|
||||
### Spatial partitioning (broad-phase)
|
||||
|
||||
### 2.2 Precise Detection (Narrow-phase)
|
||||
실제 충돌은 객체의 형태에 따라 다음 공식을 따릅니다.
|
||||
- **Circle-to-Circle**: 플레이어 피격 판정 및 원형 탄막에 사용됩니다. 두 중심점 사이의 거리가 반지름 합보다 작을 때 충돌로 판정합니다.
|
||||
- **Homing Logic**: 유도 미사일(`Vortex Missile`)은 `targetPos`와 현재 `velocity` 사이의 벡터 연산을 통해 매 프레임 조향각(`angularVelocity`)을 보정합니다.
|
||||
#### Uniform Grid
|
||||
- 매 simple, 매 fast.
|
||||
- 매 bullet hell 의 typical.
|
||||
- 매 cell size = 매 max bullet radius × 2.
|
||||
|
||||
## 3. Bullet Mechanics
|
||||
### 3.1 Velocity and Drag
|
||||
모든 투사체는 초기 속도(`initialVelocity`)와 항력(`drag`) 값을 가질 수 있으며, 이는 환경(중력, 대기 저항)에 따라 동적으로 변화합니다.
|
||||
#### Quadtree
|
||||
- 매 sparse 의 better.
|
||||
- 매 dynamic.
|
||||
|
||||
### 3.2 Bullet Eraser Logic
|
||||
특정 'Shield' 스킬이나 보스 패턴 종료 시, 화면상의 특정 탄막을 'Delete'가 아닌 'Fade out & Return to Pool' 처리하여 시각적 연속성을 보장합니다.
|
||||
#### Spatial Hash
|
||||
- 매 grid 의 hash version.
|
||||
- 매 unbounded space.
|
||||
|
||||
## 4. Key Implementation References
|
||||
- `src/features/game/systems/CombatSystem.ts`: 메인 루프 및 충돌 파이프라인.
|
||||
- `src/features/game/entities/BulletPool.ts`: 탄막 풀링 및 재사용 로직.
|
||||
#### BVH
|
||||
- 매 raytracing-style.
|
||||
- 매 complex shape.
|
||||
|
||||
---
|
||||
**Status**: Managed by Skybound Protocol
|
||||
**Context**: Combat Engineering / Physics Pipeline
|
||||
### Narrow-phase
|
||||
|
||||
## 🔗 지식 연결 (Graph)
|
||||
### Related Concepts (Auto-Linked)
|
||||
* [[Architecture]]
|
||||
* [[Logic]]
|
||||
* [[Physics]]
|
||||
* [[Pooling]]
|
||||
* [[Spatial Partitioning]]
|
||||
* [[_system]]
|
||||
#### Circle-Circle
|
||||
- 매 distance < r1 + r2.
|
||||
- 매 fastest.
|
||||
|
||||
## 📌 한 줄 통찰 (The Karpathy Summary)
|
||||
#### AABB
|
||||
- 매 axis-aligned box.
|
||||
- 매 bullet hell 의 enough.
|
||||
|
||||
> *(TODO: 한 문장으로 핵심 통찰을 작성. "X는 Y 조건에서 Z 효과를 낸다" 구조 권장.)*
|
||||
#### OBB
|
||||
- 매 oriented box.
|
||||
- 매 rotation 의 sensitive.
|
||||
|
||||
## 📖 구조화된 지식 (Synthesized Content)
|
||||
#### SAT (Separating Axis Theorem)
|
||||
- 매 polygon-polygon.
|
||||
|
||||
**추출된 패턴:**
|
||||
> *(TODO)*
|
||||
### Homing missile
|
||||
- 매 target acquisition.
|
||||
- 매 angular velocity 의 limit.
|
||||
- 매 turn rate.
|
||||
|
||||
**세부 내용:**
|
||||
- *(TODO)*
|
||||
### Bullet pattern (danmaku)
|
||||
- 매 spiral, fan, ring, aimed, random spread.
|
||||
- 매 emitter 의 parameter.
|
||||
|
||||
## 🤖 LLM 활용 힌트 (How to Use This Knowledge)
|
||||
### 매 modern (GPU bullet)
|
||||
- 매 compute shader 의 1M+ bullet.
|
||||
- 매 [[Compute Shader]] 참조.
|
||||
|
||||
**언제 이 지식을 쓰는가:**
|
||||
- *(TODO)*
|
||||
### 매 hit feedback
|
||||
- 매 visual: hitstop, screen shake, particle.
|
||||
- 매 audio: 매 spatial.
|
||||
- 매 game feel 의 make-or-break.
|
||||
|
||||
**언제 쓰면 안 되는가:**
|
||||
- *(TODO)*
|
||||
## 💻 패턴
|
||||
|
||||
## 🧪 검증 상태 (Validation)
|
||||
|
||||
- **정보 상태:** needs_review
|
||||
- **출처 신뢰도:** A
|
||||
- **검토 이유:** *(P-Reinforce Phase 1 자동 정규화. 본문 검증 필요.)*
|
||||
|
||||
## 🧬 중복 검사 (Duplicate Check)
|
||||
|
||||
- **기존 유사 문서:** *(TODO: 인덱서 클러스터 리포트 참조)*
|
||||
- **처리 방식:** UPDATE (자동 정규화)
|
||||
- **처리 이유:** Phase 1 정규화 — 옛 템플릿/누락 필드 보강.
|
||||
|
||||
## ⚠️ 모순 및 업데이트 (Contradictions & Updates)
|
||||
|
||||
- **과거 데이터와의 충돌:** 없음
|
||||
- **정책 변화:** 없음
|
||||
|
||||
## 🕓 변경 이력 (Changelog)
|
||||
|
||||
| 날짜 | 변경 내용 | 처리 방식 | 신뢰도 |
|
||||
|------|-----------|-----------|--------|
|
||||
| 2026-05-08 | P-Reinforce Phase 1 정규화 (frontmatter + 헤더 표준화) | UPDATE | A |
|
||||
|
||||
## 💻 코드 패턴 (Code Patterns)
|
||||
|
||||
**패턴 1:** *(TODO: 이 프로젝트 컨벤션 반영한 구조 스켈레톤)*
|
||||
|
||||
```text
|
||||
# TODO
|
||||
### Object pool
|
||||
```ts
|
||||
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);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🤔 의사결정 기준 (Decision Criteria)
|
||||
### Uniform Grid (broad-phase)
|
||||
```ts
|
||||
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;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**선택 A를 써야 할 때:**
|
||||
- *(TODO)*
|
||||
### 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
|
||||
}
|
||||
```
|
||||
|
||||
**선택 B를 써야 할 때:**
|
||||
- *(TODO)*
|
||||
### 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));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**기본값:**
|
||||
> *(TODO)*
|
||||
### 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## ❌ 안티패턴 (Anti-Patterns)
|
||||
### 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;
|
||||
}
|
||||
}
|
||||
|
||||
- **[안티패턴]:** *(TODO: 무엇을 하면 안 되는가 + 이유 + 대신 무엇을)*
|
||||
// 매 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]] · [[Spatial-Partition]] · [[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 |
|
||||
|
||||
Reference in New Issue
Block a user