Fork me on GitHub

会议分享:用React+lingo3d开发一款游戏

一. 基础准备

  • 开发语言选择react+lingo3d
  • 模型,动画,音效
    • 模型:Blender软件,模型素材网站:https://sketchfab.com/feed
    • 动画:https://www.mixamo.com/#/
    • 音效:https://sc.chinaz.com/tag_yinxiao/cf.html
  • 介绍Blender软件(创建物体,物体颜色(材质属性上),点光,摄像机)
  • lingo3d
    • 相机(第一人称相机,第三人称相机,轨道相机)轨道相机可以触摸屏或者鼠标点击交互
    •  
  • 新建一个demo开发测试
    • 使用vite创建 yarn create vite
    • 第三方依赖:lingo3d-react @1.2.77
    • 查找天空背景:google key words: equirectangular sky / skybox background
    •  <World>
            <OrbitCamera active enableZoom autoRotate />
            <Model src="house2.glb"></Model>
          </World>  、
    • 引入人物模型,https://sketchfab.com/feed,优点:全球最全的3的模型网站,并且所有的模型网站都是可以直接加载到网站上面展示出来的
  • demo1
      • //step1
            <World>
              <Cube width={9999} depth={9999} y={-100} color={'gray'}/>
              <Model/>
            </World>
        
        //step2
        function App() {
          const [position, setPosition] = useState({x:0,y:0,z:0})

          return (
            <World>
              <Cube
                width={9999}
                depth={9999}
                y={-100}
                color='gray'
                onClick={
                  (e)=>{
                    setPosition({x:e.point.x,y:e.point.y,z:e.point.z})}
                }
              />
              <Model
                src='Idle.fbx'
              />
              <Cube
                color="orange"
                scale={0.2}
                x={position.x}
                y={position.y}
                z={position.z}
              />
              <OrbitCamera active />
            </World>
          )
        }
         
  • demo2
      •   
        //step1 添加地图 第三人称相机 人物w移动
        function App() {
          const modelRef = useRef()
        
          return (
            <World>
              <Model
                src="map/cybercity_2099_v2/scene.gltf"
                physics="map"
                scale={120}
              />
              <ThirdPersonCamera active mouseControl>
                <Model 
                  src='Idle.fbx'
                  physics="character"
                  ref={modelRef}
                  intersectIDs={['orangeBox']}
                  onIntersect={()=>{
                    console.log(1)
                    setMove(false)
                  }}
                  animations={{idle:'Idle.fbx',running:'Running.fbx'}}
                  animation={"idle"}
                />
              </ThirdPersonCamera>
              <Keyboard
                 onKeyPress={key => {
                  if (key === "w") {
                    modelRef.current?.moveForward(-10)
                  } else if (key === "Space"){
                    modelRef.current!.velocity.y = 5
                  } else if (key === "f"){
                    modelRef.current!.velocity.y = 5
                  }
                }}
              />
        
              <Skybox texture='sky.jpg'/>
            </World>
          )
        }
        
        //step2  添加状态机 改变动作  @xstate/react  和 xstate  vscode插件xState 可视化,
        //stateMachines/postMachine

        import { createMachine } from "xstate";

        export default createMachine({
            states: {
                "idle": {
                    on: {
                        KEY_W_DOWN: "running",
                        KEY_SPACE_DOWN: "jumping",
                        KEY_F_DOWN: "flying"
                    }
                },
                "running": {
                    on: {
                        KEY_W_UP: "idle",
                        KEY_SPACE_DOWN: "jumping",
                        KEY_F_DOWN: "flying"
                    }
                },
                "jumping": {
                    on: {
                        LANDED: "idle",
                        KEY_F_DOWN: "flying"
                    },
                    entry: "enterJumping"
                },
                "flying": {
                    on: {
                        LANDED: "idle"
                    },
                    entry: "enterFlying"
                }
            },
            initial: "idle"
        })
         

        //app,tsx
          const [pose, sendPose] = useMachine(poseMachine)
              <Model
                  physics="character"
                  src="Idle.fbx"
                  animations={{
                    idle: 'Idle.fbx',
                    running: 'Running.fbx',
                    jumping: "Jumping.fbx",
                    flying: "Flying.fbx",
                  }}
                  animation={pose.value as any}
                />
              <Keyboard
                onKeyPress={key => {
                  if (key === "w") {
                    sendPose("KEY_W_DOWN")
                  } else if (key === "Space"){
                    sendPose("KEY_SPACE_DOWN")
                  } else if (key === "f"){
                    sendPose("KEY_F_DOWN")
                  }
                }}
                onKeyUp={key => {
                  if (key === "w")
                    sendPose("KEY_W_UP")
                }}
              />



        //添加修改app.tsx 使用useMef()  bot.onLoop函数每秒执行60次
        
        
        import { useMachine } from '@xstate/react'
         const modelRef = useMef()
          const [pose, sendPose] = useMachine(poseMachine, {
            actions: {
              enterJumping: () => {
                const bot = modelRef.current
                if (bot === null) return

                bot.velocity.y = 10

                bot.onLoop = () => {
                  if (bot.velocity.y === 0) {
                    bot.onLoop = undefined
                    sendPose("LANDED")
                  }
                }
              },
              enterFlying: () => {
                const bot = modelRef.current
                if (bot === null) return

                bot.onLoop = () => {
                  if (bot.velocity.y === 0) {
                    bot.onLoop = undefined
                    sendPose("LANDED")
                  }
                }
              }
            }
          })
         
                <Model
                  physics="character"
                  ref={modelRef}
                  src="Idle.fbx"
                  animations={{
                    idle: 'Idle.fbx',
                    running: 'Running.fbx',
                    jumping: "Jumping.fbx",
                    flying: "Flying.fbx",
                  }}
                  animation={pose.value as any}
                />
        
        
        
        
              
              <Keyboard
                onKeyPress={key => {
                  if (key === "w") {
                    sendPose("KEY_W_DOWN")
                    modelRef.current?.moveForward(-10)
                  } else if (key === "Space"){
                    sendPose("KEY_SPACE_DOWN")
                  } else if (key === "f"){
                    sendPose("KEY_F_DOWN")
                    modelRef.current!.velocity.y = 2
                  }
                }}
                onKeyUp={key => {
                  if (key === "w")
                    sendPose("KEY_W_UP")
                }}
              />
  • demo3 添加准星+添加汽车
  • //添加汽车
         <Model
            ref={carRef}
            physics="character"
            id="car"
            src="pixel_car/scene.gltf"
            scale={2}
            x={-2319.68}
            y={-3855.55}
            z={-10600.00}         
         />
    
    //添加准星
    <Reticle color="white" variant={7} />

    第三人称绑定
      const xSpring = useSpring({ to: camX, bounce: 0 })
      const ySpring = useSpring({ to: camY, bounce: 0 })
      const zSpring = useSpring({ to: camZ, bounce: 0 })
    //添加显示
          <Model
            ref={carRef}
            physics="character"
            id="car"
            src="pixel_car/scene.gltf"
            scale={2}
            x={-2319.68}
            y={-3855.55}
            z={-10600.00}
          >
            <Find
              name="GLTF_SceneRootNode"
            >
              <HTML>
                <div style={{ color: "white" }}>
                  <div style={{ fontWeight: "bold", fontSize: 20 }} duration={1000}>
                    一款霸气的汽车
                  </div>
                  <div>
                    按G上下车
                  </div>
                </div>
              </HTML>
            </Find>
          </Model>
    
    

     

