今天我们就用我们这些天所学的知识来完成一个游戏小实例,巩固和加深理解一下前面的知识。首先让我们来看看最终的游戏效果图。本实例用到的资源文件,大家点击去下载。

Cocos2d-x游戏实例之忍者飞镖

我们按照写游戏的思路来一步一步的完成这个效果。首先新建一个工程,删除原工程下的HelloWorld.h和HelloWorld.cpp文件,今天我们要独立完成我们的程序。在工程下,新建五个筛选器,按下图进行命名。

Cocos2d-x游戏实例之忍者飞镖

在游戏主场景筛选器下建立GameMainLayer.cpp和GameMainLayer.h文件,在GameMainLayer.h中写如下的代码。头文件的#ifndef、#define、#endif都是固定格式,c++的基础语法,以后写头文件的时候都这么写,然后当然是包含cocos2d-x的头文件和使用命名空间了,因为我们要用引擎的东西啊。其他的几个函数也不用多说,m_winSize是屏幕的大小,因为在主场景中我们经常要用,所以我们将它作为成员变量。

#ifndef _GAME_MAIN_LAYER_H
#define _GAEM_MAIN_LAYER_H
#include "cocos2d.h"

using namespace cocos2d;

class GameMainLayer : public CCLayerColor
{
public:
	bool init();
	static CCScene * scene();
	CREATE_FUNC(GameMainLayer);
private:
	CCSize m_winSize;
};

#endif

接下来就是GameMainLayer游戏主场景的cpp文件了。头文件包含神马的以后就不说了。但是用到的类记住要包含头文件。scene函数和原来的相似,就是创建一个包含主场景层的场景。init函数的语句功能注释有详细的说明。这里需要说明的是GameMainLayer继承自CCLayerColor,所以在init函数中父类的初始化方法要注意一下,其实GameMainLayer根本不需要继承CCLayerColor,如果不添加背景图片的话继承自CCLayerColor就是白色的背景。

#include "GameMainLayer.h"

CCScene * GameMainLayer::scene()
{
	CCScene * scene = NULL;
	do
	{
		scene = CCScene::create();
		CC_BREAK_IF(!scene);

		GameMainLayer * mainLayer = GameMainLayer::create();
		CC_BREAK_IF(!mainLayer);

		scene->addChild(mainLayer);
	}
	while(0);

	return scene;
}

bool GameMainLayer::init()
{
	bool bRet = false;
	do
	{
		CC_BREAK_IF(!CCLayerColor::initWithColor(ccc4(255,255,255,255)));

		//获得屏幕尺寸
		m_winSize = CCDirector::sharedDirector()->getWinSize();

		//为游戏场景添加背景图片
		CCSprite * backgroundSprite = CCSprite::create("background.png");
		backgroundSprite->setPosition(ccp(m_winSize.width/2,m_winSize.height/2));
		this->addChild(backgroundSprite);

		bRet = true;
	}
	while(0);

	return bRet;
}

现在大家运行自己的程序应该看到的是一片白。接下来就为我们的游戏添加我们的玩家Hero。在玩家筛选器下新建Hero.h和Hero.cpp文件,在头文件中写如下的代码。

#ifndef _HERO_H
#define _HERO_H
#include "cocos2d.h"

using namespace cocos2d;

class Hero : public CCSprite
{
public:
	virtual bool init();
	//以下的宏产生的create方法里边是不能传递参数的
	CREATE_FUNC(Hero);
	//通过这种方法来绑定一个精灵,这是将玩家单独分装为一个类的时候的一种方法
	void bindSprite(CCSprite * sprite);
private:
	CCSprite * m_sprite;
};

#endif

头文件中相关方法的具体实现如下。我需要说明的是Hero继承自CCSprite,但是我们通过create方法想要获得一个Hero对象指针的时候,create方法中是不能传递参数的,那如何才能使该hero对象是被一张图片所初始化呢,就像我们获得的精灵是一张图片初始化的。以下的代码就是其中的一张方法。

