概述
在模型世界中,在当前场景中加载一个子场景。或从一个场景跳到下一个场景,都是很常见的需求。这次我们来分别实现它们。
加载子场景
功能需求
这种场景只是视觉上切换了,实际上还是处于当前场景,只是在当前场景中将新的场景作为材质渲染出来。你可以理解在当前场景中展现了一个幻象覆盖在了原来场景之上。它比较适合需要在一个大场景中展示许多个小场景的情况,比如展厅。
组件文档
这个功能我们可以使用@react-three/drei 中的 MeshPortalMaterial组件来实现

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


代码实现
在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个场景,OutScene和InnerScene,并通过变量showOut来渲染场景,默认渲染OutScene。
我们在2个场景中都放置一个箱体模型Box作为门户(portal),并在上面绑定事件switchScene事件。
当用户点击门户时,我们把存证store中的变量loading设置为true,它会把加载页面显示出来。再改变渲染条件,加载新的场景。
结语
这次我们实现了如下功能
- 在主场景中平滑显示子场景;
- 场景和场景间进行跳转;
下次我们来实现通过配置文件来管理和加载模型。
碎语
昨夜思归梦故里,田园荒芜霜满地。
浮萍漂泊本无根,不畏人知畏知己。