游戏的基本元素
游戏的基本元素
大总管 CCDirector
@ runWithScene( CCScene *scene ) 启动游戏,并运行scene场景。本方法在主程序第一次启动主场景的时候调用。
@ replaceScene( CCScene *scene ) 直接使用传入的scene替换当前场景来切换画面,当前场景被释放。这是切换场景时最常用的方法。
@ pushScene( CCScene *scene ) 将当前运行中的场景暂停并压入到代码执行场景栈中,再将传入的scene设置为当前运行场景。
@ popScene( ) 释放当前场景,再从代码执行场景中弹出栈顶的场景,并将其设置为当前运行场景。如果栈为空,
直接结束应用。和PushScene结对使用
@ pause 暂停当前运行场景中的所有计时器和动作,场景仍然会显示在屏幕上
@ resume 恢复当前运行场景的所有计时器和动作,场景仍然会显示在屏幕上
@ end 结束场景,同时退出应用
CCScene: 场景
层的容器,包含了所有需要显示的游戏元素。除此之外,场景还有一个重要的作用就是流程控制的作用。利用CCDirector:: replaceScene的方法,我们可以使得游戏在不同的场景中自由切换。
通常,当我们需要完成一个场景时候,会创建一个CCScene的子类,并在子类中实现我们需要的功能。比如,我们可以在子类的初始化中载入游戏资源,为场景添加层,启动音乐播放,等等。
通常,场景之间通过CCTransitionScene系列类来实现过渡效果。我们也可以模仿Cocos2d-X
内置的场景切换代码,来编写自己的特效。
CCLayer:: 层
与场景不同,层通常包含的是直接在屏幕上呈现的内容;我们需要在层中加入精灵,文本标签或者其他游戏元素;设置游戏元素的属性,比如位置,方向和大小;设置游戏元素的动作等。通常,层中的对象功能类似,耦合较紧,与层中游戏内容相关的逻辑代码也编写在层中,在组织好层后,只需要把层按照顺序添加到场景中就可以显示出来了。要向场景添加层,我们可以使用addChild方法。
addChild( CCNode * child )
addChild( CCNode *child, int zOrder )
addChild( CCNode *child, int zOrder, int tag )
其中,Child参数就是节点。对于场景而言,通常我们添加的节点就是层。先添加的层会被置于后添加的层之下。如果需要为它们指定先后次序,可以使用不同的zOrder值。tag是元素的标识号码,如果为子节点设置了tag值,就可以在它的父节点中利用tag值就可以找到它了。
捕鱼游戏的场景主要由背景层 / 动作层 / 触摸层 / 菜单层 构成。假设这些层已经完成,那么我们最后要做的就是在游戏场景的初始化方法中把他们添
加到场景中:
this->addChild(backgroundLayer, 0);
this->addChild(actionLayer, 100);
this->addChild(touchLayer, 200);
this->addChild(menuLayer, 500);
CCLayer的另外一个重要作用就是可以接受用户的输入事件,包括触摸,加速度计和键盘输入等。
CCLayer中与输入事件相关的成员表
类型 名称 描述
TouchEnabled 获取或者设置是否接受触摸事件
属性 AccelerometerEnabled 获取或者设置是否接受加速度计事件
KeypadEnabled 获取或者设置是否启用键盘输入支持
ccTouchBegan
ccTouchMoved
ccTouchEnded
回调函数 ccTouchCancelled
ccTouchesBegan 标准触摸事件回调函数
ccTouchesMoved
ccTouchesEnded
ccTouchesCancelled
registerWithTouchDispatcher 注册触摸事件的回调函数,在此函数内设置需要注册的触摸类型
didAccelerate 加速度计改变事件的回调函数
CCSprite: 精灵
这个类可以说是游戏中最重要的组成元素,它描述了游戏的精灵,是CCNode的一个最重要也是最灵活的子类。说它重要因为它代表了游戏中一个最小的可见单位,说它灵活是因为它装载了一个平面纹理,具有丰富的表现力,而且可以通过多种方式加载。如果说CCLayer和CCScene代表了宏观的游戏元素管理,那么CCSprite则为微观世界提供了丰富灵活的细节表现。从游动的鱼儿,飞行的×××到可以旋转的炮台,都可以通过CCSprite来实现。
纹理
3D游戏中绘制到物体表面的图案。
创建精灵
在实际使用中,精灵是由一个纹理创建的。在不加任何设置的情况下,精灵就是一张显示在屏幕上的图片。通常,精灵置于层下,我们可以在层的初始化方法中创建精灵,设置属性,并添加到层中。最常用的方法是使用一张图片文件来创建精灵。
CCSprite* fish = CCSprite:: create("fish.png");
这个工厂方法包含一个字符串参数,表示精灵所用纹理的文件名。CCSprite会自动把图片作为纹理载入到游戏中,然后使用纹理初始化精灵。
精灵不但可以显示一个完整的纹理,也可以仅仅显示纹理的一部分。
CCSprite * smallFish = CCSprite:: create("fish.png", CCRectMake(0, 0, 100, 100));
其中,第一个表示纹理的文件名,第二个是CCRect类型的结构体,表示纹理中显示出来的矩形部分。CCRectMake(x, y, width, height)函数可以用来方便地创建CCRect。值得注意的是,纹理的坐标系中原点(0, 0)位于左上角,原点向右是x轴的正方向,原点向下是y轴的正方向。
设置精灵的属性
在创建了精灵后,我们还需要把精灵安排在合适的位置,否则引擎也不能决定精灵将如何呈现出来。比如精灵的位置,方向,缩放比例等参数都是精灵的属性,我们在层中添加精灵之前,需要对他们进行恰当的设置。
下面的代码首先获取屏幕大小,然后根据屏幕大小把精灵置于屏幕中间。
CCSize size = CCDirector::sharedDirector( )->getWinSize( );
CCPoint pos = ccp( size.width / 2, size.height / 2 );
fish->setPosition( pos );
我们可以看到,在这段代码中我们修改了精灵的position属性。position属性是一个CCPoint类型的结构体,表示精灵在层中的位置,它是精灵相对于层的坐标。ccp(x, y)和CCRectMake类似,是可以快速创建CCPoint的宏。精灵的属性来自于CCNode。
向层中添加精灵
设置好精灵的属性后,就需要把精灵添加到层。实际上CCLayer和CCSprite都继承自CCNode,而addChild是CCNode的方法,所以我们可以像向场景添加层的方法一样,添加精灵到层。
this->addChild( fish );
接下来,我们可以为捕鱼达人的动作层添加一个createFish( )方法来在层中创建一条鱼了。
void SpriteLayer:: createFish( )
{
CCSize size = CCDirector:: sharedDirector( )->getWinSize( );
CCSprite *fish = CCSprite:: create("fish.png");
fish->setTag( fish_red_tag );
fish->setPosition( ccp(size.width / 2, size.height / 2 ));
_fishes->addObject( fish );
this->addChild( fish );
}
其中,_fishes是CCArray的实例,是一个包含了所有鱼的数组。把鱼添加到_fishes中可以方便以后我们将要进行的检测碰撞。炮台精灵只有一个,因此可以在初始化层时候将其添加到动作层中。
bool SpriteLayer:: init( )
{
bool bRet = false;
do
{
CC_BREAK_IF( !CCLayer:: init( ));
CCSize winSize = CCDirector:: sharedDirector( )->getWinSize( );
_fishes = CCArray:: create( ); //数组的创建方法
_fishes->retain( );
// 炮台的初始化
curCannonLevel = 1;
char canonPath[80];
sprintf(canonPath, s_pCannon, curCannonLevel);
cannon = CCSprite:: create(cannonPath);
cannon->setPosition(ccp(WinSize / 2, cannon->getContentSize( ) / 3 ));
this->addChild( cannon, 2 );
bRet = true;
} while(0);
return bRet;
}
CCSprite 常用成员
初始化方法
使用图片文件
static CCSprite * create(const char * pszFileName );
static CCSprite * create(const char *pszFileName, const CCRect & rect );
bool initWithFile( const char * pszFileName );
bool initWithFile( const char * pszFileName, const CCRect & rect );
其中,pszFileName为图片文件名,直接传入图片文件相对于"Resource"文件夹下的路径就可以了; rect为可选参数,用于指定精灵显示纹理的部分,它使用前面介绍的纹理坐标系。
除了使用前面的两个工厂方法外,也可以使用构造函数加上初始化方法。
使用CCTexture2D
static CCSprite * create( CCTexture2D * PTexture );
static CCSprite * create( CCTexture2D * pTexture, const CCRect & rect );
bool initWithTexture( CCTexture2D *pTexture );
bool initWithTexture( CCTexture2D *pTexture, const CCRect & rect );
pTextrue为纹理对象,可以使用CCTextureCache类的addImage方法把图片文件装载为纹理并返回,rect和使用图片文件创建精灵的rect参数相同
使用CCSpriteFrame创建
static CCSprite * create( CCSpriteFrame *pSpriteFrame )
bool initWithSpriteFrame( CCSpriteFraem *pSpriteFrame );
纹理相关的属性
CCTexture2D *Texture
获取或者设置精灵所用的纹理。使用此方法设置纹理后,精灵将会显示一个完整的纹理。
CCRect TextureRect
获取或者设置纹理显示部分。此CCRect采用纹理坐标,即左上角为原点。
CCSpriteBatchNode * BatchNode
获取或者设置精灵所属的批节点
纹理相关的方法
void setDisplayFrame( CCSpriteFrame * pNewFrame )
设置显示中的纹理框帧,其中pNewFrame为新的纹理框帧,其代表的是纹理或者纹理的显示部分都可以和旧的不同
CCSpriteFrame *displayFrame 获取正在显示的纹理框帧
bool isFrameDisplayed( CCSpriteFrame *pFrame ) 返回一个值,表示pFrame是否是正在显示中的纹理框帧
颜色相关的属性
ccColor3Color 获取或者叠加在精灵上的颜色
GLubyte Opacity 获取或者设置精灵的不透明度
bool OpacityModifyRGB 获取或者设置精灵所使用的纹理数据是否已经预乘Alpha通道,当包含Alpha通道的图片显示错误时候,可以尝试修改这个属性
总结
游戏元素 + 游戏元素 + .... = 渲染树
游戏元素 <--------> CCNode
CCNode 属性 { 位置 / 缩放 / 是否可见 / 旋转角度 }
CCNode 功能 { 包含其他对象 / 接受各种事件与回调事件,比如定时器事件 / 运行动作 }
绘图坐标系
和openGL坐标系相同,以左下角为原点,向右为X轴正方向,向上为Y轴正方向
^ Y
|
|
|----------------> X
纹理坐标系
左上角为原点,向右为u轴正向,向下为v轴正向
u[0,1] v[0,1]
|------------->u
|
|
|
V v
绘图属性
CCRect ContentSize
获取或者设置此节点的内容大小
对CCSprite来说,ContentSize是它的纹理部分的大小,而对CCLayer等全屏的大型节点来说,ContentSize则是屏幕大小
CCPoint AnchorPoint 和 CCPosition position
AnchorPoint 用于设置一个锚点,以便精确地控制节点的位置和变换, AnchorPoint的两个参数x和y的取值都是0到1之间的实数,表示锚点相对于节点
长宽的位置
Position 用于设置节点的位置。由于Position指的是锚点在父节点中的坐标值,节点显示的位置通常与锚点有关。
对于场景等大型节点,它们的IgnoreAnchorPointForPosition为true,此时引擎会认为AnchorPoint永远为(0, 0),其他节点则为false,它们的锚点不会被忽略
@ float Rotation 获取或设置节点的旋转角度
@ float Scale float ScaleX float ScaleY 获取或者设置节点的缩放比例
@ bool Visible 获取或者设置节点的可见性
@ float SkewX float SkewY 获取或者设置斜切角度
@ int Tag 获取或者设置节点的标记
@ void * UserData 获取或者设置节点的额外信息
CCNode 的其他属性
CCArray *children 获取保存了该节点的所有子引用的数组
CCNode * Parent 获取或者设置节点的父节点
CCCamera * CameraRETURN 获取或者设置该节点的摄像机状态
CCGridBase * Grid 获取或者设置该节点的网格特效状态
CCGLProgram * ShaderProgram 获取或者设置该节点的Shader程序
CCActionManager * ActionManager 获取或者设置该节点所使用的动作管理器
CCScheduler * Scheduler 获取或者设置该节点所使用的计时器管理器
组织节点的方法
addChild( CCNode *child ) 把Child添加到当前节点中
removeFromParentAndCleanUp( bool cleanUp ) 把当前节点从其父节点中移除,如果参数为true,执行clean方法
removeChild( CCNode * child, bool cleanUp ) 从当前节点中移除child节点,如果参数为true,则调用child的clean方法
removeChildByTag( int tag, bool cleanUp ) 从当前节点中移除标记为tag的节点
removeAllChildrenWithCleanUp( bool cleanUp ) 移除当前节点的所有子节点
getChildByTag( int tag ) 返回当前标记值的节点
cleanUp 停止此节点的全部动作和计时器
定时器事件
update定时器
这种机制是CCNode的刷新事件update方法,该方法在每帧之前都会被触发一次。由于绘图帧率有限,而每次更新最终会反映到屏幕上,所以每帧之间的刷新一次已经足够响应大部分的游戏逻辑处理的要求了。
CCNode默认没有启用update事件,为了启用定时器,我们需要调用scheduleUpdate方法,并重载
update以执行自己的代码。对应的,我们可以使用unscheduleUpdate方法来停止定时器。
schedule定时器
另一种定时机制是CCNode提供的schedule方法,可以实现一定的时间间隔连续调用某个函数。由于引擎的调度机制,这里的时间间隔必须大于两帧的间隔,否则两帧期间的多次调用会被合并成一次调用。所以schedule定时器通常用在间隔较长的定时调用中。一般来说,事件间隔应在0.1秒以上。实际开发中,许多定时器操作都通过schedule定时器实现。比如鱼群的定时生成,免费金币的定时刷新。
比如,在捕鱼达人中,场景中的鱼群和炮弹随着时间的推移不断移动,因此我们需要定时刷新鱼群的位置,并进行碰撞检测。我们把处理鱼群移动和碰撞检测的代码放置在主游戏场景GameScene的UpdateGame方法中。GameScene的init方法添加如下代码来启动定时器
this->schedule( schedule_selector( GameScene:: updateGame ) );
而UpdateGame主要包含了两个操作,刷新鱼群位置和碰撞检测
void GameScene:: updateGame( CCTime dt )
{
SpriteLayer * sLayer = (SpriteLayer ) this->getChildByTag( sprite_layer_tag );
sLayer->updateFishMovement( dt );
sLayer->checkBulletCollideWithFish( );
}
CCNode的schedule方法接受一个函数指针并启动一个定时器,利用schedule方法不同的重载可以指定触发间隔和延时。schedule_selector则是把一个指定的函数转化为函数指针的宏,用于创建schedule
方法所需的函数指针。传入这个宏的函数应该包含一个float参数,表示距离前一次触发事件的时间间隔。
其他事件
方法名称方法描述
onEnter 当此节点所在场景即将呈现时候,会调用此方法
onEnterTransitionDidFinish 当此节点所在场景的入场动作结束后,会调用此方法。如果所在场景没有入场动作,则此方法会紧接着onEnter后调用
onExit 当此节点所在场景即将退出时,会调用此方法
onExitTransitionDidStart 当此节点所在场景的出场动作结束后,会调用此方法。如果所在场景没有出场动作,则此方法会紧接着onExit后调用
Cocos2d-X内置的常用层
为了方便游戏开发者,Cocos2d-X内置了三种特殊的CCLayer
@ CCLayerColor 一个单纯的实心色块
@ CCLayerGradient 一个色块,但可以设置两种颜色的渐变效果
@ CCMenu 十分常用的游戏菜单
CCLayerColor和CCLayerGradient
这两个层都很简单,仅仅包含一个色块。不同的是,前者创建的是一个实×××块,而后者创建的是一个渐变色块。
CCLayerColor有如下的初始化方法:如果采用了指定的宽度和高度的初始化方法,则创建了一个指定大小的色块;如果不指定大小的初始化方法,则创建一个屏幕大小的色块。
CCLayer的创建方法和初始化方法如下所示:
static CCLayerColor * create( const ccColor4B & color )
static CCLayerColor * create( const ccColor4B & color, GLfloat width, GLfloat height );
bool initWithColor( const ccColor4B & color );
bool initWithColor( const ccColor4B & color, GLfloat width, GLfloat height );
CCLayerGradient 和 CCLayerColor 类似,但是它在初始化时候需要指定两种颜色以及渐变的方向。在初始化方法中,start颜色为起始颜色,end参数为结束颜色,而v是方向向量。
static CCLayerGradient * create( const ccColor4B & start, const ccColor4B & end );
static CCLayerGradient * create( const ccColor4B & start, const ccColor4B & end, const CCPoint & v);
bool initWithColor ( const ccColor4B & start, const ccColor4B & end );
bool initWithColor ( const ccColor4B & start, const ccColor4B & end, const CCPoint & v);
在色块创建后,可以通过下面的方法来修改色块大小:
void changeWidth( GLfloat w );
void changeHeight( GLfloat h );
void changWidthAndHeight( GLfloat w, GLfloat h );
CCMenu: 游戏菜单
作为游戏不可或缺的一部分,在Cocos2d-X中,菜单由两部分组成,分别是菜单项和菜单本身。CCMenuItem表示一个菜单项,每个菜单项都是一个独立的按钮,定义了菜单的视觉表现和响应动作;CCMenu则是菜单,它负责将菜单项组织到一起并添加到场景中,转化屏幕的触摸事件到各个菜单项。
菜单项的创建通常需要规定普通状态,按下状态,被点击后的响应状态以及响应方法。
CCMenuItem:: create( "CloseNormal.png" // 普通状态下的图片
"CloseSelected.png" // 按下状态下的图片
this, // 响应对象
menu_selector( HelloWorld:: menuCloseCallback( ) ); // 响应函数
其中,响应方法必须满足SEL_MenuHandler形式,返回值为空,带一个CCNode* 型的参数。
比如
void HelloWorld::menuCloseCallback(CCObject* pSender)
{
// "close" menu item clicked
CCDirector::sharedDirector()->end(); //结束场景,同时退出应用
}
Cocos2d-X 的一大特色就是事件驱动的游戏框架,引起会在合适的时候调用事件处理函数,我们只需要在函数中添加对各种游戏事件的处理,就可以完成一个完整的游戏了。比如, 为了实现游戏的动态化, 引擎提供了两种定时器事件; 为了响应用户输入, Cocos2d-X 提供了触摸事件和传感器事件; 此外, Cocos2d-X还提供了一系列控制程序生命周期的事件。
游戏主循环
游戏乃至图像界面的本质就是不断地绘图,然而绘图不是随意的,任何游戏都需要遵循一定的规则呈现出来,这些规则就体现为游戏逻辑。 游戏逻辑会控制游戏内容,使得根据游戏用户输入和时间流逝来改变。因此,游戏可以抽象为不断重复以下动作:
@ 处理用户输入
@ 处理定时事件
@ 绘图
CCDirector:: mainLoop( )方法,这个方法负责调用定时器, 绘图, 发送全局通知, 并处理内存回收池。 该方法每帧调用一次, 而帧间隔取决于两个因素,一个是预先设置的帧率,默认为60帧每秒;另一个是每帧的计算量大小。当逻辑处理与绘图量过大时候,设备没法完成每秒60帧的绘制,次数帧率就会降低。
mainLoop 方法会被定时调用,然而在不同的平台下它的调用者不同。 通常CCApplication类负责处理平台相关的任务,其中就包括了对mainLoop的调用。
读者可以比较Android / iOS / Windows Phone三个平台下不同的实现,平台相关的代码位于引擎的"Platform"目录下。
mainLoop 定义在CCDirector中,不过是一个抽象方法,具体实现在CCDisplayLinkDirector类
未完待续。。。