前言
回想去年,也是在三、四月時開發出自己的第一款遊戲,正是這款遊戲,令我決定踏上獨立開發人員之路。一年過去了,第一款遊戲達到它應有的盈利水平。然而這款遊戲開發後的時間裡,都沒再取得另一款令自己滿意的作品。直到今年的三、四月,我使用cocos2d-x開發出我的第一款文字遊戲。
第一款遊戲的傳送門
第一款文字遊戲的傳送門
自從使用cocos2d-x後,發現自己愛上了這個引擎。它或許還不夠強大和完善,但是使用它,可以體驗編碼的樂趣,還可以為我重拾C++這門技術(到底對C++有多麼的執著啊),還能增進NDK和JNI的學習。恰恰滿足我各種各樣的追求。
這個第一款文字遊戲,名字我取作Anagram Puzzle,其實這款遊戲是參照RayWenderlich上的這篇教程來改寫的,教程裡使用的是iOS的UIKit編寫,雖說原理相通,但是在改寫過程中還是遇到不少折騰的地方。由於初次編寫cocos2d-x遊戲,所以錯誤難免百出,希望閱讀者們高抬貴手,點到即止……廢話不多說,馬上開始coco2d-x之道~如何製作第一款文字遊戲!
Anagram簡介
Anagram是一種把單詞或短語的字母順序打亂,重新排列後變成一個新單詞或短語的遊戲。例如,單詞cinema可以重新排列成iceman。遊戲中就要求玩家進行你所提供單詞或短語的重新排列。完成遊戲的畫面會:
在開發這個遊戲過程中,會接觸到以下這些知識:
- MVC型的遊戲結構
- 如何從檔案配置載入層級
- 載入第三方字型
- 簡單使用音樂音效
- 分離HUD層與遊戲層
- 手勢拖動及動畫
- Particle的效果
還有其他的一些cocos2d-x的基礎知識,都會在開發過程中接觸到。
初始化工程
首先最重要的當然是使用命令列來建立cocos2d-x工程,當然也有其他方法也可以建立工程,但是我認為掌握命令列來建立是必須的基本功。建立方法可以在這裡找到。建立完成後各個平台的工程檔案夾都有了。我們的主要工程檔案夾是proj.android和proj.ios兩個。整個開發過程,我使用Mac OS來開發,所以編碼是在XCode上進行,而Android工程的編譯則使用命令列,詳細教程可以參考這裡。
建立好工程後,先把所需的資源檔拷貝到Resource檔案夾中。開啟Xcode工程,目前Resource檔案夾下還是原來的資源檔,通過右擊Resource檔案夾 -> Add Files to ...,把資源檔都加到工程裡。編輯後工程會
1)載入層級設定檔
開啟level1.plist,可以看到裡面內容
有三個最頂端的key,分別是:
pointsPerTile: 每個單詞填對後獲得的分數。
timeToSolve: 解決這一關的時間(秒)。
anagrams: 是題目的列表,包含兩個item,分別是原始的短語以及最後要拼出的短語。
level檔案的介紹就到此為止,下面開始編寫Level類,在Level.h中添加以下內容
class Level:public CCObject{public:static Level * levelWithNum(int levelNum);public:int mPointPerTile;int mTimeToSovle;CCArray * pAnagrams;};
其中,三個變數對應level檔案裡的三個最頂端的item。還有一個初始化函數,是給外部調用初始化level檔案。
現在,開啟Level.cpp,實現levelWithNum函數
Level * Level::levelWithNum(int levelNum){char fileName[50];char fullPath[150];sprintf(fileName,"level%d.plist",levelNum);CCFileUtils::sharedFileUtils()->fullPathFromRelativeFile(fileName,fullPath);CCDictionary * pListDict = CCDictionary::createWithContentsOfFile(fileName);if(pListDict == NULL){CCLog("level config not found");}Level * l = new Level();CCString * tempStr;tempStr = dynamic_cast<CCString*>(pListDict->objectForKey("pointsPerTile"));l->mPointPerTile = tempStr->intValue();tempStr = dynamic_cast<CCString*>(pListDict->objectForKey("timeToSolve"));l->mTimeToSovle = tempStr->intValue();l->pAnagrams = dynamic_cast<CCArray*>(pListDict->objectForKey("anagrams"));l->pAnagrams->retain();return l;}
這裡首先用CCDictionary讀出level檔案中數值,然後就是讀取出對應key裡的值,並儲存起來。
現在,開啟主介面檔案,預設是HelloWorldScene類,但是我改寫成MainScene,其中MainScene.h是這樣的
class MainScene : public cocos2d::CCLayer{public: ~MainScene(); // Here's a difference. Method 'init' in cocos2d-x returns bool, instead of returning 'id' in cocos2d-iphone virtual bool init(); // there's no 'id' in cpp, so we recommend returning the class instance pointer static cocos2d::CCScene* scene(); // implement the "static node()" method manually CREATE_FUNC(MainScene);private:Level * pLevel;};
可見,增加了Level變數。在MainScene.cpp中的init函數,編寫
pLevel = Level::levelWithNum(1);
當然,這裡可以通過CCLog來列印出pLevel裡的內容來看看,會得到
在MainScene裡添加個新的函數
void dealRandomAnagram();
具體實現要這樣
void MainScene::dealRandomAnagram(){Common::random(0, pLevel->pAnagrams->count() - 1);int randomIndex = Common::random(0, pLevel->pAnagrams->count() - 1);CCAssert((randomIndex >= 0 && randomIndex < pLevel->pAnagrams->count()),"error random index!");CCArray * anagram = (CCArray*)pLevel->pAnagrams->objectAtIndex(randomIndex);CCString * ana1 = (CCString*)anagram->objectAtIndex(0);CCString * ana2 = (CCString*)anagram->objectAtIndex(1);int ana1len = ana1->length();int ana2len = ana2->length();}
Common::random是我自己編寫的,產生兩個數值之間的隨機數
int Common::random(int s,int e){float i = CCRANDOM_0_1()*(e-s+1)+s;return (int)i;}這樣就把初始狀態的短語和最終狀態的短語擷取到了,把dealRandomAnagram函數添加到MainScene的init函數裡,
pLevel = Level::levelWithNum(1);dealRandomAnagram();
2)建立單詞的View
在工程中新增一個繼承於CCNode的類,名字叫做TileView,在TileView.h中添加以下代碼
public:static TileView * initWithLetter(const char * l,float sideLen);private:CCSprite * pSprite;char mLetter;bool mIsMatch;
其中initWithLetter是初始化函數,pSprite是顯示的精靈,mLetter是對應的字母,mIsMatch表示結果是否已經配對上(就是找到字母所應該在的位置)。
在TileView.cpp裡,添加以下代碼
#include "TileView.h"TileView * TileView::initWithLetter(const char * l, float sideLen){TileView * tile = new TileView();CCSprite * bg = CCSprite::create("tile.png");tile->addChild(bg);tile->pSprite = bg;float scale = sideLen / bg->getContentSize().width;bg->setScale(scale);char chLetter[2];sprintf(chLetter,"%c",l[0] - 32);CCLabelTTF * letter = CCLabelTTF::create(chLetter,"Arial",75 * scale);letter->setColor(ccWHITE);tile->addChild(letter);tile->mIsMatch = false;tile->mLetter = chLetter[0];return tile;}
函數中,首先的是建立一個以tile.png為圖案的精靈,然後建立圖案上的字母
接下來就要在介面中顯示出來了,在MainScene中添加
private:Level * pLevel;CCArray * pTiles;CCArray * pTargets;
pTiles是TileView的數組,pTargets是TargetView的數組,其中TileView是放在底部給出的短語的各個單詞,TargetView是目標短語的各個單詞。在MainScene.cpp的dealRandomAnagram函數中繼續添加代碼
int ana1len = ana1->length();int ana2len = ana2->length();float tileSide = ceilf( Common::getCameraWith()*0.9 / (float)std::max(ana1len, ana2len) ) - kTileMargin;float xOffset = (Common::getCameraWith() - std::max(ana1len,ana2len) * (tileSide + kTileMargin)) / 2;xOffset += tileSide/2;
此時,開始計算各個TileView的位置。首先,比較得出原始短語和目標短語中長度最長的,然後算出各個View所需的寬度tileSide,以及各個View之間的間隔xOffset
對了,不要忘了定義全域的空隙
#define kTileMargin 20
接著,就要建立我們的TileView了
pTiles = CCArray::createWithCapacity(ana1len);const char * ana1Letter = ana1->getCString();for(int i = 0;i < ana1len; i++){char letter[3];sprintf(letter,"%c",ana1Letter[i]);if(letter[0] != ' '){TileView * tile = TileView::initWithLetter(letter,tileSide);tile->setPosition(ccp(xOffset + i * (tileSide + kTileMargin),Common::getCameraHeight() / 4));this->addChild(tile);pTiles->addObject(tile);}}pTiles->retain();
建立方法比較簡單,但是要注意的是,原始短語中可以會有Null 字元,Null 字元的地方需要留空。
3)單詞View最佳化
方方正正的TileView看著有些拘謹,下面進行一些最佳化來讓它們生動一些。在TileView中添加randomize函數
void TileView::randomize(){float rotation = Common::random(0,50) /(float)100 - 0.2;this->setRotation(rotation * 10);int yOffset = Common::random(0,10);this->setPositionY(this->getPositionY() + yOffset);}
讓TileView稍作旋轉和位移,然後在MainScene的dealRandomAnagram函數中的 this->addChild(tile); 語句後添加下面語句
tile->randomize();
4)添加TargetView
有了原始短語,下面就要開始建立目標短語的View了。對比TileView,TargetView要相對簡單一些。因為它是固定位置及不需要顯示字母。TargetView.h中添加以下代碼:
class TargetView : public CCNode{public:TargetView(void);~TargetView(void);static TargetView * initWithLetter(const char * l,float sideLen);private:CCSprite * pSprite;char mLetter;bool mIsMatch;};
與TileView相類似的,一個初始化函數,三個私人變數。與TileView的是一一對應。
TargetView * TargetView::initWithLetter(const char * l, float sideLen){TargetView * tile = new TargetView();CCSprite * bg = CCSprite::create("slot.png");tile->addChild(bg);tile->pSprite = bg;float scale = sideLen / bg->getContentSize().width;bg->setScale(scale);char chLetter[2];sprintf(chLetter,"%c",l[0] - 32);/*CCLabelTTF * letter = CCLabelTTF::create(chLetter,"Arial",78 * scale);letter->setColor(ccWHITE);tile->addChild(letter);*/tile->mIsMatch = false;tile->mLetter = chLetter[0];return tile;}
TargetView的initWithLetter函數中,注釋的語句是為顯示一下結果,但是在實際遊戲中是不顯示TargetView上的字母。
接下來,就要把TargetView顯示到情境上了,找到MainScene的dealRandomAnagram方法,在末尾處添加上以下代碼
pTargets = CCArray::createWithCapacity(ana2len);const char * ana2Letter = ana2->getCString();for(int i = 0;i < ana2len; i++){char letter[3];sprintf(letter,"%c",ana2Letter[i]);if(letter[0] != ' '){TargetView * target = TargetView::initWithLetter(letter,tileSide);target->setPosition(ccp(xOffset + i * (tileSide + kTileMargin),Common::getCameraHeight() / 4 * 3));this->addChild(target);pTargets->addObject(target);}}pTargets->retain();
是否可看到TargetView也出來,這裡,我們就完成了AnagramPuzzle的第一部分開發工作,最後上個
感覺還不錯吧,輕鬆的把所需要的遊戲介面展示出來。這次接觸到cocos2d-x知識還是比較少,主要是怎樣往主情境中新增內容,怎樣建立精靈。下一次,我們就要編寫一些有點挑戰性的東西了,比如怎麼拖動精靈,怎麼判斷是否擺在正確的位置,怎麼進行倒計時等等,真正的Code樂趣快要上場!請聽下回分解。