小白贪吃蛇大体流程:
通过学习java中的相关类,比如类JPanel、类Random、类Color、类Graphics中的一些相关方法,以及参考一些资料,学习视频等,实现贪吃蛇吃到食物这一小游戏。
Step1
构造游戏窗体类GameFrame
Step2
创建主函数类AppMain
Step3
创建蛇类GameSnake
Step4
创建食物类GameFood
Step5
创建面板类GamePanel
小白贪吃蛇第一天
上午:构造类GameFrame
public class GameFrame extends JFrame {}
类中包含:
1、设置标题
2、窗体大小
3、窗体居中
4、窗体关闭退出程序
5、实例化面板类
6、面板类添加至窗体
package Second_App;
import javax.swing.JFrame;
import java.awt.*;
public class GameFrame extends JFrame {
public GameFrame() {
Container c = getContentPane();
setTitle("小白贪吃蛇");
//设置标题;
setSize(520, 520);
//设置窗体大小;
setLocationRelativeTo(null);
//设置窗体居中
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//窗体关闭时退出程序
GamePanel gp = new GamePanel();
//实例化面板类
c.add(gp);
//将面板类添加至窗体
}
}
下午:构造主函数
public class AppMain {}
类中包含:
1、新建窗口类的实例对象
2、设置窗口可见
3、固定窗体大小
public class AppMain {
public static void main(String[] args)
{
GameFrame gf = new GameFrame();
// new出窗口类的实例对象
gf.setVisible(true);
// 使得窗口可见,默认不可见
gf.setResizable(false);
//固定窗体大小,用户不能自由改变
}
}
小白贪吃蛇第二天
上午:构造蛇类
public class GameSnake {}
类包含
1、初始化蛇基本属性:蛇身、蛇体每节大小、蛇长、蛇初始方向
public int[] snakeX;//定义保存蛇身体X和Y坐标的两个数组;
public int[] snakeY;
private final int size;//定义蛇身每节大小
public int len;//定义蛇长
private int snakeFX;//定义蛇头方向(程序通过蛇头方向来移动)
public GameSnake() {
snakeX=new int[300];//为两个数组初始化(300是任意设定,代表蛇最大长度)
snakeY=new int[300];
snakeX[0]=100;//为两个数组的前四个变量赋值(代表蛇初始身体所在位置,初始长为4)
snakeY[0]=100;
snakeX[1]=80;
snakeY[1]=100;
snakeX[2]=60;
snakeY[2]=100;
snakeX[3]=40;
snakeY[3]=100;
size=20;//为蛇身体节点大小赋值
snakeFX=39;//为蛇头方向赋值(39是对应键盘右键Code值,即蛇初始化方向为右)
len=4;//为蛇长赋值(代表初始长,可任意设置,若修改则需修改上面数组初始值)
}
2、三个方法分别是:返回蛇头的方向、设置蛇头的方向、返回蛇蛇头节点大小
public int getSnakeFX()//返回蛇头方向
{
return snakeFX;
}
public void setSnakeFX(int snakeFX)//设置蛇头方向
{
this.snakeFX = snakeFX;
}
public int getSize()//返回蛇蛇头节点大小
{
return size;
}
3、蛇死亡方法
public boolean isDie()// 蛇的死亡方法,返回布尔值
{
// for循环遍历,注意从1开始,不是0--->SnakeX[0]被设为"蛇头"了,具体看下
for (int i = 1; i < snakeX.length; i++)
{
if(snakeX[0]==snakeX[i]&&snakeY[0]==snakeY[i])// 判断蛇头是否碰到身体
{
// 弹出消息框
JOptionPane.showMessageDialog(null, "撞到自己了!!!");
return true;
}
if(snakeX[0]<0||snakeX[0]>520||snakeY[0]<0||snakeY[0]>520)// 判断蛇头是否出界
{
// 弹出消息框
JOptionPane.showMessageDialog(null, "碰到墙壁了!!!");
return true;
}
}
return false;
}
下午:构造食物类
public class GameFood {}
类包含:
1、食物基本属性
private final int size;//设置食物基本属性,食物的大小
private int foodX;//食物的两个坐标
private int foodY;
private final Random r;// 引入类Random,随机生成食物坐标
public GameFood()
{
// 将蛇类作为自己的成员变量,这样做的目的是需要引用蛇类的方法和变量
GameSnake snake = new GameSnake();//new出蛇类实例对象
r=new Random();//new出Random类实例对象
size=20;//为size赋值
foodX=r.nextInt(24)*20;//为两个食物坐标赋值
foodY=r.nextInt(24)*20;
Collision(snake.snakeX, snake.snakeY);//调用Collision方法确保生成的食物坐标与蛇身不会碰撞
}
2、Collision方法
public void Collision(int[] arr1,int[] arr2)
{
// 这个方法需要传入两个数组作为参数,分别是蛇身体的X和Y坐标
for (int i = 0; i < arr1.length; i++)
{
// 如果发生碰撞则执行if块内的语句,会再次生成食物的坐标,并递归调用
if(getFoodX()==arr1[i]&&getFoodY()==arr2[i])
{
foodX=r.nextInt(24)*20;
foodY=r.nextInt(24)*20;
Collision(arr1,arr2);
}
}
}
3、设置并返回食物两个坐标,以及返回食物大小
//设置食物两个坐标;
public void setFoodX(int foodX)
{
this.foodX = foodX;
}
public void setFoodY(int foodY)
{
this.foodY = foodY;
}
//返回食物两个坐标
public int getFoodX()
{
return foodX;
}
public int getFoodY()
{
return foodY;
}
//返回食物大小
public int getSize()
{
return size;
}
小白贪吃蛇第三天
上午:构造面板类step1
public class GamePanel extends JPanel {}
类包含:
1、GamePanel方法
2、重写面板类的画图方法
3、内部类实现多线程
GamePanel方法
public GamePanel(){}
包含:
new出两个类的实例对象
开启线程
设置焦点
匿名内部类添加键盘监听事件this.addKeyListener(new KeyAdapter(){
@Override
public void keyPressed(KeyEvent e){}
}
public GamePanel()
{
// new出两个类的实例对象
gf = new GameFood();
snake = new GameSnake();
// 开启线程
new Move().start();
// 设置焦点,这行代码是很必要的,因为默认的焦点在窗体,需要设置到这个面板中来
this.setFocusable(true);
// 通过匿名内部类添加键盘监听事件
this.addKeyListener(new KeyAdapter()
{
@Override
public void keyPressed(KeyEvent e)
{
// 根据玩家按压键盘所对应的Code值做出相应的处理
switch (e.getKeyCode())
{
// 37-40分别对应右、上、左、下,前面if语句的作用是不支持蛇头的180度转向
case 37:
if (snake.getSnakeFX() != 39)
snake.setSnakeFX(37);
break;
case 38:
if (snake.getSnakeFX() != 40)
snake.setSnakeFX(38);
break;
case 39:
if (snake.getSnakeFX() != 37)
snake.setSnakeFX(39);
break;
case 40:
if (snake.getSnakeFX() != 38)
snake.setSnakeFX(40);
break;
default:
break;
}
}
});
}
下午:构造面板类step2
重写面板类的画图方法
@Override
public void paint(Graphics g){}
包含:
背景色
画背景
食物色
画食物
蛇头色
画蛇头
蛇身色
for循环画蛇身
@Override
public void paint(Graphics g)
{
// 设置背景色为绿色
g.setColor(Color.green);
// 画背景
g.fillRect(0, 0, 500, 500);
// 设置食物为黑色
g.setColor(Color.black);
// 画食物
g.fillRect(gf.getFoodX(), gf.getFoodY(), gf.getSize(), gf.getSize());
// 设置蛇头为红色
g.setColor(Color.red);
// 画蛇头
g.fillRect(snake.snakeX[0], snake.snakeY[0], snake.getSize(), snake.getSize());
// 设置蛇身为蓝色,可自己选择
g.setColor(Color.blue);
// 通过for循环画蛇身
for (int i = 1; i < snake.len; i++)
{
g.fillRect(snake.snakeX[i], snake.snakeY[i], snake.getSize(), snake.getSize());
}
}
小白贪吃蛇第四天
上午:构造面板类step3
内部类实现多线程
class Move extends Thread{}
类包含:
线程休眠时间
移动蛇身
吃到食物
判断是否死亡
重绘方法
class Move extends Thread
{
@Override
public void run()
{
while (true)
{
try
{
// 设置线程休眠时间,400是任意的,越小就会执行的越快,相当于增加难度
Thread.sleep(400);
} catch (InterruptedException e)
{
e.printStackTrace();
}
// 移动蛇身,一定要放在移动蛇头的前面,不然会导致蛇头坐标的丢失,读者可以自行尝试
for (int i = snake.len; i > 0; i--)
{
// for循环的遍历需要从大到小,否则导致坐标值的覆盖变为一点
snake.snakeX[i] = snake.snakeX[i - 1];
snake.snakeY[i] = snake.snakeY[i - 1];
}
// 根据方向移动蛇头
switch (snake.getSnakeFX())
{
case 37:
snake.snakeX[0] -= 20;
break;
case 38:
snake.snakeY[0] -= 20;
break;
case 39:
snake.snakeX[0] += 20;
break;
case 40:
snake.snakeY[0] += 20;
break;
default:
break;
}
// 吃到食物
if (snake.snakeX[0] == gf.getFoodX() && snake.snakeY[0] == gf.getFoodY())
{
// 长度增加
snake.len++;
// 重新生成食物
Random r = new Random();
gf.setFoodX(r.nextInt(24) * 20);
gf.setFoodY(r.nextInt(24) * 20);
// 确保新生成的食物不会碰撞到蛇
gf.Collision(snake.snakeX, snake.snakeY);
}
// 判断是否死亡,如果死亡则结束while循环,相当于结束了这个线程
if (snake.isDie())
break;
// 重绘方法,也是很必要的方法,如果没有的话看不到动的效果
repaint();
}
}
}
下午:运行检测
总代码:
GameFrame.java
package Second_App;
import javax.swing.JFrame;
import java.awt.*;
public class GameFrame extends JFrame {
public GameFrame() {
Container c = getContentPane();
setTitle("小白贪吃蛇");//设置标题;
setSize(520, 520);//设置窗体大小;
setLocationRelativeTo(null);//设置窗体居中
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//窗体关闭时退出程序
GamePanel gp = new GamePanel();//实例化面板类
c.add(gp);//将面板类添加至窗体
}
}
AppMain.java
package Second_App;
public class AppMain {
public static void main(String[] args)
{
GameFrame gf = new GameFrame();// new出窗口类的实例对象
gf.setVisible(true);// 使得窗口可见,默认是不可见的
gf.setResizable(false);//固定窗体大小,用户不能自由改变
}
}
GameSnake.java
package Second_App;
import javax.swing.JOptionPane;
public class GameSnake {
public int[] snakeX;//定义保存蛇身体X和Y坐标的两个数组;
public int[] snakeY;
private final int size;//定义蛇身每节大小
public int len;//定义蛇长
private int snakeFX;//定义蛇头方向(程序通过蛇头方向来移动)
public GameSnake()
{
snakeX=new int[300];//为两个数组初始化(300是任意设定,代表蛇最大长度)
snakeY=new int[300];
snakeX[0]=100;//为两个数组的前四个变量赋值(代表蛇初始身体所在位置,初始长为4)
snakeY[0]=100;
snakeX[1]=80;
snakeY[1]=100;
snakeX[2]=60;
snakeY[2]=100;
snakeX[3]=40;
snakeY[3]=100;
size=20;//为蛇身体节点大小赋值
snakeFX=39;//为蛇头方向赋值(39是对应键盘右键Code值,即蛇初始化方向为右)
len=4;//为蛇长赋值(代表初始长,可任意设置,若修改则需修改上面数组初始值)
}
public int getSnakeFX()//返回蛇头方向
{
return snakeFX;
}
public void setSnakeFX(int snakeFX)//设置蛇头方向
{
this.snakeFX = snakeFX;
}
public int getSize()//返回蛇蛇头节点大小
{
return size;
}
public boolean isDie()// 蛇的死亡方法,返回布尔值
{
// for循环遍历,注意从1开始,不是0--->SnakeX[0]被设为"蛇头"了,具体看下
for (int i = 1; i < snakeX.length; i++)
{
if(snakeX[0]==snakeX[i]&&snakeY[0]==snakeY[i])// 判断蛇头是否碰到身体
{
// 弹出消息框
JOptionPane.showMessageDialog(null, "撞到自己了!!!");
return true;
}
if(snakeX[0]<0||snakeX[0]>520||snakeY[0]<0||snakeY[0]>520)// 判断蛇头是否出界
{
// 弹出消息框
JOptionPane.showMessageDialog(null, "碰到墙壁了!!!");
return true;
}
}
return false;
}
}
GameFood.java
package Second_App;
import java.util.Random;
public class GameFood {
private final int size;//设置食物基本属性,食物的大小
private int foodX;//食物的两个坐标
private int foodY;
private final Random r;// 引入类Random,随机生成食物坐标
public GameFood()
{
// 将蛇类作为自己的成员变量,这样做的目的是需要引用蛇类的方法和变量
GameSnake snake = new GameSnake();//new出蛇类实例对象
r=new Random();//new出Random类实例对象
size=20;//为size赋值
foodX=r.nextInt(24)*20;//为两个食物坐标赋值
foodY=r.nextInt(24)*20;
Collision(snake.snakeX, snake.snakeY);//调用Collision方法确保生成的食物坐标与蛇身不会碰撞
}
// 如果食物生成时碰撞到蛇,则递归调用重新生成,确保不会在食物的生成是与蛇发生碰撞
public void Collision(int[] arr1,int[] arr2)
{
// 这个方法需要传入两个数组作为参数,分别是蛇身体的X和Y坐标
for (int i = 0; i < arr1.length; i++)
{
// 如果发生碰撞则执行if块内的语句,会再次生成食物的坐标,并递归调用
if(getFoodX()==arr1[i]&&getFoodY()==arr2[i])
{
foodX=r.nextInt(24)*20;
foodY=r.nextInt(24)*20;
Collision(arr1,arr2);
}
}
}
//设置食物两个坐标;
public void setFoodX(int foodX)
{
this.foodX = foodX;
}
public void setFoodY(int foodY)
{
this.foodY = foodY;
}
//返回食物两个坐标
public int getFoodX()
{
return foodX;
}
public int getFoodY()
{
return foodY;
}
//返回食物大小
public int getSize()
{
return size;
}
}
GamePanel.java
package Second_App;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.Random;
import javax.swing.JPanel;
public class GamePanel extends JPanel {
// 为了使用食物类和蛇类的变量和方法,将它们定义为成员变量
private final GameFood gf;
private final GameSnake snake;
public GamePanel()
{
// new出两个类的实例对象
gf = new GameFood();
snake = new GameSnake();
// 开启线程
new Move().start();
// 设置焦点,这行代码是很必要的,因为默认的焦点在窗体,需要设置到这个面板中来
this.setFocusable(true);
// 通过匿名内部类添加键盘监听事件
this.addKeyListener(new KeyAdapter()
{
@Override
public void keyPressed(KeyEvent e)
{
// 根据玩家按压键盘所对应的Code值做出相应的处理
switch (e.getKeyCode())
{
// 37-40分别对应右、上、左、下,前面if语句的作用是不支持蛇头的180度转向
case 37:
if (snake.getSnakeFX() != 39)
snake.setSnakeFX(37);
break;
case 38:
if (snake.getSnakeFX() != 40)
snake.setSnakeFX(38);
break;
case 39:
if (snake.getSnakeFX() != 37)
snake.setSnakeFX(39);
break;
case 40:
if (snake.getSnakeFX() != 38)
snake.setSnakeFX(40);
break;
default:
break;
}
}
});
}
// 重写面板类的画图方法
@Override
public void paint(Graphics g)
{
// 设置背景色为绿色
g.setColor(Color.green);
// 画背景
g.fillRect(0, 0, 500, 500);
// 设置食物为黑色
g.setColor(Color.black);
// 画食物
g.fillRect(gf.getFoodX(), gf.getFoodY(), gf.getSize(), gf.getSize());
// 设置蛇头为红色
g.setColor(Color.red);
// 画蛇头
g.fillRect(snake.snakeX[0], snake.snakeY[0], snake.getSize(), snake.getSize());
// 设置蛇身为蓝色,可自己选择
g.setColor(Color.blue);
// 通过for循环画蛇身
for (int i = 1; i < snake.len; i++)
{
g.fillRect(snake.snakeX[i], snake.snakeY[i], snake.getSize(), snake.getSize());
}
}
// 定义内部类实现多线程
class Move extends Thread
{
@Override
public void run()
{
while (true)
{
try
{
// 设置线程休眠时间,400是任意的,越小就会执行的越快,相当于增加难度
Thread.sleep(400);
} catch (InterruptedException e)
{
e.printStackTrace();
}
// 移动蛇身,一定要放在移动蛇头的前面,不然会导致蛇头坐标的丢失,读者可以自行尝试
for (int i = snake.len; i > 0; i--)
{
// for循环的遍历需要从大到小,否则导致坐标值的覆盖变为一点
snake.snakeX[i] = snake.snakeX[i - 1];
snake.snakeY[i] = snake.snakeY[i - 1];
}
// 根据方向移动蛇头
switch (snake.getSnakeFX())
{
case 37:
snake.snakeX[0] -= 20;
break;
case 38:
snake.snakeY[0] -= 20;
break;
case 39:
snake.snakeX[0] += 20;
break;
case 40:
snake.snakeY[0] += 20;
break;
default:
break;
}
// 吃到食物
if (snake.snakeX[0] == gf.getFoodX() && snake.snakeY[0] == gf.getFoodY())
{
// 长度增加
snake.len++;
// 重新生成食物
Random r = new Random();
gf.setFoodX(r.nextInt(24) * 20);
gf.setFoodY(r.nextInt(24) * 20);
// 确保新生成的食物不会碰撞到蛇
gf.Collision(snake.snakeX, snake.snakeY);
}
// 判断是否死亡,如果死亡则结束while循环,相当于结束了这个线程
if (snake.isDie())
break;
// 重绘方法,也是很必要的方法,如果没有的话看不到动的效果
repaint();
}
}
}
}
小蛇运行起来啦。嘻嘻
后续
代码修改及功能添加
Update1:边界修改->下、右边界
运行此代码,结果如下:
此部分代码下、右边界处理不合适,导致蛇头及蛇身进去才判断撞到墙壁,作以下修改:
运行效果:
Update2:吃一食物,蛇身减一
运行效果:
Add1:计分->吃一食物,分数加一
添部分代码:
运行代码,效果
Add2:穿墙
运行效果:
总结:
虽然基本功能已经实现,但如背景音乐等其他高级功能,就得去查阅相应资料及学习相关视频教程。临近寒假,备考期末。相关高级功能,学习后会相应补充。
小白愿与大家共同进步,学习交流。欢迎大家留言!