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

7.3 KiB

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-control-points Control Points 10_Wiki/Topics verified self
Capture Points
KOTH
Domination
none A 0.85 applied
game-design
multiplayer
objectives
fps
2026-05-10 pending
language framework
csharp-gdscript 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

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

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

// 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)

{
  "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)

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

🤖 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