绘制帧是游戏的关键环节, 要做事情有两件
1) 处理游戏对象的状态变化 ( 位置移动, 碰撞等等 )
2) 重新绘制这些对象
绘制对象
本文谈一下我对 Cocos2D 绘制帧实现的理解, 见下图 ( 注意, 这不是严格的UML, 只是为了便于理解绘制的 )
drawScene 就是要求 Cocos2D 去绘制一帧, Schudler_update() 就是处理 1) 游戏对象的状态变化 这个我们后面会讨论, 先说
2) 重新绘制这些对象
visit 正是实现上屏的方法
visit的要点有2
a) visit 是 Node的方法, Node 是有层次结构的组合模式 ( Composite)
对于普通Node, visit直接调用draw 方法 ( 比如 对于 CCSprite 派生类, 会重载 draw ( 而不是重载 visit) , 而其draw的实现就是调用 opengl 函数直接画图
b) 对于 Composite Node
它的 visit 的实现就是调用它引用到的多个 Nodes ( 可以认为这些就是它的孩子 ) 的 visit 方法.
一个 CCScene 就是一个 Composite Node, 而一个 Sprite 就是一个普通 Node, 当调用 CCScene 的 visit方法, 就调用它所有孩子的 visit方法, 并把整个游戏画面给画了出来.
状态更新
对于状态更新, 我们希望有一个 user_function, 因为不同游戏,玩法逻辑不同. 我们要按照自己设想的逻辑去更新对象状态.
回忆一下是如何做到, 参见我写的中的代码
Cocos2d Box2D 开发Android下的 Breakout 撞球游戏
我们 schedule 了一个 tick 去处理游戏逻辑 ( 这个 tick 是让 Box2D engine 运行一下 step, 而后根据Box2D 的状态去更新 CCSprite, 实际就是把状态更新委托的 Box2D, 而最后做一下Box2D Body 和 Sprite 的同步而已)
bool HelloWorld::init()
{
...
schedule(schedule_selector(HelloWorld::tick));
}
void HelloWorld::tick(float dt)
{
_world->Step(dt, 10, 10);
for(b2Body *b = _world->GetBodyList(); b; b=b->GetNext()) {
if (b->GetUserData() != NULL) {
CCSprite *ballData = (CCSprite *)b->GetUserData();
ballData->setPosition(ccp(b->GetPosition().x * PTM_RATIO,
b->GetPosition().y * PTM_RATIO));
ballData->setRotation(-1 * CC_RADIANS_TO_DEGREES(b->GetAngle()));
}
}
}
因此我把这个 tick 称为 user_functions, 下边是处理流程
1) 在 init 我们 schedule 了一个 user_function, 其实他调用到了 Sheduler 的方法, 新建了 timer, 并被到一个 hashmap 中
2) 当 CCDirector 的 drawScene 被调用时, 它调用 Sheduler 的 update方法, 并传递到 Timer
3) Timer 的update 被调用时, 它检查是否到设定执行时间. 到, 就执行 user_function, 没有, 就返回, 等待下一次调用.
Cocos2D 的代码很细, 我没有仔细研究, 但机制我觉得大概如此, 还请指正