3.7 Cocos2D中的单例
单例的作用类似于全局类,更像一个全局变量。如果需要在任何地方都能用某些数据和方法,单例无疑是最佳的选择。比如某个游戏有多个关卡,可以使用单例来储存一些全局的游戏信息,并把信息从一个关卡传到另一个关卡。对于音频和屏幕大小来说,使用单例也是非常方便的选择。
Cocos2D中使用单例设计模式。单例是在程序的生命周期中只被实例化一次的类。为了确保这一点,我们利用类的静态方法创建和访问对象。通常情况下,可以使用以“shared”开头的方法访问Cocos2D的单例对象,而不使用Objective-C中常用的alloc/init或者静态autorelease初始化方法。
3.7.1 Cocos2D中的常用单例
以下是一些常用的Cocos2D单例类及其访问方法:
CCDirector* sharedDirector = [CCDirector sharedDirector];
CCSpriteFrameCache* sharedCache = [CCSpriteFrameCache sharedSpriteFrameCache];
CCTextureCache* sharedTexCache = [CCTextureCache sharedTextureCache];
CDAudioManager* sharedManager = [CDAudioManager sharedManager];
SimpleAudioEngine* sharedEngine = [SimpleAudioEngine sharedEngine];
注意 从Cocos2D v2.0开始,CCActionManager和CCTouchDispatcher不再作为单例,而是CCDirector的实例变量。
在代码清单3-7中,SharedManager提供了访问MyManager单一实例的静态方法。如果实例不存在,分配并初始化一个MyManager实例;如果实例已存在,则返回已存在的实例。
代码清单3-7 访问MyManager单一实例的静态方法
static MyManager *sharedManager = nil;
+ (MyManager*) sharedManager
{
if (sharedManager == nil)
{
sharedManager = [[MyManager alloc] init];
}
return sharedManager;
}
使用单例也会存在风险。由于其简单实用,并且可以在任何地方访问,开发者很多时候会在不恰当的地方使用单例。在创建单例之前,我们需要认真思考是否真的只需要该类的单个实例,或者需要在全局使用其中的数据,以及在后续版本中是否会对游戏设定做出重大调整。
注意 近年来由于单例模式滥用,许多开发者已经将它列为一种“反模式”。很多时候由于单例对象的生命周期过长,占用内存长时间不释放,这其实就形成了一种内存泄漏。在Java这种具有垃圾回收机制的语言中,这种长时间不释放内存的情况就是内存泄漏。所以,在Cocos2D v2.0版本中,许多类都改成了非单例形式,大家在设计游戏类时也多多注意,不要滥用。
3.7.2 CCDirector导演类
CCDirector(导演类)是Cocos2D游戏引擎的核心。第1章的HelloCocos2D应用中很多对象的初始化过程包含[CCDirectorsharedDirector]的调用。
CCDirector是一个单例,用于保存Cocos2D的全局配置设定,同时管理Cocos2D场景。它的主要作用如下:
访问和改变场景;
访问Cocos2D的配置细节;
访问视图(OpenGLES、UIView、UIWindow);
暂停、恢复和结束游戏;
在UIKit和OpenGLES之间转换坐标。
1 . CCDirector类的方法
CCDirector对象主要有以下几种方法。
1)主程序启动并显示第一个场景。示例代码如下:
// Run the intro Scene
[[CCDirector sharedDirector] runWithScene: [HelloWorldLayer scene]];
注意 如果当前已有场景在运行,则不允许调用此方法。
2)将传入场景设置为当前场景。示例代码如下:
[[CCDirector sharedDirector]pushScene:[Setting scene]];
挂起当前运行的场景,并压栈到待运行场景队列。
注意 为了减少内存占用,应避免待运行队列中场景数量过多。仅在当前已有场景运行时才可调用此方法。
3)运行待运行场景队列中的倒数第二个场景,当前场景被释放。示例代码如下:
[[CCDirector sharedDirector]popScene];
当待运行队列中没有其他场景时,系统会终止该操作。只有在当前已有场景运行时才可调用此方法。
在实际的游戏开发中,当很多地方都要用到某个场景时,比如设置游戏音效和音量的场景,pushScene和popScene方法就非常有用。我们可以单击Setting,调用pushScene进入设置场景,然后在设置场景中单击Back按钮调用popScene,游戏就会返回上一个状态。无论是在主菜单、游戏场景或其他地方打开设置菜单,popScene方法都会正常工作,无需随时记录设置菜单的状态。
注意 这种弹出式场景的内容不应过多,以免占用内存。
4)直接使用新场景取代当前运行中的场景,并释放当前场景。示例代码如下:
[[CCDirector sharedDirector] replaceScene:MenuScene];
仅在当前已有场景运行时才可调用此方法,我们今后会经常用到它。
注意 即便是经验丰富的开发者也经常会遇到因为调用replaceScene方法而导致程序崩溃的情况。这大多数是因为没有合理的retain场景中的对象。而replaceScene方法会释放所有retain的对象。要找到问题所在,大家可以逐行注释dealloc方法中释放对象的代码。
在Cocos2D模板中选择SceneTest,可以看到popScene、pushScene和replaceScene的实际效果。大家也可以仔细分析SceneTest.m文件中的具体内容。
5)结束当前运行中的场景,并释放该场景。示例代码如下:
- (void)applicationWillTerminate:(UIApplication *)application {
CCDirector *director = [CCDirector sharedDirector];
[[director openGLView] removeFromSuperview];
[viewController_ release];
[window_ release];
//结束当前运行中的场景
[director end];
}
在HelloCocos2D项目的AppDelegate.m文件中可以看到该方法,但一般情况下不会直接使用该方法,而是由程序自动调用。
6)暂停当前运行中的场景。示例代码如下:
- (void)applicationWillResignActive:(UIApplication *)application {
[[CCDirector sharedDirector] pause];
}
调用该方法后,当前画面还会存在,但预定的更新方法会停止。
在HelloCocos2D项目的AppDelegate.m文件中可以看到该方法,但一般情况下不会直接使用该方法,而是由程序自动调用。
7)恢复场景运行。示例代码如下:
- (void)applicationDidBecomeActive:(UIApplication *)application {
[[CCDirector sharedDirector] resume];
}
调用该方法后,预定的更新方法会继续执行,同时delta time会被设置为0。
在HelloCocos2D项目的AppDelegate.m文件中可以看到该方法,但一般情况下不会直接使用该方法,而是由程序自动调用。
8)绘制场景。示例代码如下:
-(void)drawScene;
该方法是由系统自动每帧调用,请不要手动调用。
9)将UIKit坐标系中的坐标转换为OpenGLES坐标系中的坐标。示例代码如下:
UITouch *myTouch = [touches anyObject];
CGPoint location = [myTouch locationInView:[myTouch view]];
location = [[CCDirector sharedDirector] convertToGL:location];
上面的代码获取了触摸点的坐标,并将其转换成Cocos2D可以使用的坐标。该方法通常用于转换多点触摸点的坐标。
10)将OpenGLES坐标系中的坐标转换为UIKit坐标系中的坐标。示例代码如下:
textField.center = [[CCDirector sharedDirector] convertToUI:ccp(240, 250)];
该方法通常用于将节点转换为窗口中的点,以便调用如glScissor之类的命令。
虽然Cocos2D的功能很强大,但有时在游戏中也需要使用UIKit的元素,比如使用虚拟键盘输入玩家的昵称等,这时就需要使用该方法将Cocos2D中的坐标转换为UIKit中的坐标。
11)清除所有自动缓存的Cocos2D数据。示例代码如下:
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
[[CCDirector sharedDirector] purgeCachedData];
}
该方法将会清除CCTextureCache和CCLabelBMFont中的缓存。但CCSpriteFrameCache中的缓存不会被清除,需要手动清除。
12)更改投射的尺寸,该方法不太常用。示例代码如下:
[[CCDirector sharedDirector]reshapeProjection:[CCDirector sharedDirector].winSize];
13)启用或禁用OpenGLES的Alpha(透明)混合处理。示例代码如下:
[[CCDirector sharedDirector]setAlphaBlending:YES];
14)启用或禁用OpenGLES的位深测试。示例代码如下:
[[CCDirector sharedDirector]setDepthTest:YES];
15)获取导演对象的单例,该方法将在开发中频繁使用。示例代码如下:
CCDirector *director = [CCDirector sharedDirector];
实际上,每次使用CCDirector时都需要调用该方法。
16)停止动画,屏幕上将不会绘制东西。示例代码如下:
[[CCdirector sharedDirector] stopAnimation];
17)启动动画,只有当调用过stopAnimation方法后才可使用该方法。示例代码如下:
[[CCdirector sharedDirector]startAnimation];
18)获取屏幕中OpenGLES视图的大小,以points(点值)显示。示例代码如下:
CGSize windowSize = [[CCDirector sharedDirector] winSize];
该方法会考虑到视窗或设备的旋转。此外,如果使用winSizeInPixels会以像素值显示。在Mac系统下,winSize和winSizeInPixels会返回相同的值。
19该方法设置OpenGLES的默认值。示例代码如下:
[[CCdirector sharedDirector] setGLDefaultValues];
20)创建FPS标签。示例代码如下:
[[CCdirector sharedDirector] createFPSLabel];
2 . CCDirector类的属性
CCDirector对象具有以下属性:
runningThread:当前运行的Cocos2D线程。变量类型为NSThread。
runningScene:当前运行的场景,CCDirector一次只能显示一个场景。变量类型为CCScene。
animationInterval:游戏的FPS(帧每秒)值。变量类型为NSTimeInterval。
displayStats:判断是否显示CCDirector的各种统计信息。变量类型为ccDirectorStats。
该属性为Cocos2D v2.0版本中新添加的属性,它具有三种可能的值,分别是kCCDirector StatsNone(不显示统计信息)、kCCDirectorStatsFPS(帧每秒的信息)、kCCDirectorStatsMPF(毫秒每帧的信息)。
openGLView:OpenGLView视图,所有要进行视觉呈现的对象都会在该视图中进行渲染。变量类型为CC_GLVIEW。
nextDeltaTimeZero:下一个时间增量是否为0。变量类型为BOOL。
isPaused:CCDirector是否被暂停。变量类型为BOOL。
projection:设置OpenGLES投射。变量类型为ccDirectorProjection。
totalFrames:自CCDirector对象启动以来已调用帧的个数。
millisecondPerFrame:每帧所耗用的毫秒时间。变量类型为ccTime。
sendCleanupToScene:判断被替换的场景是否收到cleanup(清除)消息。如果新场景是弹出的,原来的场景不会收到清除消息;如果新场景会直接替代旧场景,则原来的场景会收到清除消息。变量类型为BOOL。
notificationNode:当主场景被访问时会用到此对象,用于hook(钩住)某个通知对象,如CCNotifications。变量类型为id。
projectionDelegate:OpenGLES投射更新时,且仅使用kCCDirectorProjectionCustom投射时调用该对象。变量类型是id。