>【前言】

别小看了一个飞行棋,它其实包含了非常多的内容。尤其是可支持局域网对战、甚至是进行复盘的飞行棋,它将包含的知识点内容更将会有:

  1. 资源的载入
  2. 对各android真机屏幕的适配
  3. android对音乐的处理
  4. android的网络功能(Socket套接字)
  5. 如果支持复盘Replay,那么可能还会涉及到数据库
  6. Handler及andr的线程、消息循环机制
  7. UI的更新
综合来看,小小的一个飞行棋,其实真的是“麻雀虽小,五脏俱全”,对稍微有点安卓基础但苦于没有开发能力的人来说,确实是一个非常好的实例。

没关系,这篇博客我会给出详细的实例代码和素材包,并进行设计分析,可能会分为好几篇进行发布。


>【前期设计】

1.UI设计·适配

说到UI设计,我们就不得不提到安卓的UI设计。

安卓机型众多,分辨率、尺寸也各分千秋,因此适配问题一直是一个老大难。好在安卓已经推出了一个百分比布局库,有兴趣的朋友可以自行去查阅。

但我今天准备分享的另一种方法,利用绝对布局来进行屏幕适配。在我准备分享这个方法时,我上网搜了一下资料,发现这个方法早已有大手子给出了详细的分析,我也就不再赘述了,可以移步这里:http://blog.csdn/lmj623565791/article/details/45460089。

2.分析游戏棋盘

如图所示,这是我自制的一张1080*1080px分辨率的PNG图片,我给它分成了36*36格,每格显然是30px的正方形。这样,我们描述某个棋格,就可以用一个(x,y)坐标系来进行描述了。



你们可以到这里下载我所使用的自制素材包:

http://download.csdn/detail/shenpibaipao/9815604


3.设计棋子对象

接下来,就会开始涉及代码部分了。

为防有同学直接原封不动地copy我的代码去交作业,我在一些写法、命名、小地方动了手脚,你很难看出来,但你们的老师一眼就能看出来。还是那句话,我注重的实例开发,是一个思路的过程,直接copy什么的,是不太好的。

我们定义一个类:

public class Chessman{
    //状态量
    //常量值
    //方法
}
状态量应该有哪些呢?我们知道,飞行棋的棋子有两种状态,飞行中和停在机场、完成飞行停在机场和没完成飞行停在机场。所以,应当有两个状态量:

    private boolean Flying,Completed;

同时,还应该有一个用于记录当前棋子已移动步数的值,相信我,这个值到时候会非常好用:

    private int now_pos;//当前已移动步数

常量值有哪些呢?很显然应当有这一些:
    private int Faction,num;//棋子阵营,编号
    private ImageView img;//绑定图像
    private static int kuan,left_top_pos;//棋局格子宽度 和左上角坐标

对于静态量kuan,left_top_pos这两个值,你可能暂时还无法理解。这个kuan,记录的是该次游戏中每一个棋格的宽度,比如30px,left_top_pos记录的是棋盘左上角的坐标,用于定位用的。存为静态量,所有Chessman对象公用一份,主要用于做屏幕适配。

除此之外,我们也应当为其设定各种方法,如get()、set()、isXXX()等,同时还有一些特殊的方法:
    public void resetStatusALL(){
        //初始化棋子状态量,用于重启游戏
    }
    public void move(int POSX,int POSY){
        //按坐标移动棋子
    }
    public void move(int steps){
        //按roll点移动棋子
    }
    public void Fly(){
        //飞机起飞
    }
    public void Killed(){
       //棋子被击杀
    }
下面是我完整的实例代码,可能现在还无法完全读懂,但是在之后的讲解中你会慢慢理解:

import android.provider.Settings;
import android.widget.ImageView;

/**
 * Created by Shenpibaipao
 */

public class Cheesman {

    private boolean Completed,Flying;//棋子状态
    private int now_pos;//当前已移动步数

