贪吃蛇

思路

  1. 首先构思游戏布局,计算合理的坐标系。

  2. 绘制静态数据(广告、初始小蛇、提示信息、棋盘)

  3. 添加键盘监听事件,改变游戏状态以及小蛇运动方向

  4. 添加定时器,让小蛇在一段时间内移动一定的距离

  5. 随机产生食物,并监听食物状态是否被吃

  6. 处理游戏结束事件

  7. 扩展相关游戏机制(积分、等级)

    • 定义数据
    • 绘制图像
    • 事件监听

注意事项

  • 导入文件资源时,通过类的相对路径获取

    • URL headURL = Data.class.getResource("header.png"); // 这是放在源代码同一个包下的文件

    • URL headURL = Data.class.getResource("/header.png"); // 这是放在项目根目录下的文件

  • 键盘监听时,需要自动获取焦点。

    • this.setFocusable(true); // 获取焦点

      this.addKeyListener(new MyKeyListener());
  • 在修改数据之后,需要repaint重绘图形

  • 小蛇运动时,需要注意边界问题

  • 食物随机产生的坐标也要限制在游戏区域内

  • 添加游戏的可玩性

具体实现

Data.java 存放所有图像数据

package snake;

import javax.swing.*;
import java.net.URL; /**
* 数据中心
*/
public class Data { // 相对路径
// 绝对路径 / 当前项目-->"GUI编程目录" public static URL headURL = Data.class.getResource("header.png");
public static URL upURL = Data.class.getResource("up.png");
public static URL downURL = Data.class.getResource("down.png");
public static URL leftURL = Data.class.getResource("left.png");
public static URL rightURL = Data.class.getResource("right.png");
public static URL bodyURL = Data.class.getResource("body.png");
public static URL foodURL = Data.class.getResource("food.png");
public static URL foodURL2 = Data.class.getResource("food2.png"); public static ImageIcon header = new ImageIcon(headURL);
public static ImageIcon up = new ImageIcon(upURL);
public static ImageIcon down = new ImageIcon(downURL);
public static ImageIcon left = new ImageIcon(leftURL);
public static ImageIcon right = new ImageIcon(rightURL);
public static ImageIcon body = new ImageIcon(bodyURL);
public static ImageIcon food = new ImageIcon(foodURL);
public static ImageIcon food2 = new ImageIcon(foodURL2);
}

StartGame.java 游戏启动类

package snake;

import javax.swing.*;
import java.awt.*; public class StartGame {
public static void main(String[] args) {
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
int screenWidth = (int) screenSize.getWidth();
int screenHeight = (int) screenSize.getHeight();
System.out.println("屏幕宽度:" + screenWidth + ",屏幕高度:" + screenHeight); // 本游戏固定窗体大小(900,720)不可变
int x = (screenWidth - 900) / 2;
int y = (screenHeight - 720) / 2;
System.out.println("相对坐标x:" + x + ",y:" + y); JFrame jFrame = new JFrame("贪吃蛇");
jFrame.setBounds(x, y, 900, 720);
jFrame.setResizable(false);
jFrame.add(new GamePanel());
jFrame.setVisible(true);
jFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); }
}

GamePanel.java 处理游戏逻辑

