這是我在博客園的第一篇教程,因為本人也僅僅是初二學生,在很多方面都比不上大哥大姐們,這篇教程如果有不足的地方也請大家多多指出,希望可以幫助到一些新手。
通過這系列教程,你將可以制作出一個類似於魔塔的游戲。如果曾經玩過這個Flash小游戲,那么當然最好,如果你沒有玩過,也不用擔心,你可以點擊這里來玩這個游戲,以便於了解我們接下來該制作的游戲的大致效果。
教程圖片:
你可以在GitHub上面下載到這個項目的完整源代碼:https://github.com/hxy060799/MagicTower/
這個教程將會分為三個部分,因為時間有限,所以不能一次性把所有的三篇教程一起寫完,希望大家可以諒解。
在這個教程中,我們每一個部分都將實現不同的目標:
·在第一篇教程中,你將會學會如何用Cocos2d畫出一個二維地圖,並且如何實現地圖中每一個方塊的動畫。
·在第二篇教程中,你將會學會如何添加一個玩家方塊以及如何讓玩家和怪物進行戰斗並且拾取一些裝備。
·在第三篇教程中,你將會學會如何在游戲中添加劇情並且實現物品的交易等功能。
從這里開始
首先,你應該已經在你的xCode中安裝好了Cocos2d模板。這篇教程不應該是你學習Cocos2d的入門教程,而應該是略帶提高的教程。
那么我們開始吧,打開xCode,新建一個項目,選擇Cocos2d iOS模板,這個應用中我們不需要物理引擎。我們把這個項目命名為MagicTowerTutorial,平台選擇iPhone。
創建好了項目,嘗試運行,可以看到默認的模板所帶有的HelloWorld界面。
為了方便辨識,我們需要把模板創建的HelloWorldLayer類的名稱改為GameLayer,使用xCode的查找面板可以很容易地完成這個操作。
完成以后,手動把HelloWorldLayer.h和HelloWorldLayer.m重命名為GameLayer.h和GameLayer.m。這時應該可以編譯通過了。
制作這個游戲我們需要一些圖片和資源,你們很幸運。因為我已經將需要的資源都已經打包准備好了。請從這里下載資源包。
下載好資源包,我們首先先理解里面的資源都是做什么用的:
·MagicTowerArt:里面包含游戲中所用到的全部圖片,它們已經用TexturePacker包裝好了。
·Documents:從原版魔塔里面提取出來的怪物信息、物品信息報表、劇情等信息,在程序中不會被使用到,但是在開發過程中將會基於一定的幫助。
·buttons:程序中用到的按鈕
·OtherResources:包括地圖信息、方塊信息、商店信息、對話信息等。
·因為制作這個游戲的時候時間比較趕,所以游戲中沒有使用音頻。
我們首先添加一個背景圖片,將資源包中MagicTowerArt/background/gameBackground.png添加到工程里面,其他資源先丟在一邊。
接下來,要修改模板創建的代碼。將GameLayer.m的init中的代碼全部刪掉,用下面的代碼替換。
-(id) init { if(self=[super init]){ //添加背景 CCSprite *backgroundSprite=[[CCSprite alloc]initWithFile:@"gameBackground.png"]; backgroundSprite.anchorPoint=ccp(0,0); [self addChild:backgroundSprite z:0]; [backgroundSprite release]; } return self; }
嘗試運行,你可以看到一個紫色的背景。
准備地圖數據
接下來,我們就需要對地圖進行制作。魔塔游戲一共有21層,加上一個序章,一共有22層,再補上對所有地圖方塊進行測試的層,應該有23層。每一層都是由11*11的正方形方塊組成。我們可以借助一個三維數組表示這樣的地圖,而每一個點的方塊可以用int來表示,每個數字都表示一種方塊類型,大家可以通過下面的圖片來理解。
通過Plist可以記錄這樣的地圖信息。我已經把相關的地圖信息全部記錄在了資源包OtherResources中的MapInformation.plist里面的LevelMap數組里面了。
把這個plist文件加入到工程里面,然后我們需要將這個plist中的內容讀取到一個NSMutableArray中,通過下面的代碼可以完成
在GameLayer.m的上面加入
@interface GameLayer(){ //游戲地圖,三位數組[樓層,y,x](x<11,y<11) NSMutableArray *gameMap; } @end
因為這個地圖數據對象不需要被別的類訪問,所以定義為一個私有對象就可以了。
我們可以通過下面的代碼讀取一個Plist文件中的數據:
-(NSMutableDictionary*)readPlistWithPlistName:(NSString*)plistName{ NSString *error=nil; NSPropertyListFormat format; NSMutableDictionary *dict=nil; NSString *filePath=[[NSBundle mainBundle] pathForResource:plistName ofType:@"plist"]; NSData *plistXML=[[NSFileManager defaultManager]contentsAtPath:filePath]; dict=(NSMutableDictionary*)[NSPropertyListSerialization propertyListFromData:plistXML mutabilityOption:NSPropertyListMutableContainersAndLeaves format:&format errorDescription:&error]; return dict; }
嘗試在init中加入:
gameMap=[[NSMutableArray alloc]initWithArray:[[self readPlistWithPlistName:@"MapInformation"]objectForKey:@"LevelMap"]]; NSLog(@"%@",gameMap);
在dealloc中加入:
[gameMap release];
運行后便可以看到程序輸出了帶有大量int數據的三位數組,這個便是我們的地圖數據。
對於獲取第Z層第Y行第X列的方塊,我們可以用下面的代碼來獲取:
[[[gameMap objectAtIndex:z]objectAtIndex:y]objectAtIndex:x];
因為這里的地圖數據是用int表示的,所以光知道每個位置的方塊上的int還不能夠畫出地圖,還必須知道這個int所對應的方塊的圖片等信息,所以我有准備了一個plist,叫做blockInformation.plist和mapInformation.plist在同一位置,用於表示每個方塊數字對應的相關信息。
將這個plist也加入到工程中,以一維數組的方式記錄每個方塊的信息,里面主要包括這樣一些信息:
·ID:用於表示這個方塊的名稱,以方便在編輯這個信息表的時候進行區分,在程序中不會被用到。
·Type:方塊的類型(物品、牆壁、怪物還是空氣),它在后面用來決定玩家是要撿起它、被它擋住、走上前攻打它還是走上前。
·UsingImage:方塊對應的圖像
·AnimationID:方塊對應的動畫的標識,之后會被用到。
之后,我們也需要將方塊信息准備到程序中,在程序中加入:
//在gameMap聲明下方加入 //用於儲存各種方塊的信息 NSMutableArray *blocksInformation; //在init中加入 blocksInformation=[[NSMutableArray alloc]initWithArray:[[self readPlistWithPlistName:@"BlockInformation"]objectForKey:@"BlockInformation"]]; NSLog(@"%@",blocksInformation); //在Dealloc中加入 [blocksInformation release];
到此為止,地圖數據就已經准備完畢了,接下來就可以嘗試把地圖畫出來了。
用精靈(CCSprite)畫出地圖
盡管游戲共有11層,但是我們只需要11*11=121個精靈(CCSprite)來畫出這樣的地圖。如果樓層切換掉,則對這些進行修改來匹配新的方塊。
所以,我們需要將這些精靈管理在一個二維數組里面,所以:
//在blocksInformation聲明下方加入 //地圖是通過11*11=121個精靈拼湊而成的,這是個二維數組 NSMutableArray *mapSprites; //在Dealloc中加入 [mapSprites release];
這里需要注明的是,如果要獲取Y行X列的精靈,則需要通過下方的代碼來獲取。
[[mapSpritesobjectAtIndex:Y]objectAtIndex:X];
我們還需要一個方法來初始化所有的sprites:
-(void)initMapSprites{ mapSprites=[[NSMutableArray alloc]init]; for(int i=0;i<11;i++){ NSMutableArray *rowSprites=[[NSMutableArray alloc]init]; for(int j=0;j<11;j++){ CCSprite *mapBlockSprite=[[[CCSprite alloc]init]autorelease]; mapBlockSprite.anchorPoint=ccp(0,0); mapBlockSprite.position=ccp(103+25*j,23+25*i); [self addChild:mapBlockSprite z:2]; [rowSprites addObject:mapBlockSprite]; } [mapSprites addObject:rowSprites]; } }
上面的代碼不難理解,我們將這些精靈初始化,並且按順序擺放好。這里每個精靈的長寬也就是方塊的長寬都是25px,為了讓地圖顯示在正中間,所以將它的起始x加上了103px,y加上了23px。需要注意的Cocos2d坐標原點在左下角,和UI的左上角原點位置不同。在init中調用這個方法。
//加載sprites [self initMapSprites];
編譯運行,除了背景什么都看不到,因為所有的精靈對應的texture(圖像)還是空的,但精靈確實已經被加載出來了。
下面,我們就真正讓這些方塊以圖像的方式顯示出來。在資源包里面,我已經准備好了所有需要使用的圖片。把block_items.plist、block_items.png、block_monster.plist、block_monster.png、block_world.plist、block_world.png、block_person.plist、block_person.png、block_doors.plist、block_doors.png全部加入到工程當中。他們都是由texture packer打包好的圖像。
因為這些圖像使用的是SpriteFrames的形式,所以使用之前必須先緩加載這些圖像。(如果看過其他cocos2d教程應該可以理解)。在init中加入:
//加載圖像 [[CCSpriteFrameCache sharedSpriteFrameCache]addSpriteFramesWithFile:@"block_world.plist"]; [[CCSpriteFrameCache sharedSpriteFrameCache]addSpriteFramesWithFile:@"block_monster.plist"]; [[CCSpriteFrameCache sharedSpriteFrameCache]addSpriteFramesWithFile:@"block_items.plist"]; [[CCSpriteFrameCache sharedSpriteFrameCache]addSpriteFramesWithFile:@"block_doors.plist"]; [[CCSpriteFrameCache sharedSpriteFrameCache]addSpriteFramesWithFile:@"block_person.plist"];
然后,再加入用來加載對應圖片的方法:
-(void)loadSpritesWithFloor:(int)floor{ for(int i=0;i<11;i++){ for(int j=0;j<11;j++){ [self reloadMapBlockWithX:j Y:i Floor:floor]; } } } -(void)reloadMapBlockWithX:(int)bX Y:(int)bY Floor:(int)floor{ CCSprite *spriteToReload=[[mapSprites objectAtIndex:bY]objectAtIndex:bX]; int thisBlock=[self blockAtFloor:floor X:bX Y:bY]; NSDictionary *currentBlockInformation=[blocksInformation objectAtIndex:thisBlock]; NSString *fileName=[currentBlockInformation valueForKey:@"UsingImage"]; CCSpriteFrame* myFrame = [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:fileName]; [spriteToReload setDisplayFrame:myFrame]; } -(int)blockAtFloor:(int)floor X:(int)x Y:(int)y{ return [[[[gameMap objectAtIndex:floor]objectAtIndex:y]objectAtIndex:x]intValue]%100; }
直接看這三個方法,估計很多人會看不懂,這里作解釋。第一個方法中,進行了一個遍歷,對每一個方塊都使用第二個方法加載圖片,第二個方法中,thisBlock表示這個方塊的int值,再使用這個int值調出之前准備好的blocksInformations中的“UsingImage”信息,即使用的圖片,然后加載這個圖片並且讓精靈套用這個圖片。第三個方法用於返回指定樓層、行、列的方塊數值(上面已經說過),后面的和100取余是因為后面為了寫劇情模塊的需要而在部分的兩位數方塊數字前面加上了一個百位數用來做附加值,這里可以不用去糾結它,后面會講到。
注意一下,很多人會在這部分的實現方法上首先考慮在切換樓層的時候對所有精靈進行銷毀然后重新加載一批,這樣不僅浪費資源,而且很容易造成內存泄漏。所以,如果碰到類似這樣的大量精靈需要加載,應該把精靈的alloc init 和圖像的設置分開來寫。
激動人心的時刻到了,再init中加入:
[self loadSpritesWithFloor:0];
然后運行程序,第一層地圖已經被畫出來了。
我們需要兩個按鈕來進行樓層的上下切換,所以把資源包中的buttons中的文件里面的按鈕加入到工程中,然后加入如下代碼:
//在聲明部分中加入 //當前樓層 int currentFloor; //在init中加入 currentFloor=0; //添加按鈕 CCMenuItem *plusButton=[CCMenuItemImage itemWithNormalImage:@"ButtonPlus.png" selectedImage:@"ButtonPlusSel.png" block:^(id sender){ currentFloor+=1;[self loadSpritesWithFloor:currentFloor]; }]; plusButton.anchorPoint=ccp(0,0); CCMenuItem *minusButton=[CCMenuItemImage itemWithNormalImage:@"ButtonMinus.png" selectedImage:@"ButtonMinusSel.png" block:^(id sender){ currentFloor-=1;[self loadSpritesWithFloor:currentFloor]; }]; minusButton.anchorPoint=ccp(0,0); minusButton.position=ccp(0,35); CCMenu *buttonMenu=[CCMenu menuWithItems:plusButton,minusButton,nil]; buttonMenu.anchorPoint=ccp(0,0); [buttonMenu setPosition:ccp(0,0)]; [self addChild:buttonMenu z:100];
再次編譯運行,左下角多了用於控制樓層的按鈕。原理很簡單,當按鈕被點按的時候,對當前樓層進行操作,然后通過當前樓層重新加載這些sprites(下圖是第三層)。
何去何從
今天的教程就先講到這里吧,接下來的第一部分下將會告訴大家如何給這些sprites加入動畫。希望大家在看完這個教程之后有所收獲。我也快困死了,白天剛剛開完運動會六點多起床晚上在這里寫教程寫到接近十點半,也真心希望大家能夠喜歡這份教程。如果需要的話,可以在這里下載到這個教程到目前為止的工程。
這是我的第一篇文章。真心希望大家能夠支持並且能夠從中學到一些什么。
你可以點擊這里繼續下一篇的教程。
不見不散。