看了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,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,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); } } } }
以上全部写完,基本的画面就出来了,画面如下
好了 第一篇就先写到这里,后续会写第二篇。