Android贪吃蛇游戏实现

说明:贪吃蛇游戏是一款比较经典的休闲游戏,这是我做的第一个Android项目,会存在一些问题,文章包括项目的部分源码以及运行界面的一些图片,项目也参考学习了许多大佬的文章,会在文章最后面贴出参考文章,包括源码的链接,有什么问题也请大家指正。

登录与注册

登录界面和注册界面的后台数据采用了Android自带的sqlite数据库进行存储,通过sqlite的助手类对用户添加和用户的登录进行操作,这里贴一些关于登录和注册的方法。

 //添加新用户,即注册
    public long insertUserData(UserData userData) {
        String userName=userData.getUserName();
        String userPwd=userData.getUserPwd();
        ContentValues values = new ContentValues();
        values.put(USER_NAME, userName);
        values.put(USER_PWD, userPwd);
        return mSQLiteDatabase.insert("users", ID, values);
    }
    //根据用户名找用户,可以判断注册时用户名是否已经存在
    public int findUserByName(String userName){

        int result=0;
        String Query = "Select * from users where USER_NAME =?";
        Cursor cursor = mSQLiteDatabase.rawQuery(Query,new String[] { userName });
        if(cursor!=null){
            result=cursor.getCount();
            Log.w("result=",String.valueOf(result));
            cursor.close();
        }
        return result;
    }
    //根据用户名和密码找用户,用于登录
    public int findUserByNameAndPwd(String userName,String pwd){
        int result=0;
        String Query = "Select * from users where USER_NAME =? and USER_PWD =?";
        Cursor mCursor = mSQLiteDatabase.rawQuery(Query,new String[] { userName,pwd});
        if(mCursor!=null){
            result=mCursor.getCount();
            mCursor.close();
        }
        return result;
    }

游戏界面

Android贪吃蛇游戏实现_第1张图片

一、主游戏界面

