threejs 实现3D游戏(7)——加载子场景&切换场景

1,191 阅读3分钟

概述

在模型世界中,在当前场景中加载一个子场景。或从一个场景跳到下一个场景,都是很常见的需求。这次我们来分别实现它们。

在线体验地址

加载子场景

功能需求

这种场景只是视觉上切换了,实际上还是处于当前场景,只是在当前场景中将新的场景作为材质渲染出来。你可以理解在当前场景中展现了一个幻象覆盖在了原来场景之上。它比较适合需要在一个大场景中展示许多个小场景的情况,比如展厅。

组件文档

这个功能我们可以使用@react-three/drei 中的 MeshPortalMaterial组件来实现

image.png

效果

假设用户处于大厅场景,当用户点击门户时,加载一个新的场景将当前大厅的场景覆盖。

image.png

image.png

代码实现

在models文件夹中添加一个portal.tsx文件

import { extend, useFrame } from "@react-three/fiber";
import { useRef, useState } from "react";
import {
  useCursor,
  MeshPortalMaterial,
  Gltf,
  PortalMaterialType,
} from "@react-three/drei";
import * as THREE from "three";
import { easing, geometry } from "maath";

extend(geometry);
export default function Portal({
  width = 1,
  height = 1.61803398875,
  ...props
}) {
  const portal = useRef<PortalMaterialType>(null);
  const [clicked, setClick] = useState(false);
  const [hovered, hover] = useState(false);

  useCursor(hovered);

  useFrame((_state, dt) => {
    if (!portal.current) return;
    easing.damp(portal.current, "blend", clicked ? 1 : 0, 0.2, dt);
  });

  return (
    <group {...props}>
      <mesh
        onDoubleClick={(e) => (e.stopPropagation(), setClick(true))}
        onPointerOver={() => hover(true)}
        onPointerOut={() => hover(false)}
      >
        <roundedPlaneGeometry args={[width, height, 0.1]} />
        <MeshPortalMaterial
          ref={portal}
          events={clicked}
          side={THREE.DoubleSide}
        >
          <color attach="background" args={["#f0f0f0"]} />
          <Gltf
            src="./models/fiesta_tea-transformed.glb"
            position={[0, -2, -3]}
          />
        </MeshPortalMaterial>
      </mesh>
    </group>
  );
}

代码解释

当用户双击mesh所包裹的卡片时,会激活 MeshPortalMaterial的events参数为true。同时通过easing函数插值变化MeshPortalMaterial组件的blend属性,这里插值变化提供了一种渐隐渐现的动画效果。

对于这个属性,当它的值为0时,MeshPortalMaterial所包裹的模型只会被通过外部的roundedPlaneGeometry 渲染。当其为1时,只会渲染内部场景。如果为2则将当前场景和所包裹的模型场景混合渲染。

场景切换

功能需求

这种情况会将之前的场景卸载,展示loading页并加载一个新场景。这种情况的应用在于当模型场景过于巨大时,我们将其分割成不同的区域场景,方便加载更加容易。

许多游戏有关卡类的设计,不同关卡就是完全不同的场景,使衔接自然。

当用户点击场景中的某个‘门户’(portal) 时, 直接显示loading页加载新场景。在react中可以利用条件渲染很方便的实现。

代码实现

这里方便演示,我们直接使用一个变量来进行条件渲染:

export default function Models() {
  const { update } = usePages();
  const [showOut, setOut] = useState(true);
  
  function switchScene() {
    setOut(!showOut);
    update({ loading: true });
  }

const OutScene=()=> 
      <Canvas
        style={{ width: "100%", height: "100%" }}
      >
        <Suspense fallback={null}>
          <Lights />
          <Sky distance={500} sunPosition={[200, 300, 100]} />
          <Box position={[-0.6, 6.5, -1.5]} onClick={switchScene} />
          <Portal position={[1, 6, -2]} />
          <Physics timeStep="vary" >
            <KeyboardControls map={KEY_MAP}>
              <Player />
            </KeyboardControls>
            <Floor />
          </Physics>
        </Suspense>
        <Preload all />
      </Canvas>

  const InnerScene=()=>
      <Canvas
        style={{ width: "100%", height: "100%" }}
      >
        <Suspense fallback={null}>
          <Lights />
          <Sky distance={500} sunPosition={[200, 300, 100]} />
          <Box position={[0.6, 0.5, 1.5]} onClick={switchScene} />
          <Physics debug={DEBUG} timeStep="vary">
            <KeyboardControls map={KEY_MAP}>
              <Player />
            </KeyboardControls>
            <BlendModel />
          </Physics>
        </Suspense>
        <Preload all />
      </Canvas>

  return showOut ? <OutScene /> : <InnerScene />;
}

代码解释

如上我们创建2个场景,OutSceneInnerScene,并通过变量showOut来渲染场景,默认渲染OutScene。

我们在2个场景中都放置一个箱体模型Box作为门户(portal),并在上面绑定事件switchScene事件。

当用户点击门户时,我们把存证store中的变量loading设置为true,它会把加载页面显示出来。再改变渲染条件,加载新的场景。

结语

这次我们实现了如下功能

  • 在主场景中平滑显示子场景;
  • 场景和场景间进行跳转;

下次我们来实现通过配置文件来管理和加载模型。

源码地址

碎语

昨夜思归梦故里,田园荒芜霜满地。

浮萍漂泊本无根,不畏人知畏知己。