Bootstrap

微信小游戏0基础学习记录:0.一些准备知识&起步

因为本人对游戏引擎、前端、JS、H5什么的都不甚了解,可以说完全是兴趣使然的0基础学习,所以目前主要是跟着官方文档和教程一步步地了解,上手会比较慢,会有很多基础知识的介绍。

微信小游戏  官方文档:https://developers.weixin.qq.com/minigame/dev/guide/

一、微信小游戏是什么?做了什么?

        微信小游戏是一种JavaScript的运行环境,就像浏览器、Node.js一样,JavaScript代码可以调用这些宿主环境提供的API来获取画面绘制、音频播放、网络访问等能力,区别是不同环境下的API会有所不同。

        例如浏览器,它提供BOM(浏览器Browser对象模型,用于控制浏览器的行为)和DOM(文档Document对象模型,用于操作html页面元素)API,当想要创建画布或者获取屏幕规格时,可以使用它内置的全局变量document和window来新建一个Canvas元素或者访问屏幕宽高。

        对应到微信小游戏的环境中时,那就需要改为使用微信自己的 wx API。

// 浏览器
let canvas = document.createElement('canvas')
console.log(window.innerWidth)
console.log(window.innerHeight)

// 微信小游戏
let canvas = wx.createCanvas()
let { screenWidth, screenHeight } = wx.getSystemInfoSync()

        而游戏开发本质地来看,便是调用“底层API”来实现“游戏业务逻辑”,并在这二者之间使用游戏引擎或框架来抽象并简化开发流程。官方指南里,主要介绍了微信小游戏在这之上的两个工作,一是兼容第三方游戏引擎,二是提供它自己的小游戏框架。

来自微信小游戏官方文档

二、对第三方游戏引擎的兼容

        首先,微信小游戏本身已经在微信小程序的基础上,增加了WebGL API的支持。(什么是WebGL?简单来说就是一种用于网页中的图形尤其是3D图形渲染的JavaScript API,因为工作在GPU上所以比较高效。)

        然后,简单介绍一下微信小游戏对一些现有的游戏引擎的兼容工作。

        如上3个条目中,游戏引擎其实可以大致分为两类,基于HTML5的游戏引擎如Cocos Creator,和原生目标引擎如Unity、cocos2d-x

        1、基于HTML5的游戏引擎

        这类引擎一般都是依赖BOM、DOM等浏览器API,因此兼容工作的重点是将浏览器API转换为wx API。例如Cocos Creator,便是对引擎框架进行了微信小游戏的API的适配。微信小游戏官方也提出了一个开发者层面的适配层概念,Adapter,并实现了一个示例库weapp-adapter,它对可能被访问的浏览器属性和方法进行了模拟,例如document.createElement、Audio、Image等。

// 示例:封装wx API来模拟document.createElement
var document = {
    createElement: function (tagName) {
        tagName = tagName.toLowerCase()
        if (tagName === 'canvas') {
            return wx.createCanvas()
        }
        else if (tagName === 'image') {
            return wx.createImage()
        }
    }
}

// 示例:封装wx API来模拟Image()新建Image对象
function Image () {
    return wx.createImage()
}

       2、原生游戏引擎

        这类游戏引擎的兼容原理主要是使用WebAssembly技术。

        (什么是WebAssembly?这是一种可以像JS一样运行在Web环境的类汇编语言,它可以是C、C++、C#等语言的编译目标,可被用来移植C、C++等应用。

        目前比较流行的一个工具是EMScripten,它可以将C和C++的代码编译为.wasm也就是WebAssembly,并创建JS胶水代码来使WebAssembly可通过JS调用Web API。)

        Unity的WebGL构建选项便是利用了WebAssembly技术来编译Unity的C/C++代码和C#脚本,并导出可在浏览器环境中运行的游戏包。而微信小程序为了兼容Unity引擎的WebGL导出包,提供了开发阶段的C# SDK、导出阶段的胶水层适配,和运行阶段WebAssembly的基础能力支持。

        其它如cocos2d-x等原生游戏引擎,也是类似的原理。只要它们的编程语言可以转换为WebAssembly,那么理论上就都可以兼容到微信小游戏平台。

三、微信小游戏官方框架 

         相比于旧的使用第三方游戏引擎的开发模式,微信小游戏已经推出了自己的全新框架,并针对重度游戏尤其是3D游戏进行了逻辑计算和渲染上的优化。

        所以这个专栏就计划不再学习别的第三方引擎了,直接从微信小游戏的官方模板开始学习3D小游戏开发。