主游戏界面包括了小蛇的运动界面,上方的暂停对话框、分数显示的TextView,以及下方的方向键采用了ImageButton。主要说一下小蛇运动界面的实现,这是我参考了一位大佬的做法,先在Android写一个View的子类,将界面划分为32*32的小方格坐标存储在一个二维数组中,然后新建一个蛇运动的界面类,将界面的一些图片填入到方格中。

    Bitmap[] mTileArray;            //放置图片的数组
    int[][] mTileGrid;              //存放各坐标对应的图片
    //加载三幅小图片,包括墙,蛇身,蛇头
    public void loadTile(int key, Drawable tile) {
        Bitmap bitmap = Bitmap.createBitmap(mTileSize, mTileSize, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        tile.setBounds(0, 0, mTileSize, mTileSize);
        tile.draw(canvas);
        mTileArray[key] = bitmap;
    }
    @Override
    public void onSizeChanged(int w, int h, int oldw, int oldh){
        //地图数组初始化
        mXTileCount = (int) Math.floor(w / mTileSize);
        mYTileCount = (int) Math.floor(h / mTileSize);
        //够分成一格的分成一格, 剩下不够一格的分成两份,左边一份,右边一份
        mXOffset = ((w - (mTileSize * mXTileCount)) / 2);
        mYOffset = ((h - (mTileSize * mYTileCount)) / 2);
        mTileGrid = new int[mXTileCount][mYTileCount];
        clearTiles();
    }
    //给界面的二维数组赋值,坐标存放想要放的图片
    public void setTile(int tileindex, int x, int y) {
        mTileGrid[x][y] = tileindex;
    }

二、食物的生成

食物是通过random产生随机坐标,存放到食物的Arraylist中,然后调用父类的方法将图片画在view上。

    private ArrayList<Coordinate> mFoodList = new ArrayList<Coordinate>(); // 存储食物的所有坐标的数组
     private static final Random RNG = new Random();//随机生成食物的坐标
 private void updateFood() {
        for (Coordinate c : mFoodList) {
            setTile(snakebody, c.x, c.y);
        }
    }
 private void addRandomFood() {
        Coordinate newCoord = null;
        boolean found = false;
        while (!found) {
            int newX = 1+RNG.nextInt(28);//坐标0-22+1
            int newY = 3+RNG.nextInt(20);//坐标0-23+3
            newCoord = new Coordinate(newX, newY);
            boolean collision = false;
            int snakelength = mSnakeTrail.size();
            //遍历snake, 看新添加的apple是否在snake体内, 如果是,重新生成坐标
            for (int index = 0; index < snakelength; index++) {
                if (mSnakeTrail.get(index).equals(newCoord)) {
                    collision = true;
                }
            }for (int index = 0; index < mObstacle.size(); index++) {
                if (mObstacle.get(index).equals(newCoord)) {
                    collision = true;
                }
            }
            found = !collision;
        }
        mFoodList.add(newCoord);//储存已产生坐标
    }

三、蛇的移动和死亡

实际上蛇的移动是通过界面的重新绘制实现的,通过handle控制界面重新绘制的时间,点击方向键就改变蛇头的坐标,往某一方向进行x坐标或y坐标的增加。通过遍历食物和障碍物的Arraylist进行检测是否吃到食物或撞到障碍物,围墙的控制是通过边界坐标来控制的,如果超过边界坐标,那么游戏结束。

    private ArrayList<Coordinate> mSnakeTrail = new ArrayList<Coordinate>(); // 存储蛇的所有坐标的数组
    private ArrayList<Coordinate> mObstacle = new ArrayList<>();//存储障碍物坐标
    private void updateObstacle() {
        for (Coordinate c : mObstacle) {
            setTile(wall, c.x, c.y);
        }
    }
    private void addrandomObstacle(int count) {
        Coordinate newCoord = null;
       if(count ==4){
            int newX = 1+RNG.nextInt(28);//坐标0-22+1
            int newY = 3+RNG.nextInt(20);//坐标0-23+3
            newCoord = new Coordinate(newX, newY);
            //遍历snake, 看新添加的apple是否在snake体内, 如果是,重新生成坐标
        mObstacle.add(newCoord);//储存已产生坐标
    }}
    private void updateSnake() {
        boolean growSnake = false;
        Coordinate head = mSnakeTrail.get(0);
        Coordinate newHead = new Coordinate(1, 1);

        mDirection = mNextDirection;
        switch (mDirection) {//蛇移动的方向
            case RIGHT: {
                newHead = new Coordinate(head.x + 1, head.y);
                break;
            }
            case LEFT: {
                newHead = new Coordinate(head.x - 1, head.y);
                break;
            }
            case UP: {
                newHead = new Coordinate(head.x, head.y - 1);
                break;
            }
            case DOWN: {
                newHead = new Coordinate(head.x, head.y + 1);
                break;
            }
        }
            //检测投是否撞墙
            if ((newHead.x < 1) || (newHead.y < 3) || (newHead.x > mXTileCount-2 )
                    || (newHead.y > mYTileCount-9 )) {
                setMode(LOSE);
                return;
            }
            //检测蛇头是否撞到自己
            int snakelength = mSnakeTrail.size();
            for (int snakeindex = 0; snakeindex < snakelength; snakeindex++) {
                Coordinate c = mSnakeTrail.get(snakeindex);
                if (c.equals(newHead)) {
                    setMode(LOSE);
                    return;
                }
            }
            //检测蛇头撞见障碍物
            for(int index = 0;index < mObstacle.size();index++ ){
                Coordinate c = mObstacle.get(index);
                if (c.equals(newHead)) {
                    setMode(LOSE);
                    return;
                }
            }
            //检测蛇是否吃到食物
            int foodcount = mFoodList.size();
            for (int foodindex = 0; foodindex < foodcount; foodindex++) {
                Coordinate c = mFoodList.get(foodindex);
                if (c.equals(newHead)) {
                    mFoodList.remove(c);
                    addRandomFood();
                    mScore++;
                    mMoveDelay *= 0.95;  //蛇每迟到一个食物,延时就会减少,蛇的速度就会加快
                    growSnake = true;
                    if(mMoveDelay<150) {
                        count++;
                        Log.w("count", "" + count);
                        if (count == 4) {
                            addrandomObstacle(count);
                            count = 0;
                        }
                    }
                }
            }
            mSnakeTrail.add(0, newHead);
            if (!growSnake) {
                mSnakeTrail.remove(mSnakeTrail.size() - 1);
            }
            //添加蛇的图片
            int index = 0;
            for (Coordinate c : mSnakeTrail) {
                if (index == 0) {
                    setTile(snakehead, c.x, c.y);
                } else {
                    setTile(snakebody, c.x, c.y);
                }
                index++;
            }
        }

四、游戏界面的布局xml

在布局中引入前面做好游戏界面,同时添加暂停,分数显示等。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/dark"
    android:orientation="vertical"
    tools:context=".GameActivity">
    <LinearLayout
        android:id="@+id/linearlayout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">
        <ImageButton
            android:id="@+id/pause"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:layout_marginStart="30dp"
            android:layout_marginTop="15dp"
            android:background="@drawable/pause" />
        <TextView
            android:id="@+id/showscore"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="100dp"
            android:layout_marginTop="15dp"
            android:text="10"
            android:textSize="35sp" />
    </LinearLayout>
    <com.example.snake.Snakespace
        android:id="@+id/snakeview"
        android:layout_width="match_parent"
        android:layout_height="480dp"
        android:layout_marginTop="10dp"
        app:layout_constraintTop_toBottomOf="@id/linearlayout"
        app:layout_constraintStart_toStartOf="parent"/>
    <ImageButton
        android:id="@+id/up"
        android:layout_width="75dp"
        android:layout_height="75dp"
        android:layout_gravity="center"
        android:layout_marginTop="20dp"
        android:background="@drawable/up"
        app:layout_constraintBottom_toBottomOf="@id/snakeview"
        app:layout_constraintStart_toStartOf="@id/snakeview"
        app:layout_constraintEnd_toEndOf="@id/snakeview"/>

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:orientation="horizontal"
        app:layout_constraintTop_toBottomOf="@id/up"
        app:layout_constraintStart_toStartOf="@id/up"
        app:layout_constraintEnd_toEndOf="@id/up">
        <ImageButton
            android:id="@+id/left"
            android:layout_width="75dp"
            android:layout_height="75dp"
            android:background="@drawable/left" />
        <ImageButton
            android:id="@+id/down"
            android:layout_width="75dp"
            android:layout_height="75dp"
            android:background="@drawable/down" />
        <ImageButton
            android:id="@+id/right"
            android:layout_width="75dp"
            android:layout_height="75dp"
            android:background="@drawable/right" />
    </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

五、Mainactivity

在窗体中设置一些按键的监听,以及dialog的实现。

public class GameActivity extends AppCompatActivity implements View.OnClickListener {
    private static final String ICICLE_KEY = "aa";
    ImageButton pause;
    private  Snakespace snakeview;
    private ImageButton mLeft,mRight,mUp,mDown,out;
    private static final int UP = 1;
    private static final int DOWN = 2;
    private static final int RIGHT = 3;
    private static final int LEFT = 4;
    private SharedPreferences sp;
    private SharedPreferences.Editor editor;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        supportRequestWindowFeature(Window.FEATURE_NO_TITLE);//去掉标题栏
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);//全屏显示
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_game);
        snakeview = findViewById(R.id.snakeview);
        sp = getSharedPreferences("difficulty",MODE_PRIVATE);
        mMoveDelay = sp.getInt("model",500);
        mMode = 2;
        textView = findViewById(R.id.showscore);
        //方向键控制移动
        mLeft = findViewById(R.id.left);
        mRight = findViewById(R.id.right);
        mUp = findViewById(R.id.up);
        mDown = findViewById(R.id.down);
        mLeft.setOnClickListener(this);
        mUp.setOnClickListener(this);
        mRight.setOnClickListener(this);
        mDown.setOnClickListener(this);
        //点击暂停弹出对话框
        pause = findViewById(R.id.pause);
        pause.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mMode = 1;
              mydialog();
            }
           });
    }
    public  void gameover(){
        Intent it = new Intent(GameActivity.this,MainActivity.class);
        startActivity(it);
    }
    public void onClick(View v) {
        switch (v.getId()) {
            // 使界面上的方向按钮起作用
            case R.id.left:
                if (mDirection != RIGHT) {
                    mNextDirection = LEFT;
                }
                break;
            case R.id.right:
                if (mDirection != LEFT) {
                    mNextDirection = RIGHT;
                }
                break;
            case R.id.up:
                if (mDirection != DOWN) {
                    mNextDirection = UP;
                }
                break;
            case R.id.down:
                if (mDirection != UP) {
                    mNextDirection = DOWN;
                }
                break;
            default:
                break;
        }
    }
    private void mydialog() {
        final Dialog dialog = new Dialog(this);
        dialog.setCanceledOnTouchOutside(true);//点击外部 dialog 消失
        dialog.setContentView(R.layout.dialog);//添加自定义布局
     dialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent);//设置背景透明
        dialog.show();
        Window dialogWindow = dialog.getWindow();
        dialogWindow.setGravity(Gravity.LEFT | Gravity.CENTER);
        //继续游戏
        final ImageButton start = dialog.findViewById(R.id.game_start);
        start.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View v) {
              snakeview.setMode(RUNNING);
              Toast.makeText(GameActivity.this, "继续游戏", Toast.LENGTH_SHORT).show();
              snakeview.update();
              dialog.dismiss();
          }
        });
        //重新开始游戏
        ImageButton restart = dialog.findViewById(R.id.game_restart);
        restart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
               Toast.makeText(GameActivity.this, "重新开始", Toast.LENGTH_SHORT).show();
               restart(dialog);
            }
        });
        //停止游戏
        ImageButton over = dialog.findViewById(R.id.game_over);
        over.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                snakeview.setMode(QUIT);
                dialog.dismiss();
                Toast.makeText(GameActivity.this, "停止游戏", Toast.LENGTH_SHORT).show();
                Intent it = new Intent(GameActivity.this,choice.class);
                startActivity(it);
            }
        });
    }
    public void restart(Dialog dialog){

        mMoveDelay=sp.getInt("model",0);
        Log.w("nandu",String.valueOf(mMoveDelay));
        snakeview.initNewGame();
        snakeview.setMode(RUNNING);
        snakeview.update();
        dialog.dismiss();
    }
    @Override
    public void onSaveInstanceState(Bundle outState) {
        //保存游戏状态
        super.onSaveInstanceState(outState);
        outState.putBundle(ICICLE_KEY, snakeview.saveState());
    }
}

