Java 贪吃蛇
Java制作简易贪吃蛇
绘制静态窗口
绘制静态窗口
- 创建 JFrame 类对象,设置窗体名称
- 设置窗体打开位置(与屏幕左上角(0,0)点的距离,以及窗体长宽)
- 设置窗口大小不可改变
- 设置窗体的关闭事件(可以在窗体上点击退出程序,否则只能关闭窗体,程序无法终止)
- 将设置好的窗体展示出来
//JFrame 使用的Swing包,用于绘图
import javax.swing.*;
public class SnakeDao {
//绘制静态窗体 jFrame
public static void window(){
//title : 窗体名称
JFrame frame = new JFrame("贪吃蛇小游戏");
//设置界面大小
//x , y : 打开后与屏幕左上角的距离
frame.setBounds(30,30,900,720);
//设置窗口大小不可改变(true 为可以改变)
frame.setResizable(false);
//设置窗体关闭事件,用于关闭游戏
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
//窗体可见化,让窗体可以展现出来
frame.setVisible(true);
}
}
运行结果:程序编译成功后,打开了一个空白窗体,不可改变大小,从距离屏幕左上角30,30处打开,窗体左上角会显示窗体名称(贪吃蛇小游戏),关闭窗体时,程序结束运行

绘制游戏面板
绘制游戏面板
- 重写父类(JPanel)
- 创建画板
- 调用父类,清空画板
- 设置画板颜色
//JPanel 使用swing包和awt包
import javax.swing.*;
import java.awt.*;
public class SnakeDao extends JPanel{
//创建画板
//Graphics:画笔
@Override
protected void paintComponent(Graphics g){
//调用父类,清空画板
super.paintComponent(g);
//设置画板背景颜色
this.setBackground(Color.BLACK);
}
}
将画板添加至窗体中
- 将画板添加至显示窗体之前的位置上即可
// ......
public static void window(){
//......
//将画板添加到窗体中(画板所在类)
frame.add(new SnakeDao());
//窗体可见化,让窗体可以展现出来
frame.setVisible(true);
}
// ......
运行结果:窗体从之前的白色变为了黑色(成功载入画板)

画板优化
- 导入广告栏图片
- 将广告栏图片导入到画板中
- 将黑底色改为白底色
导入广告栏图片
import javax.swing.*;
import java.net.URL;
//存放外部数据
public class Data {
//头部图片
public static URL headerUrl = Data.class.getResource("/statics/Header.png");
public static ImageIcon Header = new ImageIcon(headerUrl);
}
绘制到画板中
// ......
protected void paintComponent(Graphics g){
//调用父类,清空画板
super.paintComponent(g);
//设置画板背景颜色
this.setBackground(Color.BLACK);
//设置头部广告栏
//component: 指定画到哪个组件
//Graphics: 使用画笔绘画
//x,y: 绘画到的坐标
Data.Header.paintIcon(this,g,25,11);
//绘制游戏区域
g.fillRect(25,75,850,600);
}
// ......
运行结果: 黑色底变为白色底,头部有广告栏,下方为游戏区域