二. 正式开发

  • demo1 鼠标点击控制人物移动
  • import { useState,useRef } from 'react'
    import './App.css'
    import { World,Cube,Model, OrbitCamera, Skybox, useLoop } from 'lingo3d-react'
    
    function App() {
      const [position, setPosition] = useState({x:0,y:0,z:0})
      const [move, setMove] = useState(false)
      const modelRef = useRef()
    
      useLoop(()=>{
        const model = modelRef.current
        model.moveForward(-5)
      },move)
    
      return (
        <World>
          <Cube
            width={9999}
            depth={9999}
            y={-200}
            onClick={
              (e)=>{
                setMove(true);
                setPosition({x:e.point.x, y:0, z:e.point.z})
                const model = modelRef.current
                model.lookAt(e.point)
              }
            }
            />
          <Model
            ref={modelRef}
            src="Idle.fbx"
            animations={{idle:'Idle.fbx',running:'Running.fbx'}}
            animation={move?"running" : "idle"}
            intersectIDs={['orange']}
            onIntersect={()=>{ setMove(false)}}
          />
          <OrbitCamera active />
          <Cube scale={0.5} id="orange" color="orange" x={position.x} y={position.y} z={position.z}/>
    
          <Skybox texture="sky.jpg"/>
        </World>
      )
    }
    
    export default App
  • demo2 键盘+状态管理机(@xstate/react  和 xstate),键盘控制人物移动
  • import { useMachine } from '@xstate/react'
    import {  useRef } from 'react'
    import './App.css'
    import poseMachine from './stateMachines/postMachine'
    import { World, Cube, Model, OrbitCamera, Skybox, useLoop, Editor, FirstPersonCamera, ThirdPersonCamera, Keyboard } from 'lingo3d-react'
    
    function App() {
      const botRef = useRef()
    
      const [pose, sendPose] = useMachine(poseMachine, {
        actions: {
          enterJumping: () => {
            const bot = botRef.current
            if (bot === null) return
    
            bot.velocity.y = 10
    
            bot.onLoop = () => {
              if (bot.velocity.y === 0) {
                bot.onLoop = undefined
                sendPose("LANDED")
              }
            }
          },
          enterFlying: () => {
            const bot = botRef.current
            if (bot === null) return
    
            bot.onLoop = () => {
              if (bot.velocity.y === 0) {
                bot.onLoop = undefined
                sendPose("LANDED")
              }
            }
          }
        }
      })
    
      return (
        <World >
          <Model src="map/cybercity_2099_v2/scene.gltf" scale={120} physics="map" innerRotationY={180} />
          <ThirdPersonCamera active mouseControl>
            <Model
              physics="character"
              ref={botRef}
              src="Idle.fbx"
              animations={{
                idle: 'Idle.fbx',
                running: 'Running.fbx',
                jumping: "Jumping.fbx",
                flying: "Flying.fbx",
              }}
              animation={pose.value as any}
            />
          </ThirdPersonCamera >
    
    
          <Skybox texture="sky.jpg" />
          {/* <Editor/> */}
          <Keyboard
            onKeyPress={key => {
              if (key === "w") {
                sendPose("KEY_W_DOWN")
                botRef.current?.moveForward(-10)
              } else if (key === "Space"){
                sendPose("KEY_SPACE_DOWN")
              } else if (key === "f"){
                sendPose("KEY_F_DOWN")
                botRef.current.velocity.y = 2
              }
            }}
            onKeyUp={key => {
              if (key === "w")
                sendPose("KEY_W_UP")
            }}
          />
        </World>
      )
    }
    
    export default App

     

  • 状态机:
  • import { createMachine } from "xstate";
    
    export default createMachine({
        states: {
            "idle": {
                on: {
                    KEY_W_DOWN: "running",
                    KEY_SPACE_DOWN: "jumping",
                    KEY_F_DOWN: "flying"
                }
            },
            "running": {
                on: {
                    KEY_W_UP: "idle",
                    KEY_SPACE_DOWN: "jumping",
                    KEY_F_DOWN: "flying"
                }
            },
            "jumping": {
                on: {
                    LANDED: "idle",
                    KEY_F_DOWN: "flying"
                },
                entry: "enterJumping"
            },
            "flying": {
                on: {
                    LANDED: "idle"
                },
                entry: "enterFlying"
            }
        },
        initial: "idle"
    })

     

  • demo3 瞄准器+制作汽车彩蛋+场景优化(@lincode/react-anim-text)  useSpring(弹簧--)
