1、摘要
特别感谢上面链接的作者,让我快速理清思路。
我将该作者的代码做了不少改动,转化为我自己的思考。
我的工作如下:
1)设置游戏的等级,让贪吃蛇的运行速度随着等级的变化而改变。
2) 设置了路障,和装饰的草地,以及与贪吃蛇触碰的条件。
3) 设置了三种不同的食物,并考虑了食物和路障的摆放。
4)修复了该作者的bug,包括页面的框架,贪吃蛇移动/死亡的条件等。
5)做了不少UI的调整,包括JPanel背景色、食物、蛇的身体、提示字的位置等。
2、 代码
这份代码只是起到抛转引玉的作用,方便我们了解一个简单的小游戏是如何开发的(感兴趣的朋友可以在web上尝试贪吃蛇游戏)
首先需要根据你们的eclipse版本下载相应版本的WindowBuilder,然后新建一个JPanel部署程序;
值得注意的是,我在工程中新建(或者直接复制粘贴)了一个/pic文件夹,用来存放icon,请大家在学习的时候务必注意图片路径:
package Jpanel;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Random;
import javax.swing.Timer;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;
/*
* Part1:
* 设计游戏图纸:画一张900*700的白色窗口
* 在窗口上添加画布,并在画布上添加标题和游戏区域
* 第一个问题:蛇的图片如何和画布适应,如何让图片移动
* Part2:
初始化放置静态的蛇:一个头、一个身体、一个尾巴
加上开始提示:按空格键开始游戏
让蛇动起来:监听Timer事件,平移数据(蛇的身体)
实现游戏暂停
实现转向功能(需要蛇头部图片的四个方向)
Part3:
添加食物
吃掉食物
添加死亡条件
实现“重新开始”功能
添加分数、长度和等级(有兴趣的朋友可以增加游戏时间的选项)
*/
public class SnakePanel extends JPanel implements KeyListener, ActionListener {
//初始化图片
ImageIcon up = new ImageIcon("pic/_up.png");
ImageIcon down = new ImageIcon("pic/_down.png");
ImageIcon left = new ImageIcon("pic/_left.png");
ImageIcon right = new ImageIcon("pic/_right.png");
ImageIcon food = new ImageIcon("pic/strawberry.png");
ImageIcon food1 = new ImageIcon("pic/apple.png");
ImageIcon food2 = new ImageIcon("pic/orange.png");
ImageIcon body = new ImageIcon("pic/_body.png");
ImageIcon stone = new ImageIcon("pic/stone.png");
ImageIcon grass = new ImageIcon("pic/grass.png");
ImageIcon tail = new ImageIcon("pic/_tail.png");
ImageIcon title = new ImageIcon("pic/title.jpg");
int[] snakex = new int[751];
int[] snakey = new int[651];
int[] stonex = new int[31];
int[] stoney = new int[31];
int[] grassx = new int[11];
int[] grassy = new int[11];
Random rand = new Random();
int foodx = rand.nextInt(34) * 25 + 25;
int foody = rand.nextInt(24) * 25 + 75;
int len = 3;// 一开始只有三节,一个头,一个身体、一个尾巴
int score = 0;//初始化分数
int level = 1;
static int time = 500;//初始化延迟的时间
int n = 0;// 控制石头
String direction = "R";//初始化方向
boolean isStarted = false;// 判断游戏是否开始
boolean isFailed = false;// 判断游戏是否结束
Timer timer = new Timer(time, this);// 控制游戏的快慢
public SnakePanel() {
this.setFocusable(true);// 获取焦点
/* 所谓焦点就是被选中的意思,或者说是“当前正在操作的组件”的意思。
* 如果一个组件被选中,或者正在被操作者,就是得到了焦点,而相反的,一个组件没有被选中或者失去操作,就是被转移了焦点,焦点已经到 别的组件上去了。
* 最明显的两个例子: 一个按钮(button)一旦被选中,就会有一个虚线框在按钮中,并且环绕着按钮的文字,一旦失去焦点,不被操作了,这个虚线框就消失了。
* 一个文本框(textfield)一旦被选中,就会有一个“|”在文本框里面闪动,提示可以输入信息,一旦失去或者转移焦点了,这个“|”就没有了,不闪动,
* 表示这个文本框你没有在操作。*/
this.addKeyListener(this);// 监听键盘事件
//下面都是初始化操作
setup();
StoneInitial();
GrassInitial();
}
public void GrassInitial() {
Random rand = new Random();
for(int i=0;i<11;i++) {
grassx[i] = rand.nextInt(34) * 25 + 25;
grassy[i] = rand.nextInt(24) * 25 + 75;
}
}
public void GrassPaint(Graphics g) {
for (int i = 0; i < 11; i++) {
grass.paintIcon(this, g, grassx[i], grassy[i]);
}
}
public void StoneInitial() {
Random rand = new Random();
for (int i = 0; i < 31; i++) {
stonex[i] = rand.nextInt(34) * 25 + 25;
stoney[i] = rand.nextInt(24) * 25 + 75;
while ((stonex[i] == 100 && stoney[i] == 100) || (stonex[i] == 75 && stoney[i] == 100)
|| (stonex[i] == 50 && stoney[i] == 100)) {//防止与初始化的蛇的身体碰撞,并以此为基准放置石头
stonex[i] = rand.nextInt(34) * 25 + 25;
stoney[i] = rand.nextInt(24) * 25 + 75;
i = 0;
}
}
}
public void StonePaint(Graphics g) {
for (int i = 0; i < 31; i++) {
stone.paintIcon(this, g, stonex[i], stoney[i]);
}
}
public void Level() {//设置游戏的等级
if (score > 5 && score <= 10 && level == 1) {
timer.stop();//timer其实也调用了线程,我们首先要暂定之前的线程再开启新的
time -= 40;
level++;
timer = new Timer(time, this);
} else if (score > 10 && score <= 20 && level == 2) {
timer.stop();
time -= 40;
level++;
timer = new Timer(time, this);
} else if (score > 20 && score <= 30 && level == 3) {
timer.stop();
time -= 50;
level++;
timer = new Timer(time, this);
} else if (score > 30 && score <= 40 && level == 4) {
timer.stop();
time -= 50;
level++;
timer = new Timer(time, this);
}
else if (score > 40 && score <= 50 && level == 5) {
timer.stop();
time -= 60;
level++;
timer = new Timer(time, this);
}
else if (score > 50 && score <= 60 && level == 6) {
timer.stop();
time -= 60;
level++;
timer = new Timer(time, this);
}
}
public void food() {// 要改进
Random rand = new Random();
for (int i = 0; i < len; i++) {
if ((snakex[i] == foodx && snakey[i] == foody)) {
foodx = rand.nextInt(34) * 25 + 25;// 返回一个大于等于0小于34的随机整数
foody = rand.nextInt(24) * 25 + 75;
i = 0;
continue;//从头开始
}
for (int j = 0; j < 31; j++) {
if ((stonex[j] == foodx && stoney[j] == foody)||Dilemma()) {
foodx = rand.nextInt(34) * 25 + 25;
foody = rand.nextInt(24) * 25 + 75;
j = 0;
i = 0;
break;
}
}
}
}
public boolean Dilemma() {//食物的放置可能出现困境
for(int i=0;i<29;i++) {//以下是全排列
for(int j=i+1;j<30;j++) {
for(int k=j+1;k<31;k++) {
if ((stonex[i]+25 == foodx && stonex[j]-25 == foodx && stoney[k]-25==foody)||
(stonex[i]+25 == foodx && stonex[j]-25 == foodx && stoney[k]+25==foody)) {
return true;
}
}
}
}
return false;
}
public void setup() {// 游戏初始化
isStarted = false;
isFailed = false;
len = 3;
score = 0;
level=1;
n=0;
time=500;
direction = "R";
snakex[0] = 100;
snakex[1] = 75;
snakex[2] = 50;
snakey[0] = 100;
snakey[1] = 100;
snakey[2] = 100;
GrassInitial();//草和石头放置的初始化
StoneInitial();
timer.start();
}
@Override
public void keyPressed(KeyEvent e) {//控制键盘的上下左右
int KeyCode = e.getKeyCode();
if (isStarted) {
if (KeyCode == KeyEvent.VK_SPACE) {
if (isFailed) {
setup();
} else {
isStarted = !isStarted;// 暂停和开始
}
} else if (KeyCode == KeyEvent.VK_UP && direction != "D") {
direction = "U";
} else if (KeyCode == KeyEvent.VK_DOWN && direction != "U") {
direction = "D";
} else if (KeyCode == KeyEvent.VK_RIGHT && direction != "L") {
direction = "R";
} else if (KeyCode == KeyEvent.VK_LEFT && direction != "R") {
direction = "L";
}
} else {
if (KeyCode == KeyEvent.VK_SPACE) {
if (isFailed) {
setup();
} else {
isStarted = !isStarted;// 暂停和开始
}
}
}
}
public void paint(Graphics g) {// Graphics 画笔
g.setColor(Color.orange); // 设置画布背景颜色
title.paintIcon(this, g, 25, 11);// 放置主题图片,从(25,11)处为title的左上角,放置图片
g.fillRect(25, 75, 850, 600); // 用画笔设置游戏方框
//画草
GrassPaint(g);
// 画石头
StonePaint(g);
// 画蛇头(注意判断蛇头的方向)
if (direction.equals("R"))
right.paintIcon(this, g, snakex[0], snakey[0]);
else if (direction.equals("L"))
left.paintIcon(this, g, snakex[0], snakey[0]);
else if (direction.equals("U"))
up.paintIcon(this, g, snakex[0], snakey[0]);
else if (direction.equals("D"))
down.paintIcon(this, g, snakex[0], snakey[0]);
// 画蛇的身体
for (int i = 1; i < len-1; i++)
body.paintIcon(this, g, snakex[i], snakey[i]);
//画蛇尾
tail.paintIcon(this, g, snakex[len-1],snakey[len-1]);
// 判断如果游戏没开始,需要显示的字段
if (!isStarted) {
g.setColor(Color.WHITE);
g.setFont(new Font("arial", Font.BOLD, 28));
g.drawString("Press Space to start / pause", 250, 320);
}
// 判断如果游戏失败,需要显示的字段
if (isFailed) {
timer.stop();
g.setColor(Color.WHITE);
g.setFont(new Font("arial", Font.BOLD, 28));
g.drawString("Game Over ! Press space to restart", 250, 320);
}
// 显示食物
food();
if(foodx % 3 == 0)
food.paintIcon(this, g, foodx, foody);
else if((foodx+1) % 3==0)
food1.paintIcon(this, g, foodx, foody);
else
food2.paintIcon(this, g, foodx, foody);
// 设置分数和蛇的长度,以及等级,我最高设置了等级七
g.setColor(Color.WHITE);
g.setFont(new Font("arial", Font.PLAIN, 15));
g.drawString("Score : " + score, 650, 37);
g.drawString("SnakeLength :" + len, 650, 57);
g.drawString("Level :" + level, 725, 37);
}
@Override
public void actionPerformed(ActionEvent e) {
// 1.再定义一个闹钟
Level();
timer.start();
// 2.移动
if (isStarted && !isFailed) {
// 移动身体
for (int i = len; i > 0; i--) {
snakex[i] = snakex[i - 1];
snakey[i] = snakey[i - 1];
}
// 移动头
if (direction.equals("R")) {
snakex[0] = snakex[0] + 25;
if (snakex[0] > 850)
snakex[0] = 25;
} else if (direction.equals("L")) {
snakex[0] = snakex[0] - 25;
if (snakex[0] < 25)
snakex[0] = 850;
} else if (direction.equals("U")) {
snakey[0] = snakey[0] - 25;
if (snakey[0] < 75)
snakey[0] = 650;
} else if (direction.equals("D")) {
snakey[0] = snakey[0] + 25;
if (snakey[0] > 650) // 底下应该是650
snakey[0] = 75;
}
if (snakex[0] == foodx && snakey[0] == foody) {
len++;
score++;
foodx = rand.nextInt(34) * 25 + 25;
foody = rand.nextInt(24) * 25 + 75;
}
for (int i = 1; i < len; i++) {
if (snakex[0] == snakex[i] && snakey[0] == snakey[i]) {
isFailed = true;
}
}
for (int i = 0; i < len; i++) {
for (int j = 1; j < 31; j++) {
if (stonex[j] == snakex[i] && stoney[j] == snakey[i]) {
isFailed = true;
}
}
}
}
// 3. repaint()
repaint();
}
@Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
@Override
public void keyReleased(KeyEvent e) {
// TODO Auto-generated method stub
}
public static void main(String[] args) {
JFrame frame = new JFrame(); // 创建一个游戏界面的框架
frame.setBounds(0, 0, 900, 720); // 设置框架的大小
java.awt.Toolkit toolkit = java.awt.Toolkit.getDefaultToolkit();//下面的代码是让框架在屏幕正中间
Dimension screenSize = toolkit.getScreenSize();
double screenWidth = screenSize.getWidth();
double screenHeight = screenSize.getHeight();
frame.setLocation((int) (screenWidth - frame.getWidth()) / 2, (int) (screenHeight - frame.getHeight()) / 2);
frame.setResizable(false); // 设置框架大小为不能改变
// 点击关闭按钮 关闭游戏界面
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
SnakePanel panel = new SnakePanel(); // 添加画布
frame.add(panel); // 刚添加时画布是空的看不到
frame.setVisible(true); // 允许显示游戏界面
}
}
特别注意:调用repaint()这个回调函数的时候,paint(Graphics g)就被调用了。方法在线程里控制,一般都要不断刷屏来显示画面,我们不用理会。
3、运行图片:
4、icon素材:
链接:[百度网盘图片下载地址] https://pan.baidu.com/s/1UlfcIzKBysqUv4cG89VHfg 提取码:bl42
5、寄语:
希望大家共同进步,如有不足,希望可以指正!转载请注明出处,谢谢!!!