人人都会写的3D小游戏

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 支持

前端随笔
[链接]

前端工程师

1.8k 声望
139 粉丝
0 条评论
推荐阅读
从零搭建 Node.js 企业级 Web 服务器(零):静态服务
过去 5 年,我前后在菜鸟网络和蚂蚁金服做开发工作,一方面支撑业务团队开发各类业务系统,另一方面在自己的技术团队做基础技术建设。期间借着 Node.js 的锋芒做了不少 Web 系统,有的至今生气蓬勃、有的早已夭折...

乌柏木148阅读 12.1k评论 10

JavaScript有用的代码片段和trick
平时工作过程中可以用到的实用代码集棉。判断对象否为空 {代码...} 浮点数取整 {代码...} 注意:前三种方法只适用于32个位整数,对于负数的处理上和Math.floor是不同的。 {代码...} 生成6位数字验证码 {代码...} ...

jenemy46阅读 5.8k评论 12

从零搭建 Node.js 企业级 Web 服务器(十五):总结与展望
总结截止到本章 “从零搭建 Node.js 企业级 Web 服务器” 主题共计 16 章内容就更新完毕了,回顾第零章曾写道:搭建一个 Node.js 企业级 Web 服务器并非难事,只是必须做好几个关键事项这几件必须做好的关键事项就...

乌柏木65阅读 6k评论 16

再也不学AJAX了!(二)使用AJAX ① XMLHttpRequest
「再也不学 AJAX 了」是一个以 AJAX 为主题的系列文章,希望读者通过阅读本系列文章,能够对 AJAX 技术有更加深入的认识和理解,从此能够再也不用专门学习 AJAX。本篇文章为该系列的第二篇,最近更新于 2023 年 1...

libinfs39阅读 6.3k评论 12

封面图
从零搭建 Node.js 企业级 Web 服务器(一):接口与分层
分层规范从本章起,正式进入企业级 Web 服务器核心内容。通常,一块完整的业务逻辑是由视图层、控制层、服务层、模型层共同定义与实现的,如下图:从上至下,抽象层次逐渐加深。从下至上,业务细节逐渐清晰。视图...

乌柏木42阅读 7.2k评论 6

CSS 绘制一只思否猫
欢迎关注我的公众号:前端侦探练习 CSS 有一个比较有趣的方式,就是发挥想象,绘制各式各样的图案,比如来绘制一只思否猫?思否猫,SegmentFault 思否的吉祥物,是一只独一无二、特立独行、热爱自由的(&gt;^ω^&lt...

XboxYan42阅读 2.9k评论 14

封面图
从零搭建 Node.js 企业级 Web 服务器(二):校验
校验就是对输入条件的约束,避免无效的输入引起异常。Web 系统的用户输入主要为编辑与提交各类表单,一方面校验要做在编辑表单字段与提交的时候,另一方面接收表单的接口也要做足校验行为,通过前后端共同控制输...

乌柏木33阅读 6.1k评论 9

前端工程师

1.8k 声望
139 粉丝
宣传栏