Java实验——贪吃蛇小游戏
一、项目简介
贪吃蛇能够被键盘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<BodyObj> bodyObjList = new ArrayList<BodyObj>();
//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<BodyObj> 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&&this.y == bodyObjList.get(i).y){
GameWin.state = 3;
}
}
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&&food.y== this.y){
//获取蛇最后一节的坐标
BodyObj lastBody = this.frame.bodyObjList.get(this.frame.bodyObjList.size()-1);
X = lastBody.x;
Y = lastBody.y;
//蛇每吃一个食物,分数加一
GameWin.score++;
this.frame.foodObj = food.getFood();
}
move();
if(X != null && Y != null){
this.frame.bodyObjList.add(new BodyObj(GameUtils.bodyImg,X,Y,this.frame));
}
//越界处理
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);
}
}
- 运行效果截图,其他的自己去敲代码观察运行效果吧

- 参考代码博客链接

浙公网安备 33010602011771号