1、安装开发工具并新建3D项目

        这一步就根据官方文档走,包括申请AppID、安装开发工具,然后选择新建一个3D项目。

        踩坑:一开始一直卡在“初始化engineWebview”上,后来参考这个问题里提出的方案,1、下载最新版小游戏工具;2、项目不放置在用户目录下;就解决了。

        进入微信小游戏的开发界面。

        2、起步:制作简单游戏的新手引导(1)

        点击新手引导案例里的第一个链接“制作简单游戏的新手引导”,微信开发工具就会自动下载一个3D小游戏的相关资源,选择导入并启动之后IDE会展示出一个Tutorial栏——它会指导并自动检测每一步是否完成,还挺人性化的。

        接下来就按照新手引导一步步操作,并顺带了解每一步的基础知识点。本篇就先介绍第一个部分,"创建并编辑3D场景"

        创建一个3D场景

        首先,了解assets目录。它主要存放本地的资源文件,包括场景、模型、代码等。该目录下的资源会编译为小游戏可以理解的格式,并存储到对应的_temp文件夹下,在播放态时加载使用。例如微信小游戏默认使用TypeScript语言,可以在该目录下开发TypeScript代码,之后会编译成JavaScript。

        然后,了解Scene。它代表游戏中的场景,比如说一个关卡对应一个场景。微信小游戏提供2d和3d两种Scene,3dScene会默认添加一个相机和平行光节点。个人理解,光节点负责打光,相机大概就是游戏运行时用户的视角。

        踩坑:Tutorial只有在检查到创建了指定Scene之后才能进行下一步,一开始少创建了一层Assets,一直不通过还以为是有什么Bug......

        创建一个3D节点Plane

         首先,了解Mesh与Plane。Mesh网格可以看作是一种描述三维物体的数据结构,它包含物体的顶点集合和连接这些点的三角形集合,有了它们就可以绘制出一个3D物体的框架。微信小游戏的Mesh节点便是默认提供的一些3D形状,例如Cube立方体、Sphere球体、Capsule胶囊体、Cylinder圆柱体。

        Plane节点则被用来表示3D空间中的一个平面,常被作为地面、墙壁等,默认是一个10*10的正方形。新手引导中设置position y 为-2,x与z为0,应该便是将其作为一块平铺、略微下陷的地面;scale的x与z为5,则代表其延伸为了一个50*50的正方形。

        踩坑:微信小游戏的开发工具好像必须得用鼠标,不然不仅不能拉远、拉近,连Inspector栏都没办法上下滑动(windows笔记本)(应急的话,可以拖动文件到Inspector栏再向下滑)。

        设置MeshRenderer组件材质

        首先,了解一下组件的概念。微信小游戏采用了类ECS(Entity-Component-System)的框架,其中,实体Entity是游戏里承载“功能”的基本单元,而“功能”由“组件”提供,添加一个组件就意味着承载一个对应的功能。例如一个节点添加了一个Transform3D组件,它便具备了在三个维度上的位置、缩放、旋转等属性,也就意味着它是一个3D节点,添加Transform2D组件则是2D节点;再例如一个默认的相机节点,会默认添加Transform3D和Camera组件,意味着它是一个具备Camera功能的3D节点。也因此,一个节点Entity可看作是由一组具备功能/数据/属性的组件Component构成,而系统System就负责处理挂载了相应组件的实体,用它们的能力实现行为。例如下面会介绍到的物理系统。

        然后,了解MeshRenderer网格渲染器。个人理解,游戏中一个物体要想展示出来,就需要具备渲染功能,就像Plane节点,它默认有一个Transform3D和一个MeshRenderer组件,后者就负责渲染出一个平面。而参考官方文档,微信小游戏的渲染模块把渲染所需的数据抽象为了如网格Mesh、贴图Texture、着色器Shader、效果Effect和材质Material等资源,渲染器负责组装这些资源。其中,MeshRenderer是最基础的一类渲染器,用来渲染静态模型。

        查看Plane的MeshRenderer组件,发现它主要指定了Mesh源文件.mesh和Material源文件.mat。Mesh前面介绍过,它主要用来描绘物体的“形状”,Plane节点就是使用内置的一个Plane.mesh来指定一个平面的形状;Material则用来描述物体如何被绘制,例如物体表面的一些如颜色、纹理等属性。在Unity中,Material的本质是着色器Shader的实例,而微信小游戏则在Shader的基础上又添加了一些其它渲染效果的管理,并抽象出效果Effect这一概念,所以微信小游戏中材质Material是效果Effect的实例,效果是材质的模板

        本步骤中,Tutorial提供了一个自定义的Plane材质plane.mat,可以在Inspector栏查看并设置它的一些渲染属性。首先是Effect(毕竟材质是效果的实例),Tutorial设置了一个内置的BlinnPhong.effect(Blinn Phong着色器是一种常用的光照模型,可以模拟物体表面在不同光源与观察角度下的明暗变化);renderQueue则可以指定使用该材质的物体的渲染顺序,数值越大顺序越靠后;useInstance可指定使用该材质的物体是否开启GPU实体化Instancing;其它还可以设置绘制选项如混合模式、着色器属性如_color、纹理等等,并覆盖Effect中设置好的默认值。

        直接使用Tutorial的默认值,设置完成后,Plane节点就会呈现为指定的绿色草地背景。

        添加物理组件和自定义脚本组件

        (这里Tutorial好像写错了,SphereCollider和Regidbody都是Physics下面的物理组件。)

        ​​​

        物理系统与物理组件

        首先,了解物理系统物理组件。在游戏框架中,物理系统负责对那些挂载了物理组件的实体进行物理模拟,为它们实现符合物理规律的移动、旋转、碰撞等行为(回忆之前ECS的介绍)。例如碰撞体组件Collider,物理系统会根据Collider描述出的物体的形状,在物理模拟阶段判断物体是否发生了碰撞,进而改变它的运动状态,像SphereCollider,便是默认提供的一种球形的基础碰撞体;而要改变物体的运动状态,就离不开刚体组件Rigidbody——只有挂载了该组件的物体才可以在物理模拟阶段进行位移或旋转,假如没有Rigidbody而只单独使用Collider组件,那这个物体就是一个完全静止的碰撞体。

        Tutorial引导设置了Rigidbody组件关于position、rotation和useGravity的几个属性。

        打开Inspector查看,可以看到Rigidbody主要持有了如下几个重要属性。例如mass,它表示刚体的质量,用于计算受力状态下的加速度,当使用addForce方法对刚体施加一个力时,它就会按照物理规律进行移动;linearDamping线性阻尼和angularDamping角阻尼分别用于衰减线性速度和角速度;useGravity决定刚体是否受到重力影响,isKinematic则决定刚体是否是一个运动学刚体,如果该值为true,那么它就是一个运动学刚体,这意味着它将不再受物理系统中的力的影响,换句话说,addForce、碰撞或者重力等都无法使其产生加速度了。

        看到这里发现,Rigidbody持有一套位置与旋转的属性,这使得它可以向刚体提供在物理模拟阶段进行位移与旋转的能力。而这个实体作为一个3D节点,也同时具备一个持有位置与旋转属性的Transform3D组件,同样可以实现它的位移和旋转。那Rigidbody和Transform的position、rotation有什么不同呢?

        根据官方文档介绍,微信小游戏的框架中,物理系统和节点系统并不能实时地共享物体的位置与旋转,也就是说它们的确是拥有各自的一套位置与旋转属性,并在每次的物理模拟的开始与结束时进行同步。那么“物理模拟的开始与结束时”又是怎样的一个时机呢?这里就又需要了解一下小游戏的生命周期

        生命周期

        如下是组件的生命周期的流程示意图。这里重点介绍一下初始化之后,“游戏逻辑->物理->游戏逻辑”这一会在游戏中循环进行的更新流程,它主要包含了3个生命周期回调,onUpdate、onFixedUpdate和onLateUpdate。其中,onUpdate和onLateUpdate都是每帧执行一次,前者常会执行一些改变物体位置、设置动画参数等游戏逻辑,后者则处理一些在onUpdate、动画和物理完成之后才可确定的逻辑,例如调整相机位置;而在onUpdate和onLateUpdate之间的便是物理模拟阶段,每次onFixedUpdate对应一次物理模拟,一次物理模拟的时长由物理系统engine.game.physicsSystem的属性maxPhysicsDeltaTime决定,它一般小于一个渲染帧的时长,也就是说一帧之内可能会进行多次物理模拟。

        了解了生命周期后,再来看文档中介绍的Transform和Rigidbody位置修改上的不同:一般来说,如果想要物体实现瞬移,那么修改Transform组件的position即可,但是如果想要修改的效果立刻在物理世界中生效,例如在下一次物理模拟之前,那么就需要修改Rigidbody的position属性。

        更多的细节可能得在后续的学习过程中慢慢了解。

