最终效果
安装 sandi-ui
pnpm install sandi-ui
在vue3的项目中使用
import { createApp } from 'vue'
import App from './App.vue'
import sandiUI from "sand-ui"
const app = createApp(App)
app.use(sandiUI)
app.mount('#app')
开始制作你的3D世界吧
创建一个渲染器
我们需要一个渲染器,将三维空间渲染出来,即我们看到的画面,渲染器一定要设置 长度和宽度, 这里 设置backgroundColor属性,默认可以不设置,注意目前还是空白的 因为我们,没有设置场景和摄像机
<SDWebglRenderer :width="862" :height="400" :backgroundColor="0x1f63d1">
</SDWebglRenderer>
创建一个场景
场景就是我们的三维空间,我们可以在里面放任意的物体
<SDWebglRenderer :width="862" :height="400" :backgroundColor="0x1f63d1">
<SDScene>
</SDScene>
</SDWebglRenderer>
创建一个摄像机
摄像机是铁三角之一,另外两个,当然是场景和渲染器,这三个是缺一不可的。此时,我们,可以看到渲染器的设置的背景色,摄像机可以放在我们放在场景中哟
<SDWebglRenderer :width="862" :height="400" :backgroundColor="0x1f63d1">
<SDScene>
<SDPerspectiveCamera/>
</SDScene>
</SDWebglRenderer>
此时应该是这样的,这个颜色是摄像机的背景色
创建一个平面
这里使用SDMesh组件和SDPlaneGeometry组件,创建一个地面.
<SDWebglRenderer :width="720" :height="360" :backgroundColor="0x1f63d1">
<SDScene>
<SDPerspectiveCamera :positionZ="1" />
<SDMesh>
<SDPlaneGeometry />
<SDMeshBasicMaterial>
</SDMeshBasicMaterial>
</SDMesh>
</SDScene>
</SDWebglRenderer>
效果
变成地面
我们调整了相机的位置,并且,对平面沿X轴转90度,
<SDWebglRenderer :width="720" :height="360" :backgroundColor="0x1f63d1">
<SDScene>
<SDPerspectiveCamera :positionZ="2" :positionY="0.5" />
<SDMesh :rotationX="-Math.PI / 2">
<SDPlaneGeometry />
</SDMesh>
</SDScene>
</SDWebglRenderer>
给地面加上材质
这里使用SDMeshBasicMaterial,加载我们材质,可以看到它变成白色了
<SDWebglRenderer :width="720" :height="360" :backgroundColor="0x1f63d1">
<SDScene>
<SDPerspectiveCamera :positionZ="2" :positionY="0.5" />
<SDMesh :rotationX="-Math.PI / 2">
<SDPlaneGeometry />
<SDMeshBasicMaterial>
<SDTextureLoader url="/img/crate.gif" type="map" />
</SDMeshBasicMaterial>
</SDMesh>
</SDScene>
</SDWebglRenderer>
给地面加上贴图
这里使用SDTextureLoader,加载贴图,这样就有一个地面了。
在地面上跑起来吧
在跑起来之前我们先把地面变大,我们使用 v-for 来铺一个更大的地面
<SDWebglRenderer :width="862" :height="400" :backgroundColor="0x1f63d1">
<SDPerspectiveCamera />
<SDScene>
<SDMesh :rotationX="-Math.PI / 2" v-for="(_, index) in Array(200)" :positionX="Math.floor(index / 10)"
<SDBoxGeometry :width="20" :height="20" :depth="20" />
<SDMeshBasicMaterial>
</SDMeshBasicMaterial>
</SDMesh>
</SDScene>
</SDWebglRenderer>
但是我们只能看到一部分 所以我们使用 SDGroup 设置位置移动到中间位置,PS:SDGroup 互相嵌套哟
<SDWebglRenderer :width="720" :height="360" :backgroundColor="0x1f63d1">
<SDScene>
<SDPerspectiveCamera :positionZ="2" :positionY="0.5" />
<SDGroup :positionX="-5" :positionZ="-5">
<SDMesh :rotationX="-Math.PI / 2" v-for="(_, index) in Array(100)" :positionX="Math.floor(index / 10)"
:positionZ="index % 10">
<SDPlaneGeometry />
<SDMeshBasicMaterial>
<SDTextureLoader url="/img/crate.gif" type="map" />
</SDMeshBasicMaterial>
</SDMesh>
</SDGroup>
</SDScene>
</SDWebglRenderer>
好了,只需要添加一个 SDPointerLockControls 组件,我们就可以肆意的在场景里跑起来了。提前告诉你esc 显示鼠标
<script setup lang="ts">
import { ref } from "vue";
const lock = ref(false);
</script>
<SDWebglRenderer @click="()=>{lock=true}" :width="720" :height="360" :backgroundColor="0x1f63d1">
<SDPointerLockControls :lock="lock" />
<SDScene>
<SDPerspectiveCamera :positionZ="2" :positionY="0.5" />
<SDGroup :positionX="-5" :positionZ="-5">
<SDMesh :rotationX="-Math.PI / 2" v-for="(_, index) in Array(200)" :positionX="Math.floor(index / 10)"
:positionZ="index % 10">
<SDPlaneGeometry />
<SDMeshBasicMaterial>
<SDTextureLoader url="/img/crate.gif" type="map" />
</SDMeshBasicMaterial>
</SDMesh>
</SDGroup>
</SDScene>
</SDWebglRenderer>
但是有个问题,我们发现安esc后第二次点击视图没办法移动,因为props是单向的,我们组件内部状态变更,我们用回调属性unlockCallback,同步状态
<SDWebglRenderer @click="() => { lock = true }" :width="720" :height="360" :backgroundColor="0x1f63d1">
<SDPointerLockControls :lock="lock" :unlockCallback="() => {lock = false}" />
<SDScene>
<SDPerspectiveCamera :positionZ="2" :positionY="0.5" />
<SDGroup :positionX="-5" :positionZ="-5">
<SDMesh :rotationX="-Math.PI / 2" v-for="(_, index) in Array(200)" :positionX="Math.floor(index / 10)"
:positionZ="index % 10">
<SDPlaneGeometry />
<SDMeshBasicMaterial>
<SDTextureLoader url="/img/crate.gif" type="map" />
</SDMeshBasicMaterial>
</SDMesh>
</SDGroup>
</SDScene>
</SDWebglRenderer>
开始添加箱子吧
这里使用SDMesh组件和SDBoxGeometry组件,创建一个箱子. 同样的 我们使用 SDMeshBasicMaterial 和 SDTextureLoader 赋予材质
<SDWebglRenderer @click="() => { lock = true }" :width="720" :height="360" :backgroundColor="0x1f63d1">
<SDPointerLockControls :lock="lock" :unlockCallback="() => { lock = false }" />
<SDScene>
<SDPerspectiveCamera :positionZ="2" :positionY="0.5" />
<SDGroup :positionX="-5" :positionZ="-5">
<SDMesh :rotationX="-Math.PI / 2" v-for="(_, index) in Array(100)" :positionX="Math.floor(index / 10)"
:positionZ="index % 10">
<SDPlaneGeometry />
<SDMeshBasicMaterial>
<SDTextureLoader url="/img/crate.gif" type="map" />
</SDMeshBasicMaterial>
</SDMesh>
</SDGroup>
<SDMesh>
<SDBoxGeometry />
<SDMeshBasicMaterial>
<SDTextureLoader url="/img/crate.gif" type="map" />
</SDMeshBasicMaterial>
</SDMesh>
</SDScene>
</SDWebglRenderer>
将箱子组件和地面封装起来
没错你可以,封装出自己的3D组件哟
Floor.vue
<template>
<SDGroup>
<SDMesh :rotationX="-Math.PI / 2" v-for="(_, index) in Array(100)" :positionX="Math.floor(index / 10)"
:positionZ="index % 10">
<SDPlaneGeometry />
<SDMeshBasicMaterial>
<SDTextureLoader url="/img/crate.gif" type="map" />
</SDMeshBasicMaterial>
</SDMesh>
</SDGroup>
</template>
Box.vue
<template>
<SDMesh>
<SDBoxGeometry />
<SDMeshBasicMaterial>
<SDTextureLoader url="/img/crate.gif" type="map" />
</SDMeshBasicMaterial>
</SDMesh>
</template>
<script setup lang="ts">
import { ref } from "vue";
import Box from "./components/Box.vue"
import Floor from "./components/Floor.vue"
const lock = ref(false);
</script>
<template>
<SDWebglRenderer @click="() => { lock = true }" :width="720" :height="360" :backgroundColor="0x1f63d1">
<SDPointerLockControls :lock="lock" :unlockCallback="() => { lock = false }" />
<SDScene>
<SDPerspectiveCamera :positionZ="2" :positionY="0.5" />
<Floor :positionX="-5" :positionZ="-5" />
<Box />
</SDScene>
</SDWebglRenderer>
</template>
创建 充满箱子的世界
我们修改 Box.vue 代码 使用 v-for 创建多个箱子 我们使用 SDGroup 包裹 所有 SDMesh
Box.vue
<script setup >
const getXZ = () => Math.floor(Math.random() * 10)
const getY = () => Math.floor(Math.random() * 100)
</script>
<template>
<SDGroup>
<SDMesh v-for=" (_, index) in new Array(500).fill(1) " :positionX="getXZ()" :positionY="getY()"
:positionZ="getXZ()">
<SDBoxGeometry />
<SDMeshBasicMaterial>
<SDTextureLoader url="/img/crate.gif" type="map" />
</SDMeshBasicMaterial>
</SDMesh>
</SDGroup>
</template>
我们一样 移动floor 到中间位置
<script setup lang="ts">
import { ref } from "vue";
import Box from "./components/Box.vue"
import Floor from "./components/Floor.vue"
const lock = ref(false);
</script>
<template>
<SDWebglRenderer @click="() => { lock = true }" :width="720" :height="360" :backgroundColor="0x1f63d1">
<SDPointerLockControls :lock="lock" :unlockCallback="() => { lock = false }" />
<SDScene>
<SDPerspectiveCamera :positionZ="2" :positionY="0.5" />
<Floor :positionX="-5" :positionZ="-5" />
<Box :positionX="-5" :positionZ="-5" />
</SDScene>
</SDWebglRenderer>
</template>
修改跳跃高度
通过设置 SDPointerLockControls 的 options 属性设置跳跃高度
<script setup lang="ts">
import { ref } from "vue";
import Box from "./components/Box.vue"
import Floor from "./components/Floor.vue"
const lock = ref(false);
</script>
<template>
<SDWebglRenderer @click="() => { lock = true }" :width="720" :height="360" :backgroundColor="0x1f63d1">
<SDPointerLockControls :lock="lock" :unlockCallback="() => { lock = false }"
:options="{ junpHeight: 5 }" />
<SDScene>
<SDPerspectiveCamera :positionZ="2" :positionY="0.5" />
<Floor :positionX="-5" :positionZ="-5" />
<Box :positionX="-5" :positionZ="-5" />
</SDScene>
</SDWebglRenderer>
</template>
源代码地址:github.com/MILIFIRE/sa…