今天我们来学习一下cocos2d-x 委托模式的巧妙运用,首先我们先看一下委托模式是什么,下面的内容摘要自维基百科:

委托模式是软件设计模式中的一项基本技巧。在委托模式中,有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理。委托模式是一项基本技巧,许多其他的模式,如状态模式、策略模式、访问者模式本质上是在更特殊的场合采用了委托模式。委托模式使得我们可以用聚合来替代继承。

简单的Java例子
在此例中,类模拟打印机Printer拥有针式打印机RealPrinter的实例,Printer拥有的方法print()将处理转交给RealPrinter的print()方法。
  1. [cpp] 
  2. class RealPrinter { // the "delegate"
  3. void print() {
  4. System.out.print("something");
  5. }
  6. }
  7. class Printer { // the "delegator"
  8. RealPrinter p = new RealPrinter(); // create the delegate
  9. void print() {
  10. p.print(); // delegation
  11. }
  12. }
  13. public class Main {
  14. // to the outside world it looks like Printer actually prints.
  15. public static void main(String[] args) {
  16. Printer printer = new Printer();
  17. printer.print();
  18. }
  19. }
复杂的Java例子
通过使用接口,委托可以做到类型安全并且更加灵活。在这个例子中,类C可以委托类A或者类B,类C拥有方法使自己可以在类A或者类B间选择。因为类A或者类B必须实现接口I规定的方法,所以在这里委托是类型安全的。这个例子显示出委托的缺点就是需要更多的代码。
  1. [cpp] 
  2. interface I {
  3. void f();
  4. void g();
  5. }
  6. class A implements I {
  7. public void f() { System.out.println("A: doing f()"); }
  8. public void g() { System.out.println("A: doing g()"); }
  9. }
  10. class B implements I {
  11. public void f() { System.out.println("B: doing f()"); }
  12. public void g() { System.out.println("B: doing g()"); }
  13. }
  14. class C implements I {
  15. // delegation
  16. I i = new A();
  17. public void f() { i.f(); }
  18. public void g() { i.g(); }
  19. // normal attributes
  20. public void toA() { i = new A(); }
  21. public void toB() { i = new B(); }
  22. }
  23. public class Main {
  24. public static void main(String[] args) {
  25. C c = new C();
  26. c.f();     // output: A: doing f()
  27. c.g();     // output: A: doing g()
  28. c.toB();
  29. c.f();     // output: B: doing f()
  30. c.g();     // output: B: doing g()
  31. }
  32. }

介绍了委托模式,根本上讲就是我是一个对象,我需要做的事我委托另一个对象来做,这样就减少了我这个对象所要做的事情,我只需把需要的东西都委托给另一个对象,它能自行处理我的需求。这样一来我这个对象所需要实现的代码就减少了,而且委托的对象可以重复利用,不光我这个对象,凡是有这个需求的都可以委托这个对象来处理同样的事,减少了开发中的重复代码。

对于cocos2d-x中的委托设计模式,在这里我不讲GUI方面的委托模式。例如Menu的响应事件,Button的响应事件,我主要将一下在游戏控制中,判断游戏开始、运行、结束时用到的委托模式,这样把这三个逻辑都交给一个委托类来实现,思路较清晰,而且可以重复利用,减少开发周期。

游戏中的主要逻辑都集中在GameLayer层中,在GameScene场景中添加所需要的Layer层,然后在各个Layer层中实现相应的逻辑。

对于大多数的开发中,我都是在一个GameLayer层中完成游戏中的所有逻辑判断,后来感觉这种方法使GameLayer层的代码太臃肿,太不清晰,自己看着都想重构一下代码。

接下来以一个小demo来介绍下cocos2d-x中的委托模式的巧妙运用,使游戏开发更清晰,更快捷。

先来看一下委托类,游戏中的开始、运行、结束的逻辑判断都在这个委托类中实现。

