import * as THREE from "three"; import { GameConfig } from "./GameConfig"; import { SceneManager } from "./SceneManager"; import { SceneKeys } from "./SceneKeys"; import { PreloaderScene } from "./scenes/PreloaderScene"; import { MainScene } from "./scenes/MainScene"; import { ViewportManager } from "./utils/ViewportManager"; export type GameInstance = { renderer: THREE.WebGLRenderer; camera: THREE.PerspectiveCamera; sceneManager: SceneManager; viewportManager: ViewportManager; clock: THREE.Clock; dispose: () => void; }; /** * 创建并启动 Three.js 游戏实例。 * * 自动配置:WebGLRenderer(抗锯齿 + 阴影 + 色调映射)、 * PerspectiveCamera、SceneManager(Preloader → Main 生命周期)、 * ViewportManager(响应式 resize)、requestAnimationFrame 主循环。 */ export function StartGame(containerId: string): GameInstance { const container = document.getElementById(containerId); if (!container) throw new Error(`Container #${containerId} not found`); // ─── Renderer ─── const renderer = new THREE.WebGLRenderer({ antialias: GameConfig.antialias, alpha: false, powerPreference: "high-performance", }); renderer.setPixelRatio(Math.min(window.devicePixelRatio, GameConfig.maxPixelRatio)); renderer.setSize(container.clientWidth, container.clientHeight); renderer.shadowMap.enabled = GameConfig.shadows; renderer.shadowMap.type = THREE.PCFSoftShadowMap; const tmMap: Record = { ACESFilmic: THREE.ACESFilmicToneMapping, Linear: THREE.LinearToneMapping, Reinhard: THREE.ReinhardToneMapping, Cineon: THREE.CineonToneMapping, }; renderer.toneMapping = tmMap[GameConfig.toneMapping] ?? THREE.ACESFilmicToneMapping; renderer.toneMappingExposure = GameConfig.toneMappingExposure; container.appendChild(renderer.domElement); // ─── Camera ─── const aspect = container.clientWidth / container.clientHeight; const cam = GameConfig.camera; const camera = new THREE.PerspectiveCamera(cam.fov, aspect, cam.near, cam.far); camera.position.set(cam.position.x, cam.position.y, cam.position.z); camera.lookAt(cam.lookAt.x, cam.lookAt.y, cam.lookAt.z); // ─── Scene Manager ─── const sceneManager = new SceneManager(camera); sceneManager.register(new PreloaderScene(), new MainScene()); // ─── Viewport Manager ─── const viewportManager = new ViewportManager(renderer, camera, container); viewportManager.enable(); // ─── Start first scene ─── const startScene = GameConfig.debugScene === "main" ? SceneKeys.Main : SceneKeys.Preloader; sceneManager.switchTo(startScene); // ─── Animation Loop ─── const clock = new THREE.Clock(); let animFrameId = 0; function animate() { animFrameId = requestAnimationFrame(animate); const dt = clock.getDelta(); const elapsed = clock.getElapsedTime(); sceneManager.update(dt, elapsed); const activeScene = sceneManager.activeThreeScene; if (activeScene) { renderer.render(activeScene, camera); } } animate(); // ─── Dispose ─── function dispose() { cancelAnimationFrame(animFrameId); viewportManager.disable(); sceneManager.dispose(); renderer.dispose(); renderer.domElement.remove(); } const instance: GameInstance = { renderer, camera, sceneManager, viewportManager, clock, dispose, }; if (typeof __DEV__ !== "undefined" && __DEV__) { (window as any).__THREE_GAME__ = instance; (window as any).THREE = THREE; console.log("[Game] Started", { viewport: { w: container.clientWidth, h: container.clientHeight, dpr: window.devicePixelRatio }, renderer: { antialias: GameConfig.antialias, shadows: GameConfig.shadows }, }); } return instance; }