想继续开发一个可以峦叠起伏的地形进入游戏,但是一直搞了两天一直搞不出来,有没有网友在游戏开发方面有点经验的给我点指导。
下面是我目前的效果:
具体代码就不详细介绍了,直接贴代码自己复制一下就行了。
<template>
<div class="current-page">
<canvas
id="draw"
class="draw"
style="border: 1px solid; background-color: #000"
></canvas>
<div class="game-ui">
<div class="health-bar">
<div class="health" :style="{ width: playerHealth + '%' }"></div>
</div>
<div class="score">得分: {{ score }}</div>
<button v-if="!gameStarted" @click="startGame">开始游戏</button>
</div>
</div>
</template>
<script setup lang="ts">
import * as THREE from "three";
import Stats from "three/addons/libs/stats.module.js";
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
import { onMounted, onUnmounted } from "vue";
// import { getThreeControls } from "../three/interactiveControls.ts";
import { DRACOLoader } from "three/addons/loaders/DRACOLoader.js";
import { getHoursData } from "./intehours.ts";
import { managerHome } from "./miniHomeManager.ts";
import { createNoise2D } from "simplex-noise";
import grass from "@/assets/img/grass.jpg";
// 新增:Vue响应式变量
import { ref } from "vue";
// Three.js 核心对象
let scene: THREE.Scene;
let renderer: THREE.WebGLRenderer;
let camera: THREE.PerspectiveCamera;
let stats: Stats;
let model: THREE.Group;
let mixer: THREE.AnimationMixer;
let clock: THREE.Clock;
let houseGroups: THREE.Group[] = []; // 存储所有房屋组
let forestHouse: THREE.Group | null = null; // 新增:森林小屋模型引用
// const SimplexNoise = createNoise3D();
let noise2D = createNoise2D();
let canvas: HTMLCanvasElement; // 添加到文件顶部的变量声明区域
// 动作控制
// 新增:游戏相关变量
let monsters: THREE.Group[] = [];
let playerHealth = ref(100);
let score = ref(0);
let gameStarted = ref(false);
let attackCooldown = false;
const monsterSpawnInterval = 5000; // 5秒生成一个怪物
// 新增:开始游戏
function startGame() {
gameStarted.value = true;
spawnMonster();
}
let currentBaseAction: keyof typeof baseActions = "idle";
const baseActions = {
idle: { weight: 1, action: null as THREE.AnimationAction | null },
walk: { weight: 0, action: null as THREE.AnimationAction | null },
run: { weight: 0, action: null as THREE.AnimationAction | null },
attack: { weight: 0, action: null as THREE.AnimationAction | null }, // 新增攻击动作
};
interface DoorRef {
meshes: THREE.Mesh<THREE.BoxGeometry, THREE.MeshPhongMaterial>[];
groups: THREE.Group[];
}
let doorRef: DoorRef = {
meshes: [],
groups: [],
};
// 键盘状态
let keyStates = {
W: false,
S: false,
A: false,
D: false,
shift: false,
};
// 运动参数
const v = new THREE.Vector3();
console.log("运动参数", v);
const a = 300; // 加速度
const maxSpeed = 50000; // 最大速度
let { houseSize, housePosition, housePositiontwo } = getHoursData();
let { createWalls, createNoDoorWall, createDoorWall, createRoof, createDoor } =
managerHome();
// 射线投射器
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
// 门的开关状态
const doorStates = new Map<THREE.Mesh, boolean>();
function initThree() {
canvas = document.getElementById("draw") as HTMLCanvasElement;
if (!canvas) {
console.error("Canvas元素未找到!");
return;
}
// 初始化场景
scene = new THREE.Scene();
scene.background = new THREE.Color(0x87ceeb); // 天空蓝背景
scene.fog = new THREE.Fog(0xa0a0a0, 10, 50);
// 设置光源
const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444, 1);
hemiLight.position.set(0, 100, 0);
scene.add(hemiLight);
const dirLight = new THREE.DirectionalLight(0xffffff, 1);
dirLight.position.set(-50, 100, -30);
dirLight.castShadow = true;
// 优化阴影设置
dirLight.shadow.camera.left = -100;
dirLight.shadow.camera.right = 100;
dirLight.shadow.camera.top = 100;
dirLight.shadow.camera.bottom = -100;
dirLight.shadow.camera.far = 200;
dirLight.shadow.mapSize.width = 1024; // 降低阴影分辨率以提高性能
dirLight.shadow.mapSize.height = 1024;
scene.add(dirLight);
// 创建地形
createTerrain();
// 添加地面网格(优化)
const grid = new THREE.GridHelper(5000, 500, 0xdfff00, 0xdfff00);
(grid.material as THREE.Material).opacity = 0.5;
(grid.material as THREE.Material).transparent = true;
scene.add(grid);
// 初始化相机
camera = new THREE.PerspectiveCamera(
65,
window.innerWidth / window.innerHeight,
0.1,
500 // 缩短视距以提高性能
);
camera.position.set(0, 1.6, -2.5);
// 初始化渲染器(优化)
renderer = new THREE.WebGLRenderer({
canvas,
antialias: true,
powerPreference: "high-performance", // 优化性能
});
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); // 限制最大像素比
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap; // 优化阴影渲染
renderer.sortObjects = false; // 禁用对象排序以提高性能
// 添加性能监视器
stats = new Stats();
document.body.appendChild(stats.dom);
// 创建小屋
initHouse(houseSize, housePosition, "house1");
initHouse(houseSize, housePositiontwo, "house2");
// 加载模型
loadModel();
loadForestHouse(); // 新增:加载森林小屋
}
// 新增:创建地形
function createTerrain() {
// 地形参数(优化)
const width = 2000;
const height = 2000;
const widthSegments = 50;
const heightSegments = 50;
const geometry = new THREE.PlaneGeometry(
width,
height,
widthSegments,
heightSegments
);
// 使用噪声生成高度
const vertices = geometry.attributes.position;
const positionArray = vertices.array as Float32Array;
console.log("1111", vertices);
// 使用更高效的方式更新顶点位置
for (let i = 0; i < vertices.count; i++) {
const index = i * 3;
const x = positionArray[index];
const z = positionArray[index + 2];
// 优化噪声计算
let y = noise2D(x * 0.005, z * 0.005) * 20; // 降低地形高度
y += noise2D(x * 0.01, z * 0.01) * 10;
y += noise2D(x * 0.05, z * 0.05) * 5;
// 使用更高效的边界限制
y = Math.max(-10, Math.min(10, y));
positionArray[index + 1] = y;
}
// 更新法线以便光照正确
geometry.computeVertexNormals();
// 创建材质
const textureLoader = new THREE.TextureLoader();
let material;
try {
const grassTexture = textureLoader.load(
grass,
() => {
console.log("纹理加载成功");
if (grassTexture) {
grassTexture.wrapS = grassTexture.wrapT = THREE.RepeatWrapping;
grassTexture.repeat.set(100, 100); // 增加重复次数使纹理更明显
}
},
undefined,
(error) => {
console.error("纹理加载失败:", error);
// 纹理加载失败时使用纯色材质
material = new THREE.MeshStandardMaterial({
color: 0x228b22, // 森林绿
side: THREE.DoubleSide,
wireframe: false,
flatShading: false,
});
}
);
if (grassTexture) {
grassTexture.wrapS = grassTexture.wrapT = THREE.RepeatWrapping;
grassTexture.repeat.set(100, 100);
material = new THREE.MeshStandardMaterial({
map: grassTexture,
side: THREE.DoubleSide,
wireframe: false,
flatShading: false,
});
} else {
// 备用材质
material = new THREE.MeshStandardMaterial({
color: 0x228b22, // 森林绿
side: THREE.DoubleSide,
wireframe: false,
flatShading: false,
});
}
} catch (error) {
console.error("纹理加载异常:", error);
// 异常时使用纯色材质
material = new THREE.MeshStandardMaterial({
color: 0x228b22, // 森林绿
side: THREE.DoubleSide,
wireframe: false,
flatShading: false,
});
}
const terrain = new THREE.Mesh(geometry, material);
terrain.rotation.x = -Math.PI / 2;
terrain.position.y = -10; // 调整地形位置使其更明显
terrain.receiveShadow = true;
terrain.castShadow = true;
terrain.name = "terrain";
console.log("确定是否添加了地形纹理", terrain);
scene.add(terrain);
// 添加碰撞检测
terrain.userData.isCollidable = true;
// 添加辅助线框以便观察地形
const wireframe = new THREE.WireframeGeometry(geometry);
const line = new THREE.LineSegments(wireframe);
// 修复类型问题:确保 material 是单个 Material 对象而不是数组
if (Array.isArray(line.material)) {
line.material[0].depthTest = false;
line.material[0].opacity = 0.25;
line.material[0].transparent = true;
} else {
line.material.depthTest = false;
line.material.opacity = 0.25;
line.material.transparent = true;
}
line.position.copy(terrain.position);
line.rotation.copy(terrain.rotation);
scene.add(line);
}
// 修改后的initHouse函数,添加name参数
function initHouse(
sizeData: {
baseWidth: number;
baseLength: number;
baseHeight: number;
baseThickness: number;
peakHeight: number;
peakWidth: number;
doorWidth: number;
doorHeight: number;
baseImg: any;
tileUrl: any;
doorImg: any;
},
positionData: { x: any; y: any; z: any },
name: string // 添加name参数
) {
const houseGroup = new THREE.Group();
houseGroup.name = name; // 设置组名称
createWalls(sizeData, positionData, houseGroup);
createNoDoorWall(sizeData, positionData, houseGroup);
createDoorWall(sizeData, positionData, houseGroup);
createRoof(sizeData, positionData, houseGroup);
const doorResult = createDoor(sizeData, positionData, houseGroup, {
meshes: doorRef.meshes,
groups: doorRef.groups,
});
// 为门添加交互标识
if (doorResult) {
doorResult.traverse((child) => {
if (child instanceof THREE.Mesh) {
child.userData.isDoor = true;
doorStates.set(child, false); // 初始状态为关闭
console.log(`门 ${child.name} 的初始状态:`, doorStates.get(child));
}
});
}
scene.add(houseGroup);
houseGroups.push(houseGroup); // 添加到房屋组数组
// console.log('所有房屋组',houseGroups);
}
function loadModel() {
const loader = new GLTFLoader();
loader.load(
"/resources/base/Xbot.glb",
(gltf) => {
model = gltf.scene;
const cameraGroup = new THREE.Group();
cameraGroup.name = "cameraGroup";
cameraGroup.add(camera);
model.add(cameraGroup);
model.scale.set(2, 3, 2); // 调整模型大小
camera.position.z = -3;
camera.lookAt(0, 3.8, 0);
// 新增:查找攻击动画
const attackClip = gltf.animations.find((anim) =>
anim.name.includes("Attack")
);
if (attackClip) {
baseActions.attack.action = mixer.clipAction(attackClip);
}
scene.add(model);
model.traverse((obj) => {
// 设置皮肤颜色
if (obj instanceof THREE.Mesh) obj.castShadow = true;
});
initAnimations(gltf.animations);
addDebugCube();
animate();
},
undefined,
(error) => {
console.error("模型加载失败:", error);
}
);
}
function loadForestHouse() {
const loader = new GLTFLoader();
// 设置DRACO解码器 (用于压缩模型)
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath(
"https://www.gstatic.com/draco/versioned/decoders/1.5.6/"
);
loader.setDRACOLoader(dracoLoader);
loader.load(
"/resources/base/forest_house.glb",
(gltf) => {
forestHouse = gltf.scene;
// 调整模型位置和大小
forestHouse.position.set(80, -2.5, 10); // 设置初始位置
forestHouse.scale.set(10, 10, 10); // 调整模型大小
forestHouse.rotation.y = Math.PI / 2; // 调整模型朝向
// 遍历模型设置阴影
forestHouse.traverse((child) => {
if (child instanceof THREE.Mesh) {
child.castShadow = true;
child.receiveShadow = true;
}
});
scene.add(forestHouse);
console.log("森林小屋模型加载成功");
},
(xhr) => {
console.log(
`森林小屋加载进度: ${((xhr.loaded / xhr.total) * 100).toFixed(2)}%`
);
},
(error) => {
console.error("加载森林小屋模型失败:", error);
console.log("尝试加载的URL:", "/resources/base/forest_house.glb");
}
);
}
// 新增:加载怪物模型
function loadMonsterModel(callback: (model: THREE.Group) => void) {
const loader = new GLTFLoader();
loader.load(
"/resources/base/Horse.glb", // 替换为你的怪物模型路径
(gltf) => {
const monster = gltf.scene;
monster.scale.set(0.03, 0.03, 0.03);
// model.scale.set(2, 3, 2); // 调整模型大小
callback(monster);
},
undefined,
(error) => {
console.error("怪物模型加载失败:", error);
}
);
}
// 新增:生成怪物
function spawnMonster() {
if (!gameStarted.value) return;
loadMonsterModel((monster) => {
// 随机生成位置
const angle = Math.random() * Math.PI * 2;
const distance = 10 + Math.random() * 10;
const x = Math.cos(angle) * distance;
const z = Math.sin(angle) * distance;
monster.position.set(x, 0, z);
monster.userData.health = 30; // 怪物生命值
monster.userData.speed = 1 + Math.random(); // 随机速度
scene.add(monster);
monsters.push(monster);
// 5秒后再次生成怪物
setTimeout(spawnMonster, monsterSpawnInterval);
});
}
// 新增:攻击逻辑
function performAttack() {
if (attackCooldown) return;
// 播放攻击动画
prepareCrossFade(currentBaseAction, "attack", 0.1);
// 使用空间分区优化攻击检测
const attackRange = 50; // 降低攻击范围
const attackPosition = new THREE.Vector3();
model.getWorldPosition(attackPosition);
// 使用球体进行粗检测
const attackSphere = new THREE.Sphere(attackPosition, attackRange);
monsters = monsters.filter((monster) => {
const monsterPos = new THREE.Vector3();
monster.getWorldPosition(monsterPos);
if (attackSphere.containsPoint(monsterPos)) {
// 粗检测通过后再进行精确距离计算
const distance = attackPosition.distanceTo(monsterPos);
if (distance < attackRange) {
monster.userData.health -= 10;
if (monster.userData.health <= 0) {
scene.remove(monster);
score.value += 10;
return false;
}
}
}
return true;
});
attackCooldown = true;
setTimeout(() => {
attackCooldown = false;
prepareCrossFade("attack", "idle", 0.2);
}, 300); // 缩短冷却时间
}
// 新增:怪物AI
function updateMonsters(delta: number) {
const playerPosition = model.position;
monsters.forEach((monster) => {
// 使用向量运算优化移动逻辑
const direction = new THREE.Vector3()
.subVectors(playerPosition, monster.position)
.normalize();
monster.position.add(
direction.multiplyScalar(monster.userData.speed * delta)
);
// 优化碰撞伤害检测
if (monster.position.distanceTo(playerPosition) < 1.5) {
playerHealth.value -= 0.5;
if (playerHealth.value <= 0) {
gameOver();
}
}
});
}
// 新增:游戏结束
function gameOver() {
gameStarted.value = false;
alert(`游戏结束!你的得分: ${score}`);
resetGame();
}
// 新增:重置游戏
function resetGame() {
monsters.forEach((monster) => scene.remove(monster));
monsters = [];
playerHealth.value = 100;
score.value = 0;
}
// 优化后的碰撞检测函数
function checkCollision(object: THREE.Group, group: THREE.Group): boolean {
const playerBox = new THREE.Box3().setFromObject(object);
// 使用空间分区优化碰撞检测
const boundingSphere = new THREE.Sphere();
playerBox.getBoundingSphere(boundingSphere);
// 检测与房屋的碰撞
return group.children.some((child) => {
if (child.name.includes("Wall") || child.name === "Collider") {
// 先进行球体碰撞粗检测
const childBox = new THREE.Box3().setFromObject(child);
const childSphere = new THREE.Sphere();
childBox.getBoundingSphere(childSphere);
if (boundingSphere.intersectsSphere(childSphere)) {
// 粗检测通过后再进行精确检测
child.updateMatrixWorld();
const objectBox = new THREE.Box3().setFromObject(child);
return playerBox.intersectsBox(objectBox);
}
}
return false;
});
}
function checkCollisionDoor(doorGroup: THREE.Group): boolean {
const hitBox = doorGroup.getObjectByName("doorHitBox") as THREE.Mesh;
if (!hitBox) return false;
// 更新碰撞体的世界矩阵
hitBox.updateMatrixWorld();
const hitBoxBounds = new THREE.Box3().setFromObject(hitBox);
// 检测与所有碰撞体的碰撞
return scene.children.some((obj) => {
if (
!obj.userData.isCollidable ||
obj === doorGroup ||
doorGroup.children.includes(obj)
) {
return false;
}
obj.updateMatrixWorld();
const objBounds = new THREE.Box3().setFromObject(obj);
return hitBoxBounds.intersectsBox(objBounds);
});
}
function initAnimations(animations: THREE.AnimationClip[]) {
clock = new THREE.Clock();
mixer = new THREE.AnimationMixer(model);
animations.forEach((clip) => {
const name = clip.name as keyof typeof baseActions; // 添加类型断言
// console.log("模型包含动画:", baseActions, "个动画剪辑");
if (baseActions[name]) {
const action = mixer.clipAction(clip);
baseActions[name].action = action;
action.play();
}
});
if (baseActions.idle.action) {
setWeight(baseActions.idle.action, 1);
}
}
function animate() {
requestAnimationFrame(animate);
const delta = clock.getDelta();
if (mixer) mixer.update(delta);
handleMovement(delta);
if (gameStarted.value) updateMonsters(delta); // 新增:更新怪物行为
// 调试信息
if (scene) {
const terrain = scene.getObjectByName("terrain");
if (terrain) {
// console.log("地形存在,位置:", terrain.position);
} else {
// console.log("地形不存在");
}
}
renderer.render(scene, camera);
stats.update();
}
// 优化后的移动处理函数
function handleMovement(delta: number) {
if (!model) return;
const previousPosition = model.position.clone();
v.set(0, 0, 0);
if (keyStates.W) {
const front = new THREE.Vector3();
model.getWorldDirection(front);
// console.log("判断运动状态", currentBaseAction);
if (keyStates.shift) {
prepareCrossFade(currentBaseAction, "run", 0.2);
v.add(front.multiplyScalar(5 * a * delta));
} else {
prepareCrossFade(currentBaseAction, "walk", 0.2);
v.add(front.multiplyScalar(a * delta));
}
} else if (keyStates.S) {
// console.log("判断运动状态", currentBaseAction);
const back = new THREE.Vector3();
model.getWorldDirection(back);
prepareCrossFade(currentBaseAction, "walk", 0.2);
v.add(back.multiplyScalar(-a * delta));
}
if (keyStates.A) {
// console.log("判断运动状态", currentBaseAction);
const left = new THREE.Vector3();
model.getWorldDirection(left);
left.crossVectors(new THREE.Vector3(0, 1, 0), left);
prepareCrossFade(currentBaseAction, "walk", 0.2);
v.add(left.multiplyScalar(a * delta));
}
if (keyStates.D) {
// console.log("判断运动状态", currentBaseAction);
const right = new THREE.Vector3();
model.getWorldDirection(right);
right.crossVectors(new THREE.Vector3(0, 1, 0), right).multiplyScalar(-1);
prepareCrossFade(currentBaseAction, "walk", 0.2);
v.add(right.multiplyScalar(a * delta));
}
if (!keyStates.W && !keyStates.S && !keyStates.A && !keyStates.D) {
prepareCrossFade(currentBaseAction, "idle", 0.2);
}
if (v.length() > maxSpeed) {
v.normalize().multiplyScalar(maxSpeed);
}
v.multiplyScalar(0.98);
model.position.add(v.clone().multiplyScalar(delta));
// 使用已存储的houseGroups数组进行碰撞检测
const collided = houseGroups.some((group) => {
return checkCollision(model, group);
});
if (collided) {
model.position.copy(previousPosition);
}
}
function prepareCrossFade(
currentActionName: keyof typeof baseActions,
targetActionName: keyof typeof baseActions,
duration: number
) {
if (currentActionName === targetActionName) return;
const currentAction = baseActions[currentActionName]?.action;
const targetAction = baseActions[targetActionName]?.action;
if (!targetAction) return;
if (currentAction) {
currentAction.fadeOut(duration);
}
targetAction.fadeIn(duration);
targetAction.play();
currentBaseAction = targetActionName;
}
function setWeight(action: THREE.AnimationAction, weight: number) {
action.enabled = true;
action.setEffectiveTimeScale(1);
action.setEffectiveWeight(weight);
}
function addDebugCube() {
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
cube.position.set(0, 0.5, 0);
scene.add(cube);
setTimeout(() => {
scene.remove(cube);
}, 1000);
}
function setupEventListeners() {
window.addEventListener("keydown", (e) => {
const key = e.key.toUpperCase();
if (key in keyStates) {
keyStates[key as keyof typeof keyStates] = true;
if (e.key == "shift") {
keyStates.shift = true;
}
}
if (e.key === "v") {
const cameraGroup = model?.getObjectByName("cameraGroup") as THREE.Group;
if (cameraGroup) {
camera.position.z = camera.position.z === -2.5 ? 0.8 : -2.5;
}
}
if (e.key === " ") {
console.log("执行攻击!!");
performAttack();
}
if (e.key === "g" && !gameStarted.value) {
gameStarted.value = true;
spawnMonster();
}
});
window.addEventListener("keyup", (e) => {
const key = e.key.toUpperCase();
if (key in keyStates) {
keyStates[key as keyof typeof keyStates] = false;
if (e.key === "shift") {
keyStates.shift = false;
}
}
});
document.addEventListener("mousedown", (e) => {
if (!scene || !camera || !canvas) return;
const rect = canvas.getBoundingClientRect();
if (keyStates.W || keyStates.A || keyStates.S || keyStates.D) {
document.body.requestPointerLock();
} else {
if (e.button === 0) {
// 鼠标左键
// 计算鼠标在标准化设备坐标中的位置
mouse.x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
mouse.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
// 通过鼠标位置更新射线
// 增强射线检测参数
raycaster.params.Points.threshold = 0.1; // 提高点检测阈值
raycaster.params.Line.threshold = 0.1; // 提高线检测阈值
raycaster.setFromCamera(mouse, camera);
// 计算射线与场景中物体的交点
const intersects = raycaster.intersectObjects(scene.children, true);
for (let i = 0; i < intersects.length; i++) {
const intersect = intersects[i];
const object = intersect.object;
handleDoorClick(object as THREE.Object3D); // 添加类型断言
}
}
}
});
document.addEventListener("mousemove", (e) => {
if (document.pointerLockElement === document.body && model) {
model.rotation.y -= e.movementX / 600;
const cameraGroup = model.getObjectByName("cameraGroup") as THREE.Group;
if (cameraGroup) {
const angleMin = THREE.MathUtils.degToRad(-15);
const angleMax = THREE.MathUtils.degToRad(15);
let angle = cameraGroup.rotation.x + e.movementY / 600;
if (angle > angleMin && angle < angleMax) {
cameraGroup.rotation.x = angle;
}
}
}
});
window.addEventListener("resize", () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
}
function handleDoorClick(doorObject: THREE.Object3D) {
// console.log('点击门:', doorObject)
// === 修改点1:增强验证逻辑 ===
// 检查是否是门对象或碰撞体
const isActualDoor = doorObject.name === "door" || doorObject.userData.isDoor;
const isHitBox =
doorObject.name === "doorHitBox" && doorObject.userData.isDoor;
if (!isActualDoor && !isHitBox) {
// console.error("Clicked object is not a door:", doorObject)
return;
}
// 确定目标门对象
const targetDoor = isHitBox ? doorObject.parent : doorObject;
// === 修改点2:增强门组查找逻辑 ===
const doorGroup = doorRef.groups.find((group) =>
group.children.some(
(child) =>
child === targetDoor || child.children.some((c) => c === targetDoor)
)
);
if (!doorGroup) {
// console.error("Cannot find door group for:", doorObject.name)
return;
}
const rotationSpeed = 0.05;
const maxRotation = Math.PI / 2;
const currentRotation = doorGroup.rotation.y;
const isOpen = Math.abs(currentRotation) >= maxRotation - rotationSpeed;
// 类型安全断言
const animatedDoor = doorGroup as unknown as THREE.Object3D & {
animationInterval?: number;
};
// 清除现有动画
if (animatedDoor.animationInterval) {
clearInterval(animatedDoor.animationInterval);
}
// 动画逻辑
animatedDoor.animationInterval = setInterval(() => {
// 在动画开始前先检测一次碰撞
if (checkCollisionDoor(doorGroup) && !isOpen) {
console.warn("Initial collision detected! Cannot open door.");
clearInterval(animatedDoor.animationInterval);
return;
}
const currentY = doorGroup.rotation.y;
if (isOpen) {
// 关门逻辑
doorGroup.rotation.y =
currentY > 0
? Math.max(0, currentY - rotationSpeed)
: Math.min(0, currentY + rotationSpeed);
if (Math.abs(doorGroup.rotation.y) < rotationSpeed) {
doorGroup.rotation.y = 0;
clearInterval(animatedDoor.animationInterval);
}
} else {
// 开门逻辑
doorGroup.rotation.y =
currentY >= 0
? Math.min(maxRotation, currentY + rotationSpeed)
: Math.max(-maxRotation, currentY - rotationSpeed);
if (
Math.abs(Math.abs(doorGroup.rotation.y) - maxRotation) < rotationSpeed
) {
clearInterval(animatedDoor.animationInterval);
}
}
// 只在开门时检测碰撞
if (!isOpen && checkCollisionDoor(doorGroup)) {
console.warn(
"Collision detected during opening! Stopping door animation."
);
clearInterval(animatedDoor.animationInterval);
}
}, 1000 / 60);
}
onMounted(() => {
initThree();
setupEventListeners();
});
onUnmounted(() => {
// 移除所有事件监听器
const events = ["resize", "mousedown", "mousemove", "keydown", "keyup"];
events.forEach((event) => {
window.removeEventListener(event, () => {});
});
// 清理资源(优化)
if (mixer) {
mixer.uncacheRoot(model);
mixer.stopAllAction();
}
// 清理怪物
monsters.forEach((monster) => {
monster.traverse((child) => {
if (child instanceof THREE.Mesh) {
child.geometry.dispose();
if (Array.isArray(child.material)) {
child.material.forEach((mat) => mat.dispose());
} else {
child.material.dispose();
}
}
});
scene.remove(monster);
});
monsters = [];
// 清理模型
if (model) {
model.traverse((child) => {
if (child instanceof THREE.Mesh) {
child.geometry.dispose();
if (Array.isArray(child.material)) {
child.material.forEach((mat) => mat.dispose());
} else {
child.material.dispose();
}
}
});
scene.remove(model);
}
// 清理地形
if (scene && scene.getObjectByName("terrain")) {
const terrain = scene.getObjectByName("terrain") as THREE.Mesh;
terrain.geometry.dispose();
if (Array.isArray(terrain.material)) {
terrain.material.forEach((mat) => mat.dispose());
} else {
terrain.material.dispose();
}
scene.remove(terrain);
}
// 清理渲染器
if (renderer) {
renderer.dispose();
(renderer.domElement as HTMLCanvasElement).width = 0;
(renderer.domElement as HTMLCanvasElement).height = 0;
}
// 清理场景
if (scene) {
scene.clear();
}
});
</script>
<style scoped>
.current-page {
width: 100%;
height: calc(100% - 50px);
display: flex;
justify-content: center;
align-items: center;
position: relative;
}
.draw {
width: 100%;
height: 100%;
}
.game-ui {
position: absolute;
top: 20px;
left: 20px;
color: white;
font-size: 20px;
}
.health-bar {
width: 200px;
height: 20px;
border: 2px solid white;
margin-bottom: 10px;
}
.health {
height: 100%;
background-color: green;
transition: width 0.3s;
}
button {
padding: 10px 20px;
font-size: 16px;
background: #4caf50;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
</style>