贪吃蛇能够被键盘wasd控制移动,小蛇每吃到一个食物,分数加1,撞到自己,则显示游戏结束。游戏可以被space键暂停继续。
二、功能架构图
三、功能详解
在600*600像素的方框中(蛇的主要移动范围),定义蛇移动一次以30*30的像素移动。
- 窗口的创建,单独创建一个窗口类,用来设置窗口的信息,后续信息逐步添加
public class GameWin extends JFrame { public void init(){ this.setVisible(true); this.setSize(600,600); this.setLocationRelativeTo(null); this.setTitle("贪吃蛇小游戏"); } public static void main(String[] args) { GameWin gameWin = new GameWin(); gameWin.init(); }}
- 创建整个游戏的物体的父类,大部分是get和set方法,还有构造方法,唯一注意的地方就只有一个paintSelf的方法,绘制自身的方法
import java.awt.*;public class GameObj { Image img; int x; int y; int width = 30; int height = 30; GameWin frame; public Image getImg() { return img; } public void setImg(Image img) { this.img = img; } public int getX() { return x; } public void setX(int x) { this.x = x; } public int getY() { return y; } public void setY(int y) { this.y = y; } public int getWidth() { return width; } public void setWidth(int width) { this.width = width; } public int getHeight() { return height; } public void setHeight(int height) { this.height = height; } public GameWin getFrame() { return frame; } public void setFrame(GameWin frame) { this.frame = frame; } public GameObj() { } public GameObj(Image img, int x, int y,GameWin frame) { this.img = img; this.x = x; this.y = y; this.frame = frame; } public void paintSelf(Graphics g){ g.drawImage(img,x,y,null); }}
- 创建游戏的工具类,将蛇的头部,身体还有食物,放进工作区中,将贴图 引入工具类中,再定义一个方法用来绘制文字。
import java.awt.*;public class GameUtils { public static Image headImage = Toolkit.getDefaultToolkit().getImage("img/head.png"); public static Image bodyImage = Toolkit.getDefaultToolkit().getImage("img/body.png"); public static Image foodImage = Toolkit.getDefaultToolkit().getImage("img/food.png"); public static void drawWord(Graphics g,String str,Color color,int size,int x,int y){ g.setColor(color); g.setFont(new Font("仿宋",Font.BOLD,size)); g.drawString(str,x,y); }}
- 创建蛇的头部(蛇的头部要继承游戏物体的父类GameObj),在蛇的头部类中,要增加一个蛇头的默认方向,默认方向向右。
- 先在窗口类中重写一个paint方法,然后在窗口创建蛇头对象,在paint方法中绘制蛇头,调用paintSelf方法。
import java.awt.*;public class HeadObj extends GameObj { private String direction = "right"; public String getDirection() { return direction; } public void setDirection(String direction) { this.direction = direction; } public HeadObj(Image img, int x, int y, GameWin frame) { super(img, x, y, frame); } @Override public void paintSelf(Graphics g) { super.paintSelf(g); }}
- 在蛇的头部类中添加一个move 方法,对蛇的方向进行判断。在paintSelf中调用move方法。在蛇头移动的过程中,要不停的repaint,在窗口类中的初始化窗口中不停的repaint,线程休眠200毫秒
- 实现键盘控制蛇头的简单移动,在蛇的头部类的构造方法中添加键盘监听事件。在蛇的头部类中,新加一个改变蛇头部移动的方法,在键盘监听事件中添加changeDirection方法
public void changeDirection(KeyEvent e){ switch(e.getKeyCode()){ case KeyEvent.VK_A: if(!direction.equals("right")){ direction = "left"; } break; case KeyEvent.VK_D: if(!direction.equals("left")){ direction = "right"; } break; case KeyEvent.VK_W: if(!direction.equals("down")){ direction = "up"; } break; case KeyEvent.VK_S: if(!direction.equals("up")){ direction = "down"; } break; default: break; }
- 对蛇越界进行处理,如果蛇从一个边界穿出,从相反方向穿进。在paintSelf方法中对蛇头坐标进行判断
- 对蛇身的添加,先创建蛇身的类,在窗口类中定义蛇身的集合,在init中对蛇身进行初始化,在paint中反向遍历身体的集合(以防止身体的重叠)。在蛇头移动的代码之前,添加蛇身的移动,从第二个身体开始,都是上一个身体的坐标,第一个是在头移动之前,头部的坐标。
import java.awt.*;public class BodyObj extends GameObj { public BodyObj(Image img, int x, int y, GameWin frame) { super(img, x, y, frame); } @Override public void paintSelf(Graphics g) { super.paintSelf(g); }}
- 蛇食物随机生成,蛇的食物随机生成范围是x:0-570,y:30-570。并且只能是30的倍数。新建一个食物类,在类中定义一个随机函数,写一个获取食物的方法,返回值是食物类。在窗口类中获取食物类的对象,在paint方法中绘制食物。
import java.awt.*;import java.util.Random;public class FoodObj extends GameObj { Random r = new Random(); public FoodObj() { super(); } public FoodObj getFood(){ return new FoodObj(GameUtils.foodImage,r.nextInt(20)*30,( r.nextInt(19)+1)*30,this.frame ); } public FoodObj(Image img, int x, int y, GameWin frame) { super(img, x, y, frame); } @Override public void paintSelf(Graphics g) { super.paintSelf(g); }}
- 蛇吃食物,吃完后,食物再次随机生成。在头部类中paintSelf方法中获取窗口类中的食物对象(一定要在蛇头移动之前),添加判断,判断食物是否和蛇头重合。如果重合就把新的随机生成的食物对象,赋值给窗口类中的foodObj。
- 蛇身体的增长,蛇吃到食物,蛇就应该增长一节,在头的paintSelf方法中,定义两个变量用来记录蛇身的最后一节坐标,获取蛇身最后一节的对象,把对象的坐标给X和Y。在move后,给一个新的身体对象,添加到蛇的身体的集合中。
- 设置计分面板(之前先将窗口扩大),蛇每吃一个食物,分数加一,在窗口类中添加静态变量分数,在paint方法中,把分数绘制出来。在头部类中,找到蛇吃食物的地方,分数累加。在paint中画线,以区分蛇的移动区域和计分面板
- 添加游戏开始的提示语,首先在窗口类中定义游戏状态的变量,有五种状态(0未开始,1游戏中,2暂停,3失败,4通关),绘制提示语,新建一个方法prompt(在里面绘制提示语),然后在paint方法中调用prompt方法
- 添加游戏开始和暂停的键盘事件,在窗口类的初始化窗口中,添加键盘事件,在键盘事件中添加对状态的判断,状态的相应改变
- 蛇头与身体的碰撞判断,在头部类的move方法中,判断蛇头的坐标与身体的坐标重合,则游戏失败,把游戏状态改为3.在prompt中添加游戏失败的提示语
- 简单处理一下零散的地方,排除bug......
有利于工程开发思维的培养,稳固知识点,增强团队分工合作的意识。意识到了自己知识水平还有很大缺陷,和高手之间还有很长的路要走。
五、展望
1,界面字有闪烁问题:双缓存解决游戏单调问题:增加关卡设置,增加障碍物,增加通关界面游戏运行效率不高:改进小蛇移动时候,贴图的移动方法,可以只改变蛇头和蛇尾使小蛇移动,增加多线程的应用2,进一步学习Java高级开发工具类,学习多线程,学习网络开发, docker容器,学习一些开发web技术
六、完整代码呈现,以及游戏截图,代码块有注释
- GameWin.java
package com.draw.test01;import javax.swing.*;import java.awt.*;import java.awt.event.KeyAdapter;import java.awt.event.KeyEvent;import java.util.ArrayList;import java.util.List;public class GameWin extends JFrame { //继承了Jframe类便具有了创建窗口,监听鼠标键盘事件的功能 //记录分数 public static int score = 0; //游戏状态 0未开始 1游戏中 2暂停 3失败 4通关 public static int state = 0; int winWidth = 800; int winHeight = 600; //蛇的头部 HeadObj headObj = new HeadObj(GameUtils.rightImg,30,30,this);//窗口引用是当前对象this //蛇身体的集合 public List bodyObjList = new ArrayList(); //food public FoodObj foodObj = new FoodObj().getFood(); public void init(){ //设置窗口是否可见 this.setVisible(true); //设置窗口大小 this.setSize(winWidth,winHeight); //设置窗口标题 this.setTitle("贪吃蛇小游戏"); //蛇身体的初始化 bodyObjList.add(new BodyObj(GameUtils.bodyImg,30,570,this)); bodyObjList.add(new BodyObj(GameUtils.bodyImg,0,570,this)); //添加键盘事件 this.addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { if(e.getKeyCode() == KeyEvent.VK_SPACE){ switch(state){ case 0: case 2: state =1; break; case 1: state = 2; repaint(); break; default: break; } } } }); while(true){ if(state == 1){ repaint(); } try { Thread.sleep(200);//每次重绘过后,线程休眠,1秒刷新5次 }catch(InterruptedException e){ e.printStackTrace(); } } } //重写paint方法 @Override public void paint(Graphics g) { //super.paint(g); //灰色背景 g.setColor(Color.GRAY); //填充矩形 g.fillRect(0,0,winWidth,winHeight); //网格线 g.setColor(Color.LIGHT_GRAY); //for(int i=1;i<=20;i++){ g.drawLine(600,30,600,600); // g.drawLine(30*i,0,30*i,600); //} //反向遍历身体的集合,防止想要的数据被覆盖 for (int i = bodyObjList.size()-1; i >= 0 ; i--) { bodyObjList.get(i).paintSelf(g); } //在paint 方法中绘制蛇头 headObj.paintSelf(g); //食物的绘制 foodObj.paintSelf(g); //将分数绘制出来 GameUtils.drawWord(g,score+" 分",Color.BLUE,50,650,300); g.setColor(Color.GRAY); prompt(g); } //绘制提示语 void prompt(Graphics g){ if(state == 0){ g.fillRect(120,240,400,70); GameUtils.drawWord(g,"按下空格开始游戏",Color.yellow,35,150,290); } if(state == 2){ g.fillRect(120,240,400,70); GameUtils.drawWord(g,"按下空格继续游戏",Color.BLUE,35,150,290); } if(state == 3){ g.fillRect(120,240,400,70); GameUtils.drawWord(g,"回家种地去吧",Color.RED,35,150,290); } } public static void main(String[] args) { GameWin gamewin = new GameWin(); gamewin.init(); }}
- GameUtils.java
package com.draw.test01;import java.awt.*;//游戏的工具类public class GameUtils { //蛇头 //public static Image upImg = Toolkit.getDefaultToolkit().getImage("img/test.png"); //public static Image dowmImg = Toolkit.getDefaultToolkit().getImage("img/test.png"); //public static Image leftImg = Toolkit.getDefaultToolkit().getImage("img/test.png"); public static Image rightImg = Toolkit.getDefaultToolkit().getImage("img/test.png"); //蛇身 public static Image bodyImg = Toolkit.getDefaultToolkit().getImage("img/body.png"); //蛇的食物 public static Image foodImg = Toolkit.getDefaultToolkit().getImage("img/food.png"); public static void drawWord(Graphics g,String str,Color color,int size,int x,int y){ g.setColor(color); g.setFont(new Font("仿宋",Font.BOLD,size)); g.drawString(str, x, y); }}
- GameObj.java
package com.draw.test01;import java.awt.*;//创建游戏物体的父类public class GameObj { //picture Image img; //coordinate int x; int y; int width = 30; int height = 30; //窗口类的引用 GameWin frame ; public Image getImg() { return img; } public void setImg(Image img) { this.img = img; } public int getX() { return x; } public GameObj(Image img, int x, int y, GameWin frame) { this.img = img; this.x = x; this.y = y; this.frame = frame; } public void setX(int x) { this.x = x; } public int getY() { return y; } public void setY(int y) { this.y = y; } public int getWidth() { return width; } public void setWidth(int width) { this.width = width; } public int getHeight() { return height; } public void setHeight(int height) { this.height = height; } public GameWin getFrame() { return frame; } public void setFrame(GameWin frame) { this.frame = frame; } //无参构造 public GameObj() { } //有参构造 public GameObj(Image img, int x, int y, int width, int height, GameWin frame) { this.img = img; this.x = x; this.y = y; this.width = width; this.height = height; this.frame = frame; } //绘制自身 public void paintSelf(Graphics g){ g.drawImage(img,x,y,null); }}
- HeadObj.java
package com.draw.test01;import java.awt.*;import java.awt.event.KeyAdapter;import java.awt.event.KeyEvent;import java.util.List;//蛇的头部public class HeadObj extends GameObj{ //方向 up down left right private String direction = "right"; public HeadObj(Image img, int x, int y, GameWin frame) { super(img, x, y, frame); //在构造方法中添加键盘监听事件 this.frame.addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { //super.keyPressed(e);//键盘按下、、获取到了键盘事件e changeDirection(e); } }); } //控制移动方向 public void changeDirection(KeyEvent e){ switch(e.getKeyCode()){ case KeyEvent.VK_A: if(!direction.equals("right")){ direction = "left"; } break; case KeyEvent.VK_D: if(!direction.equals("left")){ direction = "right"; } break; case KeyEvent.VK_S: if(!direction.equals("up")){ direction = "down"; } break; case KeyEvent.VK_W: if(!direction.equals("down")){ direction = "up"; } break; default: break; } } public String getDirection() { return direction; } public void setDirection(String direction) { this.direction = direction; } public void move (){ //在蛇头移动之前先把蛇身移动 List bodyObjList = this.frame.bodyObjList; for (int i = bodyObjList.size() - 1; i >= 1; i--) { bodyObjList.get(i).x = bodyObjList.get(i-1).x; bodyObjList.get(i).y = bodyObjList.get(i-1).y; if(this.x == bodyObjList.get(i).x } } bodyObjList.get(0).x = this.x; bodyObjList.get(0).y = this.y; switch(direction){ case "up": y -= height; break; case "down": y += height; break; case "left": x -= width; break; case "right": x += width; break; } } @Override public void paintSelf(Graphics g) { super.paintSelf(g); //获取窗口类中的食物对象,蛇吃食物 FoodObj food = this.frame.foodObj; //身体最后一节的坐标 Integer X = null; Integer Y = null; if(food.x == this.x X = lastBody.x; Y = lastBody.y; //蛇每吃一个食物,分数加一 GameWin.score++; this.frame.foodObj = food.getFood(); } move(); if(X != null } //越界处理 if(x<0){ x = 570; } else if(x>570){ x = 0; } else if(y<30){ y = 570; } else if(y>570){ y = 30; } }}
- BodyObj.java
package com.draw.test01;import java.awt.*;public class BodyObj extends GameObj{ public BodyObj(Image img, int x, int y, GameWin frame) { super(img, x, y, frame); } @Override public void paintSelf(Graphics g) { super.paintSelf(g); }}
- FoodObj.java
package com.draw.test01;import java.awt.*;import java.util.Random;public class FoodObj extends GameObj{ //定义随机函数 Random random = new Random(); public FoodObj() { super(); } public FoodObj(Image img, int x, int y, GameWin frame) { super(img, x, y, frame); } //获取食物 public FoodObj getFood(){ return new FoodObj(GameUtils.foodImg,random.nextInt(20)*30,(random.nextInt(19)+1)*30,this.frame); } @Override public void paintSelf(Graphics g) { super.paintSelf(g); }}
- 运行效果截图,其他的自己去敲代码观察运行效果吧
- 参考代码博客链接