#include "Hero.h"

bool Hero::init()
{
	bool bRet = false;
	do
	{
                //首先初始化父类的init函数
		CC_BREAK_IF(!CCSprite::init());
		//为成员变量赋值,否则的话指针指向的是空
		m_sprite = CCSprite::create();

		bRet = true;
	}
	while(0);

	return bRet;
}

void Hero::bindSprite(CCSprite * sprite)
{
	m_sprite = sprite;
	this->addChild(m_sprite);
}

然后在GameMainLayer.cpp的init方法中添加如下的代码,用来向场景中添加我们的游戏玩家。

//添加游戏玩家
		CCSprite * sprite = CCSprite::create("hero.png");
		CCSize spriteSize = sprite->getContentSize();
		m_hero = Hero::create();
		m_hero->bindSprite(sprite);
		m_hero->setPosition(ccp(spriteSize.width/2,m_winSize.height/2));
		this->addChild(m_hero,1);

运行程序我们看到如下的效果。
Cocos2d-x游戏实例之忍者飞镖接下来我们将怪物添加进来,在怪物筛选器下新建Monster.cpp和Monster.h,Monster.h和Monster.cpp中实现的代码如下。这里需要说明的是,Monster继承了CCSprite,与玩家类不同的是,在用图片初始化怪物的时候它没有使用bindSprite这个方法,而是在init函数中初始化父类的init的时候使用了initWithFile函数,传入了我们准备好的怪物图片,这样这个怪物天生被我们设置好了样子。这也是一种从CCSprite继承,使用图片资源来初始化我们的对象的一种方法。其他的说明看注释。

#ifndef _MONSTER_H_
#define _MONSTER_H_
#include "cocos2d.h"

using namespace cocos2d;

class Monster : public CCSprite
{
public:
	bool init();
	CREATE_FUNC(Monster);
	void move(float tm);
	void update(float tm);
	bool isOut();
private:
	CCSize m_winSize;
};

#endif
#include "Monster.h"

bool Monster::init()
{
	bool bRet = false;
	do
	{
		//初始化父类的时候调用initWithFile()方法里边传入一个图片资源,当创建怪物的时候就是这个图片了
		CC_BREAK_IF(!CCSprite::initWithFile("monster.png"));

		//获得屏幕的尺寸
		m_winSize = CCDirector::sharedDirector()->getWinSize();

		//每隔一帧就调用move函数和update函数
		this->schedule(schedule_selector(Monster::move));
		this->scheduleUpdate();

		bRet = true;
	}
	while(0);

	return bRet;
}

void Monster::update(float tm)
{
	//判断怪物是否超出了边界
	if(this->isOut())
	{
		//将怪物从父节点上移除
		this->removeFromParentAndCleanup(true);
	}
}

//判断怪物是否超出了边界
bool Monster::isOut()
{
	CCPoint point = this->getPosition();
	//当怪物完全出了屏幕的时候就移除掉
	if(point.xgetContentSize().width/2)
	{
		return true;
	}
	return false;
}

void Monster::move(float tm)
{
	//设置怪物的坐标,使怪物一直向右走,每帧移动的单位是一个像素
	this->setPosition(ccpAdd(this->getPosition(),ccp(-1,0)));
}

现在我们在主场景中添加进怪物。在主场景的init函数中我们添加如下的代码。

//获得一个随机数种子,之所以获得这个种子是在设置怪物的坐标时候用的
		cc_timeval tv;
		CCTime::gettimeofdayCocos2d(&tv,NULL);
		unsigned long reed = tv.tv_sec*1000+tv.tv_usec/1000;
		srand(reed);
		//每隔一秒时间添加怪物,通过改变这个时间可以改变怪物出现的快慢
		this->schedule(schedule_selector(GameMainLayer::addMonster),1.0);

在主场景的头文件中和cpp文件中分别添加如下函数的声明和实现。