绘制静态小蛇
- 导入蛇头蛇身图片
- 初始化蛇长度、蛇头坐标、蛇身坐标
- 绘制静态蛇
导入图片
// ......
public class Data {
// ......
//蛇头
public static URL upUrl = Data.class.getResource("/statics/UP.png");
public static ImageIcon up = new ImageIcon(upUrl);
public static URL downUrl = Data.class.getResource("/statics/DOWN.png");
public static ImageIcon down = new ImageIcon(downUrl);
public static URL leftUrl = Data.class.getResource("/statics/LEFT.png");
public static ImageIcon left = new ImageIcon(leftUrl);
public static URL rightUrl = Data.class.getResource("/statics/RIGHT.png");
public static ImageIcon right = new ImageIcon(rightUrl);
//身体
public static URL bodyUrl = Data.class.getResource("/statics/BODY.png");
public static ImageIcon body = new ImageIcon(bodyUrl);
}
初始化蛇、绘制蛇
// ......
public class SnakeDao extends JPanel{
//蛇长
int length;
//坐标
int[] SnakeX = new int[850];
int[] SnakeY = new int[850];
//方向
String direction;
//初始化方法
public void init(){
//初始一个长度为三的向右的蛇
length = 3;
//头
SnakeX[0] = 100; SnakeY[0] = 100;
//身体
SnakeX[1] = 75; SnakeY[1] = 100;
SnakeX[2] = 50; SnakeY[2] = 100;
}
// ......
@Override
protected void paintComponent(Graphics g){
// ......
//绘制蛇头
if(direction.equals("R")){
Data.right.paintIcon(this,g,SnakeX[0],SnakeY[0]);
}else if(direction.equals("L")){
Data.left.paintIcon(this,g,SnakeX[0],SnakeY[0]);
}else if(direction.equals("U")){
Data.up.paintIcon(this,g,SnakeX[0],SnakeY[0]);
}else if(direction.equals("D")){
Data.down.paintIcon(this,g,SnakeX[0],SnakeY[0]);
}
//绘制蛇身
for(int i = 1; i < length; i++){
Data.body.paintIcon(this,g,SnakeX[1],SnakeY[1]);
}
}
}
// ......
运行结果: 画板上多了一条静态蛇

贪吃蛇动态化
设置提示开始
- 设置 是否开始 变量
- 画板判断是否开始(未开始游戏给出提示)
// ......
public class SnakeDao extends JPanel{
// ......
//游戏开始判断
boolean isStart = false;
//创建画板
@Override //Graphics:画笔
protected void paintComponent(Graphics g){
// ......
//提示是否开始游戏
if(isStart == false){
//设置文字颜色
g.setColor(Color.WHITE);
//设置字体 样式、粗体、大小
g.setFont(new Font("微软雅黑",Font.BOLD,40));
//绘制提示语
g.drawString("按下空格开始游戏",300,350);
}
}
// ......
}
// ......
运行结果:出现了提示语:按下空格开始游戏(当然真正按下后操作无法实现,后台代码还未填写)

设置键盘监听
- 本类作为实现类实现KeyListener接口
- 重写按下操作方法
- 构造方法监听键盘事件
// ......
public class SnakeDao extends JPanel implements KeyListener {
//......
//构造方法调用
public SnakeDao(){
// ......
//获取键盘监听事件: 1.获取键盘焦点 2.添加监听
this.setFocusable(true);
this.addKeyListener(this);
}
// ......
//按下按键
@Override
public void keyPressed(KeyEvent e) {
//接受键盘输入
int keyCode = e.getKeyCode();
//判断输入的为空格,开始游戏
if(keyCode == KeyEvent.VK_SPACE){
//反转判断开始变量,游戏开始(true)
isStart = !isStart;
//刷新画板
repaint();
}
}
//不使用以下两个方法
//敲击按键
@Override
public void keyTyped(KeyEvent e) {}
//释放按键
@Override
public void keyReleased(KeyEvent e) {}
}
// ......
运行结果:按下空格后文字消失,再按下空格文字出现。

