Android 贪食蛇

初级贪食蛇

效果展示:

贪食蛇展示

界面设计

游戏界面的设计布局我们采用“相对布局中嵌套线性布局”这样做的好处是界面整体对称性更强,游戏体验更佳。
用相对布局将游戏界面分成两部分,一部分为游戏展示界面,一部分为按钮控制界面。
这里的界面设计着重是按钮布局的界面,游戏展示界面将在算法设计中的游戏面板类中展开。
游戏展示界面我们需要调用游戏面板类的内容:

    <com.example.sqltext.GamePanel
        android:id="@+id/gamePanel"
        android:layout_width="match_parent"
        android:layout_height="500dp" />

按钮控制界面则采用线性布局和相对布局相辅相成,使得整个界面规整,对称。我们将按钮控制界面分为三个部分,将开始游戏和结束游戏放于左边,方向控制居中处理,暂停和继续放于右边。

 <LinearLayout
        android:layout_below="@+id/gamePanel"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal">
        <LinearLayout
            android:orientation="vertical"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="wrap_content">
            <Button
                android:id="@+id/start"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="开始游戏" />
            <Button
                android:id="@+id/back"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="退出游戏" />
        </LinearLayout>
        <RelativeLayout
            android:layout_width="0dp"
            android:layout_weight="3"
            android:layout_height="match_parent">
            <Button
                android:id="@+id/up"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:text="上"
                android:textStyle="bold" />
            <Button
                android:id="@+id/left"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="左"
                android:layout_below="@+id/up"
                android:layout_toLeftOf="@+id/down"
                android:textStyle="bold" />
            <Button
                android:id="@+id/right"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="右"
                android:layout_below="@+id/up"
                android:layout_toRightOf="@+id/down"
                android:textStyle="bold" />
            <Button
                android:id="@+id/down"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:layout_below="@+id/up"
                android:text="下"
                android:textStyle="bold" />
        </RelativeLayout>
        <LinearLayout
            android:orientation="vertical"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="wrap_content">
            <Button
                android:id="@+id/Continue"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="继续游戏" />
            <Button
                android:id="@+id/Stop"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="暂停" />
        </LinearLayout>
    </LinearLayout>

将按钮的布置尽量美观并且符合玩家习惯。
布局效果展示:
按钮界面布局

算法设计

界面的设计完成过后,现在需要进行算法的更新以及按钮的适配。
由繁入简,我们需要先将游戏面板的内容完善,再逐渐布局“贪食蛇”

游戏面板类

在这个类里,我们需要完成定义界面高度和宽度,蛇身的类调用,分数,速度,开始游戏,格子大小,活动范围,蛇长算法,食物坐标,方向,食物与蛇身重叠,蛇头位置,按键过快导致BUG等问题。
首先完成声明定义:

class GamePanel  extends View implements View.OnTouchListener {
    public int wid,hei;//获得View的宽和高
    public ArrayList<SnackBody> body = new ArrayList<SnackBody>(); //蛇身
    public  int score;//分数
    public  int  speed;//速度
    public boolean start;//开始游戏
    private static final int MAX_Height = 20;
    private  static  final  int MAX_Width = 20; //活动范围20*20
    private int rectSize; //每一格子大小
    private  int snackLength ;//蛇长
    private int foodX,foodY;//食物坐标
    private Random random;
    private Paint paint;
    private int direction; //方向
    private int eatSign; // 用于判断刷新下一个食物是否和蛇身重叠
    private Snack snack;
    private DirectiomThread left = new DirectiomThread(this,1);
    private DirectiomThread up = new DirectiomThread(this,2);
    private DirectiomThread right = new DirectiomThread(this,3);
    private DirectiomThread down = new DirectiomThread(this,4);
    private int itemX,itemY; //判断蛇头位置
    public int lastDirection;//限制过于快速按键吃掉自己BUG
    public GamePanel(Context context) { super(context); }
    public GamePanel(Context context, AttributeSet attributeSet){super(context,attributeSet);}

其次是初始化定义,将分数设为0,速度为100,蛇头方向,食物方向,蛇身长度都完成初始化定义。