package snake;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.Random; public class GamePanel extends JPanel {
// 蛇的数据结构
int length;
int[] snakeX = new int[600];
int[] snakeY = new int[500];
char direction; // 食物的坐标
int foodx;
int foody;
Random random = new Random(); // 计数器
int count;
int score;
int worth; boolean isStart;
boolean isFail; // 定时器,每隔100毫秒执行一次参数的事件。
int interval = 100;
Timer timer = new Timer(interval, new MyActionLinstener()); public void init() {
// 默认游戏未开始
this.isStart = false;
this.isFail = false; // 初始化速度
interval = 200;
timer.setDelay(interval); // 初始化小蛇数据
this.length = 3;
this.score = 0;
this.snakeX[0] = 100;
this.snakeY[0] = 100;
this.snakeX[1] = 75;
this.snakeY[1] = 100;
this.snakeX[2] = 50;
this.snakeY[2] = 100;
this.direction = 'R'; // 随机产生事物的坐标
this.foodx = 25 + 25 * random.nextInt(34);
this.foody = 75 + 25 * random.nextInt(24);
this.count = 1; // 统计食物数量 // 获得焦点和键盘监听事件
this.setFocusable(true);
// 添加键盘监听事件
this.addKeyListener(new MyKeyListener());
// 开启定时器
timer.start();
} public GamePanel() {
init();
} // 自动绘制面板
// paintComponent()是swing的一个方法,相当于图形版的main(),是会自执行的。
@Override
protected void paintComponent(Graphics g) {
//清屏
super.paintComponent(g);
this.setBackground(Color.WHITE);
//绘制静态面板
// 顶部广告
Data.header.paintIcon(this, g, 25, 11); // 游戏区域
g.fillRect(25, 75, 850, 600); // 画积分
g.setColor(Color.white);
g.setFont(new Font("微软雅黑", Font.BOLD, 18));
g.drawString("长度:" + length, 750, 30);
g.drawString("分数:" + score, 750, 56); // 绘制食物
if (count % 5 != 0) {
Data.food.paintIcon(this, g, foodx, foody);
worth = 1;
} else {
Data.food2.paintIcon(this, g, foodx, foody);
worth = 3;
} // 游戏状态 默认为暂停 需要提示信息
if (!isStart) {
g.setColor(Color.white);
g.setFont(new Font("微软雅黑", Font.BOLD, 40));
g.drawString("按下空格开始游戏", 300, 300);
} // 画小蛇 初始化向右 长度为3
// 选择头部的方向
ImageIcon head = Data.right;
switch (this.direction) {
case 'U':
head = Data.up;
break;
case 'D':
head = Data.down;
break;
case 'L':
head = Data.left;
break;
case 'R':
head = Data.right;
break;
default:
break;
} head.paintIcon(this, g, snakeX[0], snakeY[0]);
for (int i = 1; i < this.length; i++) {
Data.body.paintIcon(this, g, snakeX[i], snakeY[i]);
} // 结束状态
if (isFail) {
g.setColor(Color.red);
g.setFont(new Font("微软雅黑", Font.BOLD, 100));
g.drawString("Game Over", 150, 200);
g.drawString("按空格重新开始", 100, 400);
} } /**
* 内部类监听键盘事件
*/
class MyKeyListener extends KeyAdapter {
// 只需要监听键盘按下
@Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
// System.out.println(keyCode);
if (keyCode == KeyEvent.VK_SPACE) {
if (isFail) {
// 重新开始
init();
isStart = true;
} else {
isStart = !isStart;
} repaint();
} // 内层增加一个判断,180度转向 不生效
switch (keyCode) {
case KeyEvent.VK_UP:
if (direction != 'D') {
direction = 'U';
}
break;
case KeyEvent.VK_DOWN:
if (direction != 'U') {
direction = 'D';
}
break;
case KeyEvent.VK_LEFT:
if (direction != 'R') {
direction = 'L';
}
break;
case KeyEvent.VK_RIGHT: if (direction != 'L') {
direction = 'R';
}
break;
default:
break;
} // 调整速度 小键盘的 + -
if (keyCode == 107) {
interval = interval > 10 ? interval - 10 : 10;
timer.setDelay(interval);
System.out.println("加速" + interval);
} else if (keyCode == 109) {
interval += 20;
timer.setDelay(interval);
System.out.println("减速" + interval);
}
}
} class MyActionLinstener implements ActionListener {
// 事件监听类
@Override
public void actionPerformed(ActionEvent e) {
// 通过事件刷新界面
// 如果游戏开始且未失败,则刷新界面
if (isStart && !isFail) { // 吃食物 蛇头与食物重合
if (snakeX[0] == foodx && snakeY[0] == foody) {
length++; // 吃了边长
foodx = 25 + 25 * random.nextInt(34);
foody = 75 + 25 * random.nextInt(24);
count++; // 统计食物数量
score += worth; // 这里可以设置积分到一定值,增加移动速度
if (score < 5) {
interval = 200;
timer.setDelay(interval);
System.out.println("一级速度");
} else if (score < 15) {
interval = 150;
timer.setDelay(interval);
System.out.println("二级速度");
} else if (score < 25) {
interval = 100;
timer.setDelay(interval);
System.out.println("三级速度");
} else if (score < 50) {
interval = 50;
timer.setDelay(interval);
System.out.println("四级速度");
} } for (int i = length - 1; i > 0; i--) {
// 从最后一节开始继承前一节的位置
snakeX[i] = snakeX[i - 1];
snakeY[i] = snakeY[i - 1];
}
// 头部找新的路,利用三元运算符判断边界重置
switch (direction) {
case 'U':
snakeY[0] = snakeY[0] <= 75 ? 650 : snakeY[0] - 25;
break;
case 'D':
snakeY[0] = snakeY[0] >= 650 ? 75 : snakeY[0] + 25;
break;
case 'L':
snakeX[0] = snakeX[0] <= 25 ? 850 : snakeX[0] - 25;
break;
case 'R':
snakeX[0] = snakeX[0] % 850 + 25;
break;
default:
break;
} // 失败判定
for (int i = 1; i < length; i++) {
if (snakeX[0] == snakeX[i] && snakeY[0] == snakeY[i]) {
isFail = true;
}
}
repaint();
}
}
} }

