看了cocos2d有一段时间了,简单的小游戏还是可以写出来。这个过程主要还是看,而很少动手去写,这样本身水平很难提高。就好比看完设计模式,现在依旧不会用,时间一久,也忘记的差不多了。俗话说,纸上得来终觉浅,绝知此事要躬行。因此,决定写一个高仿发飞机的小游戏,虽然简单,但还是要坚持写完。
这个系列主要还是基于我想到哪就写到哪,不定期更新。
1.开始界面
首先,我们得找到微信打飞机的图片资源,直接到下面链接下载就可以了:
http://download.csdn.net/detail/q100036q/7524993
解压后:
资源图片准备就绪,我们就可以开始编码工作了,依旧是看图写代码,一步一步搭建。
开始画面很简单,一张背景图片再加一个animation,便构成了开始界面。
代码如下:
#ifndef _START_GAME_SCENE_H_
#define _START_GAME_SCENE_H_
#include "cocos2d.h"
USING_NS_CC;
class StartGameScene : public Layer
{
public:
static cocos2d::Scene* createScene();
virtual bool init();
CREATE_FUNC(StartGameScene);
void loadingHandler();
void loadingDone();
};
#endif
#include "StartGameScene.h"
#include "PlayGameScene.h"
Scene* StartGameScene::createScene()
{
// 'scene' is an autorelease object
auto scene = Scene::create();
// 'layer' is an autorelease object
auto layer = StartGameScene::create();
// add layer as a child to scene
scene->addChild(layer);
// return the scene
return scene;
}
// on "init" you need to initialize your instance
bool StartGameScene::init()
{
//
// 1. super init first
if ( !Layer::init() )
{
return false;
}
Size visibleSize = Director::getInstance()->getVisibleSize();
Vec2 origin = Director::getInstance()->getVisibleOrigin();
// add background image
auto sprite = Sprite::create("shoot_background/background.png");
// position the sprite on the center of the screen
sprite->setPosition(Vec2(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));
// add the sprite as a child to this layer
this->addChild(sprite, 0);
//加载页面
loadingHandler();
return true;
}
//loading handler
void StartGameScene::loadingHandler()
{
Size visibleSize = Director::getInstance()->getVisibleSize();
//start:开始界面的animation
auto loadingSp = Sprite::create();
loadingSp->setPosition(Point(visibleSize.width / 2, visibleSize.height / 2));
this->addChild(loadingSp, 1);
Vector<SpriteFrame*> framelist;
for(int i = 1; i <=4; i++)
{
SpriteFrame* sf = SpriteFrame::create(String::createWithFormat("shoot_background/game_loading%d.png",i)->_string, Rect(0,0,186,38));
framelist.pushBack(sf);
}
Animation* an = Animation::createWithSpriteFrames(framelist, 0.5f);
auto animate = Animate::create(an);
auto repeat = Repeat::create(animate, 3);//播放三次
//回调函数
auto callback = CallFunc::create(CC_CALLBACK_0(StartGameScene::loadingDone,this));
auto sequence = Sequence::create(repeat, callback, NULL);
loadingSp->runAction(sequence);
//end:开始界面的animation
}
//开始界面播放完,直接进入游戏场景
void StartGameScene::loadingDone()
{
Director::getInstance()->replaceScene(PlayGameScene::createScene());
}
显示效果如下:
2.背景移动
进入游戏后,要让背景图片动起来,并且无缝衔接。实际上,用两张背景图片就可以达到这种效果。当第一张移出屏幕的时候,立即放置到最上面,衔接第二张,达到无缝衔接的效果。主要代码如下:
//run the background
auto bg_1 = Sprite::create("shoot_background/background.png");
bg_1->setAnchorPoint(Point(0, 0 ));
bg_1->setPosition(Point(0, 0));
bg_1->setTag(BG_1);
this->addChild(bg_1, 0);
auto bg_2 = Sprite::create("shoot_background/background.png");
bg_2->setAnchorPoint(Point(0, 0 ));
bg_2->setPosition(Point(0, bg_1->getContentSize().height));
bg_2->setTag(BG_2);
this->addChild(bg_2, 0);
//run background image
this->schedule(schedule_selector(PlayGameScene::runBackground), 0.01f);
void PlayGameScene::runBackground(float dt)
{
auto visibleSize = Director::getInstance()->getVisibleSize();
auto bg_1 = this->getChildByTag(BG_1);
auto bg_2 = this->getChildByTag(BG_2);
int bg_height = bg_1->getContentSize().height;
bg_1->setPositionY(bg_1->getPositionY() - bg_speed);
bg_2->setPositionY(bg_2->getPositionY() - bg_speed);
//bg2移出屏幕后衔接在bg1后面
if(bg_2->getPositionY() <= 0)
{
bg_1->setPositionY(bg_2->getPositionY() + bg_height);
}
//bg1移出屏幕后衔接在bg2后面
if(bg_1->getPositionY() <= 0)
{
bg_2->setPositionY(bg_1->getPositionY() + bg_height);
}
}
3.加载主飞机
经过步骤2,背景也可以无缝移动了,把主飞机(hero_plane)放上去,并且注册他的触碰事件检测,也就是我们手指触碰主飞机的时候,飞机能够跟着我们手指移动而移动。这样的效果也很简单,在层中去注册touchevent事件处理。点击时如果是touch飞机,设置一个标志位,touchmove事件的时候,不断去更新飞机的位置,这样就可以达到效果了。
主要代码如下:
//add plane hero
Sprite* hero = Sprite::create("hero1.png");
PlaneHero* hero_plane = PlaneHero::create(hero);
hero_plane->setPosition(Point(visibleSize.width / 2, visibleSize.height / 2));
hero_plane->setTag(HERO_PLANE);
hero_plane->setAnchorPoint(Point(0,0));
this->addChild(hero_plane, 1);
//touch event handler
auto touchListener = EventListenerTouchOneByOne::create();
touchListener->setSwallowTouches(true);
touchListener->onTouchBegan = CC_CALLBACK_2(PlayGameScene::onTouchBegan, this);
touchListener->onTouchMoved = CC_CALLBACK_2(PlayGameScene::onTouchMoved, this);
touchListener->onTouchCancelled = CC_CALLBACK_2(PlayGameScene::onTouchEnded, this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(touchListener, this);
//Touch event handler
bool PlayGameScene::onTouchBegan(Touch *touch, Event* event)
{
Point touchPoint = touch->getLocation();
bTouchPlane = false;
auto hero_plane = this->getChildByTag(HERO_PLANE);
Point hero_pos = hero_plane->getPosition();
Size hero_size = hero_plane->getContentSize();
log("hero_pos x = %f, y = %f", hero_pos.x, hero_pos.y);
log("touchPoint x = %f, y = %f", touchPoint.x, touchPoint.y);
log("hero_size width = %f, height = %f", hero_size.width, hero_size.height);
if(touchPoint.x >= hero_pos.x && touchPoint.x <= (hero_pos.x + hero_size.width) && touchPoint.y >= hero_pos.y && touchPoint.y <= (touchPoint.y + hero_size.height))
{
//touch the plane, mark this flag for use
bTouchPlane = true;
}
return true;
}
void PlayGameScene::onTouchMoved(Touch *touch, Event* event)
{
if(bTouchPlane == true)
{
//move the hero_pane with out touch on screen
auto hero_plane = this->getChildByTag(HERO_PLANE);
Point pos = touch->getLocation();
hero_plane->setPosition(pos);
}
}
void PlayGameScene::onTouchEnded(Touch *touch, Event* event)
{
bTouchPlane = false;
}
4.子弹管理器
首先,我们写一个基类,用来绑定一个精灵,所有的飞机,子弹都会继承他。
#ifndef _ENTITY_H_
#define _ENTITY_H_
#include "cocos2d.h"
USING_NS_CC;
class Entity : public Node
{
public:
Entity();
~Entity();
void bindSprite(Sprite* sprite);
Sprite* getSprite();
private:
Sprite* m_sprite;
};
#endif
#include "Entity.h"
Entity::Entity()
{
m_sprite = NULL;
}
Entity::~Entity()
{}
void Entity::bindSprite(Sprite* sprite)
{
if(this->m_sprite != NULL)
{
m_sprite->removeFromParentAndCleanup(true);
}
this->m_sprite = sprite;
this->addChild(m_sprite);
Size size = m_sprite->getContentSize();
this->setContentSize(size);
}
Sprite* Entity::getSprite()
{
return this->m_sprite;
}
接着,写一个子弹的基类,继承自Entity,他包含子弹的共有属性。不同的子弹类型都会继承自这个子弹基类。方便扩展。
#ifndef _BULLETBASE_BASE_H_
#define _BULLETBASE_BASE_H_
#include "Entity.h"
#define SPEED_DEFAULT 10
#define SPEED_NORMAL 5
class BulletBase : public Entity
{
public:
BulletBase();
~BulletBase();
//子弹是否在使用中
void setUsed(bool isUsed);
bool isUsed();
bool isArrive();
//设置子弹速度
void setSpeed(int speed);
int getSpeed();
//是否飞出屏幕
bool isMoveOutScreen();
void setIsMoveOutScreen(bool outScreen);
//是否加进层里面
//bool isAddInLayer();
//void setIsAddInLayer(bool inLayer);
protected:
bool m_isArrive;
int m_speed;
private:
bool m_isUsed;
bool m_isOutScreen;
//bool m_isAddInLayer;
};
#endif
#include "BulletBase.h"
BulletBase::BulletBase()
{
m_isUsed = false;
m_isArrive = false;
m_speed = SPEED_NORMAL;
m_isOutScreen = false;
//m_isAddInLayer = false;
}
BulletBase::~BulletBase()
{
}
void BulletBase::setUsed(bool isUsed)
{
this->m_isUsed = isUsed;
}
bool BulletBase::isUsed()
{
return this->m_isUsed;
}
bool BulletBase::isArrive()
{
return this->m_isArrive;
}
//设置子弹速度
void BulletBase::setSpeed(int speed)
{
this->m_speed = speed;
}
int BulletBase::getSpeed()
{
return this->m_speed;
}
//是否已经飞出屏幕
bool BulletBase::isMoveOutScreen()
{
return this->m_isOutScreen;
}
void BulletBase::setIsMoveOutScreen(bool outScreen)
{
this->m_isOutScreen = outScreen;
}
#if 0
//是否加进层里面
bool BulletBase::isAddInLayer()
{
return this->m_isAddInLayer;
}
void BulletBase::setIsAddInLayer(bool inLayer)
{
this->m_isAddInLayer = inLayer;
}
#endif
紧接着,写一个普通类型的子弹,在这里其实主要是写入子弹的速度。不同类型的子弹拥有不同的属性值,我们先简单的设置子弹的速度就可以。以后需要扩展直接对这个类进行。
#ifndef _BULLET_NORMAL_H_
#define _BULLET_NORMAL_H_
#include "BulletBase.h"
class BulletNormal : public BulletBase
{
public:
BulletNormal();
~BulletNormal();
CREATE_FUNC(BulletNormal);
virtual bool init();
static BulletNormal* create(Sprite* sprite);
bool init(Sprite* sprite);
private:
void moveEnd();
};
#endif
#include "BulletNormal.h"
BulletNormal::BulletNormal()
{
m_speed = SPEED_NORMAL;
}
BulletNormal::~BulletNormal()
{}
bool BulletNormal::init()
{
return true;
}
BulletNormal* BulletNormal::create(Sprite* sprite)
{
BulletNormal* bNor = new BulletNormal();
if(bNor && bNor->init(sprite))
{
bNor->autorelease();
}
else
{
CC_SAFE_DELETE(bNor);
}
return bNor;
}
bool BulletNormal::init(Sprite* sprite)
{
bool ret = false;
bindSprite(sprite);
ret = true;
return ret;
}
void BulletNormal::moveEnd()
{
m_isArrive = true;
}
最后,创建子弹管理器,生成子弹,保存到一个列表里面,需要的时候直接取出。同时,子弹的飞行也在管理器中做处理,不断检测子弹并且设置子弹的运行。
#ifndef _BULLET_MANAGER_H_
#define _BULLET_MANAGER_H_
#include "cocos2d.h"
USING_NS_CC;
#define BULLET_MAX_CACHE_NUM 20 //子弹缓存数量
class BulletBase;
class BulletManager : public Node
{
public:
BulletManager();
~BulletManager();
static BulletManager* create();
bool init();
//获取未用的子弹
BulletBase* getUnusedBullet();
private:
Vector<BulletBase*> m_bulletList;//子弹列表,保存子弹
void createBullets();//创建缓存子弹
void bulletLogicCheck(float dt);//子弹逻辑
};
#endif
#include "BulletManager.h"
#include "BulletBase.h"
#include "BulletNormal.h"
BulletManager::BulletManager()
{}
BulletManager::~BulletManager()
{}
BulletManager* BulletManager::create()
{
BulletManager* bulletMgr = new BulletManager();
if(bulletMgr && bulletMgr->init())
{
bulletMgr->autorelease();
}
else
{
CC_SAFE_DELETE(bulletMgr);
}
return bulletMgr;
}
bool BulletManager::init()
{
//创建子弹列表
createBullets();
log("m_bulletList size() = %d", m_bulletList.size());
//循环检测子弹列表
this->schedule(schedule_selector(BulletManager::bulletLogicCheck));
return true;
}
//获取未用的子弹
BulletBase* BulletManager::getUnusedBullet()
{
log("m_bulletList size() = %d", m_bulletList.size());
for(int i = 0; i < m_bulletList.size(); i++)
{
BulletBase* bullet = m_bulletList.at(i);
if(bullet->isUsed() == false)
{
bullet->setUsed(true);
bullet->setIsMoveOutScreen(false);
return bullet;
}
}
#if 0 //i don't know why
for(auto bullet : m_bulletList)
{
if(bullet->isUsed() == false)
{
bullet->setUsed(true);
bullet->setIsMoveOutScreen(false);
return bullet;
}
}
#endif
log("no bullet unused");
return NULL;
}
//创建子弹缓存
void BulletManager::createBullets()
{
BulletBase* bullet = NULL;
for(int i = 0; i < BULLET_MAX_CACHE_NUM; i++)
{
auto bullet_sprite = Sprite::create("bullet1.png");
bullet = BulletNormal::create(bullet_sprite);
bullet->setUsed(false);
bullet->setIsMoveOutScreen(false);
m_bulletList.pushBack(bullet);
this->addChild(bullet);
}
log("m_bulletList size() = %d", m_bulletList.size());
}
//检测子弹
void BulletManager::bulletLogicCheck(float dt)
{
auto visibleSize = Director::getInstance()->getVisibleSize();
for(auto bullet : m_bulletList)
{
if(bullet->isUsed() == true)
{
//子弹运行
int posY = bullet->getPositionY();
posY += SPEED_DEFAULT;
bullet->setPositionY(posY);
//out of screen
if(posY >= visibleSize.height)
{
bullet->setIsMoveOutScreen(true);
bullet->setUsed(false);
}
}
}
}
以上全部写完,基本的画面就出来了,画面如下
好了 第一篇就先写到这里,后续会写第二篇。