14

image

近期在尝试着学习写一些3D小游戏,分享一下成果。
严格上说还算不上完整的游戏,感兴趣的朋友可以观摩一下。

准备

在这里用到的唯一素材:小飞机

是在 Blender 软件了里面简单设计的

Blender 是一个免费开源的建模软件

如果感兴趣,这里推荐B站上的一个 入门教程

下面是我的一些步骤

cover-0.png

  1. 使用简单的几何元素构建出飞机模型
  2. 制作2个小动画:螺旋桨旋转机身摇晃
  3. 导出 .glb 格式模型

模型导入到 Babylon 的代码如下

主要用到2个参数 meshesanimationGroups

{
    animationGroups: (2) [AnimationGroup, AnimationGroup]
    geometries: (9) [Geometry, Geometry, Geometry, Geometry, Geometry]
    lights: []
    meshes: (10) [Mesh, Mesh, Mesh, Mesh, Mesh, Mesh, Mesh, Mesh, Mesh, Mesh]
    particleSystems: []
    skeletons: []
    transformNodes: (5) [TransformNode, TransformNode, TransformNode]
    __proto__: Object

代码

在这个项目主要实现一下几个环节

  1. 基本场景的搭建
  2. 飞机跟随鼠标的运动
  3. 粒子的生成和运动
  4. 飞机与粒子的碰撞

飞机模型的加载

    // 加载飞机模型
    private async loadPlane(): Promise<any> {
        // 新建一个透明元素 包裹模型
        const container = MeshBuilder.CreateBox('plane-container', { width: 1, depth: 2, height:1 }, this.scene)
        container.isVisible = false;

        // 调整到与模型重合的位置
        container.bakeTransformIntoVertices(Matrix.Translation(0, 1.2, 0.8))
        container.rotation.y = -Math.PI / 2
        container.position.x = 0.6

        // 加载飞机模型
        const glb = await SceneLoader.ImportMeshAsync(null, './public/', 'plane.glb', this.scene)
        const root = glb.meshes[0]
        console.log('glb', glb)
        // 绑定父子关系
        root.parent = container

        return {
            mesh: container,
            fly: () => {
                glb.animationGroups[0].play(true)
                glb.animationGroups[1].play(true)
            },
            stop: () => {
                glb.animationGroups[0].stop(),
                glb.animationGroups[1].stop()
            }
        }
    }

飞机模型的运动跟随

  1. 鼠标移动时获取 鼠标坐标飞机 坐标
  2. 飞机坐标 需要 3维坐标屏幕坐标 的转化
  3. 飞机坐标鼠标坐标 移动
...
    // 获取鼠标坐标存储
    this.scene.onPointerObservable.add(info => {
        const { event } = info
        // 存储鼠标坐标数据
        if (event.type === 'pointermove') {
            this.pointerPos = {
                x: event.x,
                y: event.y
            }
        }
    })
...
/** Loop中更新plane坐标 */
private updatePlane(): void {
    // 设置平滑系数-不断尝试得到到数值
    const smoothing = 2000
    // 获取plane屏幕坐标
    const originPos = this.WorldToScreen(this.plane.mesh.position)
    if (this.pointerPos.x && this.pointerPos.y) {
        // 计算鼠标位置 和 plane 位置得距离
        const deltaX = (this.pointerPos.x - originPos.x) / smoothing
        const deltaY = (this.pointerPos.y - originPos.y) / smoothing
        // plane 朝鼠标的方向移动
        this.plane.mesh.position.x += deltaX
        this.plane.mesh.position.y -= deltaY
    }
}

生成粒子

/** 
 * 生成粒子数据
 * 每个间隔时间生成粒子:并插入到 particles 中
 */
private initParticles(): void {
    // 限制 scene 最多的粒子为90
    const LIMIT = 90
    this.particles = []
    setInterval(() => {
        if (this.particles.length > LIMIT || this.state !== State.GAME) return
        // 创建粒子
        const particle = this.createParticle()
        // 随机放置粒子
        particle.position.x = 20 + Math.random() * 20
        particle.position.y = -10 + Math.random() * 20
        particle.position.z = 0
        // 粒子插入 particles 中:方便后面更新操作
        this.particles.push(particle)
    }, 300)
}

更新粒子运动 并检测 是否与飞机碰撞

/** Loop中更新粒子数据 */
private updateParticles(): void {
    // 定义粒子移动速度
    const SPEED = 0.15
    this.particles.forEach((e, index) => {
        // 粒子差不多离开屏幕后,回收重制到初始位置
        if (e.position.x < -20) {
            e.position.x = 20 + Math.random() * 20
        }
        e.position.x -= SPEED
        // 检测粒子是否和 plane 发生碰撞
        const collided = e.intersectsMesh(this.plane.mesh)
        if (collided) {
            this.particles.splice(index, 1)
            e.dispose()
            if (e.name === 'sphere') {
                this.score++
                this.updateScore()
                console.log('collided')
            }
        }
    })
}

其他

项目的主要流程就是这些了

还有其他的一些,比如:

  • 场景的搭建
  • 相机的处理
  • 灯光的处理
  • 游戏状态的处理

欢迎到GitHub查看完整代码

谢谢阅读

喜欢的话,点赞 star 支持

JesseLuo
1.8k 声望140 粉丝

前端工程师