Java线程创意游戏之——海绵宝宝版超级玛丽
【整体创意简介】
海绵宝宝出发,键盘控制其移动(上下左右),途中遭遇阻碍(水母、海胆),碰到就掉血,途中获得奖励(汉堡包),吃掉可增加血量,到达终点后,与派大星会面,解救出派大星。
【分块思路】
1.入口窗体/面板
(游戏进行、游戏帮助、退出游戏、播放开始背景音乐)
2.帮助窗体/面板
(游戏简介、游戏作者、返回入口)
3.游戏进行主窗体/面板
1)人物:海绵宝宝、派大星、水母、海胆、汉堡包
2)背景:海滩、土壤(各两块以便于背景移动)
3)音效:关闭游戏开始音,播放游戏进行音
4)移动:水母、海胆、背景的移动,键盘监听控制海绵宝宝
4.死亡窗体/面板
(图片背景,文字介绍,死亡音乐,返回重新开始游戏通道)
5.胜利窗体/面板
(图片背景,文字介绍,胜利音乐,下一关通道)
【游戏截图介绍】
1.欢迎界面
游戏的三个按钮采用画笔g画上去
2.帮助按钮界面
点击help按钮进入这个界面,点击返回返回入口界面
点击exit按钮退出游戏。
3.进入游戏界面
1)初进入
左上方:血量条以及血量具体数值显示,随着游戏的进行,血量条和数值会相应的增减。
右上方:海绵宝宝所走路程的显示
中央:海胆(碰到掉血),汉堡包(吃掉可增加血量),水母(不断由界面右边飘向左边,碰到掉血)
左下方:主人公海绵宝宝,它有左右两个方向,这里是向右走,当按键盘上的向左箭头时,可变为向左。
2)游戏进行
键盘上下左右箭头控制前进与跳起,松开向上箭头时,自动下落。
当走到界面一半路程时,背景及海滩,海盗,汉堡都开始向左移动,营造出前进的动态感。
3)血量掉光时死亡
弹出死亡界面,游戏音乐关闭,死亡音乐开启。
4)点击返回游戏,重新开始
5)走到一定路程后,被困的派大星出现,解救出派大星后,弹出游戏胜利界面。
游戏胜利音乐开启,游戏进行音乐关闭。
6)点击下一关,开始第二关。界面类似于第一关,但是阻碍更多,设计的阻碍路线使通过更困难。
【游戏设计详细介绍】
1.所需的类、图片、音乐
2.最主要的类-线程类代码
public class playThread extends JPanel implements Runnable, KeyListener {
// 背景图片、人物图片
private Image background0;
private Image background1;
private Image sand0, sand1;
private Image babyright, babyleft;
private Image paidaxing;
// 记录两张背景图片时时更新的X坐标
private int backX0 = 0;
private int backX1 = 0;
// 背景移动速度
private int backv = 5;
// 海绵宝宝走过的路程
private int load = 0;
// 记录游戏状态
private boolean state = false;
// 记录左右走的状态
private boolean left = false;
private boolean right = false;
// 游戏主线程
private Thread mThread = null;
// 向上跳初始化
private int upjump = 0;
// baby最低位置
private int babyY0 = 475;
private Graphics g;
// 将结束和胜利定义为公用
public over ov;
public win wn;
// 设置血条长度
private int width = 300;
private int blood = 100;
// 游戏进行的音乐
public AudioClip playsound;
// 实例化对象
JFrame jf;
Baby baby = new Baby();
haidan[] hai = new haidan[3];
hamburger[] ham = new hamburger[3];
shuimu[] shui = new shuimu[6];
public playThread(JFrame jf) {
playsound = Applet.newAudioClip(this.getClass().getResource("playGame.au"));
playsound.play();
jf.addKeyListener(this);
initUI();
/** 启动游戏线程 **/
state = true;
mThread = new Thread(this);// 实例化线程
mThread.start();
this.setVisible(true);
}
public void initUI() {
/************** 绘制游戏背景 **************/
// 初始化背景图片
background0 = new ImageIcon(this.getClass().getResource("界面2.png")).getImage();
background1 = new ImageIcon(this.getClass().getResource("界面3.png")).getImage();
// 第一张图片贴在屏幕0,0点,第二张图片在第一张图片右边
backX0 = 0;
backX1 = 1194;
// 加载地面背景图
sand0 = new ImageIcon(this.getClass().getResource("sand0.png")).getImage();
sand1 = new ImageIcon(this.getClass().getResource("sand1.png")).getImage();
// 加载baby向左、右的动图
babyleft = new ImageIcon(this.getClass().getResource("spongeL.gif")).getImage();
babyright = new ImageIcon(this.getClass().getResource("spongeR.gif")).getImage();
//
paidaxing = new ImageIcon(this.getClass().getResource("派大星.png")).getImage();
/************** 初始化汉堡和海胆的数值对象 **************/
for (int i = 0; i < hai.length; i++) {
hai[i] = new haidan();
}
for (int i = 0; i < hai.length; i++) {
ham[i] = new hamburger();
}
for (int i = 0; i < shui.length; i++) {
shui[i] = new shuimu();
shui[i].y = 200;
}
hai[0].x = 200;
hai[1].x = 700;
hai[2].x = 1200;
ham[0].x = 400;
ham[1].x = 900;
ham[2].x = 1400;
shui[0].x = 100;
shui[1].x = 700;
shui[2].x = 1300;
shui[3].x = 1900;
shui[4].x = 2500;
shui[5].x = 3100;
}
public void paint(Graphics g) {
super.paint(g);
/*************** 绘制游戏背景 *******************/
g.drawImage(background0, backX0, 0, jf);
g.drawImage(background1, backX1, 0, jf);
/*************** 绘制海绵宝宝、海胆、汉堡 ********************/
baby.draw(g, jf);
for (int i = 0; i < hai.length; i++) {
hai[i].draw(g, jf);
ham[i].draw(g, jf);
}
for (int i = 0; i < shui.length; i++) {
shui[i].draw(g, jf);
shui[i].x -= 7;
}
/*************** 绘制地面背景 *******************/
g.drawImage(sand0, backX0, 545, jf);
g.drawImage(sand1, backX1, 545, jf);
/*************** 绘制路程显示框 *******************/
g.setColor(Color.blue);
g.setFont(new Font("", Font.BOLD, 20));// 设置字体样式以及大小
g.drawString("路程:" + load, 1000, 80);
/*************** 绘制血量条 *******************/
g.setColor(Color.RED);
g.drawString("血量:", 60, 70);
g.drawString("" + blood, 430, 70);
g.fillRect(120, 50, width, 20);
g.drawRect(120, 50, 300, 20);
/*************** 绘制派大星 *******************/
if (load > 2000) {// 当走过一段路程后,派大星才出现
g.drawImage(paidaxing, 1000, 440, 150, 150, jf);
}
/***************** 控制上下跳 *************/
jump();
/*********** 调用碰撞方法 ******************/
collisionBall();
}
public void jump() {// 跳跃方法
/***************** 控制上下跳 *************/
if (upjump > 0) {// 上跳
baby.y -= 8;
upjump--;
}
if (babyY0 != baby.y && upjump == 0) // 下降
baby.y += 8;
/***************** 控制左右跳 *************/
if (left) {// 打开左走开关
baby.x -= baby.v;
if (baby.x <= 80) {
baby.x = 80;
backX0 += backv;
backX1 += backv;
for (int i = 0; i < hai.length; i++) {
hai[i].x += backv;
ham[i].x += backv;
}
if (backX0 > 0) // 背景循环
backX1 = -1 * backX0;
//
// if (backX1 > 0)
// backX0 = - backX1;
}
}
if (right) {// 打开右走开关
baby.x += baby.v;
load += baby.v;
if (baby.x >= 600 && load < 2000) {
baby.x = 600;
backX0 -= backv;
backX1 -= backv;
for (int i = 0; i < hai.length; i++) {
hai[i].x -= backv;
ham[i].x -= backv;
}
/**
* 背景问题修改:注释部分为原代码,这样做不能保证backX1正好是0,因为是每次减去速度,减完之后不一定恰好是0.
* 而修改后解决了这一问题
*/
// if(backX1==0)backX0=1194;
// if(backX0==0)backX1=1194;
if (backX1 < 0) // 背景循环
backX0 = 1194 + backX1;
if (backX0 <= 0)
backX1 = 1194 + backX0;
}
// 当baby走过某段路程后,baby开始重新动
if (load >= 2000) {
baby.x += 3;
// baby停在界面上的末位置
if (baby.x >= 920 && baby.y >= 440) {
baby.x = 920;
state = false;
wn = new win(playsound);
wn.setVisible(true);// 游戏胜利了!
}
}
}
}
public void Draw() {
repaint(); // 绘制游戏界面(包括背景、海绵宝宝、海胆、蟹黄堡、水母)
}
public void collisionBall() {
// 当碰到海胆
for (int i = 0; i < hai.length; i++) {
double d = Math.sqrt((baby.x - hai[i].x) * (baby.x - hai[i].x) + (baby.y - hai[i].y) * (baby.y - hai[i].y));
if (d < 80) {
AudioClip hurtsound = Applet.newAudioClip(this.getClass().getResource("eat.au"));
hurtsound.play();
width -= 5;
blood -= 1;
}
}
// 当碰到汉堡
for (int j = 0; j < ham.length; j++) {
double f = Math.sqrt((baby.x - ham[j].x) * (baby.x - ham[j].x) + (baby.y - ham[j].y) * (baby.y - ham[j].y));
if (f < 72) {
AudioClip jumpsound = Applet.newAudioClip(this.getClass().getResource("jump.au"));
jumpsound.play();
width += 5;
blood += 1;
ham[j].x = 1194 + ham[j].x;// 让汉堡在原来的位置上消失
}
}
// 当碰到水母
for (int k = 0; k < shui.length; k++) {
double g = Math
.sqrt((baby.x - shui[k].x) * (baby.x - shui[k].x) + (baby.y - shui[k].y) * (baby.y - shui[k].y));
if (g < 82) {
AudioClip hurtsound = Applet.newAudioClip(this.getClass().getResource("eat.au"));
hurtsound.play();
width -= 5;
blood -= 2;
}
}
// 血量为0
if (width == 0) {
state = false;
ov = new over(playsound);
ov.setVisible(true);// 开启结束面板
}
}
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
if (key == KeyEvent.VK_UP) {
upjump = 10;
}
if (key == KeyEvent.VK_LEFT) {// 假如是向左键被按下
baby.babyimage = babyleft;
left = true;// 打开左走开关
/*****
* 注意不要repaint了,不然打开开关后还是会画
********/
// repaint();
}
if (key == KeyEvent.VK_RIGHT) {
// 假如是向右键被按下
baby.babyimage = babyright;
right = true;// 打开右走开关
// repaint();
}
}
// 线程执行
public void run() {
while (state) {
Draw();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void keyReleased(KeyEvent e) {// 释放时候关闭开关
int key = e.getKeyCode();
if (key == KeyEvent.VK_LEFT) {
left = false;
}
if (key == KeyEvent.VK_RIGHT) {
right = false;
}
if (key == KeyEvent.VK_UP) {
upjump = 0;
}
}
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
}
【游戏感悟】
1.各类间的调用是一大难题,尤其是在游戏死亡后重新开始游戏,以及游戏胜利后某些类的关闭及某些类的重新调用。
2.游戏入口时,如果用流式布局的话,达不到美观的效果,所以用g画笔实现,在playgress类,根据所画的大小来确定监听范围,从而到达按钮的效果
3.游戏音乐设置,调用Applet.newAudioClip
设置无标题super.setUndecorated(true);
设置字体样式以及大小g.setFont(new Font("", Font.BOLD, 20));
4.画游戏背景以及海滩时,都用了两张图片,第一张图片贴在屏幕0,0点,第二张图片在第一张图片右边。
5.海绵宝宝为了营造出向左右走的效果,也调用了两张图片。
6.人物上下左右跳时,遇到一个问题,就是不能同时两个方向走,为了实现同时向右且向上跳的效果,设置boolean开关
7.背景循环时,遇到问题
* 原来代码
// if(backX1==0)backX0=1194;
// if(backX0==0)backX1=1194;
,这样做不能保证backX1正好是0,因为是每次减去速度,减完之后不一定恰好是0.修改后:
if (backX1 < 0) // 背景循环
backX0 = 1194 + backX1;
if (backX0 <= 0)
backX1 = 1194 + backX0;
8.还有一些不断改进的问题,时间比较久远我有些不太记得了……总之不断的想办法改进游戏,使其更流畅更美观。