贪吃蛇
结果展示
首先展示一下结果,只有基础的功能,很简陋,主要还是为了巩固一下Java的基础:
设计思路
1、首先,需要一个窗口,直接使用JFrame就可以了,所以需要一个JFrame类作为游戏窗口。关于游戏窗口大小的设计,基本单元选择30X30,宽和高分别尾24个单元与16个单元。
2、因为习惯,不喜欢直接在JFrame中操作(忘记在哪里听过,一般都会在Jframe中定义一个JPanel),所以定义了一个JPanel类作为游戏的主面板。在里面进行游戏的所有操作
在这个类里边,主要干两件事情:
a、定义一个线程,可以让游戏画面动起来
线程里面的事件:重新绘制游戏界面、更新小蛇与食物的位置、判断游戏是否结束
b、定义一个键盘监听事件,可以监听键盘的操作
监听空格键,改变游戏状态
监听上下左右,改变小蛇的方向(交给小蛇🐍来判断,因为是小蛇的行为)
3、需要单独封装Snake以及Food,在Jpanel中只需要实现与游戏相关的操作就可以了。
全部的源码
Food
package game;
import java.awt.*;
import java.util.Random;
/*
* 因为全局只有一个,所以属性和方法都定义成静态的,方便在GamePanel和Snake中使用
* */
public class Food {
public static Point location;
public static boolean isEat;
//初始化食物
public static void initFood(){
location = new Point(12,8);
isEat = false;
}
//提供给游戏界面绘制食物
public static void paintFood(Graphics g){
Color color = g.getColor();
g.setColor(Color.YELLOW);
g.fillOval((location.x-1)*30,(location.y-1)*30,30,30);
g.setColor(color);
}
//产生食物
public static void createFood(){
if(isEat){
Random random = new Random();
location.x = random.nextInt(24)+1;
location.y = random.nextInt(14)+3;
isEat = false;
}
}
}
Snake
package game;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.util.LinkedList;
/*
* 蛇类:
* 主要分为三部分:
* 1、绘制小蛇
* 提供给游戏面板绘制小蛇
* 2、键盘监听
* 通过游戏界面传递过来的KeyEvent改变小蛇的方向
* 3、小蛇位置的更新
* 位置更新包括更新后的边缘检测、碰撞检测、食物检测
* 4、其余的话,就是一些参数的Getter方法,提供给外界,判断游戏是否结束
* */
public class Snake {
private int length; // 小蛇的长度
private Point head; // 小蛇的头,不跟身体在一起,主要还是为了可以分开来处理
private LinkedList<Point> body; // 链表存储小蛇的身体坐标
private int direction; //蛇的方向,1-上、2-下、3-左、4-右
private boolean isLive; //是否存活
public Snake() {
length = 3;
body = new LinkedList<Point>();
head = new Point(3,1);
body.add(new Point(2,1));
body.add(new Point(1,1));
direction = 4;
isLive = true;
}
//1、绘制小蛇,分开画头和身体,因为吃到头应该在食物顶层,而身体应该在食物底层
/* public void snakePaint(Graphics g){
Color color = g.getColor();
//身体用蓝色
g.setColor(Color.BLUE);
for (int i = 0; i < length-1; i++) {
Point bodyPoint = body.get(i);
g.fillOval((bodyPoint.x-1)*30,(bodyPoint.y-1)*30,30,30);
}
//蛇头用红色
g.setColor(Color.RED);
g.fillOval((head.x-1)*30,(head.y-1)*30,30,30);
//用完画笔回到最初的颜色
g.setColor(color);
}*/
public void snakePaintHead(Graphics g){
Color color = g.getColor();
//蛇头用红色
g.setColor(Color.RED);
g.fillOval((head.x-1)*30,(head.y-1)*30,30,30);
//用完画笔回到最初的颜色
g.setColor(color);
}
public void snakePaintBody(Graphics g){
Color color = g.getColor();
//身体用蓝色
g.setColor(Color.BLUE);
for (int i = 0; i < length-1; i++) {
Point bodyPoint = body.get(i);
g.fillOval((bodyPoint.x-1)*30,(bodyPoint.y-1)*30,30,30);
}
//用完画笔回到最初的颜色
g.setColor(color);
}
//2、键盘监听
public void listenKey(KeyEvent e){
switch (e.getKeyCode()){
case KeyEvent.VK_UP:
//如果本来是向下的话,向上就无效,下面同理
direction = (direction == 2) ? 2 : 1;
break;
case KeyEvent.VK_DOWN:
direction = (direction == 1) ? 1 : 2;
break;
case KeyEvent.VK_LEFT:
direction = (direction == 4) ? 4 : 3;
break;
case KeyEvent.VK_RIGHT:
direction = (direction == 3) ? 3 : 4;
break;
}
}
//3、小蛇位置的更新
public void move(){
switch (direction){
//小蛇上移动
case 1:
body.addFirst(new Point(head));
head.y -= 1;
break;
//下
case 2:
body.addFirst(new Point(head));
head.y += 1;
break;
//左
case 3:
body.addFirst(new Point(head));
head.x -= 1;
break;
//右
case 4:
body.addFirst(new Point(head));
head.x += 1;
break;
}
//碰撞检测,是否撞到自己的身体
if(isPeng()){
return;
}
//食物检测,是否需要移除尾结点
if(head.x == Food.location.x && head.y == Food.location.y){
eatFood();
}else {
body.removeLast();
}
//边缘检测
checkBounds();
}
//边缘检测
public void checkBounds(){
if(head.x < 1){
head.x = 24;
}else if(head.x > 24){
head.x = 1;
}else if(head.y < 1){
head.y = 16;
}else if(head.y > 16){
head.y = 1;
}
}
//碰撞检测,是否撞到自己的身体
public boolean isPeng(){
Point point;
for (int i = 0; i < body.size(); i++) {
point = body.get(i);
if(head.x == point.x && head.y == point.y){
isLive = false;
return true;
}
}
return false;
}
//吃到食物
public void eatFood(){
length++;
Food.isEat = true;
}
//4、其余的方法
//isLive
public boolean isLive() {
return isLive;
}
//length
public int getLength() {
return length;
}
}
GamePanel
package game;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
/*
* 这个类是游戏的主类了,里面主要是做了两件事
* 1、定义了一个线程,使游戏画面动起来(其实也可以使用Timer类的)
* :绘制游戏界面(包括小蛇、食物、游戏提示等)
* 更新小蛇的位置
* 检测小蛇的状态(游戏是否结束)
*
* 2、定义了游戏状态的键盘监听事件,返回给外层的Jframe,在jframe里面添加键盘事件
* :游戏的状态变化(空格)
* 小蛇的移动变化(上下左右):将键盘事件交给Snake类去判断
* */
public class GamePanel extends JPanel {
private Snake snake; //小蛇
private int state; //游戏状态:0-是停止的,1-是开始的,2-结束
private int score; //游戏得分
public GamePanel() {
setBounds(0,0,24*30,16*30);
setBackground(Color.black);
initGame();
new Thread(new ViewGoThread()).start();
}
//初始化游戏
public void initGame(){
snake = new Snake();
state = 0;
score = 0;
Food.initFood();
}
@Override
public void paint(Graphics g) {
//刷新面板
super.paint(g);
/*
* 注意画的顺序,先画的会被后面画的覆盖
* */
//绘制小蛇身体
snake.snakePaintBody(g);
//绘制食物
Food.paintFood(g);
//绘制小蛇头
snake.snakePaintHead(g);
//绘制提示信息
paintGameTips(g);
}
//绘制游戏里的提示信息
public void paintGameTips(Graphics g){
Color color = g.getColor();
g.setColor(Color.RED);
g.setFont(new Font("微软雅黑",Font.BOLD,20));
switch (state){
case 0:
g.drawString("按下空格开始游戏",250,250);
break;
case 1:
break;
case 2:
g.drawString("游戏结束,得分:"+score,250,250);
}
g.setColor(color);
}
//判断游戏是否结束
public void isGameOver(){
if(!snake.isLive()){
score = snake.getLength()-3;
state = 2;
}
}
//因为会有忙式等待,或许使用定时器会更好一点,之后再修改
private class ViewGoThread implements Runnable{
@Override
public void run() {
//每隔xx做一次
while(true){
//重新绘制图像
repaint();
//更新小蛇与食物
if(state == 1){
snake.move();
Food.createFood();
}
//判断游戏是否结束
isGameOver();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//监听键盘的事件,本来是打算在Jpanel里面添加就好了的,但是好像不可以,只能返回给jFrame来监听
private class MyKeyListener extends KeyAdapter{
@Override
public void keyPressed(KeyEvent e) {
if(e.getKeyCode() == KeyEvent.VK_SPACE){
if(state == 0){ //如果是停止的,就动起来
state = 1;
}else if(state == 1){ //如果是动的,就停下来
state = 0;
}else if(state == 2){ //游戏结束状态,再按一次就初始化游戏
initGame();
}
}
if(state == 1){
snake.listenKey(e);
}
}
}
public KeyAdapter myKeyListener(){
return new MyKeyListener();
}
}
SnakeGame
package game;
import javax.swing.*;
import java.awt.*;
/*
* 主启动类
* */
public class SnakeGame extends JFrame {
public SnakeGame(String title) throws HeadlessException {
super(title);
//设置窗口位置大小
setBounds(400,300,24*30,16*30);
//加载游戏主面板
GamePanel gamePanel = new GamePanel();
getContentPane().add(gamePanel);
addKeyListener(gamePanel.myKeyListener());
//设置窗口默认关闭事件以及可视化
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setVisible(true);
}
public static void main(String[] args) {
new SnakeGame("贪吃蛇小游戏");
}
}
改进(下面的我还没有使用)
1、双缓存技术
可以使用双缓存来保证游戏画面不会闪烁,但是本身运行起来似乎闪烁不大,所以没有使用
//定义一个图像
private Image offScreenImage = null;
@Override
public void update(Graphics g) {
if (offScreenImage == null) {
offScreenImage = this.createImage(GAME_WIDTH, GAME_HEIGHT);
}
//先把所有元素画在一个图片上
Graphics goffScreen = offScreenImage.getGraphics();// 重新定义一个画虚拟桌布的画笔//
paint(goffScreen);
//再把这个图片画到JPanel中
g.drawImage(offScreenImage, 0, 0, null);
}
2、定时器
可以使用Timer来代替这里的线程
//定时器
Timer timer = new Timer(100,new MyActionListener()); //100ms执行一次
private class MyActionListener implements ActionListener{
//事件监听
@Override
public void actionPerformed(ActionEvent e) {
//这里就是要做的事情了
}
}
timer.start(); //定时器开始工作
timer.stop(); //定时器停止工作
3、加载图片资源代替圆圆的蛇
可以定义一个Data类来专门存储图片资源
import javax.swing.*;
import java.net.URL;
public class Data {
public static URL headUrl = Data.class.getResource("statics/head.png");
public static URL bodyUrl = Data.class.getResource("statics/body.png");
public static URL foodUrl = Data.class.getResource("statics/food.png");
public static ImageIcon head = new ImageIcon(headUrl);
public static ImageIcon food = new ImageIcon(foodUrl);
public static ImageIcon body = new ImageIcon(bodyUrl);
}
//方便再其他的地方调用:
@Override
public void paint(Graphics g) {
//this:就是自己定义的Jpanel了,表示在哪个组件上面画图标
Data.food.paintIcon(this,g,foodX,foodY);
//也可以用下面这个
g.drawImage(Data.food.getImage(),foodX,foodY,null);
}
总结
- 在刚开始想要自己从头开始写的时候,以为会很难,但是从真正设计到实现却并没有想象中的那么困难(大概可能是以前看过了)
- 设计一个类似这样的Java小游戏,总结起来,好像只有两样东西,一个是游戏面板,另一个就是游戏元素了
- 在游戏面板里面,定义游戏的整体操作(游戏界面的绘制,判断游戏是否结束,游戏初始化等等),整个游戏的流程控制(线程或者定时器),以及 键盘的监听(当然也可以是其他的监听)
- 而游戏元素则具体定义不同的游戏元素的内部规则,就比如贪吃蛇中,小蛇位置的更新,更新后会发生什么等具体的实现。
- 无了~