Java小游戏之飞翔的小鸟
大家好,今天给大家带来一个Java经典小游戏—飞翔的小鸟。鉴于小博主初学,文章不足之处,还请见谅。
一、所用知识
- JFrame
- 线程
二、开发工具
- myeclipse
- jdk 1.8
三、开发过程
- 创建项目
- 右键—new—Java Project
- 为项目起个名字flappybird
- 导入素材
- 右键项目—new—Package包
- 把包命名为img,用来存放素材
- 将素材图片全部粘贴到img包里
- 素材一览
3. 窗体设计
(1) 创建JFrame容器
- 右键项目—new—Package包,命名为ui
- 右键ui包—new----class,新建一个类,命名为GameFrame
public class GameFrame extends JFrame{
public GameFrame(){
//设置窗体标题
this.setTitle("飞翔的小鸟");
//设置窗体大小
this.setSize(432, 644);
//设置窗体居中
this.setLocationRelativeTo(null);
//不允许用户改变窗体大小
this.setResizable(false);
//关闭窗体的同时关闭程序
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
GameFrame frame = new GameFrame();
frame.setVisible(true);
}
}
(2) 在进行其他操作之前,我们可以想一下,素材图片这么多,我们不能每次都要写一大串的io代码吧,本着能省就省的原则,我们定一个工具类GameUtil,专门用来读取图片。
public class ImageUtil {
//添加读取指定路径下的图片方法(给洗衣机添加洗衣服的功能)
//path 需要读取图片的路径(相当于将衣服丢入洗衣机中)
//如果不写下面的方法,每传输一张图片就要执行一次try……
public static BufferedImage getImg(String path){
try {
//尝试根据地址找到图片
BufferedImage img=ImageIO.read(ImageUtil.class.getResource(path));
//如果找到,就返回图片
return img;
} catch (IOException e) {
// TODO Auto-generated catch block
//如果找不到,就捕获找不到的原因,并将其输出出来
e.printStackTrace();
}//把图片变成流
return null;
}
}
这样的话,如果需要读取图片,仅需要调用该工具类的getImg方法即可。
- 在GameFrame中用新创建额工具类设置窗体图标。
//获取图标图片
BufferedImage icon=FlayUtil.getImg("/img/0.png");
//将图标图片设置成窗体图标
this.setIconImage(icon);
(3) 创建JPanel容器
- 用来存放诸多控件
- 可以把JFrame当成一张桌子,把JPanel当成一个盘子,把控件当成菜,我们总不能直接把菜倒到桌子上吧。
-右键ui包—new----class,新建一个类,命名为GamePanel,我们先把背景图加载出来,代码如下:
package ui;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import javax.swing.JPanel;
public class GamePanel extends JPanel{
GameFrame fr;
//定义背景图片
BufferedImage bg;
public GamePanel(GameFrame frame) {
// TODO Auto-generated constructor stub
this.fr = frame;
bg = GameUtil.getImg("/img/bg.png");
}
public void paint(Graphics g) {
// TODO Auto-generated method stub
super.paint(g);
//画背景图片
g.drawImage(bg, 0, 0, null);
}
}
效果如下:
(4) 窗体基本上就搭建好了,下一步我们要做的就是加载地面图片,并让它动起来。
- 首先定义一个地面类Ground,用来定地面的属性和方法。代码如下:
package ui;
import java.awt.image.BufferedImage;
/**
* 地面类
* 注释方法:1./** 2.shift+alt+j
* @author Lenovo
*
*/
public class Ground {
//定义地面的横坐标纵坐标
int x;
int y;
//定义地面的宽高
int w;
int h;
BufferedImage img;
//地面的构造器,用来构造一个地面对象
public Ground(){//在此处双击,可以选中括号中所有内容
//地面图片
img=GameUtil.getImg("/img/ground.png");
w=img.getWidth();
h=img.getHeight();
x=0;
y=644-h;
}
}
- 像画背景的步骤一样,在GamePanel中把地面画出来。代码如下:
package ui;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import javax.swing.JPanel;
public class GamePanel extends JPanel{
GameFrame fr;
//定义背景图片
BufferedImage bg;
Ground ground;
public GamePanel(GameFrame frame) {
// TODO Auto-generated constructor stub
this.fr = frame;
bg = GameUtil.getImg("/img/bg.png");
//初始化地面类对象,这样就可以引用ground中的属性和方法了
ground = new Ground();
}
public void paint(Graphics g) {
// TODO Auto-generated method stub
super.paint(g);
//画背景图片
g.drawImage(bg, 0, 0, null);
//画地面
g.drawImage(ground.img,ground.x,ground.y,null);//不设置宽高,默认为图片原始尺寸
}
}
- 此时地面图片已经加载出来了,下一步我们要做的就是让它动起来。
- 在Ground类中创建一个move方法,负责地面的移动。
//地面移动的方法
public void move(){//访问修饰符 返回值类型 方法名
x=x-2;
//越界问题
if(x<=-(w-432)){
//1.地面图片比窗体的宽度要大。
//2.当地面图片右侧移动到窗体右侧时,让地面图片横坐标归零
x=0;
}
}
- 在GamePanel中创建一个线程,在GameFrame中调用上面的方法,真正让地面动起来。代码如下:
//游戏开始的方法
public void action(){
new Thread(){
public void run() {
while(true){//让地面一直移动
ground.move();
//线程休眠时间
try {
Thread.sleep(20);
//图片已经改动位置,因此要重画显示效果
repaint();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}.start();
}
(5) 创建柱子对象,用来当做障碍物。
- 首先定义一个柱子类Column,用来定义柱子的属性和方法。代码如下:
package ui;
import java.awt.image.BufferedImage;
import java.util.Random;
/**柱子类
*
* @author Lenovo
*
*/
public class Column {
//柱子的图片
BufferedImage img;
//柱子的横纵坐标
int x;
int y;
//柱子的宽高
int w;
int h;
//柱子间距
int distance;
Random rd = new Random();
public Column(int i){
img=FlayUtil.getImg("/img/column.png");
//建议y在-400到-250之间
w=img.getWidth();
h=img.getHeight();
distance=300;
x=400+distance*(i-1);
y=-(rd.nextInt(150)+250);
// y=rd.nextDouble(-151)-250;
}
}
- 在GamePanel中定义柱子对象,并初始化。
//定义柱子1
Column column1;
//由于同一画面中可能会出现两根柱子,因此我们定义两个柱子对象
Column column2;
//初始化柱子1对象
column1 = new Column(1);
//初始化柱子2对象
column2 = new Column(2);
- 在GamePanel的paint方法中画出柱子。
- 注:画柱子的代码要写在画地面的代码之后,否则地面图片将无法将多余的柱子图片覆盖。
//画柱子1
g.drawImage(column1.img, column1.x, column1.y, column1.w, column1.h, null);
//画柱子2
g.drawImage(column2.img, column2.x, column2.y, column2.w, column2.h, null);
- 在Ground类中定义move方法,并在GamePanel的线程中调用(调用代码略)。让柱子移动起来,move方法代码如下:
//柱子移动的方法
public void move(){
x=x-2;
if(x<=-w){
x=300+distance;
y=-(rd.nextInt(150)+250);
}
}
(6)创建一个小鸟对象。
- 新建一个小鸟类Bird,用来定义小鸟的属性和方法。代码如下:
package ui;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
/**
* 小鸟类
* @author Lenovo
*
*/
public class Bird {
//小鸟的横纵坐标
int x;
int y;
//小鸟的宽高
int w;
int h;
BufferedImage img;
int index=0;
//List<BufferedImage> animation = new ArrayList();
//初速度
double v0;
//时间间隔(往上移动的时间)
double t;
//距离
double s;
//重力
double g;
Ground ground = new Ground();
public Bird(){
img=GameUtil.getImg("/img/0.png");
w=img.getWidth()*2/3;
h=img.getHeight()*2/3;
x=200;
y=200;
v0=3;
t=0.2;
s=0;
g=5;
}
}
- 在GamePanel中定义柱子对象,并初始化。
//定义小鸟类
Bird bird;
//初始化小鸟对象
bird = new Bird();
- 在GamePanel的paint方法中画出小鸟。
//画小鸟
g.drawImage(bird.img,bird.x,bird.y,bird.w,bird.h,null);
- 在GamePanel中添加键盘监听事件,通过键盘的空格键来控制小鸟上抛。
//键盘监听
// TODO Auto-generated method stub
KeyAdapter adapter = new KeyAdapter(){
public void keyPressed(java.awt.event.KeyEvent e) {
int keyCode = e.getKeyCode();
//按下空格键
if(keyCode==32){
//小鸟上抛
System.out.println("123");
bird.moveUp();
}
//按Ctrl键,游戏重新开始
}
};
fr.addKeyListener(adapter);
}
- 在Bird类中定义fly方法(让小鸟实现飞翔效果)、move方法(小鸟做落体运动)、moveUp方法(键盘调用的小鸟上抛运动)、bang方法(小鸟撞到窗体顶部或底部)、beng方法(小鸟撞到柱子)。代码如下:
//加载多张小鸟图片,让小鸟实现飞翔效果
int step_num=0;
public void fly(){
index++;
if(index>5){
step_num++;
img=FlayUtil.getImg("/img/"+step_num%8+".png");
w=img.getWidth()*2/3;
h=img.getHeight()*2/3;
index=0;
}
}
//小鸟的落体运动
public void move(){
//计算小鸟上抛的距离 距离等于速度×时间
s = v0*t;
//得到小鸟上抛到最高点时的坐标
y=y-(int)s;//s为double类型,y为int类型,类型不匹配,因此要做强制类型转换
//计算小鸟到达最高点时的速度
double v2 = v0-g*t;
//最高点的速度就是小鸟下落时的初速度
v0 = v2;
}
//小鸟上抛
public void moveUp(){
v0=20;
}
//撞到窗体顶部或底部
public Boolean bang() {
// TODO Auto-generated method stub
if((y<0)||(y+h>644-ground.h)){
return true;
}
//没撞到
return false;
}
//小鸟和柱子碰撞
public Boolean beng(Column column){
if((x>column.x-w && x<column.x+column.w) &&//鸟的宽进入柱子宽的范围
((y>column.y+column.h-526-h)||(y<column.y+526))){//鸟的高不在安全区的范围
return true;
}
return false;
}
- 在GamePanel的线程中调用这些方法。
//加载鸟飞的动画
bird.fly();
//小鸟做落体运动
bird.move();
//小鸟和柱子相撞
Boolean boo1 = bird.beng(column1);
Boolean boo2 = bird.beng(column2);
if(bird.bang()||boo1||boo2){
//游戏窗体类对象全部静止
//结束当前循环
break;
}
(7) 设置分数与关卡数。游戏规则:每过一根柱子分数便+1,每过10根柱子关卡数便+1,每次关卡升级,游戏速度加快。
- 在FlayPanel中定义分数和关卡变量。
//定义分数
int fraction=0;
//定义关卡数
int level=1;
- 在线程中判断小鸟是否飞过柱子,并设置分数与关卡数的值。
//判断小鸟是否飞过柱子
if(bird.x==column1.x+column1.w || bird.x==column2.x+column2.w){
fraction++;
//给关卡属赋值,每过10根柱子关卡数便+1
level=(fraction/10)+1;
}
- 更改休眠时间
Thread.sleep(11-level);
- 在paint方法中画出分数和关卡数。
//画分数
g.setColor(Color.red);
Font f = new Font("宋体",Font.BOLD,20);
g.setFont(f);
g.drawString("分数:"+fraction, 120, 30);
g.drawString("关卡数:"+level,20,30);
(8) 添加控制游戏(线程)开始和结束以及重新开始游戏的代码,由于代码位置过于琐碎,因此小博主只将思路简单概括出来,详细代码见下方链接。
- 定义开始(start)与结束(gmov)图片,并初始化。
- 定义一布尔变量show,用来决定游戏是否开始以及是否显示开始(start)图片,设置初始值为true。
- paint方法中添加一if判断条件,当show=true时,画出游戏开始图片。
- 定义一布尔变量gameover,用来决定游戏是否结束以及是否显示结束(gmov)图片,设置初始值为false。
- paint方法中添加一if判断条件,当gameover=true时,画出游戏结束图片。
- 键盘监听事件中添加一if判断条件,当KeyCode=17(按下Ctrl键)且gameover=true(游戏已经结束)时,重新开始游戏:重置柱子、地面、小鸟对象,将start的值设置成true、gameover值设置成false,调用action方法。
- 键盘监听事件中添加一if判断条件,在按下空格键开始游戏之后,如果show=true时(显示开始图片),让show=false。这样既能在开始游戏之后不再显示开始图片,又能使用空格键操控小鸟上抛。
- 在线程的while(true)死循环下添加一个if条件,当show=false,即按下空格键开始游戏之后才实现地面柱子等移动。
至此,飞翔的小鸟已基本完成。
四、 素材与源码打包
网盘链接:https://pan.baidu.com/s/1KSd8dX4oVmYPJusd6RN0Pg
提取码:bxmd
五、 结语
小博主才疏学浅,文章有不足之处,还请各位见谅,有不足之处,万望指出。整理素材、图文排版实属不易,请各位大牛们嘴下留情,我以诚待诸君,望诸君以诚待我。