refactor: tighten project structure and strengthen tooling
This commit is contained in:
+3
-3
@@ -31,7 +31,7 @@ Scene objects are **never** singleton classes. Managers are **never** React comp
|
|||||||
- Scene components live in `src/world/` and `src/components/3d/`
|
- Scene components live in `src/world/` and `src/components/3d/`
|
||||||
- UI overlays live in `src/components/ui/`
|
- UI overlays live in `src/components/ui/`
|
||||||
- Managers live in `src/stateManager/`
|
- Managers live in `src/stateManager/`
|
||||||
- Debug tooling lives in `src/debug/`
|
- Debug tooling lives in `src/utils/debug/`
|
||||||
- Hooks live in `src/hooks/`
|
- Hooks live in `src/hooks/`
|
||||||
- Static data lives in `src/data/`
|
- Static data lives in `src/data/`
|
||||||
- Shaders live in `src/shaders/`
|
- Shaders live in `src/shaders/`
|
||||||
@@ -55,9 +55,9 @@ import { useGameState } from "@/hooks/useGameState";
|
|||||||
### Debug
|
### Debug
|
||||||
|
|
||||||
- Debug panel activates with `?debug` in URL
|
- Debug panel activates with `?debug` in URL
|
||||||
- All debug logic goes through `Debug.getInstance()` from `src/debug/Debug.ts`
|
- All debug logic goes through `Debug.getInstance()` from `src/utils/debug/Debug.ts`
|
||||||
- Never scatter `if (isDev)` blocks across files
|
- Never scatter `if (isDev)` blocks across files
|
||||||
- `r3f-perf` is lazy-loaded only in debug mode via `src/debug/DebugPerf.tsx`
|
- `r3f-perf` is lazy-loaded only in debug mode via `src/utils/debug/DebugPerf.tsx`
|
||||||
|
|
||||||
## Managers (4 max)
|
## Managers (4 max)
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ The free debug camera is toggled from the debug panel, not mounted permanently.
|
|||||||
## Debug singleton
|
## Debug singleton
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// src/debug/Debug.ts
|
// src/utils/debug/Debug.ts
|
||||||
import GUI from "lil-gui";
|
import GUI from "lil-gui";
|
||||||
|
|
||||||
export class Debug {
|
export class Debug {
|
||||||
@@ -58,9 +58,9 @@ if (debug.active) {
|
|||||||
r3f-perf is loaded only in debug mode to avoid dependency issues in production:
|
r3f-perf is loaded only in debug mode to avoid dependency issues in production:
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
// src/debug/DebugPerf.tsx
|
// src/utils/debug/DebugPerf.tsx
|
||||||
import { Suspense, lazy } from "react";
|
import { Suspense, lazy } from "react";
|
||||||
import { Debug } from "@/debug/Debug";
|
import { Debug } from "@/utils/debug/Debug";
|
||||||
|
|
||||||
const Perf = lazy(() => import("r3f-perf").then((m) => ({ default: m.Perf })));
|
const Perf = lazy(() => import("r3f-perf").then((m) => ({ default: m.Perf })));
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,12 @@ name: 🔍 Lint
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [opened, synchronize, reopened]
|
types: [opened, synchronize, reopened]
|
||||||
|
branches: [develop, main]
|
||||||
push:
|
push:
|
||||||
branches: [main]
|
branches:
|
||||||
|
- main
|
||||||
|
- develop
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
lint:
|
||||||
|
|||||||
@@ -3,8 +3,12 @@ name: 📊 Quality
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [opened, synchronize, reopened]
|
types: [opened, synchronize, reopened]
|
||||||
|
branches: [develop, main]
|
||||||
push:
|
push:
|
||||||
branches: [main]
|
branches:
|
||||||
|
- main
|
||||||
|
- develop
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
security:
|
security:
|
||||||
|
|||||||
@@ -8,31 +8,31 @@ Built with React, Three.js, and Vite. Runs in the browser, no installation requi
|
|||||||
|
|
||||||
### Build & Language
|
### Build & Language
|
||||||
|
|
||||||
| Package | Doc |
|
| Package |
|
||||||
| -------------------------------------------------- | ------------------------------------ |
|
| -------------------------------------------------- |
|
||||||
| [TypeScript](https://www.typescriptlang.org/docs/) | https://www.typescriptlang.org/docs/ |
|
| [TypeScript](https://www.typescriptlang.org/docs/) |
|
||||||
| [React](https://react.dev/learn) | https://react.dev/learn |
|
| [React](https://react.dev/learn) |
|
||||||
| [Vite](https://vite.dev/guide/) | https://vite.dev/guide/ |
|
| [Vite](https://vite.dev/guide/) |
|
||||||
| [ESLint](https://eslint.org/docs/latest/) | https://eslint.org/docs/latest/ |
|
| [ESLint](https://eslint.org/docs/latest/) |
|
||||||
| [Prettier](https://prettier.io/docs/) | https://prettier.io/docs/ |
|
| [Prettier](https://prettier.io/docs/) |
|
||||||
|
|
||||||
### 3D Engine
|
### 3D Engine
|
||||||
|
|
||||||
| Package | Doc |
|
| Package |
|
||||||
| ----------------------------------------------------------------------------------------- | ---------------------------------------------- |
|
| ----------------------------------------------------------------------------------------- |
|
||||||
| [Three.js](https://threejs.org/docs/) | https://threejs.org/docs/ |
|
| [Three.js](https://threejs.org/docs/) |
|
||||||
| [@react-three/fiber](https://docs.pmnd.rs/react-three-fiber/getting-started/introduction) | https://docs.pmnd.rs/react-three-fiber |
|
| [@react-three/fiber](https://docs.pmnd.rs/react-three-fiber/getting-started/introduction) |
|
||||||
| [@react-three/drei](https://pmndrs.github.io/drei) | https://pmndrs.github.io/drei |
|
| [@react-three/drei](https://pmndrs.github.io/drei) |
|
||||||
| [@react-three/rapier](https://rapier.rs/docs/) | https://rapier.rs/docs/user_guides/javascript/ |
|
| [@react-three/rapier](https://rapier.rs/docs/) |
|
||||||
| [@react-three/postprocessing](https://github.com/pmndrs/postprocessing) | https://github.com/pmndrs/postprocessing |
|
| [@react-three/postprocessing](https://github.com/pmndrs/postprocessing) |
|
||||||
| [GSAP](https://gsap.com/docs/v3/Installation/) | https://gsap.com/docs/v3/ |
|
| [GSAP](https://gsap.com/docs/v3/Installation/) |
|
||||||
|
|
||||||
### Performance & Effects
|
### Performance & Effects
|
||||||
|
|
||||||
| Package | Doc |
|
| Package |
|
||||||
| --------------------------------------------------------------------------- | --------------------------------------------------------- |
|
| --------------------------------------------------------------------------- |
|
||||||
| [r3f-perf](https://github.com/utsuboco/r3f-perf) | https://github.com/utsuboco/r3f-perf |
|
| [r3f-perf](https://github.com/utsuboco/r3f-perf) |
|
||||||
| [AnimationMixer](https://threejs.org/docs/#api/en/animation/AnimationMixer) | https://threejs.org/docs/#api/en/animation/AnimationMixer |
|
| [AnimationMixer](https://threejs.org/docs/#api/en/animation/AnimationMixer) |
|
||||||
|
|
||||||
## 🗂 Project Structure
|
## 🗂 Project Structure
|
||||||
|
|
||||||
@@ -98,16 +98,20 @@ la-fabrik/
|
|||||||
│ ├── vertex.glsl
|
│ ├── vertex.glsl
|
||||||
│ └── fragment.glsl
|
│ └── fragment.glsl
|
||||||
│
|
│
|
||||||
├── debug/ # Dev-only tools and scene inspection
|
|
||||||
│ ├── Debug.ts # Global lil-gui manager
|
|
||||||
│ ├── DebugPerf.tsx # r3f-perf overlay mounted in Canvas
|
|
||||||
│ └── scene/
|
|
||||||
│ ├── DebugHelpers.tsx # Grid + axes helpers shown in debug mode
|
|
||||||
│ └── DebugCameraControls.tsx # Free debug camera for map inspection
|
|
||||||
│
|
|
||||||
├── utils/
|
├── utils/
|
||||||
│ ├── EventEmitter.ts # Simple pub/sub for manager-to-manager events
|
│ ├── EventEmitter.ts # Simple typed pub/sub utility
|
||||||
│ └── Dispose.ts # traverse() + dispose() helper
|
│ ├── Sizes.ts # Viewport size tracking
|
||||||
|
│ ├── Time.ts # Animation frame timing utility
|
||||||
|
│ ├── Readme.md
|
||||||
|
│ └── debug/ # Dev-only tools and scene inspection
|
||||||
|
│ ├── Debug.ts # Global lil-gui manager
|
||||||
|
│ ├── DebugPerf.tsx # r3f-perf overlay mounted in Canvas
|
||||||
|
├── hooks/
|
||||||
|
│ └── debug/
|
||||||
|
│ └── useCameraMode.ts
|
||||||
|
│ └── scene/
|
||||||
|
│ ├── DebugHelpers.tsx # Grid + axes helpers shown in debug mode
|
||||||
|
│ └── DebugCameraControls.tsx # Free debug camera for map inspection
|
||||||
│
|
│
|
||||||
├── App.tsx # Canvas bootstrap
|
├── App.tsx # Canvas bootstrap
|
||||||
└── main.tsx
|
└── main.tsx
|
||||||
@@ -122,12 +126,8 @@ npm install
|
|||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
Open `http://localhost:5173` — standard experience.
|
- `http://localhost:5173` for the app
|
||||||
Open `http://localhost:5173?debug` — debug panel + r3f-perf overlay. The free debug camera is enabled from the debug panel.
|
- `http://localhost:5173?debug` to enable debug tooling
|
||||||
|
|
||||||
## 🧭 Conventions
|
|
||||||
|
|
||||||
Coding conventions and generation rules live in `.agent/skills/best-practices.md`.
|
|
||||||
|
|
||||||
## 📜 License
|
## 📜 License
|
||||||
|
|
||||||
|
|||||||
+32
-403
@@ -1,414 +1,43 @@
|
|||||||
# Architecture Patterns
|
# Implemented Architecture
|
||||||
|
|
||||||
Coding conventions are maintained in `.agent/skills/best-practices.md`.
|
This document describes the code that exists today in the repository.
|
||||||
|
|
||||||
The project uses **two complementary patterns**:
|
## Runtime Structure
|
||||||
|
|
||||||
- **Singleton service classes** for orchestration and side effects
|
- `src/App.tsx` mounts the `Canvas`, the 3D `World`, the debug perf overlay, and the HTML crosshair overlay.
|
||||||
- **Declarative React components** for all 3D scene objects
|
- `src/world/World.tsx` composes the active 3D scene.
|
||||||
|
- `src/world/Map.tsx` loads and centers the blocking map model.
|
||||||
|
- `src/world/Lighting.tsx` owns the current ambient and directional light setup.
|
||||||
|
- `src/world/Environment.tsx` owns the current background color.
|
||||||
|
- `src/world/player/FPSController.tsx` provides the current player camera, pointer lock, and `ZQSD` movement.
|
||||||
|
- `src/utils/debug/` contains debug-only tooling such as `lil-gui`, scene helpers, and the free debug camera.
|
||||||
|
- `src/components/ui/Crosshair.tsx` is the only current HTML overlay component in use.
|
||||||
|
|
||||||
This distinction is intentional. Scene elements such as the map, lights, environment, zones, and player are implemented as **React Three Fiber components** and mounted through `<Canvas>`.
|
## Camera Modes
|
||||||
Global systems such as gameplay flow, cinematics, audio, and debug tooling are implemented as **manager classes**.
|
|
||||||
|
|
||||||
Consistency matters, but the codebase does **not** force the same lifecycle pattern on scene components and global services.
|
The application currently has two camera modes:
|
||||||
|
|
||||||
---
|
- `player`
|
||||||
|
- controlled by `FPSController`
|
||||||
|
- player height is `1.75m`
|
||||||
|
- movement uses `ZQSD`
|
||||||
|
- `E` is reserved for future interaction
|
||||||
|
- `debug`
|
||||||
|
- controlled by `DebugCameraControls`
|
||||||
|
- enabled from the debug panel
|
||||||
|
|
||||||
## 1. Singleton Pattern for Global Managers Only
|
The active mode is stored in the debug subsystem and consumed through `src/hooks/debug/useCameraMode.ts`.
|
||||||
|
|
||||||
Only cross-cutting services use the singleton pattern.
|
## Debug System
|
||||||
|
|
||||||
Examples:
|
- `src/utils/debug/Debug.ts` is a singleton wrapper around `lil-gui`
|
||||||
|
- `src/utils/debug/DebugPerf.tsx` lazy-loads `r3f-perf`
|
||||||
|
- `src/utils/debug/scene/DebugHelpers.tsx` mounts grid and axes in debug mode
|
||||||
|
- `src/utils/debug/scene/DebugCameraControls.tsx` mounts the free camera in debug mode
|
||||||
|
|
||||||
- `GameManager`
|
## Current Limitations
|
||||||
- `CinematicManager`
|
|
||||||
- `AudioManager`
|
|
||||||
- `ZoneManager`
|
|
||||||
- `Debug`
|
|
||||||
- `EventEmitter`
|
|
||||||
|
|
||||||
These services must exist once, be accessible from anywhere, and coordinate the experience globally.
|
- There is no gameplay state manager implemented yet.
|
||||||
|
- There are no zone systems, missions, dialogue systems, or cinematic systems implemented yet.
|
||||||
```ts
|
- Player movement currently uses a simple height clamp instead of real collision or ground detection.
|
||||||
// stateManager/GameManager.ts
|
- The map is currently a blocking preview scene, not a full playable world.
|
||||||
export class GameManager {
|
|
||||||
private static _instance: GameManager | null = null;
|
|
||||||
|
|
||||||
cinematic!: CinematicManager;
|
|
||||||
audio!: AudioManager;
|
|
||||||
zone!: ZoneManager;
|
|
||||||
|
|
||||||
static getInstance(): GameManager {
|
|
||||||
if (!GameManager._instance) {
|
|
||||||
GameManager._instance = new GameManager();
|
|
||||||
}
|
|
||||||
return GameManager._instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
private constructor() {
|
|
||||||
this.cinematic = CinematicManager.getInstance();
|
|
||||||
this.audio = AudioManager.getInstance();
|
|
||||||
this.zone = ZoneManager.getInstance();
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy(): void {
|
|
||||||
this.cinematic.destroy();
|
|
||||||
this.audio.destroy();
|
|
||||||
this.zone.destroy();
|
|
||||||
GameManager._instance = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
const game = GameManager.getInstance();
|
|
||||||
game.startMission("workshop");
|
|
||||||
```
|
|
||||||
|
|
||||||
**Important:** scene objects such as `Map`, `WorkshopZone`, `Lighting`, or `Environment` are **not** singletons and must remain standard React components.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Scene Objects Are React Components, Not Manager Classes
|
|
||||||
|
|
||||||
All 3D scene objects are implemented as **declarative React components**.
|
|
||||||
|
|
||||||
This includes:
|
|
||||||
|
|
||||||
- maps
|
|
||||||
- lights
|
|
||||||
- environments
|
|
||||||
- player controllers
|
|
||||||
- zones
|
|
||||||
- interactive props
|
|
||||||
- postprocessing layers
|
|
||||||
|
|
||||||
This keeps the code aligned with the R3F runtime instead of rebuilding a parallel imperative engine.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
// world/zones/WorkshopZone.tsx
|
|
||||||
import { useEffect, useRef } from "react";
|
|
||||||
import * as THREE from "three";
|
|
||||||
import { useFrame } from "@react-three/fiber";
|
|
||||||
import { useGLTF } from "@react-three/drei";
|
|
||||||
|
|
||||||
export function WorkshopZone() {
|
|
||||||
const root = useRef<THREE.Group>(null);
|
|
||||||
const gltf = useGLTF("/models/workshop/ebike.glb");
|
|
||||||
const mixer = useRef<THREE.AnimationMixer | null>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
mixer.current = new THREE.AnimationMixer(gltf.scene);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
mixer.current?.stopAllAction();
|
|
||||||
mixer.current = null;
|
|
||||||
};
|
|
||||||
}, [gltf.scene]);
|
|
||||||
|
|
||||||
useFrame((_, delta) => {
|
|
||||||
mixer.current?.update(delta);
|
|
||||||
});
|
|
||||||
|
|
||||||
return <primitive ref={root} object={gltf.scene.clone()} />;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Per-frame values such as movement, interpolation, camera smoothing, and physics must stay in:
|
|
||||||
|
|
||||||
- `useRef`
|
|
||||||
- `useFrame`
|
|
||||||
- Rapier bodies
|
|
||||||
- other frame-based systems
|
|
||||||
|
|
||||||
They must **never** go through React state.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Single Source of Truth for Durable Gameplay State
|
|
||||||
|
|
||||||
The project uses a single authoritative `GameManager` for durable gameplay state.
|
|
||||||
|
|
||||||
React components subscribe to that state through thin hooks.
|
|
||||||
Other managers communicate through `GameManager`, which acts as the main gameplay orchestrator.
|
|
||||||
|
|
||||||
High-frequency values such as movement, camera interpolation, or physics never go through React state and stay in refs or frame-based systems.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// stateManager/GameManager.ts
|
|
||||||
type Phase = "loading" | "intro" | "exploring" | "cinematic" | "outro";
|
|
||||||
type ZoneId = "workshop" | "powerGrid" | "farm" | null;
|
|
||||||
|
|
||||||
type GameSnapshot = {
|
|
||||||
phase: Phase;
|
|
||||||
activeZone: ZoneId;
|
|
||||||
missionId: string | null;
|
|
||||||
missionStep: number;
|
|
||||||
inputLocked: boolean;
|
|
||||||
dialogueId: string | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class GameManager {
|
|
||||||
private static _instance: GameManager | null = null;
|
|
||||||
private listeners = new Set<() => void>();
|
|
||||||
|
|
||||||
private state: GameSnapshot = {
|
|
||||||
phase: "loading",
|
|
||||||
activeZone: null,
|
|
||||||
missionId: null,
|
|
||||||
missionStep: 0,
|
|
||||||
inputLocked: false,
|
|
||||||
dialogueId: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
static getInstance(): GameManager {
|
|
||||||
if (!GameManager._instance) {
|
|
||||||
GameManager._instance = new GameManager();
|
|
||||||
}
|
|
||||||
return GameManager._instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
getState(): GameSnapshot {
|
|
||||||
return this.state;
|
|
||||||
}
|
|
||||||
|
|
||||||
subscribe(listener: () => void): () => void {
|
|
||||||
this.listeners.add(listener);
|
|
||||||
return () => this.listeners.delete(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
private emit(): void {
|
|
||||||
this.listeners.forEach((cb) => cb());
|
|
||||||
}
|
|
||||||
|
|
||||||
setPhase(phase: Phase): void {
|
|
||||||
this.state.phase = phase;
|
|
||||||
this.emit();
|
|
||||||
}
|
|
||||||
|
|
||||||
setActiveZone(zone: ZoneId): void {
|
|
||||||
this.state.activeZone = zone;
|
|
||||||
this.emit();
|
|
||||||
}
|
|
||||||
|
|
||||||
startMission(id: string): void {
|
|
||||||
this.state.missionId = id;
|
|
||||||
this.state.missionStep = 0;
|
|
||||||
this.emit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// hooks/useGameState.ts
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { GameManager } from "@/stateManager/GameManager";
|
|
||||||
|
|
||||||
export function useGameState() {
|
|
||||||
const game = GameManager.getInstance();
|
|
||||||
const [state, setState] = useState(game.getState());
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
return game.subscribe(() => {
|
|
||||||
setState({ ...game.getState() });
|
|
||||||
});
|
|
||||||
}, [game]);
|
|
||||||
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This keeps the architecture simple:
|
|
||||||
|
|
||||||
- **GameManager** owns durable gameplay state
|
|
||||||
- **other managers** handle side effects
|
|
||||||
- **React components** render that state
|
|
||||||
- **R3F frame systems** handle fast-changing values
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Side Effects Stay in Specialized Managers
|
|
||||||
|
|
||||||
Managers other than `GameManager` should not become secondary state stores.
|
|
||||||
|
|
||||||
Their role is to manage side effects and specialized runtime logic, such as:
|
|
||||||
|
|
||||||
- GSAP timelines
|
|
||||||
- audio playback
|
|
||||||
- zone entry detection
|
|
||||||
- interaction triggers
|
|
||||||
- camera lock/unlock
|
|
||||||
- temporary event coordination
|
|
||||||
|
|
||||||
They can read from `GameManager`, react to its state, or notify it of important transitions.
|
|
||||||
|
|
||||||
Example flow:
|
|
||||||
|
|
||||||
```
|
|
||||||
Component / Hook
|
|
||||||
↓
|
|
||||||
GameManager.getInstance()
|
|
||||||
├── startMission('workshop')
|
|
||||||
├── cinematic.play('intro_workshop')
|
|
||||||
├── audio.playAmbience('workshop')
|
|
||||||
└── zone.setActive('workshop')
|
|
||||||
```
|
|
||||||
|
|
||||||
This keeps the dependency graph understandable while avoiding duplicated durable state.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. Memory Management — Dispose Only What You Own
|
|
||||||
|
|
||||||
GPU memory must be cleaned carefully.
|
|
||||||
|
|
||||||
However, the project does **not** blindly deep-dispose every object on unmount.
|
|
||||||
Only resources explicitly created and owned by the current component or manager should be disposed.
|
|
||||||
|
|
||||||
This includes things like:
|
|
||||||
|
|
||||||
- custom materials
|
|
||||||
- render targets
|
|
||||||
- postprocessing passes
|
|
||||||
- manually created geometries
|
|
||||||
- manually created textures
|
|
||||||
- temporary clones with owned resources
|
|
||||||
|
|
||||||
Shared or cached assets must **not** be blindly disposed.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// utils/Dispose.ts
|
|
||||||
import * as THREE from "three";
|
|
||||||
|
|
||||||
export class Dispose {
|
|
||||||
static material(material: THREE.Material): void {
|
|
||||||
for (const value of Object.values(material)) {
|
|
||||||
if (value instanceof THREE.Texture) {
|
|
||||||
value.dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
material.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
static mesh(mesh: THREE.Mesh): void {
|
|
||||||
mesh.geometry?.dispose();
|
|
||||||
|
|
||||||
const materials = Array.isArray(mesh.material)
|
|
||||||
? mesh.material
|
|
||||||
: [mesh.material];
|
|
||||||
|
|
||||||
for (const material of materials) {
|
|
||||||
if (material) this.material(material);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static renderTarget(rt: THREE.WebGLRenderTarget): void {
|
|
||||||
rt.texture.dispose();
|
|
||||||
rt.dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Example usage:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
useEffect(() => {
|
|
||||||
const material = new THREE.ShaderMaterial({
|
|
||||||
vertexShader,
|
|
||||||
fragmentShader,
|
|
||||||
});
|
|
||||||
|
|
||||||
meshRef.current.material = material;
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
Dispose.material(material);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
```
|
|
||||||
|
|
||||||
**Rule:** disposal is ownership-based, not automatic and not blind.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. Debug System
|
|
||||||
|
|
||||||
The debug panel can be activated by appending `?debug` to the URL:
|
|
||||||
|
|
||||||
`http://localhost:5173?debug`
|
|
||||||
|
|
||||||
All debug logic is centralized in `src/debug/`.
|
|
||||||
Do not scatter debug checks across the codebase.
|
|
||||||
|
|
||||||
- `src/debug/Debug.ts` owns the global lil-gui singleton
|
|
||||||
- `src/debug/DebugPerf.tsx` mounts the perf overlay
|
|
||||||
- `src/debug/scene/*` contains debug-only R3F helpers such as free camera controls and axes/grid helpers
|
|
||||||
|
|
||||||
`world/` stays focused on product scene components, while `debug/` contains developer tooling.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// src/debug/Debug.ts
|
|
||||||
import GUI from "lil-gui";
|
|
||||||
|
|
||||||
export class Debug {
|
|
||||||
private static _instance: Debug | null = null;
|
|
||||||
|
|
||||||
readonly active: boolean;
|
|
||||||
gui: GUI | null = null;
|
|
||||||
|
|
||||||
static getInstance(): Debug {
|
|
||||||
if (!Debug._instance) Debug._instance = new Debug();
|
|
||||||
return Debug._instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
private constructor() {
|
|
||||||
this.active = new URLSearchParams(window.location.search).has("debug");
|
|
||||||
if (this.active) {
|
|
||||||
this.gui = new GUI({ title: "La-Fabrik Debug" });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy(): void {
|
|
||||||
this.gui?.destroy();
|
|
||||||
Debug._instance = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
const debug = Debug.getInstance();
|
|
||||||
|
|
||||||
if (debug.active) {
|
|
||||||
debug.gui!.add(params, "bloomIntensity", 0, 3).name("Bloom");
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Debug-only scene helpers should live outside `world/`:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
// src/debug/scene/DebugHelpers.tsx
|
|
||||||
import { Debug } from "@/debug/Debug";
|
|
||||||
|
|
||||||
export function DebugHelpers(): React.JSX.Element | null {
|
|
||||||
const debug = Debug.getInstance();
|
|
||||||
|
|
||||||
if (!debug.active) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<gridHelper
|
|
||||||
args={[180, 36, "#1d4ed8", "#1e293b"]}
|
|
||||||
position={[0, 0.01, 0]}
|
|
||||||
/>
|
|
||||||
<axesHelper args={[10]} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
# Target Architecture
|
||||||
|
|
||||||
|
This document describes the intended medium-term architecture for the project.
|
||||||
|
|
||||||
|
## Goals
|
||||||
|
|
||||||
|
- Keep `main` stable, `develop` as the integration branch, and `feat/*` for feature work.
|
||||||
|
- Keep the runtime split between scene composition, gameplay systems, debug tooling, and HTML UI.
|
||||||
|
- Keep one clear source of truth per concern.
|
||||||
|
|
||||||
|
## Intended Layers
|
||||||
|
|
||||||
|
### App Layer
|
||||||
|
|
||||||
|
- `App.tsx` should stay small and orchestration-oriented.
|
||||||
|
- It should mount the canvas scene and top-level HTML overlays.
|
||||||
|
|
||||||
|
### World Layer
|
||||||
|
|
||||||
|
- `src/world/` should contain only production scene objects and scene composition.
|
||||||
|
- Expected responsibilities:
|
||||||
|
- world composition
|
||||||
|
- map/environment/lighting
|
||||||
|
- player controller
|
||||||
|
- zones
|
||||||
|
- post-processing used in production
|
||||||
|
|
||||||
|
### Debug Layer
|
||||||
|
|
||||||
|
- `src/utils/debug/` should contain only developer tooling.
|
||||||
|
- Expected responsibilities:
|
||||||
|
- `lil-gui`
|
||||||
|
- performance overlay
|
||||||
|
- scene helpers
|
||||||
|
- free camera and calibration controls
|
||||||
|
|
||||||
|
### UI Layer
|
||||||
|
|
||||||
|
- `src/components/ui/` should contain HTML overlays used by the player.
|
||||||
|
- Expected examples:
|
||||||
|
- crosshair
|
||||||
|
- loading screen
|
||||||
|
- mission HUD
|
||||||
|
- narrative overlays
|
||||||
|
|
||||||
|
### Gameplay Layer
|
||||||
|
|
||||||
|
- Gameplay state should eventually live in dedicated managers and thin hooks once those systems exist.
|
||||||
|
- Expected future concerns:
|
||||||
|
- missions
|
||||||
|
- zones
|
||||||
|
- cinematics
|
||||||
|
- audio
|
||||||
|
- interactions
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
- `world/` should not contain debug-only tooling.
|
||||||
|
- `debug/` should not own production gameplay systems.
|
||||||
|
- Shared types should live close to their domain and move outward only when they gain multiple real consumers.
|
||||||
|
- New files should only be created when they have an active runtime purpose.
|
||||||
+43
-2
@@ -1,3 +1,44 @@
|
|||||||
# Features
|
# Implemented Features
|
||||||
|
|
||||||
TODO: Documenter les fonctionnalités du jeu.
|
This document lists features that are actually implemented in the current codebase.
|
||||||
|
|
||||||
|
## Scene Preview
|
||||||
|
|
||||||
|
- Fullscreen React Three Fiber scene
|
||||||
|
- Blocking map loaded from `public/models/map/blocking/model.glb`
|
||||||
|
- Ambient and directional lighting
|
||||||
|
- Solid background environment color
|
||||||
|
|
||||||
|
## Camera Modes
|
||||||
|
|
||||||
|
- Player camera mode
|
||||||
|
- eye height at `1.75m`
|
||||||
|
- pointer lock mouse look
|
||||||
|
- movement with `ZQSD`
|
||||||
|
- vertical clamp to prevent falling below the map plane
|
||||||
|
- Debug camera mode
|
||||||
|
- free orbit camera
|
||||||
|
- switchable from the debug panel
|
||||||
|
|
||||||
|
## UI
|
||||||
|
|
||||||
|
- Center-screen crosshair shown only in player mode
|
||||||
|
|
||||||
|
## Debug Tooling
|
||||||
|
|
||||||
|
- `?debug` query param enables the debug panel
|
||||||
|
- `lil-gui` panel with camera mode selection
|
||||||
|
- debug lighting controls
|
||||||
|
- debug scene helpers
|
||||||
|
- `r3f-perf` overlay
|
||||||
|
|
||||||
|
## Not Implemented Yet
|
||||||
|
|
||||||
|
- missions
|
||||||
|
- interactions on `E`
|
||||||
|
- gameplay zones
|
||||||
|
- cinematics
|
||||||
|
- audio systems
|
||||||
|
- loading flow
|
||||||
|
- minimap and mission HUD
|
||||||
|
- collisions beyond the current simple player height clamp
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
# public/models/environment/\*
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
# public/models/farm/\*
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
# public/models/general/\*
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
# public/models/powergrid/\*
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
# public/models/workshop/\*
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
# public/sounds/\*
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
# public/textures/\*
|
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
import { Canvas } from "@react-three/fiber";
|
import { Canvas } from "@react-three/fiber";
|
||||||
import { Crosshair } from "@/components/ui/Crosshair";
|
import { Crosshair } from "@/components/ui/Crosshair";
|
||||||
import { DebugPerf } from "@/debug/DebugPerf";
|
import { DebugPerf } from "@/utils/debug/DebugPerf";
|
||||||
import { World } from "@/world/World";
|
import { World } from "@/world/World";
|
||||||
|
|
||||||
function App(): React.JSX.Element {
|
function App(): React.JSX.Element {
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
// src/components/3d/InteractiveObject.tsx
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
// src/components/ui/CinematicBars.tsx
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useCameraMode } from "@/debug/useCameraMode";
|
import { useCameraMode } from "@/hooks/debug/useCameraMode";
|
||||||
|
|
||||||
export function Crosshair(): React.JSX.Element | null {
|
export function Crosshair(): React.JSX.Element | null {
|
||||||
const cameraMode = useCameraMode();
|
const cameraMode = useCameraMode();
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
// src/components/ui/LoadingScreen.tsx
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
// src/components/ui/MapHUD.tsx
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
// src/components/ui/MissionHUD.tsx
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
// src/components/ui/NarrativeOverlay.tsx
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
// src/data/dialogues.ts
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
// src/data/missions.ts
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
// src/data/zones.ts
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useSyncExternalStore } from "react";
|
import { useSyncExternalStore } from "react";
|
||||||
import type { CameraMode } from "@/debug/Debug";
|
import type { CameraMode } from "@/types/debug";
|
||||||
import { Debug } from "@/debug/Debug";
|
import { Debug } from "@/utils/debug/Debug";
|
||||||
|
|
||||||
export function useCameraMode(): CameraMode {
|
export function useCameraMode(): CameraMode {
|
||||||
const debug = Debug.getInstance();
|
const debug = Debug.getInstance();
|
||||||
@@ -1 +0,0 @@
|
|||||||
// src/hooks/useAudio.ts
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
// src/hooks/useCinematic.ts
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
// src/hooks/useGameState.ts
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
// src/hooks/useInteraction.ts
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
// src/hooks/useLOD.ts
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
// src/hooks/useZoneDetection.ts
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
// src/stateManager/AudioManager.ts
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
// src/stateManager/CinematicManager.ts
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
// src/stateManager/GameManager.ts
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
// src/stateManager/ZoneManager.ts
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export type CameraMode = "player" | "debug";
|
||||||
@@ -1 +0,0 @@
|
|||||||
// src/utils/Dispose.ts
|
|
||||||
@@ -1 +1,54 @@
|
|||||||
// src/utils/EventEmitter.ts
|
type Listener<TPayload> = (payload: TPayload) => void;
|
||||||
|
|
||||||
|
export class EventEmitter<TEvents extends Record<string, unknown>> {
|
||||||
|
private readonly listeners = new Map<
|
||||||
|
keyof TEvents,
|
||||||
|
Set<Listener<TEvents[keyof TEvents]>>
|
||||||
|
>();
|
||||||
|
|
||||||
|
on<TKey extends keyof TEvents>(
|
||||||
|
event: TKey,
|
||||||
|
listener: Listener<TEvents[TKey]>,
|
||||||
|
): () => void {
|
||||||
|
const currentListeners = this.listeners.get(event) ?? new Set();
|
||||||
|
currentListeners.add(listener as Listener<TEvents[keyof TEvents]>);
|
||||||
|
this.listeners.set(event, currentListeners);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
this.off(event, listener);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
off<TKey extends keyof TEvents>(
|
||||||
|
event: TKey,
|
||||||
|
listener: Listener<TEvents[TKey]>,
|
||||||
|
): void {
|
||||||
|
const currentListeners = this.listeners.get(event);
|
||||||
|
|
||||||
|
if (!currentListeners) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentListeners.delete(listener as Listener<TEvents[keyof TEvents]>);
|
||||||
|
|
||||||
|
if (currentListeners.size === 0) {
|
||||||
|
this.listeners.delete(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emit<TKey extends keyof TEvents>(event: TKey, payload: TEvents[TKey]): void {
|
||||||
|
const currentListeners = this.listeners.get(event);
|
||||||
|
|
||||||
|
if (!currentListeners) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentListeners.forEach((listener) => {
|
||||||
|
listener(payload as TEvents[keyof TEvents]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
clear(): void {
|
||||||
|
this.listeners.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+50
-1
@@ -1 +1,50 @@
|
|||||||
// src/utils/Sizes.ts
|
type SizeSnapshot = {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
pixelRatio: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type SizeListener = (snapshot: SizeSnapshot) => void;
|
||||||
|
|
||||||
|
export class Sizes {
|
||||||
|
private snapshot: SizeSnapshot;
|
||||||
|
private readonly listeners = new Set<SizeListener>();
|
||||||
|
private readonly handleResize = (): void => {
|
||||||
|
this.snapshot = Sizes.readWindow();
|
||||||
|
this.emit();
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.snapshot = Sizes.readWindow();
|
||||||
|
window.addEventListener("resize", this.handleResize);
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribe(listener: SizeListener): () => void {
|
||||||
|
this.listeners.add(listener);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
this.listeners.delete(listener);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getSnapshot(): SizeSnapshot {
|
||||||
|
return this.snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy(): void {
|
||||||
|
window.removeEventListener("resize", this.handleResize);
|
||||||
|
this.listeners.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private emit(): void {
|
||||||
|
this.listeners.forEach((listener) => listener(this.snapshot));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readWindow(): SizeSnapshot {
|
||||||
|
return {
|
||||||
|
width: window.innerWidth,
|
||||||
|
height: window.innerHeight,
|
||||||
|
pixelRatio: Math.min(window.devicePixelRatio, 2),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+42
-1
@@ -1 +1,42 @@
|
|||||||
// src/utils/Time.ts
|
type TickListener = (delta: number, elapsed: number) => void;
|
||||||
|
|
||||||
|
export class Time {
|
||||||
|
private readonly listeners = new Set<TickListener>();
|
||||||
|
private animationFrameId = 0;
|
||||||
|
private lastTick = performance.now();
|
||||||
|
private elapsed = 0;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.tick = this.tick.bind(this);
|
||||||
|
this.animationFrameId = window.requestAnimationFrame(this.tick);
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribe(listener: TickListener): () => void {
|
||||||
|
this.listeners.add(listener);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
this.listeners.delete(listener);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getElapsed(): number {
|
||||||
|
return this.elapsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy(): void {
|
||||||
|
window.cancelAnimationFrame(this.animationFrameId);
|
||||||
|
this.listeners.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private tick(now: number): void {
|
||||||
|
const delta = (now - this.lastTick) / 1000;
|
||||||
|
this.lastTick = now;
|
||||||
|
this.elapsed += delta;
|
||||||
|
|
||||||
|
this.listeners.forEach((listener) => {
|
||||||
|
listener(delta, this.elapsed);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.animationFrameId = window.requestAnimationFrame(this.tick);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import GUI from "lil-gui";
|
import GUI from "lil-gui";
|
||||||
|
import type { CameraMode } from "@/types/debug";
|
||||||
export type CameraMode = "player" | "debug";
|
|
||||||
|
|
||||||
export class Debug {
|
export class Debug {
|
||||||
private static instance: Debug | null = null;
|
private static instance: Debug | null = null;
|
||||||
@@ -9,8 +8,9 @@ export class Debug {
|
|||||||
private readonly gui: GUI | null;
|
private readonly gui: GUI | null;
|
||||||
private readonly folders = new Map<string, GUI>();
|
private readonly folders = new Map<string, GUI>();
|
||||||
private readonly listeners = new Set<() => void>();
|
private readonly listeners = new Set<() => void>();
|
||||||
private readonly controls = { cameraMode: "player" as CameraMode };
|
private readonly controls: { cameraMode: CameraMode } = {
|
||||||
private cameraMode: CameraMode = "player";
|
cameraMode: "player",
|
||||||
|
};
|
||||||
|
|
||||||
static getInstance(): Debug {
|
static getInstance(): Debug {
|
||||||
if (!Debug.instance) {
|
if (!Debug.instance) {
|
||||||
@@ -27,17 +27,22 @@ export class Debug {
|
|||||||
if (this.gui) {
|
if (this.gui) {
|
||||||
const folder = this.createFolder("Debug");
|
const folder = this.createFolder("Debug");
|
||||||
|
|
||||||
|
if (!folder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
folder
|
folder
|
||||||
?.add(this.controls, "cameraMode", { Player: "player", Debug: "debug" })
|
.add(this.controls, "cameraMode", { Player: "player", Debug: "debug" })
|
||||||
.name("Camera Mode")
|
.name("Camera Mode")
|
||||||
.onChange((value: CameraMode) => {
|
.onChange((value: CameraMode) => {
|
||||||
this.controls.cameraMode = value;
|
this.controls.cameraMode = value;
|
||||||
this.cameraMode = value;
|
|
||||||
this.emit();
|
this.emit();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createFolder(name: string): GUI;
|
||||||
|
createFolder(name: string): GUI | null;
|
||||||
createFolder(name: string): GUI | null {
|
createFolder(name: string): GUI | null {
|
||||||
if (!this.gui) {
|
if (!this.gui) {
|
||||||
return null;
|
return null;
|
||||||
@@ -63,19 +68,8 @@ export class Debug {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
isDebugCameraEnabled(): boolean {
|
|
||||||
return this.cameraMode === "debug";
|
|
||||||
}
|
|
||||||
|
|
||||||
getCameraMode(): CameraMode {
|
getCameraMode(): CameraMode {
|
||||||
return this.cameraMode;
|
return this.controls.cameraMode;
|
||||||
}
|
|
||||||
|
|
||||||
destroy(): void {
|
|
||||||
this.listeners.clear();
|
|
||||||
this.folders.clear();
|
|
||||||
this.gui?.destroy();
|
|
||||||
Debug.instance = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private emit(): void {
|
private emit(): void {
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Suspense, lazy } from "react";
|
import { Suspense, lazy } from "react";
|
||||||
import { Debug } from "@/debug/Debug";
|
import { Debug } from "@/utils/debug/Debug";
|
||||||
|
|
||||||
const Perf = lazy(() => import("r3f-perf").then((m) => ({ default: m.Perf })));
|
const Perf = lazy(() => import("r3f-perf").then((m) => ({ default: m.Perf })));
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Debug } from "@/debug/Debug";
|
import { Debug } from "@/utils/debug/Debug";
|
||||||
|
|
||||||
export function DebugHelpers(): React.JSX.Element | null {
|
export function DebugHelpers(): React.JSX.Element | null {
|
||||||
const debug = Debug.getInstance();
|
const debug = Debug.getInstance();
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
import { useFrame } from "@react-three/fiber";
|
import { useFrame } from "@react-three/fiber";
|
||||||
import type { AmbientLight, DirectionalLight } from "three";
|
import type { AmbientLight, DirectionalLight } from "three";
|
||||||
import { Debug } from "@/debug/Debug";
|
import { Debug } from "@/utils/debug/Debug";
|
||||||
|
|
||||||
type LightingState = {
|
type LightingState = {
|
||||||
ambientIntensity: number;
|
ambientIntensity: number;
|
||||||
@@ -27,22 +27,16 @@ export function Lighting(): React.JSX.Element {
|
|||||||
const debug = Debug.getInstance();
|
const debug = Debug.getInstance();
|
||||||
|
|
||||||
if (!debug.active) {
|
if (!debug.active) {
|
||||||
return undefined;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const folder = debug.createFolder("Lighting");
|
const folder = debug.createFolder("Lighting");
|
||||||
|
|
||||||
if (!folder) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
folder.add(LIGHTING_STATE, "ambientIntensity", 0, 5, 0.1).name("Ambient");
|
folder.add(LIGHTING_STATE, "ambientIntensity", 0, 5, 0.1).name("Ambient");
|
||||||
folder.add(LIGHTING_STATE, "sunIntensity", 0, 8, 0.1).name("Sun Intensity");
|
folder.add(LIGHTING_STATE, "sunIntensity", 0, 8, 0.1).name("Sun Intensity");
|
||||||
folder.add(LIGHTING_STATE, "sunX", -100, 100, 1).name("Sun X");
|
folder.add(LIGHTING_STATE, "sunX", -100, 100, 1).name("Sun X");
|
||||||
folder.add(LIGHTING_STATE, "sunY", 0, 150, 1).name("Sun Y");
|
folder.add(LIGHTING_STATE, "sunY", 0, 150, 1).name("Sun Y");
|
||||||
folder.add(LIGHTING_STATE, "sunZ", -100, 100, 1).name("Sun Z");
|
folder.add(LIGHTING_STATE, "sunZ", -100, 100, 1).name("Sun Z");
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useFrame(() => {
|
useFrame(() => {
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
// src/world/PostFX.tsx
|
|
||||||
+3
-3
@@ -1,7 +1,7 @@
|
|||||||
import { Suspense } from "react";
|
import { Suspense } from "react";
|
||||||
import { DebugCameraControls } from "@/debug/scene/DebugCameraControls";
|
import { useCameraMode } from "@/hooks/debug/useCameraMode";
|
||||||
import { DebugHelpers } from "@/debug/scene/DebugHelpers";
|
import { DebugCameraControls } from "@/utils/debug/scene/DebugCameraControls";
|
||||||
import { useCameraMode } from "@/debug/useCameraMode";
|
import { DebugHelpers } from "@/utils/debug/scene/DebugHelpers";
|
||||||
import { Environment } from "@/world/Environment";
|
import { Environment } from "@/world/Environment";
|
||||||
import { Lighting } from "@/world/Lighting";
|
import { Lighting } from "@/world/Lighting";
|
||||||
import { Map } from "@/world/Map";
|
import { Map } from "@/world/Map";
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
// src/world/player/Crosshair.tsx
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
// src/world/zones/FarmZone.tsx
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
// src/world/zones/PowerGridZone.tsx
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
// src/world/zones/ResidentialZone.tsx
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
// src/world/zones/SchoolZone.tsx
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
// src/world/zones/WorkshopZone.tsx
|
|
||||||
@@ -7,6 +7,9 @@
|
|||||||
"types": ["vite/client"],
|
"types": ["vite/client"],
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"ignoreDeprecations": "6.0",
|
"ignoreDeprecations": "6.0",
|
||||||
|
"strict": true,
|
||||||
|
"exactOptionalPropertyTypes": true,
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
|
||||||
/* Bundler mode */
|
/* Bundler mode */
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
|
|||||||
@@ -6,6 +6,9 @@
|
|||||||
"module": "esnext",
|
"module": "esnext",
|
||||||
"types": ["node"],
|
"types": ["node"],
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
|
"exactOptionalPropertyTypes": true,
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
|
||||||
/* Bundler mode */
|
/* Bundler mode */
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
|
|||||||
Reference in New Issue
Block a user