cocos2d-x 委托模式的巧妙运用

2015年02月01日 15:29 0 点赞 0 评论 更新于 2017-05-01 04:47

今天我们来深入学习一下 cocos2d-x 委托模式的巧妙运用。首先,我们先了解一下委托模式的基本概念,以下内容摘要自维基百科:

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

委托模式的 Java 示例

简单的 Java 例子

在此示例中,类 Printer 模拟打印机,它拥有 RealPrinter 的实例。Printerprint() 方法将处理任务转交给 RealPrinterprint() 方法。

class RealPrinter {
// 委托对象
void print() {
System.out.print("something");
}
}

class Printer {
// 委托者
RealPrinter p = new RealPrinter();
// 创建委托对象
void print() {
p.print();
// 委托操作
}
}

public class Main {
// 对于外部世界而言,看起来像是 Printer 实际执行了打印操作
public static void main(String[] args) {
Printer printer = new Printer();
printer.print();
}
}

复杂的 Java 例子

通过使用接口,委托可以实现类型安全且更加灵活。在这个例子中,类 C 可以委托类 A 或者类 B,并且类 C 拥有方法能够在类 A 和类 B 之间进行选择。由于类 A 和类 B 必须实现接口 I 规定的方法,所以这里的委托是类型安全的。不过,这个例子也显示出委托的缺点,即需要编写更多的代码。

interface I {
void f();
void g();
}

class A implements I {
public void f() {
System.out.println("A: doing f()");
}
public void g() {
System.out.println("A: doing g()");
}
}

class B implements I {
public void f() {
System.out.println("B: doing f()");
}
public void g() {
System.out.println("B: doing g()");
}
}

class C implements I {
// 委托
I i = new A();
public void f() {
i.f();
}
public void g() {
i.g();
}
// 普通属性
public void toA() {
i = new A();
}
public void toB() {
i = new B();
}
}

public class Main {
public static void main(String[] args) {
C c = new C();
c.f();
// 输出: A: doing f()
c.g();
// 输出: A: doing g()
c.toB();
c.f();
// 输出: B: doing f()
c.g();
// 输出: B: doing g()
}
}

从本质上讲,委托模式就是一个对象将自己需要完成的任务委托给另一个对象。这样做可以减少该对象需要处理的事务,只需将需求委托给另一个对象,该对象就能自行处理这些需求。如此一来,该对象需要实现的代码量减少了,而且委托的对象可以被重复利用,凡是有相同需求的对象都可以委托这个对象来处理相同的事务,从而减少了开发中的重复代码。

cocos2d-x 中的委托设计模式

在 cocos2d-x 中,我们不讨论 GUI 方面的委托模式,例如 Menu 的响应事件、Button 的响应事件。这里主要介绍在游戏控制中,判断游戏开始、运行、结束时所用到的委托模式。将这三个逻辑交给一个委托类来实现,不仅思路清晰,还可以重复利用,从而缩短开发周期。

游戏的主要逻辑通常集中在 GameLayer 层。在 GameScene 场景中添加所需的 Layer 层,然后在各个 Layer 层中实现相应的逻辑。在大多数开发中,开发者会在一个 GameLayer 层中完成游戏的所有逻辑判断。但这种方法会使 GameLayer 层的代码变得臃肿、不清晰,开发者自己都可能想要重构代码。

接下来,我们通过一个小 demo 来介绍 cocos2d-x 中委托模式的巧妙运用,让游戏开发更加清晰、快捷。

委托类的实现

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

StatusLayer.h

#include "cocos2d.h"
#include "GameLayer.h"
USING_NS_CC;

const int SPRITE_TITLE_TAG = 1000;

/**
* StatusDelegate 是委托类的父类,在 GameLayer 中实现三个虚函数
*/
class StatusDelegate {
public:
virtual void onGameStart() = 0;
virtual void onGamePlaying() = 0;
virtual void onGameEnd() = 0;
};

class StatusLayer : public Layer, public StatusDelegate {
public:
StatusLayer();
~StatusLayer();
virtual bool init();
CREATE_FUNC(StatusLayer);

// 实现父类 StatusDelegate 的三个虚函数
void onGameStart();
// 游戏开始逻辑判断函数
void onGamePlaying();
// 游戏运行逻辑判断函数
void onGameEnd();
// 游戏结束逻辑判断函数

private:
void moveFinished(Ref* pSender);
// title 精灵移动结束后回调此函数
void showRestartMenu(Ref* pSender);
// 显示重新开始按钮
void showOverSprite();
// 显示 GameOver 精灵函数
void menuRestartCallback(cocos2d::Ref* pSender);
// 点击开始按钮后回调此函数
void menuShareCallback(cocos2d::Ref* pSender);
// 分享按钮回调函数

private:
Size visibleSize;
Point origin;
Sprite* gameOverSprite;
};

代码中在需要注释的地方都有详细注释,这里不再过多解释。

StatusLayer.cpp

#include "StatusLayer.h"
#include "GameScene.h"
USING_NS_CC;

StatusLayer::StatusLayer() {
}

StatusLayer::~StatusLayer() {
}