import { useMachine } from '@xstate/react'
import { useRef, useState } from 'react'
import './App.css'
import poseMachine from './stateMachines/postMachine'
import { World, Cube, HTML, Model, OrbitCamera, Skybox, useLoop, Editor, FirstPersonCamera, ThirdPersonCamera, Keyboard, Reticle, useSpring, Find } from 'lingo3d-react'
import AnimText from "@lincode/react-anim-text"

function App() {
  const botRef = useRef()
  const carRef = useRef()
  const [mouseOver, setMouseOver] = useState(false)
  const [driveCar, setDriveCar] = useState(false)
  const [intersectCar, setIntersectCar] = useState(false)


  const [pose, sendPose] = useMachine(poseMachine, {
    actions: {
      enterJumping: () => {
        const bot = botRef.current
        if (bot === null) return

        bot.velocity.y = 10

        bot.onLoop = () => {
          if (bot.velocity.y === 0) {
            bot.onLoop = undefined
            sendPose("LANDED")
          }
        }
      },
      enterFlying: () => {
        const bot = botRef.current
        if (bot === null) return

        bot.onLoop = () => {
          if (bot.velocity.y === 0) {
            bot.onLoop = undefined
            sendPose("LANDED")
          }
        }
      }
    }
  })

  const camX = mouseOver ? 25 : 0
  const camY = mouseOver ? 50 : 50
  const camZ = mouseOver ? 50 : 200

  const xSpring = useSpring({ to: camX, bounce: 0 })
  const ySpring = useSpring({ to: camY, bounce: 0 })
  const zSpring = useSpring({ to: camZ, bounce: 0 })

  return (
    <>
      <World >
        <Model src="map/cybercity_2099_v2/scene.gltf" scale={120} physics="map" innerRotationY={180} >
          {/* <Find
           name="Hovercars_Holograms_0"
           outline={mouseOver}
           onMouseOver={() => setMouseOver(true)}
           onMouseOut={() => setMouseOver(false)}
          >
            {mouseOver && (
              <HTML>
                <div style={{ color: "white" }}>
                  <AnimText style={{ fontWeight: "bold", fontSize: 20 }} duration={1000}>
                    可乐汉堡店铺
                  </AnimText>
                  <AnimText duration={1000}>
                    hamburger coca-cola
                  </AnimText>
                </div>
              </HTML>
            )}
          </Find> */}
        </Model>
        <ThirdPersonCamera
          active={!driveCar}
          mouseControl
          innerY={ySpring}
          innerZ={zSpring}
          innerX={xSpring}
        >
          <Model
            physics="character"
            ref={botRef}
            src="Idle.fbx"
            animations={{
              idle: 'Idle.fbx',
              running: 'Running.fbx',
              jumping: "Jumping.fbx",
              flying: "Flying.fbx",
            }}
            animation={pose.value as any}
            intersectIDs={["car"]}
            onIntersect={() => setIntersectCar(true)}
            onIntersectOut={() => setIntersectCar(false)}
          />
        </ThirdPersonCamera >
        <ThirdPersonCamera
          active={driveCar}
          mouseControl
          innerY={ySpring}
          innerZ={zSpring}
          innerX={xSpring}
        >
          <Model
            ref={carRef}
            physics="character"
            id="car"
            src="pixel_car/scene.gltf"
            scale={2}
            x={-2319.68}
            y={-3855.55}
            z={-10600.00}
          >
            <Find
              name="GLTF_SceneRootNode"
              outline={!driveCar && mouseOver}
              onMouseOver={() => setMouseOver(true)}
              onMouseOut={() => setMouseOver(false)}
            >
              {!driveCar && mouseOver && (
                <HTML>
                  <div style={{ color: "white" }}>
                    <AnimText style={{ fontWeight: "bold", fontSize: 20 }} duration={1000}>
                      一款霸气的汽车
                    </AnimText>
                    <AnimText duration={1000}>
                      按G上下车
                    </AnimText>
                  </div>
                </HTML>
              )}
            </Find>
          </Model>
        </ThirdPersonCamera >
        <Skybox texture="sky.jpg" />
        {/* <Editor/> */}
        <Keyboard
          onKeyPress={key => {
            console.log(key)
            if (driveCar) {
              if (key === "w") {
                carRef.current?.moveForward(-20)
              }
            } else {
              if (key === "w") {
                sendPose("KEY_W_DOWN")
                botRef.current?.moveForward(-10)
              } else if (key === "Space") {
                sendPose("KEY_SPACE_DOWN")
              } else if (key === "f") {
                sendPose("KEY_F_DOWN")
                botRef.current.velocity.y = 2
              }
            }

          }}
          onKeyUp={key => {
            if (key === "w") {
              sendPose("KEY_W_UP")
            } else if (key === "g") {
              if (driveCar) {
                botRef.current!.x = carRef.current?.x
                botRef.current!.y = carRef.current?.y
                botRef.current!.z = carRef.current?.z
                setDriveCar(false)
              } else if (intersectCar) {
                setDriveCar(true)
              }
            }
          }}
        />
        {/* <Editor/> */}

      </World>
      {!driveCar && <Reticle color="white" variant={7} />}
    </>
  )
}

export default App


🔸3D模型下载网站:https://sketchfab.com/feed
🔸3D人物动作绑定:www.mixamo.com
🔸3D角色生产工具:https://readyplayer.me/
🔸模型压缩网站:gltf.report
🔸查找天空背景:google key words: equirectangular sky / skybox background
🔸材质贴图素材:https://www.textures.com
🔸hdr素材库(环境贴图): https://polyhaven.com
🔸二次元风3D角色生产软件VRoid Studio: https://vroid.com/en/studio

 

posted on 2022-05-16 21:26  康心志  阅读(539)  评论(0编辑  收藏  举报

导航