本人最近幾個月在工作之余,都有斷斷續續地去學習cocos2dx的一些東西,在一些論壇上參考有關資料,源碼,比如www.9miao.com,泰然網等等,畢竟開源,而且較為有趣。
7月份離職后,希望換個方向做手游(我之前一直做的是JAVA,web,數據庫),發現沒有工作經驗真的是不好找....,筆試過了,面試總會問:“你有沒有什么作品”,一想想,平時都是做一些demo,或者是參閱別人的項目源碼,好像真的沒有完整的弄一樣東西出來。
為此,我准備做一個橫版的動作游戲(因為是第一次做,前后總共花了6天時間才基本完成)
在看官您閱讀以下內容時,我認為您是對cocos2x是有一些基礎了解的。
在做這個游戲之前,我們先來設想一下,我們游戲需要哪些對象,內容等等到這些東西呢?
1.在一個戰斗場景中,我們需要一副地圖作為基礎,所以你可能需要用到Tiled 這個工具,來制作我們的地圖(在此我會認為您對瓦片地圖是有了解的);
2.我們需要控制操作英雄(hero),怪物(AI)要來攻擊英雄,hero和AI的閑置,行走,戰斗都是需要不停地播放動畫的,所以你可能需要自己去找一些人物素材(這玩意還真是不好找),然后為了方便加載使用,你可能還需要將幀動畫序列打包,所以你很可能需要用到TexturePacker工具
3.英雄需要釋放各種技能,還需要能使用血瓶加血,魔瓶加藍,同時我們還需要左上角能夠顯示當前英雄的血量,藍量和等級等等信息,我們還需要一個搖桿來操控英雄,等等......麻雀雖小,東西也不能少啊.....,這些素材都需要自己去找,准備好了布局又是一個問題了,這里你可能需要用到CocosStudio的UI編輯器了
4.素材的處理:你找的素材不一定都能直接用,你很可能還得自己去用PotoShop進行加工....
好了,以上這些介紹東西,已經大概能支撐我們做一款橫版游戲最基礎的“磚石”了,當然還有cocos2dx了,不管你在XCode下還是在VS下,這是你各人的喜好了,這里就不介紹cocos2d的配置了,網上有很多這方面的帖子。
在此我們來整理一下,我們上面說到要用到的一些工具:
1.Tiled :用來做瓦片地圖的
2.TexturePacker :幀動畫打包的
3.CocosStudio:UI制作的
4.PotoShop:素材加工
在此,我先放張圖出來,看看開發完成之后的效果
這張圖就是最基本的效果了。
接下來,我們開動之前還需要做些預備工作,那就是設計啦.....,我們不能直接拿上素材就開搞。
//------------------------設計部分----------------------------
整個戰斗場景我們可以將他看成兩層:
一個是游戲層(GameLayer):這里包括英雄,怪物,地圖,已經地圖上的掉落,道具等等.....
一個是操作層(OptionLayer):這里包括操作搖桿,技能UI,物品UI,人物頭像屬性UI
然后我們需要考慮一下一些面向對象的東西,在這里,英雄跟怪物是有很多共性的.我們可以將他們看成是一類角色(Role).....(包括你可能需要加一些非敵對類的NPC等都可歸為這一類),怪物和英雄都可繼承自它,這樣我們能省很多事.....
我們最后還得考慮以下,各個對象直接的交互訪問問題....因為一個場景里的各個對象有時候需要相互訪問,那這個對象的實例怎么獲取比較方便呢,這里我們用一個單例來保存這些對象的指針或者引用(如果您對單例設計模式不了解的話,可以先去稍微了解一十幾分鍾)
好了,設計部分好像說的差不多了(再往下說就沒個模塊的細節設計了,這點我們后面結合代碼將),看起來是不是很簡單呢?(是的,一目了然,但是我們做的最多的工作永遠都是細節上的處理)
//---------------------地圖的處理-------------
我們將地圖圖片的可達區域和不可區域理解為地面(floor)和牆壁(wall)兩個Layer
這在后續控制英雄和怪物移動位置是能較好把握好范圍,畢竟,地圖的每個區域不應該都是可達的(具體的編輯細節可自己摸索和參考網上的資料,這里不再贅述);
總之你做好之后,在后續開發時肯定會用到它,以下是地圖的加載......
//初始化地圖 void GameDisplayLayer::initMapWithFile(const char * path){ CCTMXTiledMap *tileMap = CCTMXTiledMap::create(path); CCObject *pObject = NULL; //遍歷tmx地圖中的Layer CCARRAY_FOREACH(tileMap->getChildren(), pObject) { CCTMXLayer *child = (CCTMXLayer*)pObject; child->getTexture()->setAliasTexParameters(); } tileMap->setAnchorPoint(ccp(0,0)); tileMap->setPosition(ccp(0,0)); this->addChild(tileMap, 0); global->tmxTileMap = tileMap; }
加載完后,若我們想要知道對象要移動的點到底在不在floor這一區域內呢?以下是轉換和判斷位置代碼(這里我提前貼出代碼.....省的后面忘記)
//判斷位置是否在地圖可移動范圍內 bool Global::tileAllowMove(CCPoint MovePoint){ CCTMXLayer *floor = global->tmxTileMap->layerNamed("floor"); //計算當前touchpoint在地圖中的Gid CCPoint tileGid = Global::tilePosFromLocation(MovePoint); if(0 == floor->tileGIDAt(tileGid)){ //CCLog("current touchPoint tileGIDAt(?) is Empty "); return false; } return true; } //將CCPoint轉換成Gid位置 CCPoint Global::tilePosFromLocation(CCPoint MovePoint, CCTMXTiledMap *map) { if(NULL == map){ map = global->tmxTileMap; } // 移動點的屏幕坐標必須減去瓷磚地圖的坐標 - 因為地圖可能比屏幕大,很多時候地圖會隨着操作移動,地圖位置不一定在(0,0)點上了 CCPoint point = ccpSub(MovePoint, map->getPosition()); // 將得到坐標值轉換成整數 CCPoint pointGID = ccp(0,0); pointGID.x = (int) (point.x / map->getTileSize().width); pointGID.y = (int) ((map->getMapSize().height * map->getTileSize().height - point.y) / map->getTileSize().height); //CCLog("pointGID.x,%f,%f",pointGID.x,pointGID.y); return pointGID; }
//----------------------Role類設計--------------------------------------
Role類是怪物和英雄的基類,它定義了兩者所共有的屬性,方法等.......以下是代碼示例,Role.h的頭文件
注意:每個序列幀動畫Animation 需要在怪物和英雄的init()中給其初始化
//基礎角色類,主角和NPC都需要繼承它 class Role :public CCSprite { public: Role(void); ~Role(void); CC_SYNTHESIZE(std::string,Name,Name); //角色名稱 CC_SYNTHESIZE(CCAnimation*,idleAnimation,IdleAnimation); //角色空閑時序列 CC_SYNTHESIZE(CCAnimation*,movingAnimation,MovingAnimation); //角色移動時動畫幀序列 CC_SYNTHESIZE(CCAnimation*,attackAnimation,AttackAnimation); //角色普通攻擊時動畫幀序列 CC_SYNTHESIZE(CCAnimation*,attackAnimation_1,AttackAnimation_1); //角色特殊攻擊1動畫幀序列 CC_SYNTHESIZE(CCAnimation*,attackAnimation_2,AttackAnimation_2); //角色特殊攻擊2動畫幀序列 CC_SYNTHESIZE(CCAnimation*,attackAnimation_3,AttackAnimation_3); //角色特殊攻擊3動畫幀序列 CC_SYNTHESIZE(CCAnimation*,attackAnimation_4,AttackAnimation_4); //角色特殊攻擊4動畫幀序列 CC_SYNTHESIZE(CCAnimation*,hurtAnimation,HurtAnimation); //角色受傷時動畫幀序列 CC_SYNTHESIZE(CCAnimation*,deadAnimation,DeadAnimation); //角色死亡時動畫幀序列 CC_SYNTHESIZE(float,curtLifeValue,CurtLifeValue); //角色當前生命值 CC_SYNTHESIZE(float,sumLifeValue,SumLifeValue); //角色總體生命值 CC_SYNTHESIZE(float,attackStrenth,AttackStrenth); //角色當前攻擊力 CC_SYNTHESIZE(ActionState,actionState,ActionState); //當前Action狀態(據此狀態處理各個動畫之間的銜接問題) CC_SYNTHESIZE(Direction, roleDirection, RoleDirection); //角色朝向(分向左還是向右) CC_SYNTHESIZE(bool, allowMove, AllowMove); //角色是否允許移動,例如:攻擊,受傷等動畫執行期間不可移動 CC_SYNTHESIZE(CCPoint, _vector, Vector); //偏移量,AI自動移動時下一幀的偏移向量 void Role::callBackAction(CCNode* pSender); //動畫執行完畢的通用回調處理 //action methods virtual void RunIdleAction(); //執行閑置動畫 virtual void RunMovingAction(); //執行移動行走動畫 virtual void RunAttackAction(); //執行普通攻擊動畫 virtual void RunAttackAction_1(); //執行特殊攻擊1動畫 virtual void RunHurtAction(); //執行被攻擊后受傷動畫 //......死亡動畫等 };
//-------------下面我再給出動畫執行部分的代碼和回調----------------------- void Role::RunAttackAction() { if(this->getActionState() == ActionStateNone || this->getActionState() == ActionStateIdle || this->getActionState() == ActionStateMove){ this->setAllowMove(false); this->stopAllActions(); this->setActionState(ActionStateAttack); CCFiniteTimeAction *sequence = NULL; CCAnimate * AttackAnimate = CCAnimate::create(this->getAttackAnimation()); CCFiniteTimeAction * callFuncN = CCCallFuncN::create(this, callfuncN_selector(Role::callBackAction)); sequence = CCSequence::create(AttackAnimate, callFuncN,NULL); //最后加上NULL,否則報錯 this->runAction(sequence); } } //動畫執行結束后的通用回調,可在子類自己定義回調 void Role::callBackAction(CCNode* pSender){ if(pSender != 0) { this->setActionState(ActionStateNone); this->setAllowMove(true); //非行走類動畫結束角色可以移動 this->RunIdleAction(); //繼續執行空閑動畫 } }
//這里我再給出英雄(hero)類的初始化init()方法中幀動畫的初始動作,怪物類可依葫蘆畫瓢.......
bool Hero::init(){ //預加載精靈圖片 CCSpriteFrameCache::sharedSpriteFrameCache()->addSpriteFramesWithFile("MyImages/role_ac_monkey.plist"); //獲取紋理緩存 CCSpriteFrameCache *spriteFrameCache = CCSpriteFrameCache::sharedSpriteFrameCache(); Hero::initWithSpriteFrameName("39597-1.png"); int frameNum = 0; //取幾幀 int frameStart = 0;//起始number CCArray *actionArry = CCArray::createWithCapacity(20); CCSpriteFrame *spriteFrame = NULL; //------------------------------idle action 空閑---------------------- frameNum = 4; //取幾幀 frameStart = 1;//起始number for(int i=frameStart;i<frameNum;i++){ spriteFrame = spriteFrameCache->spriteFrameByName(CCString::createWithFormat("39597-%d.png",i)->getCString()); actionArry->addObject(spriteFrame); } CCAnimation* idleAnimation = CCAnimation::createWithSpriteFrames(actionArry,0.2f); actionArry->removeAllObjects(); idleAnimation->retain(); this->setIdleAnimation(idleAnimation);
//......還有行走,攻擊動畫等等初始化 //---------------------釋放array-------------------------------------- actionArry->release(); return true; }
//-----------------------搖桿控制英雄行走的設計---------------------
怪物和英雄都有同樣的屬性,血量,攻擊,狀態,是否允許移動等等,貌似做的事情還不少,為了能看到某種效果,我們先來看看如何通過觸摸搖桿來控制hero行走
為此我們設計一個專門的腰桿類來處理英雄的移動控制,搖桿的控制為此我做單章說明,請參考:http://www.cnblogs.com/zouly/p/3841830.html
//---------------------技能UI,及技能遮罩冷卻效果-------------------------
為了使本篇幅看起來不是太長,我還希望在介紹本功能點時能開單章說明技能UI的制作過程,冷卻遮罩處理,請參考:http://www.cnblogs.com/zouly/p/3842333.html
好了,為了使本文篇幅看起來不至於太長,我們第一部分就介紹到這里
另外,實現這個游戲參考了很多文章,記不住了,也不一一列舉了
http://pan.baidu.com/s/1jGh0wJk 這是源代碼,在vs2012 cocos2dx 2.2.2上運行,3.0~2.0版本的應該是都可以運行的