初级贪食蛇
效果展示:
界面设计
游戏界面的设计布局我们采用“相对布局中嵌套线性布局”这样做的好处是界面整体对称性更强,游戏体验更佳。
用相对布局将游戏界面分成两部分,一部分为游戏展示界面,一部分为按钮控制界面。
这里的界面设计着重是按钮布局的界面,游戏展示界面将在算法设计中的游戏面板类中展开。
游戏展示界面我们需要调用游戏面板类的内容:
<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); //分数
}
}
}
如图,蓝色为蛇头,绿色为蛇身,黄色为食物,红色为分数提示。
为了后续蛇身移动,定义方向,还需要一个大小更改的定义:
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();
}
}
大家也可以一起尝试开发出自己的贪食蛇小游戏哟