宣传视频在这里:https://www.bilibili.com/video/av62633283#reply1843437215
仿贪吃蛇小游戏,可以穿过边缘,吃到蛋可以变色。
这个是暑假学了一个月java做的。java很多知识点太枯燥,很难有学下去的动力,因此决定从一个小项目开始用实例了解知识点,更容易记住,也更容易学习。因此里面的注释有部分是比较乱的,涉及到一些基础知识点…
目前游戏有一些bug,还不知道怎么处理,不过应该不痛不痒。
跟着b站“马士兵”的视频做的,在此表示感谢0.0
将代码开源给大家,仅供学习参考,严禁用于各种商业用途,转载注明出处。
Yard
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;
public class Yard extends Frame {
public final static int ROWS = 30; //先定义院子的行和列,设置为常量,变量名大写
public final static int COLS = 30;
public static final int BLOCK_SIZE = 20; //每个小格的长度
public static int flag = 1; //线程开关
public static int speed = 50; //fps
public static int init_size = 30; //蛇的初始长度
Image offScreenImage = null; //解决缓冲闪烁问题(刷背景)
public void launch() //将程序运行写在一个函数,里面的this是在调用继承的Frame,不用this也可以
{
this.setLocation(350, 10); //方框位置
this.setSize(COLS*BLOCK_SIZE, ROWS*BLOCK_SIZE ); //方框大小
setResizable(false); //不可调整大小
this.addWindowListener(new WindowAdapter() { //按钮可关闭
@Override
public void windowClosing(WindowEvent e) {
super.windowClosing(e);
System.exit(0);
}
});
this.setVisible(true); //可显示
new Thread(new PaintThread()).start(); //启动线程
this.addKeyListener(new KeyMonitor()); //加入键盘监听事件
}
Snake s = new Snake(); //生成蛇
Egg e = new Egg(); //生成蛋
public static void main(String[] args) {
new Yard().launch(); //launch为非静态,因此需要创建实例对象Yard访问该方法(因为main是静态方法),此为匿名类
}
@Override
public void update(Graphics g) { //双缓冲
if(offScreenImage == null) {
offScreenImage = this.createImage(COLS*BLOCK_SIZE, ROWS*BLOCK_SIZE);
}
Graphics gOff = offScreenImage.getGraphics();
paint(gOff);
g.drawImage(offScreenImage, 0, 0, null);
}
private class KeyMonitor extends KeyAdapter{
@Override
public void keyPressed(KeyEvent e) {
s.keyPressed(e);
}
}
private class PaintThread implements Runnable{
@Override
public void run() {
while(flag == 1) {
repaint();
try {
Thread.sleep(speed); //每speed毫秒一帧
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public void paint(Graphics g) { //画面板 paint方法不需要调用,只需要重写
g.setColor(Color.BLACK); //背景色
g.fillRect(0, 0, COLS*BLOCK_SIZE, ROWS*BLOCK_SIZE); //填充整个矩形区域
g.setColor(Color.RED); //画红线
for(int i = 1;i < ROWS;i++) //画出横线
{
g.drawLine(0, BLOCK_SIZE*i, COLS*BLOCK_SIZE, BLOCK_SIZE*i);
}
for(int i = 1;i < COLS;i++) //画出竖线
{
g.drawLine(BLOCK_SIZE*i, 0, BLOCK_SIZE*i, ROWS*BLOCK_SIZE);
}
s.eat(e); //调用蛇吃蛋函数
s.draw(g); //调用画出蛇的函数
e.draw(g); //调用画出蛋的函数
}
}
Snake
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
//蛇是由头,尾 + n个结点拼接而成,因此蛇的成员变量应该有头结点,尾结点,蛇长度,蛇移动方向。
//结点又作为一个类,是蛇的内部类。结点成员变量有x坐标,y坐标,长度,宽度(表示出结点的位置和大小), 蛇吃蛋后新增结点的方向dir, 结点尾指针(类似于链表).方向有4个,新建一个方向枚举类Dir型表示.
public class Snake {
public Node head = null;
Node tail = null;
int size = 0; //蛇节数
Color snake_color;
Node color_n = head;
private Node n = new Node(20, 20, Dir.L); //初始化Snake
public Snake(Node node) { //在外部类中可以直接访问内部类,但不能直接访问内部类成员
head = node;
tail = node;
size = 1;
}
public Snake() { //初始化Snake
head = n;
tail = n;
head.next = null;
tail.next = null;
size = 1;
for(int i = 1;i <= Yard.init_size;i++) AddToHead(); //设置初始长度
}
public void AddToTail() { //双向链表实现
Node node = null;
switch(tail.dir) {
case U:
node = new Node(tail.row+1, tail.col, tail.dir);
break;
case D:
node = new Node(tail.row-1, tail.col, tail.dir);
break;
case L:
node = new Node(tail.row, tail.col+1, tail.dir);
break;
case R:
node = new Node(tail.row, tail.col-1, tail.dir);
break;
}
tail.next = node;
tail = node;
size++;
}
public void AddToHead() { //双向链表实现
Node node = null;
switch(head.dir) {
case D:
node = new Node(head.row+1, head.col, head.dir);
break;
case U:
node = new Node(head.row-1, head.col, head.dir);
break;
case R:
node = new Node(head.row, head.col+1, head.dir);
break;
case L:
node = new Node(head.row, head.col-1, head.dir);
break;
}
node.next = head;
head.pre = node;
head = node;
size++;
}
public void draw(Graphics g) { //画出整条蛇
if(size <= 0) return;
move();
ChangeColor(); //改变蛇的颜色
for(Node n = head;n != null;n = n.next) //循环画出结点
{
n.draw(g, n);
}
}
private void ChangeColor() { //蛇颜色变化
int speed = 2; //颜色变化速度调节
for(int i = 1;i <= speed;i++)
{
if(color_n == null) return;
color_n.node_color = snake_color;
color_n = color_n.next;
}
}
private void move() { //蛇移动
AddToHead(); //增加头部结点
checkEdge(); //检测边缘
// checkDead(); //检测是否死亡
deleteFromTail(); //删除尾巴结点
}
public void checkEgg() { //检测鸡蛋位置是否与蛇冲突
// int limit = 5;
// if(Egg.row - limit<= 0 || Egg.row+ limit >= Yard.ROWS || Egg.col - limit <= 0 && Egg.col+ limit >= Yard.COLS)
// {
// System.out.println(1);
// Egg.hush = true;
// return;
// }
if(Egg.egg_color == snake_color)
{
Egg.hush = true;
return;
}
for(Node n = head.next;n != null;n = n.next)
{
if(n.row == Egg.row && n.col == Egg.col)
{
Egg.hush = true;
return;
}
}
Egg.hush = false;
}
private void checkDead() { //检验蛇是否死亡
for(Node n = head.next;n != null;n = n.next) {
if(head.row == n.row && head.col == n.col) {
deleteFromHead();
AddToTail();
Yard.flag = 0; //停止线程
}
}
}
private void checkEdge() { //检验边缘
if(head.row > Yard.ROWS) head.row = 0;
else if(head.row < 0) head.row = Yard.ROWS;
else if(head.col > Yard.COLS-1) head.col = 0;
else if(head.col < 0) head.col = Yard.COLS-1;
}
public void eat(Egg e) { //蛇吃掉鸡蛋
if(this.getRect(head).intersects(e.getRect())) { //intersects判断矩形是否重合
snake_color = Egg.egg_color;
color_n = head;
e.reAppear(); //重置鸡蛋位置
checkEgg(); //检测鸡蛋位置是否位于蛇身
while(Egg.hush)
{
e.reAppear();
checkEgg();
}
AddToHead();
}
}
private Rectangle getRect(Node tmp) { //返回头结点的矩形对象(用于碰撞)
return new Rectangle(Yard.BLOCK_SIZE * tmp.col, Yard.BLOCK_SIZE * tmp.row, tmp.w, tmp.h);
}
private void deleteFromTail() {
if(size == 0) return;
tail = tail.pre;
tail.next = null;
}
private void deleteFromHead() {
if(size == 0) return;
head = head.next;
}
private class Node{
int w = Yard.BLOCK_SIZE; //结点宽度
int h = Yard.BLOCK_SIZE; //结点高度
int row, col; //结点所在位置
int fix_shape = 3; //画结点的修补参数,让结点有立体感
Color node_color = snake_color;
Dir dir = Dir.L; //默认方向
Node next = null;
Node pre = null;
Node(int row, int col, Dir dir) { //结点构造函数(可以用Alt+Shift引用source里面选Constructor生成构造函数)
this.row = row;
this.col = col;
this.dir = dir;
}
void draw(Graphics g, Node n) { //画出结点
g.setColor(node_color);
if(n == head)
{
g.drawOval(Yard.BLOCK_SIZE*col, Yard.BLOCK_SIZE*row, w-2, h-2);
}
else
{
g.fillRect(Yard.BLOCK_SIZE*col+fix_shape, Yard.BLOCK_SIZE*row+fix_shape, w-2*fix_shape, h-2*fix_shape); //矩形填充结点的x,y坐标和宽度高度
}
}
}
public void keyPressed(KeyEvent e) { //响应键盘
int key = e.getKeyCode();
switch(key) {
case KeyEvent.VK_LEFT:
if(head.dir != Dir.R) head.dir = Dir.L;
break;
case KeyEvent.VK_UP:
if(head.dir != Dir.D) head.dir = Dir.U;
break;
case KeyEvent.VK_DOWN:
if(head.dir != Dir.U) head.dir = Dir.D;
break;
case KeyEvent.VK_RIGHT:
if(head.dir != Dir.L) head.dir = Dir.R;
break;
}
}
}
Egg
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.util.Random;
public class Egg {
public static int row, col;
int w = Yard.BLOCK_SIZE; //结点宽度
int h = Yard.BLOCK_SIZE; //结点高度
int seed = 0; //随机数种子
public static Color egg_color;
public static boolean hush = false;
private static Random r = new Random(); //随机出生点
private static Random r1 = new Random();//随机数
public Egg(int row, int col, int seed) {
this.row = row;
this.col = col;
this.seed = seed;
}
public Egg() { //r.nextInt(i)表示(0-i)的随机数, r1随机6个数表示6种颜色
this(r.nextInt(Yard.ROWS-5) + 5, r.nextInt(Yard.COLS-5) + 5, r1.nextInt(6)); //产生1~边界-1的随机坐标,去除出生点在边界可能性
}
public void reAppear() { //Egg重新出现,采用随机坐标
int limit = 6; //limit用于限制鸡蛋出现的范围
// this.row = row + r.nextInt(limit) - limit;
// this.col = col + r.nextInt(limit) - limit;
Egg.row = r.nextInt(Yard.ROWS-1);
Egg.col = r.nextInt(Yard.COLS-1);
if(row < limit) row += limit;
if(row > Yard.ROWS-limit) row -= limit;
if(col < limit) col += limit;
if(col > Yard.COLS-limit) col -= limit;
this.seed = r1.nextInt(6);
switch(seed) //随机颜色
{
case 0: egg_color = Color.green; break;
case 1: egg_color = Color.BLUE;break;
case 2: egg_color = Color.MAGENTA;break;
case 3: egg_color = Color.ORANGE;break;
case 4: egg_color = Color.PINK;break;
case 5: egg_color = Color.RED;break;
default: break;
}
}
public Rectangle getRect() { //返回头结点的矩形对象(用于碰撞)
return new Rectangle(Yard.BLOCK_SIZE * this.col, Yard.BLOCK_SIZE * this.row, this.w, this.h);
}
void draw(Graphics g) { //画出结点
g.setColor(egg_color);
g.fillOval(Yard.BLOCK_SIZE*col, Yard.BLOCK_SIZE*row, w, h); //椭圆填充结点的x,y坐标和宽度高度
}
}