其他

上面大概就是主界面实现的一些思路,除了主界面的实现,我也做了一些其他设置,像游戏得分排行榜,背景音乐设置,难度设置,内容比较多,这里只贴部分源码和程序界面图。

//排行榜界面的oncreat方法
    protected void onCreate(Bundle savedInstanceState) {
        supportRequestWindowFeature(Window.FEATURE_NO_TITLE);//去掉标题栏
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);//全屏显示
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_rank);
        spinner = findViewById(R.id.spinner);  //将可选内容与ArrayAdapter连接起来
        adapter = new ArrayAdapter<>(this,android.R.layout.simple_spinner_item,m);
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);//设置下拉列表的风格
        spinner.setAdapter(adapter);//将adapter 添加到spinner中
        spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                mode2 =m[position];
                setrank(mode2);
            }
            @Override
            public void onNothingSelected(AdapterView<?> parent) {
                mode2 ="'简单'";
                setrank(mode2);
            }
        });//添加事件Spinner事件监听
        spinner.setVisibility(View.VISIBLE);  //设置默认值
        lv = findViewById(R.id.lv);
        home = findViewById(R.id.home);
        home.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });
    }
//音乐设置 采用service和Mediaplayer
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        int music = intent.getIntExtra("music",0);
        Log.w("music=",String.valueOf(music));
        switch (music){
            case 1:
                this.mediaPlayer = MediaPlayer.create(this,R.raw.music1);
                break;
            case 2:
                this.mediaPlayer = MediaPlayer.create(this,R.raw.music2);
                break;
            case 3:
                this.mediaPlayer = MediaPlayer.create(this,R.raw.music3);
                break;
            default:
                break;
        }
        this.mediaPlayer.seekTo(0);
        this.mediaPlayer.setLooping(true);
        this.mediaPlayer.start();
        return super.onStartCommand(intent, flags, startId);
    }

Android贪吃蛇游戏实现_第2张图片Android贪吃蛇游戏实现_第3张图片
参考文章链接:
https://www.jb51.net/article/89477.htm
https://blog.csdn.net/linzekai100/article/details/80582116
源文件链接:
https://download.csdn.net/download/weixin_44799020/12604804

你可能感兴趣的:(Android贪吃蛇游戏实现)