import * as THREE from "three"; import { OrbitControls } from "three/addons/controls/OrbitControls.js"; import { BaseScene } from "./BaseScene"; import { SceneKeys } from "../SceneKeys"; import { GameConfig } from "../GameConfig"; /** * 主游戏场景(空白骨架)。 * * 执行脚手架后,此场景显示旋转的 Torus Knot + 网格 + 坐标轴, * 作为开发起点。替换 onInit() / onUpdate() 中的内容即可开始编写游戏逻辑。 */ export class MainScene extends BaseScene { private controls: OrbitControls | null = null; private torusKnot: THREE.Mesh | null = null; private fpsDiv: HTMLDivElement | null = null; private frameCount = 0; private fpsTime = 0; constructor() { super(SceneKeys.Main); } protected onEnter(): void { const overlay = document.getElementById("loading-overlay"); if (overlay) overlay.classList.add("hidden"); } protected onInit(): void { if (!this._camera) return; // ─── 背景 ─── this.scene.background = new THREE.Color(GameConfig.clearColor); this.scene.fog = new THREE.Fog(GameConfig.clearColor, 20, 80); // ─── 灯光 ─── const { lighting } = GameConfig; const ambient = new THREE.AmbientLight(lighting.ambientColor, lighting.ambientIntensity); this.scene.add(ambient); const dirLight = new THREE.DirectionalLight(lighting.directionalColor, lighting.directionalIntensity); dirLight.position.set( lighting.directionalPosition.x, lighting.directionalPosition.y, lighting.directionalPosition.z, ); dirLight.castShadow = GameConfig.shadows; if (dirLight.shadow) { dirLight.shadow.mapSize.setScalar(1024); dirLight.shadow.camera.near = 0.5; dirLight.shadow.camera.far = 50; const s = 10; dirLight.shadow.camera.left = -s; dirLight.shadow.camera.right = s; dirLight.shadow.camera.top = s; dirLight.shadow.camera.bottom = -s; } this.scene.add(dirLight); // ─── 地面 ─── const groundGeo = new THREE.PlaneGeometry(30, 30); const groundMat = new THREE.MeshStandardMaterial({ color: 0x222244, roughness: 0.9, metalness: 0.1, }); const ground = new THREE.Mesh(groundGeo, groundMat); ground.rotation.x = -Math.PI / 2; ground.receiveShadow = true; this.scene.add(ground); // ─── 参考网格 + 坐标轴 ─── if (GameConfig.showGrid) { const grid = new THREE.GridHelper(20, 20, 0x444466, 0x333355); grid.position.y = 0.01; this.scene.add(grid); const axes = new THREE.AxesHelper(3); axes.position.y = 0.02; this.scene.add(axes); } // ─── Hello Three.js — 旋转 Torus Knot ─── const geometry = new THREE.TorusKnotGeometry(0.8, 0.3, 128, 32); const material = new THREE.MeshStandardMaterial({ color: 0x4488ff, roughness: 0.3, metalness: 0.6, }); this.torusKnot = new THREE.Mesh(geometry, material); this.torusKnot.position.y = 1.5; this.torusKnot.castShadow = true; this.scene.add(this.torusKnot); // ─── "Hello Three.js!" 文字精灵 ─── const canvas = document.createElement("canvas"); canvas.width = 512; canvas.height = 128; const ctx = canvas.getContext("2d")!; ctx.fillStyle = "transparent"; ctx.fillRect(0, 0, 512, 128); ctx.font = "bold 48px Arial, sans-serif"; ctx.fillStyle = "#ffffff"; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillText("Hello Three.js!", 256, 64); const spriteTex = new THREE.CanvasTexture(canvas); const spriteMat = new THREE.SpriteMaterial({ map: spriteTex, transparent: true }); const sprite = new THREE.Sprite(spriteMat); sprite.position.set(0, 3.2, 0); sprite.scale.set(4, 1, 1); this.scene.add(sprite); // ─── OrbitControls ─── if (GameConfig.enableOrbitControls) { const rendererDom = document.querySelector("#game-container canvas"); if (rendererDom) { this.controls = new OrbitControls(this._camera, rendererDom as HTMLElement); this.controls.enableDamping = true; this.controls.dampingFactor = 0.08; this.controls.target.set(0, 1, 0); this.controls.update(); } } // ─── FPS 显示 ─── if (GameConfig.showFps) { this.fpsDiv = document.createElement("div"); Object.assign(this.fpsDiv.style, { position: "fixed", top: "8px", left: "8px", padding: "2px 6px", background: "rgba(0,0,0,0.5)", color: "#0c0", fontSize: "12px", fontFamily: "monospace", zIndex: "9999", pointerEvents: "none", }); document.body.appendChild(this.fpsDiv); } } protected onUpdate(dt: number, elapsed: number): void { if (this.torusKnot) { this.torusKnot.rotation.y = elapsed * 0.5; this.torusKnot.rotation.x = elapsed * 0.3; } this.controls?.update(); if (this.fpsDiv) { this.frameCount++; this.fpsTime += dt; if (this.fpsTime >= 0.5) { this.fpsDiv.textContent = `FPS: ${Math.round(this.frameCount / this.fpsTime)}`; this.frameCount = 0; this.fpsTime = 0; } } } protected onDispose(): void { this.controls?.dispose(); this.fpsDiv?.remove(); } }