//添加怪物
void GameMainLayer::addMonster(float tm)
{
	Monster * monster = Monster::create();

	//设置怪物出现在屏幕的最左边
	int x = this->m_winSize.width;
	//怪物的y坐标是我们通过getRandomNumber函数随机获得的数值
	//我们希望的怪物y坐标是不低于怪物自己的半身高,不高于它的头部,记住怪物的锚点在中心位置
	int y = getRandomNumber(monster->getContentSize().height/2,
		this->m_winSize.height-monster->getContentSize().height/2);
	monster->setPosition(ccp(x,y));

	this->addChild(monster);
}

//start和end代表希望获得的随机数范围
int GameMainLayer::getRandomNumber(int start,int end)
{
	return CCRANDOM_0_1()*(end-start)+start;
}

写完了上述的代码,运行程序如下。
Cocos2d-x游戏实例之忍者飞镖接下来我们实现子弹类,在子弹筛选器下新建Bullet.h和Bullet.cpp文件,实现代码如下。

#ifndef _BULLET_H_
#define _BULLET_H_
#include "cocos2d.h"

using namespace cocos2d;

class Bullet : public CCSprite
{
public:
	bool init();
	CREATE_FUNC(Bullet);
	bool isOut();
	void update(float tm);
private:
	CCSize m_winSize;
};

#endif
#include "Bullet.h"

bool Bullet::init()
{
	bool bRet = false;
	do
	{
		CC_BREAK_IF(!CCSprite::initWithFile("bullet.png"));

		m_winSize = CCDirector::sharedDirector()->getWinSize();

		this->scheduleUpdate();

		bRet = true;
	}
	while(0);

	return bRet;
}

//判断子弹是否出界
void Bullet::update(float tm)
{
	if(this->isOut())
	{
		//出界就从父节点中移除
		this->removeFromParentAndCleanup(true);
	}
}

bool Bullet::isOut()
{
	CCPoint point = this->getPosition();
	//子弹可能从各个方向发射,所以进行如下的判断
	if(point.xthis->m_winSize.width || point.ythis->m_winSize.height)
	{
		return true;
	}
	return false;
}

接下来我们为主场景实现屏幕触摸事件,首先在init文件中开启触摸。

//开启屏幕触摸
		this->setTouchEnabled(true);

接下来在主场景的头文件和cpp文件中实现如下的函数。

//为主场景注册单击事件
void GameMainLayer::registerWithTouchDispatcher()
{
	CCDirector::sharedDirector()->getTouchDispatcher()->addTargetedDelegate(this,0,true);
}

//当手指按下的时候触发该事件
bool GameMainLayer::ccTouchBegan(CCTouch * touch,CCEvent * pEvent)
{
	CCPoint touchPoint = touch->getLocation();
	//玩家向手指按下的放下发射子弹
	m_hero->fire(touchPoint);
	return true;
}

接下来到Hero.cpp中实现fire函数。

void Hero::fire(CCPoint touchPoint)
{
	Bullet * bullet = Bullet::create();
	//将子弹的开始位置设置为玩家所在的坐标位置
	bullet->setPosition(this->getPosition());
	//将子弹添加到主场景中
	this->getParent()->addChild(bullet);

	//设置动作
	CCRotateBy * rotateBy = CCRotateBy::create(3.0,360*4);
	//获得方向向量direction,指向手指的方向
	CCPoint direction = ccpSub(touchPoint,this->getPosition());

	//因为手指指向不同的地点的时候获得的方向向量大小不一样,离手指进的时候方向向量小,远的时候大
	//在子弹做移动的动作的时候会产生不同的效果,所以进行如下的设置,这种设置方法不是最好的,你有什么好方法请告诉我
	CCSize winSize = CCDirector::sharedDirector()->getWinSize();
	if(touchPoint.x<winSize.width/10)
	{
		direction = ccpMult(direction,15);
	}
	else if(touchPoint.x<winSize.width*2/3 && touchPoint.x>winSize.width/10)
	{
		direction = ccpMult(direction,4);
	}
	else
	{
		direction = ccpMult(direction,2);
	}

	//移动的动作
	CCMoveBy * moveBy = CCMoveBy::create(3.0,direction);
	CCSpawn * spawn = CCSpawn::create(rotateBy,moveBy,NULL);

	bullet->runAction(spawn);
}

