실제 환경(소음·조도·기울기·배터리·시각)이 게임 세계에 직접 반영되는 하이브리드 리얼리티 서바이벌 게임입니다.
빌드 과정 없이 index.html을 브라우저에서 바로 열어 플레이할 수 있습니다.
# 방법 1: 직접 열기 (마이크·모션 없이 슬라이더 폴백으로 플레이 가능)
open index.html
# 방법 2: 마이크·모션 센서 사용 시 로컬 서버 필요 (HTTPS 또는 localhost)
python3 -m http.server 8080
# → 브라우저에서 http://localhost:8080 접속iOS 13+: 모션 센서 권한을 위해 화면을 먼저 탭한 후 Enable Sensors 버튼을 눌러야 합니다.
데스크톱: 모션 센서가 없으면 키보드 방향키(↑↓←→)로 기울기를 시뮬레이션합니다.
| 센서 | 종류 | 사용 API | 비고 |
|---|---|---|---|
| 마이크 / 소음 | ✅ 실제 | getUserMedia + Web Audio API |
HTTPS 또는 localhost 필요 |
| 주변 조도 | ✅ 실제* | AmbientLightSensor |
Chrome + 플래그 또는 Android |
| 기기 기울기 | ✅ 실제 | DeviceOrientationEvent |
모바일에서 HTTPS 필요, iOS 13+ 제스처 필요 |
| 배터리 잔량 | ✅ 실제* | Battery Status API |
Chrome 데스크톱/Android |
| 화면 밝기 | 🎚 시뮬레이션 | 인앱 슬라이더 | 브라우저 API 미지원 |
| 시각(시간대) | ✅ 실제 | 시스템 시계 | 항상 사용 가능 |
* 표시 항목은 Firefox/Safari에서 개인정보 보호 정책으로 API가 제거되어 슬라이더로 폴백될 수 있습니다.
모든 센서는 Graceful Degradation 방식으로 동작합니다. 지원하지 않는 센서는 자동으로 슬라이더 폴백으로 전환되며, 게임은 항상 플레이 가능합니다.
벽·그림자·조명이 있는 스테이지 맵에서 감시원의 눈을 피해 탈출 지점까지 도달하는 실시간 스텔스 게임입니다.
기본 규칙
- 이동: 기기 기울기 또는 키보드 방향키로 플레이어 이동
- 승리: 탈출 지점(초록 ▶) 반경 안에 도달
- 패배: 에너지가 0이 되면 게임 오버
센서와 게임 메커니즘
| 센서 값 | 게임 영향 |
|---|---|
| 소음 높음 | 플레이어 탐지 반경 증가, 주변 감시원 경계 상태 전환 |
| 소음 낮음 (3초 지속) | 스텔스 모드(유령) 발동 — 탐지 반경 최소화 |
| 주변 조도 낮음 | 그림자 커버 발생, 감시원 시야 범위 감소 |
| 기울기 급격한 변화 | 발 헛딛음(Stumble) 발생 — 에너지 손실 + 소음 이벤트 |
| 배터리 낮음 (25% 이하) | 이동 속도 감소 |
| 야간 (20시–6시) | 감시원 이동 속도 및 경계 민감도 증가 |
감시원(Watcher) AI — 7단계 상태 머신
IDLE → SUSPICIOUS → LISTENING → INVESTIGATING → ALERTED → CHASING → RETURNING
- IDLE: 사전 설정된 경로를 순찰
- SUSPICIOUS: 약한 신호 감지 — 소리 방향으로 시선 전환
- LISTENING: 증거 평가 중 — 신뢰도를 쌓거나 잃음
- INVESTIGATING: 소리 발생 위치로 이동
- ALERTED: 높은 신뢰도 — 공격적 구역 탐색
- CHASING: 시각 확인 — 플레이어 직접 추적
- RETURNING: 추적 실패 — 순찰 경로로 복귀
감시원은 벽에 의해 차단되는 시야각(FOV) 콘과 음향 전파 모델(거리 감쇠 + 벽 차단)을 조합하여 탐지합니다.
감시원 그룹 종류
| 그룹 | 특성 |
|---|---|
| Standard (0) | 균형 잡힌 시야와 청각 |
| Scout (1) | 좁은 시야, 예민한 청각, 빠른 이동 |
| Guardian (2) | 넓은 시야, 둔한 청각, 느린 이동 |
스테이지 구성 요소
- 벽(walls): 이동 충돌 + 시야·소리 차단
- 소품(props): 박스(crate), 책상(desk), 기계(machine) — 엄폐물
- 그림자 구역(shadowZones): 어두운 영역, 플레이어 노출 감소
- 조명 구역(lightZones): 밝은 영역, 노출 증가
- 탈출 지점(escapePoints): 목표 위치
매일 제한된 횟수의 행동을 선택하며 최대한 오래 생존하는 턴제 전략 게임입니다. 진행 상황은 localStorage에 자동 저장됩니다.
관리해야 할 자원
| 자원 | 초기값 | 설명 |
|---|---|---|
| 자원 (Resources) | 50 | 식량·보급품 (0이 되면 건강 감소) |
| 스트레스 (Stress) | 20 | 누적될수록 행동 효율 저하 |
| 체력 (Health) | 80 | 0이 되면 게임 오버 |
| 피난처 에너지 (Shelter Energy) | 60 | 안전 기지의 에너지 |
행동 종류 (하루 최대 3회)
| 행동 | 효과 | 환경 영향 |
|---|---|---|
| 탐색 (Explore) | 자원 획득 | 밝을수록 수확 증가, 위협 높으면 위험 |
| 휴식 (Rest) | 체력·스트레스 회복 | 소음 높으면 효과 감소 |
| 은신 (Hide) | 스트레스 감소, 안전 확보 | 어두울수록 효과 증가 |
| 충전 (Recharge) | 피난처 에너지 보충 | 배터리 높을수록 효율 증가 |
| 다음 날 (Next Day) | 하루 종료, 자원 자동 소모 | — |
- 같은 행동을 하루에 반복할수록 효율이 감소합니다 (2회: ×0.7, 3회 이상: ×0.5).
- 매일 랜덤 이벤트(10종)가 발생하여 자원 상황에 영향을 줍니다.
- 모든 센서의 실시간 상태와 수치 확인
- 슬라이더로 폴백 값 수동 조절 가능
- 실제 센서 사용 여부 표시
inandout/
├── index.html # 앱 진입점 (SPA 마운트 포인트)
├── style.css # 전체 스타일
└── js/
├── app.js # 초기화 (엔진 시작 → Survive 상태 복원 → 홈 이동)
├── router.js # SPA 라우터 (화면 전환)
├── nav.js # 순환 의존성 방지용 navigate 인디렉션
├── state.js # 전역 상태 (sensorRaw, derived, exploreRun, survive, ui)
├── storage.js # localStorage 래퍼 (inandout_ 접두사)
├── utils.js # 공용 유틸 (clamp, lerp, smooth, randInt 등)
├── engine/
│ ├── hybridRealityEngine.js # 엔진 최상위 (센서 → 인터프리터 → 모드)
│ ├── environmentReader.js # 마이크(소음), 주변 조도 센서
│ ├── deviceReader.js # 배터리, 기울기(모션), 시각, 키보드 기울기 시뮬레이션
│ └── interpreter.js # raw → derived 변환 (visibility, exposure, stealth 등)
├── explore/
│ ├── stageData.js # 스테이지 맵 정의 (벽, 그림자/조명 구역, 감시원 스폰)
│ ├── geometry.js # 이동 충돌 처리, 시야 직선(LOS), 그림자/조명 판정
│ ├── soundSystem.js # 소리 이벤트 생성·전파·벽 감쇠
│ └── watcherAI.js # 감시원 7-상태 AI (순찰 → 의심 → 추적 등)
├── modes/
│ ├── exploreMode.js # 실시간 스텔스 탈출 게임 로직
│ └── surviveMode.js # 일별 서바이벌 행동 로직 및 랜덤 이벤트
└── ui/
├── screens.js # 4개 화면(Home, Sensor, Explore, Survive) DOM 빌드·업데이트
└── components.js # 재사용 UI 컴포넌트 (미터, 버튼, 캔버스, 모달 등)
[실제 센서 / 슬라이더 폴백]
│
Layer 1: 센서 입력
┌──────────────────────┐
│ environmentReader │ 마이크(Web Audio 링버퍼 평균), AmbientLightSensor
│ deviceReader │ Battery Status API, DeviceOrientationEvent, 키보드 시뮬
└──────────────────────┘
│ sensorRaw
│ noiseLevel 0–100 · ambientLight 0–100 · tiltX/Y -1–1
│ batteryLevel 0–100 · brightnessLevel 0–100 · hour 0–23
▼
Layer 2: 인터프리터 (interpreter.js)
┌─────────────────────────────────────────────────────────┐
│ raw → derived 변환 (10 Hz 틱, 지수 평활화 per 값) │
│ visibility · exposure · stealth · stability │
│ energyModifier · threatLevel (모두 0–100) │
└─────────────────────────────────────────────────────────┘
│ derived (0–100 정규화값)
▼
Layer 3: 게임 모드
┌─────────────────────────┐ ┌──────────────────────────┐
│ exploreMode.js │ │ surviveMode.js │
│ 실시간 스텔스 탈출 │ │ 일별 자원 관리 │
│ ├ stageData.js │ │ (턴제, localStorage 저장) │
│ ├ geometry.js │ └──────────────────────────┘
│ ├ soundSystem.js │
│ └ watcherAI.js │
└─────────────────────────┘
│
UI Layer
┌─────────────────────────────────────────────┐
│ screens.js + components.js → DOM 렌더링 │
└─────────────────────────────────────────────┘
엔진 틱 루프 (10 Hz, 100 ms 간격):
- 실제 센서 또는 슬라이더 폴백에서 raw 값 읽기 + 지수 평활화 적용
interpreter.js가 raw → derived 변환 수행- 게임 모드(
exploreMode/surviveMode)가 derived 값을 소비하여 게임 상태 업데이트 - UI가 상태를 읽어 DOM 반영
derived 값 수식 요약
| 값 | 수식 요약 |
|---|---|
visibility |
일조(시간) × 30 + 주변조도 × 0.4 + 밝기 × 0.3 |
exposure |
소음 × 0.4 + 주변조도 × 0.3 + 일조 × 20 + 밝기 × 0.1 |
stealth |
100 − 소음 × 0.5 − 기울기 × 15 + 어둠 × 0.2 |
stability |
100 − 기울기크기 × 70 (sqrt 비선형) |
energyModifier |
배터리 × 0.6 + 정적 × 0.2 + 야간 × 20 |
threatLevel |
소음 × 0.35 + 주변조도 × 0.25 + (100−stealth) × 0.2 |
| 파일 | 역할 |
|---|---|
app.js |
DOMContentLoaded 후 엔진 시작 → Survive 상태 복원 → 홈 화면 이동 |
router.js |
navigate(name) 호출 시 현재 화면 teardown → 새 화면 build |
nav.js |
router↔screens 순환 의존성 방지를 위한 얇은 인디렉션 레이어 |
state.js |
앱 전체 공유 상태 객체 (sensorRaw, derived, exploreRun, survive, ui) |
storage.js |
inandout_ 접두사를 붙인 localStorage 키/값 저장·불러오기 |
utils.js |
clamp, lerp, mapRange, smooth, randInt, randFloat, pickRandom, formatTime, debounce |
engine/hybridRealityEngine.js |
센서 시작·중지, setFallback, 10 Hz 틱 루프 관리, currentState 스냅샷 제공 |
engine/environmentReader.js |
마이크(Web Audio API 링버퍼 평균) 및 AmbientLightSensor 래퍼 |
engine/deviceReader.js |
Battery Status API, DeviceOrientation(데드존 처리), 키보드 기울기 시뮬레이션 |
engine/interpreter.js |
raw 센서 → derived 값 수식 변환 + 지수 평활화. levelLabel() 인간 친화적 라벨 |
explore/stageData.js |
스테이지 레이아웃 정의 (벽, 소품, 그림자/조명 구역, 감시원 스폰, 탈출 지점) |
explore/geometry.js |
AABB 충돌, 시야 직선(LOS) 판정, 그림자/조명 구역 포함 여부 검사 |
explore/soundSystem.js |
소리 이벤트 생성·수명 관리·거리 감쇠·벽 차단 감쇠 계산 |
explore/watcherAI.js |
감시원 생성 및 7-상태 AI 업데이트 (시야 콘 + 소리 기억 기반 탐지) |
modes/exploreMode.js |
스테이지 로드, 플레이어 이동, 발 헛딛음, 스텔스 모드, 랜덤 이벤트, 점수 계산 |
modes/surviveMode.js |
5가지 행동 함수, 10가지 랜덤 일별 이벤트, 반복 페널티, getSurviveAdvice() |
ui/screens.js |
4개 화면(Home, Sensor, Explore, Survive) DOM 빌드·업데이트·teardown |
ui/components.js |
buildMeter, buildButton, renderLog, buildSensorRow, buildGameCanvas, showModal |
| 브라우저 | 마이크 | 조도 | 기울기 | 배터리 |
|---|---|---|---|---|
| Chrome (데스크톱) | ✅ | ⚠ 플래그 필요 | ❌ 키보드 시뮬 | ✅ |
| Chrome (Android) | ✅ | ✅ | ✅ | ✅ |
| Firefox | ✅ | ❌ | ✅ | ❌ |
| Safari (iOS 13+) | ✅ | ❌ | ✅ 제스처 필요 | ❌ |
| Safari (macOS) | ✅ | ❌ | ❌ | ❌ |
지원하지 않는 센서는 슬라이더 시뮬레이션으로 자동 폴백되며 게임은 항상 플레이 가능합니다.