植物大戰僵尸一直是一個很受歡迎的經典的小游戲,我主要用cocos2d-android做了一個類似的小demo,在這里主要介紹一下我做給這個小demo。
### 開發前各種準備工作
做一個小游戲我們首先要有一個地圖吧,所以我用tiled這個軟件來制作地圖,安裝和使用都挺簡單了,畫好后用notepad++打開看一下圖片路徑對不對,然后把圖片、字體文件、地圖文件.ttf放到工程的assets目錄下,然后我們就可以在后面使用這些資源了。
當然我,我們先來了解一下一些其他相關知識點
加載地圖
~~~
CCTMXTiledMap map=CCTMXTiledMap.tiledMap("map.tmx");
this.addChild(map);
~~~
解析地圖
//解析地圖
~~~
private void parseMap() {
roadPoints=new ArrayList<CGPoint>();
CCTMXObjectGroup objectGroupNamed=map.objectGroupNamed("road");
ArrayList<HashMap<String,String>> objects=objectGroupNamed.objects;
for(HashMap<String,String> hashMap:objects){
int x=Integer.parseInt(hashMap.get("x"));
int y=Integer.parseInt(hashMap.get("y"));
CGPoint cgPoint=ccp(x,y);
roadPoints.add(cgPoint);
}
}
~~~
//展示僵尸
~~~
int position=3;
private void loadZombies() {
CCSprite sprite=CCSprite.sprite("z_1_01.png");
sprite.setPosition(roadPoints.get(position));
sprite.setAnchorPoint(0.5f,0);
sprite.setScale(0.65f);
this.addChild(sprite);
}
~~~
粒子系統
eg:飄雪:CCParticleSnow
~~~
private void loadParticle() {
system = CCParticleSnow.node();
// 設置雪花的樣式
system.setTexture(CCTextureCache.sharedTextureCache().addImage("f.png"));
this.addChild(system, 1);
}
system.stopSystem();// 停止粒子系統
~~~
自定義效果,后綴.plis
聲音引擎
~~~
SoundEngine engine=SoundEngine.sharedEngine();
// 1 上下文 2. 音樂資源的id 3 是否循環播放
engine.playSound(CCDirector.theApp, R.raw.psy, true);
~~~
暫停和繼續
1.onExit();
2.onEnter();
~~~
@Override
public boolean ccTouchesBegan(MotionEvent event) {
this.onExit(); // 暫停
this.getParent().addChild(new PauseLayer());// 讓場景添加新的圖層
return super.ccTouchesBegan(event);
}
// 專門用來暫停的圖層
private class PauseLayer extends CCLayer{
private CCSprite heart;
public PauseLayer(){
this.setIsTouchEnabled(true);// 打開觸摸事件的開關
heart = CCSprite.sprite("heart.png");
// 獲取屏幕的尺寸
CGSize winSize = CCDirector.sharedDirector().getWinSize();
heart.setPosition(winSize.width/2, winSize.height/2);// 讓圖片再屏幕的中間
this.addChild(heart);
}
// 當點擊PauseLayer的時候
@Override
public boolean ccTouchesBegan(MotionEvent event) {
CGRect boundingBox = heart.getBoundingBox();
// 把Android坐標系中的點 轉換成Cocos2d坐標系中的點
CGPoint convertTouchToNodeSpace = this.convertTouchToNodeSpace(event);
if(CGRect.containsPoint(boundingBox, convertTouchToNodeSpace)){// 確實點擊了心
this.removeSelf();// 回收當前圖層
DemoLayer.this.onEnter();//游戲繼續
}
return super.ccTouchesBegan(event);
}
}
~~~
### 項目正式開始
首先要有一個logo,logo下面有一個背景圖片,需要加載一個進度條,一步步跳轉就可以了,說到這個進度條,其實就是一個幀動畫
用下面這段代碼來看一下,當然這里之后我抽取了一個CommonUtils工具類,