现在运行程序看看效果吧,可以发射子弹了,但是子弹和怪物碰到后没反应,接下来我们就实现碰撞检测。在实现碰撞检测之前,我们总得需要将每个怪物和每个子弹做比较吧,方法是写在update函数中,但是我们需要先将所有的子弹放到容器中,然后拿出来做比较。这样,在子弹管理器中我们建立BulletManager.h和BulletManager.cpp文件,具体的实现如下。

#ifndef _BULLET_MANAGER_H_
#define _BULLET_MANAGER_H_
#include "cocos2d.h"

using namespace cocos2d;
//采用单例设计模式
class BulletManager
{
public:
	bool init();
	static BulletManager * sharedBulletManager();
	~BulletManager();
	//获得存储子弹的数组
	CCArray * getBulletArray(){return m_bulletArray;};
private:
	//存储子弹的数组
	CCArray * m_bulletArray;
	static BulletManager * m_bulletManager;
//构造函数私有,导致不能通过new获得类的实例
private:
	BulletManager(){init();};
};
#endif
#include "BulletManager.h"

//单例设计模式,采用以下的固定写法,记住就可以了
BulletManager * BulletManager::m_bulletManager = NULL;

BulletManager * BulletManager::sharedBulletManager()
{
	if(!m_bulletManager)
	{
		m_bulletManager = new BulletManager();
	}
	return m_bulletManager;
}

bool BulletManager::init()
{
	bool bRet = false;
	do
	{
		this->m_bulletArray = new CCArray();
		bRet = true;
	}
	while(0);

	return bRet;
}

BulletManager::~BulletManager()
{
	delete this->m_bulletArray;
	delete this->m_bulletManager;
}

接着在Hero的fire方法中当我们新建完子弹以后添加如下的代码。

BulletManager::sharedBulletManager()->getBulletArray()->addObject(bullet);

monster的update方法完善以后如下所示。

void Monster::update(float tm)
{
	//判断怪物是否超出了边界
	if(this->isOut())
	{
		//将怪物从父节点上移除
		this->removeFromParentAndCleanup(true);
	}
	CCObject * obj;
	//以下的语句将取出CCArray中的每个元素,取出来的时候事CCObject,所以要转化为我们要的类型
	CCARRAY_FOREACH(BulletManager::sharedBulletManager()->getBulletArray(),obj)
	{
		CCSprite * bullet = (CCSprite *)obj;
		//boundingBox()函数将返回一个CCRect的对象,它的intersectsRect方法用来判断与另一个CCRect有没有相交
		if(this->boundingBox().intersectsRect(bullet->boundingBox()))
		{
			//如果子弹和怪物相交就将子弹从数组中移除出去
			BulletManager::sharedBulletManager()->getBulletArray()->removeObject(bullet);
			//如果子弹和怪物相交就将怪物和子弹从主场景中移除
			this->removeFromParentAndCleanup(true);
			bullet->removeFromParentAndCleanup(true);
		}
	}
}

还有个问题就是当子弹移出屏幕以后也要从数组中去除出去。Bullet的update方法完善以后如下。

void Bullet::update(float tm)
{
	if(this->isOut())
	{
		//出界就从父节点中移除
		this->removeFromParentAndCleanup(true);
		//出界就从数组中移除出去
		BulletManager::sharedBulletManager()->getBulletArray()->removeObject(this);
	}
}

这样游戏的主要逻辑就完成了!运行起程序看看效果吧。

Cocos2d-x游戏实例之忍者飞镖