纯Web前端打造的元宇宙展厅——开箱即用的Lingo3D游戏引擎 支持原生、React、Vue

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"
// lingo3D库
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"
// 一款文字动画库,也是lingo3D作者的
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)// fixedWindow的开关

  // 角色model的ref
  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);
      }
    }
  })


  // ThirdPersonCameraInnerPosition,第三人称相机的Inner偏移量
  const CamInnerX = mouseOver?20:0
  const CamInnerZ = mouseOver?40:200
  const xSpring = useSpring({to:CamInnerX, bounce: 0})
  const zSpring = useSpring({to: CamInnerZ, bounce: 0})

  // characterAnimations,主角所有的动画
  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 (
    <>
      {/* Word世界,参数分别为:高质量画面,天空盒图片,Find(下文标签的边框,被遮挡是黑色),环境光遮蔽 */}
      <World performance="quality" skybox='skylight.hdr' outlineHiddenColor="black" ambientOcclusion>
        {/* 场景模型,参数分别为:模型地址(public下的),缩放,物理碰撞检测(我是地图)*/}
        <Model
          src="scene/Pavilion.glb"
          scale={18}
          physics='map'
          boxVisible={false}
        >
          {/* Find,可以把他理解成,场景模型中的某一个名字是name的元素,这个name哪里来,blender建模的时候命名的,参数分别为:name,边框,鼠标移入事件、鼠标移出事件、点击事件 */}
          <Find
            name="a5_CRN.a5_0"
            outline
            onMouseOver={()=>{setMouseOver(true)}}
            onMouseOut={()=>{setMouseOver(false)}}
            onClick={()=>{setfixedWindow(true)}}
          >
            {
              mouseOver &&
              // Html标签,就是普通的Ht,里面可以放html的标签
              <HTML>
                <AnimText className='HTML_TITLE'>SDTA-Gallery</AnimText>
              </HTML>
            }
          </Find>
          {/* 另一个Find,名字不同,让他播放一个视频 */}
          <Find name="b11_CRN.b11_0" texture={"https://media.sdta.cn/themes/sdta/assets/video/sdta-slider3.mp4"}></Find>
        </Model>

        {/* 第三人称相机,参数分别问:鼠标控制、激活、inner角度(x,y,z) */}
        <ThirdPersonCamera
          mouseControl
          active={!fixedWindow}
          innerX={xSpring}
          innerY={30}
          innerZ={zSpring}
        >
          {/* 人物模型,参数分别为:模型地址,ref(通过ref可以获取并操作这个model),物理碰撞检测(我是主角),初始位置(x,y,z),这个model都有什么动画,这个model当前执行的动画,模型的盒子线关闭,当设置环境光时需要打开pbr */}
          <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>
        {/* 键盘事件,这里就需要配合状态机来进行操作(如果只是简单的上下左右,通过useState即可,当功能复杂时,需要状态机进行管理) */}
        <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'){
              // sendPose('KEY_S_DOWN')
              characterRef.current?.moveRight(-3)
            }
            else if(key === 'a'){
              // sendPose('KEY_S_DOWN')
              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>
      {/* Model编辑器,可以查看模型的各种参数进行调试 */}
      {/* <Editor /> */}
      {
        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、期待更多的发现

  • 1
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值