Files
2nd/10_Wiki/Topics/Architecture/Control-Points.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

228 lines
7.3 KiB
Markdown

---
id: wiki-2026-0508-control-points
title: Control Points
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [Capture Points, KOTH, Domination]
duplicate_of: none
source_trust_level: A
confidence_score: 0.85
verification_status: applied
tags: [game-design, multiplayer, objectives, fps]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: csharp-gdscript
framework: unity-godot-unreal
---
# Control Points
## 매 한 줄
> **"매 spatial objective — 매 team 이 매 zone 의 occupy 통해 score/win."**. Control Points는 multiplayer game 의 가장 ubiquitous objective primitive. Domination, KOTH, Capture-and-Hold, Push, Hardpoint 모두 변형. Team Fortress 2, Battlefield, Overwatch, Apex, CS Bombsite (variant), Splatoon (area-based) 의 핵심. 2026년 server-authoritative netcode + lag compensation 패턴 매 stable.
## 매 핵심
### 매 variants
| Variant | Mechanic |
|---|---|
| **Domination** | Multiple points, hold majority for tickets/score |
| **KOTH** | Single point, hold-time wins |
| **Capture & Hold** | Capture sequentially, last team standing |
| **Hardpoint** | Single rotating point, score over time |
| **Push/Payload** | Mobile control point along track |
| **Linear (5CP TF2)** | Sequential capture, central pivot |
### 매 mechanics
- **Capture progress**: 0~100%, increases with attackers in zone.
- **Multi-capture rate**: more attackers → faster (capped, e.g. 2x at 2+).
- **Contest**: defenders inside → progress paused.
- **Lock/unlock**: previous point capture unlocks next.
- **Decay**: progress drops when zone empty (configurable).
- **Overtime**: contested point prevents game end.
### 매 design principles
- **Sightline balance**: defenders 의 advantage 와 attacker chokes 의 균형.
- **Capture time**: too short → trivial, too long → stalemate. 5-15s typical.
- **Cap-zone size**: encourages clustering vs spread.
- **Spawn distance**: defender respawn 가 너무 가까우면 attack 불가.
### 매 응용
1. FPS multiplayer modes (Overwatch, BF, CoD).
2. MOBA jungle camps / objectives (Roshan, Drake areas).
3. RTS resource nodes (StarCraft expansions).
4. MMO PvP zones (WoW battlegrounds).
## 💻 패턴
### Unity C# — control point trigger
```csharp
using UnityEngine;
using Unity.Netcode;
public class ControlPoint : NetworkBehaviour {
public NetworkVariable<float> Progress = new(0f);
public NetworkVariable<int> OwnerTeam = new(-1);
[SerializeField] private float captureRate = 10f; // pct/sec per attacker
[SerializeField] private float maxRate = 20f;
private readonly Dictionary<int, int> teamCount = new();
private void OnTriggerEnter(Collider other) {
if (!IsServer) return;
if (other.TryGetComponent<Player>(out var p)) {
teamCount.TryGetValue(p.Team, out var n);
teamCount[p.Team] = n + 1;
}
}
private void OnTriggerExit(Collider other) {
if (!IsServer) return;
if (other.TryGetComponent<Player>(out var p)
&& teamCount.TryGetValue(p.Team, out var n)) {
teamCount[p.Team] = Mathf.Max(0, n - 1);
}
}
private void FixedUpdate() {
if (!IsServer) return;
var teams = new List<KeyValuePair<int,int>>(teamCount);
teams.Sort((a, b) => b.Value.CompareTo(a.Value));
if (teams.Count == 0 || teams[0].Value == 0) return;
// Contested: top two teams equal & nonzero
if (teams.Count > 1 && teams[0].Value == teams[1].Value) return;
var attacker = teams[0];
var rate = Mathf.Min(maxRate, captureRate * Mathf.Sqrt(attacker.Value));
if (OwnerTeam.Value == attacker.Key) {
Progress.Value = Mathf.Min(100f, Progress.Value + rate * Time.fixedDeltaTime);
} else {
Progress.Value = Mathf.Max(0f, Progress.Value - rate * Time.fixedDeltaTime);
if (Progress.Value <= 0f) OwnerTeam.Value = attacker.Key;
}
}
}
```
### Godot 4 / GDScript
```gdscript
extends Area3D
class_name ControlPoint
@export var capture_rate: float = 10.0
var progress: float = 0.0
var owner_team: int = -1
var team_in_zone: Dictionary = {}
func _physics_process(delta: float) -> void:
if not multiplayer.is_server(): return
var top_team := -1
var top_count := 0
var contested := false
for team in team_in_zone:
var n: int = team_in_zone[team]
if n > top_count:
top_count = n
top_team = team
contested = false
elif n == top_count and n > 0:
contested = true
if contested or top_count == 0: return
var rate = capture_rate * sqrt(top_count)
if owner_team == top_team:
progress = min(100.0, progress + rate * delta)
else:
progress = max(0.0, progress - rate * delta)
if progress <= 0.0:
owner_team = top_team
_notify_capture(top_team)
```
### Server-authoritative state with lag compensation
```csharp
// Server stores state snapshots for last 1s
private readonly CircularBuffer<Snapshot> history = new(60);
public bool WasInsideAtTime(Vector3 playerPos, float clientTime) {
var snap = history.SampleAt(clientTime);
return snap.Bounds.Contains(playerPos);
}
```
### Configurable progression (data-driven)
```json
{
"id": "cp_central",
"captureTimeSeconds": 8,
"multiCapMultiplier": [1.0, 1.5, 1.75, 2.0],
"decayRate": 0.5,
"unlockedBy": ["cp_a", "cp_b"],
"scoresPerSecond": 10
}
```
### UI broadcast (client-side prediction visual)
```typescript
// Client receives Progress NetworkVariable changes,
// interpolates between ticks for smooth bar fill
useEffect(() => {
const start = performance.now();
const startVal = displayedProgress;
const targetVal = serverProgress;
const tick = () => {
const t = Math.min(1, (performance.now() - start) / 100);
setDisplayedProgress(startVal + (targetVal - startVal) * t);
if (t < 1) requestAnimationFrame(tick);
};
tick();
}, [serverProgress]);
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| Casual fast-match | KOTH, single-point, 3-5 min rounds |
| Competitive | Linear/5CP, longer rounds, overtime |
| Asymmetric | Push/Payload (attack vs defend) |
| Objective rotation | Hardpoint (rotating zone keeps action moving) |
| Large maps | Domination (multiple distributed) |
**기본값**: server-authoritative + 5-10s capture + multi-cap multiplier + decay + contested-pause + overtime.
## 🔗 Graph
- 변형: [[Domination]]
- Adjacent: [[Lag Compensation]]
## 🤖 LLM 활용
**언제**: multiplayer mode design, level layout review, balance tuning, netcode design for objectives.
**언제 X**: single-player, asynchronous (turn-based), pure deathmatch (no objective).
## ❌ 안티패턴
- **Client-authoritative capture**: trivially exploitable — server-side only.
- **Spawn too close to objective**: defender immortal — distance + lockout window.
- **No contested-pause**: solo defender can't stall — feels unfair.
- **Capture too short**: zerg wins, no skill — 8-12s standard.
- **No decay**: half-cap then leave is permanent — partial progress decay.
## 🧪 검증 / 중복
- Verified (Valve TF2 design / Blizzard Overwatch dev blogs / GDC talks 2018-2024).
- 신뢰도 A-.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — variants + Unity/Godot impl + netcode |