java GUI编程实现贪吃蛇小游戏
一 准备
首先准备三张图片,分别是:蛇头,蛇身体,食物。(尺寸 25 * 25 像素)
我创建的是一个springboot项目,所以我把这些静态资源放在了resources目录下的static文件夹下,创建个文件夹叫snake,再把这三张图片放进去。
二 新建一个Data类,作为图片资源
import cn.hutool.core.io.resource.ClassPathResource;
import javax.swing.*;
import java.net.URL;
/**
* create by fzg
* 2022/11/15 13:07
* 获取数据,图片
*/
// 数据:图片资源
public class Data {
// 图片资源放哪里都可以,只要能找到,并且将类型转化为URL
// 我用的是胡图工具的 ClassPathResource
// 蛇头
public static URL headerURL = new ClassPathResource("/static/snake/header.png").getUrl();
// 蛇身体
public static URL bodyURL = new ClassPathResource("/static/snake/body.png").getUrl();
// 食物
public static URL foodURL = new ClassPathResource("/static/snake/food.png").getUrl();
public static ImageIcon header = new ImageIcon(headerURL);
public static ImageIcon body = new ImageIcon(bodyURL);
public static ImageIcon food = new ImageIcon(foodURL);
}
三 Snake类,定义蛇的数据结构和初始化方法
/**
* create by fzg
* 2022/11/15 13:33
*/
// 贪吃蛇基本类
public class Snake {
// 定义蛇的数据结构
int length; // 蛇的长度
int[] snakex = new int[600]; // 蛇的x坐标
int[] snakey = new int[500]; // 蛇的y坐标
// 初始化方向
String fx;
// 初始化方法
public void init(){
// 初始化长度3
length = 3;
// 脑袋的坐标
snakex[0] = 100;
snakey[0] = 100;
// 第一个身体的坐标
snakex[1] = 75;
snakey[1] = 100;
// 第二个身体的坐标
snakex[2] = 50;
snakey[2] = 100;
// 初始化方向往右
fx = "D";
}
public Snake(){
init();
}
}
四 游戏面板类 GamePanel(核心代码)
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;
import java.util.Random;
/**
* create by fzg
* 2022/11/15 13:39
*/
/**
* 游戏面板
* 键盘监听器
* 定时器
*/
public class GamePanel extends JPanel implements KeyListener, ActionListener {
// 游戏当前的状态
boolean isStart = false; // 默认不开始
// 游戏失败,默认FALSE
boolean isFail = false;
// 初始化成绩
int score = 0;
Snake snake = new Snake();
// 定时器(小蛇快慢)
Timer timer = new Timer(500,this);
// 食物坐标
int foodx;
int foody;
// 前一个按键
String prePressKey = "";
Random random = new Random();
public GamePanel(){
// 获取焦点
this.setFocusable(true);
// 获取键盘的监听事件
this.addKeyListener(this);
timer.start();
createFood();
}
/**
* 绘制面板,
* 绘制游戏的画面
*/
@Override
protected void paintComponent(Graphics g){
// 清屏,没有这段代码就会不停闪屏
super.paintComponent(g);
// 绘制静态面板,背景颜色:白色
this.setBackground(Color.WHITE);
// 默认游戏界面
g.fillRect(25,75,850,600);
// 食物
Data.food.paintIcon(this,g,foodx,foody);
// 画积分
g.setColor(Color.blue);
g.setFont(new Font("微软雅黑",Font.BOLD,18));
g.drawString("长度: ",750,35);
g.drawString("分数: ",750,53);
g.setColor(Color.RED);
g.drawString(snake.length + "",800,35);
g.drawString(score + "",800,53);
// 画小蛇
for (int i = 0; i < snake.length; i++) {
if (i == 0){
// 画脑袋
Data.header.paintIcon(this,g,snake.snakex[i],snake.snakey[i]);
}else {
Data.body.paintIcon(this,g,snake.snakex[i],snake.snakey[i]);
}
}
// 游戏未开始时
if (!isStart){
g.setColor(Color.WHITE);
g.setFont(new Font("微软雅黑",Font.BOLD,40));
g.drawString("按下空格开始游戏", 200,300);
// 说明一下:请确保键盘的WSAD键处于英文状态下
g.setColor(Color.GREEN);
g.setFont(new Font("微软雅黑",Font.PLAIN,25));
g.drawString("请确保键盘上的WSAD键处于英文状态",200,350);
g.drawString("除了键盘上的WSAD键,还可以按up,down,left,right键",200,400);
}
// 游戏失败
if (isFail){
g.setColor(Color.RED);
g.setFont(new Font("微软雅黑",Font.BOLD,40));
g.drawString("游戏结束",300,200);
g.setFont(new Font("微软雅黑",Font.PLAIN,30));
g.drawString("按下空格重新开始游戏", 300,300);
timer.setDelay(500);
}
}
/**
* Invoked when an action occurs.
*
* @param e
*
* 事件监听
* 通过固定事件监听
*/
@Override
public void actionPerformed(ActionEvent e) {
if (isStart && !isFail){
// 如果游戏状态是开始,就让小蛇动起来
for (int i = snake.length - 1; i > 0; i--) {
snake.snakex[i] = snake.snakex[i - 1];
snake.snakey[i] = snake.snakey[i - 1];
}
// 走向
switch (snake.fx){
case "D":
snake.snakex[0] = snake.snakex[0] + 25;
// 边界判断(撞墙就死)
if (snake.snakex[0] > 850){
// 这是小蛇撞墙不会死,从另一边出来
// snake.snakex[0] = 25;
// 让小蛇撞墙就game over
isFail = true;
}
break;
case "A":
snake.snakex[0] = snake.snakex[0] - 25;
// 边界判断(撞墙就死)
if (snake.snakex[0] < 25){
// snake.snakex[0] = 850;
isFail = true;
}
break;
case "W":
snake.snakey[0] = snake.snakey[0] - 25;
// 边界判断(撞墙就死)
if (snake.snakey[0] < 75){
// snake.snakey[0] = 650;
isFail = true;
}
break;
case "S":
snake.snakey[0] = snake.snakey[0] + 25;
// 边界判断(撞墙就死)
if (snake.snakey[0] > 650){
// snake.snakey[0] = 75;
isFail = true;
}
break;
}
// 把下面的代码注释,让小蛇可以掉头,并且吃到自己的身体也不会死
// 判断失败,撞到自己就算失败
// for (int i = 1; i < snake.length; i++) {
// if (snake.fx.equals("W") && prePressKey.equals("S")){
// isFail = false;
// }else if (snake.fx.equals("S") && prePressKey.equals("W")){
// isFail = false;
// }else if (snake.fx.equals("A") && prePressKey.equals("D")){
// isFail = false;
// }else if(snake.fx.equals("D") && prePressKey.equals("A")){
// isFail = false;
// }else {
// if (snake.snakex[0] == snake.snakex[i] && snake.snakey[0] == snake.snakey[i]) {
// isFail = true;
// }
// }
// }
// 吃到食物
if (snake.snakex[0] == foodx && snake.snakey[0] == foody){
// 长度加1
snake.length++;
// 加分
score = addCarry(score,10);
/**
* 分数等级:
* 0 - 100:原速度 0.5s
* 100 - 150:-100毫秒 0.4s
* 150 - 200:-100毫秒 0.3s
* 200 - 250:-100毫秒 0.2s
* 250 - 300: -100毫秒 0.1s
* 300 - 350: -50毫秒 0.05s 最快速度了
*/
if (score >= 10 && score < 40){
timer.setDelay(400);
}else if (score >= 40 && score < 60){
timer.setDelay(300);
}else if (score >= 60 && score < 80){
timer.setDelay(200);
}else if (score >= 80 && score < 100){
timer.setDelay(100);
}else if (score >= 100){
timer.setDelay(50);
}
// 再次生成食物
createFood();
}
// 重画页面
repaint();
}
// 开启定时
timer.start();
}
/**
* 生成食物
* g.fillRect(25,75,850,600); 界面的位置和宽高,
* 游戏界面坐标:25,75 ;距离左边25,距离上边75
* 宽度:850 ; 高度: 600
* 食物必须随机生成在游戏界面内
* 所以:食物的x坐标 必须在25 到 850 + 25之间,
* 如果刚好等于850 + 25那么食物就生成在游戏界面的右边所以不能等于850 + 25
* y坐标也是
* 所以食物的生成坐标区间
* x: [25,850]
* y: [75,675-25]
*/
public void createFood(){
/**
* 把食物随机分配到界面上
* random.nextInt生成随机整数
* 850 / 25 = 34 最大值
* 650 / 25 = 26 最大值
*/
foodx = 25 + 25 * random.nextInt(34);
foody = 75 + 25 * random.nextInt(26);
}
/**
* 位运算实现相加
*/
int addCarry(int a,int b){
int result;
int sum,carry;
sum = a ^ b;
carry = (a & b) << 1;
if (carry == 0){
result = sum;
}else {
result = addCarry(sum,carry);
}
return result;
}
/**
* Invoked when a key has been typed.
* See the class description for {@link KeyEvent} for a definition of
* a key typed event.
*
* @param e
*/
@Override
public void keyTyped(KeyEvent e) {
}
/**
* Invoked when a key has been pressed.
* See the class description for {@link KeyEvent} for a definition of
* a key pressed event.
* 键盘按下
* @param e
*/
@Override
public void keyPressed(KeyEvent e) {
// 获取键盘按键是哪个
int keyCode = e.getKeyCode();
/**
* 上:38
* 下:40
* 左:37
* 右:39
*/
if (keyCode == KeyEvent.VK_SPACE){
// 按下的是空格
if (isFail){
// 重新开始
isFail = false;
snake.init();
createFood();
score = 0;
}else {
isStart = !isStart;
}
repaint();
}
// 小蛇移动
switch (keyCode){
case KeyEvent.VK_W:
snake.fx = "W";
break;
case KeyEvent.VK_S:
snake.fx = "S";
break;
case KeyEvent.VK_A:
snake.fx = "A";
break;
case KeyEvent.VK_D:
snake.fx = "D";
break;
case 37:
snake.fx = "A";
break;
case 38:
snake.fx = "W";
break;
case 39:
snake.fx = "D";
break;
case 40:
snake.fx = "S";
break;
}
prePressKey = snake.fx;
}
/**
* Invoked when a key has been released.
* See the class description for {@link KeyEvent} for a definition of
* a key released event.
*
* @param e
*/
@Override
public void keyReleased(KeyEvent e) {
}
}
1. 游戏规则:蛇头撞墙结束游戏
2. 根据积分等级,蛇的移动速度加快,积分越高,移动速度越快
3. 键盘:WSAD 和 上下左右键 都可以控制蛇的移动方向
4. 蛇可以撞到自己的身体,可以直接反方向移动,都不会结束游戏
五 最后一个类,启动类 StartGame
import javax.swing.*;
/**
* create by fzg
* 2022/11/15 14:23
*/
// 游戏启动类
public class StartGame {
public static void main(String[] args) {
JFrame jFrame = new JFrame();
jFrame.setBounds(500, 280,950,800);
jFrame.setResizable(false); // 窗口大小不准拉伸
// 正常的游戏都在面板上
GamePanel gamePanel = new GamePanel();
jFrame.add(gamePanel);
jFrame.setVisible(true);
jFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
}
六. 这样一个贪吃蛇小游戏就做成了