我终于也是会写3d小游戏的人了,发个掘金显摆显摆

先上代码块,代码共用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"
  }
}

效果是

相关推荐
haaaaaaarry18 分钟前
Vue常见指令
前端·javascript·vue.js
GISer_Jing25 分钟前
WebView&Native详解
前端·javascript
Future_object28 分钟前
react控制react Popover组件显示隐藏
前端·javascript·react.js
GISer_Jing29 分钟前
前端流式渲染&流式SSR详解
前端·javascript
隐含29 分钟前
基于echarts的水球的样式。
前端·javascript·echarts
脱离语言30 分钟前
前端下载文件并按GBK编码解析内容
前端·axios
16年上任的CTO32 分钟前
一文讲清楚React的render优化,包括shouldComponentUpdate、PureComponent和memo
前端·javascript·react.js·purecomponent·memo·render优化
YUJIANYUE33 分钟前
纯前端html实现图片坐标与尺寸(XY坐标及宽高)获取
开发语言·前端·javascript
晨岳37 分钟前
web开发-HTML
前端·html
周某人姓周1 小时前
xss作业
前端·xss