~~~
private void loading() {
CCSprite loading=CCSprite.sprite("image/loading/loading_01.png");
loading.setPosition(winSize.width/2, 30);
this.addChild(loading);
CCAction animate = CommonUtils.getAnimate("image/loading/loading_%02d.png", 9, false);
loading.runAction(animate);
start = CCSprite.sprite("image/loading/loading_start.png");
start.setPosition(winSize.width/2, 30);
start.setVisible(false);// 暫時不可見
this.addChild(start);
}
~~~
當然我們可以來看一下這個工具類,這在之后的開發中有很多的實用價值:
~~~
`public class CommonUtils {
/**
* 切換圖層
* @param newLayer 新進入的圖層
*/
public static void changeLayer(CCLayer newLayer){
CCScene scene=CCScene.node();
scene.addChild(newLayer);
CCFlipXTransition transition=CCFlipXTransition.transition(2, scene, 0);
CCDirector.sharedDirector().replaceScene(transition);//切換場景 ,參數 新的場景
}
/**
* 解析地圖上 對象的所有點
* @param map 地圖
* @param name 對象的名字
* @return
*/
public static List<CGPoint> getMapPoints(CCTMXTiledMap map,String name){
List<CGPoint> points = new ArrayList<CGPoint>();
// 解析地圖
CCTMXObjectGroup objectGroupNamed = map.objectGroupNamed(name);
ArrayList<HashMap<String, String>> objects = objectGroupNamed.objects;
for (HashMap<String, String> hashMap : objects) {
int x = Integer.parseInt(hashMap.get("x"));
int y = Integer.parseInt(hashMap.get("y"));
CGPoint cgPoint = CCNode.ccp(x, y);
points.add(cgPoint);
}
return points;
}
/**
* 創建了序列幀的動作
* @param format 格式化的路徑
* @param num 幀數
* @param isForerver 是否永不停止的循環
* @return
*/
public static CCAction getAnimate(String format,int num,boolean isForerver){
ArrayList<CCSpriteFrame> frames=new ArrayList<CCSpriteFrame>();
//String format="image/loading/loading_%02d.png";
for(int i=1;i<=num;i++){
CCSpriteFrame spriteFrame = CCSprite.sprite(String.format(format, i)).displayedFrame();
frames.add(spriteFrame);
}
CCAnimation anim=CCAnimation.animation("", 0.2f, frames);
// 序列幀一般必須永不停止的播放 不需要永不停止播放,需要指定第二個參數 false
if(isForerver){
CCAnimate animate=CCAnimate.action(anim);
CCRepeatForever forever=CCRepeatForever.action(animate);
return forever;
}else{
CCAnimate animate=CCAnimate.action(anim,false);
return animate;
}
}
}`
~~~
至于其他小的細節我就不一一啰嗦了,只說一下僵尸和植物對戰需要的幾個關鍵代碼:
~~~
/**
* 處理游戲開始后的操作
*
*
*/
public class GameCotroller {
private GameCotroller() {
}
private static GameCotroller cotroller = new GameCotroller();
public static GameCotroller getInstance() {
return cotroller;
}
public static boolean isStart; // 游戲是否開始
private CCTMXTiledMap map;
private List<ShowPlant> selectPlants;
private static List<FightLine> lines; // 管理了五行
private List<CGPoint> roadPoints;
static {
lines = new ArrayList<FightLine>();
for (int i = 0; i < 5; i++) {
FightLine fightLine = new FightLine(i);
lines.add(fightLine);
}
}
/**
* 開始游戲
*
* @param map
* 游戲的地圖
* @param selectPlants
* 玩家已選植物的集合
*/
public void startGame(CCTMXTiledMap map, List<ShowPlant> selectPlants) {
isStart = true;
this.map = map;
this.selectPlants = selectPlants;
loadMap();
// 添加僵尸
// 定時器
// 參數1 方法名(方法帶float類型的參數) 參數2 調用方法的對象 參數3 間隔時間 參數4 是否暫停
CCScheduler.sharedScheduler().schedule("addZombies", this, 1,false);
// CCCallFunc.action(target, selector)
// addZombies();
// 安放植物
// 僵尸攻擊植物
// 植物攻擊僵尸
progress();
}
CGPoint[][] towers = new CGPoint[5][9];
private void loadMap() {
roadPoints = CommonUtils.getMapPoints(map, "road");
for (int i = 1; i <= 5; i++) {
List<CGPoint> mapPoints = CommonUtils.getMapPoints(map,
String.format("tower%02d", i));
for (int j = 0; j < mapPoints.size(); j++) {
towers[i - 1][j] = mapPoints.get(j);
}
}
}
/***
* 添加僵尸
*
* @param t
*/
public void addZombies(float t) {
Random random = new Random();
int lineNum = random.nextInt(5);// [0-5)
PrimaryZombies primaryZombies = new PrimaryZombies(
roadPoints.get(lineNum * 2), roadPoints.get(lineNum * 2 + 1));
map.addChild(primaryZombies,1);// 讓僵尸一直在植物的上面
lines.get(lineNum).addZombies(primaryZombies);// 把僵尸記錄到行戰場中
progress+=5;
progressTimer.setPercentage(progress);//設置新的進度
}
public void endGame() {
isStart = false;
}
private ShowPlant selectPlant; // 玩家選擇的植物
private Plant installPlant;
/**
* 當游戲開始后處理點擊事件的方法
*
* @param point
* 點擊到的點
*/
public void handleTouch(CGPoint point) {
CCSprite chose = (CCSprite) map.getParent().getChildByTag(
FightLayer.TAG_CHOSE);
if (CGRect.containsPoint(chose.getBoundingBox(), point)) {
// 認為玩家有可能選擇了植物
if (selectPlant != null) {
selectPlant.getShowSprite().setOpacity(255);
selectPlant = null;
}
for (ShowPlant plant : selectPlants) {
CGRect boundingBox = plant.getShowSprite().getBoundingBox();
if (CGRect.containsPoint(boundingBox, point)) {
// 玩家選擇了植物
selectPlant = plant;
selectPlant.getShowSprite().setOpacity(150);
int id = selectPlant.getId();
switch (id) {
case 1:
installPlant =new PeasePlant();
break;
case 4:
installPlant = new Nut();
break;
default:
break;
}
}
}
} else {
// 玩家有可能安放植物
if (selectPlant != null) {
int row = (int) (point.x / 46) - 1; // 1-9 0-8
int line = (int) ((CCDirector.sharedDirector().getWinSize().height - point.y) / 54) - 1;// 1-5
// 0-4
// 限制安放的植物的范圍
if (row >= 0 && row <= 8 && line >= 0 && line <= 4) {
// 安放植物
// selectPlant.getShowSprite().setPosition(point);
// installPlant.setPosition(point); // 坐標需要修改
installPlant.setLine(line);// 設置植物的行號
installPlant.setRow(row); // 設置植物的列號
installPlant.setPosition(towers[line][row]); // 修正了植物的坐標
FightLine fightLine = lines.get(line);
if (!fightLine.containsRow(row)) { // 判斷當前列是否已經添加了植物 如果添加了 就不能再添加了
fightLine.addPlant(installPlant);// 把植物記錄到了行戰場中
map.addChild(installPlant);
}
}
installPlant = null;
selectPlant.getShowSprite().setOpacity(255);
selectPlant = null;// 下次安裝需要重新選擇
}
}
}
CCProgressTimer progressTimer;
int progress=0;
private void progress() {
// 創建了進度條
progressTimer = CCProgressTimer.progressWithFile("image/fight/progress.png");
// 設置進度條的位置
progressTimer.setPosition(CCDirector.sharedDirector().getWinSize().width - 80, 13);
map.getParent().addChild(progressTimer); //圖層添加了進度條
progressTimer.setScale(0.6f); // 設置了縮放
progressTimer.setPercentage(0);// 每增加一個僵尸需要調整進度,增加5
progressTimer.setType(CCProgressTimer.kCCProgressTimerTypeHorizontalBarRL); // 進度的樣式
CCSprite sprite = CCSprite.sprite("image/fight/flagmeter.png");
sprite.setPosition(CCDirector.sharedDirector().getWinSize().width - 80, 13);
map.getParent().addChild(sprite);
sprite.setScale(0.6f);
CCSprite name = CCSprite.sprite("image/fight/FlagMeterLevelProgress.png");
name.setPosition(CCDirector.sharedDirector().getWinSize().width - 80, 5);
map.getParent().addChild(name);
name.setScale(0.6f);
}
}
~~~
還有一個很關鍵的就是:
~~~
/**
* 對戰界面的圖層
*
*/
public class FightLayer extends BaseLayer {
public static final int TAG_CHOSE = 10;
private CCTMXTiledMap map;
private List<CGPoint> zombilesPoints;
private CCSprite choose; // 玩家可選植物的容器
private CCSprite chose; // 玩家已選植物的容器
public FightLayer() {
init();
}
private void init() {
loadMap();
parserMap();
showZombies();
moveMap();
}
// 加載展示用的僵尸
private void showZombies() {
for (int i = 0; i < zombilesPoints.size(); i++) {
CGPoint cgPoint = zombilesPoints.get(i);
ShowZombies showZombies = new ShowZombies();
showZombies.setPosition(cgPoint);// 給展示用的僵尸設置了位置
map.addChild(showZombies);// 注意: 把僵尸加載到地圖上
}
}
private void parserMap() {
zombilesPoints = CommonUtils.getMapPoints(map, "zombies");
}
// 移動地圖
private void moveMap() {
int x = (int) (winSize.width - map.getContentSize().width);
CCMoveBy moveBy = CCMoveBy.action(3, ccp(x, 0));
CCSequence sequence = CCSequence
.actions(CCDelayTime.action(4), moveBy, CCDelayTime.action(2),
CCCallFunc.action(this, "loadContainer"));
map.runAction(sequence);
}
private void loadMap() {
map = CCTMXTiledMap.tiledMap("image/fight/map_day.tmx");
map.setAnchorPoint(0.5f, 0.5f);
CGSize contentSize = map.getContentSize();
map.setPosition(contentSize.width / 2, contentSize.height / 2);
this.addChild(map);
}
// 加載兩個容器
public void loadContainer() {
chose = CCSprite.sprite("image/fight/chose/fight_chose.png");
chose.setAnchorPoint(0, 1);
chose.setPosition(0, winSize.height);// 設置位置是屏幕的左上角
this.addChild(chose,0,TAG_CHOSE);
choose = CCSprite.sprite("image/fight/chose/fight_choose.png");
choose.setAnchorPoint(0, 0);
this.addChild(choose);
loadShowPlant();
start = CCSprite.sprite("image/fight/chose/fight_start.png");
start.setPosition(choose.getContentSize().width/2, 30);
choose.addChild(start);
}
private List<ShowPlant> showPlatns; // 展示用的植物的集合
// 加載展示用的植物
private void loadShowPlant() {
showPlatns = new ArrayList<ShowPlant>();
for (int i = 1; i <= 9; i++) {
ShowPlant plant = new ShowPlant(i); // 創建了展示的植物
CCSprite bgSprite = plant.getBgSprite();
bgSprite.setPosition(16 + ((i - 1) % 4) * 54,
175 - ((i - 1) / 4) * 59);
choose.addChild(bgSprite);
CCSprite showSprite = plant.getShowSprite();// 獲取到了展示的精靈
// 設置坐標
showSprite.setPosition(16 + ((i - 1) % 4) * 54,
175 - ((i - 1) / 4) * 59);
choose.addChild(showSprite); // 添加到了容器上
showPlatns.add(plant);
}
setIsTouchEnabled(true);
}
public void unlock(){
isLock=false;
}
private List<ShowPlant> selectPlants = new CopyOnWriteArrayList<ShowPlant>();// 已經選中植物的集合
boolean isLock;
boolean isDel; // 是否刪除了選中的植物
private CCSprite start;
@Override
public boolean ccTouchesBegan(MotionEvent event) {
// 需要把Android坐標系中的點 轉換成Cocos2d坐標系中的點
CGPoint point = this.convertTouchToNodeSpace(event);
if(GameCotroller.isStart){// 如果游戲開始了 交給GameCtoller 處理
GameCotroller.getInstance().handleTouch(point);
return super.ccTouchesBegan(event);
}
CGRect boundingBox = choose.getBoundingBox();
CGRect choseBox = chose.getBoundingBox();
// 玩家有可能反選植物
if(CGRect.containsPoint(choseBox, point)){
isDel=false;
for(ShowPlant plant:selectPlants){
CGRect selectPlantBox = plant.getShowSprite().getBoundingBox();
if(CGRect.containsPoint(selectPlantBox, point)){
CCMoveTo moveTo=CCMoveTo.action(0.5f, plant.getBgSprite().getPosition());
plant.getShowSprite().runAction(moveTo);
selectPlants.remove(plant);// 走到這一步 確實代表反選植物了
isDel=true;
continue;// 跳出本次循環,繼續下次循環
}
if(isDel){
CCMoveBy ccMoveBy=CCMoveBy.action(0.5f, ccp(-53, 0));
plant.getShowSprite().runAction(ccMoveBy);
}
}
}else if (CGRect.containsPoint(boundingBox, point)) {
if(CGRect.containsPoint(start.getBoundingBox(), point)){
// 點擊了一起來搖滾
ready();
}else if (selectPlants.size() < 5&&!isLock) { //如果已經選擇滿了 就不能再選擇了
// 有可能 選擇植物
for (ShowPlant plant : showPlatns) {
CGRect boundingBox2 = plant.getShowSprite()
.getBoundingBox();
if (CGRect.containsPoint(boundingBox2, point)) {// 如果點恰好落在植物展示的精靈矩形之中
// 當前植物被選中了
isLock=true;
// System.out.println("我被選中了...");
CCMoveTo moveTo = CCMoveTo.action(
0.5f,
ccp(75 + selectPlants.size() * 53,
255));
CCSequence sequence=CCSequence.actions(moveTo, CCCallFunc.action(this, "unlock"));
plant.getShowSprite().runAction(sequence);
selectPlants.add(plant);
}
}
}
}
return super.ccTouchesBegan(event);
}
/**
* 點擊了一起來搖滾 做的操作
*/
private void ready() {
// 縮小玩家已選植物容器
chose.setScale(0.65f);
// 把選中的植物重新添加到 存在的容器上
for(ShowPlant plant:selectPlants){
plant.getShowSprite().setScale(0.65f);// 因為父容器縮小了 孩子一起縮小
plant.getShowSprite().setPosition(
plant.getShowSprite().getPosition().x * 0.65f,
plant.getShowSprite().getPosition().y
+ (CCDirector.sharedDirector().getWinSize().height - plant
.getShowSprite().getPosition().y)
* 0.35f);// 設置坐標
this.addChild(plant.getShowSprite());
}
choose.removeSelf();// 回收容器
// 地圖的平移
int x = (int) (map.getContentSize().width-winSize.width);
CCMoveBy moveBy = CCMoveBy.action(1, ccp(x, 0));
CCSequence sequence=CCSequence.actions(moveBy, CCCallFunc.action(this, "preGame"));
map.runAction(sequence);
}
private CCSprite ready;
public void preGame(){
ready=CCSprite.sprite("image/fight/startready_01.png");
ready.setPosition(winSize.width/2, winSize.height/2);
this.addChild(ready);
String format="image/fight/startready_%02d.png";
CCAction animate = CommonUtils.getAnimate(format, 3, false);
CCSequence sequence=CCSequence.actions((CCAnimate)animate, CCCallFunc.action(this, "startGame"));
ready.runAction(sequence);
}
public void startGame(){
ready.removeSelf();// 移除中間的序列幀
GameCotroller cotroller=GameCotroller.getInstance();
cotroller.startGame(map,selectPlants);
}
}
~~~