 public void init(){
        rectSize = wid/MAX_Width;  
        score = 0;   
        speed = 100;
        itemX = -1;
        itemY = -1;
        body.clear();
        snackLength= 0;
        random = new Random();
        paint = new Paint();
        paint.setAntiAlias(true);
        direction  =3;
        lastDirection = 3;
        this.setWillNotDraw(false);
    }

然后完成画布的颜色分类,前期的准备工作就基本完成了:

protected void onDraw(Canvas canvas){//画布
        if (start){
            paint.setColor(Color.YELLOW);   //食物颜色
            canvas.drawRect(foodX*rectSize,
                    foodY*rectSize,
                    foodX*rectSize+rectSize,
                    foodY*rectSize+rectSize,
                    paint);
            if (body.size()>0){
                paint.setColor(Color.BLUE);   //头部颜色
                canvas.drawRect(body.get(0).getX()*rectSize,
                        body.get(0).getY()*rectSize,
                        body.get(0).getX()*rectSize+rectSize,
                        body.get(0).getY()*rectSize+rectSize,
                        paint);
                paint.setColor(Color.GREEN);    //身体颜色
                for (int i=1;i<body.size();i++){
                    canvas.drawRect(body.get(i).getX()*rectSize,
                            body.get(i).getY()*rectSize,
                            body.get(i).getX()*rectSize+rectSize,
                            body.get(i).getY()*rectSize+rectSize,
                            paint);
                }
                paint.setColor(Color.RED);
                paint.setTextSize(50);
                canvas.drawText("分数:" + score, wid-300, wid-20, paint); //分数
            }
        }
    }

如图,蓝色为蛇头,绿色为蛇身,黄色为食物,红色为分数提示。
Alt
为了后续蛇身移动,定义方向,还需要一个大小更改的定义:

 protected void onSizeChanged(int w,int h,int oldw,int oldh){
        super.onSizeChanged(w,h,oldw,oldh);
        wid = w;
        hei = h;
    }

完成这些设置之后,我们开始写点击开始游戏后的算法:
点击开始游戏后,会自动向右边移动,蛇身位置会从newBody到newBody2;

public void startGame(){   //开始游戏
        init();
        start = true;
        SnackBody newBody = new SnackBody();
        newBody.setX(MAX_Width/2);
        newBody.setY(MAX_Height/2);
        SnackBody newBody2 = new SnackBody();
        newBody2.setX(MAX_Width/2-1);
        newBody2.setY(MAX_Width/2);
        body.add(newBody);
        body.add(newBody2);
        do {
            foodX = random.nextInt(MAX_Width-1);
            foodY = random.nextInt(MAX_Height-1);
        }while (foodX == newBody.getX() && foodY == newBody.getY());
        snack = new Snack(this);
        snack.start();
    }

游戏开始后,蛇体可由玩家控制,我们需要完成一个移动的算法:

 public void move(int x,int y){   //移动
        itemX = body.get(0).getX()+x;
        itemY = body.get(0).getY()+y;
        if (itemX == foodX && itemY == foodY){
            eat();
            for (int i = body.size()-2;i>0;i--){
                body.get(i).setX(body.get(i-1).getX());
                body.get(i).setY(body.get(i-1).getY());
            }
        }else {
            for (int i = body.size() - 1; i > 0; i--) {
                body.get(i).setX(body.get(i - 1).getX());
                body.get(i).setY(body.get(i - 1).getY());
            }
        }
        body.get(0).setX(body.get(0).getX() + x);
        body.get(0).setY(body.get(0).getY() + y);
    }

贪食蛇的重点在于吃,吃了以后身体会加长:
每吃到一个食物,身体就会张长。将身体张长的数据增加到growbody数据类中。

 public void eat(){    //吃
        score = score+10;
        snackLength++;
        do {
            eatSign = 0;
            foodX = random.nextInt(MAX_Height-1);
            foodY = random.nextInt(MAX_Width-1);
            for (int i = 0; i<body.size();i++){
                if (foodX == body.get(i).getX() && foodY == body.get(i).getY()){
                    eatSign++;
                }
            }
            if (foodX == itemX  && foodY == itemY)
                eatSign++;
        }while (eatSign>0);
        SnackBody growBody = new SnackBody();
        growBody.setX(body.get(body.size()-1).getX());
        growBody.setY(body.get(body.size()-1).getY());
        body.add(growBody);
    }

贪食蛇的死亡判定为撞到边界或者自己身体,此时游戏会结束。