    private int Faction,num;//棋子阵营,编号
    private ImageView img;//绑定图像
    private static int kuan,left_top_pos;//棋局格子宽度 和左上角坐标

    public Cheesman(int FACTION,int NUM,ImageView IMAGEVIEW){
        resetStatusALL();
        Faction=FACTION;
        img=IMAGEVIEW;
        num=NUM;
        now_pos=0;
    }
    public void resetStatusALL(){
        Completed=false;
        Flying=false;
        now_pos=0;
    }
    public void move(int POSX,int POSY){
        img.setX(POSX*kuan);
        img.setY(left_top_pos+POSY*kuan);
    }
    public void move(int steps){
        //最多57格 0~26
        int cha=now_pos+steps-Value.Teminal;
        if(cha<0)cha=0;
        if(isFlying()){
            switch(Faction){
                case Value.red:
                    move(Value.redPathx[now_pos+steps-cha],Value.redPathy[now_pos+steps-cha]);
                    break;
                case Value.yellow:
                    move(Value.yellowPathx[now_pos+steps-cha],Value.yellowPathy[now_pos+steps-cha]);
                    break;
                case Value.blue:
                    move(Value.bluePathx[now_pos+steps-cha],Value.bluePathy[now_pos+steps-cha]);
                    break;
                case Value.green:
                    move(Value.greenPathx[now_pos+steps-cha],Value.greenPathy[now_pos+steps-cha]);
                    break;
            }
        }
        now_pos+=steps;
    }
    public int getX(){
        return (int)img.getX()/kuan;
    }
    public int getY(){
        return (int)(img.getY()-left_top_pos)/kuan;
    }
    public int getFaction(){
        return Faction;
    }
    public int getNum(){
        return num;
    }
    public int getNow_pos(){
        return now_pos;
    }

    public boolean isCompleted(){
        return Completed;
    }
    public boolean isFlying(){
        return Flying;
    }
    //功能类
    public void CompletedTour(){
        now_pos=0;
        setFlying(false);
        setCompleted(true);
    }
    public void Fly(){
        now_pos=0;
        switch (Faction){
            case Value.red:
                move(Value.redPathx[now_pos],Value.redPathy[now_pos]);
                break;
            case Value.yellow:
                move(Value.yellowPathx[now_pos],Value.yellowPathy[now_pos]);
                break;
            case Value.blue:
                move(Value.bluePathx[now_pos],Value.bluePathy[now_pos]);
                break;
            case Value.green:
                move(Value.greenPathx[now_pos],Value.greenPathy[now_pos]);
                break;
            default:
                System.out.println("注意:起飞失败");
        }
        setFlying(true);
    }
    public void Killed(){
        now_pos=0;
        setFlying(false);
        System.out.println("注意:棋子被击杀");
    }

    public static void load_qipan(int KUAN,int LEFT_TOP_POS){
        kuan=KUAN;
        left_top_pos=LEFT_TOP_POS;
    }
    public ImageView getImg(){
        return img;
    }
    private void setFlying(boolean bool){
        Flying=bool;
    }
    private void setCompleted(boolean bool){
        Completed=bool;
    }

}

4.设置帮助类

我们的目标是制作一个可供本地多人或网上对战的飞行棋,因此我们需要一个ConfigHelper,你可以把它设计为一个单实例。我在下面的代码里没有这么写,因为我不想写好了给别人Copy。请自行修改。

在这个类中,需要读取到该次游戏中有几个玩家、几个AI、几个网上对手,由于人类玩家可设置成“托管”状态,所以应该给出相应的方法,进行数值操作。

下面是完整的实例代码:


import android.text.Layout;
import android.view.View;

/**
 * Created by a on 2017/4/15.
 */

public class ConfigHelper {
    private int LocalHumanNum,OnlineHumanNum,AINum;
    private int GameType;
    private int PlayerType[]=new int[4];
    private boolean Host;
    //private int redType,yellowType,blueType,greenType;

