--- 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 Progress = new(0f); public NetworkVariable OwnerTeam = new(-1); [SerializeField] private float captureRate = 10f; // pct/sec per attacker [SerializeField] private float maxRate = 20f; private readonly Dictionary teamCount = new(); private void OnTriggerEnter(Collider other) { if (!IsServer) return; if (other.TryGetComponent(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(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>(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 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 |