来自微信小游戏官方文档

        脚本组件

        最后,了解脚本组件。一个脚本组件可以看作是一个继承自engine.Script的类(engine.Script则是继承自engine.Component)。不像Rigidbody等内置组件,脚本组件是由开发者自定义开发的,就像本步骤中最后挂载的PlayerScript。

        如下是PlayerScript的部分代码,它定义了一个可序列化的脚本类PlayerScript,并注册了onAwake(只在初始化时执行一次)、onLateUpdate(每帧执行一次,在物理阶段之后)、onDestroy(只在销毁时执行一次,和onAwake是一对一的关系)这3个生命周期回调。除此之外,PlayerScript把主要的游戏逻辑放在了触摸事件和物理碰撞事件的监听回调中。

        更多的细节后续再讨论。

// 文件名 PlayerScript.ts,可以从assets目录下直接打开查看文件

import engine from 'engine';
...

// serialize 装饰器,标明这是一个可序列化的脚本,参数是这个序列化类型的全局唯一的名字
@engine.decorators.serialize("PlayerScript")
// 脚本组件需要:export default这个脚本类 + 继承Script
export default class PlayerScript extends engine.Script {
  ...
  // 类里注册onAwake、onUpdate、onLateUpdate等生命周期回调 
  public onAwake() {
    // 回调函数里,使用this.entity获取当前的实体Entity
    this.player = this.entity;
    ...
    // 添加 TouchMove事件监听器 和 物理事件监听器
  }