    public ConfigHelper(int GAMETYPE){
        GameType=GAMETYPE;
        if(GAMETYPE==Value.Online){
            LocalHumanNum=1;
            OnlineHumanNum=3;
            AINum=0;
        }
        else if(GAMETYPE==Value.Local){
            LocalHumanNum=4;
            OnlineHumanNum=0;
            AINum=0;
        }
        Host=true;//默认为主机
        PlayerType[Value.red]=Value.LocalHuman;
        PlayerType[Value.yellow]=Value.LocalHuman;
        PlayerType[Value.blue]=Value.LocalHuman;
        PlayerType[Value.green]=Value.LocalHuman;
    }

    public void cgPlayerType(int FACTION,int TYPE){
        if(TYPE==Value.LocalHuman) LocalHumanNum++;
        else if(TYPE==Value.OnlineHuman)OnlineHumanNum++;
        else if(TYPE==Value.AI)AINum++;

        int t=0;
        if(FACTION==Value.red){
            t=PlayerType[Value.red];
            setRedType(TYPE);
        }
        else if(FACTION==Value.yellow){
            t=PlayerType[Value.yellow];
            setYellowType(TYPE);
        }
        else if(FACTION==Value.blue){
            t=PlayerType[Value.blue];
            setBlueType(TYPE);
        }
        else if(FACTION==Value.green){
            t=PlayerType[Value.green];
            setGreenType(TYPE);
        }

        switch(t){
            case Value.LocalHuman:
                LocalHumanNum--;
                break;
            case Value.OnlineHuman:
                OnlineHumanNum--;
                break;
            case Value.AI:
                AINum--;
                break;
        }
    }
    public void resetStatusALL(){
        if(GameType==Value.Online){
            LocalHumanNum=1;
            OnlineHumanNum=3;
            AINum=0;
        }
        else if(GameType==Value.Local){
            LocalHumanNum=4;
            OnlineHumanNum=0;
            AINum=0;
        }
        PlayerType[Value.red]=Value.LocalHuman;
        PlayerType[Value.yellow]=Value.LocalHuman;
        PlayerType[Value.blue]=Value.LocalHuman;
        PlayerType[Value.green]=Value.LocalHuman;
    }

    public void setRedType(int TYPE){
        PlayerType[Value.red]=TYPE;
    }
    public void setYellowType(int TYPE){
        PlayerType[Value.yellow]=TYPE;
    }
    public void setBlueType(int TYPE){
        PlayerType[Value.blue]=TYPE;
    }
    public void setGreenType(int TYPE){
        PlayerType[Value.green]=TYPE;
    }
    public void setHost(boolean host) {
        Host = host;
    }

    public int getGameType(){
        return GameType;
    }
    public boolean isHost() {
        return Host;
    }
    public int getPlayerType(int FACTION) {
        return PlayerType[FACTION];
    }

    public int getRedType(){
        return PlayerType[Value.red];
    }
    public int getYellowType(){
        return PlayerType[Value.yellow];
    }
    public int getBlueType(){
        return PlayerType[Value.blue];
    }
    public int getGreenType(){
        return PlayerType[Value.green];
    }
}
5.常用值的封装

你可能注意到了我使用了不少Value.xxxx,这是哪里来的呢?

为了代码阅读性考虑,我另外封装了一个值类Value.Class,里面记录了所有的常用值,我暂时在这里给出一部分:

public class Value {
    static final int red=0;
    static final int yellow=1;
    static final int blue=2;
    static final int green=3;

    static final int AI=0;
    static final int OnlineHuman=1;
    static final int LocalHuman=2;

    static final int Online=0;
    static final int Local=1;

    static final String  PlayerName[]=new String[]{"Red","Yellow","Blue","Green"};
}


今天先写到这,我把整个游戏数据对象已经介绍完了,接下来就是具体的实现过程。

其实有了这些对象,完成这个游戏已经不难了。剩下要做的,就是制定行棋规则、UI更新等。这个我们留到下次再说。




更多推荐

android游戏开发实例-可局域网对战的飞行棋(一)