bool StatusLayer::init() {
if (!Layer::init()) {
return false;
}

// 获取屏幕大小和原点坐标
visibleSize = Director::getInstance()->getVisibleSize();
origin = Director::getInstance()->getVisibleOrigin();

// 添加游戏中的背景
Sprite* background = Sprite::createWithSpriteFrameName("flappyrec_welcome_bg.png");
background->setPosition(Point::ZERO);
background->setAnchorPoint(Point::ZERO);
this->addChild(background);

// 添加游戏中的标题
Sprite* title = Sprite::createWithSpriteFrameName("flappyrec_welcome_title.png");
title->setPosition(Vec2(0 - title->getContentSize().width, visibleSize.height * 4 / 5));
title->setTag(SPRITE_TITLE_TAG);
// 设置 tag 值
this->addChild(title);

auto move = MoveTo::create(1.0f, Vec2(visibleSize.width / 2 - 50, title->getPositionY()));
// 移动结束后回调此函数
auto moveDone = CallFuncN::create(CC_CALLBACK_1(StatusLayer::moveFinished, this));
// 先加速后减速的动画特效
EaseExponentialOut* sineIn = EaseExponentialOut::create(move);
// 序列动画
auto sequence = Sequence::createWithTwoActions(sineIn, moveDone);
title->runAction(sequence);

return true;
}

/**
* title 移动结束后调用此函数
*/
void StatusLayer::moveFinished(Ref* pSender) {
// TODO
}

/**
* 委托类的方法,此方法会在 GameLayer 中被调用,用于游戏的开始
*/
void StatusLayer::onGameStart() {
this->getChildByTag(SPRITE_TITLE_TAG)->runAction(FadeOut::create(0.4f));
}

/**
* 委托类的方法,此方法会在 GameLayer 中被调用,用于游戏的运行中的逻辑实现
*/
void StatusLayer::onGamePlaying() {
// TODO
}

/**
* 委托类的方法,此方法会在 GameLayer 中被调用,用于游戏的结束逻辑的实现
*/
void StatusLayer::onGameEnd() {
this->showOverSprite();
}

/**
* gameOverSprite 精灵的添加,并添加从下到上出现的动作,
* 动作结束后调用显示按钮的函数
*/
void StatusLayer::showOverSprite() {
gameOverSprite = Sprite::createWithSpriteFrameName("flappyrec_welcome_rec.png");
gameOverSprite->setPosition(Vec2(visibleSize.width / 2, 0 - gameOverSprite->getContentSize().height));
gameOverSprite->setScale(0.5f);
this->addChild(gameOverSprite);

auto move = MoveTo::create(0.8f, Vec2(visibleSize.width / 2, visibleSize.height / 2 + 60));
auto moveDone = CallFuncN::create(CC_CALLBACK_1(StatusLayer::showRestartMenu, this));
EaseExponentialOut* sineIn = EaseExponentialOut::create(move);
Sequence* sequence = Sequence::createWithTwoActions(sineIn, moveDone);
gameOverSprite->runAction(sequence);
}

/**
* 按钮显示的回调函数,显示开始和分享按钮
* 并为按钮设置回调函数
*/
void StatusLayer::showRestartMenu(Ref* pSender) {
Node* tmpNode = Node::create();
// 两个按钮的父节点
Sprite* restartBtn = Sprite::createWithSpriteFrameName("play.png");
Sprite* restartBtnActive = Sprite::createWithSpriteFrameName("play.png");
restartBtn->setScale(0.6f);
// 缩放
restartBtnActive->setScale(0.6f);
// 缩放
restartBtnActive->setPositionY(-4);
// 向下移动四个单位

auto menuRestartItem = MenuItemSprite::create(restartBtn, restartBtnActive, NULL,
CC_CALLBACK_1(StatusLayer::menuRestartCallback, this));
// 设置按钮回调函数
auto menuRestart = Menu::create(menuRestartItem, NULL);
menuRestart->setPosition(Vec2(this->visibleSize.width / 2 - 35,
this->visibleSize.height / 2 - gameOverSprite->getContentSize().height / 3 + 60.0f));
tmpNode->addChild(menuRestart);
// 将按钮添加到父节点中

Sprite* shareBtn = Sprite::createWithSpriteFrameName("share.png");
Sprite* shareBtnActive = Sprite::createWithSpriteFrameName("share.png");
shareBtn->setScale(0.6f);
shareBtnActive->setScale(0.6f);
shareBtnActive->setPositionY(-4);

auto menuShareItem = MenuItemSprite::create(shareBtn, shareBtnActive, NULL,
CC_CALLBACK_1(StatusLayer::menuShareCallback, this));
// 分享按钮的回调函数
auto menuShare = Menu::create(menuShareItem, NULL);
menuShare->setPosition(Point(this->visibleSize.width / 2 + 65,
this->visibleSize.height / 2 - gameOverSprite->getContentSize().height / 3 + 60.0f));
tmpNode->addChild(menuShare);

this->addChild(tmpNode);
// 添加父节点

auto fadeIn = FadeIn::create(0.1f);
// 0.1s 后显示出现
tmpNode->runAction(fadeIn);
// 父节点执行动作
}

/**
* 重新开始按钮的回调函数,再次开始游戏
*/
void StatusLayer::menuRestartCallback(cocos2d::Ref* pSender) {
auto scene = GameScene::create();
TransitionScene *transition = TransitionFade::create(1, scene);
Director::getInstance()->replaceScene(transition);
}

/**
* 分享按钮的回调函数
*/
void StatusLayer::menuShareCallback(cocos2d::Ref* pSender) {
// TODO
}

上述函数都有详细的注释,后续会提供具体的源码。委托类的实现主要是实现了父类 StatusDelegate 类中的三个虚函数 onGameStart()onGamePlaying()onGameEnd()。这三个函数无需在 GameLayer 中实现,而是可以在 GameLayer 中调用委托类中的这三个函数来实现相应的逻辑判断,从而达到 GameLayer 层和 StatusLayer 层通信的目的。

作者信息

feifeila

feifeila

共发布了 570 篇文章