简易的贪吃蛇游戏
基于所学的Java.AWT
和Java.Swing
,实现简易的贪吃蛇游戏,以巩固所学知识。
实现效果:
实现工具类
准备必要的图片素材:蛇头,蛇的身体,食物等素材图片,本demo采用的素材均来自于阿里巴巴矢量图标库。
创建类用于加载数据,数据与实现分离,体会面向对象的思想。
LoadData
类用于加载数据:
package xyz.lilmoon.snakegame;
import javax.swing.*;
import java.net.URL;
public class LoadData {
public ImageIcon up = this.load("statics/up.png");
public ImageIcon down = this.load("statics/down.png");
public ImageIcon right = this.load("statics/right.png");
public ImageIcon left = this.load("statics/left.png");
public ImageIcon food = this.load("statics/food.png");
public ImageIcon body = this.load("statics/body.png");
public ImageIcon load(String path) {
URL url = this.getClass().getResource(path);
assert url != null;
return new ImageIcon(url);
}
}
Location
类用于作为坐标数据结构,保存位置的x、y坐标:
package xyz.lilmoon.snakegame;
public class Location {
public int x;
public int y;
public Location(int x, int y) {
this.x = x;
this.y = y;
}
}
Snake
类包含贪吃蛇的相关信息(长度,头朝向,身体的坐标数组),提供初始化方法及改变朝向的方法:
package xyz.lilmoon.snakegame;
public class Snake {
// 蛇的长度
public int len = 3;
// 蛇的朝向
String direction;
// 蛇的系列坐标
public Location[] snake = new Location[800];
public Snake() {
init();
}
public void init() {
direction = "R";
snake[0] = new Location(5, 2);
snake[1] = new Location(4, 2);
snake[2] = new Location(3, 2);
}
public void setDirection(String direction) {
this.direction = direction;
}
}
创建绘图类
创建GamePanel
类,继承JPanel
类;实现KeyListener
接口(用于监听键盘事件),ActionListener
接口(用于监听计时器带来的事件)。
声明要用到的一系列属性
public static Boolean state; // 游戏状态:暂停/开始,初始为暂停
public Boolean fail; // 判输状态:初始为false
Snake snake; // 实例化蛇类,包含了蛇的长度及坐标
Timer timer = new Timer(100, this); // 创建计时器,让蛇随时间自动前进,触发间隔设为100ms
Location food; // 创建食物的坐标对象
LoadData data; // 实例化数据类,用于加载数据
Random random = new Random(); // 实例化随机数类,生成随机数,实现食物的随机位置生成
创建构造器,用于初始加载个类数据
public GamePanel() {
// 获得焦点
this.setFocusable(true);
// 添加键盘监听器
this.addKeyListener(this);
// 加载icon素材类
data = new LoadData();
}
提供init()
方法,用于初始化游戏状态
// 初始化游戏状态
public void init() {
fail = false;
state = false;
// 创建蛇类
snake = new Snake();
// 随机生成食物位置
food = new Location(random.nextInt(34), random.nextInt(24));
// 启动计时器,计时器会不断触发事件监听器
timer.start();
}
重写JComponent
类的paintComponent()
方法
该方法为绘画程序,随程序自动启动,可通过repaint()
方法再次启动,游戏的各种绘制均由该方法完成:
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// 绘制提示信息
g.setColor(Color.BLACK);
g.drawString("按下空格开始/暂停游戏,W/A/S/D控制上/下/左/右,死亡后按下空格可重新开始游戏。", 50, 50);
// 绘制游戏区域
g.setColor(Color.GRAY);
g.fillRect(25, 75, 850, 600);
// 绘制食物
drawFood(food, g);
// 蛇类
// 根据朝向绘制蛇头
switch (snake.direction) {
case "R":
data.right.paintIcon(this, g, snake.snake[0].x * 25 + 25, snake.snake[0].y * 25 + 75);
break;
case "L":
data.left.paintIcon(this, g, snake.snake[0].x * 25 + 25, snake.snake[0].y * 25 + 75);
break;
case "U":
data.up.paintIcon(this, g, snake.snake[0].x * 25 + 25, snake.snake[0].y * 25 + 75);
break;
case "D":
data.down.paintIcon(this, g, snake.snake[0].x * 25 + 25, snake.snake[0].y * 25 + 75);
break;
}
// 遍历蛇数组,绘制身体
for (int i = 1; i < snake.len; i++) {
data.body.paintIcon(this, g, snake.snake[i].x * 25 + 25, snake.snake[i].y * 25 + 75);
}
// 根据游戏状态,暂停/启动,绘制提示字符
if (!state) {
g.setColor(Color.WHITE);
g.setFont(new Font(null, Font.BOLD, 40));
g.drawString("Press SPACE to Start!", 240 ,400);
}
// 根据游戏输赢,绘制提示字符
if (fail) {
g.setColor(Color.RED);
g.setFont(new Font(null, Font.BOLD, 500));
g.drawString("寄", 200 ,560);
}
}
提供工具方法简化重复代码
drawFood()
方法用于绘制食物:
public void drawFood(Location food, Graphics g) {
data.food.paintIcon(this, g, food.x * 25 + 25, food.y * 25 + 75);
}
eat()
方法用于在蛇吃到食物时为其增加长度,添加块:
public static void eat(Snake snake) {
int len = ++snake.len;
// 在尾部的位置添加块
snake.snake[len - 1] = new Location(snake.snake[len - 2].x, snake.snake[len - 2].y);
}
重写KeyPressed()
方法,实现需要键盘监听效果
// 键盘监听器
@Override
public void keyPressed(KeyEvent e) {
// 空格键会根据游戏状态实现游戏的暂停/开始或重新游戏
if (e.getKeyCode() == KeyEvent.VK_SPACE) {
if (fail) {
init();
}
repaint();
state = !state;
repaint();
}
// 根据朝向设置键盘监听器,使蛇不能回头
switch (snake.direction) {
case "R":
switch (e.getKeyCode()) {
case KeyEvent.VK_S:
snake.setDirection("D");
break;
case KeyEvent.VK_D:
snake.setDirection("R");
break;
case KeyEvent.VK_W:
snake.setDirection("U");
break;
}
break;
case "U":
switch (e.getKeyCode()) {
case KeyEvent.VK_D:
snake.setDirection("R");
break;
case KeyEvent.VK_W:
snake.setDirection("U");
break;
case KeyEvent.VK_A:
snake.setDirection("L");
break;
}
break;
case "D":
switch (e.getKeyCode()) {
case KeyEvent.VK_S:
snake.setDirection("D");
break;
case KeyEvent.VK_D:
snake.setDirection("R");
break;
case KeyEvent.VK_A:
snake.setDirection("L");
break;
}
break;
case "L":
switch (e.getKeyCode()) {
case KeyEvent.VK_S:
snake.setDirection("D");
break;
case KeyEvent.VK_W:
snake.setDirection("U");
break;
case KeyEvent.VK_A:
snake.setDirection("L");
break;
}
break;
}
}
重写actionPerformed()方法,该方法会被计时器唤醒,用于实现蛇的随时间前进
@Override
public void actionPerformed(ActionEvent e) {
// 每次timer触发都前进一格;若非暂停,且未失败,则继续移动
if (state && !fail) {
// 身体向前移动:从尾部开始,每一块依次获取前面的坐标,所以是从后往前依次前进,头部最后前进
for (int i = snake.len - 1; i > 0; i--) {
snake.snake[i].x = snake.snake[i - 1].x;
snake.snake[i].y = snake.snake[i - 1].y;
}
// 蛇头向前移动:根据朝向改变蛇头的位置
switch (snake.direction) {
case "R":
snake.snake[0].x = (snake.snake[0].x + 1) % 34;
break;
case "U":
// +24 使得坐标不会为负
snake.snake[0].y = (snake.snake[0].y - 1 + 24) % 24;
break;
case "D":
snake.snake[0].y = (snake.snake[0].y + 1) % 24;
break;
case "L":
// +34 使得坐标不会为负
snake.snake[0].x = (snake.snake[0].x - 1 + 34) % 34;
break;
}
// 吃到食物
if (snake.snake[0].x == food.x && snake.snake[0].y == food.y ){
// 调用eat方法,增加一格长度
eat(snake);
// 设置食物随机位置,重新绘制
food.x = random.nextInt(34);
food.y = random.nextInt(24);
}
// 失败判定
for (int i = 1; i < snake.len; i++) {
if (snake.snake[0].x == snake.snake[i].x && snake.snake[0].y == snake.snake[i].y) {
fail = true;
break;
}
}
repaint();
}
}
创建窗口类
创建Index
类,继承JFrame
类;使用写好的GamePanel
类及方法,来实现游戏界面,并实例化窗口,打开界面:
package xyz.lilmoon.snakegame;
import javax.swing.*;
public class Index extends JFrame {
public void init() {
setBounds(300, 200, 900, 720);
setResizable(false);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
GamePanel gamePanel = new GamePanel();
gamePanel.init();
add(gamePanel);
setVisible(true);
}
public static void main(String[] args) {
new Index().init();
}
}