一.Spx介绍

介绍Spx前,先要说明一下Go+和Spx间的关系。第一点,Go+的「三位一体」中有一位是STEM教育,Spx是Go+为STEM教育做的2D游戏引擎,对标的是CodeMonkey、Scratch类的引擎工具。第二点,Go+有个很核心的能力——ClassFile,相当于一种行业描述的语言。我们常常说Go+要去通过跨行业沟通,建立跨行业沟通的通用语言,而STEM教育正好是一个行业,那么Go+在这个行业中便可借助ClassFile进行沟通。第三点是跨平台。Go+天生具备跨平台的属性,Spx在Go+中可以实现跨平台的能力,比如发布在web端、手机端、微信小程序等等。这样用户做出的游戏便不再是依赖于某一个引擎来执行,而是相当于做出来一个作品。最终的一句话是:Spx是Go+在STEM教育领域的一个行业描述语言,是一个很经典的ClassFile的应用。我们常说Go+最核心的能力是ClassFile,那我们今天就来讲讲Spx是如何与Go+结合在一起,实现STEM教育的DSL。首先第一点,最重要的就是如何使用,如何跑起来。

第一步是到GitHub中把文件down下来安装。例子中有一个天气,gop run一下便会出现一个「你来自哪里?」这个步骤相当于拿到一个新东西后如何跑出一个「Hello World」。当我们看到这个例子后,会思考如何用Spx去做一个「Hello World」,这也是学习其他语言的第一步。那么在Spx中应该如何写?上图便是一个基础的工程。gop run一下便会出现下图,一个猴子说「Hello World」。那这个工程中具体有什么?.gmx是工程文件入口,代表gop run的时候从哪里开始读。工程文件其实很简单,首先看下工程文件中有哪些对象,比如「Monkey Monkey」,run一下后便会出现一个资源包。.spx是精灵对象文件,好比Scratch中经常会对某个精灵进行编程,比如下方指令中包含的onStart,就代表猴子开始启动时说一句「Hello World」;.res是资源文件,也就是工程的布局。这个「Hello World」的例子已经发布在GitHub中(https://github.com/sunqirui1987/HelloWorldForSpx),大家可以下载跑一下。我认为helloWorld是认识一个语言和框架最经典的方式。

二.Spx运行机制

刚才我们讲到的运行「Hello World」,其实就是Go+Spx的一段极简代码。一个工程文件就是一个run,一个精灵文件就是开始时说一句「Hello World」。当我们知道有一个「Hello World」时,应该如何将它跑起来?或者说它在Spx中的运行机制是什么?在分析这个问题前,首先要讲一下Spx是如何运行的,为什么一个.spx文件就能跑起来。这是ClassFile中最重要的一个事情——我会把.spx和.gmx注册在Go+中,这样Go+在run的时候便能将这个文件跑起来了。此前老许分享过,程序跑起来后Go+会生成Go的代码,我们来看下是如何生成的。首先大家可以看到,第19行Spx有一个游戏启动的入口函数,第16行对应的是前面我们讲的run,运行后便会将资源标记信息传进去,进行渲染。当猴子对象产生Start方法时,便会说一句「Hello World」。那这种代码是如何真正跑起来的呢?在深入分析前,我们先要了解前面提到的资源文件的构造。以下图这个场景为例:

总的来看是一个地图,地图中有一只猴子。我们首先要定义这个场景中包含哪些对象,对象的参数以及对象所在的位置。场景就相当于对这个世界的描述,在世界中有一只猴子,那么这只猴子到底长什么样子,就要看精灵对象文件的配置。如下图所示,该文件中包含图片的属性、是否是序列帧、序列帧朝向、大小等等。我们相当于通过配置生成了一个世界。当有这个配置文件后,应该如何跑起来?整个精简化的运行时是比较清晰的:首先是设置一个(640,480)的窗口,给窗口起名为「Hello World」然后便可以运行起来。在第五行不断的进行update,不断的更新坐标以及一些动画信息。这种精简化的运行时对应到最底层的openGL大概会是什么绘制流程?如果大家做过图形渲染或者播放器的工程,对上图中的代码会比较熟悉。首先初始化一个window,通过glfw框架生成(640,480)的「Hello World」,生成一个渲染窗口。然后画一个openGL的实例,在这个过程中编译顶点信息和渲染信息,完成图形的绘制。这是一个比较经典的openGL的绘制流程,在不同的平台可能会有一些差异。openGL的渲染是单线程的方式,会通过协程的方式不断切换时间片来运行。我们来看两个Demo:Demo地址:https://github.com/goplus/spx/tree/main/tutorial/03-Clone上图中左侧是一只牛,通过点击进行小牛的复制。

Demo地址:

https://github.com/goplus/spx/tree/main/tutorial/09-AircraftWar

上图是一个小孩子借助Spx实现的飞机大战游戏,是工程化比较完整的一个Demo。

三.动画机制概述

动画机制是Spx中的一个很重要模块。因为我们做的是一个STEM的游戏引擎,游戏中最重要的便是动画,不能动起来的动画就像图片一样,带给用户的感官会比较差。因此在讲Spx的动画前,需要先讲一下动画的机制。在我看来,动画类型无非两种:

  • 帧动画

  • 属性动画

其中帧动画也就是本体的动画,无论是哭、笑或者其他移动,都是本体的变化,展现的是从某一帧到某一帧的变化,就像看GIF的感觉。属性动画主要就是平移、旋转、矩阵。比如上图中右侧的例子,正方形先进行向右侧的平移后,进行向上方的旋转。这就是一个属性动画的过程。当存在这两种属性间的变化后,就可以认为这是一个时序动画。所以可以给动画下一个定义:在关键帧与关键帧间创建的时序动画。这个时序可能会穿插一些曲线、补间等等。动画的第二个重点,是它是如何动起来的。我们还是来举个例子。30 FPS和60 FPS的动画给我们的感受区别,就是60 FPS的会更流畅一些,因为它快。所以动画的第一个概念,就是播放速度,也就是30 FPS、60 FPS、120 FPS的区别。第二个概念是时长。如下图所示,当我们1秒钟有9 FPS时,帧数会比1秒钟只有6 FPS的多。因此时长代表我们的动画要播放多久,FPS代表我们要播放多快。第三个概念是From To,即我们要从哪里到哪里播放。From To的意思其实就是关键帧,从哪个关键帧播放到哪个关键帧。比如要从0°旋转至90°,那就是From 0 To 90。我们再增加其他设定,比如要用30 FPS的方式来制作这个动画,持续2秒钟,这就是一个动画清楚的描述。第四个概念是动画补间Tween。从一个关键帧到另一个关键帧有很多不同的方式,比如先快后慢、先慢后快,或者不断加速、不断减速,这里不同的方式就是动画补间的概念。通过这些概念的叠加,我们就构建出了动画的概念,实现了动画插值。动画插值的意思是当我们给定起始点和终点后,中间的过程可以插值出来,中间的过程就叫做动画插值,反映的是关键帧与关键帧间的补间动画。第五个概念是动画事件。动画在过程还有一个事件过程,比如还是从0°到90°,那在这个变换过程中我们要如何实现?是通过旋转还是平移?动画时间反映的便是在过程中做了什么。上面的内容基本就是动画的基本概念,这五个概念组成了整个动画的体系。

四.Spx动画机制

下面我们来讲Spx如何实现动画。首先我们来给Spx的动画类型做下类别的定义:

  • Frame动画

  • 移动动画

  • 旋转动画

因为我们是2D游戏,所以需要尽可能的让小朋友更容易理解,因此我们直接在配置上便将动画的类型做了拆分。第一类Frame动画,也就是帧动画,物体的本体动画;移动动画和旋转动画我们统称为属性动画。下面我们分别来做详细的介绍。

1.帧动画

 上图是帧动画的配置文件。duration代表持续的时间,From TO代表从哪儿到哪儿。但大家可以发现一个问题,为什么没有FPS、没有动画补间?这是因为FPS在加载过程中已经被计算出来了,根据时间与路径便可以计算出包含多少帧。那么具体怎么用呢?上图中的代码指令是点击播放帧动画,通过一个动画函数直接调用动画,在0.8秒后调用第二个动画。整个动画过程在使用过程中很简单,只要配置下行为就可以交给Spx引擎去执行。小朋友在编程的过程中,只要播放相应的帧动画,就可以实现人物的哭或者笑。

2.移动动画

我们此前提到过,帧动画是本体的动画,属性动画往往也会和帧动画有关联。比如实现一个人物的行走,人物除了会出现整体的位移,在位移过程中脚也需要有配合的移动,因此便会即涉及到帧动画也涉及到属性动画。所以我们单独定义了一个移动动画的配置选项,比如“step”。定义之后的第一个概念是行走单位,也就是走一步走多远。现实生活中的度量标准不一样,有像素、厘米、米等等,在Spx中的基础度量标准是逻辑单元。在包含一个绘制逻辑的单元,我们可以设置每一步具体是多少个单元。另外还包含每走一步需要多久,上图代码设定的是0.3秒。onPlay是动画播放过程中的事件,从第0张图走到第8张图,从而模拟出行走的效果。借助这一配置,用户在使用中可以无感的来进行配置开发。只要配置行走动画、配置参数,过程便会变为一个动画。

3.旋转动画

在移动动画中我们举了一个行走的例子,配置包含一步走多远、走多久。而旋转动画中只有如下图所示一个可衡量的配置,也就是时长。这里的时长指的是旋转一周所需花费的时间。为什么这里只有这么一个简单的配置?因为旋转动画的起始点和终点,和精灵当前的位置有关。移动动画中是以步长为单位来移动,而旋转动画中起始点和重点是在配置旋转代码中就需要实现的。如上图所示从0°旋转至40°,便需要配置From 0 To 40,20 FPS,根据这些参数来计算时长与播放的帧数。具体的使用也很简单。配置完旋转动画后,只需要调用turn 45,设定旋转方向即可。底层虽然是异步的方式,但对用户编程来说是同步。

4.运行原理

我们结合前面三个动画的概述,来看下底层真正的实现。首先第一步是Duration*ani.Fps。比如有一个1秒钟的行走动画,每秒钟要行走25帧,那么FPS便是25。其次是给动画起名字,设置动画类型(这里的动画类型指的是插值对象),增加补帧类型(默认值为线性动画)。接下来插入两个关键帧,来计算总共的帧数。设置From To、设置是否循环播放。经过这几步,我们便创建了一个动画实体。动画实体创建后,我们会设置两个事件,播放事件和停止事件。播放过程中会告知播放到了哪一帧、插值是多少,从而可以设置旋转、平移等动画,在回调中实现动画效果。最后便是停止。我们可以把动画的概念类比为一个视频,有播放有停止,有时长、速度、变速等等。下面我们来看当创建动画对象后,在Spx的渲染机制中应该如何调用。我们会基于时间片来轮询动画时间轴机器。比如每60 FPS来调动动画,内部基于时间片来进行计算是否该渲染,如何进行分配。

五.Spx动画DEMO讲解

DEMO讲解部分请点击文末阅读原文,查看相关视频回放。