2012年5月24日
使用主机屋免费空间已经很长时间了,第一次知道主机屋是搜索看到的,我便试着申请了一个,虽然认证的时候麻烦一些,刚开始还有些怀疑。 等我认证之后申请下来使用的时候,我感觉到主机屋免费主机的优点是很多的。
1.访问速度非常快,不管是电信还是联通,都能在5秒之内打开网页。
2.主机屋免费空间后台管理功能非常强大,刚开始使用的时候还可以绑定子目录。这个功能我很喜欢。
3。越来越多的人知道了主机屋,而我也在继续使用着主机屋的空间。
免费主机屋空间可以说 是我目前 使用过访问速度最稳定,任何时间都不会出现突然访问速度变慢的情况。FTP访问也很快 很稳定,目前就是免费空间续期比较麻烦。不过对于免费空间 如此稳定 花点时间还是值得的 选择主机屋,你是对的 !!
2012年5月20日
本文将会比较简短。如果你对于前面几篇介绍cocos2d-x里面如何实现mvc有什么不清楚的地方,请跟我讲,这样我就可以补充地更详细一点。在此,我简单地总结一下前面写的这些文章:
1 我们实现了Model,它可以帮助我们封装游戏的逻辑,这样可以使我们的关注点集中于游戏逻辑,而不是其它的(比如渲染)
2 我们实现了Controller来处理用户交互,同时相应地更新model。
3 我们实现了View,而它的职责仅仅是负责显示model的状态。
它们三者之间的交互关系如下:
4 Controller负责初始化Model和View
5 View负责显示Model
6 View负责接收用户touch事件,然后传递给controller来处理。
7 Controller实现view的代理,同时可以操作Model。
8 model则执行一些游戏逻辑处理并通知view它的状态改变。
9 View根据model的当前状态来更新它里面的所有对象,比如精灵。
留几个开放性的问题:
10 如果你想使用NSUodoManager来实现“撤销”功能,你会把该功能放在哪里呢?Model?Controller?View?
11 你会怎么保存当前场景的状态信息呢?
12 或者你还可以补充其它问题?
参考来源:http://www.cnblogs.com/andyque/archive/2012/03/18/2390109.html
本文基于前面两篇文章,如果您还没有看过,建议先阅读下面两篇文章:
· cocos2d-x里面如何实现mvc(三)
· cocos2d-x里面如何实现mvc(四)
更新Model
当用户从工具箱中选一个小工具,然后把它放置到game board上面去时,我们需要编码响应这些事件。在上一篇文章中,我们已经实现了GameBoardViewDelegate的touchedAtRow方法。我们还需要给这个协议再添加一个接口方法。如下所示:
class GameBoardViewDelegate
{
public:
virtual void touchAtGrid(GameBoard *gameBoard, int row, int column) = 0;
virtual void touchWithToolBoxItemAtIndex(GameBoard *gameBoard, int index) = 0;
};
我们需要修改touch事件处理器,这样就可以判断我们到底是触摸了工具箱还是game board。
void GameBoardView::ccTouchesBegan(CCSet *pTouches, CCEvent *pEvent)
{
CCTouch *touch = (CCTouch*) pTouches->anyObject();
if(touch == NULL)
return;
// 置换坐标,等效如下
//CCPoint location = touch->locationInView(touch->view());
//location = CCDirector::sharedDirector()->convertToGL(location);
CCPoint point = this->convertTouchToNodeSpace(touch);
// 下面假设游戏区与工具区8:2来划分宽度
CCSize size = CCDirector::sharedDirector()->getWinSize();
CCRect *gameBoardRectangle = new CCRect(0, 0, size.width*0.8, size.height);
CCRect *toolBoxRectangle = new CCRect(size.width*0.8, 0, size.width*0.2, size.height);
if(CCRect::CCRectContainsPoint(*gameBoardRectangle, point)){
// calculate row and column touched by the user and call a delegate method
int row = 0;
int column = 0;
// ...
this->gameBoardViewDelegate->touchAtGrid(gameBoard, row, column);
} else if (CCRect::CCRectContainsPoint(*toolBoxRectangle, point)){
int index = 0;
// calculate toolbox item index based on a touch coordinate
this->gameBoardViewDelegate->touchWithToolBoxItemAtIndex(gameBoard, index);
}
}
在controller类里面处理touch事件是非常简单的,我们只需要持有一个model的引用,然后基于touch事件来调用model的方法就行了。我们的接口看起来和下面差不多,只是省略掉了一些实现细节:
class GameBoard : public CCObject
{
public:
// ...
GamePiece* getGamePieceFromToolBoxItemAtIndex(int index);
public:
// ...
int selectedToolBoxItemIndex;
};
然后,我们在GameBoardController里面完全实现GameBoardViewDelegate的两个方法。
void GameBoardController::touchAtGrid(GameBoard *gameBoard, int row, int column)
{
// if the toolbox item is selected move item from toolbox to game board
if(gameBoard->selectedToolBoxItemIndex != -1){
GamePiece *gamePiece = gameBoard->getGamePieceFromToolBoxItemAtIndex(gameBoard->selectedToolBoxItemIndex);
gameBoard->putGamePiece(gamePiece, row, column);
}
}
void GameBoardController::touchWithToolBoxItemAtIndex(GameBoard *gameBoard, int index) {
// keep the toolbox selection state in the Model
gameBoard->selectedToolboxItemIndex = index;
}
到目前为止,我们实现了,用户可以点击工具箱中的小工具,然后把它们放置到game board中的一个小方块上面,同时model类在中间起了桥梁作用。
通知view关于model的改变
为了在view里面反映出model的状态更改,我们可以在model有变化的时候给view发送通知消息,然后view就可以根据不同的消息来作出不同的响应了。和我们在实现view通过controller一样,这里我们也定义了一个GameBoardDelegate,用来通知view model的变化。
class GameBoardDelegate
{
public:
virtual void didPutGamePiece(GamePiece *gamePiece, int row, int column) = 0;
};
class GameBoard : public CCObject
{
// ...
public:
void setGameBoardDelegate(GameBoardDelegate *aGameBoardDelegate);
private:
GameBoardDelegate *gameBoardDelegate;
};
void GameBoard::putGamePiece(GamePiece *gamePiece, int row, int column)
{
// ...
// store game piece
// notify that the game piece was put on a gameboard
this->gameBoardDelegate->didPutGamePiece(gamePiece, row, column);
}
void GameBoard::setGameBoardDelegate(GameBoardDelegate *aGameBoardDelegate)
{
this->gameBoardDelegate = aGameBoardDelegate;
}
在GameBoardView里面实现GameBoardDelegate的时候,当我们需要在game board上面放置一个小工具的时候,我们定义了一个CCSprite。
class GameBoardView :
public CCLayer, public GameBoardDelegate
{
// ...
};
void GameBoardView::initWithGameBoard(GameBoard *aGameBoard, GameBoardViewDelegate *aDelegate)
{
// ...
this->gameBoard = aGameBoard;
this->gameBoard->retain();
this->gameBoard->setGameBoardDelegate(this);
this->gameBoardViewDelegate = aDelegate;
// ...
}
void GameBoardView::didPutGamePiece(GamePiece *gamePiece, int row, int column)
{
// create CCSprite and put it on a game board at corresponding position
CCSprite *gamePieceSprite = CCSprite::spriteWithFile("CloseNormal.png");
// ...
this->addChild(gamePieceSprite, 1);
}
总结
现在框架中所有的部分都联系起来了,model、view和controller三者组成了著名的MVC模式
· View接收touch事件,然后把事件传递给controller,
· Controller 响应用户的touch事件,然后更新model
· model 更新它自身的状态, 处理游戏逻辑,然后告诉view它改变了哪些东西。
· View则基于Model当前的状态来更新自己的显示
参考来源:http://www.cnblogs.com/andyque/archive/2012/03/18/2390106.html
2012年5月19日
在上一篇文章中,我们使用cocos2d-x基于mvc做了一个简单了游戏架子,这个架子还非常简单,还有许多东西有待实现。
介绍模型
在上一篇博文中,我们介绍了view和controller。为了实现mvc模式,我们还需要添加一个model类来维护游戏的状态。我们的实现应该要包含下列这些类:
1 GameBoardView - 也就是View,
2 GameBoardController - 也就是Controller.
3 GameBoard – 也就是Model.
Model 实现
GameBoard 实现
我们在第一部分所描述的需求是这样子的:
。。。一个game board 是通过n行n列组成的,它会随着游戏难度有所变化。
因此,我们按照下面的编码方式来实现之:
Class GameBoard : public CCObject {
public:
int numberOfRows;
int numberOfColums;
void initGridSize(int aNumberOfRows, int aNumberOfColumns){
this->numberOfColumns = aNumberOfColumns;
this->numberOfRows = aNumberOfRows;
}
}
请注意,model是从CCbject继承过来的---因为model只需要关注game board model的状态就行了(当然,还有相应的更新状态的方法)---我们不应该把其它东西也放进来,比如继承到CCNode就不行,我们并不需要CCNode的东西,所以,为了纯粹性,我们这里继承到game board 。
GameBoardView 的实现
我们现在需要修改View,同时它包含一个model的引用,我们可以通过initWithGameBoard方法来初始化这个成员变量:
class GameBoardView :: public CCLayer {
public:
GameBoard *gameBoard;
void initWithGameBoardModel(GameBoard *aGameBoard);
}
具体GameBoardView的实现细节如下:(为了演示方便,我们忽略了实际渲染每一个小方块的代码)
void GameBoardView::initWithGameBoard(GameBoard *aGameBoard)
{
if(gameBoard != NULL){
gameBoard->release();
gameBoard = NULL;
}
this->gameBoard = aGameBoard;
this->gameBoard->retain();
// render gameBoard background
CCSprite *gameBoardSprite = CCSprite::spriteWithFile("gameBoard.png");
gameBoardSprite->setAnchorPoint(ccp(0, 0));
this->addChild(gameBoardSprite);
for(int i=0; i<gameBoard->numberOfRows; i++)
for (int j=0; j<gameBoard->numberOfColumns; j++)
{
// position and render game board spacse
}
}
GameBoardController
最后,我们要更新GameBoardController的init方法,因为view需要把GameBoard对象通过init方法注入进去,所以,我们在controller的init方法里面,就应该定义好model对象,然后传递给view。
virtual bool init(){
bool bRet = false;
do{
// 先调用超类的init方法
CC_BREAK_IF(! CCLayer::init());
GameBoard *gameBoard = new GameBoard();
gameBoard->initGridSize(7, 9);
view = GameBoardView::node();
view->initWithGameBoard(gameBoard);
this->addChild(view, 0);
bRet = true;
}while(0)
beturn bRet;
}
处理touch事件
GameBoardView updates
我们的View虽然继承了CCLayer,但view本身是不应该处理用户的交互(touch事件)的,所以,我们需要定义一个代理(GameBoardViewDelegate)。(译者:为什么这里要定义代理呢?所谓代理代理,当然就是你不想做的事,找别人去做,这就是代理。所以,当你写代码的时候,你想保持类的简单性、重用性,你就可以把事件尽量都交给其它类去做,自己只管做好自己的事。也就是SRP,单一职责原则。如果一个类关注的点过多,做的事情太多。这些事情不管是你直接做的,还是调用别的对象去完成的。这都不行,自己做这些事,那就会使类的功能复杂化,维护不方便。而过多地调用其它对象来完成一些事情,表面上看起来好像不错,实际上是过度耦合了。我们编写类的原则应该是追求高内聚,低耦合的。可能你会说,用代理不也是交给别人做吗?没错,问的好。但是,代理是接口,我们是针对接口编程,所以它的重用性会非常好。因此,下次当你想写可扩展和可重用的代码的时候,不妨先想想代理这个东西吧。C++和java者是用接口实现的,而objc里面delegate是用protocol实现的,具体怎么转换的,比较一下应该就可以了。)
class GameBoardViewDelegate
{
public:
virtual void dealWithTouchesBegan(GameBoard *gameBoard, int row, int column) = 0;
};
我们还需要再修改一下GameBoardView的init方法,通过传送一个delegate进来处理touch事件。
void initWithGameBoard(GameBoard *aGameBoard, GameBoardViewDelegate *aDelegate);
下面是touch事件的具体实现,记得先在初始化时setIsTouchEnable(true)哦^_^:
void GameBoardView::ccTouchesBegan(CCSet *pTouches, CCEvent *pEvent)
{
CCTouch *touch = (CCTouch*) pTouches->anyObject();
if(touch == NULL)
return;
// 置换坐标,等效如下
//CCPoint location = touch->locationInView(touch->view());
//location = CCDirector::sharedDirector()->convertToGL(location);
CCPoint point = this->convertTouchToNodeSpace(touch);
// calculate row and column touched by the user and call a delegate method
int row = 0;
int column = 0;
// ...
this->gameBoardViewDelegate->dealWithTouchesBegan(gameBoard, row, column);
}
GameBoardController 更新
GameBoardController将会负责处理用户touch事件,所以,我们需要让GameBoardController实现GameBoardViewDelegate接口:
class GameBoardController :
public CCLayer, public GameBoardViewDelegate
{
public:
//...
virtual void dealWithTouchesBegan(GameBoard *gameBoard, int row, int column){
// do the game logic here and update view accordingly
}
// ...
};
还有最后一步,那就是修改view的init方法,把controller传递进去。
// initialize view
view->initWithGameBoard(gameBoard, this);
总结
在这篇文章中,我们实现了Model,同时还通过一些代码把view和controller联系起来了。同时,我们还在view和controller,以及用户touch事件之间建立了联系,这样controller类就可以来响应游戏里面的用户输入了。(译者:为什么费这么多劲,无非就是职责分离,这个非常重要!)。在接下来的文章里面,我们会谈到下面两个问题:
· 在Controller里面更新Model,
· 通知View关于Model的改变.
参考来源:http://www.cnblogs.com/andyque/archive/2012/03/18/2390102.html
引子:前面两篇文章介绍了一些关于在cocos2d-x里面如何实现mvc的理论知识,接下来的这三篇教程,我将用一个简单的教程示例,给大家演示一下具体代码实现细节。
这篇文章的写作目的就是让大家更好地理解如何在cocos2d-x里面实践mvc模式(当然,这里演示的不一定是标准的mvc,因为cocos2d-x特殊的编程方式。但是,这并不妨碍我们编写更好的代码,你们说对吧?),本文是基于前两篇文章的,所以,在继续阅读之前,我强烈建议你先读一下第一篇的理论介绍。
情景
我们将制作一个简单的面板解谜游戏(board puzzle game),当然,我们不是简单地开发一个游戏,而是要利用mvc开发出一个简单的“游戏框架”,而且这个框架将会在我的新游戏里面使用到,它具有如下一些特性:
1 一个n行n列的游戏面板(game board),n可以随着游戏难度进行变化。
2 这个游戏面板里面会包含一些“小方块(game pieces)”,而且每一个game board上都只能放一个game piece。
3 这个游戏面板可以初始化一些固定的小方块,玩家在游戏过程中,是不能移动这些小方块的。
4 这里还定义了一个“工具箱(toolbox)”,它上面可以放置许多小工具(toolbox item),它们可以看作是“可放置可移动小方块的槽子”。
5 小工具(或者叫槽子)上面可以放置许多同一类型的小工具。
6 这些小工具可以从工具箱上面移动,并且可以放置到game board 上面。
基本概念
来自 wikipedia:
model负责管理应用领域的数据和行为逻辑,同时负责响应对自己的状态数据请求(这些请求通常是从view过来的),然后响应一些指令来更改自身的状态(这些请求通常是来自controller的)。在一个事件驱动的系统中,model会通知订阅者(observers)(通常是views)它的状态改变,这样view就可以做相应的显示更新。
view则根据model的状态来合理地显示,通常是一些UI元素。一个model可以对应多个view,比如,同一数据的柱状图、饼状条、曲线图等。
controller负责接收多用户输入和调用model的一些方法。一个controller通过从用户那里获得输入,然后操作model对象,最后,model通知view来更新显示。
从维基百科的定义中,我们可以识别出以下几个主要的类(我们会在后面把model给加上去):
7 GameBoardView 代表应用程序的主视图
8 GameBoardController 是GameBoardView的一个控制器。
请注意,这里的实线代表一种直接的关联关系(controller里面包含一个view的引用),而虚线则代表了一种间接的关联(通过观察者模式)。这里的直接关联后面会用来实现touch事件处理。
实现
项目组织结构
在VS2008里面基于cocos2d-x的默认模板创建一个新的项目之后,我们又创建了下面这些组:
9 View – views & controller 组 (我们也可以把view和controller放在那个不同的group里面,但是,由于我们两个有直接的关联关系,为了方便,我就把它们放在一起了)
10 Model – 之后,我们会把model类放在这个group下面。
GameBoardView 的实现
接下来,我们开始实现GameBoardView。首先,我们把GameBoardView继承至CCNode。
class GameBoardView : public CCLayer {
// ...
// implement the "static node()" method manually
LAYER_NODE_FUNC(GameBoardView);
}
然后,实现它的init方法,然后简单地显示一串字符来验证程序的正确性。(译者:这就和我们有时候会在方法的第一句加一个CCLOG一样,只是为了验证函数是否被调用了,确保每一步都是按照你的想法去走的,这样比那种埋头编写2个小时代码不编译,而后花一晚上修改编译错误和bug要好很多。有时候只是输出还不够,还必须要做单元测试,这样才能提高效率)
virtual bool init(){
bool bRet = false;
do
{
// 先调用超类的init方法
CC_BREAK_IF(! CCLayer::init());
CCLabelTTF *label = CCLabelTTF::labelWithString("Hello World from view", "Marker Felt", 48);
// 获取窗体的尺寸
CCSize size = CCDirector::sharedDirector()->getWinSize();
label->setPosition(ccp(size.width/2, size.height/2));
this->addChild(label, 0);
bRet = true;
} while (0);
return bRet;
}
GameBoardController 实现
GameBoardController负责初始化view,所以它里面包含了一个GameBoardView的引用,将来就可以非常方便地直接使用了。
class GameBoardController : public CCLayer {
public:
GameBoardView *view;
// ...
// implement the "static node()" method manually
LAYER_NODE_FUNC(GameBoardController);
}
因为我们的GameBoardController继承到CCNode,所以,我们可以把GameBoardView当作GameBoardController的孩子给添加进去。
virtual bool init(){
bool bRet = false;
do{
// 先调用超类的init方法
CC_BREAK_IF(! CCLayer::init());
view = GameBoardView::node();
this->addChild(view, 0);
bRet = true;
}while(0)
beturn bRet;
}
static CCScene* scene(){
CCScene *scene = NULL;
do{
scene = CCScene::node();
CC_BREAK_IF(!scene);
GameBoardController *layer = GameBoardController::node();
CC_BREAK_IF(!layer);
scene->addChild(layer);
}while(0);
return scene;
}
最后的修改
我们然后修改AppDelegate类,然后运行我们新创建的contorller:
CCDirector::sharedDirector()->runWithScene(GameBoardController::scene());
好了,现在编译并运行。当程序跑起来的时候,这个结果和cocos2d自带的模板运行效果差不多。但是,有个很重要的区别,那就是我们创建了一个mvc的骨架,在接下来的游戏逻辑中,我们可以在上面做很多文章。
接下来做什么
该项目进行到现在,已经为我们引入一些更高级的概念打下了良好的基础,所以,在下一篇教程里,我们将涉及下面两个东西:
11 处理touch事件.
12 引用model的概念.
参考来源:http://www.cnblogs.com/andyque/archive/2012/03/18/2390100.html
2012年5月15日
上一篇博文中,我提到了《如何在cocos2d-x里面实现mvc(一)》,但是,都是一些纯理论的东西,我们需要看一些代码才能理解地更清楚。这篇博文是基于上一篇来写的,所以我建议你先阅读完上一篇。
模型类
就像之前所讨论的,GameModel类存储了游戏世界里面的一些属性,比如当前的重力。但是,它同时也负责创建和联接游戏里面的对象,比如Player和Platforms。它们之间的关系如下图所示:(译者:这里采用了针对接口编程的方法,所有的游戏对象都实例updateable接口,这样就可以在game loop里面更新自己了。同时GameModel类提供了一个工厂方法createGameObjects,用来创建游戏里面的对象。)
你可能已经注意到了,所有的model类都实现了updateable protocol,并实现了update方法。这样它们就可以在game loop里面更新自己的状态了。比如,在Player类里面,我们需要根据当前x轴和y轴的速度来更新player的位置信息。在我的游戏里面,我把它委托给Physics组件,它是我实现的一个简单的物理引擎。但是,假如你的游戏很简单的话,你可以不用分开你的物理代码,然后可以直接在update方法里面来做碰撞检测等物理操作。
// Player.h
#include "cocos2d.h"
using namespace cocos2d;
class Player : public Updateable{
public:
void update(ccTime dt){
_physics->updateModel(this, dt);
// detect collisions with game objects, etc.
}
}
GameModel实现的update方法,不仅仅用来更新自己的状态,同时,它还调用player的update方法和所有platform的update方法。这个update方法,之后会被game loop所调用。
// GameModel.h
#include "cocos2d.h"
using namespace cocos2d;
class GameModel : public Updateable{
public:
virtual void update(ccTime dt){
// modify game model properties here
// update player
this->player->update(dt);
// update platforms
for(int i=0; i<_platforms.size(); i++){
_platforms.at(i)->update(dt);
}
// ...
}
}
视图和控制器类
对于我的游戏里面的每一个场景,都关联了一个Controller类,它负责处理用户交互、创建视图和管理场景的跳转。控制器会schedule一个游戏主循环,在这个loop里面,所有的model和view的update方法都会被调用。
// GameplayController.h
#include "cocos2d.h"
using namespace cocos2d;
class GameplayController : public GameplayViewDelegate{
public:
virtual bool init() {
GameplayView *view = new GameplayView();
view->initWithDelegate(this);
// retain view in controller
this->view = view;
// release view
view->release();
// init model
GameModel *model = GameModel::sharedModel();
model->createGameObjects();
model->getPlayer()->run();
this->scheduleUpdate();
return true;
}
void update(ccTime dt) {
GameModel *model = GameModel::shareMode();
if(model->getIsGameover()) {
CCDirector::sharedDirector()->replaceScene(GameOverController::node());
// process model
model->update(dt);
// update view
this->view->update(dt);
}
}
}
View主要负责根据model的状态来渲染游戏画面。但是,同时,因为cococs2d的实现方式,我们还需要把touch事件传递给controller类。你应该注意到了,view不并直接依赖controller。view类调用controller的方法是通过GameViewDelegate协议来实现的。这也是为什么我们要在init方法里面传递一个delegate的原因。
// GameplayView.h
#include "cocos2d.h"
using namespace cocos2d;
class GameplayView {
public:
void initWithDelegate(CCObject *theDelegate) {
this->delegate = theDelegate;
// initialize layers
_backgroundLayer = GameplayBackgroundLayer::node();
this->delegate->addChild(_backgroundLayer);
_platformLayer = GameplayPlatformLayer::node();
this->delegate->addChild(_platformLayer);
_playerLayer = GameplayPlayerLayer::node();
_playerLayer.delegate = theDelegate;
this->delegate->addChild(_playerLayer);
_hudLayer = GameplayHudLayer::node();
_hudLayer.delegate = theDelegate;
this->delegate->addChild(_hudLayer);
}
}
更新:我忘了告诉大家layer本身是怎么实现的了。其实很简单,就是创建一些sprite、action和animation等。
// GameplayPlayerLayer.h
#include "cocos2d.h"
using namespace cocos2d;
class GameplayPlayerLayer : public CCLayer, public Updateable{
public:
virtual bool init() {
this->setIsTouchEnabled(true);
this->setIsAccelerometerEnabled(true);
// ResourceManager is the self-declared class
ResourceManager *resources = ResourceManager::sharedResourceManager();
CCSpriteFrameCache::sharedSpriteFrameCache()->addSpriteFramesWithFile(PLAYER_SPRITE_SHEET_PLIST);
CCSpriteBatchNode *spriteSheet = resources->playerSpriteSheet;
this->addChild(spriteSheet);
// ...
// initialize sprites
// initialize animations
}
// ...
}
层里面的精灵都会在layer的update方法里面被更新,如下所示:
void update(ccTime dt)
{
// update player sprite based on model
GameModel *model = GameModel::sharedModel();
_playerSprite->setPosition(ccp((model->getPlayer()->getPosition().x - model->getViewPort()->getRect()->getOrigin().x)*PRM_RATIO, model->getPlayer()->getPosition().h - model->getViewPort()->getRect()->getOrigin().y)*PRM_RATIO);
}
注意,在渲染player的位置的时候,我们使用了PPM_RATIO,用来把米转换成point。(为什么是point而不是pixel,因为cocos2d使用的是point而不是pixel,不明白的可以看看源代码和官方文档)
touch事件被传递给了controller类,如下所示:
class GameplayPlayerLayer : public CCLayer, public Updateable{
// ...
virtual void ccTouchesBegan(CCset *pTouches, CCEvent *pEvent){
this.delegate->playerBeginJump();
}
}
然后下图就是view和controller交互的完整的UML图:
处理模型事件
上一篇博文中,我留下了一个问题,就是怎么处理model和controller之间的交互。其它很简单,就是使用观察者模式,controller只要订阅model的事件,然后定义相应的处理方法即可。当model更新的时候,会触发事件,然后所以侦听了该事件的controller都能被通知到。下面给出实现:(译者:很多童靯不知道对象之间该怎么交互,其实使用Notification可以大大地解耦对象的交互,使代码更容易维护。)
class Player : public Updateable{
public:
void beginJump(){
// 在Object-c中有NSNotificationCenter类,在C++中要自行实现该类
NotificationCenter::shareCenter()->postNotificationName(EVENT_PLAYER_BEGIN_JUMP, NULL);
}
}
controller订阅事件,当事件发生的时候会得到通知,同时相应的事件处理函数将会被调用。
class GameplayController : public GameplayViewDelegate{
public:
virtual bool init() {
NotificationCenter::shareCenter()->addObserver(this, callfunc_selector(GameplayController::onPlayerBeginJumpNotification), EVENT_PLAYER_BEGIN_JUMP, NULL);
//...
}
void onPlayerBeginJumpNotification(){
SimpleAudioEngine::sharedEngine()->playEffect(PLAYER_JUMP_SOUND);
}
}
就这么多!
乍一看,可能会觉得有点复杂。而且,要创建这么多类,而只是为了实现一个简单的功能,确实有点划不来。而且,你还记得吗?如果在系统里面添加太多的类,其实是一种反模式(anti-pattern),叫做Fear of Adding Classes。但是,从长远的角度来看,从可维护性的角度来看,加这么多类是值得的。后面的教程我将向大家展示出来。如果大家对于如何在cocos2d里面使用mvc有更好的看法,欢迎补充。
参考来源:http://www.cnblogs.com/andyque/archive/2012/03/11/2390082.html
前言:
众所周知,现在MVC非常流行。现在只要随便搜索一下,哪里都是MVC的影子。刚开始在j2ee里面,然后是rails,后面居然.net也出来了,ios更不用说,哪里都是mvc,而且强制你必须使用mvc。但是,我们写的那些程序,真正完全符合mvc吗?呵呵,这个不好说,看个人理解程度而异。mvc实在是太火了,那么cocos2d-x该怎么实现mvc呢?
Model-View-Controller (MVC) 在web应用开发中非常流行,它是一种组合设计模式,目前被广泛应用于带有图形交互用户界面程序开发中。一些web开发框架,比如Ruby On Rails,Django 和 ASP.NET MVC, 它们是不同语言平台上面的web开发框架,但是,它们都共用同样的原则--那就是把用户表示层和逻辑层分离开来。关注点分离(SoC),这个原则在现代软件工程方法中是一个非常重要的设计理念--不要迷失于实现细节,遇到一个实际问题的时候,要划分不同的关注点,且这些关注点必须隔离开来,这样才能达到更好的代码重用度,以获得鲁棒性、可适配性和可维护性。所有这些软件属性对于软件质量来说都是至关重要的。
Cocos2d-x本身并不是基于mvc的理念来设计的,但是,这并不防碍你在自己的游戏开发中使用mvc。实现方式肯定是多种多样的,在这篇博文中, 我只是向大家分享一下我是怎么在cocos2d-x里面实现mvc的,同时,在最后,我会写一个简单的游戏demo,当然,里面使用的是cocos2d-x+mvc。
现有问题
cocos2d-x里面有这样一些类,CCSprite,CCLayer,CCScene,所有这些,都是CCNode的子类。基本上,大家在使用cocos2d-x开发游戏的时候,都会采用下面的步骤来实现游戏逻辑:
1 通过应用程序代理类来初始化第一个CCScene(即AppDelegate里面的第一个CCScene),
2 CCScene里面实例化一个或者多个CCLayer,并把它们当作孩子添加进去。
3 CCLayer 里面实例化一个或者多个CCSprite,也调用addChild添加进去
4 CCScene 处理用户输入(比如touch事件和加速计的改变),同时更新CCLayer和CCSpirte的属性,比如更改CCSprite的position,让sprite运行一个或多个actioin等。
5 CCScene里在运行一个游戏循环(game loop,一般是1/60更新一次),然后CCLayer和CCSprite就在这个game loop里面做一些更新和游戏逻辑。
这个过程看起来非常简单,而且也可以很快地做出游戏来。这也是为什么cocos2d-x这么流行的原因,它实在是太简单了。但是,当你的游戏逻辑越来越复杂的时候,你的代码会变得越来越难以维护。这里面最突出的问题就是,CCScene这个类负责的事情太多了---同时要处理用户交互,还有负责游戏逻辑(逻辑层)和画面显示(表示层)。(译者:根据SoC的原则,这显然是不合理的,我们应该把职责分离开来,这样代码才更容易维护。同时SRP(单一职责原则)也是这么要求的,一个类只负责一件事情)
模型(Model)
MVC它会把一个系统划分为以下几个组件:
· Model ,它负责与领域相关的逻辑处理代码,也可以说是逻辑层,或者领域层。
· View ,只负责界面显示。
· Controller ,它负责处理用户交互。
让我们先从model开始。Model代表了游戏逻辑。因为我现在正在制作一个platform游戏,所以,我讲的一些东西也是与platform游戏相关联的。我的游戏里面的model包含下面一些类(当然,仅仅是一部分类)
· Player,
· 包含一些属性,比如:player的位置、当前速度(x轴速度、y轴速度)等。
· 包含一些与player有关的处理逻辑,比如:run,walk,jmup等。
· 包含一个update方法,该方法会被游戏主循环每一帧刷新时所调用,它主要负责更新player model。
· Platform,
· 包含一些属性,比如:platform位置、宽度、高度等。
· 包含一些与platform有关的处理逻辑,比如:倾塌等
· 包含一个update方法,该方法会被游戏主循环每一帧刷新时所调用,它主要负责更新patform的model。
· GameModel,
· 包含一些游戏世界的属性,比如重力等。
· 包含一些方法来执行游戏逻辑。
· 包含一个update方法,该方法会在每一帧刷新的时候被game loop所调用,然后它就可以更新自己的状态,同时还会触发游戏世界里面的其它对象也相应地更新自己的状态。
你可能会问:有些属性你完全没有必要重复定义,你可以直接从CCSprite里面得到,比如position、width、height等。我想说:有对有错。说对呢,是因为它们确实差不多,可以拿来就用。说不对呢,那是因为,model有可能使用一些不同的计量单位,比如米,而不是像素。(比如box2d这样的,就不是使用像素作为单位)。在我的model里面,我使用的是米,当然,你也可以使用英尺,或者其它单位。渲染引擎对于model来说是透明的,model完全不用关心。
视图(View)
根据mvc的原则,view应该只负责界面显示。它实际上也是在cocos2d-x里面实现mvc时,最简单的一个。如果你有一个model,你可以使用CCLayer,然后添加一些CCSprite或者其它coocs2d-x类来处理显示问题。把model和view分开的好处就是,你没必要把model的属性直接映射到view的属性上面去。比如,你的玩家在x轴方向上移动,但是,你想让它总是在距离屏幕左边10px的位置。这时候,你就可以实现CCLayer了,而不是真的在移动sprite。当把model对象显示出来的时候,你必须考虑单位,如果你使用的是米作为计量单位,你在渲染的时候必须转化为像素。(你可以像box2d里面一样,定义一个PTM_RATIO)那么你的model怎么和view打交道呢?你可以从controller里面得到view,或者你可以把game model制作成一个单例,然后使用静态方法来负责它。
控制器(Controller)
controlller负责把view和model联系起来。它的主要职责就是处理用户输入。由于我们需要实例化model和view,我发现在controller里面来做非常合适。我是把controller类继承到CCScene类,然后我们一个初始controller类,它由appDelegate来实例化。然而,这里会有一个问题,touch事件是由CCLayer来处理的,而它在我的设计里面的角色是view。而我又不想让view来处理用户输入,所以,我需要传递一个view的引用给controller(不是直接传递,而是通过delegate),然后通过delegate来执行controller的touch事件处理代码,以此来处理view里面的touch事件。好了,现在我的controller类就能够处理来自view的用户事件了。然后,它可以根据用户的输入来操作model,要么通过修改model的属性,或者调用model的方法。再更新完model之后,我们的view也需要得到通知并更新。所以这些,我都在game loop里面完成,实际上它就是一个controller。controller的职责只是负责调用view的update方法,然后剩下的就交给view去完成啦。
还有一件事情…
游戏并不仅仅是根据model状态的更改来更新一下view就可以了,它还需要播放音乐和音效。由于controller负责处理用户交互,它肯定知道何时该播放什么音效。但是,有些时候也会有例外。如果一个player掉到platform上面,但是controller并不知道,因为这部分逻辑判断在model里面。那我们可以从model里面播放音效吗?。。。不,我们不能这样做。因为这样就破坏了SoC的原则了,model就应该只负责游戏逻辑。那么,我们该怎么做呢?在下一篇博文中,我将向大家展示我是怎么做的,我打赌,你肯定差不多也想到呢,对吧?
参考来源:http://www.cnblogs.com/andyque/archive/2012/03/11/2390074.html
2012年5月12日
要点:
- 关于随机函数:srand和rand是C标准库函数,对每个平台来说,可以获取毫秒级时间来产生随机数
沃Phone: srand(TimGetTicks());
iOS: arc4random(); 这个可以直接用
其他: srand((unsigned)time(NULL));
Object-c -> C++
YES -> true
NO -> false
Object-c:
selector:@selector(spriteMoveFinished) schedule:@selector(gameLogic)
C++: schedule_selector(_SELECTOR) 函数_SELECTOR中的参数类型有且只有一个,为ccTime
callfunc_selector(_SELECTOR) 函数_SELECTOR中的参数类型有且最多有一个,为void
callfuncN_selector(_SELECTOR) 函数_SELECTOR中的参数类型有且只有一个,为CCNode*
callfuncND_selector(_SELECTOR) 函数_SELECTOR中的参数类型有且只有两个,依次为CCNode*, void*
menu_selector(_SELECTOR) 函数_SELECTOR中的参数类型有且只有一个,为CCObject*