게임 구축하기
플레이 가능하고 올바른 브라우저 게임을 구축하세요. 정적 스크린샷이 아닌 실제 동작하는 게임이어야 합니다. 게임은 <canvas> (또는 R3F의 <Canvas>)와 DOM 오버레이 UI를 포함하는 React 라우트일 뿐입니다. 오버레이(시작 화면, HUD, 메뉴)는 design-ui 스킬로 스타일링하고, 이 스킬은 게임플레이 루프와 월드를 담당합니다.
컨트롤 / 반전 A/D: WASD, 조향, 비행 입력을 작성하기 전에 .grok/skills/controls/SKILL.md 를 여십시오. 차량/비행 데모는 이 파일이나 단일 장르 플레이북만 읽으면 A/D가 반전되어 제공되는 경우가 많습니다.
범위 참고 — 싱글 플레이어, 봇, 또는 소규모 P2P 협동:
- 기본적으로 싱글 플레이어 또는 싱글 플레이어 + AI/봇을 제공합니다.
- 2–8인 협동 / 캐주얼 실시간 (공유 커서, 파티 게임, 친구 간 캐주얼 액션)은 지원됩니다 —
multiplayer-p2p스킬 (WebRTC 메시,/api/rtc에서 시그널링)을 사용하세요. 먼저 신뢰 모델을 읽으십시오. - 현재 지원되는 유일한 멀티플레이어는 P2P입니다. 연결할 수 없는 소켓을 반쯤 만들지 마십시오.
참고 자료 (필요 시 로드):
controls스킬 (../controls/) — 이동/조향/비행에 필수: 플레이어에게 보이는 A/D, 반전 조향 안티패턴, 비행 에일러론, 필수 자체 테스트 +window.__controlsTest. 차량/비행의 경우 선택 사항이 아닙니다.references/threejs-foundational.md— 심층 3D/루프/성능 참고 자료. 3D의 경우 읽으십시오.references/3d-libs.md— three.js + @react-three/fiber + drei + rapier 사용법.threejs스킬 (../threejs/) — 공식 전체 Three.js + TSL API 덤프 (llms-full.txt). 고급 재질/셰이더/WebGPU/로더에 사용하고, 간단한 2D 캔버스 게임에는 사용하지 마십시오.references/babylon.md— Babylon.js, 배터리 포함 3D 엔진 대안.references/phaser.md— Phaser 3, 2D 게임용 기본 엔진.references/ecs-architecture.md— 대규모 게임을 위한 엔티티-컴포넌트-시스템 구조.references/genres/*.md— 장르별 플레이북 (FPS, 2D 플랫포머, 레이싱-카트, 퍼즐-매치3-테트리스, 복셀-마인크래프트, 무한 러너, 탑다운 트윈스틱, 타워 디펜스, 보드-카드-체스). 장르 파일은controls를 대체하지 않습니다.references/game-feel-juice.md,input.md,audio.md,collision-physics.md,save-persistence.md,procedural-generation.md,ai-pathfinding.md.game-asset-core(+game-animation-frames/game-tilesets/game-character-consistency/game-ui-icons) — 스프라이트, 시트, 타일 또는 UI를 생성할 때 엔진에 바로 사용 가능한 2D 아트 기본값 및 검증(§6 참조).
빌드에 맞는 특정 장르/주제 참고 자료를 선택하십시오. 이 파일은 루프/월드를 위한 범용 코어입니다. 입력 신호 → controls. 2D 게임 아트 → game-asset-core.
1. 게임 루프 및 타이밍 (#1 정확성 이슈)
- 엔진의 RAF 루프(
renderer.setAnimationLoop, R3FuseFrame, 또는 2D 캔버스의 경우requestAnimationFrame)로 루프를 구동하십시오. 게임 타이밍에 절대setInterval/setTimeout/Date.now()를 사용하지 마십시오. - 모든 이동/애니메이션을 델타 타임(초)으로 스케일링하여 속도가 프레임 속도와 무관하도록 하십시오(60Hz vs 144Hz). 델타를 프레임당 한 번 계산하고 재사용하십시오.
- three.js:
THREE.Timer를 사용하십시오(Clock사용 금지 —Clock.getDelta()는 같은 프레임에서 두 번째 호출 시 ~0을 반환하여 고전적인 프리즈 버그를 유발합니다).
- three.js:
- 델타를 제한하십시오(
min(delta, 0.1)). 백그라운드 탭이 물체를 순간이동시키는 것을 방지합니다. - 물리/게임플레이에 고정 타임스텝 사용: 델타를 누적하고 디스플레이 속도로 렌더링하는 동안 고정 속도(예: 1/60)로 시뮬레이션을 스텝합니다. 터널링 및 비결정론을 방지합니다.
2. 컨트롤 (controls 스킬에 위임)
WASD / 조향 / 비행 코드를 구현하기 전에 .grok/skills/controls/SKILL.md를 여십시오. 해당 스킬은 다음에 대한 진실 공급원입니다:
- 플레이어에게 보이는 A = 왼쪽 / D = 오른쪽 (추적 카메라, 전진 중)
- 왜
KeyA → steer−+yaw += steer * +rate가 반전되는지 (가장 흔한 버그) - 차량 vs FPS (스트레이프 ≠ 조향), 고정익 에일러론, 헬리/드론 참고 사항
- 필수 자체 테스트 +
window.__controlsTest프로브
genres/racing-kart.md만이 조향 신호가 있는 유일한 장소로 취급하지 마십시오 — 비행기, 제트스키, 메카는 절대 열지 않습니다.
짧은 알림 (전체 세부 사항은 controls):
// 차량 요우 본체 (추적 카메라): A는 이 기준으로 요우를 증가시켜야 함
forward = (-sin(yaw), 0, -cos(yaw))
// KeyA → steer = +1; yaw += steer * turnRate * speedFactor * dt
// 틀림 (반전됨): KeyA → steer = -1; yaw += steer * turnRate * dt
- 포인터 잠금: 마우스-룩만 지원 — WASD는 직접 구현하십시오; 클릭하여 재생 오버레이; 잠금 시 해제.
- 키를 누름 상태 + dt로 추적하십시오;
references/input.md를 통해 장치를 통합하십시오. - 완료:
controls스킬 체크리스트를 실행하십시오. 스크린샷만으로는 차량/비행에 충분하지 않습니다.
3. 3D 방향 및 월드 오브젝트 ("옆으로/뒤로" 버그)
- three.js는 오른손 좌표계, +Y 업: +X 오른쪽, +Y 위, +Z 뷰어 방향. 메시는 +Z를 향함; 카메라는 −Z를 봄 (고전적인 "카메라가 뒤로" 실수).
Cone/Cylinder와 같은 프리미티브는 기본적으로 +Y 방향 → 팁을 전진 방향으로 정렬하려면 회전(geo.rotateX(Math.PI/2)).- 메시를
forward방향으로 정렬 (메시는 +Z를 향함): 가장 간단한 올바른 방법은mesh.lookAt(mesh.position.clone().add(forward)). 직접 기저를 구축하려면 +Z 열을forward로 설정하고 올바른 오른손 회전(det = +1)을 유지하는 x축을 선택하십시오:xAxis = normalize(cross(up, forward)), 그런 다음makeBasis(xAxis, up, forward). 이것은cross(forward, up)이 아닌cross(up, forward)입니다(§2의 이동right = cross(forward, up)와 다름). +Z를 대상으로 하면(카메라의 −Z 대신) x축 부호가 반전되어xAxis × up = forward가 됩니다. 프레임은 오른손을 유지합니다(반사 없음). 메시의 경우makeBasis(xAxis, up, -forward)를 사용하지 마십시오 — 이는 -forward(카메라 규칙)를 대상으로 하므로 메시가 뒤를 향합니다. - 카메라를
forward방향으로 정렬 (카메라는 −Z를 봄): +Z 열을-forward로 설정 —xAxis = normalize(cross(forward, up)), 그런 다음makeBasis(xAxis, up, -forward)(또는 간단히camera.lookAt(target)). - 일관된 월드
up을 유지하여 오브젝트가 똑바로 서 있게 하십시오. 평면 프리미티브만 회전하여 세우십시오. - glTF 임포트 방향을 확인하십시오;
AxesHelper/ArrowHelper로 디버깅하십시오. 직립 자체 테스트: 캐릭터가 지면에 서 있고, 눕거나 가라앉지 않아야 합니다.
4. 카메라는 이동과 일치해야 함
- 전용
moveForward/moveRight를 이동에 사용하고, 한 번 계산한 후 카메라 코드가 절대 변경하지 않도록 하십시오(공유 임시 벡터를 별칭으로 사용하면 카메라와 이동이 불일치 — 실제 재현 가능한 버그). - 3인칭 추적:
desired = playerPos + up*height + moveForward*(-followDist); 카메라를 목표 방향으로 lerp(지수 기반 평활화, 델타 스케일링 사용),lookAt(player). - 레이어 분리 디버그 순서: (1) 키 등록 → (2) 이동 신호 올바름 → (3) 카메라 일치. 이 순서로 수정하십시오.
5. 성능
- 드로우 콜 최소화 (
renderer.info.render.calls, 대상 <100): 재질 공유, 반복 오브젝트에InstancedMesh/BatchedMesh, 아틀라스. - GPU 리소스를 직접 해제 (
geometry/material/texture.dispose()) 레벨 변경 시 — three.js는 이를 가비지 컬렉션하지 않습니다. 총알/적/파티클을 풀링하십시오. - 프레임당 할당 없음 (임시 벡터 재사용). 텍스처 압축; 거리에 따른 LOD.
6. 애셋 (생성된 사진 함정 피하기)
- 인터랙티브 3D 요소 (무기 뷰모델, 캐릭터, 소품, 발사체) → 3D 지오메트리 / glTF로 구축하고 생성된 이미지를 사용하지 마십시오. 손에 든 총의 평면적인 사실적 JPG를 FPS 뷰모델로 사용하면 이상해 보이고, 애니메이션이 불가능하며, (JPG에는 알파 채널이 없음) 검은색 상자로 렌더링됩니다. 실제 3D 뷰모델을 오버레이 렌더 레이어로 카메라에 부모로 설정하십시오.
- 이미지 생성은 평면 2D 애셋(텍스처, 하늘/메뉴 배경, 2D 스프라이트, UI 아트)에만 예약하십시오. 생성된 사진을 3D 메시, 뷰모델 또는 캐릭터 대체물로 절대 사용하지 마십시오 — 3D 지오메트리 / glTF로 구축하십시오. 캔버스/텍스처에 그려지는 이미지에
crossOrigin="anonymous"를 설정하십시오.imagine-grok-build스킬 참조 (2D 전용 — 이미지 도구는 실제 3D를 생성할 수 없습니다). - 엔진 준비 게임 아트 교리 →
game-asset-core(../game-asset-core/)를 열어 기본값 + 블라인드 검증 + 재시도 규율을 확인한 다음, 해당 전문가:game-animation-frames(루프/모션 법칙),game-tilesets,game-character-consistency,game-ui-icons를 확인하십시오. 이들은 QC/교리이지 내보내기 파이프라인이 아닙니다. 실제 아트가 예상되는 상황에서 막대기 그림 플레이스홀더를 제공하지 마십시오. - 2D 게임 스프라이트 / 애니메이션 시트 →
generate2dsprite(.grok/skills/generate2dsprite/SKILL.md) 실행: 단색#FF00FF마젠타imagine_text_to_image시트 + 크로마 포스트프로세스 스크립트(프로세서에는 마젠타가 필요함). 투명 PNG/GIF를 Canvas/Phaser에 연결하십시오.game-asset-core(+ 관련 애니메이션/캐릭터 전문가)를 계속 적용하십시오. - 2D 맵 / 레벨 / 소품 팩 →
generate2dmap(.grok/skills/generate2dmap/SKILL.md)을 여십시오. 플레이 가능한 맵을 위해 기초만 있는 베이스 + 별도 소품/충돌을 선호하십시오. 브라우저 기본값:raw_canvas/ Phaser. 타일형 지면/벽 → 2×2 이음새 검사를 위해game-tilesets도 확인하십시오. - 선택적 밀도 높은 로코모션 →
video2dsprite(Grokimagine_image_to_video+ 샌드박스 스크립트; 마젠타 베이스) 실행. 선명한 프로덕션 히어로를 위해generate2dsprite를 선호하십시오. 이 샌드박스에서 임시 ffmpeg 전용 수확보다video2dsprite를 선호하십시오.
7. 오디오, 저장, 느낌
- 오디오: 첫 번째 사용자 제스처(탭하여 시작)에서
AudioContext를 잠금 해제하거나 iOS는 무음입니다;visibilitychange시 재개하십시오. (references/audio.md) - 저장:
version필드 + 마이그레이션이 있는localStorage/IndexedDB. - 주스: 화면 흔들림, 힛스톱, 이징 트윈, 파티클 — 저렴하지만 인지된 품질이 크게 향상됩니다. 프레젠테이션을 시뮬레이션과 분리하여 유지하십시오.
8. 모바일
- 캔버스 버퍼 크기와 CSS 크기를 구분하십시오;
devicePixelRatio를 존중하십시오. touch-action: none, 기본 해상도에 레터박스 맞춤, 방향 처리.- 터치 컨트롤 (가상 조이스틱 + 액션 버튼), ≥44px 타겟.
스택 / 엔진 선택
- 3D → three.js, 이상적으로는 @react-three/fiber + drei를 통해 (React 앱에 적합; drei는 포인터 잠금/컨트롤/로더 제공) + 물리/캐릭터 컨트롤러를 위한 @react-three/rapier.
references/3d-libs.md참조. - 2D → 기본 Canvas 2D는 스네이크/테트리스/플래피/플랫포머에 충분합니다. 장르가 필요할 때만 Phaser를 사용하십시오.
- 이러한 게임 종속성은 사전 설치되지 않습니다 —
npm install하십시오(그리고 Vercel 빌드가 사용할 수 있도록package.json에 추가되었는지 확인하십시오).
완료 기준 ("완료" 전)
- 콘솔 오류 없이 로드됨; 눈에 보이는 게임플레이 (빈 캔버스 아님).
controls스킬 자체 테스트 통과 (추적 카메라에서 전진 중 A = 왼쪽 / D = 오른쪽; 반전된 경우 부호 하나 뒤집기). 스크린샷 전용 아님.- 3D 직립 및 카메라 일치 자체 테스트 통과 (§3, §4).
- 터치 컨트롤로 모바일 뷰포트에서 실행.
- 프로덕션 빌드(
npm run build)가 빌드된 출력을 렌더링하며, dev 전용이 아님. - 공유 / X 카드:
og스킬을 여십시오 — 사용자 정의public/og.jpg및"type": "x:game"을src/lib/og/site.json에 추가하십시오. X는og:type="x:game"을 사용하여 언폴드를 게임 카드로 표시합니다. 이를 위해twitter:card를 사용하거나x:type을 발명하지 마십시오.browser-smoke/brand-check는 캔버스 앱이site.json필드를 생략할 때 경고합니다.