我最近逐渐认识到,在人气不高的时候把框架的功能过于细化只能起反作用,因为细化也意味着复杂。何况当初设计这个框架的本意就是为了方便 C++ 新手学习,所以新版本 beta7 进一步简化 Easy2D ,删除了大段代码,同时也带来了一些新特性。
这次更新几乎将整个 E2D 框架重做,不要担心这会花费很大的学习成本,它只会让你更容易上手,而且会减少你对一些模糊不清的函数的疑问。
此次更新修改了 Game
类的几个方法名,所以 beta7 不兼容旧的项目。
现在,一个最简单的 Easy2D 程序如下:
1 |
|
重做后的 Game::init
方法必须在主函数的第一行调用,其他任何操作都应该在初始化成功后执行。这个方法不再设置窗口标题和大小,但你可以通过 Window::setTitle
方法设置窗口标题,Window::setSize
方法设置窗口大小。
而且你也不必再判断 Game::init
函数的返回值,因为初始化失败时,它将抛出异常,终止程序。你可以用下面的方法让异常处理的方式更加友好。
1 |
|
Game::run
函数更名为 Game::start
,通常它应在程序的最后一行调用,因为这个函数返回时表示游戏已经结束。这个函数会在游戏结束时自动回收游戏资源。
Game::uninit
函数更名为 Game::destroy
,现在你不必手动执行这个函数,因为 Game::start
会自动调用它。
如果你不希望 start
函数自动回收资源,可以使用 Game::start(false)
启动游戏,然后在你需要的地方调用 destroy
方法。
beta7 版本之前,e2d::Object 对象是直接用 new 运算符创建的,如下面的代码。
1 | // 创建一个精灵对象 |
我在 Object 的构造函数中做了些 “手脚”,使它的内存可以被自动回收,所以你会发现示例代码中从来没有 delete 语句,但却不会发生内存泄漏。
这种做法其实很糟糕,因为你不能自由控制 new 创建的对象,而且通过其他方式创建的对象在回收时会崩溃。为了解决这个问题,beta7 版本增加了全局的 Create
函数。
这是一种工厂模式,你所学过的工厂模式也许是这样的:
1 | // 简单工厂模式 |
通过 Factory
工厂来创建不同的对象,这个对象就是经过了处理的对象。还有另外一种工厂方法模式,如下
1 | // 工厂方法模式 |
这三种方法各有优势,但它们都有一个共同缺陷:扩展性差。如果你自定义了一个 Bird
类,那么你就需要为它添加一个 Bird::create
方法,或者 Factory::createBird
方法,才能创建它。
beta7 版本新增了 Create
函数,它是一个模版函数,并拥有强大的扩展性。它的使用方法如下
1 | auto sprite = gcnew Sprite("image.png"); |
使用 Create 函数创建的对象将被 GC(垃圾回收装置)
跟踪,当这个对象不再被需要时,GC 会自动 delete 它。
GC
判断一个对象是否需要被释放的方法如下:
retain
和 release
方法可以使引用计数加一或减一Game::destory
方法会强制让 GC 释放所有对象比如我们使用 Create
函数创建了一个 Scene
,下面的代码展示了引用计数的变化
1 | // 创建一个场景,此时引用计数为 0 |
另外,你仍然可以使用 new 去创建对象,但是需要你自己去 delete。
beta7 版本移除了 MusicManager
类,并新增了 Player
播放器类,它更加简单易用。
现在,如果你想播放一段音乐,只需要:
1 | Player::play("music.wav"); |
如果你想停止它,只需要:
1 | Player::stop("music.wav"); |
另外需要注意的是,在游戏开始之前,最好把游戏中所有的音乐预加载,防止第一次播放音乐时卡顿。
1 | // 预加载音乐 |
Player
的所有操作都被最大限度的简化,当然这也意味着不够灵活,和受更大的限制。beta7 版本保留了 Music
类,你可以手动创建一个音乐对象。
1 | // 创建一个音乐对象 |
有了这个对象,你可以设置它的音量,判断它的播放状态,以及设置它播放结束时执行特定的函数。
1 | // 设置音乐对象的音量 |
虽然 Music
对象是通过 Create 函数创建,但它在播放时不会被引擎自动回收,直到它播放结束。
1 | // 创建一个音乐对象并播放 |
现在,Easy2D 支持了从资源加载图片或音乐的功能,所以你可以把游戏中所有的图片和音乐都打包到 exe 中。
方法如下:
例如,当你导入第一张 .png 图片时,资源名称往往是 IDB_PNG1 ,资源类型是 “PNG” ,然后你就可以使用下面的代码创建一个精灵
1 | auto s = new Sprite(IDB_PNG1, "PNG"); |
PS. Visual Studio 的添加资源功能一直存在问题,偶尔会导致 VS 崩溃,而且也经常无法正常识别 resource.h 中的宏,但是它仍然可以正常编译。
Timer
类中的方法全都改为了静态方法,这说明你不再需要 new 一个定时器出来再去操作它,而是直接开启一个定时器,在启动时设置好它的参数即可。
1 | // 添加一个定时器,该定时器执行 func 函数 |
添加定时器时,你可以给它起一个名称
1 | // 添加定时器,该定时器执行 func 函数 |
这样你就可以把名字叫 “定时器” 的定时器停止或开启
1 | // 停止名称叫 "定时器" 的定时器 |
添加定时器时,你还可以设置定时器的执行时间间隔、执行次数、以及创建后是否暂停
1 | // 添加定时器,该定时器执行 func 函数,执行间隔为 1.5 秒, |
当你不需要这个定时器时,你可以把它移除,移除的定时器无法再启动
1 | // 移除名称叫 "定时器" 的定时器 |
定时器也可以在等待足够时间后执行一个函数
1 | // 启动定时器,该定时器在 2 秒后执行一次 func 函数 |
beta7 中增加了 Collider::Type
枚举,可以使用 Node::setCollider
方法直接设置碰撞体的形状,这意味着你不需要 new 一个 Collider 再把它赋给 Node 了。
Collider::Type
枚举有三个类型,分别是
1 | Collider::Type::RECT // 矩形 |
你应该这样去使用它
1 | // 设置碰撞体类型为圆形 |
碰撞体只是描述了物体的形状,你还需要告诉 Easy2D 哪些物体间可以发生碰撞。
假设节点 node1 和 node2 的名称分别为 “节点一” 和 “节点二”
1 | node1->setName("节点一"); |
如果想检测到 node1 与 node2 的碰撞,需要调用 Collision::addName
方法把它们的名字加进去。
1 | // 添加可碰撞物体名称 |
这样设置过后,node1 和 node2 之间的碰撞就会被检测到。
为了检测碰撞的发生,你需要创建一个监听器去监听碰撞事件,方法如下
1 | // 创建一个伪函数类 |
其中 CollisionListen
是一个函数,它可以定义如下
1 | void CollisionListen() |
在 VS2013 及以上版本中,可以使用初始化列表一次性添加多个值。
例如,下面的代码使用初始化列表将多个节点一次性添加到场景中
1 | // 使用初始化列表一次添加多个值 |
再如,一次性添加多个可碰撞物体名称
1 | // 使用初始化列表一次添加多个值 |
再如,一次性添加多个 Image 到 Animation 中
1 | // 使用初始化列表一次添加多个值 |
Text
现在具备了简单的文字排版能力,包括文字描边、行间距、对齐方式、自动换行、下划线、删除线等。String::format
支持创建格式化的字符串,如下所示1 | // 字符串 str 的值为 "level 12" |
调用 Renderer::showFps
函数可以显示画面的 FPS 值
NodeProperty
结构体可获取节点的所有属性,如下所示
1 | // 获取节点所有属性 |
更多新内容请到入门教程中查看。
]]>File
类更名为 Path
类,因为这个类只提供一些系统路径、数据保存路径等。
时隔两个月,我们又将 Listener(监听器)这一概念加了进来,它配合 Input
类可以达到监听用户按键和鼠标消息的功能。
监听器的执行方式和定时器类似,在监听到用户输入时执行回调函数。你需要先创建一个回调函数。
1 | void GetInput() |
然后创建一个监听器,并启动它。
1 | // 创建监听器 |
监听器启动后,当检测到用户按键时,它会自动执行 GetInput 函数,判断用户按下的是否是右键,如果是,移动精灵。
如果你觉得创建监听器的方法太繁琐,可以直接使用 Input::add
函数添加一个监听器。
1 | // 添加监听器 |
Input::add
函数把 GetInput 函数添加到了监听队列中,检测到用户输入时自动执行 GetInput 函数。
为了让 Shape(形状)更好使用,现在每个节点都默认包含了一个矩形形状。
让我们看看 FlappyBird 游戏中展现的效果。调用场景的 Scene::setShapeVisiable
函数,让形状可见,如下图所示
可以看到,即使没有创建 Shape,所有的图形边框也都被显示了出来。这在 Debug 时很有帮助,同时也能更好的帮助我们观察游戏中物体的运动。
之前 Shape 类是为了检测物体间碰撞存在的,现在所有节点都有 Shape,为了防止碰撞的频繁发生,也为了游戏性能,碰撞检测默认是关闭的,可以通过 ShapeManager::setCollisionEnable
函数重新启用。开启后,当 ShapeManager 检测到碰撞发生时,会自动调用发生碰撞节点和其所在场景的 onCollide
函数。
如果你不想让节点包含默认形状,可以通过 Node::setDefaultShapeEnable
函数开启或关闭。关闭后,新建的节点将不包含默认形状。
Data
类用来保存用户的数据,它将数据和键值对应保存,当用户需要取出数据时,可以根据键值取出相应的数据。键值和数据的关系就像钥匙和抽屉,一把钥匙对应一个抽屉,一个键值也对应一个数据。
现在,Data 增加了 字段
这个新属性,就好比柜子,一个柜子有很多个抽屉,你可以把数据放置到不同的柜子里。这样,用同一把钥匙,可以在不同的柜子打开不同的抽屉。
Data 保存的文件名也可以通过 Data::setDataFileName
设置(注意:不应有特殊字符和后缀名),就好比房间的概念。
简单来说,一个房间里面有很多个柜子,柜子里有很多个抽屉,一个抽屉里面放着一个物品。
对应到 Data 上,一个数据文件有很多个字段,字段里有很多个键值,一个键值对应一个数据。
首先要说明的是函数命名上发生了一些变化
例如,进入场景函数 SceneManager::enterScene
改为了 SceneManager::enter
,改名的原因是太啰嗦,场景管理器的 enter 函数必然是进入场景,不用再加一个 Scene 后缀。
更名的函数有
SceneManager::enterScene
=> SceneManager::enter
SceneManager::backScene
=> SceneManager::back
MusicManager::add
=> MusicManager::preload
Image::loadFrom
=> Image::open
Text::setWordWrapping
=> Text::setWordWrappingEnable
Timer::setRepeatTimes
=> Timer::setUpdateTimes
曾经的 MusicManager
需要用 get
函数获取一个 Music
对象的指针,这个指针可能为空指针(如果找不到那个音乐文件),这是个新手可能忽略的安全隐患。
新的 MusicManager
可以直接使用 play
、pause
、resume
、stop
这四个函数控制音乐的播放、暂停、继续、停止,而不需要考虑空指针的问题。如果你播放了一个不存在的音乐文件,游戏不会崩溃也不会暂停,而是在控制台输出一条警告信息。
另外,Music
类现在可以手动创建(上一个版本不能创建 Music 对象),不过不建议这样使用。
一直以来我都忘了 1.x 中一个非常好用的函数,现在我把它重新添加了进来
Node::isIntersectWith
函数用来判断两个节点是否相交,也就是碰撞,这个函数返回 true 说明发生了碰撞。
1 | // 假设存在节点 node1 和 node2 |
对于现在的 String 来说,Unicode 和 ANSI 没有任何区别,这是 String
类的新功能~
所以下面的写法都是对的
1 | // ANSI 版 |
如果你不考虑性能问题的话,直接使用 ANSI 字符串会非常方便(其实不会影响多少性能)
另外新的 String 可以方便地转换成整数、浮点数,例如下面的代码
1 | String str = "123"; |
String::toInt
、String::toDouble
、String::toBool
函数用于将字符串转化成不同的类型。
其他类型的值也可以使用String::toString
非常方便地转化成字符串
1 | int x = 123; |
使用 <<
运算符可以将任意类型的值存入字符串,如下所示
1 | String str; |
上面的代码中,在将内容存入 str 的同时,自动将整型2017的值转化为了字符串。
新版本的定时器可谓是重焕新生 (>▽<),再也不用使用什么烦人的 std::bind 函数了!例如我们想在游戏启动一段时间后在控制台输出一个 Hello,可以用下面的代码做到
1 |
|
上面的代码运行后,程序运行后的第 2 秒会输出一个 Hello。
上面的代码中,Test函数只管输出,让 TimerManager 控制何时执行 Test 函数。TimerManager::start
函数需要两个参数,第一个表示等待时长,第二个表示执行的函数,比如 TimerManager::start(2, Test) 的意思就是 2 秒后自动执行一次 Test 函数。
Timer
定时器类可以更灵活地使用定时功能,首先你得创建一个定时器
1 | // 创建一个定时器,它与 Test 函数绑定了 |
然后该怎么做,绑定在节点上吗?还是添加到 TimerManager 中?
这都是曾经的用法了,现在的 Timer 只需要 start(启动)
1 | // 启动定时器 |
然后这个定时器将会疯狂输出 “Hello”。
如果 Test 函数是类的成员函数,那么定时器需要函数名和对象指针。
1 | class Test |
其中 CreateCallback(t, Test::test) 的意思是,创建一个定时器的执行函数,它让 t 对象执行 test 函数。
有关定时器的更多用法请看 入门教程-定时器
]]>新增
EButtonToggle开关按钮类;新增
EMenu菜单类;新增
监听器增加吞噬消息功能;新增
ENode::setDefaultPivot函数,用于设置节点的默认中心点;更新
EButton的监听方法;更新
更新了判断点是否在节点内的算法;更新
中心点默认位置改为(0,0);修复
EFont某些情况下崩溃的bug;修复
EButton没有获取启用和禁用状态的函数的bug;修复
EButton不显示禁用状态的bug;修复
所有回调函数不检测空引用就执行的bug;修复
EFileUtils中参数类型与EString不兼容的问题;修复
子节点与父节点相对位置错误的bug;修复
修复了其他的一些小bug。目前存在的已知 BUG 有:
v2.0.0 测试版引入了节点、支点、几何形状等新概念,新增了碰撞检测功能。想了解更多相关信息请查看 [Easy2D 入门教程]
]]>新增
新增Timer::addTimer重载函数,默认时间间隔为20毫秒新增
新增Sprite::runAction函数,实际效果与addAction相同修复
批量节点的移动问题修复
Sprite进行碰撞判断时的bug新增
Image::load 函数实现图片的预加载新增
Math类和random函数,可以获取任意范围内的随机数新增
对监听器、定时器和动画加入了等待和唤醒机制新增
BatchSprite::addAction函数,使所有精灵同时执行同一个动画新增
Sprite暂停、继续、停止动画新增
Action::getTarget函数,获取执行该动作的目标更新
重整了场景切换时的流程,应重写Scene::init函数对场景进行初始化更新
使用图片缓存机制防止重复加载同一图片更新
获取鼠标消息改为MouseMsg::getMsg更新
Object不再自动释放,除非调用autoRelease函数将其加入释放管理池中更新
取消安全宏,改用inline函数代替更新
tstring宏改为类型定义TString更新
动作初始化的时机改为第一次运行时修复
App::free 和 App::destory 函数造成内存泄漏的bug函数造成内存泄漏的bug修复
BatchNode清空所有节点时,未销毁子节点的bug修复
创建窗口时重置AppName的bug修复
重置动画时的一些bug修复
Image裁剪图片范围越界导致图片不显示的bug修复
ActionManager在动作执行时添加动作崩溃的bug修复
Sprite未设置图片时崩溃的bug修复
Action在拷贝和逆向拷贝时导致错误的BUG此次版本更新的内容有:
新增
批精灵BatchSprite
,可以同时管理多个精灵的属性新增
矩形节点RectNode
,可以方便的判断矩形碰撞和其他矩形操作新增
文本类、图片类、精灵类、按钮类现在都继承自RectNode
修复
图片默认透明度为 0 的BUG修复
执行缩放动画时的BUG修复
帧动画和其他动画一起使用时动画混乱的BUG修复
按钮设置了不同大小的图片时,范围判断不准确的BUG修复
上个版本删除定时器会崩溃的BUG精灵(Sprite)
对象运行它!此次版本更新的内容有:
新增
精灵类Sprite
,可以执行动作Action
、判断两精灵碰撞等新增
动作类Action
,实现了 14 种动作,所有动作都可以暂停、继续和停止新增
动画管理器ActionManager
,用于管理所有当前存在的动作新增
图片类新增透明度设置函数setOpacity
更新
App 类的大部分函数都改为了静态方法,但仍保留get
方法以保证兼容性修复
图片类按比例拉伸函数名改为setScale
修复
其他细节上的优化1.0.4
版本新增了将数据保存到文件的功能,可以快速存取简单的数据类型。要使用这个功能,首先应该设置你的AppName
。AppName 是你游戏的唯一标识,它的默认值和你的窗口名称相同,用于区别其他人做的同类游戏。如果你做了“推箱子”这个游戏而没有设置 AppName,当你运行其他人做的推箱子时,你保存的数据文件就有可能被覆盖。
应在创建 App 对象,并调用 createWindow 函数后设置 AppName,例如:
1 | App app; |
注意:不要在AppName中设置特殊字符,也尽量不使用中文,防止字符集问题导致错误。
FileUtils
支持保存的数据类型有int
、double
和字符串,对应的函数分别如下
1 | // 保存 |
第一个参数key
用于标识保存的这个变量,例如游戏现在进行到第五关,你想保存这个值,就可以使用
1 | int level = 5; |
保存之后,可以根据这个数据的key
把它取出
1 | int level = FileUtils::getInt(_T("level"), 1); // 取出之前保存的关卡值 |
若使用了一个不存在的key
,则会用第二个参数的值作为默认值
1 | int level = FileUtils::getInt(_T("test"), 1); // 没有 test 对应的值,则 level 为 1 |
注意: AppName、数据的key、保存的字符串中含有中文时,在Unicode工程下会出现错误
FileUtils
将数据文件保存在系统的 AppData\Local 文件夹里,数据并不是加密的,你可以在 AppData\Local\你的AppName\DefaultData.ini 中找到保存的数据。
程序默认不使用图标,如果要自定义程序图标,应使用 Win32 程序添加图标的方法向工程中添加图标资源。
以 VS2017 为例,先选中当前工程
然后点击菜单栏中的项目
- 添加资源
选中Icon
后点击导入
,将你的ico
格式图标导入工程,再次运行程序即可。
新增
Scene 类新增onEnter
和onExit
函数,重写这两个函数,它们将在场景载入和退出时自动调用新增
App 类enterScene
函数增加第二个参数,用于设置场景切换是否可逆。参数为 false 时,不可调用backScene
函数返回新增
App 类新增clearScene
函数,清空所有已保存的场景新增
Layer 图层类可以设置是否阻塞消息向下传递更新
删除鼠标监听函数名由delListener
改为deleteListener
修复
防止了持续按键响应修复
现在 Shape 可以选择ROUND
、SOLID
、FILL
三种填充类型修复
重合按钮显示不正常的 BUG修复
字体类中存在已久的 BUG,曾导致游戏一开始就崩溃此次版本更新的内容有:
增加
鼠标消息监听(MouseMsg::addListener)增加
按钮在鼠标移入、移出、按下、抬起时的回调函数更新
设置按钮回调函数名由setOnMouseClicked
改为setClickedCallback
更新
为 TextButton 和 ImageButton 增加了更简便的构造函数修复
Image 加载空图片时崩溃的 BUG修复
FreePool 允许重复添加对象导致崩溃的 BUG更新
Application 类更名为 App更新
MouseMsg 鼠标消息检测的相关函数名改动增加
现在低版本编译器下会报错增加
现在创建窗口时自动在屏幕居中修复
加载游戏阶段显示黑窗口的 BUG修复
修改窗口大小时的一个 BUG修复
FreePool 有时造成内存泄漏的 BUG