  public onLateUpdate(dt) {
    ...
    // 通知相机同步运动
    ...
  }
    
  // onWake阶段 添加 TouchMove事件 监听器
  public initEvent() {
    // 事件系统 engine.game.customEventEmitter,使用了EventEmitter3
    engine.game.customEventEmitter.on(EventTypes.TOUCH_MOVE, (...) => {
        ...
    });
  }
  // onAwake阶段 添加 碰撞事件 监听器
  public initColliderEvent() {
    // 通过this.entity.getComponent获取其它组件
    const controller = this.entity.getComponent(engine.SphereCollider) as engine.SphereCollider
    // collider.onCollisionEnter 获取碰撞体的碰撞事件的Delegate
    // add 注册碰撞事件回调
    controller.onCollisionEnter.add((...) => {
      ...
    }, this)
  }

  // 销毁阶段
  public onDestroy() {
    ...
    // 移除监听器
    ...
  }

}

         创建一个3D节点Cube

        首先,了解Cube。它和Plane一样,也是内置提供的一种3D节点,初始默认是一个1*1*1的立方体。本步骤中修改了它的scale属性,将其延伸为一个50*3*1的长方体。(回忆一下之前的Plane设置,是从一个10*10的平面延伸为50*50,所以现在它们是等长的。)

        然后,了解BoxCollider。它和SphereCollider一样,是内置提供的一种基础的长方体碰撞体。(框架主要提供了3种基础碰撞体,Box长方体,Sphere球体和Capsule胶囊体。)

        拷贝3D节点

        这步比较简单,注意层级即可。最后总共创建出4个wall节点,来作为Plane四面的墙壁。

        调整3D节点位置

         首先,了解编辑框上的这几个图标。其中绿色选中的图标表示“拖动模式”,它后面的4个图标则分别表示移动工具旋转工具缩放工具矩形变换工具,用于拖拽并修改对应的position、rotation、scale和size+position属性。

        选中wall节点和移动工具图标,可以看到一个以wall中心作为原点的红、绿、蓝三色坐标轴,三色箭头分别代表了x、y、z的正方向,点击箭头进行拖拽,就可以改变wall的位置。

        

        因为已经知道了wall和Panel的大小,所以这步也可以直接在Inspector栏设置position属性。首先可以在蓝色箭头也就是z轴方向上,放置两面墙壁,它们对应的z坐标分别是24.5和-24.5;同时考虑到Panel的y坐标为-2,wall的高为3,所以统一设置y为-0.5,让墙壁和Panel可以严丝合缝(如下两图)。

     

        其余两面墙壁需要进行旋转。点击旋转工具图标,可以看到红、绿、蓝三色的旋转控制线,依然是对应着x、y、z这3个方向,可以发现这里需要旋转的是绿色也就是y轴。将两个wall的rotation y属性都设置为90,这样便可以获得两面垂直的墙壁。

         

         注意:这里再次查看垂直wall的位置坐标系时,会发现x轴与z轴的方向发生了改变(下图左),而当拖拽蓝色也就是z轴箭头时,wall虽然会按拖拽方向移动,但改变的却是x坐标。个人理解,旋转改变的只是wall节点内部的坐标系方向,例如下图右,给wall添加一个子节点并为它设置x方向上的正坐标,那它的位置就符合红绿蓝的方向指示了。而wall节点本身的位置是基于它的父节点的坐标系方向的,这个坐标系不会受wall旋转影响,所以拖拽wall坐标系箭头移动wall的位置时会让人有些迷惑。

             

        按照原来的坐标系方向也就是x轴方向,调整两面墙壁位置,分别设置它们的position x为24.5和-24.5,最后效果如下。

         调整相机Camera

        

        首先,了解相机。它是框架中展示场景的“观察者”,会捕捉3D视图并对其进行平面化的展示。一般来说,一个相机节点会默认挂载Transform和Camera两个组件,其中,Transform组件设置视点的位置,并用z轴指定视图方向(下两图分别是修改前、后的视图方向);Camera组件则可以设置渲染目标(指定将看到的物体绘制到什么目标上,默认为空,即渲染至主屏上)、投影方式(默认为透视投影Perspective,简单来说就是近大远小,一个锥形视体)、跟随目标(相机在移动过程中会始终朝向目标tragetTransform,默认为空)等属性。

       

        本步骤中,Camera节点除了修改了position和rotation外,大部分属性都采用了默认值,并额外挂载了一个脚本组件 MoveCameraScript。

        查看该组件,发现它的游戏逻辑主要是注册事件MOVE_PLAYER的监听器,并在监听回调中根据参数move来修改自己的position;而回看PlayerScript,发现它是在每一帧的onLateUpdate回调里检查并计算Player的位置变化move,并触发事件MOVE_PLAYER的监听器,传入参数move。这就意味着,在游戏的每一帧中,相机的位置都会根据Player的位置进行同步变化。

