第一节 游戏引擎
起源
在游戏发展的初期,游戏开发者关心的只是如何尽量多地开发出新的游戏并把它们推销给玩家。尽管那时的游戏大多简单粗糙,但每款游戏的平均开发周期也要达到8到10个月以上,这一方面是由于技术的原因,另一方面则是因为几乎每款游戏都要从头编写代码,造成了大量的重复劳动。
渐渐地,一些有经验的开发者摸索出了一条偷懒的方法,他们借用上一款类似题材的游戏中的部分代码作为新游戏的基本框架,以节省开发时间和开发费用。
根据马老先生的生产力学说:
单位产品的成本因生产力水平的提高而降低,自动化程度较高的手工业者最终将把那些生产力低下的手工业者淘汰出局
引擎的概念就是在这种机器化作业的背景下诞生的。
游戏引擎
游戏引擎是指一些已编写好的,并可继续向上编辑的游戏系统或者一些交互式实时图像应用程序的核心组件。这些系统为游戏设计者提供各种编写游戏所需的各种工具,其目的在于让游戏设计者能容易和快速地做出游戏程式而不用由零开始。
简单的说,不论你要做的是哪种类型的游戏,它通常都会涉及到音乐播放、文件存储、动画特效、关卡跳转、骨骼动画、物理碰撞等功能,而这些操作就是由游戏引擎来提供的,我们是在游戏引擎提供的功能基础之上,进行二次开发。
不论在是2D游戏中还是3D游戏中,引擎的制作往往会占用非常多的时间,《马科斯•佩恩》的MAX-FX引擎从最初的雏形FinalReality到最终的成品共花了四年多时间,LithTech引擎的开发共花了整整五年时间,耗资700万美元,Monolith公司(LithTech引擎的开发者)的老板詹森•霍尔甚至不无懊悔地说:“如果当初意识到制作自己的引擎要付出这么大的代价的话,我们根本就不可能去做这种傻事。没有人会预料得到五年后的市场究竟是怎样的。”
正是出于节约成本、缩短周期和降低风险这三方面的考虑,越来越多的开发者倾向于使用第三方的现成引擎制作自己的游戏,一个庞大的引擎授权市场已经形成。
组成
市面上的每种引擎都有自己的特色,其所提供的功能也是不尽相同的,现在通常每一款游戏都会有自己的引擎,但真正能获得他人认可并成为标准的引擎并不多。以桌面游戏引擎来说,其主要包含以下系统:渲染引擎(即“渲染器”,含二维图像引擎和三维图像引擎)、物理引擎、碰撞检测系统、音效、脚本引擎、电脑动画、人工智能、网络引擎以及场景管理。
Cocos2d
Cocos2d是开源的软件框架,它可用于构建游戏、应用程序和其他基于GUI的跨平台交互程序。
发展历程:
2008年2月,在阿根廷的Los Cocos镇,Python版的Cocos2D引擎诞生。
2008年6月,Objective-C版Cocos2D for iphone诞生。由于引擎过于火爆,后来又出现了各种衍生版本,如:Cocos2d-x、Cocos2d-js、Cocos2d-android等。
在这些版本中,Cocos2d-x意义重大,Cocos2d-x 是一款国产的开源的手机游戏开发框架,基于MIT许可证发布,它可以轻易做出跨平台的游戏。从2016年开始,推出了CocosCreator工具,目前该工具同步支持2D、3D游戏创作,其可视化、脚本化等特点,让更多新人轻松上手。
本节参考阅读:
第二节 基础概念
为了全面掌握游戏开发,我们首先需要了解该引擎的几个基本概念。实际上这些基本概念是所有游戏开发所必须的,并非Cocos2d-x
专有。任何游戏都是通过这些概念所针对的对象组建起来的,游戏的复杂程度决定了这些对象实现的复杂程度。
游戏组成
导演(Diretor)
一款游戏好比一部电影,只是游戏具有更强的交互性,不过它们的基本原理是一致的。所以在Cocos2dx
中把统筹游戏大局的类抽象为导演(Director
),Director
是整个Cocos2dx
引擎的核心,是整个游戏的导航仪,游戏中的一些常用操作就是由Director
来控制的。
导演(Director
)常用的功能:
- OpenGL ES的初始化、游戏暂停继续的控制
- 场景的转换、世界坐标和GL坐标之间的切换
- 游戏数据的保存调用,屏幕尺寸的获取
- 控制FPS的显示隐藏,窗口大小,游戏的进入,退出,获取当前正在运行的场景等等。
在整个游戏里面,一般只有一个导演,导演来指定游戏规则让游戏内的场景、布景和人物等可以有序的运行。
我们假设一个只有两关的游戏,通常情况下,我们会返样设计整个游戏的流程(workflow):
显示完开场动画之后,接下来玩家的选择有多种,无论开始还是读取进度都会进入到游戏的预设关卡。游戏过程中第一关胜利则进入第二关,第二关胜利则进入结尾胜利画面(播放视频或者在背景图上显示文字),确认以后进入排名画面看看本次得了多少分。有一个统一的画面处理失败提示,确定后跳转到主画面重新开始。
从开场动画到菜单显示,再到各个关卡的切换,这一切的流程都是由导演来控制的。
场景(Scene)
从上面的描述中我们可以知道,玩家玩游戏的过程就是在我们预设的画面之间进行跳转,一个画面根据玩家操作的结果(选择菜单项、杀死或被敌人消灭)跳转到不同的画面。
这些构成整个游戏的流程的画面就是我们所说的场景(Scene
)。显然不同的场景都提供不同的操作,大致可以分为以下几类场景:
- 展示类场景:播放视频或简单的在图像上输出文字,来实现游戏的开场介绍、胜利、失败提示、帮助简介。
- 选项类场景:主菜单、设置游戏参数等。
- 游戏场景:这是游戏的主要内容,除了这个场景之外的其他类场景基本上都是通用架构实现的。
那么不同的场景是如何实现不同的功能的呢?每个场景都是通过不同的层(Layer
)的叠加和组合协作来实现不同的功能的。因此,通常每个场景都是由一个或者几个层组成的。
通常,当我们需要完成一个场景时候,会创建一个Scene
的子类,并在子类中实现我们需要的功能。比如,我们可以在子类的初始化中载入游戏资源,为场景添加层,启动音乐播放等等。
层(Layer)
每个游戏场景中都可以有很多层,每一层有各自的职责任务,例如专门负责显示背景,专门负责显示道具,专门负责显示任务角色等。层是我们写游戏的重点,我们大约99%
以上的时间是在层上实现我们游戏内容。如下图所示,一个简单的主菜单画面是由3个层叠加实现的:
本图描述的是主菜单画面场景
中所包含的三个层:图像背景层、运动精灵层、菜单选项层。
细心的读者可能已经注意到,为了让不同的层可以组合产生统一的效果,这些层基本上都是透明或者半透明的(否则我们只能看到最上面的一个层了)。
层的叠加是有顺序的,如图所示:编号为1的图像背景层在最下面,2号中间,3号最上面,处于最上面的层内部的不透明的内容将覆盖下面层的内容。
这个次序同样用于编程模型中的事件响应机制。即编号3的层最先接收到系统事件(手指单击屏幕事件),然后是编号2,最后编号1。在事件的传递过程中,如果有一个层处理了该事件,则排在后面的层将不再接收到该事件。
在每一层上面可以放置不同的元素,包括文本(Label
)、链接(HTMLLabel
)、精灵(Sprite
)、地图等等,其中,精灵是重点。
通过层与层之间的组合关系,我们可以很容易的控制和显示各种各样的界面了。
也许你会有疑问:为什么不把文本、链接、精灵等直接放在场景里却放到层里?
事实上Layer
是处理玩家事件响应的Node
子类。与场景不同,层通常包含的是直接在屏幕上呈现的内容,并且可以接受用户的输入事件,包括触摸,加速度计和键盘输入等。我们需要在层中加入精灵,文本标签或者其他游戏元素,并设置游戏元素的属性,比如位置,方向和大小;设置游戏元素的动作等。通常,层中的对象功能类似,耦合较紧,与层中游戏内容相关的逻辑代码也编写在层中,在组织好层后,只需要把层按照顺序添加到场景中就可以显示出来了。
对于场景而言,通常我们添加的节点就是层。先添加的层会被置于后添加的层之下。如果需要为它们指定先后次序,可以使用不同的zOrder
值。层可以包含任何Node作为子节点,包括Sprites
,Labels
,甚至其他的Layer
对象。
精灵(Sprite)
精灵是整个游戏开发处理的主要对象,包括主角和敌人、NPC
等,甚至随机飘过的一片云或飞鸟从技术上讲,也是精灵,因为精灵在Cocos2dx
中,就是一个可以不断变化的图片,这些变化包括位置变化,旋转、放大缩小和运动等。
因此所谓游戏,本质上就是玩家操作一个或多个人工控制的精灵与一个或者若干个系统控制的敌方精灵进行互动:近身肉搏、远程射击、贴近对话等等。
Cocos2dx
中的精灵和其他游戏引擎中的精灵相似,它可以移动,旋转,缩放,执行动画,并接受其他转换。
整体结构
到此为止,我们已经大概了解了一个游戏的整体架构,不同的场景由不同的层组成,每个层又包括自己的精灵在层上运动。玩家玩游戏的过程就是在操作每个层上的精灵或者菜单选项,导致整个游戏在不同的场景中切换。
好了,有些OO编程基础的读者已经猜到导演(Director
)对象的作用了。是的,按照面向对象的设计原则和反向依赖原则:精灵不应该依赖层、层不应该依赖场景、场景不应该依赖整个流程。导演对象是整个流程的代表,他负责游戏全过程的场景切换。
导演通常只有一个,因此这个对象是单例(singleton
),Cocos2d-x
框架已经预定义了该实例,我们直接使用就可以。
导演接受层/场景的要求,按照预先设计好的流程来终止、压栈、激活当前场景,引导下一个场景。至此,我们可以勾勒出一个游戏的整体框架和Cocos2d-x
关键对象与之的对应关系:
需要特别说明的是:任何时间只有一个Scene
对象实例处于运行激活状态。该对象可以作为当前游戏内容的对象的整体包容对象。
以上就是一个游戏的主要整体对象架构。
实际上,针对每个游戏场景而言,不同场景(关卡)、每一个层(静态、动态)、每一个对象(敌人、我方、中立方)其实都很复杂。
万里长征,第一步吧。
备注:
笔者在此万分感谢新浪博主“知易”写的“知易Cocco2D-iPhone开发教程”,前人为我们铺平了道路,在此向工作在前线的前辈们致敬。
本节参考阅读:
Cocos2d-x 坐标系详解
坐标系
无论是搞2D
(dimension,维)游戏还是3D
游戏开发,最需要搞清楚的就是坐标系,这部分混乱的话就没啥奔头了。
在介绍坐标系之前,先介绍一下维度的概念。
维度,又称维数,是数学中独立参数的数目。在物理学和哲学的领域内,指独立的时空坐标的数目。
- 0维是一点,没有长度。
- 1维是线,它由n个连续点组成,线只有长度。如直线、曲线。
- 2维是一个平面,它由n个连续的线组成,平面有长度和宽度,以及面积。如圆形、矩形。
- 3维是2维加上高度,形成“体积面”。如圆柱、长方体。而常说的2D、3D开发,就是指在二维游戏、三维游戏。
游戏在运行的时候会绘制各种游戏元素(精灵、层等)到屏幕上,这些元素在屏幕上的位置称为该元素的在屏幕中的坐标。
在2D
游戏中坐标由x
和y
两个值构成,它们分别代表x
轴(水平方向)和y
轴(垂直方向)相对于原点的偏移量。 如将一个精灵放在屏幕坐标(10,10)
上,就是水平和垂直都偏移10
个位置。如图所示:
而在3D
游戏中的坐标则是由三个值构成。
坐标系是数学或物理学用语,定义:对于一个n
维系统,能够使每一个点和一组(n个)标量构成一一对应的系统。
在Cocos2d
中存在四种坐标系(屏幕坐标系
、Cocos2dx坐标系
、世界坐标系
、本地坐标系
),它们各自有自己的特点,接下来将对他们依次介绍。
屏幕坐标系和Cocos2dx坐标系
iOS
,Android
,Windows Phone
等在开发普通应用时使用的是标准屏幕坐标系,即它们视屏幕左上角为原点,x
向右为正方向,y
向下为正方向。而Cocos2dx
坐标系和OpenGL
坐标系一样,它们将屏幕左下角视为原点,x
向右为正方向,y
向上为正方向。
在开发中,我们还经常会提到两个比较抽象的概念:世界坐标系
和本地坐标系
。这两个概念可以帮助我们更好的理解节点在Cocos2dx
坐标系中的位置以及对应关系。
世界坐标系(World Coordinate)和本地坐标系(Node Local)
世界坐标系
也叫做绝对坐标系
,是游戏开发中建立的概念。这里的“世界”
指游戏世界。
Cocos2dx
中的元素是有父子关系的层级结构,我们通过Node
的setPosition()
方法(关于Node类后面会详细介绍)设定元素的位置使用的是相对与其父节点的本地坐标系而非世界坐标系。最后在绘制屏幕的时候Cocos2dx
会把这些元素的本地坐标映射成世界坐标系坐标
。
本地坐标系
也叫相对坐标系
,是和节点相关联的坐标系。每个节点都有独立的坐标系,当节点移动或改变方向时,和该节点关联的坐标系将随之移动或改变方向。
Cocos2dx最终在屏幕中绘制元素时会把这些元素的本地坐标映射成世界坐标系坐标
。
几乎所有的游戏引擎都会使用本地坐标系而非世界坐标系来指定元素的位置,这样做的好处是当计算物体运动的时候使用同一本地坐标系的元素可以作为一个子系统独立计算,最后再加上坐标系的运动即可,这是物理研究中常用的思路。例如一个在行驶的车厢内上下跳动的人,我们只需要在每帧绘制的时候计算他在车厢坐标系中的位置,然后加上车的位置就可以计算出人在世界坐标系中的位置,如果使用单一的世界坐标系,人的运动轨迹就变复杂了。
锚点
通常我们习惯于只使用一个点来决定图片的位置的。选中了某个点后,图片的左上角就会被绘制到该点上。
比如,在App开发中向屏幕上绘制图片只需要提供三个参数即可: 图片对象
、x坐标
、y坐标
,然后图片的左上角就会被绘制到x
和y
的位置上。
但是在游戏开发中可能就会有更高的要求了,除了指定position
(上面提到的x
和y
坐标)外,还需要指定一个锚点(anchor point
)。
锚点用来指出position
究竟是针对于图片的那个部位来说的。
比如,g.drawImage(img,10,20,Gravity.LEFT|Gravity.TOP);
的作用就是在(10,20)
这个地方画一个图片,而Gravity.LEFT|Gravity.TOP
则表示,图片的左上角就会被画在(10,20)
这个坐标上。
相应的,如果锚点是Gravity.CENTER_HORIZONTAL|Gravity.CENTER_VERTICAL
,则表示图片的正中点就会被画在(10,20)
这个坐标。
因此,一张图片的最终显示位置是由position
和锚点
来共同决定的。
锚点的范围是[0,1]
,当设置(0,0)
时是以左下角为锚点,当设置(1,1)
时是以右上角角为锚点。
锚点的概念存在于任何的Node
对象中。
本节参考阅读: