草庐IT

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

ATWLee 2023-04-12 原文

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、期待更多的发现

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

  1. ruby - 我需要从 facebook 游戏中抓取数据——使用 ruby - 2

    修改(澄清问题)我已经花了几天时间试图弄清楚如何从Facebook游戏中抓取特定信息;但是,我遇到了一堵又一堵砖墙。据我所知,主要问题如下。我可以使用Chrome的检查元素工具手动查找我需要的html-它似乎位于iframe中。但是,当我尝试抓取该iframe时,它​​是空的(属性除外):如果我使用浏览器的“查看页面源代码”工具,这与我看到的输出相同。我不明白为什么我看不到iframe中的数据。答案不是它是由AJAX之后添加的。(我知道这既是因为“查看页面源代码”可以读取Ajax添加的数据,也是因为我有b/c我一直等到我可以看到数据页面之后才抓取它,但它仍然不存在)。发生这种情况是因为

  2. ruby - 如何使用 readline 支持重新安装 ruby​​? - 2

    我已经按照https://github.com/wayneeseguin/rvm#installation上的说明通过RVM安装了Ruby.有关信息,我有所有文件(readline-5.2.tar.gz、readline-6.2.tar.gz、ruby-1.9.3-p327.tar.bz2、rubygems-1.8.24.tgz、wayneeseguin-rvm-stable.tgz和yaml-0.1.4.tar.gz)在~/.rvm/archives目录中,我不想在任何目录中重新下载它们方式。当我这样做时:sudo/usr/bin/apt-getinstallbuild-essent

  3. ruby-on-rails - "undefined method ` stub_request '"访问 RSpec 支持文件中的方法时 - 2

    我的Ruby-on-Rails项目中有以下文件结构,用于规范:/spec/msd/serviceservice_spec.rb/support/my_modulerequests_stubs.rb我的request_stubs.rb有:moduleMyModule::RequestsStubsmodule_functiondeflist_clientsurl="dummysite.com/clients"stub_request(:get,url).to_return(status:200,body:"clientsbody")endend在我的service_spec.rb我有:re

  4. ruby - Ruby 是否支持逐字字符串? - 2

    Ruby是否支持(找不到更好的词)非转义(逐字)字符串?就像在C#中一样:@"c:\ProgramFiles\"...或者在Tcl中:{c:\ProgramFiles\} 最佳答案 是的,您需要在字符串前加上%前缀,然后是描述其类型的单个字符。你想要的是%q{c:\programfiles\}。镐书很好地涵盖了这一点here,部分是通用分隔输入。 关于ruby-Ruby是否支持逐字字符串?,我们在StackOverflow上找到一个类似的问题: https:/

  5. python - Ruby 或 Python 的 3d 游戏引擎? - 2

    关闭。这个问题不符合StackOverflowguidelines.它目前不接受答案。要求我们推荐或查找工具、库或最喜欢的场外资源的问题对于StackOverflow来说是偏离主题的,因为它们往往会吸引自以为是的答案和垃圾邮件。相反,describetheproblem以及迄今为止为解决该问题所做的工作。关闭9年前。Improvethisquestion是否有适用于这些的3d游戏引擎?

  6. ruby - 在 Ruby 1.8 中支持 Ruby 1.9 的哈希语法 - 2

    我正在编写一个Rubygem,在我的代码中使用{key:'value'}哈希语法。我的测试都在1.9.x中通过,但我(可以理解)在1.8.7中得到syntaxerror,unexpected':',expecting')'。是否有支持1.8.x的最佳实践?我是否需要使用我们的老friend=>重写代码,还是有更好的策略? 最佳答案 我认为你运气不好,如果你想支持1.8,那么你必须使用=>。像往常一样,我会提到在1.9的某些情况下您必须使用=>:如果键不是一个符号。请记住,任何对象(符号、字符串、类、float……)都可以是Ruby哈

  7. ruby-on-rails - Rails 是否支持监听 UDP 套接字的简洁方式? - 2

    在Rails中,什么是集成更新模型某些元素的UDP监听过程的最佳方式(特别是它将向其中一个表添加行)。简单的答案似乎是在同一个进程中使用UDP套接字对象启动一个线程,但我什至不清楚我应该在哪里做适合Rails方式的事情。有没有一种巧妙的方法来开始收听UDP?具体来说,我希望能够编写一个UDPController并在每个数据报消息上调用一个特定的方法。理想情况下,我希望避免在UDP上使用HTTP(因为它会浪费一些在这种情况下非常宝贵的空间),但我完全控制消息格式,因此我可以为Rails提供它需要的任何信息。 最佳答案 Rails是一个

  8. ruby - Watir-Webdriver 是否支持点击目标为 javascript 的链接? - 2

    我是Ruby和Watir-Webdriver的新手。我有一套用VBScript编写的站点自动化程序,我想将其转换为Ruby/Watir,因为我现在必须支持Firefox。我发现我真的很喜欢Ruby,而且我正在研究Watir,但我已经花了一周时间试图让Webdriver显示我的登录屏幕。该站点以带有“我同意”区域的“警告屏幕”开头。用户点击我同意并显示登录屏幕。我需要单击该区域以显示登录屏幕(这是同一页面,实际上是一个表单,只是隐藏了)。我整天都在用VBScript这样做:objExplorer.Document.GetElementsByTagName("area")(0).click

  9. ruby - 使用 Ruby 编写 Unity 游戏 - 2

    所以我看到unity支持c#、JS和Boo。我可以学习其中一个,但我想制作一个“编译器”或类似的东西,让我可以编写ruby​​代码并输出JS代码或制作一个可以被Unity编译器读取的层。这有可能吗?我愿意在这方面投入很多时间并且有相当多的经验。 最佳答案 如果您的问题实际上是“我如何将Ruby编译为JavaScript”,那么这更容易回答:Opal:RubytoJavaScriptcompiler但是,学习其中一种受支持的语言会更好。当运行的是用另一种语言解释的代码时,很难调试“您的”代码。

  10. Ruby - 不支持的密码算法 (AES-256-GCM) - 2

    我收到错误:unsupportedcipheralgorithm(AES-256-GCM)(RuntimeError)但我似乎具备所有要求:ruby版本:$ruby--versionruby2.1.2p95OpenSSL会列出gcm:$opensslenc-help2>&1|grepgcm-aes-128-ecb-aes-128-gcm-aes-128-ofb-aes-192-ecb-aes-192-gcm-aes-192-ofb-aes-256-ecb-aes-256-gcm-aes-256-ofbRuby解释器:$irb2.1.2:001>require'openssl';puts

随机推荐