先上代码块,代码共用4部分组成 1.就是代码的主体部分index.vue
vue
<template>
<div class="current-page">
<canvas
id="draw"
class="draw"
style="border: 1px solid; background-color: #000"
></canvas>
</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 { getHoursData } from "./intehours.ts";
import { managerHome } from "./miniHomeManager.ts";
// let { handleDoorClick } = getThreeControls();
// 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 canvas: HTMLCanvasElement; // 添加到文件顶部的变量声明区域
// 动作控制
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 },
};
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(0xa0a0a0);
scene.fog = new THREE.Fog(0xa0a0a0, 10, 50);
// 设置光源
const hemiLight = new THREE.HemisphereLight(0xffffff, 0x8d8d8d, 3);
hemiLight.position.set(0, 20, 0);
scene.add(hemiLight);
const dirLight = new THREE.DirectionalLight(0xffffff, 3);
dirLight.position.set(3, 10, 10);
dirLight.castShadow = true;
scene.add(dirLight);
// 添加地面网格
const grid = new THREE.GridHelper(5000, 5000, 0x000000, 0x000000);
(grid.material as THREE.Material).opacity = 0.2;
(grid.material as THREE.Material).transparent = true;
scene.add(grid);
// 初始化相机
camera = new THREE.PerspectiveCamera(
65,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.set(0, 1.6, -2.5);
// 初始化渲染器
renderer = new THREE.WebGLRenderer({
canvas,
antialias: true,
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
// 添加性能监视器
stats = new Stats();
document.body.appendChild(stats.dom);
// 创建小屋
initHouse(houseSize, housePosition, "house1");
initHouse(houseSize, housePositiontwo, "house2");
// 加载模型
loadModel();
}
// 修改后的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);
camera.position.z = -3;
camera.lookAt(0, 1.6, 0);
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 checkCollision(object: THREE.Group, group: THREE.Group): boolean {
const playerBox = new THREE.Box3().setFromObject(object);
// 仅检查墙壁和门碰撞体
return group.children.some((child) => {
// console.log("checkCollision:", child.name);
// 检测所有碰撞体(包括墙体碰撞体和门框碰撞体)console.log("checkCollision:", child.name)
if (child.name.includes("Wall") || child.name === "Collider") {
child.updateMatrixWorld();
const objectBox = new THREE.Box3().setFromObject(child);
if (playerBox.intersectsBox(objectBox)) {
console.log("碰撞发生在:", child.name, "位置:", child.position);
return true;
}
}
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; // 添加类型断言
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);
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;
}
}
});
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);
if (renderer) renderer.dispose();
if (scene) scene.clear();
});
</script>
<style scoped>
.current-page {
width: 100%;
height: calc(100% - 50px);
display: flex;
justify-content: center;
align-items: center;
}
.draw {
width: 100%;
height: 100%;
}
</style>
第二部分是里面自己绘制的小房子部分miniHomeManager.ts
ts
import * as THREE from 'three'
import floorUrl from "@/assets/img/floor.jpeg";
import type { Object3D } from 'three/webgpu';
// 创建地板
function createFloor(
sizeData: {
baseWidth: number;
baseLength: number;
baseHeight: number;
baseThickness: number;
peakHeight: number;
peakWidth: number;
doorWidth: number;
doorHeight: number;
baseImg?: string;
tileUrl?: string;
doorImg?: string;
},
PositionData: { x: any; y: any; z: any },
group: THREE.Group<THREE.Object3DEventMap>
) {
if (!group) return;
const texture = new THREE.TextureLoader().load(floorUrl);
const floor = new THREE.BoxGeometry(
sizeData.baseWidth,
1,
sizeData.baseLength
);
const material = new THREE.MeshPhongMaterial({ map: texture });
const mesh = new THREE.Mesh(floor, material);
mesh.position.set(PositionData.x, PositionData.y, PositionData.z);
mesh.name = "floor";
group.add(mesh);
}
// 创造墙面
function createWall(
width: number | undefined,
height: number | undefined,
thickness: number | undefined,
imgUrl: string
) {
const wallTexture = new THREE.TextureLoader().load(imgUrl);
const wall = new THREE.BoxGeometry(width, height, thickness);
const material = new THREE.MeshPhongMaterial({ map: wallTexture });
return new THREE.Mesh(wall, material);
}
function createWalls(
sizeData: {
baseWidth: any;
baseLength: any;
baseHeight: any;
baseThickness: any;
peakHeight?: number;
peakWidth?: number;
doorWidth?: number;
doorHeight?: number;
baseImg: any;
tileUrl?: string;
doorImg?: string;
},
PositionData: { x: number; y: number; z: number },
group: THREE.Group<THREE.Object3DEventMap>
) {
if (!group) return;
// 左侧墙
const leftWall = createWall(
sizeData.baseWidth,
sizeData.baseHeight,
sizeData.baseThickness,
sizeData.baseImg
);
leftWall.name = "leftWall";
leftWall.position.set(
PositionData.x,
-1 + sizeData.baseHeight / 2 + PositionData.y,
sizeData.baseLength / 2 + PositionData.z
);
group.add(leftWall);
// 右侧墙
const rightWall = createWall(
sizeData.baseWidth,
sizeData.baseHeight,
sizeData.baseThickness,
sizeData.baseImg
);
rightWall.name = "rightWall";
rightWall.position.set(
PositionData.x,
-1 + sizeData.baseHeight / 2 + PositionData.y,
-sizeData.baseLength / 2 + PositionData.z
);
group.add(rightWall);
}
// 创建前后墙的通用规则
function genwallShapeNo(sizeData: {
baseLength: number;
peakWidth?: number; // 不再是可选参数
baseHeight: number;
peakHeight: number;
}) {
// 添加默认值处理
const peakWidth = sizeData.peakWidth ?? 5;
const shape = new THREE.Shape();
let height = sizeData.baseHeight; // 使用统一的高度
// 绘制墙体轮廓
shape.moveTo(0, 0);
shape.lineTo(0, height);
shape.lineTo(
sizeData.baseLength / 2 - peakWidth / 2,
sizeData.baseHeight + sizeData.peakHeight - 1
);
shape.lineTo(
sizeData.baseLength / 2 - peakWidth / 2,
sizeData.baseHeight + sizeData.peakHeight
);
shape.lineTo(
sizeData.baseLength / 2 + peakWidth / 2,
sizeData.baseHeight + sizeData.peakHeight
);
shape.lineTo(
sizeData.baseLength / 2 + peakWidth / 2,
sizeData.baseHeight + sizeData.peakHeight - 1
);
shape.lineTo(sizeData.baseLength, sizeData.baseHeight);
shape.lineTo(sizeData.baseLength, 0);
shape.lineTo(0, 0);
return shape;
}
function createIrregularWall(
shape: THREE.Shape | THREE.Shape[] | undefined,
position: any[],
textureUrl: string,
group: THREE.Group<THREE.Object3DEventMap>,
sizeData: { baseWidth: number; baseLength: number; baseHeight: number; baseThickness: number; peakHeight: number; peakWidth: number; doorWidth: number; doorHeight: number; baseImg?: any; tileUrl?: string; doorImg?: string; }
) {
const extrudeSettings = {
depth: sizeData.baseThickness, // 使用统一的厚度
bevelEnabled: false,
};
const wallTexture = new THREE.TextureLoader().load(textureUrl);
const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
wallTexture.wrapS = wallTexture.wrapT = THREE.RepeatWrapping;
wallTexture.repeat.set(0.05, 0.05);
const material = new THREE.MeshPhongMaterial({
map: wallTexture,
side: THREE.DoubleSide,
});
const mesh = new THREE.Mesh(geometry, material);
mesh.position.set(position[0], position[1], position[2]);
group.add(mesh);
return mesh;
}
// 创建有门的墙
function createDoorWall(
sizeData: {
baseWidth: number;
baseLength: number;
baseHeight: number;
baseThickness: number;
peakHeight: number;
peakWidth: number;
doorWidth: number;
doorHeight: number;
baseImg?: any;
tileUrl?: string;
doorImg?: string;
},
positionData: { x: any; y: any; z: any },
group: THREE.Group<THREE.Object3DEventMap>
) {
const shape = genwallShapeNo(sizeData);
const door = new THREE.Path();
const doorOffsetX = sizeData.baseLength / 2;
door.moveTo(doorOffsetX - sizeData.doorWidth / 2, 0);
door.lineTo(doorOffsetX - sizeData.doorWidth / 2, sizeData.doorHeight);
door.lineTo(doorOffsetX + sizeData.doorWidth / 2, sizeData.doorHeight);
door.lineTo(doorOffsetX + sizeData.doorWidth / 2, 0);
shape.holes.push(door);
const mesh = createIrregularWall(
shape,
[
sizeData.baseWidth / 2 + positionData.x - 1,
-1 + positionData.y,
sizeData.baseLength / 2 + positionData.z,
],
sizeData.baseImg,
group,
sizeData
);
mesh.name = "doorQian";
mesh.rotation.y = Math.PI / 2;
// ===== 精确碰撞体方案 =====
// 计算基本位置参数 0 20
const wallCenterX = positionData.x + sizeData.baseWidth / 2 - 1;
// 0 30 - 12
const wallCenterZ = positionData.z + sizeData.baseLength / 2;
const wallBottomY = positionData.y - 1;
// 1. 左侧墙体碰撞体
const leftWidth = (sizeData.baseLength - sizeData.doorWidth) / 2;
const leftCollider = new THREE.Mesh(
new THREE.BoxGeometry(
leftWidth,
sizeData.baseHeight,
sizeData.baseThickness
),
new THREE.MeshBasicMaterial({
visible: false,
wireframe: true,
color: 0xff0000 // 红色线框便于调试
})
);
// leftCollider.position.set(
// wallCenterX - sizeData.baseLength / 2 + leftWidth / 2,
// wallBottomY + sizeData.baseHeight / 2,
// wallCenterZ
// );
leftCollider.position.set(
wallCenterX + 0.5,
0,
wallCenterZ - sizeData.doorWidth - 2.05
);
leftCollider.rotation.y = Math.PI / 2;
leftCollider.name = "leftWallCollider";
leftCollider.userData.isCollidable = true;
group.add(leftCollider);
// 2. 右侧墙体碰撞体
const rightCollider = leftCollider.clone();
rightCollider.position.set(
wallCenterX + 0.5,
0,
-wallCenterZ + sizeData.doorWidth + 2.05
);
rightCollider.name = "rightWallCollider";
rightCollider.userData.isCollidable = true;
group.add(rightCollider);
// 3. 门上方墙体碰撞体
const topHeight = sizeData.baseHeight - sizeData.doorHeight;
const topCollider = new THREE.Mesh(
new THREE.BoxGeometry(
sizeData.doorWidth,
topHeight,
sizeData.baseThickness
),
new THREE.MeshBasicMaterial({
visible: false,
wireframe: true,
color: 0x00ff00 // 绿色线框便于调试
})
);
topCollider.position.set(
wallCenterX,
wallBottomY + sizeData.doorHeight + topHeight / 2,
wallCenterZ
);
topCollider.rotation.y = Math.PI / 2;
topCollider.name = "topWallCollider";
topCollider.userData.isCollidable = true;
group.add(topCollider);
return mesh;
// ========== 新增碰撞体部分结束 ==========
}
// 创建没有门的墙
function createNoDoorWall(
sizeData: {
baseWidth: number;
baseLength: number;
baseHeight: number;
baseThickness: number;
peakHeight: number;
peakWidth: number;
doorWidth: number;
doorHeight: number;
baseImg?: any;
tileUrl?: string;
doorImg?: string;
},
positionData: { x: any; y: any; z: any },
group: THREE.Group<THREE.Object3DEventMap>
) {
const shape = genwallShapeNo(sizeData);
let mesh = createIrregularWall(
shape,
[
-sizeData.baseWidth / 2 + positionData.x,
-1 + positionData.y,
sizeData.baseLength / 2 + positionData.z,
],
sizeData.baseImg,
group, sizeData
);
mesh.name = "backWall";
mesh.rotation.y = Math.PI / 2;
}
// 创建屋顶
function createRoof(
sizeData: {
baseWidth: any;
baseLength?: number;
baseHeight: any;
baseThickness?: number;
peakHeight: any;
peakWidth?: number;
doorWidth?: number;
doorHeight?: number;
baseImg?: string;
tileUrl: any;
doorImg?: string;
},
positionData: { x: any; y: any; z: any },
group: THREE.Group<THREE.Object3DEventMap>
) {
if (!group) return;
// 屋顶参数
const roofPeakHeight = sizeData.peakHeight; // 屋顶顶点高度
const roofEaveLength = 5; // 屋檐延伸长度
// 计算屋顶实际尺寸
const roofWidth =
Math.sqrt(sizeData.baseWidth ** 2 + roofPeakHeight ** 2) + roofEaveLength;
const roofLength = sizeData.baseWidth - 6; // 比墙面略长
const roofThickness = 1; // 屋顶厚度
// 屋顶高度位置 (墙高 + 屋顶厚度)
const roofPositionY = sizeData.baseHeight + roofThickness + 1;
// 创建屋顶几何体
const geometry = new THREE.BoxGeometry(roofLength, roofWidth, roofThickness);
// 加载屋顶纹理
const texture = new THREE.TextureLoader().load(sizeData.tileUrl);
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set(2, 2);
// 创建屋顶材质
const material = new THREE.MeshPhongMaterial({
map: texture,
side: THREE.DoubleSide,
});
// 创建右侧屋顶
const rightRoof = new THREE.Mesh(geometry, material);
rightRoof.rotation.set(
80 * (Math.PI / 180), // X轴旋转角度(弧度)
0 * (Math.PI / 180), // Y轴旋转180度
90 * (Math.PI / 180) // Z轴旋转45度
);
rightRoof.position.set(
-sizeData.baseWidth / 2 + sizeData.baseHeight + positionData.x,
roofPositionY + positionData.y,
positionData.z - 16.5
);
rightRoof.name = "rightRoof";
group.add(rightRoof);
// 创建左侧屋顶(对称)
const leftRoof = new THREE.Mesh(geometry, material);
leftRoof.rotation.set(
100 * (Math.PI / 180), // X轴旋转角度(弧度)
0 * (Math.PI / 180), // Y轴旋转180度
90 * (Math.PI / 180) // Z轴旋转45度
);
leftRoof.position.set(
sizeData.baseWidth / 2 - sizeData.baseHeight + positionData.x,
roofPositionY + positionData.y,
positionData.z + 16.5
);
leftRoof.name = "leftRoof";
group.add(leftRoof);
return {
roofs: [rightRoof, leftRoof],
width: roofWidth,
};
}
// 创建门
function createDoor(
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 },
group: THREE.Group,
doorRef: {
meshes: THREE.Mesh<THREE.BoxGeometry, THREE.MeshPhongMaterial>[],
groups: THREE.Group[]
}
): THREE.Group | null {
if (!group) return null;
// 创建门组
const newDoorGroup = new THREE.Group()
newDoorGroup.name = "doorGroup"
// 加载门纹理
const texture = new THREE.TextureLoader().load(sizeData.doorImg)
// 创建门几何体
const doorGeometry = new THREE.BoxGeometry(
sizeData.doorWidth,
sizeData.doorHeight,
sizeData.baseThickness
)
// 创建门材质
const material = new THREE.MeshPhongMaterial({
map: texture,
transparent: true,
opacity: 1,
})
// 正确更新门引用
const doorMesh = new THREE.Mesh(doorGeometry, material)
doorMesh.name = "door"
// 将门模型的位置调整到标记点2处(即门的左侧边缘)
doorMesh.position.set(-sizeData.doorWidth / 2, 0, 0); // 标记点2的位置
doorMesh.userData.isDoor = true // 新增
// 更新引用
doorRef.meshes.push(doorMesh)
doorRef.groups.push(newDoorGroup)
// 将门添加到门组
newDoorGroup.add(doorMesh)
// 设置门组位置和旋转
newDoorGroup.position.set(
sizeData.baseWidth / 2 + positionData.x - 0.5,
sizeData.doorHeight / 2 + positionData.y - 1,
-sizeData.doorWidth / 2 + positionData.z
)
newDoorGroup.rotation.y = Math.PI / 2
// 将门组添加到场景组
group.add(newDoorGroup)
// 添加辅助碰撞体
const hitBoxGeometry = new THREE.BoxGeometry(
sizeData.doorWidth * 1.2, // 加宽20%
sizeData.doorHeight * 1.1, // 加高10%
0.8 // 加厚60%
);
const hitBox = new THREE.Mesh(hitBoxGeometry);
hitBox.visible = false; // 不可见只用于碰撞检测
hitBox.name = "doorHitBox";
hitBox.userData.isDoor = true // 碰撞体也标记为门相关
hitBox.position.set(0, 0, 0); // 标记点2的位置
doorMesh.add(hitBox)
// 修改点击检测逻辑
doorMesh.userData.isInteractive = true;
return newDoorGroup; // 返回门组对象
}
// 重点修改部分结束
// 或者如果需要更新对象,可以导出函数形式
export function managerHome() {
return {
createFloor,
createWalls,
createNoDoorWall,
createDoorWall,
createRoof,
createDoor
}
}
第三部分是房子的校验条件tsType.ts
ts
export interface HouseSize {
baseWidth: number;
baseLength: number;
baseHeight: number;
baseThickness: number;
peakHeight: number;
peakWidth: number; // 明确为必填项
doorWidth: number;
doorHeight: number;
baseImg: string;
tileUrl: string;
doorImg: string;
}
export interface PositionData {
x: number,
y: number,
z: number,
}
最后是房子生成的参数 intehours.ts
ts
import wallbUrl from '@/assets/img/bmqiang.jpg'
import tileUrl from '@/assets/img/tile.jpg'
import doorUrl from '@/assets/img/door.jpg'
// 房屋尺寸接口
interface HouseSize {
baseWidth: number;
baseLength: number;
baseHeight: number;
baseThickness: number;
peakHeight: number;
peakWidth: number;
doorWidth: number;
doorHeight: number;
baseImg: string;
tileUrl: string;
doorImg: string;
}
// 位置坐标接口
interface PositionData {
x: number;
y: number;
z: number;
}
// 房屋数据配置
const houseSize: HouseSize = {
baseWidth: 40, // 房屋宽度
baseLength: 60, // 房屋长度
baseHeight: 20, // 墙面高度
baseThickness: 1, // 墙面厚度
peakHeight: 6, // 顶面高度(顶点突出高度)
peakWidth: 2, // 顶面宽度(顶点宽度)
doorWidth: 10, // 门宽度
doorHeight: 16, // 门高度
baseImg: wallbUrl, // 墙面图片
tileUrl: tileUrl, // 瓷砖图片
doorImg: doorUrl // 门图片
}
// 房屋位置配置
const housePosition: PositionData = {
x: 0,
y: 1,
z: 0
}
const housePositiontwo: PositionData = {
x: 100,
y: 1,
z: 100
}
// 获取房屋数据
export function getHoursData() {
return {
houseSize,
housePosition,
housePositiontwo
}
}
// 房屋尺寸类型
export type { HouseSize, PositionData }
开发环境是vite+vue3+ts+three 配置是package.json
json
{
"name": "routerpag",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vue-tsc -b && vite build",
"preview": "vite preview"
},
"dependencies": {
"@react-three/drei": "^10.5.1",
"drei": "^2.2.21",
"element-plus": "^2.10.4",
"gltfjsx": "^6.5.3",
"gsap": "^3.13.0",
"lil-gui": "^0.20.0",
"lodash": "^4.17.21",
"router": "^2.2.0",
"stats.js": "^0.17.0",
"three": "^0.178.0",
"three-stdlib": "^2.36.0",
"vue": "^3.5.17",
"web-localstorage-plus": "^0.0.19"
},
"devDependencies": {
"@types/lodash": "^4.17.20",
"@types/node": "^24.0.13",
"@types/stats.js": "^0.17.4",
"@types/three": "^0.178.1",
"@types/vue-router": "^2.0.0",
"@vitejs/plugin-vue": "^6.0.0",
"@vue/tsconfig": "^0.7.0",
"less": "^4.3.0",
"sass-embedded": "^1.89.2",
"typescript": "~5.8.3",
"unplugin-auto-import": "^19.3.0",
"vite": "^7.0.4",
"vue-router": "^4.5.1",
"vue-tsc": "^2.2.12"
}
}
效果是