小蛇动态化
- 定义时间变量
- 实现接口ActionListener
- 重写定时操作方法
- 构造方法中启动定时器
// ......
public class SnakeDao extends JPanel implements KeyListener, ActionListener {
// ......
//时间变量:1.delay --- 时间间隔(毫秒) 2.listener --- 监听对象
Timer timer = new Timer(100,this);
// ......
//构造方法调用
public SnakeDao(){
// ......
//初始化运行时间定时器
timer.start();
}
//计时器,监听时间(可以视为 帧)
//执行定时操作
@Override
public void actionPerformed(ActionEvent e) {
//判断游戏是否开始
if (isStart) {
//移动身体
for (int i = length - 1; i > 0; i--) {
SnakeX[i] = SnakeX[i - 1];
SnakeY[i] = SnakeY[i - 1];
}
//判断头部方向
if (direction.equals("R")) {
//移动头部
SnakeX[0] = SnakeX[0] + 25;
//边界判断
if(SnakeX[0]>850){
SnakeX[0] = 25;
}
} else if (direction.equals("L")) {
//移动头部
SnakeX[0] = SnakeX[0] - 25;
//边界判断
if(SnakeX[0] < 25){
SnakeX[0] = 850;
}
} else if (direction.equals("U")) {
//移动头部
SnakeY[0] = SnakeY[0] - 25;
//边界判断
if(SnakeY[0] < 75){
SnakeY[0] = 650;
}
} else if (direction.equals("D")) {
//移动头部
SnakeY[0] = SnakeY[0] + 25;
//边界判断
if(SnakeY[0] > 650){
SnakeY[0] = 75;
}
}
//刷新画板
repaint();
}
//启动定时器,让时间动起来
timer.start();
}
// ......
}
运行结果:小蛇可以动起来,无法操作方向,接触边界会从另一边边界进入游戏区域(关闭游戏窗体后需要手动关闭程序)
小蛇可操作化
键盘输入控制
- 添加控制方向代码块
// ......
public class SnakeDao extends JPanel implements KeyListener, ActionListener {
@Override
public void keyPressed(KeyEvent e) {
//接受键盘输入
int keyCode = e.getKeyCode();
//判断输入的为空格,开始游戏
if(keyCode == KeyEvent.VK_SPACE){
//反转判断开始变量,游戏开始(true)
isStart = !isStart;
//刷新画板
repaint();
}
//控制方向 WASD 、 ↑↓←→
if ((keyCode == KeyEvent.VK_A || keyCode == KeyEvent.VK_LEFT) && !direction.equals("R")){
direction = "L";
}else if ((keyCode == KeyEvent.VK_D || keyCode == KeyEvent.VK_RIGHT) && !direction.equals("L")){
direction = "R";
}else if ((keyCode == KeyEvent.VK_W || keyCode == KeyEvent.VK_UP) && !direction.equals("D")){
direction = "U";
}else if ((keyCode == KeyEvent.VK_S || keyCode == KeyEvent.VK_DOWN) && !direction.equals("U")){
direction = "D";
}
}
}
// ......
运行结果:小蛇可以上下左右转向了
置入小蛇食物
随机食物
- 定义食物坐标
- 初始化随机坐标
- 拾取后补充食物
// ......
public class SnakeDao extends JPanel implements KeyListener, ActionListener {
// ......
//食物坐标
int foodX;
int foodY;
// ......
//初始化方法
public void init(){
// ......
//初始化食物坐标
foodX = 25 + (int)(Math.random()*34)*25;
foodY = 75 + (int)(Math.random()*24)*25;
}
// ......
//创建画板
@Override //Graphics:画笔
protected void paintComponent(Graphics g){
// ......
//绘制食物
Data.food.paintIcon(this,g,foodX,foodY);
// ......
}
/计时器,监听时间(可以视为 帧)
//执行定时操作
@Override
public void actionPerformed(ActionEvent e) {
//判断游戏是否开始
if (isStart) {
// ......
if(SnakeX[0] == foodX && SnakeY[0] == foodY){
//吃到食物,身体加长
length++;
//重新生成食物坐标
foodX = 25 + (int)(Math.random()*34)*25;
foodY = 75 + (int)(Math.random()*24)*25;
}
//刷新画板
repaint();
}
// ......
}
}
运行结果:小蛇吃到食物增加一节身体,食物重新刷新,其中因为增加一节身体后未及时给当节身体赋坐标值,会在默认的(0,0)点闪一下
死亡以及得分
撞身死亡
- 声明死亡变量
- 画板死亡语句
- 键盘空格刷新
- 计时器死亡停止
// ......
public class SnakeDao extends JPanel implements KeyListener, ActionListener {
// ......
//判断是否失败
boolean isFail = false;
// ......
//创建画板
@Override //Graphics:画笔
protected void paintComponent(Graphics g){
// ......
//提示游戏结束
if(isFail){
//设置文字颜色
g.setColor(Color.WHITE);
//设置字体 样式、粗体、大小
g.setFont(new Font("微软雅黑",Font.BOLD,40));
//绘制提示语
g.drawString("游戏结束,按下空格开始新游戏",300,350);
}
}
//计时器,监听时间(可以视为 帧)
//执行定时操作
@Override
public void actionPerformed(ActionEvent e) {
//判断游戏是否开始
if (isStart && isFail == false) {
// ......
//结束判断
for(int i = 1; i < length; i++){
if(SnakeX[0] == SnakeX[i] && SnakeY[0] == SnakeY[i]){
isFail = true;
}
}
//刷新画板
repaint();
}
//启动定时器,让时间动起来
timer.start();
}
//键盘监听
//按下按键
@Override
public void keyPressed(KeyEvent e) {
//接受键盘输入
int keyCode = e.getKeyCode();
//判断输入的为空格,开始游戏
if(keyCode == KeyEvent.VK_SPACE){
if(isFail){
//重新初始化游戏
isFail = false;
init();
}else {
//暂停游戏
isStart = !isStart;
}
//刷新画板
repaint();
}
// ......
}
// ......
}
积分系统
- 创建积分变量
- 绘制积分面板
- 吃食增加积分
// ......
public class SnakeDao extends JPanel implements KeyListener, ActionListener {
// ......
//成绩系统
int score;
// ......
//初始化方法
public void init(){
// ......
//初始化分数
score = 0;
// ......
}
// ......
//创建画板
@Override //Graphics:画笔
protected void paintComponent(Graphics g){
// ......
//绘制积分区
g.setColor(Color.BLACK);
//设置字体 样式、粗体、大小
g.setFont(new Font("微软雅黑",Font.BOLD,15));
//绘制提示语
g.drawString("长度: " + length,750,35);
g.drawString("分数: " + score,750,50);
// ......
}
//计时器,监听时间(可以视为 帧)
//执行定时操作
@Override
public void actionPerformed(ActionEvent e) {
//判断游戏是否开始
if (isStart && isFail == false) {
// ......
if(SnakeX[0] == foodX && SnakeY[0] == foodY){
//吃到食物,身体加长
length++;
score = score + 10;
//重新生成食物坐标
foodX = 25 + (int)(Math.random()*34)*25;
foodY = 75 + (int)(Math.random()*24)*25;
}
// ......
}
// ......
}
}
运行结果:基本上贪吃蛇内容能够实现了。
打包发布方法
文件 → 项目结构