 public boolean hitOrBite(){    //碰到边界
        if (body.get(0).getX() < 0 || body.get(0).getX() >= 20
                || body.get(0).getY() < 0 || body.get(0).getY() >= 20)
            return true;
        else {
            for (int i = body.size() - 1; i > 2; i--) {
                if (body.get(0).getX() == body.get(i).getX()
                        && body.get(0).getY() == body.get(i).getY())
                    return true;
            }
        }
        return false;
    }
     public void dead() { }
    public int getDirection(){return direction;}
    public void setDirection(int direction) {
        this.direction = direction;
    }
    public int getScore() {
        return score;
    }

完成上面的算法,我们仅仅完成了游戏展示界面以及其算法。下面我们还要继续完善按钮控制的算法。
为防止BUG冲突,例如蛇头突然180°转头导致游戏结束。设置了以下规则:当蛇头为左边时候,不难按下右键,以此类推。

@Override
    public boolean onTouch(View v, MotionEvent event) {
        if (v.getId() == R.id.left && event.getAction() == MotionEvent.ACTION_DOWN){
            if (lastDirection !=3 ){
                left = new DirectiomThread(this,1);
                up.canRun = false;
                right.canRun = false;
                down.canRun = false;
                left.canRun = true;
                left.start();
            }
        }else if (v.getId() == R.id.left && event.getAction() == MotionEvent.ACTION_UP){
            left.canRun = false;
        }

        if (v.getId() == R.id.up && event.getAction() == MotionEvent.ACTION_DOWN){
            if (lastDirection !=4 ){
                up = new DirectiomThread(this,2);
                up.canRun = true;
                right.canRun = false;
                down.canRun = false;
                left.canRun = false;
                up.start();
            }
        }else if (v.getId() == R.id.up && event.getAction() == MotionEvent.ACTION_UP){
            up.canRun = false;
        }

        if (v.getId() == R.id.right && event.getAction() == MotionEvent.ACTION_DOWN){
            if (lastDirection !=1 ){
                right = new DirectiomThread(this,3);
                up.canRun = false;
                right.canRun = true;
                down.canRun = false;
                left.canRun = false;
                right.start();
            }
        }else if (v.getId() == R.id.right && event.getAction() == MotionEvent.ACTION_UP){
            right.canRun = false;
        }

        if (v.getId() == R.id.down && event.getAction() == MotionEvent.ACTION_DOWN){
            if (lastDirection !=2 ){
                down = new DirectiomThread(this,4);
                up.canRun = false;
                right.canRun = false;
                down.canRun = true;
                left.canRun = false;
                down.start();
            }
        }else if (v.getId() == R.id.right && event.getAction() == MotionEvent.ACTION_UP){
            down.canRun = false;
        }

规范了游戏规则后,还需设定开始游戏以及暂停,继续游戏的算法:

if (v.getId() == R.id.start && event.getAction() == MotionEvent.ACTION_UP && start==false){
            this.startGame();
        }

        if (v.getId() == R.id.Continue && event.getAction() == MotionEvent.ACTION_UP && start ==false){
            start = true;
            snack = new Snack(this);
            snack.start();
        }
        if (v.getId() == R.id.Stop && event.getAction() == MotionEvent.ACTION_UP){
            start = false;
        }
        return false;

如此,游戏面板类的内容大致完成,接下面是完善蛇身类,方向类,蛇类定义以及最后的主函数使用。
接下来这三类均为游戏面板类线程的子线程,为方便修改。

蛇身类

此类用来定义蛇身的位置信息:
自动完善get和set方法。

class SnackBody {
    private int x,y;
    public int getX() {
        return x;
    }
    public int getY() {
        return y;
    }
    public void setX(int x) {
        this.x = x;
    }
    public void setY(int y) {
        this.y = y;
    }
}

蛇类

此类用来定义蛇体本身的跟踪,调用游戏面板类的算法集合。

class Snack extends  Thread {
    private GamePanel gamePanel;   //声明地图
    public Snack(GamePanel gamePanel){
        this.gamePanel= gamePanel;
    }  //声明蛇存在地图
    public void run(){   //处理的逻辑
        while (gamePanel.start){   //当点击开始
            try {
                long before = System.currentTimeMillis();  //开始前时间
                gamePanel.postInvalidate();   //调用gamePanel
                long after= System.currentTimeMillis();   //结束时间
                Thread.sleep((int)((20000/gamePanel.speed)-(after-before)));  //蛇移动速度
            }catch (InterruptedException e){   //中断异常
                e.printStackTrace();    //追踪
            }
            switch (gamePanel.getDirection()){
                case 1:                             //选择左
                    gamePanel.move(-1,0);            //X轴向左移动一个像素,y轴不变
                    gamePanel.lastDirection = 1;    //最后一个位置为:左
                    break;
                case 2:                           //选择上
                    gamePanel.move(0,-1);           // X轴不变,y轴向上移动一个像素
                    gamePanel.lastDirection = 2;    //最后一个位置为:上
                    break;
                case 3:                             //选择右
                    gamePanel.move(1,0);               //X轴向右移动一个像素,y轴不变
                    gamePanel.lastDirection = 3;      //最后一个位置为:右
                    break;
                case 4:                             //选择右
                    gamePanel.move(0,1);                // X轴不变,y轴向下移动一个像素
                    gamePanel.lastDirection = 4;          //最后一个位置为:下
                    break;
            }
            if (gamePanel.hitOrBite()){    //撞到墙壁
                gamePanel.start = false;   //结束游戏
                gamePanel.dead();      //蛇死亡
                continue;
            }
        }
    }
}

方向类

此类用来定义蛇的方向条件以及蛇的速度:

class DirectiomThread  extends  Thread{
    private GamePanel gamePanel;
    private int direction;
    public  boolean canRun;
    public DirectiomThread(GamePanel gamePanel,int direction){
        this.gamePanel = gamePanel;
        this.direction = direction;
    }

    public void run(){
        while (canRun){
            gamePanel.setDirection(direction);
            try {
                Thread.sleep(10);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}

主函数类

在主函数中需要添加一段重要代码,该代码为调用游戏面板类进行游戏展示:

RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams)gamePanel.getLayoutParams();
        gamePanel.setLayoutParams(params);
        params.width = width;
        params.height = width;

如此就能够完成贪食蛇的游戏展示
主函数完全代码:

public class game extends AppCompatActivity {

    private Button up,left,right,down;
    private Button start,back,Continue,Stop;
    private GamePanel gamePanel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_game);
        getSupportActionBar().hide();//隐藏标题栏
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);//隐藏状态栏
        up = findViewById(R.id.up);
        left = findViewById(R.id.left);
        right = findViewById(R.id.right);
        down = findViewById(R.id.down);
        start = findViewById(R.id.start);
        back = findViewById(R.id.back);
        Continue = findViewById(R.id.Continue);
        Stop = findViewById(R.id.Stop);
        DisplayMetrics metrics = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(metrics);
        int width = metrics.widthPixels;
        int height  = metrics.heightPixels;
        gamePanel = findViewById(R.id.gamePanel);
        gamePanel.setBackgroundColor(Color.LTGRAY);
        RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams)gamePanel.getLayoutParams();
        gamePanel.setLayoutParams(params);
        params.width = width;
        params.height = width;
        start.setOnTouchListener(gamePanel);
        Continue.setOnTouchListener(gamePanel);
        back.setOnTouchListener(gamePanel);
        Stop.setOnTouchListener(gamePanel);
        left.setOnTouchListener(gamePanel);
        right.setOnTouchListener(gamePanel);
        up.setOnTouchListener(gamePanel);
        down.setOnTouchListener(gamePanel);
        back.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(game.this,Main2.class);
                startActivity(intent);
                finish();
            }
        });
    }
    protected void onStart(){
        super.onStart();
    }
    protected void onStop(){
        super.onStop();
    }
    protected  void onPause(){
        super.onPause();
        gamePanel.start = false;
    }
    protected void onResume() {
        super.onResume();
    }
    protected void onRestart() {
        super.onRestart();
    }
    protected void onDestroy() {
        super.onDestroy();
    }
}

大家也可以一起尝试开发出自己的贪食蛇小游戏哟

  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值