说明:贪吃蛇游戏是一款比较经典的休闲游戏,这是我做的第一个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;}
游戏界面
一、主游戏界面
主游戏界面包括了小蛇的运动界面,上方的暂停对话框、分数显示的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;}@Overridepublic 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+1int newY = 3+RNG.nextInt(20);//坐标0-23+3newCoord = 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+1int newY = 3+RNG.nextInt(20);//坐标0-23+3newCoord = 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"><LinearLayoutandroid: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"><ImageButtonandroid:id="@+id/pause"android:layout_width="50dp"android:layout_height="50dp"android:layout_marginStart="30dp"android:layout_marginTop="15dp"android:background="@drawable/pause" /><TextViewandroid: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.Snakespaceandroid: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"/><ImageButtonandroid: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"/><LinearLayoutandroid: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"><ImageButtonandroid:id="@+id/left"android:layout_width="75dp"android:layout_height="75dp"android:background="@drawable/left" /><ImageButtonandroid:id="@+id/down"android:layout_width="75dp"android:layout_height="75dp"android:background="@drawable/down" /><ImageButtonandroid: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;@Overrideprotected 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() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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();}@Overridepublic 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() {@Overridepublic void onItemSelected(AdapterView<?> parent, View view, int position, long id) {mode2 =m[position];setrank(mode2);}@Overridepublic 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() {@Overridepublic void onClick(View v) {finish();}});}
//音乐设置 采用service和Mediaplayer@Overridepublic 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);}
参考文章链接:
https://www.jb51.net/article/89477.htm
https://blog.csdn.net/linzekai100/article/details/80582116
源文件链接:
https://download.csdn.net/download/weixin_44799020/12604804