文章目录
- 一:窗口的绘制
- 1.1 定义窗口类——SnakeGame
- 1.2 设置窗口的参数
- 1.3 启动main方法
- 二:窗口网格的绘制
- 2.1 重写paint方法
- 2.1.1 为什么要重写paint方法
- 2.1.2 实现方式
- 2.2 Graphics
- 2.3 设置网格线的参数
- 三:游戏物体父类的创建——SnakeEntity
- 四:游戏工具类的创建——SnakeUtil
- 4.1 获取蛇头的图片
- 4.2 获取蛇身的图片
- 4.3 获取食物的图片
- 4.4 绘制文字
- 五:蛇头部的绘制——SnakeHeadEntity
- 5.1 设置蛇头的方向
- 5.2 绘制自身
- 六:蛇头的简单移动
- 6.1 在HeadObj添加move方法
- 6.2 在paintSelf方法中添加move方法
- 6.3 蛇的重复移动需要调用repaint()方法
- 七:键盘控制蛇的方向
- 7.1 在SnakeHeadEntity中添加键盘监听事件
- 7.2 编写changeDirection方法
- 八:蛇越界后的处理
- 1.编写checkIndex()方法
- 2.在paintSelf方法中添加checkIndex()方法
- 九:蛇身的添加和移动
- 9.1 新建蛇身实体类——SnakeBodyEntity
- 9.2 在SnakeGame定义蛇身集合
- 9.3 在launch()方法对蛇身进行初始化
- 9.4 在paint方法绘制蛇身体
- 9.5 在move方法中添加身体移动逻辑
- 十:食物的随机生成
- 10.1 新建食物实体类——SnakeFoodEntity
- 10.2 获取食物
- 10.3 在SnakeGame中添加食物实体类
- 10.4 在paint中绘制食物
- 十一:蛇吃食物和食物的重新生成
- 11.1 在SnakeHeadEntity中的paintSelf添加获取食物的对象
- 11.2 判断蛇头和食物是否重合
- 十二:蛇的增长
- 12.1 定义身体最后一节的坐标
- 12.2 在move方法后添加元素
- 12.3 修改move方法的遍历顺序
- 十三:计分面板的编写
- 13.1 在SnakeGame定义窗口的宽高
- 13.2 定义游戏分数,并且在paint绘制出来
- 13.3 蛇吃食物,分数加一
- 十四:游戏开始的提示语
- 14.1 在SnakeGame定义游戏状态
- 14.2 在while语句中添加判断,只有状态为1,才需要重复执行repaint()方法
- 14.3 绘制提示语
- 14.4 在paint方法添加提示语
- 十五:游戏开始和暂停的键盘事件以及通关判断
- 15.1 在SnakeGame的launch方法添加键盘函数
- 15.2 通关判断
- 15.3 添加通关提示语
- 十六:蛇和身体碰撞判断
- 16.1 在move方法中添加代码
- 16.2 在prompt方法中添加游戏失败的提示语
- 十七:双缓存解决画面闪烁
- 十八:游戏失败后重新开始
- 18.1 SnakeGame添加resetGame方法
- 18.2 修改Switch语句
- 十九:源码奉上
- 19.1 SnakeGame
- 19.2 SnakeUtil
- 19.3 SnakeEntity
- 19.4 SnakeHeadEntity
- 19.5 SnakeBodyEntity
- 19.6 SnakeFoodEntity
一:窗口的绘制
1.1 定义窗口类——SnakeGame
import javax.swing.*;
public class SnakeGame extends JFrame {
}
1.2 设置窗口的参数
import javax.swing.*;
public class SnakeGame extends JFrame {
public void launch(){
//设置窗口是否可见
this.setVisible(true);
//设置窗口的大小
this.setSize(600,600);
//设置窗口的位置在屏幕上居中
this.setLocationRelativeTo(null);
//设置窗口的标题
this.setTitle("贪吃蛇");
}
public static void main(String[] args) {
GameWin gameWin = new GameWin();
gameWin.launch();
}
}
1.3 启动main方法
二:窗口网格的绘制
@Override
public void paint(Graphics g) {
//灰色背景
g.setColor(Color.gray);
//绘制矩形,和窗口一样,宽和高都是600
g.fillRect(0,0,600,600);
//网格线
g.setColor(Color.black);
//横线
for (int i = 0; i <= 20 ; i++) {
//横线
g.drawLine(0,i * 30,600,i * 30);
//竖线
g.drawLine(i * 30,0,i * 30,600);
}
}
2.1 重写paint方法
2.1.1 为什么要重写paint方法
当我们使用JFrame创建出窗体时,遇到窗体的尺寸改变(ie.拉伸窗体),或者窗体的部分像素被移动到屏幕之外,都会导致窗体的刷新。窗体有一个系统自带的刷新方法。但这时如果窗体中有此前绘制出的图像,则图像会随窗体的刷新而消失,这时候我们就需要将绘制图像的方法重写入JFrame的paint方法中,让图像随窗体的刷新而同步再次被绘制出来。
2.1.2 实现方式
函数要在类继承JFrame或者JPanel两个属性下才能实现重写,并且这个方法是系统自动调用的。
重写绘制方法的本质是将图像数据化、对象化。将图像的属性(eg. 坐标、颜色、图形类型……)和其绘制方法写入paint()。
2.2 Graphics
实际生活中如果需要画图,首先我们得准备一张纸,然后在拿一支画笔,配和一些颜色,就可以在纸上画出来各种各样的图形,例如圆圈、矩形等等。
程序中绘图也一样,也需要画布,画笔,颜料等等。AWT中提供了Canvas类充当画布,提供了Graphics类来充当画笔,通过调用Graphics对象的setColor()方法可以给画笔设置颜色。
2.3 设置网格线的参数
//灰色背景
g.setColor(Color.gray);
//绘制矩形,和窗口一样,宽和高都是600
g.fillRect(0,0,600,600);
//网格线颜色
g.setColor(Color.black);
//绘制网格线
for (int i = 0; i <= 20 ; i++) {
//横线
g.drawLine(0,i * 30,600,i * 30);
//竖线
g.drawLine(i * 30,0,i * 30,600);
}
三:游戏物体父类的创建——SnakeEntity
import com.sysg.game.sanke.SnakeGame;
import java.awt.*;
public class SnakeEntity{
//图片
Image img;
//坐标
int x;
int y;
//宽高
int width = 30;
int height = 30;
//窗口类的引用
GameWin frame;
public Image getImg() {
return img;
}
public void setImg(Image img) {
this.img = img;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public GameWin getFrame() {
return frame;
}
public void setFrame(GameWin frame) {
this.frame = frame;
}
public SnakeEntity() {
}
public SnakeEntity(Image img, int x, int y, int width, int height, GameWin frame) {
this.img = img;
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.frame = frame;
}
//绘制自身
public void paintSelf(Graphics g){
g.drawImage(img,x,y,null);
}
}
四:游戏工具类的创建——SnakeUtil
import java.awt.*;
public class SnakeUtil{
//蛇头
public static Image upImg = Toolkit.getDefaultToolkit().getImage("img/up.png");
public static Image downImg = Toolkit.getDefaultToolkit().getImage("img/down.png");
public static Image leftImg = Toolkit.getDefaultToolkit().getImage("img/left.png");
public static Image rightImg = Toolkit.getDefaultToolkit().getImage("img/right.png");
//蛇身
public static Image bodyImg = Toolkit.getDefaultToolkit().getImage("img/body.png");
//食物
public static Image foodImg = Toolkit.getDefaultToolkit().getImage("img/food.png");
//绘制文字
public static void drawWord(Graphics g,String str,Color color,int size,int x,int y){
g.setColor(color);
g.setFont(new Font("仿宋",Font.BOLD,size));
g.drawString(str,x,y);
}
}
4.1 获取蛇头的图片
public static Image upImg = Toolkit.getDefaultToolkit().getImage("img/up.png");
public static Image downImg = Toolkit.getDefaultToolkit().getImage("img/down.png");
public static Image leftImg = Toolkit.getDefaultToolkit().getImage("img/left.png");
public static Image rightImg = Toolkit.getDefaultToolkit().getImage("img/right.png");
4.2 获取蛇身的图片
public static Image bodyImg = Toolkit.getDefaultToolkit().getImage("img/body.png");
4.3 获取食物的图片
public static Image foodImg = Toolkit.getDefaultToolkit().getImage("img/food.png");
4.4 绘制文字
//绘制文字
public static void drawWord(Graphics g,String str,Color color,int size,int x,int y){
g.setColor(color);
g.setFont(new Font("仿宋",Font.BOLD,size));
g.drawString(str,x,y);
}
五:蛇头部的绘制——SnakeHeadEntity
import java.awt.*;
public class SnakeHeadEntity extends SnakeEntity{
//方向 up down left right
private String direction = "right";
public String getDirection() {
return direction;
}
public void setDirection(String direction) {
this.direction = direction;
}
public SnakeHeadEntity (Image img, int x, int y, GameWin frame) {
super(img, x, y, frame);
}
@Override
public void paintSelf(Graphics g) {
super.paintSelf(g);
}
}
5.1 设置蛇头的方向
//方向 up down left right
private String direction = "right";
默认蛇头的方向是向右的!
5.2 绘制自身
@Override
public void paintSelf(Graphics g) {
super.paintSelf(g);
}
六:蛇头的简单移动
6.1 在HeadObj添加move方法
/**
* 蛇的移动
* 蛇身体的移动的代码一定要写在蛇头移动的前面
*/
public void move(){
//蛇头的移动
switch (direction) {
case "up":
y -= height;
break;
case "down":
y += height;
break;
case "left":
x -= width;
break;
case "right":
x += width;
break;
default:
break;
}
}
6.2 在paintSelf方法中添加move方法
@Override
public void paintSelf(Graphics g) {
//设置蛇头的位置
super.paintSelf(g);
//移动蛇头
move();
}
6.3 蛇的重复移动需要调用repaint()方法
/**
* 窗口的绘制
*/
public void launch(){
//设置窗口是否可见、默认是false不可见
this.setVisible(true);
//设置窗口的大小
this.setSize(winWidth,winHeight);
//设置窗口的位置在屏幕居中
this.setLocationRelativeTo(null);
//设置窗口的标题
this.setTitle("贪吃蛇");
//移动蛇头
while(true){
// repaint()方法是一个具有刷新页面效果的方法,若不调用repaint方法图形发生变化后不会立刻显示
repaint();
try {
//一秒会调用五次
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
七:键盘控制蛇的方向
7.1 在SnakeHeadEntity中添加键盘监听事件
public SnakeHeadEntity(Image img, int x, int y, SnakeGame frame) {
super(img, x, y, frame);
//监听键盘
this.frame.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
//控制蛇头的移动
changeDirection(e);
}
});
}
7.2 编写changeDirection方法
/**
* 控制蛇头的移动
* w->a->s->d
*/
public void changeDirection(KeyEvent e) {
switch (e.getKeyCode()){
//按下A键->朝左移动
case KeyEvent.VK_A:
if(!"right".equals(direction)){
//将方向改为左边
direction = "left";
//修改蛇头图片的方向
img = SnakeUtil.leftSnakeHeadImg;
}
break;
//按下D键->朝右移动
case KeyEvent.VK_D:
if(!"left".equals(direction)){
//将方向改为右边
direction = "right";
//修改蛇头图片的方向
img = SnakeUtil.rightSnakeHeadImg;
}
break;
//按下W键->朝上移动
case KeyEvent.VK_W:
if(!"down".equals(direction)){
//将方向改为上边
direction = "up";
//修改蛇头图片的方向
img = SnakeUtil.upSnakeHeadImg;
}
break;
//按下S键->朝下移动
case KeyEvent.VK_S:
if(!"up".equals(direction)){
//将方向改为上边
direction = "down";
//修改蛇头图片的方向
img = SnakeUtil.downSnakeHeadImg;
}
break;
default:
break;
}
}
八:蛇越界后的处理
1.编写checkIndex()方法
注:标题栏高度为30,所以y<30,不是y<0
/**
* 贪吃蛇的越界处理
*/
public void checkIndex(){
if(x < 0){
x = 570;
} else if (x > 570){
x = 0;
} else if (y < 30){
y = 570;
} else if (y > 570){
y = 30;
}
}
2.在paintSelf方法中添加checkIndex()方法
@Override
public void paintSelf(Graphics g) {
//设置蛇头的位置
super.paintSelf(g);
//移动蛇头
move();
checkIndex();
}
九:蛇身的添加和移动
9.1 新建蛇身实体类——SnakeBodyEntity
/**
* 蛇身体
*/
public class SnakeBodyEntity extends SnakeEntity {
public SnakeBodyEntity(Image img, int x, int y, SnakeGame frame) {
super(img, x, y, frame);
}
@Override
public void paintSelf(Graphics g) {
super.paintSelf(g);
}
}
9.2 在SnakeGame定义蛇身集合
/**
* 蛇身集合
*/
public List snakeBodyEntityList = new ArrayList<>();
9.3 在launch()方法对蛇身进行初始化
//蛇身初始化
snakeBodyEntityList.add(new SnakeBodyEntity(SnakeUtil.snakeBodyImg,30,570,this));
snakeBodyEntityList.add(new SnakeBodyEntity(SnakeUtil.snakeBodyImg,0,570,this));
9.4 在paint方法绘制蛇身体
防止身体重叠,所以进行反向遍历
//绘制蛇身体
for (int i = snakeBodyEntityList.size() - 1; i >= 0; i--) {
snakeBodyEntityList.get(i).paintSelf(graphicsImage);
}
9.5 在move方法中添加身体移动逻辑
//蛇身体的移动
List<SnakeBodyEntity> snakeBodyEntityList = this.frame.snakeBodyEntityList;
for (int i = snakeBodyEntityList.size() - 1; i >= 1; i--) {
//蛇的移动就是上一个坐标和当前坐标相等
snakeBodyEntityList.get(i).x = snakeBodyEntityList.get(i-1).x;
snakeBodyEntityList.get(i).y = snakeBodyEntityList.get(i-1).y;
}
十:食物的随机生成
食物的坐标需要满足x->(0-570),y->(30,570)并且是30的倍数
10.1 新建食物实体类——SnakeFoodEntity
/**
* 食物实体类
*/
public class SnakeFoodEntity extends SnakeEntity {
/**
* 随机
*/
Random r = new Random();
public SnakeFoodEntity() {
super();
}
public SnakeFoodEntity(Image img, int x, int y, SnakeGame frame) {
super(img, x, y, frame);
}
/**
* 获取食物
* x:0->570
* y.3:0->570
* @return
*/
public SnakeFoodEntity getFood(){
return new SnakeFoodEntity(SnakeUtil.snakeFoodImg,r.nextInt(20) * 30,(r.nextInt(19) + 1) * 30,this.frame);
}
@Override
public void paintSelf(Graphics g) {
super.paintSelf(g);
}
}
10.2 获取食物
/**
* 获取食物
* x:0->570
* y.3:0->570
* @return
*/
public SnakeFoodEntity getFood(){
return new SnakeFoodEntity(SnakeUtil.snakeFoodImg,r.nextInt(20) * 30,(r.nextInt(19) + 1) * 30,this.frame);
}
10.3 在SnakeGame中添加食物实体类
/**
* 食物
*/
public SnakeFoodEntity snakeFoodEntity = new SnakeFoodEntity().getFood();
10.4 在paint中绘制食物
//食物绘制
snakeFoodEntity.paintSelf(g);
十一:蛇吃食物和食物的重新生成
11.1 在SnakeHeadEntity中的paintSelf添加获取食物的对象
//食物
SnakeFoodEntity snakeFoodEntity = this.frame.snakeFoodEntity;
注:此方法需要放在move()之前
11.2 判断蛇头和食物是否重合
if(this.x == snakeFoodEntity.x && this.y == snakeFoodEntity.y ){
//此时蛇头和食物重合了
this.frame.snakeFoodEntity = snakeFoodEntity.getFood();
}
十二:蛇的增长
蛇的增长本质就是给蛇身添加一个元素,根据集合的最后一个位置确定
12.1 定义身体最后一节的坐标
//身体最后一节的坐标
Integer newX = null;
Integer newY = null;
if(this.x == snakeFoodEntity.x && this.y == snakeFoodEntity.y ){
//此时蛇头和食物重合了
this.frame.snakeFoodEntity = snakeFoodEntity.getFood();
//获取蛇身的最后一个元素
SnakeBodyEntity lastSnakeBodyEntity = this.frame.snakeBodyEntityList.get(this.frame.snakeBodyEntityList.size() - 1);
newX = lastSnakeBodyEntity.x;
newY = lastSnakeBodyEntity.y;
}
12.2 在move方法后添加元素
if(newX != null && newY != null){
this.frame.snakeBodyEntityList.add(new SnakeBodyEntity(SnakeUtil.snakeBodyImg,newX,newY,this.frame));
}
12.3 修改move方法的遍历顺序
/**
* 蛇的移动
* 蛇身体的移动的代码一定要写在蛇头移动的前面
*/
public void move(){
//蛇身体的移动
List<SnakeBodyEntity> snakeBodyEntityList = this.frame.snakeBodyEntityList;
for (int i = snakeBodyEntityList.size() - 1; i >= 1; i--) {
//蛇的移动就是上一个坐标和当前坐标相等
snakeBodyEntityList.get(i).x = snakeBodyEntityList.get(i-1).x;
snakeBodyEntityList.get(i).y = snakeBodyEntityList.get(i-1).y;
}
snakeBodyEntityList.get(0).x = this.x;
snakeBodyEntityList.get(0).y = this.y;
//蛇头的移动
switch (direction) {
case "up":
y -= height;
break;
case "down":
y += height;
break;
case "left":
x -= width;
break;
case "right":
x += width;
break;
default:
break;
}
}
十三:计分面板的编写
13.1 在SnakeGame定义窗口的宽高
//窗口宽高
int winWidth = 800;
int winHeight = 600;
注:将之前定义的600,600替换为winWidth ,winHeight
13.2 定义游戏分数,并且在paint绘制出来
//当前分数
public int score = 0;
//分数绘制
SnakeUtil.drawWord(graphicsImage,score +"分" , Color.BLUE , 50,650,330);
13.3 蛇吃食物,分数加一
在SnakeHeadEntity的paintSelf添加对应方法
@Override
public void paintSelf(Graphics g) {
//设置蛇头的位置
super.paintSelf(g);
//食物
SnakeFoodEntity snakeFoodEntity = this.frame.snakeFoodEntity;
//身体最后一节的坐标
Integer newX = null;
Integer newY = null;
if(this.x == snakeFoodEntity.x && this.y == snakeFoodEntity.y ){
//此时蛇头和食物重合了
this.frame.snakeFoodEntity = snakeFoodEntity.getFood();
//获取蛇身的最后一个元素
SnakeBodyEntity lastSnakeBodyEntity = this.frame.snakeBodyEntityList.get(this.frame.snakeBodyEntityList.size() - 1);
newX = lastSnakeBodyEntity.x;
newY = lastSnakeBodyEntity.y;
//分数+1
this.frame.score ++;
}
//移动蛇头
move();
if(newX != null && newY != null){
this.frame.snakeBodyEntityList.add(new SnakeBodyEntity(SnakeUtil.snakeBodyImg,newX,newY,this.frame));
}
//贪吃蛇的越界处理,移动后新的身体对象添加到snakeBodyEntityList
checkIndex();
}
十四:游戏开始的提示语
14.1 在SnakeGame定义游戏状态
//游戏状态 0-未开始 1-游戏中 2-暂停 3-失败 4-通关 5-失败后重启 6-下一关
public static int state = 0;
14.2 在while语句中添加判断,只有状态为1,才需要重复执行repaint()方法
/**
* 窗口的绘制
*/
public void launch(){
//设置窗口是否可见、默认是false不可见
this.setVisible(true);
//设置窗口的大小
this.setSize(winWidth,winHeight);
//设置窗口的位置在屏幕居中
this.setLocationRelativeTo(null);
//设置窗口的标题
this.setTitle("贪吃蛇");
//蛇身初始化
snakeBodyEntityList.add(new SnakeBodyEntity(SnakeUtil.snakeBodyImg,30,570,this));
snakeBodyEntityList.add(new SnakeBodyEntity(SnakeUtil.snakeBodyImg,0,570,this));
//对开始游戏添加键盘事件
this.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
//点击空格
if(e.getKeyCode() == KeyEvent.VK_SPACE){
switch (state){
case 0:
//未开始
state = 1;
break;
case 1:
//游戏中
state = 2;
repaint();
break;
case 2:
//游戏暂停
state = 1;
break;
case 3:
//失败后重启
state = 5;
break;
case 4:
//游戏通关,进入下一关
state = 6;
break;
default:
break;
}
}
}
});
//移动蛇头
while(true){
//只有状态为游戏中,才需要反复执行repaint()方法
if(state == 1){
// repaint()方法是一个具有刷新页面效果的方法,若不调用repaint方法图形发生变化后不会立刻显示
repaint();
}
try {
//一秒会调用五次
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
14.3 绘制提示语
/**
* 绘制提示语
* @param g
*/
void prompt(Graphics g){
//未开始
if(state == 0){
g.fillRect(120,240,400,70);
SnakeUtil.drawWord(g,"按下空格开始游戏",Color.yellow,35,150,290);
}
}
14.4 在paint方法添加提示语
/**
* 绘制
* @param g
*/
@Override
public void paint(Graphics g){
//灰色背景
g.setColor(Color.gray);
//绘制矩形,和窗口一样,宽和高都是600
g.fillRect(0,0,winWidth,winHeight);
//网格线颜色
g.setColor(Color.black);
//绘制网格线
for (int i = 0; i <= 20 ; i++) {
//横线
g.drawLine(0,i * 30,600,i * 30);
//竖线
g.drawLine(i * 30,0,i * 30,600);
}
//绘制蛇身体
for (int i = snakeBodyEntityList.size() - 1; i >= 0; i--) {
snakeBodyEntityList.get(i).paintSelf(g);
}
//绘制蛇头
snakeHeadEntity.paintSelf(g);
//食物绘制
snakeFoodEntity.paintSelf(g);
//关卡绘制
SnakeUtil.drawWord(g,"第"+SnakeUtil.level+"关",Color.orange,40,650,260);
//分数绘制
SnakeUtil.drawWord(g,score +"分" , Color.BLUE , 50,650,330);
//绘制提示语
g.setColor(Color.gray);
prompt(g);
}
十五:游戏开始和暂停的键盘事件以及通关判断
15.1 在SnakeGame的launch方法添加键盘函数
//对开始游戏添加键盘事件
this.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
//点击空格
if(e.getKeyCode() == KeyEvent.VK_SPACE){
switch (state){
case 0:
//未开始
state = 1;
break;
case 1:
//游戏中
state = 2;
repaint();
break;
case 2:
//游戏暂停
state = 1;
break;
case 3:
//失败后重启
state = 5;
break;
case 4:
//游戏通关,进入下一关
state = 6;
break;
default:
break;
}
}
}
});
15.2 通关判断
在SnakeHeadEntity的paintSelf中添加
//通关判断
if(this.frame.score >= 3){
//通关
SnakeGame.state = 4;
}
15.3 添加通关提示语
/**
* 绘制提示语
* @param g
*/
void prompt(Graphics g){
//未开始
if(state == 0){
g.fillRect(120,240,400,70);
SnakeUtil.drawWord(g,"按下空格开始游戏",Color.yellow,35,150,290);
}
//游戏暂停
if(state == 2){
g.fillRect(120,240,400,70);
SnakeUtil.drawWord(g,"按下空格开始游戏",Color.yellow,35,150,290);
}
//通关
if(state == 4){
g.fillRect(120,240,400,70);
if(SnakeUtil.level == 3){
SnakeUtil.drawWord(g,"达成条件,游戏通关",Color.green,35,150,290);
} else {
SnakeUtil.drawWord(g,"达成条件,点击空格开始下一关",Color.green,35,150,290);
}
}
}
十六:蛇和身体碰撞判断
16.1 在move方法中添加代码
//蛇头和身体碰撞判断
if(this.x == snakeBodyEntityList.get(i).x && this.y == snakeBodyEntityList.get(i).y){
//蛇咬到自己,失败
SnakeGame.state = 3;
}
16.2 在prompt方法中添加游戏失败的提示语
//失败
if(state == 3){
g.fillRect(120,240,400,70);
SnakeUtil.drawWord(g,"失败,按下空格重新开始",Color.red,35,150,290);
}
十七:双缓存解决画面闪烁
原因:每次刷新都是重新绘制图片
//定义双缓存图片
Image offScreenImage = null;
/**
* 绘制
* @param g
*/
@Override
public void paint(Graphics g){
//初始化双缓存图片
if(offScreenImage == null){
offScreenImage = this.createImage(winWidth,winHeight);
}
//获取图片对应的Graphics对象
Graphics graphicsImage = offScreenImage.getGraphics();
//灰色背景
graphicsImage.setColor(Color.gray);
//绘制矩形,和窗口一样,宽和高都是600
graphicsImage.fillRect(0,0,winWidth,winHeight);
//网格线颜色
graphicsImage.setColor(Color.black);
//绘制网格线
for (int i = 0; i <= 20 ; i++) {
//横线
graphicsImage.drawLine(0,i * 30,600,i * 30);
//竖线
graphicsImage.drawLine(i * 30,0,i * 30,600);
}
//绘制蛇身体
for (int i = snakeBodyEntityList.size() - 1; i >= 0; i--) {
snakeBodyEntityList.get(i).paintSelf(graphicsImage);
}
//绘制蛇头
snakeHeadEntity.paintSelf(graphicsImage);
//食物绘制
snakeFoodEntity.paintSelf(graphicsImage);
//关卡绘制
SnakeUtil.drawWord(graphicsImage,"第"+SnakeUtil.level+"关",Color.orange,40,650,260);
//分数绘制
SnakeUtil.drawWord(graphicsImage,score +"分" , Color.BLUE , 50,650,330);
//绘制提示语
graphicsImage.setColor(Color.gray);
prompt(graphicsImage);
//将双缓存图片绘制到主窗口中
g.drawImage(offScreenImage,0,0,null);
}
十八:游戏失败后重新开始
18.1 SnakeGame添加resetGame方法
/**
* 失败后,重置游戏
*/
void resetGame(){
//关闭当前窗口
//开启新的窗口
String[] args = {};
main(args);
}
18.2 修改Switch语句
/**
* 窗口的绘制
*/
public void launch(){
//设置窗口是否可见、默认是false不可见
this.setVisible(true);
//设置窗口的大小
this.setSize(winWidth,winHeight);
//设置窗口的位置在屏幕居中
this.setLocationRelativeTo(null);
//设置窗口的标题
this.setTitle("贪吃蛇");
//蛇身初始化
snakeBodyEntityList.add(new SnakeBodyEntity(SnakeUtil.snakeBodyImg,30,570,this));
snakeBodyEntityList.add(new SnakeBodyEntity(SnakeUtil.snakeBodyImg,0,570,this));
//对开始游戏添加键盘事件
this.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
//点击空格
if(e.getKeyCode() == KeyEvent.VK_SPACE){
switch (state){
case 0:
//未开始
state = 1;
break;
case 1:
//游戏中
state = 2;
repaint();
break;
case 2:
//游戏暂停
state = 1;
break;
case 3:
//失败后重启
state = 5;
break;
case 4:
//游戏通关,进入下一关
state = 6;
break;
default:
break;
}
}
}
});
//移动蛇头
while(true){
//只有状态为游戏中,才需要反复执行repaint()方法
if(state == 1){
// repaint()方法是一个具有刷新页面效果的方法,若不调用repaint方法图形发生变化后不会立刻显示
repaint();
}
//失败重启
if(state == 5){
state = 0;
resetGame();
}
//进入下一关
if(state == 6 && SnakeUtil.level != 3){
state = 1;
SnakeUtil.level++;
resetGame();
}
try {
//一秒会调用五次
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
十九:源码奉上
19.1 SnakeGame
package com.sysg.game.sanke;
import com.sysg.game.sanke.entity.SnakeBodyEntity;
import com.sysg.game.sanke.entity.SnakeFoodEntity;
import com.sysg.game.sanke.entity.SnakeHeadEntity;
import com.sysg.game.sanke.utils.SnakeUtil;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;
/**
* @author ikun
*/
public class SnakeGame extends JFrame {
//定义双缓存图片
Image offScreenImage = null;
//游戏状态 0-未开始 1-游戏中 2-暂停 3-失败 4-通关 5-失败后重启 6-下一关
public static int state = 0;
//当前分数
public int score = 0;
//窗口宽高
int winWidth = 800;
int winHeight = 600;
/**
* 创建蛇头对象
* 默认为右边、坐标为30,570、窗口为当前this
*/
SnakeHeadEntity snakeHeadEntity = new SnakeHeadEntity(SnakeUtil.rightSnakeHeadImg,60,570,this);
/**
* 蛇身集合
*/
public List<SnakeBodyEntity> snakeBodyEntityList = new ArrayList<>();
/**
* 食物
*/
public SnakeFoodEntity snakeFoodEntity = new SnakeFoodEntity().getFood();
/**
* 窗口的绘制
*/
public void launch(){
//设置窗口是否可见、默认是false不可见
this.setVisible(true);
//设置窗口的大小
this.setSize(winWidth,winHeight);
//设置窗口的位置在屏幕居中
this.setLocationRelativeTo(null);
//设置窗口的标题
this.setTitle("贪吃蛇");
//蛇身初始化
snakeBodyEntityList.add(new SnakeBodyEntity(SnakeUtil.snakeBodyImg,30,570,this));
snakeBodyEntityList.add(new SnakeBodyEntity(SnakeUtil.snakeBodyImg,0,570,this));
//对开始游戏添加键盘事件
this.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
//点击空格
if(e.getKeyCode() == KeyEvent.VK_SPACE){
switch (state){
case 0:
//未开始
state = 1;
break;
case 1:
//游戏中
state = 2;
repaint();
break;
case 2:
//游戏暂停
state = 1;
break;
case 3:
//失败后重启
state = 5;
break;
case 4:
//游戏通关,进入下一关
state = 6;
break;
default:
break;
}
}
}
});
//移动蛇头
while(true){
//只有状态为游戏中,才需要反复执行repaint()方法
if(state == 1){
// repaint()方法是一个具有刷新页面效果的方法,若不调用repaint方法图形发生变化后不会立刻显示
repaint();
}
//失败重启
if(state == 5){
state = 0;
resetGame();
}
//进入下一关
if(state == 6 && SnakeUtil.level != 3){
state = 1;
SnakeUtil.level++;
resetGame();
}
try {
//一秒会调用五次
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 绘制
* @param g
*/
@Override
public void paint(Graphics g){
//初始化双缓存图片
if(offScreenImage == null){
offScreenImage = this.createImage(winWidth,winHeight);
}
//获取图片对应的Graphics对象
Graphics graphicsImage = offScreenImage.getGraphics();
//灰色背景
graphicsImage.setColor(Color.gray);
//绘制矩形,和窗口一样,宽和高都是600
graphicsImage.fillRect(0,0,winWidth,winHeight);
//网格线颜色
graphicsImage.setColor(Color.black);
//绘制网格线
for (int i = 0; i <= 20 ; i++) {
//横线
graphicsImage.drawLine(0,i * 30,600,i * 30);
//竖线
graphicsImage.drawLine(i * 30,0,i * 30,600);
}
//绘制蛇身体
for (int i = snakeBodyEntityList.size() - 1; i >= 0; i--) {
snakeBodyEntityList.get(i).paintSelf(graphicsImage);
}
//绘制蛇头
snakeHeadEntity.paintSelf(graphicsImage);
//食物绘制
snakeFoodEntity.paintSelf(graphicsImage);
//关卡绘制
SnakeUtil.drawWord(graphicsImage,"第"+SnakeUtil.level+"关",Color.orange,40,650,260);
//分数绘制
SnakeUtil.drawWord(graphicsImage,score +"分" , Color.BLUE , 50,650,330);
//绘制提示语
graphicsImage.setColor(Color.gray);
prompt(graphicsImage);
//将双缓存图片绘制到主窗口中
g.drawImage(offScreenImage,0,0,null);
}
/**
* 绘制提示语
* @param g
*/
void prompt(Graphics g){
//未开始
if(state == 0){
g.fillRect(120,240,400,70);
SnakeUtil.drawWord(g,"按下空格开始游戏",Color.yellow,35,150,290);
}
//游戏暂停
if(state == 2){
g.fillRect(120,240,400,70);
SnakeUtil.drawWord(g,"按下空格开始游戏",Color.yellow,35,150,290);
}
//失败
if(state == 3){
g.fillRect(120,240,400,70);
SnakeUtil.drawWord(g,"失败,按下空格重新开始",Color.red,35,150,290);
}
//通关
if(state == 4){
g.fillRect(120,240,400,70);
if(SnakeUtil.level == 3){
SnakeUtil.drawWord(g,"达成条件,游戏通关",Color.green,35,150,290);
} else {
SnakeUtil.drawWord(g,"达成条件,点击空格开始下一关",Color.green,35,150,290);
}
}
}
/**
* 失败后,重置游戏
*/
void resetGame(){
//关闭当前窗口
//开启新的窗口
String[] args = {};
main(args);
}
public static void main(String[] args) {
SnakeGame snakeGame = new SnakeGame();
snakeGame.launch();
}
}
19.2 SnakeUtil
package com.sysg.game.sanke.utils;
import java.awt.*;
/**
* 贪吃蛇工具类
* @author ikun
*/
public class SnakeUtil {
//蛇头
public static Image upSnakeHeadImg = Toolkit.getDefaultToolkit().getImage("src/main/resources/static/img/snake/up.png");
public static Image downSnakeHeadImg = Toolkit.getDefaultToolkit().getImage("src/main/resources/static/img/snake/down.png");
public static Image rightSnakeHeadImg = Toolkit.getDefaultToolkit().getImage("src/main/resources/static/img/snake/right.png");
public static Image leftSnakeHeadImg = Toolkit.getDefaultToolkit().getImage("src/main/resources/static/img/snake/left.png");
//蛇身
public static Image snakeBodyImg = Toolkit.getDefaultToolkit().getImage("src/main/resources/static/img/snake/body.png");
//食物
public static Image snakeFoodImg = Toolkit.getDefaultToolkit().getImage("src/main/resources/static/img/snake/food.png");
//关卡,默认是第一关
public static int level = 1;
/**
* 绘制文字
* @param g 绘图的实体类
* @param str 字符串的内容
* @param color 字符串的颜色
* @param strFontSize 字符串的字体大小
* @param x 字符串的横坐标
* @param y 字符串的纵坐标
*/
public static void drawWord(Graphics g,String str,Color color,int strFontSize,int x,int y){
g.setColor(color);
//设置字体样式,Font.BOLD->粗体
g.setFont(new Font("仿宋",Font.BOLD,strFontSize));
//将文字绘制到屏幕上
g.drawString(str,x,y);
}
}
19.3 SnakeEntity
package com.sysg.game.sanke.entity;
import com.sysg.game.sanke.SnakeGame;
import java.awt.*;
/**
* 贪吃蛇实体类
*/
public class SnakeEntity {
//物体的图片
Image img;
//物体的坐标
int x,y;
//物体的宽高,都为30,与方格保持一致
int width = 30,height = 30;
//窗口类的引用
SnakeGame frame;
public Image getImg() {
return img;
}
public void setImg(Image img) {
this.img = img;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public SnakeGame getFrame() {
return frame;
}
public void setFrame(SnakeGame frame) {
this.frame = frame;
}
/**
* 有参构造
* @param img
* @param x
* @param y
* @param frame
*/
public SnakeEntity(Image img, int x, int y, SnakeGame frame) {
this.img = img;
this.x = x;
this.y = y;
this.frame = frame;
}
/**
* 有参构造
* @param img
* @param x
* @param y
* @param width
* @param height
* @param frame
*/
public SnakeEntity(Image img, int x, int y, int width, int height, SnakeGame frame) {
this.img = img;
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.frame = frame;
}
/**
* 无参构造
*/
public SnakeEntity() {
}
//绘制自身的方法
public void paintSelf(Graphics g){
g.drawImage(img,x,y,null);
}
}
19.4 SnakeHeadEntity
package com.sysg.game.sanke.entity;
import com.sysg.game.sanke.SnakeGame;
import com.sysg.game.sanke.utils.SnakeUtil;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.List;
/**
* 蛇头实体类
* @author ikun
*/
public class SnakeHeadEntity extends SnakeEntity{
/**
* 蛇头方向 up down left right
* 默认为right
*/
private String direction = "right";
public String getDirection() {
return direction;
}
public void setDirection(String direction) {
this.direction = direction;
}
public SnakeHeadEntity(Image img, int x, int y, SnakeGame frame) {
super(img, x, y, frame);
//监听键盘
this.frame.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
//控制蛇头的移动
changeDirection(e);
}
});
}
/**
* 控制蛇头的移动
* w->a->s->d
*/
public void changeDirection(KeyEvent e) {
switch (e.getKeyCode()){
//按下A键->朝左移动
case KeyEvent.VK_A:
if(!"right".equals(direction)){
//将方向改为左边
direction = "left";
//修改蛇头图片的方向
img = SnakeUtil.leftSnakeHeadImg;
}
break;
//按下D键->朝右移动
case KeyEvent.VK_D:
if(!"left".equals(direction)){
//将方向改为右边
direction = "right";
//修改蛇头图片的方向
img = SnakeUtil.rightSnakeHeadImg;
}
break;
//按下W键->朝上移动
case KeyEvent.VK_W:
if(!"down".equals(direction)){
//将方向改为上边
direction = "up";
//修改蛇头图片的方向
img = SnakeUtil.upSnakeHeadImg;
}
break;
//按下S键->朝下移动
case KeyEvent.VK_S:
if(!"up".equals(direction)){
//将方向改为上边
direction = "down";
//修改蛇头图片的方向
img = SnakeUtil.downSnakeHeadImg;
}
break;
default:
break;
}
}
/**
* 蛇的移动
* 蛇身体的移动的代码一定要写在蛇头移动的前面
*/
public void move(){
//蛇身体的移动
List<SnakeBodyEntity> snakeBodyEntityList = this.frame.snakeBodyEntityList;
for (int i = snakeBodyEntityList.size() - 1; i >= 1; i--) {
//蛇的移动就是上一个坐标和当前坐标相等
snakeBodyEntityList.get(i).x = snakeBodyEntityList.get(i-1).x;
snakeBodyEntityList.get(i).y = snakeBodyEntityList.get(i-1).y;
//蛇头和身体碰撞判断
if(this.x == snakeBodyEntityList.get(i).x && this.y == snakeBodyEntityList.get(i).y){
//蛇咬到自己,失败
SnakeGame.state = 3;
}
}
snakeBodyEntityList.get(0).x = this.x;
snakeBodyEntityList.get(0).y = this.y;
//蛇头的移动
switch (direction) {
case "up":
y -= height;
break;
case "down":
y += height;
break;
case "left":
x -= width;
break;
case "right":
x += width;
break;
default:
break;
}
}
@Override
public void paintSelf(Graphics g) {
//设置蛇头的位置
super.paintSelf(g);
//食物
SnakeFoodEntity snakeFoodEntity = this.frame.snakeFoodEntity;
//身体最后一节的坐标
Integer newX = null;
Integer newY = null;
if(this.x == snakeFoodEntity.x && this.y == snakeFoodEntity.y ){
//此时蛇头和食物重合了
this.frame.snakeFoodEntity = snakeFoodEntity.getFood();
//获取蛇身的最后一个元素
SnakeBodyEntity lastSnakeBodyEntity = this.frame.snakeBodyEntityList.get(this.frame.snakeBodyEntityList.size() - 1);
newX = lastSnakeBodyEntity.x;
newY = lastSnakeBodyEntity.y;
//分数+1
this.frame.score ++;
}
//通关判断
if(this.frame.score >= 3){
//通关
SnakeGame.state = 4;
}
//移动蛇头
move();
if(newX != null && newY != null){
this.frame.snakeBodyEntityList.add(new SnakeBodyEntity(SnakeUtil.snakeBodyImg,newX,newY,this.frame));
}
//贪吃蛇的越界处理,移动后新的身体对象添加到snakeBodyEntityList
checkIndex();
}
/**
* 贪吃蛇的越界处理
*/
public void checkIndex(){
if(x < 0){
x = 570;
} else if (x > 570){
x = 0;
} else if (y < 30){
y = 570;
} else if (y > 570){
y = 30;
}
}
}
19.5 SnakeBodyEntity
package com.sysg.game.sanke.entity;
import com.sysg.game.sanke.SnakeGame;
import java.awt.*;
/**
* 蛇身体
*/
public class SnakeBodyEntity extends SnakeEntity {
public SnakeBodyEntity(Image img, int x, int y, SnakeGame frame) {
super(img, x, y, frame);
}
@Override
public void paintSelf(Graphics g) {
super.paintSelf(g);
}
}
19.6 SnakeFoodEntity
package com.sysg.game.sanke.entity;
import com.sysg.game.sanke.SnakeGame;
import com.sysg.game.sanke.utils.SnakeUtil;
import java.awt.*;
import java.util.Random;
/**
* 食物实体类
*/
public class SnakeFoodEntity extends SnakeEntity {
/**
* 随机
*/
Random r = new Random();
public SnakeFoodEntity() {
super();
}
public SnakeFoodEntity(Image img, int x, int y, SnakeGame frame) {
super(img, x, y, frame);
}
/**
* 获取食物
* x:0->570
* y.3:0->570
* @return
*/
public SnakeFoodEntity getFood(){
return new SnakeFoodEntity(SnakeUtil.snakeFoodImg,r.nextInt(20) * 30,(r.nextInt(19) + 1) * 30,this.frame);
}
@Override
public void paintSelf(Graphics g) {
super.paintSelf(g);
}
}