// 文件 MoveCameraScript.ts
 
import engine from "engine";

@engine.decorators.serialize("MoveCameraScript")
export default class MoveCameraScript extends engine.Script {
  
  public onAwake() {
     // 注册一个 MOVE_PLAYER 事件监听器
     engine.game.customEventEmitter.on(EventTypes.MOVE_PLAYER, (move) => {
      // 直接修改 transform 的 position
      this.entity.transform.position.x += move.x;
      this.entity.transform.position.y += move.y;
      this.entity.transform.position.z += move.z;
    });
  }
 
  public onDestroy() {
    // 移除监听器
    ...
  }
}
// 文件 PlayerScript.ts 
...
public onLateUpdate(dt) {
    ...
    // 计算 Player 的位置变化
    const move = this.entity.transform.position.sub(this._lastPos)
    if (move.x !== 0 || move.y !== 0 || move.z !== 0) {
        // 触发 MOVE_PLAYER 事件的监听器
        engine.game.customEventEmitter.emit(EventTypes.MOVE_PLAYER, move); 
    }
  }

        这部分涉及到了框架的事件系统engine.game.customEventEmitter和它基于的EventEmitter3事件库,后续再详细了解。目前简单地看,就是这个系统允许使用方法on(event: string, listener: (...args: any[]) => any)来注册事件event的监听器,并使用emit(event: string, ...args: any[])来触发事件event的监听器。

        创建粒子特效

        首先,了解粒子系统Particle System。当创建一个3D节点Particle时,节点会默认挂载一个Transform3D组件和一个Particle System组件,后者继承自MeshRenderer,常被用来模拟一些如火花、云雾、发光轨迹等视觉特效。它的相关设置非常繁杂,可以去官方文档自行查看。

        本步骤中,该Particle节点设置了一个自定义材质.mat(回忆之前对MeshRenderer的介绍),并设置position z为-1。考虑到Particle是Player的子节点,Player又对x、y、z方向上的旋转都进行了限制,所以合理猜测在z轴方向上,Particle会一直处于相机与Player之间。

        创建预制体Prefab

        首先,了解预制体Prefab。它常被用于需要复用节点的场景中,即如果一类节点会在游戏中多次出现,那就可以将这类节点序列化为一个Prefab资源,Prefab负责存储该类节点的实体、组件以及这些实体和组件的属性值,并作为模板来创建该类节点的实例。

        像Tutorial中这样,将Cube节点拖入Project的文件夹中,便是生成了一个以Cube节点为根节点的Prefab资源,它会存储Cube所有子节点和组件的相关数据。

        这一步骤中,Cube还挂载了一个脚本组件CubeScript。查看代码,发现它的游戏逻辑是在每一帧的onUpdate回调中进行旋转变化,也就是说,Cube是一个一直进行自转的立方体。具体地看,Cube每帧的旋转幅度为(N * dt)。已知dt代表一帧的时长,一秒内的 帧数*dt 结果为1,那么一秒内的旋转幅度之和便为 (N * dt * 帧数),也就是N。

