说明:贪吃蛇游戏是一款比较经典的休闲游戏,这是我做的第一个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;
}
@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 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>
在窗体中设置一些按键的监听,以及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);
}
参考文章链接:
https://www.jb51.net/article/89477.htm
https://blog.csdn.net/linzekai100/article/details/80582116
源文件链接:
https://download.csdn.net/download/weixin_44799020/12604804