下面是委托类的实现代码,StatusLayer.h的具体代码
  1. [cpp] 
  2. #include "cocos2d.h"
  3. #include "GameLayer.h"
  4. USING_NS_CC;
  5. const int SPRITE_TITLE_TAG = 1000;
  6.  /**
  7. * StatusDelegate 是委托类的父类,在GameLayer中实现三个虚函数
  8. * 具体代码如下
  9. * class StatusDelegate {
  10. *  public:
  11. *      virtual void onGameStart() = 0;
  12. *      virtual void onGamePlaying() = 0;
  13. *      virtual void onGameEnd() = 0;
  14. *  };
  15. */
  16. class StatusLayer : public Layer ,public StatusDelegate{
  17. public:
  18. StatusLayer(void);
  19. ~StatusLayer(void);
  20. virtual bool init();
  21. CREATE_FUNC(StatusLayer);
  22. //实现父类StatusDelegate的三个虚函数
  23. void onGameStart();//游戏开始逻辑判断函数
  24. void onGamePlaying();//游戏运行逻辑判断函数
  25. void onGameEnd();//游戏结束逻辑判断函数
  26. private:
  27. void moveFinished(Ref* pSender);//title精灵移动结束后回调此函数
  28. void showRestartMenu(Ref* pSender);//显示重新开始按钮
  29. void showOverSprite();//显示GameOver精灵函数
  30. void menuRestartCallback(cocos2d::Ref* pSender);//点击开始按钮后回调此函数
  31. void menuShareCallback(cocos2d::Ref* pSender);//分享按钮回调按钮
  32. private:
  33. Size visibleSize;
  34. Point origin;
  35. Sprite* gameOverSprite;
  36. };
