这是牛客网上一个开源的项目原网址如下:https://www.nowcoder.com/project/index/65
图片资源也可以在网站上进行下载。
目录
目录:
一:效果展示
二.原理
三:实现步骤
1.类的设计
2.代码结构
3.源码
预备知识:java类与对象、java常用GUI组件、动画相关知识。
这里稍微解释一下动画的相关知识。
我们看到的游戏画面为什么会动,简单来说是因为显示器一秒显示若干张相似有关联的图片,当每秒钟的图片数达到30张以上,在视觉上即可产生比较流畅的动画。当每秒显示的图片数越来越多,则动画越流畅。每一张图片也可称位帧——是影像动画中最小单位的单幅影像画面,相当于电影胶片上的每一格镜头。
而飞翔小鸟这个游戏正是利用了这个原理,每秒钟若干次的刷新GUI界面,以模拟动画效果。实际上大部分用GUI做的游戏都是基于此原理。
飞翔小鸟游戏一共需要四个类:小鸟类、地面类、柱子类、GUI界面类
小鸟类:用于控制小鸟的位置、小鸟飞行、碰撞
地面类:主要用于检测小鸟是否碰撞到地面以及利用运动的相对性,地面每一帧左移一个像素,在视觉上实现小鸟是向右飞行的。
柱子类:同地面类。用于检测碰撞,以及模拟运动。
GUI界面类:负责游戏的逻辑控制以及游戏的整个界面显示。
开发工具——IDEA
代码结构:所有类全部放在games文件下,用到的图片文件放在resources文件下
Bird类:
package FlyingBird.games;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
public class Bird {
BufferedImage image;//加载小鸟图片
int x,y;//小鸟中心点坐标
int width,height;//小鸟宽度和高度,涉及到碰撞体积
double g;//重力加速度
double t;//一次位移设定的时间间隔
double v0;//上抛初速度
double speed;//小鸟当前速度
double s;//一次时间间隔内的位移
double alpha;//角度
BufferedImage images[];//用于存放小鸟的动画帧
int index;//动画帧下标
public Bird() throws Exception{
image=ImageIO.read(getClass().getResource("/FlyingBird/resources/0.png"));
width=image.getWidth();
height=image.getHeight();
x=132;//小鸟初始x位置
y=280;//小鸟初始y位置
g=4;
v0=20;
t=0.25;
speed=v0;
s=0;
alpha=0;
images=new BufferedImage[8];//加载动画帧
for (int i = 0; i < images.length; i++) {
images[i]=ImageIO.read(
getClass().getResource("/FlyingBird/resources/"+i+".png"));
}
index=0;
}
//飞行动画
public void fly(){
index++;
image=images[(index/12)%8];//每12帧小鸟的动画帧更新一次
}
//移动一帧
public void step(){
double v1=speed;//这个v1是临时记录speed
s=v1*t-0.5*g*t*t;//模拟自由落体,小鸟向上移动的距离
y=y-(int)s;
double v=v1-g*t;//更新速度
speed=v;
alpha=Math.atan(s/10);//更新小鸟的倾角
}
//鼠标点击后,小鸟向上飞行
public void flappy(){
speed=v0;//将小鸟速度重置
}
//检测小鸟是否碰撞到地面
public boolean hit(Ground ground){
boolean hit=y+height/2>ground.y;//当小鸟的模型下边缘碰到地面,视为碰撞
if(hit){
//更新小鸟倾角设为90度
alpha=-Math.PI/2;
}
return hit;
}
//检测小鸟是否碰撞到柱子,这里用了方法重载。
public boolean hit(Column column){
if(x+width/2>column.x-column.width/2&&
x-width/2column.y-column.gap/2&&//小鸟是否在柱子缝隙中间
y+height/2
Ground类:
package FlyingBird.games;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
public class Ground {
BufferedImage image;//地面背景图
int x;//地面的位置
int y;
int width;
int height;
public Ground() throws IOException {
image= ImageIO.read(getClass().getResource("/FlyingBird/resources/ground.png"));
width=image.getWidth();
height=image.getHeight();
x=0;//设定地面的位置
y=500;
}
//模拟地面移动的动画
public void step(){
x--;
if(x==-109){
x=0;
}
}
}
Column类:
package FlyingBird.games;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;
public class Column {
BufferedImage image;
int x,y;//柱子模型中心的坐标
int width,height;//柱子模型的宽度和高度,这个高度其实是没用的
int gap;//柱子间间隙
int distance;//两根柱子间距离
Random random=new Random();
//初始化第n根柱子
public Column(int n) throws IOException {
image= ImageIO.read(getClass().getResource("/FlyingBird/resources/column.png"));
width=image.getWidth();
height=image.getHeight();
gap=144;
distance=245;
x=550+(n-1)*distance;//初始化柱子中心坐标
y=random.nextInt(218)+132;
}
//柱子的移动动画
//这里用两根柱子的交替出现模拟小鸟所遇到的障碍(柱子)
public void step(){
x--;
if(x==-width/2){
x=distance*2-width/2;
y=random.nextInt(218)+132;
}
}
}
GUI界面:
package FlyingBird.games;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
public class FlyingBirdGUI extends JPanel {
BufferedImage background;//背景图片
BufferedImage startImage;//开始图片
BufferedImage gameOverImage;//结束图片
Bird bird;//小鸟
Ground ground;//地面
Column column1;//柱子1
Column column2;//柱子2
int score;//得分
static int maxscore;//历史最高得分
int state;//游戏状态
//三个状态常量
public static final int START=0;//开始
public static final int RUNNING=1;//运行
public static final int GAME_OVER=2;//结束
//初始化
public FlyingBirdGUI() throws Exception{
this.background= ImageIO.read(getClass().getResource("/FlyingBird/resources/bg.png"));
this.startImage= ImageIO.read(getClass().getResource("/FlyingBird/resources/start.png"));
this.gameOverImage= ImageIO.read(getClass().getResource("/FlyingBird/resources/gameover.png"));
ground=new Ground();
column1=new Column(1);
column2=new Column(2);
bird=new Bird();
score=0;
state=START;
}
//绘制游戏界面
public void paint(Graphics g) {
g.drawImage(background, 0, 0, null);// 绘制背景
g.drawImage(ground.image, ground.x, ground.y, null);//绘制地面
//绘制柱子1、2
g.drawImage(column1.image, column1.x - column1.width / 2, column1.y
- column1.height / 2, null);
g.drawImage(column2.image, column2.x - column2.width / 2, column2.y
- column2.height / 2, null);
//绘制小鸟,涉及到旋转坐标系
Graphics2D g2 = (Graphics2D) g;
g2.rotate(-bird.alpha, bird.x, bird.y);
g.drawImage(bird.image,
bird.x - bird.width / 2, bird.y - bird.height / 2, null);
g2.rotate(bird.alpha, bird.x, bird.y);
//设定字体样式
Font f = new Font(Font.SANS_SERIF, Font.BOLD, 40);
g.setFont(f);
//运行阶段,显示当前分数
if(state==RUNNING){
g.drawString("" + score, 40, 60);
g.setColor(Color.WHITE);
g.drawString("" + score, 40 - 3, 60 - 3);//阴影效果
}
//开始截断,显示历史记录分数(历史最高分)
if(state==START){
g.setColor(Color.RED);
g.drawString("历史记录:" + maxscore, 40, 60);
}
//绘制开始与结束界面
switch (state) {
case START:
g.drawImage(startImage, 0, 0, null);
break;
case GAME_OVER:
g.drawImage(gameOverImage, 0, 0, null);
break;
}
}
//main方法
public static void main(String[] args) throws Exception{
JFrame jFrame = new JFrame();
FlyingBirdGUI flyingBirdGUI = new FlyingBirdGUI();
jFrame.add(flyingBirdGUI);
jFrame.setSize(440,670);
jFrame.setLocationRelativeTo(null);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.setVisible(true);
flyingBirdGUI.action();
}
public void action() throws Exception{
//设置鼠标监视器
MouseListener l=new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
try {
switch (state) {
case START:
// 在开始状态,按下鼠标则转为运行状态。
state = RUNNING;
break;
case RUNNING:
// 在运行状态,按下鼠标则小鸟向上飞行。
bird.flappy();
break;
case GAME_OVER:
// 在结束状态,按下鼠标则重置数据,再次转为开始态。
column1 = new Column(1);
column2 = new Column(2);
bird = new Bird();
//更新历史最高分
if(score>=maxscore){
maxscore=score;
}
score = 0;
state = START;
break;
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
};
addMouseListener(l);
//用while循环模拟游戏动画,即不断的移动和重绘
while (true) {
switch (state) {
case START:
// 小鸟做出飞行动作
bird.fly();
// 地面向左移动一步
ground.step();
break;
case RUNNING:
// 地面向左移动一步
ground.step();
// 柱子向左移动一步
column1.step();
column2.step();
// 小鸟做出飞行动作
bird.fly();
// 小鸟上下移动一步
bird.step();
// 计算分数,小鸟飞过柱子右边界分数+1
if (bird.x == column1.x+column1.width/2 || bird.x == column2.x+column2.width/2) {
score++;
}
// 检测是否发生碰撞
if (bird.hit(ground) || bird.hit(column1) || bird.hit(column2)) {
state = GAME_OVER;
}
break;
}
// 重新绘制界面
repaint();
// 休眠 1000/120 毫秒,可以调节,间隔越短动画速度越快。
Thread.sleep(1000 /120);
}
}
}
可以调节下面这行代码控制游戏的速度。1000/120可以理解为1秒钟(1000毫秒)刷新120次界面。刷新次数越快,画面变化越快,即游戏难度越高。可以尝试将120调整为60,90。
Thread.sleep(1000 /120);