我的小游戏——贪吃蛇
简介
最近学习了Java的GUI编程写一个贪吃蛇的游戏来玩玩。
-
游戏主要涉及到Java的GUI编程和多线程等技术。
-
游戏一共有两种模式:窗墙模式和撞墙模式,默认是窗墙模式,选择不同的模式有不一样的游戏规则。
-
背景音乐的开与关的功能,默认进入游戏界面开启音乐,单曲循环。
-
可以调贪吃蛇的初始速度,默认是150(Timer定时器每150ms刷新一次),一共分为五节速度,分别对应5:50,4:80,3:110,2:140,1:170。
-
随着贪吃蛇的长度的增加,其速度会逐渐加快,长度为5的倍数时,速度就会减2,直到贪吃蛇阵亡。
-
贪吃蛇初始长度为3,每吃到一个食物长度加1,分数加10,大食物每当贪吃蛇的长度为10的倍数时,大食物后会出现一次,大食物停留的时间为5s,5s后大食物自动消失,每吃到大食物分数会加50。
-
游戏失败时,有提示音提示。
-
游戏按下空格进行开始,空格暂停,贪吃蛇阵亡后,按下空格重新开始游戏。
游戏界面
具体实现过程
1、准备游戏素材
-
头部 上
:
右:
下:
左: -
身体
-
食物 小:
大:
2、创建游戏窗口
创建一个宽为905,高为720,标题为"贪吃蛇"的JFrame窗口,并设置默认关闭窗口操作,设置不可调整窗口大小,设置窗口可见,这样一个简单的游戏窗口就创建好了。
JFrame frame = new JFrame("贪吃蛇");
frame.setBounds(500,100,905,720);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
frame.setVisible(true);
3、绘制游戏面板
头部菜单栏:用画笔画一个宽为900,高为55,颜色为蓝色的长方形。
g.setColor(new Color(30, 86, 198, 255));
g.fillRect(0, 0, 900, 55);
游戏区域:用画笔画一个宽为900,高为625,颜色为黑色的长方形。
g.setColor(Color.BLACK);
g.fillRect(0, 60, 900, 625);
4、实现菜单栏功能
设置面板为绝对布局和统一字体
this.setLayout(null);
Font font = new Font("微软雅黑", Font.BOLD, 15);
画一个字符串为”游戏模式“,并添加单选按钮到菜单栏上
g.setColor(Color.white);
g.setFont(new Font("微软雅黑", Font.CENTER_BASELINE, 16));
g.drawString("游戏模式:", 10, 30);
model1 = new JRadioButton("穿墙模式");
model1.setFont(font);
model1.setBounds(85, 10, 90, 30);
model1.setBackground(new Color(255, 255, 255));
model1.addActionListener(new MyActionListener());
model1.setSelected(true);
model1.setFocusable(false);
model2 = new JRadioButton("撞墙模式");
model2.setFont(font);
model2.setBounds(175, 10, 90, 30);
model2.setBackground(new Color(255, 255, 255));
model2.addActionListener(new MyActionListener());
model2.setFocusable(false);
ButtonGroup bg = new ButtonGroup();
bg.add(model1);
bg.add(model2);
this.add(model1);
this.add(model2);
画一个字符串为”背景音乐“,并添加单选按钮到菜单栏上
g.setColor(Color.white);
g.setFont(new Font("微软雅黑", Font.CENTER_BASELINE, 16));
g.drawString("背景音乐:", 280, 30);
on = new JRadioButton("开");
on.setFont(font);
on.setBounds(355, 10, 50, 30);
on.setBackground(new Color(255, 255, 255));
on.addActionListener(new MyActionListener());
on.setSelected(true);
on.setFocusable(false);
off = new JRadioButton("关");
off.setFont(font);
off.setBounds(400, 10, 40, 30);
off.setBackground(new Color(255, 255, 255));
off.addActionListener(new MyActionListener());
off.setFocusable(false);
ButtonGroup bg2 = new ButtonGroup();
bg2.add(on);
bg2.add(off);
this.add(on);
this.add(off);
画一个字符串为”速度“,并添加组合框到菜单栏上
g.setColor(Color.white);
g.setFont(new Font("微软雅黑", Font.CENTER_BASELINE, 16));
g.drawString("速度:", 455, 30);
JComboBox speed = new JComboBox();
speed.addItem(1);
speed.addItem(2);
speed.addItem(3);
speed.addItem(4);
speed.addItem(5);
speed.setFont(font);
speed.setBounds(500, 10, 40, 30);
this.add(speed);
画一个字符串为”当前速度”,添加到菜单栏上
g.setColor(Color.white);
g.setFont(new Font("微软雅黑", Font.CENTER_BASELINE, 16));
g.drawString("当前速度:"+Data.time, 550, 30);
画一个字符串为”长度和分数”,添加到菜单栏上
g.setColor(Color.white);
g.setFont(new Font("微软雅黑", Font.BOLD, 18));
g.drawString("长度:" + (Data.length - 1), 800, 20);
g.drawString("分数:" + Data.score, 800, 45);
5、绘制贪吃蛇的初始位置和食物
使用switch语句对方向进行判断选择,默认开始是向右,最后一个身体是从(0,60)开始画起,食物是随机生成
switch (Data.dire) {
case "U":
Data.up.paintIcon(this, g, Data.snakeX[0], Data.snakeY[0]);
break;
case "D":
Data.down.paintIcon(this, g, Data.snakeX[0], Data.snakeY[0]);
break;
case "L":
Data.left.paintIcon(this, g, Data.snakeX[0], Data.snakeY[0]);
break;
case "R":
Data.right.paintIcon(this, g, Data.snakeX[0], Data.snakeY[0]);
break;
}
for (int i = 1; i < Data.length - 1; i++) {
Data.body.paintIcon(this, g, Data.snakeX[i], Data.snakeY[i]);
}
Data.food.paintIcon(this, g, Data.foodX, Data.foodY);
6、绘制游戏提示文字
开始游戏提示文字
g.setColor(Color.white);
g.setFont(new Font("微软雅黑", Font.BOLD, 30));
g.drawString("按下空格开始游戏",300,300);
游戏失败提示
g.setColor(Color.RED);
g.setFont(new Font("微软雅黑", Font.BOLD, 30));
g.drawString("按下空格重新开始游戏", 280, 300);
这样基本的展示功能已经实现了,下面是如何使贪吃蛇动起来吃食物的过程。
7、注册键盘监听事件
当进入游戏按下空格时,游戏开始,isStart=true,再次按下空格实现暂停,isStart=false
int keyCode = e.getKeyCode();
if (keyCode==KeyEvent.VK_SPACE){
if (Data.isFail){
Data.time = 150;
GamePanel.timer.setDelay(150);
Data.isFail = false;
if (Data.isMusic){
new MusicThread().start();
}
GamePanel.init();
}else {
Data.isStart = !Data.isStart;
}
控制贪吃蛇的走向,U:向上,D:向下,L:向左,R:向右
if (keyCode==KeyEvent.VK_UP && Data.backU){
Data.dire = "U";
}else if(keyCode==KeyEvent.VK_DOWN && Data.backD){
Data.dire = "D";
}else if (keyCode==KeyEvent.VK_LEFT && Data.backL){
Data.dire = "L";
}else if (keyCode==KeyEvent.VK_RIGHT&& Data.backR){
Data.dire = "R";
}
然后在游戏面板中注册键盘监听事件,并设置焦点
this.setFocusable(true);
this.addKeyListener(new MyKeyListener());
这样的贪吃蛇还不能根据按键实现走动,还需要一个定时器的功能
8、定时器
要使贪吃蛇能够动起来,还需要在事件监听中添加一个定时器的功能,定时刷新游戏面板,实现动画的效果
Timer timer = new Timer(150,this);
timer.start();
9、让贪吃蛇动起来
首先判断游戏是否开始,游戏一旦开始,通过改变贪吃蛇的头部和身体的位置,利用定时器实现动画效果,根据按键去改变贪吃蛇的走动方向
//头部每向前走一步,后面的身体部分也跟着向前走一步
if (Data.isStart && !Data.isFail) {
for (int i = Data.length - 1; i > 0; i--) {
Data.snakeX[i] = Data.snakeX[i - 1];
Data.snakeY[i] = Data.snakeY[i - 1];
}
//通过方向来控制头部方向
switch (Data.dire) {
case "R":
Data.backL = false;
Data.snakeX[0] = Data.snakeX[0] + 25;
if (Data.isWall) {
if (Data.snakeX[0] > 880) {
Data.isFail = true;
PlayMusic.player.close();
new DeathMusicThread().start();
}
} else {
if (Data.snakeX[0] > 900) Data.snakeX[0] = 0;
}
break;
case "L":
Data.backR = false;
Data.snakeX[0] = Data.snakeX[0] - 25;
if (Data.isWall) {
if (Data.snakeX[0] < 0) {
Data.isFail = true;
PlayMusic.player.close();
new DeathMusicThread().start();
}
} else {
if (Data.snakeX[0] < 0) Data.snakeX[0] = 900;
}
break;
case "U":
Data.backD = false;
Data.snakeY[0] = Data.snakeY[0] - 25;
if (Data.isWall) {
if (Data.snakeY[0] < 60) {
Data.isFail = true;
PlayMusic.player.close();
new DeathMusicThread().start();
}
} else {
if (Data.snakeY[0] < 60) Data.snakeY[0] = 710;
}
break;
case "D":
Data.backU = false;
Data.snakeY[0] = Data.snakeY[0] + 25;
if (Data.isWall) {
if (Data.snakeY[0] > 665) {
timer.setDelay(150);
PlayMusic.player.close();
new DeathMusicThread().start();
}
} else {
if (Data.snakeY[0] > 710) Data.snakeY[0] = 60;
}
break;
}
这样,贪吃蛇就可以根据按键实现走动了。
10、吃到食物
当贪吃蛇的头部X,Y坐标与食物的X,Y坐标重合时,贪吃蛇就可以吃到食物了,吃到食物后,长度加1,食物消失,重新刷出新的食物坐标位置
if (Data.snakeX[0] == Data.foodX && Data.snakeY[0] == Data.foodY) {
Data.length++;
Data.score += 10;
Data.foodX = 25 * random.nextInt(35);
Data.foodY = 60 + 25 * random.nextInt(25);
}
吃到大食物
if (Data.length%10==0){
Data.food2X = 25 * random.nextInt(35);
Data.food2Y = 60 + 25 * random.nextInt(25);
Data.isFood = true;
}
11、游戏失败判断
当贪吃蛇的头部撞到自己的身体或撞到墙上时,游戏就失败。通过判断贪吃蛇的头部坐标是否与身体坐标重合来判断游戏是否失败
for (int i = 1; i < Data.length; i++) {
if (Data.snakeX[0] == Data.snakeX[i] && Data.snakeY[0] == Data.snakeY[i]) {
Data.isFail = true;
PlayMusic.player.close();
new DeathMusicThread().start();
break;
}
}
12、判断食物是否出现在蛇身上
检查食物的坐标是否落在蛇的身上,若是在身上就重新刷出新食物
for (int i = 0; i <= Data.length; i++) {
if (Data.snakeX[i] == Data.foodX && Data.snakeY[i] == Data.foodY) {
Data.foodX = 25 * random.nextInt(35);
Data.foodY = 60 + 25 * random.nextInt(25);
break;
}
}
最后,要记得在事件监听刷新界面repaint()并启动定时器timer.start().
这样就可以基本实现游戏的操作了,可以玩游戏了。下面是添加游戏模式和背景音乐过程。
13、添加游戏模式
通过判断模式选择,根据不同的游戏模式开启不同的功能,只要结合游戏面板的大小去改变贪吃蛇头部坐标的范围即可实现
//这里只是示例一个向右的操作,其它方向也类似,改变范围即可
if (Data.isWall) {
if (Data.snakeX[0] > 880) {
Data.isFail = true;
}
} else {
if (Data.snakeX[0] > 900) Data.snakeX[0] = 0;
}
14、添加背景音乐和阵亡提示音
这里需要引用一个第三方jar包(javazoom)来实现播放音乐,然后通过选择开关是否打开音乐
public static Player player = null;
public void BackgroundMusic() {
String[] music = {"贪吃蛇","River_Flows_in_You"};
try {
InputStream is = this.getClass().getResourceAsStream("/statics/" + music[0] + ".mp3");
player = new Player(is);
player.play();
while (player.isComplete()){
is = this.getClass().getResourceAsStream("/statics/" + music[0] + ".mp3");
player = new Player(is);
player.play();
}
} catch (JavaLayerException e) {
e.printStackTrace();
}
}
当游戏失败时,会有提示音
public void DeathMusic(){
InputStream inputStream = this.getClass().getResourceAsStream("/statics/死亡提示.mp3");
Player player = null;
try {
player = new Player(inputStream);
player.play();
} catch (JavaLayerException e) {
e.printStackTrace();
}
}
15、全部功能实现
这里需要使用Java的多线程去实现多个功能。该游戏一个有三个线程:展示游戏面板,播放背景音乐,播放阵亡音乐。
游戏面板
public void run() {
JFrame frame = new JFrame("贪吃蛇");
frame.setBounds(500,100,905,720);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
frame.add(new GamePanel());
frame.setVisible(true);
}
背景音乐
public void run() {
new PlayMusic().BackgroundMusic();
}
阵亡音乐
public void run() {
new PlayMusic().DeathMusic();
}
最后是主启动线程
public class GameStart{
public static void main(String[] args) {
new GameThread().start();
new MusicThread().start();
}
}
16、总结
以上就是该游戏的全部实现过程,最后就可以启动游戏完了。
可能过程有点复杂,细心一下去研究里面的代码,其实也挺不是很难的,基本就是结合了我们所学的Java知识加以运用实践。
通过写这个贪吃蛇游戏,可以加深我对Java的GUI编程和多线程等知识的认识和理解,提高自己的能力。
希望大家也可以跟我一样,通过学习到的知识充分利用到实践操作中去,不断提高自己的编程能力。