代码中不做太多解释,在该注释的地方都有详细注释。
下面来看委托类StatusLayer的具体实现。
Statuslayer.cpp
  1. [cpp]
  2.   /*
  3. * StatusLayer.cpp
  4. *
  5. */
  6. #include "StatusLayer.h"
  7. #include "GameScene.h"
  8. USING_NS_CC;
  9. StatusLayer::StatusLayer() {
  10. }
  11. StatusLayer::~StatusLayer() {
  12. }
  13. bool StatusLayer::init() {
  14. if(!Layer::init()) {
  15. return false;
  16. }
  17. //获取屏幕大小和原点坐标
  18. visibleSize = Director::getInstance()->getVisibleSize();
  19. origin = Director::getInstance()->getVisibleOrigin();
  20. //添加游戏中的背景
  21. Sprite* background = Sprite::createWithSpriteFrameName("flappyrec_welcome_bg.png");
  22. background->setPosition(Point::ZERO);
  23. background->setAnchorPoint(Point::ZERO);
  24. this->addChild(background);
  25. //添加游戏中的标题
  26. Sprite* title = Sprite::createWithSpriteFrameName("flappyrec_welcome_title.png");
  27. title->setPosition(Vec2(0-title->getContentSize().width,visibleSize.height*4/5));
  28. title->setTag(SPRITE_TITLE_TAG);//设置tag值
  29. this->addChild(title);
  30. auto move = MoveTo::create(1.0f,Vec2(visibleSize.width/2-50,title->getPositionY()));
  31. //移动结束后回调此函数
  32. auto moveDone = CallFuncN::create(CC_CALLBACK_1(StatusLayer::moveFinished,this));
  33. //先加速后减速的动画特效
  34. EaseExponentialOut* sineIn = EaseExponentialOut::create(move);
  35. //序列动画
  36. auto sequence = Sequence::createWithTwoActions(sineIn,moveDone);
  37. title->runAction(sequence);
  38. return true;
  39. }
  40.  /**
  41. * title移动结束后调用此函数
  42. */
  43. void StatusLayer::moveFinished(Ref* pSender) {
  44. //TODO
  45. }
  46.  /**
  47. * 委托类的方法,此方法会在GameLayer中被调用,用于游戏的开始
  48. */
  49. void StatusLayer::onGameStart(){
  50. this->getChildByTag(SPRITE_TITLE_TAG)->runAction(FadeOut::create(0.4f));
  51. }
  52.  /**
  53. * 委托类的方法,此方法会在GameLayer中被调用,用于游戏的运行中的逻辑实现
  54. */
  55. void StatusLayer::onGamePlaying(){
  56. //TODO
  57. }
  58.  /**
  59. * 委托类的方法,此方法会在GameLayer中被调用,用于游戏的结束逻辑的实现
  60. */
  61. void StatusLayer::onGameEnd(){
  62. this->showOverSprite();
  63. }
  64.  /**
  65. * gameOverSprite精灵的添加,并添加从下到上出现的动作,
  66. * 动作结束后调用显示按钮的函数
  67. */
  68. void StatusLayer::showOverSprite() {
  69. gameOverSprite = Sprite::createWithSpriteFrameName("flappyrec_welcome_rec.png");
  70. gameOverSprite->setPosition(Vec2(visibleSize.width / 2,0-gameOverSprite->getContentSize().height));
  71. gameOverSprite->setScale(0.5f);
  72. this->addChild(gameOverSprite);
  73. auto move = MoveTo::create(0.8f ,Vec2(visibleSize.width/2,visibleSize.height/2 + 60));
  74. auto moveDone = CallFuncN::create(CC_CALLBACK_1(StatusLayer::showRestartMenu,this));
  75. EaseExponentialOut* sineIn = EaseExponentialOut::create(move);
  76. Sequence* sequence = Sequence::createWithTwoActions(sineIn,moveDone);
  77. gameOverSprite->runAction(sequence);
  78. }
  79.  /**
  80. * 按钮显示的回调函数,显示开始和分享按钮
  81. * 并为按钮设置回调函数
  82. */
  83. void StatusLayer::showRestartMenu(Ref* pSender) {
  84. Node* tmpNode = Node::create();//两个按钮的父节点
  85. Sprite* restartBtn = Sprite::createWithSpriteFrameName("play.png");
  86. Sprite* restartBtnActive = Sprite::createWithSpriteFrameName("play.png");
  87. restartBtn->setScale(0.6f);//缩放
  88. restartBtnActive->setScale(0.6f);//缩放
  89. restartBtnActive->setPositionY(-4);//先下移动四个单位
  90. auto menuRestartItem = MenuItemSprite::create(restartBtn,restartBtnActive,NULL,
  91. CC_CALLBACK_1(StatusLayer::menuRestartCallback,this));//设置按钮回调函数
  92. auto menuRestart = Menu::create(menuRestartItem,NULL);
  93. menuRestart->setPosition(Vec2(this->visibleSize.width/2 - 35,
  94. this->visibleSize.height/2 - gameOverSprite->getContentSize().height/3 + 60.0f));
  95. tmpNode->addChild(menuRestart);//将按钮添加到父节点中
  96. Sprite* shareBtn = Sprite::createWithSpriteFrameName("share.png");
  97. Sprite* shareBtnActive = Sprite::createWithSpriteFrameName("share.png");
  98. shareBtn->setScale(0.6f);
  99. shareBtnActive->setScale(0.6f);
  100. shareBtnActive->setPositionY(-4);
  101. auto menuShareItem = MenuItemSprite::create(shareBtn,shareBtnActive,NULL,
  102. CC_CALLBACK_1(StatusLayer::menuShareCallback,this));//分享按钮的会点函数
  103. auto menuShare = Menu::create(menuShareItem,NULL);
  104. menuShare->setPosition(Point(this->visibleSize.width/2 + 65,
  105. this->visibleSize.height/2 - gameOverSprite->getContentSize().height/3 + 60.0f));
  106. tmpNode->addChild(menuShare);
  107. this->addChild(tmpNode);//添加父节点
  108. auto fadeIn = FadeIn::create(0.1f);//0.1s后显示出现
  109. tmpNode->runAction(fadeIn);//父节点执行动作
  110. }
  111.  /**
  112. * 重新开始按钮的回调函数,再次开始游戏
  113. */
  114. void StatusLayer::menuRestartCallback(cocos2d::Ref* pSender){
  115. auto scene = GameScene::create();
  116. TransitionScene *transition = TransitionFade::create(1, scene);
  117. Director::getInstance()->replaceScene(transition);
  118. }
  119.  /**
  120. * 分享按钮的回调函数
  121. */
  122. void StatusLayer::menuShareCallback(cocos2d::Ref* pSender){
  123. //TODO
  124. }

上面的函数都做了详细的注释,后面会给出具体的源码。

委托类中的实现主要是实现了父类StatusDelegate类中的三个虚函数onGameStart(),onGamePlaying(),onGameEnd(),这三个函数不必在GameLayer中实现,可以在GameLayer中调用委托类中的这三个函数,来实现相应的逻辑判断,也起到了GameLayer层和StatusLayer层通信的目的。