在做這個的過程中總是遇到空指針的異常,例如這次:后來發現是因為我ShowPlant.java這個地方寫錯了

做媒一個項目都需要一些成長和一些經驗,在之前的CCSequence,CGPoint,CCSprite,CCTMXTiledMap
后面又學會了CCScheduler.sharedScheduler().schedule(“attackPlant”, this, 0.5f, false),ready.removeSelf();// 移除中間的序列幀等內容,讓我對這整個架構有了初步了了解了實踐,學習之路很長,我們一起加油!
- 前言
- 內存溢出的解決方案
- 安卓消息推送解決方案
- 語言識別和聊天機器人的實現
- 抽屜效果的實現(DrawerLayout和SlidingMenu的對比)
- 植物大戰僵尸經典開發步驟
- 屏幕適配全攻略
- 安卓圖像處理入門教程
- android開發常用工具箱
- java基礎知識總結
- 剖析軟件外包項目
- java基礎知識——網絡編程、IO流
- 安卓性能優化手冊
- 電商活動中刮刮卡的實現
- Android系統的安全設計與架構
- AsnycTask的內部的實現機制
- Android應用UI設計流程
- 數據結構與算法,每日一道
- html5全解析
- 深入解讀XML解析
- 新聞客戶端案例開發
- 細說Http協議
- win10+ubuntu雙系統安裝方案
- 隨機驗證碼實現案例
- 動態數組的實現案例
- 猜拳游戲案例
- 商業級項目——基金客戶端的架構設計與開發(上)