f8b21af4be
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>
164 lines
5.6 KiB
Markdown
164 lines
5.6 KiB
Markdown
---
|
|
id: wiki-2026-0508-beat-saber
|
|
title: Beat Saber
|
|
category: 10_Wiki/Topics
|
|
status: verified
|
|
canonical_id: self
|
|
aliases: [Beat Saber, VR Rhythm Game]
|
|
duplicate_of: none
|
|
source_trust_level: A
|
|
confidence_score: 0.85
|
|
verification_status: applied
|
|
tags: [vr, game-architecture, rhythm-game, unity, ecs]
|
|
raw_sources: []
|
|
last_reinforced: 2026-05-10
|
|
github_commit: pending
|
|
tech_stack:
|
|
language: csharp
|
|
framework: unity,unity-dots,openxr
|
|
---
|
|
|
|
# Beat Saber
|
|
|
|
## 매 한 줄
|
|
> **"매 VR rhythm game 의 architecture 의 reference — 90Hz minimum framerate · sub-20ms motion-to-photon · ECS-style note pool"**. 2018 Beat Games (Meta acq 2019) release, 2026 의 Quest 3/Vision Pro 의 cross-platform mainstream, "framerate 의 holy 의 above 의 nothing" 의 architecture 의 lesson.
|
|
|
|
## 매 핵심
|
|
|
|
### 매 architecture 제약
|
|
- **Framerate floor**: 90 fps (Quest 2) · 120 fps (Quest 3 · Vision Pro).
|
|
- **Motion-to-photon**: < 20 ms.
|
|
- **GC 의 hostile**: 매 frame 의 spike 의 nausea 의 cause → object pool · Burst · Job System.
|
|
- **Determinism**: scoring 의 reproducible — 매 input · note 의 fixed seed.
|
|
|
|
### 매 component
|
|
- **Beatmap loader**: `.dat` JSON parse → preallocated note buffer.
|
|
- **Note spawner**: 매 future 6 sec 의 lookahead, pool 에서 의 pull.
|
|
- **Saber controller**: hand pose tracking + velocity smoothing.
|
|
- **Cut detector**: plane intersection · direction match · score.
|
|
- **Audio sync**: NJS (Note Jump Speed) + offset 의 calibrate.
|
|
|
|
### 매 응용
|
|
1. 매 fitness app (Supernatural, FitXR) 의 rhythm pattern 의 inherit.
|
|
2. Trainer/simulator (medical · drill) 의 timing-critical UX.
|
|
3. Education app (language drill · typing tutor) 의 VR variant.
|
|
|
|
## 💻 패턴
|
|
|
|
### Unity DOTS: note spawn 의 zero-alloc
|
|
```csharp
|
|
[BurstCompile]
|
|
public partial struct NoteSpawnSystem : ISystem
|
|
{
|
|
public void OnUpdate(ref SystemState state)
|
|
{
|
|
var beatmap = SystemAPI.GetSingleton<BeatmapBuffer>();
|
|
var time = SystemAPI.Time.ElapsedTime;
|
|
var ecb = new EntityCommandBuffer(Allocator.Temp);
|
|
|
|
for (int i = beatmap.NextIndex; i < beatmap.Notes.Length; i++)
|
|
{
|
|
var n = beatmap.Notes[i];
|
|
if (n.Time - time > LookaheadSeconds) break;
|
|
|
|
var e = ecb.Instantiate(beatmap.NotePrefab);
|
|
ecb.SetComponent(e, new Translation { Value = n.SpawnPosition });
|
|
ecb.SetComponent(e, new NoteData { CutDirection = n.Direction, HitTime = n.Time });
|
|
beatmap.NextIndex = i + 1;
|
|
}
|
|
|
|
ecb.Playback(state.EntityManager);
|
|
}
|
|
}
|
|
```
|
|
|
|
### Saber-cut detector (plane intersection)
|
|
```csharp
|
|
public bool TryCut(Vector3 saberTip, Vector3 saberBase, Vector3 saberVelocity, NoteData note,
|
|
out CutResult result)
|
|
{
|
|
var plane = new Plane(saberVelocity.normalized, saberBase);
|
|
if (!plane.Raycast(new Ray(note.Position, note.Forward), out float enter)) {
|
|
result = default; return false;
|
|
}
|
|
|
|
float speed = saberVelocity.magnitude;
|
|
float angle = Vector3.Angle(saberVelocity, note.RequiredCutDirection);
|
|
float accuracy = 1f - Mathf.Clamp01(angle / 60f);
|
|
|
|
result = new CutResult {
|
|
Score = Mathf.RoundToInt(speed * accuracy * 115f),
|
|
IsValid = speed > 2f && angle < 60f,
|
|
};
|
|
return result.IsValid;
|
|
}
|
|
```
|
|
|
|
### Audio-visual sync (NJS-based)
|
|
```csharp
|
|
// Note 의 spawn position 의 calc
|
|
float jumpDist = njs * (60f / bpm) * halfJumpDuration * 2f;
|
|
Vector3 spawnPos = playerPosition + Vector3.forward * jumpDist;
|
|
|
|
// Note 의 frame 마다 의 lerp
|
|
float t = (Time.timeAsDouble - spawnTime) / (hitTime - spawnTime);
|
|
note.transform.position = Vector3.Lerp(spawnPos, hitPos, (float)t);
|
|
```
|
|
|
|
### Object pool (zero-GC frame)
|
|
```csharp
|
|
public class NotePool
|
|
{
|
|
readonly Stack<Note> pool = new(256);
|
|
readonly Note prefab;
|
|
|
|
public Note Get() => pool.Count > 0 ? pool.Pop() : Object.Instantiate(prefab);
|
|
public void Return(Note n) {
|
|
n.gameObject.SetActive(false);
|
|
pool.Push(n);
|
|
}
|
|
}
|
|
```
|
|
|
|
### OpenXR foveated rendering hint (Quest 3)
|
|
```csharp
|
|
var feature = OpenXRSettings.Instance.GetFeature<FoveationFeature>();
|
|
feature.foveatedRenderingLevel = FoveatedRenderingLevel.High;
|
|
feature.useDynamicFoveatedRendering = true; // eye-tracked on Quest 3
|
|
```
|
|
|
|
## 매 결정 기준
|
|
| 상황 | Approach |
|
|
|---|---|
|
|
| < 100 active note · prototype | MonoBehaviour + pool |
|
|
| > 200 active note · production | DOTS/ECS + Burst |
|
|
| Cross-platform (PCVR + Quest) | Unity URP + multi-quality preset |
|
|
| Modding support | Open beatmap format (`.dat`) + PluginLoader |
|
|
| 매 90fps 미달 의 perf budget | Foveation · LOD · GPU instancing |
|
|
|
|
**기본값**: Unity DOTS + URP + OpenXR — 매 Quest 3 의 baseline 의 fit.
|
|
|
|
## 🔗 Graph
|
|
- 응용: [[bitECS와_SharedArrayBuffer의_실제_코드_통합]]
|
|
- Adjacent: [[Object Pool]]
|
|
|
|
## 🤖 LLM 활용
|
|
**언제**: beatmap 의 procedural generation, cut-direction pattern 의 difficulty curve 의 tune, NJS · offset 의 starter value 의 suggest.
|
|
**언제 X**: 매 hand-crafted choreography (top mapper 의 art form) — LLM 의 generate 의 bland.
|
|
|
|
## ❌ 안티패턴
|
|
- **GC 의 frame 의 allocate**: 매 90fps 의 break → motion sickness.
|
|
- **Animation curve 의 audio sync**: dt 의 drift → pop. NJS-based linear lerp 의 use.
|
|
- **Saber 의 trigger collider**: physics step 의 sub-frame miss — manual raycast/plane 의 use.
|
|
- **Per-note GameObject Instantiate**: pool 의 mandatory.
|
|
|
|
## 🧪 검증 / 중복
|
|
- Verified (Beat Games postmortem GDC 2019, Unity DOTS 1.3 docs, OpenXR 1.1 spec, BSMG modding wiki).
|
|
- 신뢰도 A-.
|
|
|
|
## 🕓 Changelog
|
|
| 날짜 | 변경 |
|
|
|---|---|
|
|
| 2026-05-08 | Phase 1 |
|
|
| 2026-05-10 | Manual cleanup — VR architecture constraints + DOTS spawner + cut detector |
|