贪吃蛇第五弹
要求: 创建一个界面,并在界面中画单元格,添加一个线程控制重画,添加键盘方向键来控制蛇的移动,并使小蛇初始时就朝一个方向移动,并添加一个食物类,添加碰撞检测功能
代码:
package snake;
import java.awt.Color;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
/**
* 项目名称:Snake
* 创建人:YM
* 类描述: 创建一个界面,并在界面中画单元格,添加一个线程控制重画,添加键盘方向键来控制蛇的移动
* 并使小蛇初始时就朝一个方向移动,并添加一个食物类,添加碰撞检测功能
* 创建时间:2019年1月16日 下午2:59:06
*
*/
public class SnakeMain extends Frame {
static final int SNAKE_WIDTH = 1000; // 定义组件的宽度
static final int SNAKE_HEIGHT = 600; // 定义组件的高度
private static final int squareSize = 25; // 用来确定单元格的大小
public static final int xwidth= SNAKE_WIDTH / squareSize;
public static final int xheight = SNAKE_HEIGHT / squareSize;
private Snake s = new Snake (this);//实例化一个对象,确定蛇的位置,并确定蛇的初始移方向
private Egg egg=new Egg();//实例化一个对象,用来引用食物
private Image offScreenImage = null; //利用双缓冲来消除屏幕的闪烁
public static void main(String[] args) {
SnakeMain s = new SnakeMain(); // 构建SnakeMai类的一个实例,并命名为s
s.launchFrame(); // 调用实例中的launchFrame()方法
}
private void launchFrame() {
this.setLocation(100, 100); // 确定组件位置
this.setSize(SNAKE_WIDTH, SNAKE_HEIGHT); // 确定组件大小
this.setBackground(Color.WHITE); // 确定组件背景颜色
this.setTitle("我的贪吃蛇"); // 设置窗口的标题
// 添加关闭的处理事件
this.addWindowListener(new WindowAdapter() {
// 添加一个窗口监听
@Override
public void windowClosing(WindowEvent e) {
System.exit(0); // 窗口关闭事件
}
});
// 禁止改变窗的大小
this.setResizable(false);
this.setVisible(true);
//通过调用线程运行启动线程
new Thread(new MyPaintThread()).start();
//通过添加指定的按键侦听器,以接收此组件的按键事件
this.addKeyListener(new MyKeyListener());
}
@Override
public void update(Graphics g) {
//利用双缓冲来消除屏幕的闪烁的书写方法
if(offScreenImage==null){
offScreenImage = this.createImage(SNAKE_WIDTH, SNAKE_HEIGHT);//绘制一个与屏幕窗口一致的对象
}
Graphics offg = offScreenImage.getGraphics();
//先将内容画在虚拟画布上
paint(offg);
//然后将虚拟画布上的内容一起画在画布上
g.drawImage(offScreenImage, 0, 0, null);
s.draw(g); //调用画小蛇的方法
s.eatEgg(egg);
egg.draw(g);//调用食物方法
}
@Override
public void paint(Graphics g) {
// 调用画笔
Color c = g.getColor();
g.setColor(Color.GRAY); // 设置画笔颜色
g.setColor(c);
// 画线
for (int i = 0; i < xwidth; i++) {
g.drawLine(i * squareSize, 0, i * squareSize, SNAKE_HEIGHT);
} // 画纵向的线
for (int i = 0; i < xheight; i++) {
g.drawLine(0, i * squareSize, SNAKE_WIDTH, i * squareSize);
} // 画横向的线
}
private class MyPaintThread implements Runnable{
//通过新建一个MyPaintThread类来实现Runnable接口
//重画线程
@Override
public void run() {
//线程的入口
//每隔80ms重画一次
while(true){
repaint();//会自动调用paint方法
try {
//指定捕获异常的类型
Thread.sleep(80);//可以通过此处控制小蛇移动的速度
} catch (InterruptedException e)
//InterruptedException异常是指当一个当一个正在执行的线程被中断
{
e.printStackTrace();//在命令行中打印异常信息在程序中出错的位置既原因
}
}
}
}
private class MyKeyListener extends KeyAdapter{
// 键盘监听类
@Override
public void keyPressed(KeyEvent e) {
s.keyPressed(e);
}
}
}
package snake;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
public class Snake {
// 确定蛇初始的大小
public static final int width = 25;
public static final int height = 25;
// 利用链表形式实现小蛇的添加
private Node head = null;// 初始头指针为空
private Node tail = null;// 初始尾指针为空
private int size = 0; // 初始小蛇长度为0
private SnakeMain sankeMain;
private Node node = new Node(3, 4, Direction.R);
public class Node {
private int xwidth;// 确定窗口有多少根纵线
private int xheight;// 确定窗口有多少跟横线
// 以上两者主要用来确定头结点位置
private Direction dir;// 实例化方向类
private Node pre; // 指向前一个结点的指针
private Node next; // 指向下一个结点的指针
public Node(int xwidth, int xheight, Direction dir) {
// 构造3个参数的构造方法,并初始化,确定小蛇初始位置和运动方向
this.xwidth = xwidth;
this.xheight = xheight;
this.dir = dir;
}
public void draw(Graphics g) {
// 调用画笔
Color c = g.getColor();
g.setColor(Color.BLACK);// 设置画笔颜色
g.setColor(c);
g.fillRect(xwidth * width, xheight * height, width, height);// 画填充了颜色矩形小蛇
}
}
public Snake(SnakeMain snakeMain) {
// 确定初始时小蛇的位置
head = node;
tail = node;
size++; // 初始小蛇长度变为1
this.sankeMain = sankeMain;
}
private void deleteNodeInTail() {
// 删除一个尾结点
Node node = tail.pre;
tail = null;
node.next = null;
tail = node;
}
public void addNodeInHead() {
// 插入一个头结点
Node node = null;
switch (head.dir) {
case L: // 蛇向上走,x坐标不变,y坐标减一
node = new Node(head.xwidth, head.xheight - 1, head.dir);
break;
case U: // 蛇向左走,x坐标减一,y坐标不变
node = new Node(head.xwidth - 1, head.xheight, head.dir);
break;
case R: // 蛇向下走,x坐标不变,y坐标加一
node = new Node(head.xwidth, head.xheight + 1, head.dir);
break;
case D: // 蛇向右走,x坐标加一,y坐标不变
node = new Node(head.xwidth + 1, head.xheight, head.dir);
break;
}
node.next = head;// 头指针指向新添加的结点
head.pre = node; // 现在的头指针的前一个指针指向原来的结点
head = node;
}
public void move() {
// 在头部添加一个节点,然后删除尾部的节点,这样就完成了移动
addNodeInHead();
deleteNodeInTail();
}
// 构造带4个参数的方法
// public Snake(int x, int y, Direction dir, SnakeMain snakeMain) {
// this.x = x;
// this.y = y;
// this.sankeMain = snakeMain;
// this.dir = dir;
//
// }
public void draw(Graphics g) {
if (head == null) {
return;
}
move();
for (Node node = head; node != null; node = node.next) {
node.draw(g); // 蛇移动后,结点位置改变
}
}
public Rectangle getRect() {
return new Rectangle(head.xwidth * width, head.xheight * height, width, height);
// 返回头结点所在的矩形
}
public boolean eatEgg(Egg egg) {
if (this.getRect().intersects(egg.getRect())) {
// 如果蛇的头结点矩形与食物所在的矩形相交
addNodeInHead(); // 添加结点
egg.reAppear(); // 食物随机变化位置
return true;
} else {
return false;
}
}
// 此代码使小蛇没有按键时就不动
// public void keyPressed(KeyEvent e) {
// // 获取键盘上的键入的方向事件
// int key = e.getKeyCode();
// switch (key) {
// case KeyEvent.VK_LEFT: // 键入为左
// this.x = x - width; // 相对原位置减一个宽度
// break;
// case KeyEvent.VK_UP: // 键入为上
// this.y = y - height; // 相对原位置减一个高度
// break;
// case KeyEvent.VK_RIGHT: // 键入为右
// this.x = x + width; // 相对原位置加一个宽度
// break;
// case KeyEvent.VK_DOWN: // 键入为下
// this.y = y + height;// 相对原位置加一个高度
// break;
// }
// 此代码使小蛇没有按键时就按照初始化方向移动
public void keyPressed(KeyEvent e) {
// 获取键盘上的键入的方向事件
// int key1 = e.getKeyCode();
// switch (key1) {
// case KeyEvent.VK_LEFT: // 键入为左
// this.dir = Direction.L;
// break;
// case KeyEvent.VK_UP: // 键入为上
// this.dir = Direction.U;
// break;
// case KeyEvent.VK_RIGHT: // 键入为右
// this.dir = Direction.R;
// break;
// case KeyEvent.VK_DOWN: // 键入为下
// this.dir = Direction.D;
// break;
//
// }
int key = e.getKeyCode();
switch (key) {
case KeyEvent.VK_LEFT:
head.dir = Direction.U;
break;
case KeyEvent.VK_RIGHT:
head.dir = Direction.D;
break;
case KeyEvent.VK_UP:
head.dir = Direction.L;
break;
case KeyEvent.VK_DOWN:
head.dir = Direction.R;
break;
}
}
// public void move() {
// if (dir == Direction.L) {
// x = x - width;// 相对原位置减一个宽度
// } else if (dir == Direction.U) {
// y = y - height;// 相对原位置减一个高度
// } else if (dir == Direction.R) {
// x = x + width;// 相对原位置加一个宽度
// } else if (dir == Direction.D) {
// y = y + height;// 相对原位置加一个高度
// }
//
// // 边界控制
// if (x <= 0)
// {
// x = 0;
// } else if (x > SnakeMain.SNAKE_WIDTH - width) {
// x = SnakeMain.SNAKE_WIDTH - width;
// }
// if (y <= 0) {
// y = 0;
// } else if (y > SnakeMain.SNAKE_HEIGHT - height) {
// y = SnakeMain.SNAKE_HEIGHT - width;
// }
// }
}
package snake;
public enum Direction {
L,R,U ,D,STOP //枚举列出4个方向LEFT,UP,RIGHT,DOEN
}
package snake;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.util.Random;
public class Egg {
private static final int width = Snake.width; //食物的横轴
private static final int height = Snake.height; //食物的纵轴
private int xwidth; //确定窗口有多少根纵线
private int xheight; //确定窗口有多少跟横线
private static final Random r = new Random(); //调用随机函数,使食物随机出现
private Color color = Color.RED; //确定食物的初始颜色
public Egg(int xwidth, int xheight) {
//创建含有2个参数的构造函数
this.xwidth = xwidth;
this.xheight = xheight;
}
public Egg() {
this((r.nextInt(SnakeMain.xwidth - 2)) + 2, (r.nextInt(SnakeMain.xheight - 2)) + 2);
//食物初始时位置随机
}
public void reAppear() {
this.xwidth = (r.nextInt(SnakeMain.xwidth - 2)) + 2;
this.xheight = (r.nextInt(SnakeMain.xheight - 2) + 2);
//用于食物被吃后的位置随机
}
//画食物
public void draw(Graphics g) {
//调用画笔
Color c = g.getColor();
g.setColor(color);//设置画笔颜色
g.fillOval(width * xwidth, height * xheight, width, height);
//绘制有有填充颜色的椭圆,但是当横轴与纵轴相等时为圆形
g.setColor(c);//确定填充的颜色
//使食物的颜色随时间而改变
if (color == Color.RED) {
color = Color.YELLOW;
} else {
color = Color.RED;
}
}
public Rectangle getRect() {
//返回食物对应的矩形区域,判断矩形是否相交,用于碰撞检测
return new Rectangle(xwidth * width, xheight * height, width, height);
}
}
从贪吃蛇第五弹学到的知识点:
1,private Image offScreenImage = null;//利用双缓冲消除屏幕闪烁法双缓冲即在内存中创建一个与屏幕绘制区域一致的对象,先将图形绘制在内存中中的这个对象上,再利用getGraphics()方法获得空间,并一次性将这个对象上的图形拷贝到屏幕上,这样能大大加快绘图的速度,避免动画上的闪烁效果
详细方法讲解:
1,建立一个Image对象offScreenImage,通过offScreenImage=createImage(width,height)在内存中分配与原来设置的动画窗口一致的空间
2,建立一个Graphics对象 offg,通过offg=offScreenImage.getGraphics()把要绘制的对象保存并放到分配好的内存空间中
3,利用paint(offg)将其全部绘制到内存之中,最后调用paint(Graphics g)方法中的g.drawImage(offScreenImage,0,0,null)将offScreenImage全部一次性的绘制到动画窗口。
内存分配空间窗口关闭调用dispose()方法
2,paint()方法和draw()方法有什么不同
paint()方法在Container类中,主要绘制的是绘制的容器中需要绘制的画面
draw()方法在Graphics类中,主要绘制的是容器中具体的东西
3,碰撞检测技术
游戏中,多个元素是否碰到一起,实际上,通常是用“矩形检测”原理实现的,游戏中,所有的物体都可以抽象成矩形,我们只需要判断两个矩形是否相交即可
getRect()方法返回一个矩形
intersects函数专门用来做矩形的碰撞监测,判断两矩形是否相交