Lingo3D
一款新出的开源框架,主要目的是用来做游戏引擎的,对标端游PUBG 因公司业务需求不同,跟着教程做了一款纯展示的展厅性质的Demo(React) 近几年元宇宙的概念有点热门,这里亦可沾点边
官方Demo截图
地址
NPM地址 GitHub地址 官方文档地址 展厅Dem地址,PC端,简单做了做,功能很少,除基本的人物移动逻辑外,只做了图片轮播预览和视频展示的功能 官方B站教学视频 相关3D模型问题B站视频
优缺点
1、纯Web前端 2、上手超简单。支持原JS,React,Vue 3、可通过websocket实现多人联机互动 4、支持PC、移动端摇杆
1、框架不断内测完善中,业务场景功能待完善 2、框架支持的3D模型具有局限性,需要blender进行处理。这也是市面上3D模型不统一导致的。 3、官方文档暂时不完善,文档阅读不便(快一点呀快一点)
所需相关技术
1、Vite
一款前端构建工具,跟着官方文档走一下,create一个项目即可,不需要了解很多 当然如果业务需求很多了,可能需要了解这个工具的配置项什么的,可能会有冲突
2、TS
支持TS,也支持JS,如果觉TS很搞,可以使用JS,但是还是建议使用TS,如果你要构建一个长久维护的项目的话。
3、状态机xstate
用来管理你的键盘事件与动画状态的关系
4、模型材质相关
建议使用blender进行模型处理,blender2.93LTS版本
- 人物模型:1、mixamo下载角色及动作动画;2、blender自己建模,导出fbx格式后上传至mixamo进行骨骼绑定后下载角色及动作动画;3、readyplayer.me用照片生成的人物模型,导入blender后导出fbx格式,上传至mixamo进行骨骼绑定后下载角色及动作动画。
- 场景模型:sketchfab、blendswap、模之屋
- 环境光模型:熟悉建模的朋友应该知道光是建模当中很重要的一个概念。Lingo3D支持hdr的图片,可以用hdr进行光照模拟
- 天空图:Google搜索关键字: equirectangular sky / skybox background ,也可直接用hdr图片当作天空图
Demo相关代码——React
源码下载地址
import { useState, useRef } from "react"
import { World, Editor, Cube, Model, ThirdPersonCamera, Keyboard, OrbitCamera, useLoop, useKeyboard, types, Find, HTML, Reticle, useSpring } from "lingo3d-react"
import { useMachine } from "@xstate/react"
import poseMachine from "./stateMachines/poseMachine"
import AnimText from "@lincode/react-anim-text"
import './App.css'
import Gallery from './components/gallery'
function App() {
const [mouseOver, setMouseOver] = useState(false)
const [fixedWindow, setfixedWindow] = useState(false)
const characterRef = useRef<types.Model>(null)
const [pose, sendPose] = useMachine(poseMachine, {
actions: {
enterJumping: ()=>{
const character = characterRef.current;
if(character === null) return
character.velocity.y = 5
character.onLoop = () => {
if(character.velocity.y === 0){
character.onLoad = undefined
sendPose('LADNED')
}
}
},
enterWaving: () => {
const character = characterRef.current;
if(character === null) return
const animationTimeout = setTimeout(() => {
sendPose('ENDTHISANIMATION')
clearTimeout(animationTimeout)
}, 4000);
}
}
})
const CamInnerX = mouseOver?20:0
const CamInnerZ = mouseOver?40:200
const xSpring = useSpring({to:CamInnerX, bounce: 0})
const zSpring = useSpring({to: CamInnerZ, bounce: 0})
const characterAnimations = {
idle: 'person/Idle.fbx',
walking: 'person/Walking.fbx',
running:'person/Running.fbx',
jumping: 'person/Falling.fbx',
waklingback:'person/WalkingBackwards.fbx',
waving:'person/Waving.fbx'
}
return (
<>
{}
<World performance="quality" skybox='skylight.hdr' outlineHiddenColor="black" ambientOcclusion>
{}
<Model
src="scene/Pavilion.glb"
scale={18}
physics='map'
boxVisible={false}
>
{}
<Find
name="a5_CRN.a5_0"
outline
onMouseOver={()=>{setMouseOver(true)}}
onMouseOut={()=>{setMouseOver(false)}}
onClick={()=>{setfixedWindow(true)}}
>
{
mouseOver &&
<HTML>
<AnimText className='HTML_TITLE'>SDTA-Gallery</AnimText>
</HTML>
}
</Find>
{}
<Find name="b11_CRN.b11_0" texture={"https://media.sdta.cn/themes/sdta/assets/video/sdta-slider3.mp4"}></Find>
</Model>
{}
<ThirdPersonCamera
mouseControl
active={!fixedWindow}
innerX={xSpring}
innerY={30}
innerZ={zSpring}
>
{}
<Model
src="person/T-Pose.fbx"
ref={characterRef}
physics='character'
x={-424.99} y={-825.31} z={-661.13}
animations={characterAnimations}
animation={pose.value as any}
boxVisible={false}
pbr
/>
</ThirdPersonCamera>
{}
<Keyboard
onKeyPress={(key: string) => {
if(key === 'w'){
sendPose('KEY_W_DOWN')
characterRef.current?.moveForward(-3)
}
else if(key === 's'){
sendPose('KEY_S_DOWN')
characterRef.current?.moveForward(1)
}
else if(key === 'd'){
characterRef.current?.moveRight(-3)
}
else if(key === 'a'){
characterRef.current?.moveRight(3)
}
else if(key === 'Space'){
sendPose('KEY_SPACE_DOWN')
}
else if(key === 'Shift'){
sendPose('KEY_SHIFT_DOWN')
characterRef.current?.moveForward(-3)
}
else if(key === 'h'){
sendPose('KEY_H_DOWN')
}
}}
onKeyUp={(key: string) => {
if(key === 'w'){
sendPose('KEY_W_UP')
}
else if(key === 's'){
sendPose('KEY_S_UP')
}
else if(key === 'Shift'){
sendPose('KEY_SHIFT_UP')
}
}}
/>
</World>
{}
{}
{
fixedWindow &&
<FixedWindow handleClose={()=>setfixedWindow(false)}></FixedWindow>
}
</>
)
}
function FixedWindow(props: any){
return(
<div className="fixedWindow">
<span onClick={()=>props.handleClose()} className="fixedWindow_close">{"close"}</span>
<span className="fixedWindow_websocket">{"OPEN CHAT"}</span>
{}
<Gallery></Gallery>
</div>
)
}
export default App
PS遇到的一些坑
1、blender建议使用2.9.3稳定版 2、用vite创建的react是18,后面用到了Swiper不知道什么错,降到了17 3、期待更多的发现
|