项目设置 → 工件 → +

JAR → 依赖模块

添加主类

确定设置

构建 → 构建工件

xxx.jar → 构建

out → xxx.jar → 打开于Explorer

全部代码浏览
功能实现类
package per.Snake.operation;
import per.Snake.util.Data;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
public class SnakeOperation extends JPanel implements KeyListener, ActionListener {
//蛇长
int length;
//蛇坐标
int[] SnakeX = new int[850];
int[] SnakeY = new int[850];
//方向
String direction;
//食物
int foodX;
int foodY;
//成绩系统
int score;
//游戏开始判断
boolean isStart = false;
//判断是否失败
boolean isFail = false;
//时间变量:1.delay --- 时间间隔(毫秒) 2.listener --- 监听对象
Timer timer = new Timer(100,this);
//初始化方法
public void init(){
//初始一个长度为三的向右的蛇
length = 3;
//初始化分数
score = 0;
//头
SnakeX[0] = 100; SnakeY[0] = 100;
//身体
SnakeX[1] = 75; SnakeY[1] = 100;
SnakeX[2] = 50; SnakeY[2] = 100;
//朝向
direction = "R";
foodX = 25 + (int)(Math.random()*34)*25;
foodY = 75 + (int)(Math.random()*24)*25;
}
//构造方法调用
public SnakeOperation(){
//初始化
init();
//获取键盘监听事件: 1.获取键盘焦点 2.添加监听
this.setFocusable(true);
this.addKeyListener(this);
//初始化运行时间定时器
timer.start();
}
//绘制静态窗体 jFrame
public void window(){
//title : 窗体名称
JFrame frame = new JFrame("贪吃蛇小游戏");
//设置界面大小
//x , y : 打开后与屏幕左上角的距离
frame.setBounds(30,30,900,720);
//设置窗口大小不可改变(true 为可以改变)
frame.setResizable(false);
//设置窗体关闭事件,用于关闭游戏
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
//将画板添加到JFrame中(画板所在类)
frame.add(new SnakeOperation());
//窗体可见化,让窗体可以展现出来
frame.setVisible(true);
}
//创建画板
@Override //Graphics:画笔
protected void paintComponent(Graphics g){
//调用父类,清空画板
super.paintComponent(g);
//设置画板背景颜色
this.setBackground(Color.WHITE);
//设置头部广告栏
//component: 指定画到哪个组件
//Graphics: 使用画笔绘画
//x,y: 绘画到的坐标
Data.Header.paintIcon(this,g,25,11);
//绘制游戏区域
g.fillRect(25,75,850,600);
//绘制蛇头
if(direction.equals("R")){
Data.right.paintIcon(this,g,SnakeX[0],SnakeY[0]);
}else if(direction.equals("L")){
Data.left.paintIcon(this,g,SnakeX[0],SnakeY[0]);
}else if(direction.equals("U")){
Data.up.paintIcon(this,g,SnakeX[0],SnakeY[0]);
}else if(direction.equals("D")){
Data.down.paintIcon(this,g,SnakeX[0],SnakeY[0]);
}
//绘制蛇身
for(int i = 1; i < length; i++){
Data.body.paintIcon(this,g,SnakeX[i],SnakeY[i]);
}
//绘制积分区
g.setColor(Color.BLACK);
//设置字体 样式、粗体、大小
g.setFont(new Font("微软雅黑",Font.BOLD,15));
//绘制提示语
g.drawString("长度: " + length,750,35);
g.drawString("分数: " + score,750,50);
//绘制食物
Data.food.paintIcon(this,g,foodX,foodY);
//提示是否开始游戏
if(isStart == false){
//设置文字颜色
g.setColor(Color.WHITE);
//设置字体 样式、粗体、大小
g.setFont(new Font("微软雅黑",Font.BOLD,40));
//绘制提示语
g.drawString("按下空格开始游戏",300,350);
}
//提示游戏结束
if(isFail){
//设置文字颜色
g.setColor(Color.WHITE);
//设置字体 样式、粗体、大小
g.setFont(new Font("微软雅黑",Font.BOLD,40));
//绘制提示语
g.drawString("游戏结束,按下空格开始新游戏",300,350);
}
}
//计时器,监听时间(可以视为 帧)
//执行定时操作
@Override
public void actionPerformed(ActionEvent e) {
//判断游戏是否开始
if (isStart && isFail == false) {
//移动身体
for (int i = length - 1; i > 0; i--) {
SnakeX[i] = SnakeX[i - 1];
SnakeY[i] = SnakeY[i - 1];
}
//判断头部方向
if (direction.equals("R")) {
//移动头部
SnakeX[0] = SnakeX[0] + 25;
//边界判断
if(SnakeX[0]>850){
SnakeX[0] = 25;
}
} else if (direction.equals("L")) {
//移动头部
SnakeX[0] = SnakeX[0] - 25;
//边界判断
if(SnakeX[0] < 25){
SnakeX[0] = 850;
}
} else if (direction.equals("U")) {
//移动头部
SnakeY[0] = SnakeY[0] - 25;
//边界判断
if(SnakeY[0] < 75){
SnakeY[0] = 650;
}
} else if (direction.equals("D")) {
//移动头部
SnakeY[0] = SnakeY[0] + 25;
//边界判断
if(SnakeY[0] > 650){
SnakeY[0] = 75;
}
}
if(SnakeX[0] == foodX && SnakeY[0] == foodY){
//吃到食物,身体加长
length++;
//得分
score = score + 10;
//重新生成食物坐标
foodX = 25 + (int)(Math.random()*34)*25;
foodY = 75 + (int)(Math.random()*24)*25;
}
//结束判断
for(int i = 1; i < length; i++){
if(SnakeX[0] == SnakeX[i] && SnakeY[0] == SnakeY[i]){
isFail = true;
}
}
//刷新画板
repaint();
}
//启动定时器,让时间动起来
timer.start();
}
//键盘监听
//按下按键
@Override
public void keyPressed(KeyEvent e) {
//接受键盘输入
int keyCode = e.getKeyCode();
//判断输入的为空格,开始游戏
if(keyCode == KeyEvent.VK_SPACE){
if(isFail){
//重新初始化游戏
isFail = false;
init();
}else {
//暂停游戏
isStart = !isStart;
}
//刷新画板
repaint();
}
//控制方向
if (!direction.equals("R") && (keyCode == KeyEvent.VK_A || keyCode == KeyEvent.VK_LEFT)){
direction = "L";
}else if (!direction.equals("L") && (keyCode == KeyEvent.VK_D || keyCode == KeyEvent.VK_RIGHT)){
direction = "R";
}else if (!direction.equals("D") && (keyCode == KeyEvent.VK_W || keyCode == KeyEvent.VK_UP)){
direction = "U";
}else if (!direction.equals("U") && (keyCode == KeyEvent.VK_S || keyCode == KeyEvent.VK_DOWN)){
direction = "D";
}
}
//不使用以下两个方法
//敲击按键
@Override
public void keyTyped(KeyEvent e) {
}
//释放按键
@Override
public void keyReleased(KeyEvent e) {
}
}
数据存储类
package per.Snake.util;
import javax.swing.*;
import java.net.URL;
//存放外部数据
public class Data {
//头部图片
public static URL headerUrl = Data.class.getResource("/statics/Header.png");
public static ImageIcon Header = new ImageIcon(headerUrl);
//蛇头
public static URL upUrl = Data.class.getResource("/statics/UP.png");
public static ImageIcon up = new ImageIcon(upUrl);
public static URL downUrl = Data.class.getResource("/statics/DOWN.png");
public static ImageIcon down = new ImageIcon(downUrl);
public static URL leftUrl = Data.class.getResource("/statics/LEFT.png");
public static ImageIcon left = new ImageIcon(leftUrl);
public static URL rightUrl = Data.class.getResource("/statics/RIGHT.png");
public static ImageIcon right = new ImageIcon(rightUrl);
//身体
public static URL bodyUrl = Data.class.getResource("/statics/BODY.png");
public static ImageIcon body = new ImageIcon(bodyUrl);
//食物
public static URL foodUrl = Data.class.getResource("/statics/FOOD.png");
public static ImageIcon food = new ImageIcon(foodUrl);
}
主方法调用
package per.Snake.test;
import per.Snake.operation.SnakeOperation;
public class TestSnake {
public static void main(String[] args) {
SnakeOperation player = new SnakeOperation();
player.window();
}
}
bug的修改方法
身体增加零点闪烁身体问题
这个问题是因为没有及时给新添加的身体赋值坐标导致,该身体的坐标使用 int 类型默认值赋值,所以只需要在计时器重写方法中的 length ++ 后 立刻赋值给其倒数第二节身体坐标即可
if(SnakeX[0] == foodX && SnakeY[0] == foodY){
//吃到食物,身体加长
length++;
//长度增加后立刻给其新节身体赋值
SnakeX[length - 1] = SnakeX[length - 2];
SnakeY[length - 1] = SnakeY[length - 2];
//得分
score = score + 10;
//重新生成食物坐标
foodX = 25 + (int)(Math.random()*34)*25;
foodY = 75 + (int)(Math.random()*24)*25;
}