// 文件 CubeScript.ts
import engine from "engine";

@engine.decorators.serialize("CubeScript")
export default class CubeScript extends engine.Script {

  // 参数dt, delta time 增量时间,表示一帧的时长
  public onUpdate(dt) {
    // rotate, 对自身做旋转变化,参数列表分别表示 旋转幅度, 是否局部空间,是否弧度制
    this.entity.transform.rotate(engine.Vector3.createFromNumber(15*dt, 30* dt, 45 * dt), true, false)
  }
}

        使用Prefab实例化节点

        有了Cube节点对应的Prefab资源后,便可以使用Prefab来生成Cube实例。        

        一种情形是在编辑器中创建实例:可以在Hierarchy窗口新建节点,点击“添加Prefab”并选择对应的.prefab文件;也可以直接将Project中的.prefab文件拖拽至Hierarchy窗口的节点中。

        另一种情形是在游戏运行时,用代码进行Prefab的实例化。可以查看PlayerScript中加载Cube的相关代码,它大致分为了3步:加载.prefab文件(所以会涉及到异步的处理)、使用.prefab文件实例化节点、将实例节点添加至父节点。

// 文件 PlayerScript.ts  

public async genCubes() {
    // 加载Prefab文件
    const cubePrefab = await engine.loader.load<engine.Prefab>('Assets/DemoGame/Cube.prefab').promise;
    ...
    // Prefab实例化为一个cube节点
    const cube = cubePrefab.instantiate();
    ...
    // cube节点添加为节点cubesEntity的子节点
    // addChild使用的是节点的Transform组件
    cubesEntity.transform.addChild(cube.transform);
    ...
    }
    ...
  }

        踩坑:在编辑器里直接实例化Prefab的过程似乎会有很多bug,一不小心就会卡死,退出后前面几步操作也都消失了得重做,所以小心操作。

        保存场景

        将Prefab设置为入口资源

        首先,了解微信小游戏的构建与产物。微信小游戏的构建产物一般分为主包,代码分包和资源包。主包是进入小游戏后直接下载的游戏资源,包括主要代码、入口场景(也就是被指定的整个游戏启动的首场景,一般是登录或者loading页面)和它静态依赖的所有美术资源;代码分包是那些不立刻需要、可在游戏运行时异步下载使用的代码;资源包则是非代码的游戏资源,它包含了大量的资源碎包,可以在构建之后存放在CDN上,并在运行时动态下载。

        而那些会在游戏运行过程中用代码动态加载的资源,由于构建工具无法分析它们,所以需要手动地把它们定义为入口资源。入口资源可能放在主包,但大部分都会放在资源包中,工具会为它们生成映射表register_XXX.json,来存储路径和CDN包的映射,用于后续加载资源。

        本步骤中,因为Cube的Prefab文件会在代码中动态加载,所以需要把它设置为入口资源。

        3、新手引导(1)完成

        新手引导的第一步,“创建并编辑3D场景”,到这里就完成了。在“2d/3d演示”按钮中选择这个DemoGame3d.scene并播放,可以在Game栏中查看场景运行起来后的游戏状态。

        ​​​​​

        目前这个Game中运行的场景还不能点击或者操纵Player移动,因为这个示例小游戏的触摸事件是由一个2D Panel接收、处理,并进一步触发PlayerScript的TOUCH_MOVE事件监听器的。下一篇再介绍这个2D场景的创建与编辑。

四、总结

        因为基本是0基础的学习,所以本篇对游戏框架包括节点、组件、渲染,和脚本开发等基础知识进行了大量介绍,不过这样梳理了一下之后,目前对微信小游戏已经有了比较朦胧的理解了,IDE使用也熟练了很多。希望后续学习顺利!

;