游戏效果

该版本已知bug

小蛇不能反向运动,玩家操作过快导致的意外死亡。

比如小蛇向↑运动过程中,玩家按 ↓ 是无效的,但如果按 ← 的瞬间按 ↓

由于定时器的刷新速度没有跟上玩家的操作,游戏帧尚未刷新就已经有了下一次操作。

误判为小蛇回头撞了自己,游戏结束。

第二个问题是

空格本应该具有暂停/继续/重玩的功能。

游戏局数为偶数时,空格键暂停功能失效。

打包游戏

首先通过IDEA生成Jar包,再通过exe4j将jar打包为exe

具体操作跳转至Java桌面应用程序打包。

Java贪吃蛇小游戏的更多相关文章

  1. java贪吃蛇小游戏详解

    https://blog.csdn.net/u011622021/article/details/81162083

  2. Java GUI学习,贪吃蛇小游戏

    JAVA GUI练习 贪吃蛇小游戏 前几天虽然生病了,但还是跟着狂神学习了GUI的方面,跟着练习了贪吃蛇的小项目,这里有狂神写的源码点我下载,还有我跟着敲的点我下载,嘿嘿,也就注释了下重要的地方,这方 ...

  3. 用GUI实现java版贪吃蛇小游戏

    项目结构 新建一个JFrame窗口,作为程序入口 public class GameStart{ public static void main(String[] args) { JFrame jFr ...

  4. C++ 简单的控制台贪吃蛇小游戏

    由于比较懒,所以不怎么写,觉得这样不应该.我应该对自己学的做出整理,不管是高端低端,写出来是自己的. // 贪吃蛇.cpp : 定义控制台应用程序的入口点. // #include "std ...

  5. 贪吃蛇小游戏-----C语言实现

    1.分析 众所周知,贪吃蛇游戏是一款经典的益智游戏,有PC和手机等多平台版本,既简单又耐玩.该游戏通过控制蛇头方向吃食物,从而使得蛇变得越来越长,蛇不能撞墙,也不能装到自己,否则游戏结束.玩过贪吃蛇的 ...

  6. JS高级---案例:贪吃蛇小游戏

    案例:贪吃蛇小游戏 可以玩的小游戏,略复杂,过了2遍,先pass吧 先创建构造函数,再给原型添加方法.分别创建食物,小蛇和游戏对象. 食物,小蛇的横纵坐标,设置最大最小值,运动起来的函数,按上下左右键 ...

  7. Java 用java GUI写一个贪吃蛇小游戏

    目录 主要用到 swing 包下的一些类 上代码 游戏启动类 游戏数据类 游戏面板类 代码地址 主要用到 swing 包下的一些类 JFrame 窗口类 JPanel 面板类 KeyListener ...

  8. html5面向对象做一个贪吃蛇小游戏

    canvas加面向对象方式的贪吃蛇 2016-08-25 这个小游戏可以增加对面向对象的理解,可以加强js逻辑能力,总之认真自己敲一两遍收获还是不少啊!!适合刚学canvas的同学练习!! 废话不多说 ...

  9. 用python+pygame写贪吃蛇小游戏

    因为python语法简单好上手,前两天在想能不能用python写个小游戏出来,就上网搜了一下发现了pygame这个写2D游戏的库.了解了两天再参考了一些资料就开始写贪吃蛇这个小游戏. 毕竟最开始的练手 ...

随机推荐

  1. Python 网络爬虫基本概念篇

    爬虫的概念 网络爬虫(又称为网页蜘蛛,网络机器人,在FOAF社区中间,更经常的称为网页追逐者),是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本.这是百度百科对爬虫的定义,其实,说简单点,爬虫 ...

  2. 哦,Vendor

    vendor踩坑笔记: 接过公司里面X平台golang部分的后端后,需要新添加一个业务功能,美滋滋~ 拿过项目一顿写之后后遇到事了. 报错的描述如下: 报错的大意说:方法的入参类型不匹配,我们不能把 ...

  3. How to delete a directory recursively in Java

    在java8或更高版本中,使用NIO API递归删除一个非空目录: try { // 创建stream流 Stream<Path> file = Files.walk(Paths.get( ...

  4. Vue路由-详细总结

    Vue路由vue-router   前面的话 在Web开发中,路由是指根据URL分配到对应的处理程序.对于大多数单页面应用,都推荐使用官方支持的vue-router.Vue-router通过管理URL ...

  5. Python 报错:NameError: name 'ctypes' is not defined

    使用python中的ctypes模块可以很方便的调用windows的dll(也包括linux下的so等文件) 引入ctypes库 有两种方法 from ctypes import * import c ...

  6. .net core3.1 abp动态菜单和动态权限(思路) (二)

    ps:本文需要先把abp的源码下载一份来下,跟着一起找实现,更容易懂 在abp中,对于权限和菜单使用静态来管理,菜单的加载是在登陆页面的地方(具体是怎么知道的,浏览器按F12,然后去sources中去 ...

  7. Dedecms中{dede:type}标签支持调用父级栏目名称

    需求: 我们用{dede:type}标签调用栏目相关内容时,同时需要调用该栏目的父级栏目的名称. {dede:type}标签的代码做了一下开发,支持这个调用了. 开发方法: 1.打开include/t ...

  8. redis的持久化(RDB与AOF)

    1.为什么redis要实现持久化? 避免因宕机.断电等场景导致进程退出后数据丢失,如果redis的数据都只存放于内存,那么进程退出后数据就丢失了.持久化机制可以持久化内存数据到硬盘,重启redis后基 ...

  9. TCP 三次握手的意义

    概述 在网络的传输层协议中, 存在着两大悍将: TCP 和 UDP . 从前, 我傻傻的以为自己对他们虽谈不上精通, 但还是知道的, 但是, 我错了, 我被自己问住了, 我傻了. 啥也不是. UDP ...

  10. @loj - 3043@「ZJOI2019」线段树

    目录 @description@ @solution@ @accepted code@ @details@ @description@ 九条可怜是一个喜欢数据结构的女孩子,在常见的数据结构中,可怜最喜 ...