贪吃蛇
感谢秦疆老师对于游戏的耐心讲解,这作为我对于GUI界面的联系是至关重要的,从游戏构思到资料准备,到函数,执行过程一步一步用一个小时完成了贪吃蛇游戏。
规划思路
素材准备
1.首先进行一把贪吃蛇游戏,看到窗口,初始化界面,图形按压界面,蛇需要移动,我们看到的就是需要我们准备的素材。
蛇头,分为上下左右,每次移动都要改变一个图片因为图片是静态的,
窗口:窗口上部准备的一个LOGO
蛇身:蛇身可以自己使用画笔进行画画,也可以直接采用我们自己的图片,在这里我也准备了图片
食物:食物可以自己使用画笔进行画画,也可以直接采用我们自己的图片,在这里我也准备了图片
提示界面:按压空格开始游戏
绘制界面
通俗讲就是把看见的界面画上去。
1.首先准备窗口,在这里我把窗口单独封装了一个类。
package com.xiucai;
import javax.swing.*;
public class JFrameDemo extends JFrame {
public JFrameDemo() {
this.setTitle("Snake");
this.setBounds(200,200,900,720);
//设置大小窗口不可变
this.setResizable(false);
//关闭不用自己写事件了 this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
//设置文字,设置剧中
this.setVisible(true);
}
}
2.需要一个Jpanel 画布 承载我们的内容,
也就是我们看到的蓝色部分
其实这是最关键的一个部分,我们也把他单独抽象
声明这个类
public class SnackPanal extends JPanel
进行计算,在游戏中实际上就是数据有规律的在进行变化,那我们对于窗口大小以及排布需要十分注意,所以在这里使用绝对布局的方法
计算:画布需要850,600,蓝色窗口,这是我根据我设置的窗口自己设定的,这个部分不做强制要求,但最好是整数,而且这个数据会作为之后一直使用的一个工具
画蓝色框的代码。
g.setColor(Color.BLUE);
g.fillRect(25,75,850,600);
3.画静态的小蛇
画完蓝色框我们怎么把小蛇,包括头部的一个图片放在这个画布上呢。此时使用ICON 组件。
这里因为重复代码太多而且涉及到数据的读取问题,我新建了一个类
package com.xiucai;
import javax.swing.*;
import java.awt.*;
import java.net.URL;
/**
* 数据中心
*/
public class Data {
// 首先用静态的获取图片的地址
public static URL url_up = Data.class.getResource("photo\\up.jpg");
public static URL url_down =Data.class.getResource("photo\\down.jpg");
public static URL url_head =Data.class.getResource("photo\\head.png");
public static URL url_left =Data.class.getResource("photo\\left.jpg");
public static URL url_right =Data.class.getResource("photo\\right.png");
public static URL url_body =Data.class.getResource("photo\\body.jpg");
public static URL url_dian =Data.class.getResource("photo\\dian.png");
// 首先用静态的获取图片的把地址,建立ImageIcon对象
public static ImageIcon Iurl_up = new ImageIcon(url_up);
public static ImageIcon Iurl_down = new ImageIcon(url_down);
public static ImageIcon Iurl_head = new ImageIcon(url_head);
public static ImageIcon Iurl_left = new ImageIcon(url_left);
public static ImageIcon Iurl_right = new ImageIcon(url_right);
public static ImageIcon Iurl_body = new ImageIcon(url_body);
public static ImageIcon Iurl_dian = new ImageIcon(url_dian);
//为了解决我图片大小不能正好放置在窗口上,我在构造函数中使用
// 对象.setImage(对象.getImage().getScaledInstance(长,高,Image.SCALE_DEFAULT));这样一个方法进行图片的缩放
public Data() {
Iurl_head.setImage(Iurl_head.getImage().getScaledInstance(850,55,Image.SCALE_DEFAULT));
Iurl_up.setImage(Iurl_up.getImage().getScaledInstance(25,25,Image.SCALE_DEFAULT));
Iurl_down.setImage(Iurl_down.getImage().getScaledInstance(25,25,Image.SCALE_DEFAULT));
Iurl_left.setImage(Iurl_left.getImage().getScaledInstance(25,25,Image.SCALE_DEFAULT));
Iurl_right.setImage(Iurl_right.getImage().getScaledInstance(25,25,Image.SCALE_DEFAULT));
Iurl_body.setImage(Iurl_body.getImage().getScaledInstance(24,24,Image.SCALE_DEFAULT));
Iurl_dian.setImage(Iurl_dian.getImage().getScaledInstance(25,25,Image.SCALE_DEFAULT));
}
}
用以上代码只是获取了图片对象,下一步我们进行绘制
在画笔函数中,让我们的所有图片运行一次
new Data();
画头部标题
Data.Iurl_head.paintIcon(this,g,25,11);
这里25跟11是长宽,this指的是当前面板,g是画笔的代号,在画方法生成的过程中会自己生成
画小蛇,小蛇的头部跟尾部实际上他们的位置可以用数组存储我们先定义一个他们首尾的数组
定义
int length;//蛇的长度
//五百跟六百实际上是大了 我们把每个小方格当成25乘25的正方形在宽高 900 720那边实际上有点高了
int[] snakeX = new int[600];
int[] snakeY = new int[500];
初始化
写了初始化函数init
public void init() {
length = 3;
//初始的三个区域块的位置
snakeX[0] =100;snakeY[0] =100;//脑袋的坐标
snakeX[1] =75;snakeY[1] =100;//第一个身体的坐标
snakeX[2] =50;snakeY[2] =100;//第二个身体坐标
}
进行画,在pain函数中
for (int l = 1; l < length; l++) {
Data.Iurl_body.paintIcon(this,g,snakeX[l],snakeY[l]);
}
//画食物
Data.Iurl_dian.paintIcon(this, g, dianX, dianY);
此时小蛇已经可以运行了
画这个字体
//画开始字体
if (isStrat == false) {
//设置字体
g.setColor(Color.RED);//设置画笔颜色
//设置字体
g.setFont(new Font("微软雅黑",Font.BOLD,40));
g.drawString("按压空格开始游戏",300,300);
}
基本上已经绘制出整体的样子了,再解决按压键盘的问题,此时需要使用监听器,键盘监听器 ,这里我们用了静态内部类,当键盘进行按压的时候我们进行的一个动作,下边代码包括一点后边判断失败的情况,及撞头会出现的,以及使蛇头摆位置的
//获取焦点
this.setFocusable(true);
//匿名内部类写键盘按下事件
this.setVisible(true);
this.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
super.keyPressed(e);
if (e.getKeyCode() == KeyEvent.VK_SPACE) {
isStrat = !isStrat;
isFill = false;
//重新画
repaint();
System.out.println(isStrat);
}
//检测左右
if (e.getKeyCode() == KeyEvent.VK_LEFT&&fx.equals("R")==false) {
fx = "L";
} else if (e.getKeyCode() == KeyEvent.VK_UP&&fx.equals("D")==false) {
fx = "U";
}else if (e.getKeyCode() == KeyEvent.VK_DOWN&&fx.equals("U")==false) {
fx = "D";
}else if (e.getKeyCode() == KeyEvent.VK_RIGHT&&fx.equals("L")==false) {
fx = "R";
}
}}); //获得键盘监听事件
那怎么让小蛇动起来呢
4.让小蛇动起来
使用时钟的概念实现ActionListener接口,
timer = new Timer(100,this);
public class SnackPanal extends JPanel implements ActionListener
public void actionPerformed(ActionEvent e) {
//如果是游戏开始的状态,小蛇动起来
if (isStrat&&isFill!=true) {
//小蛇往右移动
//关键性代码:后一个位子的图片替换前一个为题的图片使整个图片动起来
for (int i = length-1; i>0; i--) {
snakeX[i] = snakeX[i-1] ;
snakeY[i] = snakeY[i-1];
}
//根据方向进行循环
if (fx.equals("R")) {
snakeX[0] = snakeX[0] + 25;
}if (fx.equals("L")) {
snakeX[0] = snakeX[0] -25;
}if (fx.equals("U")) {
snakeY[0] = snakeY[0] -25;
}if (fx.equals("D")) {
snakeY[0] = snakeY[0] +25;
}
//边界判断
if (snakeX[0] > 850) {
snakeX[0] = 25;
}
if (snakeX[0] < 25) {
snakeX[0] = 850;
}
if (snakeY[0] < 75) {
snakeY[0] = 600;
}
if (snakeY[0] >650) {
snakeY[0] = 75;
}
//吃点
if (snakeX[0] == dianX && snakeY[0] == dianY) {
length++;
this.newdian();
}
//装了自己的情况
for (int i = 1; i <length ; i++) {
if (snakeX[0] == snakeX[i] && snakeY[0] == snakeY[i]) {
isFill = true;
isStrat = false;
}
}
//变化完成重画页面
repaint();
}
}
//生成一个新的随机点
最关键的代码
for (int i = length-1; i>0; i--) {
snakeX[i] = snakeX[i-1] ;
snakeY[i] = snakeY[i-1];}
这里是最关键的代码,使得我们不用管后边的动态变化,只用管头部怎么变化就可以后边顺应着就自己带过去了
为了实现从一头出去从另一头回来我们加了 这个代码
//边界判断 通过判断,蛇头是否出界重新定义蛇头的位置
if (snakeX[0] > 850) {
snakeX[0] = 25;
}
if (snakeX[0] < 25) {
snakeX[0] = 850;
}
if (snakeY[0] < 75) {
snakeY[0] = 600;
}
if (snakeY[0] >650) {
snakeY[0] = 75;
}
5.吃食物,变长
//吃点
if (snakeX[0] == dianX && snakeY[0] == dianY) {
length++;
this.newdian();
}
使用随机数函数.
为了解决随机数点出现在小蛇的身上,我加了一个出现再蛇身上判断的函数,定义isReat为是否重复
public void newdian() {
//生成随机点
dianX = 25 + 25 * random.nextInt(34);
dianY = 75 + 25 * random.nextInt(24);
isRepeat = false;
//点不能再蛇的身体上出现,加一个判断如果出现重新画
//判断是否点在身体里边
for (int i = 0; i <length; i++) {
if (dianX == snakeX[i] && dianY == snakeY[i]) {
isRepeat = true;
}
}
//如果在身体里边重新画
while (isRepeat) {
dianX = 25 + 25 * random.nextInt(34);
dianY = 75 + 25 * random.nextInt(24);
isRepeat = false;
for (int i = 0; i <length; i++) {
if (dianX == snakeX[i] && dianY == snakeY[i]) {
isRepeat = true;
}
}
}
}
}
6.失败机制
在这里需要注意的是撞了需要重新弹出一行文字,而且也要判断是否继续开始,所以需要在画函数中,定义两个变量 一个是是否开始,一个是是否失败,
if (isStrat == false&&isFill!=true) {
//设置字体
g.setColor(Color.RED);
g.setFont(new Font("微软雅黑",Font.BOLD,40));
g.drawString("按压空格开始游戏",300,300);
//把食物点画上去
}
if (isStrat == false&&isFill == true) {
g.setColor(Color.RED);
g.setFont(new Font("微软雅黑",Font.BOLD,40));
g.drawString("按压空格,重新开始游戏",300,300);
this.init();
}
for (int i = 1; i <length ; i++) {
if (snakeX[0] == snakeX[i] && snakeY[0] == snakeY[i]) {
isFill = true;
isStrat = false;
}
} //这是撞了自己的情况在里边重新运行
整体复盘
明确几点,
- 需要画的都在 画函数中
- 需要动态运行的都在 时钟函数中
- 定义初始变量的,都在init函数中,初始化,在类下进行声明
- 构造函数中,是进行初始话数据方法执行,键盘监听,时钟启动的
- 键盘监听是使变量进行改变,从而达到控制的效果.
- 时钟动态运行的以后进行repain,所以有比较好的效果,此外用空格进行开关控制所以在那边也有一个repain.
下边是main函数的工作
package com.xiucai;
import javax.swing.*;
import java.awt.*;
import java.util.Date;
public class Snake {
public static void main(String[] args) {
new Snake().init();
}
public void init() {
//设置窗口
JFrameDemo jFrame = new JFrameDemo();
//
Container contentPane = jFrame.getContentPane();
contentPane.add(new SnackPanal(jFrame));
// jFrame.setVisible(true);
}
}