Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
Guangjun Jia
FlappyBird
Commits
2968bb8e
Commit
2968bb8e
authored
Jul 15, 2020
by
kingyu
Committed by
kingyuluk
Jul 15, 2020
Browse files
feat: 优化记分方法
BREAKING CHANGE: 移除了计时器,更改了游戏的记分方法,现在记分更准确了
parent
e2cb276d
Changes
20
Hide whitespace changes
Inline
Side-by-side
CHANGELOG.md
View file @
2968bb8e
...
...
@@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file. See
[
standard-version
](
https://github.com/conventional-changelog/standard-version
)
for commit guidelines.
### [1.2.2](https://github.com/kingyuluk/FlappyBird/compare/v1.2.1...v1.2.2) (2020-07-12)
### ⚠ BREAKING CHANGES
*
移除了计时器,更改了游戏的记分方法,现在记分更准确了
### Features
*
优化记分方法 (
[
9aa9275
](
https://github.com/kingyuluk/FlappyBird/commit/9aa927537dc9180942c4a982ea46edd840d7c769
)
)
### [1.2.1](https://github.com/kingyuluk/FlappyBird/compare/v1.2.0...v1.2.1) (2020-07-12)
...
...
FlappyBird.jar
0 → 100644
View file @
2968bb8e
File added
README.md
View file @
2968bb8e
...
...
@@ -8,21 +8,26 @@ Flappy Bird for desktop platforms.
## Overview
本项目为Flappy bird的桌面平台版,具备原版的所有功能,且相较于原版优化了游戏难度并加入移动型水管,让游戏更具可玩性。
### 游戏玩法
游戏只需空格键即可操作,每局游戏随机刷新所有元素,小鸟受到重力作用会不断下坠,敲击空格键小鸟就会振翅向上飞,游戏过程中需要玩家控制小鸟不断飞行,并注意躲避随机生成的水管,每飞过一对水管就会得分,飞行过程中如果撞到水管或掉落在地则游戏结束。
本项目为Flappy bird的桌面平台版,具备原版的所有功能,且相较于原版优化了游戏难度并加入移动型水管,增加可玩性。
## How to play
直接运行FlappyBird.jar即可开始游戏。
游戏使用空格键操作。
每局游戏随机刷新所有元素,小鸟受到重力作用会不断下坠,敲击空格键使小鸟振翅向上飞,游戏过程中需要玩家控制小鸟不断飞行,并注意躲避随机生成的水管,每飞过一对水管就会得分,飞行过程中如果撞到水管或掉落在地则游戏结束。
## 游戏界面
### 游戏启动


### 运行示例


### 游戏结束


## Package Contents
...
...
@@ -34,6 +39,9 @@ Flappy Bird for desktop platforms.
## Change Log
v1.2.2 - July 12, 2020
*
移除了计时器,优化了游戏的记分方式,现在记分更准确了
v1.2.1 - July 12, 2020
*
使用AudioClip类的方法播放连续的短音频可能会导致线程冲突使游戏卡顿
...
...
@@ -55,4 +63,8 @@ v1.0.0 - July 10, 2020
*
sun包在不同操作系统和不同版本的JDK中可能发生变化,因此无法确保工作在所有JAVA平台上
## Contact
*
email:
<kingyuluk@mail.dlut.edu.cn>
*
email:
<kingyuluk@hotmail.com>
## License
*
图片与音效资源皆来源于网络,仅供学习交流
examples
/over.png
→
resources/readme_img
/over.png
View file @
2968bb8e
File moved
examples
/play.gif
→
resources/readme_img
/play.gif
View file @
2968bb8e
File moved
examples
/start.png
→
resources/readme_img
/start.png
View file @
2968bb8e
File moved
src/com/bird/main/Bird.java
View file @
2968bb8e
...
...
@@ -9,289 +9,275 @@ import com.bird.util.Constant;
import
com.bird.util.GameUtil
;
import
com.bird.util.MusicUtil
;
import
static
com
.
bird
.
util
.
GameUtil
.
drawTitle
;
/**
* 小鸟类,小鸟的绘制与飞行逻辑都在此类
*
* @author Kingyu
*
* @author Kingyu
*/
public
class
Bird
{
public
static
final
int
IMG_COUNT
=
8
;
// 图片数量
public
static
final
int
STATE_COUNT
=
4
;
// 状态数
private
BufferedImage
[][]
birdImgs
;
// 小鸟的图片数组对象
private
int
x
,
y
;
// 小鸟的坐标
int
wingState
;
// 翅膀状态
// 图片资源
private
BufferedImage
image
;
// 实时的小鸟图片
private
BufferedImage
scoreImg
;
// 计分牌
private
BufferedImage
overImg
;
// 结束标志
private
BufferedImage
againImg
;
// 继续标志
// 小鸟的状态
private
int
state
;
public
static
final
int
STATE_NORMAL
=
0
;
public
static
final
int
STATE_UP
=
1
;
public
static
final
int
STATE_DOWN
=
2
;
public
static
final
int
STATE_FALL
=
3
;
public
static
final
int
STATE_DEAD
=
4
;
private
Rectangle
birdRect
;
// 碰撞矩形
public
static
final
int
RECT_DESCALE
=
2
;
// 补偿碰撞矩形宽高的参数
private
GameTime
timing
;
// 飞行时间
// 在构造器中对资源初始化
public
Bird
()
{
timing
=
GameTime
.
getInstance
();
// 计时器
// 读取小鸟图片资源
birdImgs
=
new
BufferedImage
[
STATE_COUNT
][
IMG_COUNT
];
for
(
int
j
=
0
;
j
<
STATE_COUNT
;
j
++)
{
for
(
int
i
=
0
;
i
<
IMG_COUNT
;
i
++)
{
birdImgs
[
j
][
i
]
=
GameUtil
.
loadBUfferedImage
(
Constant
.
BIRDS_IMG_PATH
[
j
][
i
]);
}
}
// 初始化小鸟的坐标
x
=
Constant
.
FRAME_WIDTH
>>
2
;
y
=
Constant
.
FRAME_HEIGHT
>>
1
;
int
ImgWidth
=
birdImgs
[
state
][
0
].
getWidth
();
int
ImgHeight
=
birdImgs
[
state
][
0
].
getHeight
();
// 初始化碰撞矩形
int
rectX
=
x
-
ImgWidth
/
2
;
int
rectY
=
y
-
ImgHeight
/
2
;
int
rectWidth
=
ImgWidth
;
int
rectHeight
=
ImgHeight
;
birdRect
=
new
Rectangle
(
rectX
+
RECT_DESCALE
,
rectY
+
RECT_DESCALE
*
2
,
rectWidth
-
RECT_DESCALE
*
3
,
rectHeight
-
RECT_DESCALE
*
4
);
// 碰撞矩形的坐标与小鸟相同
}
// 绘制方法
public
void
draw
(
Graphics
g
)
{
fly
();
int
state_index
=
state
>
STATE_FALL
?
STATE_FALL
:
state
;
// 图片资源索引
// 小鸟中心点计算
int
halfImgWidth
=
birdImgs
[
state_index
][
0
].
getWidth
()
>>
1
;
int
halfImgHeight
=
birdImgs
[
state_index
][
0
].
getHeight
()
>>
1
;
if
(
speed
>
0
)
image
=
birdImgs
[
STATE_UP
][
0
];
g
.
drawImage
(
image
,
x
-
halfImgWidth
,
y
-
halfImgHeight
,
null
);
// x坐标于窗口1/4处,y坐标位窗口中心
if
(
state
==
STATE_DEAD
)
{
drawGameover
(
g
);
}
else
if
(
state
==
STATE_FALL
)
{
}
else
{
drawScore
(
g
);
}
// 绘制矩形
// g.setColor(Color.black);
// g.drawRect((int) birdRect.getX(), (int) birdRect.getY(), (int) birdRect.getWidth(), (int) birdRect.getHeight());
}
public
static
final
int
SPEED_UP
=
32
;
// 小鸟向上的速度
public
static
final
double
g
=
9.8
;
// 重力加速度
public
static
final
double
T
=
0.2
;
// 小鸟的下落函数执行的时间
private
double
h
;
// 小鸟y轴的位移量
private
double
speed
=
0
;
// 小鸟的初速度
private
boolean
keyFlag
=
true
;
// 按键状态,true为已释放,使当按住按键时不会重复调用方法
public
void
keyPressed
()
{
keyFlag
=
false
;
}
public
void
keyReleased
()
{
keyFlag
=
true
;
}
public
boolean
keyIsReleased
()
{
return
keyFlag
;
}
// 小鸟的飞行逻辑
private
void
fly
()
{
// 翅膀状态,实现小鸟振翅飞行
wingState
++;
image
=
birdImgs
[
state
>
STATE_FALL
?
STATE_FALL
:
state
][
wingState
/
10
%
IMG_COUNT
];
switch
(
state
)
{
case
STATE_NORMAL:
break
;
case
STATE_UP:
break
;
case
STATE_DOWN:
// 物理公式
speed
-=
g
*
T
;
h
=
speed
*
T
-
g
*
T
*
T
/
2
;
y
=
(
int
)
(
y
-
h
);
birdRect
.
y
=
(
int
)
(
birdRect
.
y
-
h
);
// 控制坠落的边界,若y坐标 > 窗口的高度 - 地面的高度 - 小鸟图片的高度则死亡
if
(
birdRect
.
y
>=
Constant
.
FRAME_HEIGHT
-
Constant
.
GROUND_HEIGHT
-
(
birdImgs
[
state
][
0
].
getHeight
()
>>
1
))
{
y
=
Constant
.
FRAME_HEIGHT
-
Constant
.
GROUND_HEIGHT
-
(
birdImgs
[
state
][
0
].
getHeight
()
>>
1
);
birdRect
.
y
=
Constant
.
FRAME_HEIGHT
-
Constant
.
GROUND_HEIGHT
-
(
birdImgs
[
state
][
0
].
getHeight
()
>>
1
);
birdFall
();
}
break
;
case
STATE_FALL:
// 鸟死亡,自由落体
speed
-=
g
*
T
;
h
=
speed
*
T
-
g
*
T
*
T
/
2
;
y
=
(
int
)
(
y
-
h
);
birdRect
.
y
=
(
int
)
(
birdRect
.
y
-
h
);
// 控制坠落的边界,若y坐标 > 窗口的高度 - 地面的高度 - 小鸟图片的高度则死亡
if
(
birdRect
.
y
>=
Constant
.
FRAME_HEIGHT
-
Constant
.
GROUND_HEIGHT
-
(
birdImgs
[
state
][
0
].
getHeight
()
>>
1
))
{
y
=
Constant
.
FRAME_HEIGHT
-
Constant
.
GROUND_HEIGHT
-
(
birdImgs
[
state
][
0
].
getHeight
()
>>
1
);
birdRect
.
y
=
Constant
.
FRAME_HEIGHT
-
Constant
.
GROUND_HEIGHT
-
(
birdImgs
[
state
][
0
].
getHeight
()
>>
1
);
GameFrame
.
setGameState
(
GameFrame
.
STATE_OVER
);
// 改变游戏状态
birdDead
();
}
break
;
case
STATE_DEAD:
break
;
}
// 控制上方边界
if
(
birdRect
.
y
<
-
1
*
Constant
.
TOP_PIPE_LENGTHENING
/
2
)
{
birdRect
.
y
=
-
1
*
Constant
.
TOP_PIPE_LENGTHENING
/
2
;
y
=
-
1
*
Constant
.
TOP_PIPE_LENGTHENING
/
2
;
}
// 控制下方边界
if
(
birdRect
.
y
>
Constant
.
FRAME_HEIGHT
-
Constant
.
GROUND_HEIGHT
-
(
image
.
getHeight
()
>>
1
))
{
birdFall
();
}
}
// 小鸟振翅
public
void
birdUp
()
{
if
(
keyIsReleased
())
{
// 如果按键已释放
if
(
state
==
STATE_DEAD
||
state
==
STATE_UP
||
state
==
STATE_FALL
)
return
;
// 小鸟死亡或坠落时返回
MusicUtil
.
playFly
();
// 播放音效
state
=
STATE_UP
;
speed
=
SPEED_UP
;
// 每次振翅将速度改为上升速度
wingState
=
0
;
// 重置翅膀状态
keyPressed
();
}
}
// 小鸟下降
public
void
birdDown
()
{
if
(
state
==
STATE_DEAD
||
state
==
STATE_FALL
)
return
;
// 小鸟死亡或坠落时返回
state
=
STATE_DOWN
;
}
// 小鸟坠落(已死)
public
void
birdFall
()
{
state
=
STATE_FALL
;
MusicUtil
.
playCrash
();
// 播放音效
// 结束计时
timing
.
endTiming
();
}
// 小鸟死亡
public
void
birdDead
()
{
state
=
STATE_DEAD
;
// 加载游戏结束的资源
if
(
overImg
==
null
)
{
overImg
=
GameUtil
.
loadBUfferedImage
(
Constant
.
OVER_IMG_PATH
);
scoreImg
=
GameUtil
.
loadBUfferedImage
(
Constant
.
SCORE_IMG_PATH
);
againImg
=
GameUtil
.
loadBUfferedImage
(
Constant
.
AGAIN_IMG_PATH
);
}
}
public
boolean
isDead
()
{
return
state
==
STATE_FALL
||
state
==
STATE_DEAD
;
}
// 开始计时的方法
public
void
startTiming
()
{
if
(
timing
.
isReadyTiming
())
timing
.
startTiming
();
}
// 绘制实时分数
private
void
drawScore
(
Graphics
g
)
{
g
.
setColor
(
Color
.
white
);
g
.
setFont
(
Constant
.
TIME_FONT
);
String
str
=
Long
.
toString
(
timing
.
TimeToScore
());
int
x
=
Constant
.
FRAME_WIDTH
-
GameUtil
.
getStringWidth
(
Constant
.
TIME_FONT
,
str
)
>>
1
;
g
.
drawString
(
str
,
x
,
Constant
.
FRAME_HEIGHT
/
10
);
}
private
static
final
int
SCORE_LOCATE
=
5
;
// 位置补偿参数
private
int
flash
=
0
;
// 图片闪烁参数
// 绘制游戏结束的显示
private
void
drawGameover
(
Graphics
g
)
{
// 绘制结束标志
int
x
=
Constant
.
FRAME_WIDTH
-
overImg
.
getWidth
()
>>
1
;
int
y
=
Constant
.
FRAME_HEIGHT
/
4
;
g
.
drawImage
(
overImg
,
x
,
y
,
null
);
// 绘制计分牌
x
=
Constant
.
FRAME_WIDTH
-
scoreImg
.
getWidth
()
>>
1
;
y
=
Constant
.
FRAME_HEIGHT
/
3
;
g
.
drawImage
(
scoreImg
,
x
,
y
,
null
);
// 绘制本局的分数
g
.
setColor
(
Color
.
white
);
g
.
setFont
(
Constant
.
SCORE_FONT
);
x
=
(
Constant
.
FRAME_WIDTH
-
scoreImg
.
getWidth
()
/
2
>>
1
)
+
SCORE_LOCATE
;
// 位置补偿
y
+=
scoreImg
.
getHeight
()
>>
1
;
String
str
=
Long
.
toString
(
timing
.
TimeToScore
());
x
-=
GameUtil
.
getStringWidth
(
Constant
.
SCORE_FONT
,
str
)
>>
1
;
y
+=
GameUtil
.
getStringHeight
(
Constant
.
SCORE_FONT
,
str
);
g
.
drawString
(
str
,
x
,
y
);
// 绘制最高分数
if
(
timing
.
getBestScore
()
>
0
)
{
str
=
Long
.
toString
(
timing
.
getBestScore
());
x
=
(
Constant
.
FRAME_WIDTH
+
scoreImg
.
getWidth
()
/
2
>>
1
)
-
SCORE_LOCATE
;
// 位置补偿
x
-=
GameUtil
.
getStringWidth
(
Constant
.
SCORE_FONT
,
str
)
>>
1
;
g
.
drawString
(
str
,
x
,
y
);
}
// 绘制继续游戏
final
int
COUNT
=
30
;
// 控制闪烁间隔的参数
if
(
flash
++
>
COUNT
)
{
x
=
Constant
.
FRAME_WIDTH
-
againImg
.
getWidth
()
>>
1
;
y
=
Constant
.
FRAME_HEIGHT
/
5
*
3
;
g
.
drawImage
(
againImg
,
x
,
y
,
null
);
if
(
flash
==
COUNT
*
2
)
flash
=
0
;
}
}
// 重置小鸟
public
void
reset
()
{
state
=
STATE_NORMAL
;
// 小鸟状态
y
=
Constant
.
FRAME_HEIGHT
>>
1
;
// 小鸟坐标
speed
=
0
;
// 小鸟速度
int
ImgHeight
=
birdImgs
[
state
][
0
].
getHeight
();
birdRect
.
y
=
y
-
ImgHeight
/
2
+
RECT_DESCALE
*
2
;
// 小鸟碰撞矩形坐标
timing
.
reset
();
// 计时器
flash
=
0
;
}
// 获取小鸟的碰撞矩形
public
Rectangle
getBirdRect
()
{
return
birdRect
;
}
}
\ No newline at end of file
public
static
final
int
IMG_COUNT
=
8
;
// 图片数量
public
static
final
int
STATE_COUNT
=
4
;
// 状态数
private
final
BufferedImage
[][]
birdImages
;
// 小鸟的图片数组对象
private
final
int
x
;
private
int
y
;
// 小鸟的坐标
int
wingState
;
// 翅膀状态
// 图片资源
private
BufferedImage
image
;
// 实时的小鸟图片
private
BufferedImage
scoreImg
;
// 计分牌
private
BufferedImage
overImg
;
// 结束标志
private
BufferedImage
againImg
;
// 继续标志
// 小鸟的状态
private
int
state
;
public
static
final
int
STATE_NORMAL
=
0
;
public
static
final
int
STATE_UP
=
1
;
public
static
final
int
STATE_DOWN
=
2
;
public
static
final
int
STATE_FALL
=
3
;
public
static
final
int
STATE_DEAD
=
4
;
private
final
Rectangle
birdRect
;
// 碰撞矩形
public
static
final
int
RECT_DESCALE
=
2
;
// 补偿碰撞矩形宽高的参数
private
final
GameScore
countScore
;
// 计分器
// 在构造器中对资源初始化
public
Bird
()
{
countScore
=
GameScore
.
getInstance
();
// 计分器
// 读取小鸟图片资源
birdImages
=
new
BufferedImage
[
STATE_COUNT
][
IMG_COUNT
];
for
(
int
j
=
0
;
j
<
STATE_COUNT
;
j
++)
{
for
(
int
i
=
0
;
i
<
IMG_COUNT
;
i
++)
{
birdImages
[
j
][
i
]
=
GameUtil
.
loadBufferedImage
(
Constant
.
BIRDS_IMG_PATH
[
j
][
i
]);
}
}
// 初始化小鸟的坐标
x
=
Constant
.
FRAME_WIDTH
>>
2
;
y
=
Constant
.
FRAME_HEIGHT
>>
1
;
int
ImgWidth
=
birdImages
[
state
][
0
].
getWidth
();
int
ImgHeight
=
birdImages
[
state
][
0
].
getHeight
();
// 初始化碰撞矩形
int
rectX
=
x
-
ImgWidth
/
2
;
int
rectY
=
y
-
ImgHeight
/
2
;
birdRect
=
new
Rectangle
(
rectX
+
RECT_DESCALE
,
rectY
+
RECT_DESCALE
*
2
,
ImgWidth
-
RECT_DESCALE
*
3
,
ImgHeight
-
RECT_DESCALE
*
4
);
// 碰撞矩形的坐标与小鸟相同
}
// 绘制方法
public
void
draw
(
Graphics
g
)
{
fly
();
int
state_index
=
Math
.
min
(
state
,
STATE_FALL
);
// 图片资源索引
// 小鸟中心点计算
int
halfImgWidth
=
birdImages
[
state_index
][
0
].
getWidth
()
>>
1
;
int
halfImgHeight
=
birdImages
[
state_index
][
0
].
getHeight
()
>>
1
;
if
(
speed
>
0
)
image
=
birdImages
[
STATE_UP
][
0
];
g
.
drawImage
(
image
,
x
-
halfImgWidth
,
y
-
halfImgHeight
,
null
);
// x坐标于窗口1/4处,y坐标位窗口中心
if
(
state
==
STATE_DEAD
)
{
drawGameOver
(
g
);
}
else
if
(
state
!=
STATE_FALL
)
{
drawScore
(
g
);
}
// 绘制矩形
// g.setColor(Color.black);
// g.drawRect((int) birdRect.getX(), (int) birdRect.getY(), (int) birdRect.getWidth(), (int) birdRect.getHeight());
}
public
static
final
int
SPEED_UP
=
32
;
// 小鸟向上的速度
public
static
final
double
g
=
9.8
;
// 重力加速度
public
static
final
double
T
=
0.2
;
// 小鸟的下落函数执行的时间
private
double
speed
=
0
;
// 小鸟的初速度
private
boolean
keyFlag
=
true
;
// 按键状态,true为已释放,使当按住按键时不会重复调用方法
public
void
keyPressed
()
{
keyFlag
=
false
;
}
public
void
keyReleased
()
{
keyFlag
=
true
;
}
public
boolean
keyIsReleased
()
{
return
keyFlag
;
}
// 小鸟的飞行逻辑
private
void
fly
()
{
// 翅膀状态,实现小鸟振翅飞行
wingState
++;
image
=
birdImages
[
Math
.
min
(
state
,
STATE_FALL
)][
wingState
/
10
%
IMG_COUNT
];
switch
(
state
)
{
case
STATE_DOWN:
// 物理公式
speed
-=
g
*
T
;
// 小鸟y轴的位移量
double
h
=
speed
*
T
-
g
*
T
*
T
/
2
;
y
=
(
int
)
(
y
-
h
);
birdRect
.
y
=
(
int
)
(
birdRect
.
y
-
h
);
// 控制坠落的边界,若y坐标 > 窗口的高度 - 地面的高度 - 小鸟图片的高度则死亡
if
(
birdRect
.
y
>=
Constant
.
FRAME_HEIGHT
-
Constant
.
GROUND_HEIGHT
-
(
birdImages
[
state
][
0
].
getHeight
()
>>
1
))
{
y
=
Constant
.
FRAME_HEIGHT
-
Constant
.
GROUND_HEIGHT
-
(
birdImages
[
state
][
0
].
getHeight
()
>>
1
);
birdRect
.
y
=
Constant
.
FRAME_HEIGHT
-
Constant
.
GROUND_HEIGHT
-
(
birdImages
[
state
][
0
].
getHeight
()
>>
1
);
birdFall
();
}
break
;
case
STATE_FALL:
// 鸟死亡,自由落体
speed
-=
g
*
T
;
h
=
speed
*
T
-
g
*
T
*
T
/
2
;
y
=
(
int
)
(
y
-
h
);
birdRect
.
y
=
(
int
)
(
birdRect
.
y
-
h
);
// 控制坠落的边界,若y坐标 > 窗口的高度 - 地面的高度 - 小鸟图片的高度则死亡
if
(
birdRect
.
y
>=
Constant
.
FRAME_HEIGHT
-
Constant
.
GROUND_HEIGHT
-
(
birdImages
[
state
][
0
].
getHeight
()
>>
1
))
{
y
=
Constant
.
FRAME_HEIGHT
-
Constant
.
GROUND_HEIGHT
-
(
birdImages
[
state
][
0
].
getHeight
()
>>
1
);
birdRect
.
y
=
Constant
.
FRAME_HEIGHT
-
Constant
.
GROUND_HEIGHT
-
(
birdImages
[
state
][
0
].
getHeight
()
>>
1
);
GameFrame
.
setGameState
(
GameFrame
.
STATE_OVER
);
// 改变游戏状态
birdDead
();
}
break
;
case
STATE_NORMAL:
case
STATE_UP:
case
STATE_DEAD:
break
;
}
// 控制上方边界
if
(
birdRect
.
y
<
-
1
*
Constant
.
TOP_PIPE_LENGTHENING
/
2
)
{
birdRect
.
y
=
-
1
*
Constant
.
TOP_PIPE_LENGTHENING
/
2
;
y
=
-
1
*
Constant
.
TOP_PIPE_LENGTHENING
/
2
;
}
// 控制下方边界
if
(
birdRect
.
y
>
Constant
.
FRAME_HEIGHT
-
Constant
.
GROUND_HEIGHT
-
(
image
.
getHeight
()
>>
1
))
{
birdFall
();
}
}
// 小鸟振翅
public
void
birdUp
()
{
if
(
keyIsReleased
())
{
// 如果按键已释放
if
(
state
==
STATE_DEAD
||
state
==
STATE_UP
||
state
==
STATE_FALL
)
return
;
// 小鸟死亡或坠落时返回
MusicUtil
.
playFly
();
// 播放音效
state
=
STATE_UP
;
speed
=
SPEED_UP
;
// 每次振翅将速度改为上升速度
wingState
=
0
;
// 重置翅膀状态
keyPressed
();
}
}
// 小鸟下降
public
void
birdDown
()
{
if
(
state
==
STATE_DEAD
||
state
==
STATE_FALL
)
return
;
// 小鸟死亡或坠落时返回
state
=
STATE_DOWN
;
}
// 小鸟坠落(已死)
public
void
birdFall
()
{
state
=
STATE_FALL
;
MusicUtil
.
playCrash
();
// 播放音效
}
// 小鸟死亡
public
void
birdDead
()
{
state
=
STATE_DEAD
;
// 加载游戏结束的资源
if
(
overImg
==
null
)
{
overImg
=
GameUtil
.
loadBufferedImage
(
Constant
.
OVER_IMG_PATH
);
scoreImg
=
GameUtil
.
loadBufferedImage
(
Constant
.
SCORE_IMG_PATH
);
againImg
=
GameUtil
.
loadBufferedImage
(
Constant
.
AGAIN_IMG_PATH
);
}
countScore
.
isSaveScore
();
// 判断是否保存纪录
}
public
boolean
isDead
()
{
return
state
==
STATE_FALL
||
state
==
STATE_DEAD
;
}
// 绘制实时分数
private
void
drawScore
(
Graphics
g
)
{
g
.
setColor
(
Color
.
white
);
g
.
setFont
(
Constant
.
CURRENT_SCORE_FONT
);
String
str
=
Long
.
toString
(
countScore
.
getScore
());
int
x
=
Constant
.
FRAME_WIDTH
-
GameUtil
.
getStringWidth
(
Constant
.
CURRENT_SCORE_FONT
,
str
)
>>
1
;
g
.
drawString
(
str
,
x
,
Constant
.
FRAME_HEIGHT
/
10
);
}
private
static
final
int
SCORE_LOCATE
=
5
;
// 位置补偿参数
private
int
flash
=
0
;
// 图片闪烁参数
// 绘制游戏结束的显示
private
void
drawGameOver
(
Graphics
g
)
{
// 绘制结束标志
int
x
=
Constant
.
FRAME_WIDTH
-
overImg
.
getWidth
()
>>
1
;
int
y
=
Constant
.
FRAME_HEIGHT
/
4
;
g
.
drawImage
(
overImg
,
x
,
y
,
null
);
// 绘制计分牌
x
=
Constant
.
FRAME_WIDTH
-
scoreImg
.
getWidth
()
>>
1
;
y
=
Constant
.
FRAME_HEIGHT
/
3
;
g
.
drawImage
(
scoreImg
,
x
,
y
,
null
);
// 绘制本局的分数
g
.
setColor
(
Color
.
white
);
g
.
setFont
(
Constant
.
SCORE_FONT
);
x
=
(
Constant
.
FRAME_WIDTH
-
scoreImg
.
getWidth
()
/
2
>>
1
)
+
SCORE_LOCATE
;
// 位置补偿
y
+=
scoreImg
.
getHeight
()
>>
1
;
String
str
=
Long
.
toString
(
countScore
.
getScore
());
x
-=
GameUtil
.
getStringWidth
(
Constant
.
SCORE_FONT
,
str
)
>>
1
;
y
+=
GameUtil
.
getStringHeight
(
Constant
.
SCORE_FONT
,
str
);
g
.
drawString
(
str
,
x
,
y
);
// 绘制最高分数
if
(
countScore
.
getBestScore
()
>
0
)
{
str
=
Long
.
toString
(
countScore
.
getBestScore
());
x
=
(
Constant
.
FRAME_WIDTH
+
scoreImg
.
getWidth
()
/
2
>>
1
)
-
SCORE_LOCATE
;
// 位置补偿
x
-=
GameUtil
.
getStringWidth
(
Constant
.
SCORE_FONT
,
str
)
>>
1
;
g
.
drawString
(
str
,
x
,
y
);
}
// 绘制继续游戏,使图像闪烁
final
int
COUNT
=
30
;
// 闪烁周期
if
(
flash
++
>
COUNT
)
drawTitle
(
againImg
,
g
);
if
(
flash
==
COUNT
*
2
)
// 重置闪烁参数
flash
=
0
;
}
// 重置小鸟
public
void
reset
()
{
state
=
STATE_NORMAL
;
// 小鸟状态
y
=
Constant
.
FRAME_HEIGHT
>>
1
;
// 小鸟坐标
speed
=
0
;
// 小鸟速度
int
ImgHeight
=
birdImages
[
state
][
0
].
getHeight
();
birdRect
.
y
=
y
-
ImgHeight
/
2
+
RECT_DESCALE
*
2
;
// 小鸟碰撞矩形坐标
countScore
.
reset
();
// 重置计分器
flash
=
0
;
}
// 获取小鸟的碰撞矩形
public
Rectangle
getBirdRect
()
{
return
birdRect
;
}
}
src/com/bird/main/Cloud.java
View file @
2968bb8e
...
...
@@ -3,78 +3,52 @@ package com.bird.main;
import
java.awt.Graphics
;
import
java.awt.image.BufferedImage
;
import
com.bird.util.Constant
;
/**
* 云朵类
*
* @author Kingyu
*
* @author Kingyu
*/
public
class
Cloud
{
private
int
dir
;
// 方向
public
static
final
int
DIR_NONE
=
0
;
public
static
final
int
DIR_LEFT
=
1
;
public
static
final
int
DIR_RIGHT
=
2
;
private
int
speed
;
// 速度
private
int
x
,
y
;
// 坐标
private
BufferedImage
img
;
// 云朵图片缩放的比例 1.0~2.0
private
double
scale
;
private
int
scaleImageWidth
;
private
int
scaleImageHeight
;
// 构造器
public
Cloud
(
BufferedImage
img
,
int
dir
,
int
x
,
int
y
)
{
super
();
this
.
img
=
img
;
this
.
dir
=
dir
;
this
.
x
=
x
;
this
.
y
=
y
;
this
.
speed
=
2
;
//云朵的速度
scale
=
1
+
Math
.
random
();
// Math.random()返回0.0~1.0的随机值
// 缩放云朵图片
scaleImageWidth
=
(
int
)
(
scale
*
img
.
getWidth
());
scaleImageHeight
=
(
int
)
(
scale
*
img
.
getWidth
());
}
// 绘制方法
public
void
draw
(
Graphics
g
,
Bird
bird
)
{
int
speed
=
this
.
speed
;
if
(
dir
==
DIR_NONE
)
//云彩不动
speed
=
0
;
x
=
(
dir
==
DIR_LEFT
)
?
x
-
speed
:
x
+
speed
;
// 方向逻辑
g
.
drawImage
(
img
,
x
,
y
,
scaleImageWidth
,
scaleImageHeight
,
null
);
}
/**
* 判断云朵是否飞出屏幕
*
* @return 飞出则返回true,否则返回false
*/
public
boolean
isOutFrame
()
{
boolean
result
=
false
;
if
(
dir
==
DIR_LEFT
)
{
if
(
x
<
-
1
*
scaleImageWidth
)
{
return
true
;
}
}
else
if
(
dir
==
DIR_RIGHT
)
{
if
(
x
>
Constant
.
FRAME_WIDTH
)
{
return
true
;
}
}
return
result
;
}
private
final
int
speed
;
// 速度
private
int
x
;
// 坐标
private
final
int
y
;
private
final
BufferedImage
img
;
private
final
int
scaleImageWidth
;
private
final
int
scaleImageHeight
;
// 构造器
public
Cloud
(
BufferedImage
img
,
int
x
,
int
y
)
{
super
();
this
.
img
=
img
;
this
.
x
=
x
;
this
.
y
=
y
;
this
.
speed
=
2
;
//云朵的速度
// 云朵图片缩放的比例 1.0~2.0
double
scale
=
1
+
Math
.
random
();
// Math.random()返回0.0~1.0的随机值
// 缩放云朵图片
scaleImageWidth
=
(
int
)
(
scale
*
img
.
getWidth
());
scaleImageHeight
=
(
int
)
(
scale
*
img
.
getWidth
());
}
// 绘制方法
public
void
draw
(
Graphics
g
,
Bird
bird
)
{
int
speed
=
this
.
speed
;
if
(
bird
.
isDead
())
speed
=
1
;
x
-=
speed
;
g
.
drawImage
(
img
,
x
,
y
,
scaleImageWidth
,
scaleImageHeight
,
null
);
}
/**
* 判断云朵是否飞出屏幕
*
* @return 飞出则返回true,否则返回false
*/
public
boolean
isOutFrame
()
{
return
x
<
-
1
*
scaleImageWidth
;
}
// 改变方向
public
void
setDir
(
int
dir
)
{
this
.
dir
=
dir
;
}
}
src/com/bird/main/GameBackground.java
View file @
2968bb8e
...
...
@@ -14,9 +14,9 @@ import com.bird.util.GameUtil;
*/
public
class
GameBackground
{
private
static
BufferedImage
BackgroundImg
;
// 背景图片
private
static
final
BufferedImage
BackgroundImg
;
// 背景图片
private
int
speed
;
// 背景层的速度
private
final
int
speed
;
// 背景层的速度
private
int
layerX
;
// 背景层的坐标
// 在构造器中初始化
...
...
@@ -26,11 +26,9 @@ public class GameBackground {
}
static
{
//读取背景图片
BackgroundImg
=
GameUtil
.
loadB
U
fferedImage
(
Constant
.
BG_IMG_PATH
);
BackgroundImg
=
GameUtil
.
loadB
u
fferedImage
(
Constant
.
BG_IMG_PATH
);
}
public
static
final
int
BG_IMAGE_HEIGHT
=
BackgroundImg
.
getHeight
();
//图片的高度
// 绘制方法
public
void
draw
(
Graphics
g
,
Bird
bird
)
{
// 绘制背景色
...
...
@@ -58,4 +56,4 @@ public class GameBackground {
if
(
layerX
>
BackgroundImg
.
getWidth
())
layerX
=
0
;
}
}
\ No newline at end of file
}
src/com/bird/main/GameElementLayer.java
View file @
2968bb8e
...
...
@@ -9,230 +9,217 @@ import com.bird.util.GameUtil;
/**
* 游戏中各种元素层的类
*
* @author Kingyu
*
* @author Kingyu
*/
public
class
GameElementLayer
{
private
List
<
Pipe
>
pipes
;
// 水管的容器
// 构造器
public
GameElementLayer
()
{
pipes
=
new
ArrayList
<>();
}
// 绘制方法
public
void
draw
(
Graphics
g
,
Bird
bird
)
{
// 遍历水管容器,如果可见则绘制,不可见则归还
for
(
int
i
=
0
;
i
<
pipes
.
size
();
i
++)
{
Pipe
pipe
=
pipes
.
get
(
i
);
if
(
pipe
.
isVisible
())
{
pipe
.
draw
(
g
,
bird
);
}
else
{
Pipe
remove
=
pipes
.
remove
(
i
);
PipePool
.
giveBack
(
remove
);
i
--;
}
}
// 碰撞检测
isCollideBird
(
bird
);
pipeBornLogic
(
bird
);
}
/**
* 添加水管的逻辑: 当容器中添加的最后一个元素完全显示到屏幕后,添加下一对; 水管成对地相对地出现,空隙高度为窗口高度的1/6;
* 每对水管的间隔距离为屏幕高度的1/4; 水管的高度的取值范围为窗口的[1/8~5/8]
*/
public
static
final
int
VERTICAL_INTERVAL
=
Constant
.
FRAME_HEIGHT
/
5
;
public
static
final
int
HORIZONTAL_INTERVAL
=
Constant
.
FRAME_HEIGHT
>>
2
;
public
static
final
int
MIN_HEIGHT
=
Constant
.
FRAME_HEIGHT
>>
3
;
public
static
final
int
MAX_HEIGHT
=
((
Constant
.
FRAME_HEIGHT
)
>>
3
)
*
5
;
private
void
pipeBornLogic
(
Bird
bird
)
{
if
(
bird
.
isDead
())
{
// 鸟死后不再添加水管
return
;
}
if
(
pipes
.
size
()
==
0
)
{
// 若容器为空,则添加一对水管
int
topHeight
=
GameUtil
.
getRandomNumber
(
MIN_HEIGHT
,
MAX_HEIGHT
+
1
);
// 随机生成水管高度
Pipe
top
=
PipePool
.
get
(
"Pipe"
);
top
.
setAttribute
(
Constant
.
FRAME_WIDTH
,
-
Constant
.
TOP_PIPE_LENGTHENING
,
topHeight
+
Constant
.
TOP_PIPE_LENGTHENING
,
Pipe
.
TYPE_TOP_NORMAL
,
true
);
Pipe
bottom
=
PipePool
.
get
(
"Pipe"
);
bottom
.
setAttribute
(
Constant
.
FRAME_WIDTH
,
topHeight
+
VERTICAL_INTERVAL
,
Constant
.
FRAME_HEIGHT
-
topHeight
-
VERTICAL_INTERVAL
,
Pipe
.
TYPE_BOTTOM_NORMAL
,
true
);
pipes
.
add
(
top
);
pipes
.
add
(
bottom
);
}
else
{
// 判断最后一对水管是否完全进入游戏窗口,若进入则添加水管
Pipe
lastPipe
=
pipes
.
get
(
pipes
.
size
()
-
1
);
// 获得容器中最后一个水管
if
(
lastPipe
.
isInFrame
())
{
// 根据游戏分数难度递增
if
(
GameTime
.
getInstance
().
TimeToScore
()
<
Constant
.
HOVER_MOVING_SCORE
)
{
try
{
if
(
GameUtil
.
isInProbability
(
2
,
5
))
{
// 40%的概率生成悬浮的普通水管
addHoverPipe
(
lastPipe
);
}
else
{
addNormalPipe
(
lastPipe
);
}
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
}
}
else
{
try
{
if
(
GameUtil
.
isInProbability
(
1
,
4
))
{
// 1/4的概率生成普通水管
if
(
GameUtil
.
isInProbability
(
1
,
2
))
// 生成普通水管和悬浮水管的概率
addNormalPipe
(
lastPipe
);
else
addHoverPipe
(
lastPipe
);
}
else
{
if
(
GameUtil
.
isInProbability
(
1
,
3
))
// 生成移动水管和移动悬浮水管的概率
addMovingHoverPipe
(
lastPipe
);
else
addMovingNormalPipe
(
lastPipe
);
}
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
}
}
}
}
}
/**
* 添加普通水管
*
* @param lastPipe 传入最后一根水管以获取x坐标
*/
private
void
addNormalPipe
(
Pipe
lastPipe
)
{
int
topHeight
=
GameUtil
.
getRandomNumber
(
MIN_HEIGHT
,
MAX_HEIGHT
+
1
);
// 随机生成水管高度
int
x
=
lastPipe
.
getX
()
+
HORIZONTAL_INTERVAL
;
// 新水管的x坐标 = 最后一对水管的x坐标 + 水管的间隔
Pipe
top
=
PipePool
.
get
(
"Pipe"
);
//从水管对象池中获取对象
//设置x, y, height, type属性
top
.
setAttribute
(
x
,
-
Constant
.
TOP_PIPE_LENGTHENING
,
topHeight
+
Constant
.
TOP_PIPE_LENGTHENING
,
Pipe
.
TYPE_TOP_NORMAL
,
true
);
Pipe
bottom
=
PipePool
.
get
(
"Pipe"
);
bottom
.
setAttribute
(
x
,
topHeight
+
VERTICAL_INTERVAL
,
Constant
.
FRAME_HEIGHT
-
topHeight
-
VERTICAL_INTERVAL
,
Pipe
.
TYPE_BOTTOM_NORMAL
,
true
);
pipes
.
add
(
top
);
pipes
.
add
(
bottom
);
}
/**
* 添加悬浮水管
*
* @param lastPipe
*/
private
void
addHoverPipe
(
Pipe
lastPipe
)
{
// 随机生成水管高度,屏幕高度的[1/4,1/6]
int
topHoverHeight
=
GameUtil
.
getRandomNumber
(
Constant
.
FRAME_HEIGHT
/
6
,
Constant
.
FRAME_HEIGHT
/
4
);
int
x
=
lastPipe
.
getX
()
+
HORIZONTAL_INTERVAL
;
// 新水管的x坐标 = 最后一对水管的x坐标 + 水管的间隔
int
y
=
GameUtil
.
getRandomNumber
(
Constant
.
FRAME_HEIGHT
/
12
,
Constant
.
FRAME_HEIGHT
/
6
);
// 随机水管的y坐标,窗口的[1/6,1/12]
int
type
=
Pipe
.
TYPE_HOVER_NORMAL
;
// 生成上部的悬浮水管
Pipe
topHover
=
PipePool
.
get
(
"Pipe"
);
topHover
.
setAttribute
(
x
,
y
,
topHoverHeight
,
type
,
true
);
// 生成下部的悬浮水管
int
bottomHoverHeight
=
Constant
.
FRAME_HEIGHT
-
2
*
y
-
topHoverHeight
-
VERTICAL_INTERVAL
;
Pipe
bottomHover
=
PipePool
.
get
(
"Pipe"
);
bottomHover
.
setAttribute
(
x
,
y
+
topHoverHeight
+
VERTICAL_INTERVAL
,
bottomHoverHeight
,
type
,
true
);
pipes
.
add
(
topHover
);
pipes
.
add
(
bottomHover
);
}
/**
* 添加移动的悬浮水管
*
* @param lastPipe
*/
private
void
addMovingHoverPipe
(
Pipe
lastPipe
)
{
// 随机生成水管高度,屏幕高度的[1/4,1/6]
int
topHoverHeight
=
GameUtil
.
getRandomNumber
(
Constant
.
FRAME_HEIGHT
/
6
,
Constant
.
FRAME_HEIGHT
/
4
);
int
x
=
lastPipe
.
getX
()
+
HORIZONTAL_INTERVAL
;
// 新水管的x坐标 = 最后一对水管的x坐标 + 水管的间隔
int
y
=
GameUtil
.
getRandomNumber
(
Constant
.
FRAME_HEIGHT
/
12
,
Constant
.
FRAME_HEIGHT
/
6
);
// 随机水管的y坐标,窗口的[1/6,1/12]
int
type
=
Pipe
.
TYPE_HOVER_HARD
;
// 生成上部的悬浮水管
Pipe
topHover
=
PipePool
.
get
(
"MovingPipe"
);
topHover
.
setAttribute
(
x
,
y
,
topHoverHeight
,
type
,
true
);
// 生成下部的悬浮水管
int
bottomHoverHeight
=
Constant
.
FRAME_HEIGHT
-
2
*
y
-
topHoverHeight
-
VERTICAL_INTERVAL
;
Pipe
bottomHover
=
PipePool
.
get
(
"MovingPipe"
);
bottomHover
.
setAttribute
(
x
,
y
+
topHoverHeight
+
VERTICAL_INTERVAL
,
bottomHoverHeight
,
type
,
true
);
pipes
.
add
(
topHover
);
pipes
.
add
(
bottomHover
);
}
/**
* 添加移动的普通水管
*
* @param lastPipe
*/
private
void
addMovingNormalPipe
(
Pipe
lastPipe
)
{
int
topHeight
=
GameUtil
.
getRandomNumber
(
MIN_HEIGHT
,
MAX_HEIGHT
+
1
);
// 随机生成水管高度
int
x
=
lastPipe
.
getX
()
+
HORIZONTAL_INTERVAL
;
// 新水管的x坐标 = 最后一对水管的x坐标 + 水管的间隔
Pipe
top
=
PipePool
.
get
(
"MovingPipe"
);
top
.
setAttribute
(
x
,
-
Constant
.
TOP_PIPE_LENGTHENING
,
topHeight
+
Constant
.
TOP_PIPE_LENGTHENING
,
Pipe
.
TYPE_TOP_HARD
,
true
);
Pipe
bottom
=
PipePool
.
get
(
"MovingPipe"
);
bottom
.
setAttribute
(
x
,
topHeight
+
VERTICAL_INTERVAL
,
Constant
.
FRAME_HEIGHT
-
topHeight
-
VERTICAL_INTERVAL
,
Pipe
.
TYPE_BOTTOM_HARD
,
true
);
pipes
.
add
(
top
);
pipes
.
add
(
bottom
);
}
/**
* 判断元素和小鸟是否发生碰撞,若发生碰撞返回true,否则返回false
*
* @param bird
* @return
*/
public
boolean
isCollideBird
(
Bird
bird
)
{
// 若鸟已死则不再判断
if
(
bird
.
isDead
())
{
return
false
;
}
// 遍历水管容器
for
(
int
i
=
0
;
i
<
pipes
.
size
();
i
++)
{
Pipe
pipe
=
pipes
.
get
(
i
);
// 判断碰撞矩形是否有交集
if
(
pipe
.
getPipeRect
().
intersects
(
bird
.
getBirdRect
()))
{
bird
.
birdFall
();
//有交集则小鸟坠落
return
true
;
}
}
return
false
;
}
// 重置元素层
public
void
reset
()
{
for
(
int
i
=
0
;
i
<
pipes
.
size
();
i
++)
{
Pipe
pipe
=
pipes
.
get
(
i
);
PipePool
.
giveBack
(
pipe
);
}
pipes
.
clear
();
}
}
\ No newline at end of file
private
final
List
<
Pipe
>
pipes
;
// 水管的容器
// 构造器
public
GameElementLayer
()
{
pipes
=
new
ArrayList
<>();
}
// 绘制方法
public
void
draw
(
Graphics
g
,
Bird
bird
)
{
// 遍历水管容器,如果可见则绘制,不可见则归还
for
(
int
i
=
0
;
i
<
pipes
.
size
();
i
++)
{
Pipe
pipe
=
pipes
.
get
(
i
);
if
(
pipe
.
isVisible
())
{
pipe
.
draw
(
g
,
bird
);
}
else
{
Pipe
remove
=
pipes
.
remove
(
i
);
PipePool
.
giveBack
(
remove
);
i
--;
}
}
// 碰撞检测
isCollideBird
(
bird
);
pipeBornLogic
(
bird
);
}
/**
* 添加水管的逻辑: 当容器中添加的最后一个元素完全显示到屏幕后,添加下一对; 水管成对地相对地出现,空隙高度为窗口高度的1/6;
* 每对水管的间隔距离为屏幕高度的1/4; 水管的高度的取值范围为窗口的[1/8~5/8]
*/
public
static
final
int
VERTICAL_INTERVAL
=
Constant
.
FRAME_HEIGHT
/
5
;
public
static
final
int
HORIZONTAL_INTERVAL
=
Constant
.
FRAME_HEIGHT
>>
2
;
public
static
final
int
MIN_HEIGHT
=
Constant
.
FRAME_HEIGHT
>>
3
;
public
static
final
int
MAX_HEIGHT
=
((
Constant
.
FRAME_HEIGHT
)
>>
3
)
*
5
;
private
void
pipeBornLogic
(
Bird
bird
)
{
if
(
bird
.
isDead
())
{
// 鸟死后不再添加水管
return
;
}
if
(
pipes
.
size
()
==
0
)
{
// 若容器为空,则添加一对水管
int
topHeight
=
GameUtil
.
getRandomNumber
(
MIN_HEIGHT
,
MAX_HEIGHT
+
1
);
// 随机生成水管高度
Pipe
top
=
PipePool
.
get
(
"Pipe"
);
top
.
setAttribute
(
Constant
.
FRAME_WIDTH
,
-
Constant
.
TOP_PIPE_LENGTHENING
,
topHeight
+
Constant
.
TOP_PIPE_LENGTHENING
,
Pipe
.
TYPE_TOP_NORMAL
,
true
);
Pipe
bottom
=
PipePool
.
get
(
"Pipe"
);
bottom
.
setAttribute
(
Constant
.
FRAME_WIDTH
,
topHeight
+
VERTICAL_INTERVAL
,
Constant
.
FRAME_HEIGHT
-
topHeight
-
VERTICAL_INTERVAL
,
Pipe
.
TYPE_BOTTOM_NORMAL
,
true
);
pipes
.
add
(
top
);
pipes
.
add
(
bottom
);
}
else
{
// 判断最后一对水管是否完全进入游戏窗口,若进入则添加水管
Pipe
lastPipe
=
pipes
.
get
(
pipes
.
size
()
-
1
);
// 获得容器中最后一个水管
if
(
lastPipe
.
isInFrame
())
{
if
(
pipes
.
size
()
>=
Constant
.
FULL_PIPE
-
2
)
// 若窗口中可容纳的水管已满,说明小鸟已飞到第一对水管的位置,开始记分
GameScore
.
getInstance
().
setScore
(
bird
);
try
{
int
currentScore
=
(
int
)
GameScore
.
getInstance
().
getScore
()
+
1
;
// 获取当前分数
// 移动水管刷新的概率随当前分数递增,当得分大于19后全部刷新移动水管
if
(
GameUtil
.
isInProbability
(
currentScore
,
20
))
{
if
(
GameUtil
.
isInProbability
(
1
,
4
))
// 生成移动水管和移动悬浮水管的概率
addMovingHoverPipe
(
lastPipe
);
else
addMovingNormalPipe
(
lastPipe
);
}
else
{
if
(
GameUtil
.
isInProbability
(
1
,
2
))
// 生成静止普通水管和静止悬浮水管的概率
addNormalPipe
(
lastPipe
);
else
addHoverPipe
(
lastPipe
);
}
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
}
}
}
}
/**
* 添加普通水管
*
* @param lastPipe 传入最后一根水管以获取x坐标
*/
private
void
addNormalPipe
(
Pipe
lastPipe
)
{
int
topHeight
=
GameUtil
.
getRandomNumber
(
MIN_HEIGHT
,
MAX_HEIGHT
+
1
);
// 随机生成水管高度
int
x
=
lastPipe
.
getX
()
+
HORIZONTAL_INTERVAL
;
// 新水管的x坐标 = 最后一对水管的x坐标 + 水管的间隔
Pipe
top
=
PipePool
.
get
(
"Pipe"
);
// 从水管对象池中获取对象
// 设置x, y, height, type属性
top
.
setAttribute
(
x
,
-
Constant
.
TOP_PIPE_LENGTHENING
,
topHeight
+
Constant
.
TOP_PIPE_LENGTHENING
,
Pipe
.
TYPE_TOP_NORMAL
,
true
);
Pipe
bottom
=
PipePool
.
get
(
"Pipe"
);
bottom
.
setAttribute
(
x
,
topHeight
+
VERTICAL_INTERVAL
,
Constant
.
FRAME_HEIGHT
-
topHeight
-
VERTICAL_INTERVAL
,
Pipe
.
TYPE_BOTTOM_NORMAL
,
true
);
pipes
.
add
(
top
);
pipes
.
add
(
bottom
);
}
/**
* 添加悬浮水管
*
* @param lastPipe 传入最后一根水管以获取x坐标
*/
private
void
addHoverPipe
(
Pipe
lastPipe
)
{
// 随机生成水管高度,屏幕高度的[1/4,1/6]
int
topHoverHeight
=
GameUtil
.
getRandomNumber
(
Constant
.
FRAME_HEIGHT
/
6
,
Constant
.
FRAME_HEIGHT
/
4
);
int
x
=
lastPipe
.
getX
()
+
HORIZONTAL_INTERVAL
;
// 新水管的x坐标 = 最后一对水管的x坐标 + 水管的间隔
int
y
=
GameUtil
.
getRandomNumber
(
Constant
.
FRAME_HEIGHT
/
12
,
Constant
.
FRAME_HEIGHT
/
6
);
// 随机水管的y坐标,窗口的[1/6,1/12]
int
type
=
Pipe
.
TYPE_HOVER_NORMAL
;
// 生成上部的悬浮水管
Pipe
topHover
=
PipePool
.
get
(
"Pipe"
);
topHover
.
setAttribute
(
x
,
y
,
topHoverHeight
,
type
,
true
);
// 生成下部的悬浮水管
int
bottomHoverHeight
=
Constant
.
FRAME_HEIGHT
-
2
*
y
-
topHoverHeight
-
VERTICAL_INTERVAL
;
Pipe
bottomHover
=
PipePool
.
get
(
"Pipe"
);
bottomHover
.
setAttribute
(
x
,
y
+
topHoverHeight
+
VERTICAL_INTERVAL
,
bottomHoverHeight
,
type
,
true
);
pipes
.
add
(
topHover
);
pipes
.
add
(
bottomHover
);
}
/**
* 添加移动的悬浮水管
*
* @param lastPipe 传入最后一根水管以获取x坐标
*/
private
void
addMovingHoverPipe
(
Pipe
lastPipe
)
{
// 随机生成水管高度,屏幕高度的[1/4,1/6]
int
topHoverHeight
=
GameUtil
.
getRandomNumber
(
Constant
.
FRAME_HEIGHT
/
6
,
Constant
.
FRAME_HEIGHT
/
4
);
int
x
=
lastPipe
.
getX
()
+
HORIZONTAL_INTERVAL
;
// 新水管的x坐标 = 最后一对水管的x坐标 + 水管的间隔
int
y
=
GameUtil
.
getRandomNumber
(
Constant
.
FRAME_HEIGHT
/
12
,
Constant
.
FRAME_HEIGHT
/
6
);
// 随机水管的y坐标,窗口的[1/6,1/12]
int
type
=
Pipe
.
TYPE_HOVER_HARD
;
// 生成上部的悬浮水管
Pipe
topHover
=
PipePool
.
get
(
"MovingPipe"
);
topHover
.
setAttribute
(
x
,
y
,
topHoverHeight
,
type
,
true
);
// 生成下部的悬浮水管
int
bottomHoverHeight
=
Constant
.
FRAME_HEIGHT
-
2
*
y
-
topHoverHeight
-
VERTICAL_INTERVAL
;
Pipe
bottomHover
=
PipePool
.
get
(
"MovingPipe"
);
bottomHover
.
setAttribute
(
x
,
y
+
topHoverHeight
+
VERTICAL_INTERVAL
,
bottomHoverHeight
,
type
,
true
);
pipes
.
add
(
topHover
);
pipes
.
add
(
bottomHover
);
}
/**
* 添加移动的普通水管
*
* @param lastPipe 传入最后一根水管以获取x坐标
*/
private
void
addMovingNormalPipe
(
Pipe
lastPipe
)
{
int
topHeight
=
GameUtil
.
getRandomNumber
(
MIN_HEIGHT
,
MAX_HEIGHT
+
1
);
// 随机生成水管高度
int
x
=
lastPipe
.
getX
()
+
HORIZONTAL_INTERVAL
;
// 新水管的x坐标 = 最后一对水管的x坐标 + 水管的间隔
Pipe
top
=
PipePool
.
get
(
"MovingPipe"
);
top
.
setAttribute
(
x
,
-
Constant
.
TOP_PIPE_LENGTHENING
,
topHeight
+
Constant
.
TOP_PIPE_LENGTHENING
,
Pipe
.
TYPE_TOP_HARD
,
true
);
Pipe
bottom
=
PipePool
.
get
(
"MovingPipe"
);
bottom
.
setAttribute
(
x
,
topHeight
+
VERTICAL_INTERVAL
,
Constant
.
FRAME_HEIGHT
-
topHeight
-
VERTICAL_INTERVAL
,
Pipe
.
TYPE_BOTTOM_HARD
,
true
);
pipes
.
add
(
top
);
pipes
.
add
(
bottom
);
}
/**
* 判断元素和小鸟是否发生碰撞
*
* @param bird 传入小鸟对象
*/
public
void
isCollideBird
(
Bird
bird
)
{
// 若鸟已死则不再判断
if
(
bird
.
isDead
())
{
return
;
}
// 遍历水管容器
for
(
Pipe
pipe
:
pipes
)
{
// 判断碰撞矩形是否有交集
if
(
pipe
.
getPipeRect
().
intersects
(
bird
.
getBirdRect
()))
{
bird
.
birdFall
();
// 有交集则小鸟坠落
return
;
}
}
}
// 重置元素层
public
void
reset
()
{
for
(
Pipe
pipe
:
pipes
)
{
PipePool
.
giveBack
(
pipe
);
}
pipes
.
clear
();
}
}
src/com/bird/main/GameForeground.java
View file @
2968bb8e
...
...
@@ -10,89 +10,71 @@ import com.bird.util.GameUtil;
/**
* 前景类, 游戏中的遮挡层 包含多朵云
*
* @author Kingyu
*
* @author Kingyu
*/
public
class
GameForeground
{
private
List
<
Cloud
>
clouds
=
new
ArrayList
<>();
// 云朵的容器
private
final
List
<
Cloud
>
clouds
;
// 云朵的容器
private
final
BufferedImage
[]
cloudImages
;
// 图片资源
private
long
time
;
// 控制云的逻辑运算周期
public
static
final
int
CLOUD_INTERVAL
=
100
;
//云朵刷新的逻辑运算的周期
private
BufferedImage
[]
cloudImgs
;
// 图片资源
private
int
cloudDir
;
// 云的方向
public
GameForeground
()
{
clouds
=
new
ArrayList
<>();
//云朵的容器
private
long
time
;
// 控制云的逻辑运算周期
public
static
final
int
CLOUD_INTERVAL
=
100
;
//云朵刷新的逻辑运算的周期
// 读入图片资源
cloudImages
=
new
BufferedImage
[
Constant
.
CLOUD_IMAGE_COUNT
];
for
(
int
i
=
0
;
i
<
Constant
.
CLOUD_IMAGE_COUNT
;
i
++)
{
cloudImages
[
i
]
=
GameUtil
.
loadBufferedImage
(
Constant
.
CLOUDS_IMG_PATH
[
i
]);
}
time
=
System
.
currentTimeMillis
();
// 获取当前时间,用于控制云的逻辑运算周期
}
public
GameForeground
()
{
clouds
=
new
ArrayList
<>();
//云朵的容器
// 读入图片资源
cloudImgs
=
new
BufferedImage
[
Constant
.
CLOUD_IMAGE_COUNT
];
for
(
int
i
=
0
;
i
<
Constant
.
CLOUD_IMAGE_COUNT
;
i
++)
{
cloudImgs
[
i
]
=
GameUtil
.
loadBUfferedImage
(
Constant
.
CLOUDS_IMG_PATH
[
i
]);
}
// 绘制方法
public
void
draw
(
Graphics
g
,
Bird
bird
)
{
cloudLogic
();
for
(
Cloud
cloud
:
clouds
)
{
cloud
.
draw
(
g
,
bird
);
}
}
// 初始化云朵的属性
cloudDir
=
Cloud
.
DIR_LEFT
;
time
=
System
.
currentTimeMillis
();
// 获取当前时间,用于控制云的逻辑运算周期
}
// 云朵的控制
private
void
cloudLogic
()
{
// 100ms运算一次
if
(
System
.
currentTimeMillis
()
-
time
>
CLOUD_INTERVAL
)
{
time
=
System
.
currentTimeMillis
();
// 重置time
// 如果屏幕的云朵的数量小于允许的最大数量,根据给定的概率随机添加云朵
if
(
clouds
.
size
()
<
Constant
.
MAX_CLOUD_COUNT
)
{
try
{
if
(
GameUtil
.
isInProbability
(
Constant
.
CLOUD_BORN_PERCENT
,
100
))
{
// 根据给定的概率添加云朵
int
index
=
GameUtil
.
getRandomNumber
(
0
,
Constant
.
CLOUD_IMAGE_COUNT
);
// 随机选取云朵图片
// 绘制方法
public
void
draw
(
Graphics
g
,
Bird
bird
)
{
cloudLogic
();
for
(
int
i
=
0
;
i
<
clouds
.
size
();
i
++)
{
clouds
.
get
(
i
).
draw
(
g
,
bird
);
}
}
// 云朵刷新的坐标
int
x
=
Constant
.
FRAME_WIDTH
;
// 从屏幕左侧开始刷新
// y坐标随机在上1/3屏选取
int
y
=
GameUtil
.
getRandomNumber
(
Constant
.
TOP_BAR_HEIGHT
,
Constant
.
FRAME_HEIGHT
/
3
);
// 云朵的控制
private
void
cloudLogic
()
{
// 100ms运算一次
if
(
System
.
currentTimeMillis
()
-
time
>
CLOUD_INTERVAL
)
{
time
=
System
.
currentTimeMillis
();
// 重置time
// 如果屏幕的云朵的数量小于允许的最大数量,根据给定的概率随机添加云朵
if
(
clouds
.
size
()
<
Constant
.
MAX_CLOUD_COUNT
)
{
try
{
if
(
GameUtil
.
isInProbability
(
Constant
.
CLOUD_BORN_PERCENT
,
100
))
{
// 根据给定的概率添加云朵
int
index
=
GameUtil
.
getRandomNumber
(
0
,
Constant
.
CLOUD_IMAGE_COUNT
);
// 随机选取云朵图片
// 云朵刷新的坐标
int
x
=
Constant
.
FRAME_WIDTH
;
// 从屏幕左侧开始刷新
// 根据当前的方向调整云朵刷新的x坐标(暂时无用)
// if (cloudDir == Cloud.DIR_LEFT) {
// } else {
// x = -1 * cloudImgs[index].getWidth();
// }
// y坐标随机在上1/3屏选取
int
y
=
GameUtil
.
getRandomNumber
(
Constant
.
TOP_BAR_HEIGHT
,
Constant
.
FRAME_HEIGHT
/
3
);
//向容器中添加云朵
Cloud
cloud
=
new
Cloud
(
cloudImgs
[
index
],
cloudDir
,
x
,
y
);
clouds
.
add
(
cloud
);
}
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
}
}
// 添加云朵
//向容器中添加云朵
Cloud
cloud
=
new
Cloud
(
cloudImages
[
index
],
x
,
y
);
clouds
.
add
(
cloud
);
}
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
}
}
// 添加云朵
// 若云朵飞出屏幕则从容器中移除
for
(
int
i
=
0
;
i
<
clouds
.
size
();
i
++)
{
// 遍历容器中的云朵
Cloud
tempCloud
=
clouds
.
get
(
i
);
if
(
tempCloud
.
isOutFrame
())
{
clouds
.
remove
(
i
);
i
--;
}
}
// 若云朵飞出屏幕则从容器中移除
for
(
int
i
=
0
;
i
<
clouds
.
size
();
i
++)
{
// 遍历容器中的云朵
Cloud
tempCloud
=
clouds
.
get
(
i
);
if
(
tempCloud
.
isOutFrame
())
{
clouds
.
remove
(
i
);
i
--;
}
}
/*
* 功能已实现,但不太符合现实,暂时注释掉 // 云朵随机改变方向, try { if (GameUtil.isInProbability(1,
* Constant.CLOUD_DIRCHANGE)) { // 新的云彩方向 int newDir =
* GameUtil.getRandomNumber(Cloud.DIR_LEFT, Cloud.DIR_RIGHT + 1); if (newDir !=
* cloudDir) { // 风向改变,调整云彩状态 cloudDir = newDir; for (int i = 0; i <
* clouds.size(); i++) { // 遍历容器中的云朵 Cloud tempCloud = clouds.get(i);
* tempCloud.setDir(newDir); } } System.out.println(cloudDir); } } catch
* (Exception e) { e.printStackTrace(); }
*/
}
}
}
\ No newline at end of file
}
}
}
src/com/bird/main/GameFrame.java
View file @
2968bb8e
...
...
@@ -18,158 +18,152 @@ import java.awt.image.BufferedImage;
/**
* 主窗口类,游戏窗口和绘制的相关内容
*
* @author Kingyu
*
* @author Kingyu
*/
public
class
GameFrame
extends
Frame
implements
Runnable
{
private
static
final
long
serialVersionUID
=
1L
;
// 保持版本的兼容性
private
static
int
gameState
;
// 游戏状态
public
static
final
int
STATE_READY
=
0
;
// 游戏未开始
public
static
final
int
STATE_START
=
1
;
// 游戏开始
public
static
final
int
STATE_OVER
=
2
;
// 游戏结束
private
GameBackground
background
;
// 游戏背景对象
private
GameForeground
foreground
;
// 游戏前景对象
private
Bird
bird
;
// 小鸟对象
private
GameElementLayer
gameElement
;
// 游戏元素对象
private
GameReady
ready
;
// 游戏未开始时对象
// 在构造器中初始化
public
GameFrame
()
{
initFrame
();
// 初始化游戏窗口
setVisible
(
true
);
// 窗口默认为不可见,设置为可见
initGame
();
// 初始化游戏对象
}
// 初始化游戏窗口
private
void
initFrame
()
{
setSize
(
FRAME_WIDTH
,
FRAME_HEIGHT
);
// 设置窗口大小
setTitle
(
GAME_TITLE
);
// 设置窗口标题
setLocation
(
FRAME_X
,
FRAME_Y
);
// 窗口初始位置
setResizable
(
false
);
// 设置窗口大小不可变
// 添加关闭窗口事件(监听窗口发生的事件,派发给参数对象,参数对象调用对应的方法)
addWindowListener
(
new
WindowAdapter
()
{
@Override
public
void
windowClosing
(
WindowEvent
e
)
{
System
.
exit
(
0
);
// 结束程序
}
});
addKeyListener
(
new
BirdKeyListener
());
// 添加按键监听
}
// 用于接收按键事件的对象的内部类
class
BirdKeyListener
implements
KeyListener
{
// 按键按下,根据游戏当前的状态调用不同的方法
public
void
keyPressed
(
KeyEvent
e
)
{
int
keycode
=
e
.
getKeyChar
();
switch
(
gameState
)
{
case
STATE_READY:
if
(
keycode
==
KeyEvent
.
VK_SPACE
)
{
// 游戏启动界面时按下空格,小鸟振翅一次并开始受重力影响
bird
.
birdUp
();
bird
.
birdDown
();
setGameState
(
STATE_START
);
// 游戏状态改变
bird
.
startTiming
();
// 计时器开始计时
}
break
;
case
STATE_START:
if
(
keycode
==
KeyEvent
.
VK_SPACE
)
{
//游戏过程中按下空格则振翅一次,并持续受重力影响
bird
.
birdUp
();
bird
.
birdDown
();
}
break
;
case
STATE_OVER:
if
(
keycode
==
KeyEvent
.
VK_SPACE
)
{
//游戏结束时按下空格,重新开始游戏
resetGame
();
}
break
;
}
}
// 重新开始游戏
private
void
resetGame
()
{
setGameState
(
STATE_READY
);
gameElement
.
reset
();
bird
.
reset
();
}
// 按键松开,更改按键状态标志
public
void
keyReleased
(
KeyEvent
e
)
{
int
keycode
=
e
.
getKeyChar
();
if
(
keycode
==
KeyEvent
.
VK_SPACE
)
{
bird
.
keyReleased
();
}
}
public
void
keyTyped
(
KeyEvent
e
)
{
}
}
// 初始化游戏中的各个对象
private
void
initGame
()
{
background
=
new
GameBackground
();
gameElement
=
new
GameElementLayer
();
foreground
=
new
GameForeground
();
ready
=
new
GameReady
();
bird
=
new
Bird
();
setGameState
(
STATE_READY
);
// 启动用于刷新窗口的线程
new
Thread
(
this
).
start
();
}
// 项目中存在两个线程:系统线程,自定义的线程:调用repaint()。
// 系统线程:屏幕内容的绘制,窗口事件的监听与处理
// 两个线程会抢夺系统资源,可能会出现一次刷新周期所绘制的内容,并没有在一次刷新周期内完成
// (双缓冲)单独定义一张图片,将需要绘制的内容绘制到这张图片,再一次性地将图片绘制到窗口
private
BufferedImage
bufImg
=
new
BufferedImage
(
FRAME_WIDTH
,
FRAME_HEIGHT
,
BufferedImage
.
TYPE_4BYTE_ABGR
);
/**
* 绘制游戏内容 当repaint()方法被调用时,JVM会调用update(),参数g是系统提供的画笔,由系统进行实例化
* 单独启动一个线程,不断地快速调用repaint(),让系统对整个窗口进行重绘
*
*/
public
void
update
(
Graphics
g
)
{
Graphics
bufG
=
bufImg
.
getGraphics
();
// 获得图片画笔
// 使用图片画笔将需要绘制的内容绘制到图片
background
.
draw
(
bufG
,
bird
);
// 背景层
foreground
.
draw
(
bufG
,
bird
);
// 前景层
if
(
gameState
==
STATE_READY
)
{
// 游戏未开始
ready
.
draw
(
bufG
);
bird
.
draw
(
bufG
);
// 鸟
}
else
{
// 游戏结束
gameElement
.
draw
(
bufG
,
bird
);
// 游戏元素层
bird
.
draw
(
bufG
);
// 鸟
}
g
.
drawImage
(
bufImg
,
0
,
0
,
null
);
// 一次性将图片绘制到屏幕上
}
@Override
public
void
run
()
{
while
(
true
)
{
repaint
();
// 通过调用repaint(),让JVM调用update()
try
{
Thread
.
sleep
(
GAME_INTERVAL
);
}
catch
(
InterruptedException
e
)
{
e
.
printStackTrace
();
}
}
}
// 获取、设置游戏状态的方法
public
static
int
getGameState
()
{
return
gameState
;
}
public
static
void
setGameState
(
int
gameState
)
{
GameFrame
.
gameState
=
gameState
;
}
}
\ No newline at end of file
private
static
final
long
serialVersionUID
=
1L
;
// 保持版本的兼容性
private
static
int
gameState
;
// 游戏状态
public
static
final
int
STATE_READY
=
0
;
// 游戏未开始
public
static
final
int
STATE_START
=
1
;
// 游戏开始
public
static
final
int
STATE_OVER
=
2
;
// 游戏结束
private
GameBackground
background
;
// 游戏背景对象
private
GameForeground
foreground
;
// 游戏前景对象
private
Bird
bird
;
// 小鸟对象
private
GameElementLayer
gameElement
;
// 游戏元素对象
private
GameReady
ready
;
// 游戏未开始时对象
// 在构造器中初始化
public
GameFrame
()
{
initFrame
();
// 初始化游戏窗口
setVisible
(
true
);
// 窗口默认为不可见,设置为可见
initGame
();
// 初始化游戏对象
}
// 初始化游戏窗口
private
void
initFrame
()
{
setSize
(
FRAME_WIDTH
,
FRAME_HEIGHT
);
// 设置窗口大小
setTitle
(
GAME_TITLE
);
// 设置窗口标题
setLocation
(
FRAME_X
,
FRAME_Y
);
// 窗口初始位置
setResizable
(
false
);
// 设置窗口大小不可变
// 添加关闭窗口事件(监听窗口发生的事件,派发给参数对象,参数对象调用对应的方法)
addWindowListener
(
new
WindowAdapter
()
{
@Override
public
void
windowClosing
(
WindowEvent
e
)
{
System
.
exit
(
0
);
// 结束程序
}
});
addKeyListener
(
new
BirdKeyListener
());
// 添加按键监听
}
// 用于接收按键事件的对象的内部类
class
BirdKeyListener
implements
KeyListener
{
// 按键按下,根据游戏当前的状态调用不同的方法
public
void
keyPressed
(
KeyEvent
e
)
{
int
keycode
=
e
.
getKeyChar
();
switch
(
gameState
)
{
case
STATE_READY:
if
(
keycode
==
KeyEvent
.
VK_SPACE
)
{
// 游戏启动界面时按下空格,小鸟振翅一次并开始受重力影响
bird
.
birdUp
();
bird
.
birdDown
();
setGameState
(
STATE_START
);
// 游戏状态改变
}
break
;
case
STATE_START:
if
(
keycode
==
KeyEvent
.
VK_SPACE
)
{
//游戏过程中按下空格则振翅一次,并持续受重力影响
bird
.
birdUp
();
bird
.
birdDown
();
}
break
;
case
STATE_OVER:
if
(
keycode
==
KeyEvent
.
VK_SPACE
)
{
//游戏结束时按下空格,重新开始游戏
resetGame
();
}
break
;
}
}
// 重新开始游戏
private
void
resetGame
()
{
setGameState
(
STATE_READY
);
gameElement
.
reset
();
bird
.
reset
();
}
// 按键松开,更改按键状态标志
public
void
keyReleased
(
KeyEvent
e
)
{
int
keycode
=
e
.
getKeyChar
();
if
(
keycode
==
KeyEvent
.
VK_SPACE
)
{
bird
.
keyReleased
();
}
}
public
void
keyTyped
(
KeyEvent
e
)
{
}
}
// 初始化游戏中的各个对象
private
void
initGame
()
{
background
=
new
GameBackground
();
gameElement
=
new
GameElementLayer
();
foreground
=
new
GameForeground
();
ready
=
new
GameReady
();
bird
=
new
Bird
();
setGameState
(
STATE_READY
);
// 启动用于刷新窗口的线程
new
Thread
(
this
).
start
();
}
// 项目中存在两个线程:系统线程,自定义的线程:调用repaint()。
// 系统线程:屏幕内容的绘制,窗口事件的监听与处理
// 两个线程会抢夺系统资源,可能会出现一次刷新周期所绘制的内容,并没有在一次刷新周期内完成
// (双缓冲)单独定义一张图片,将需要绘制的内容绘制到这张图片,再一次性地将图片绘制到窗口
private
final
BufferedImage
bufImg
=
new
BufferedImage
(
FRAME_WIDTH
,
FRAME_HEIGHT
,
BufferedImage
.
TYPE_4BYTE_ABGR
);
/**
* 绘制游戏内容 当repaint()方法被调用时,JVM会调用update(),参数g是系统提供的画笔,由系统进行实例化
* 单独启动一个线程,不断地快速调用repaint(),让系统对整个窗口进行重绘
*/
public
void
update
(
Graphics
g
)
{
Graphics
bufG
=
bufImg
.
getGraphics
();
// 获得图片画笔
// 使用图片画笔将需要绘制的内容绘制到图片
background
.
draw
(
bufG
,
bird
);
// 背景层
foreground
.
draw
(
bufG
,
bird
);
// 前景层
// 鸟
if
(
gameState
==
STATE_READY
)
{
// 游戏未开始
ready
.
draw
(
bufG
);
}
else
{
// 游戏结束
gameElement
.
draw
(
bufG
,
bird
);
// 游戏元素层
}
bird
.
draw
(
bufG
);
// 鸟
g
.
drawImage
(
bufImg
,
0
,
0
,
null
);
// 一次性将图片绘制到屏幕上
}
// TODO: 不建议在while循环中使用sleep
@SuppressWarnings
(
"InfiniteLoopStatement"
)
@Override
public
void
run
()
{
while
(
true
)
{
repaint
();
// 通过调用repaint(),让JVM调用update()
try
{
Thread
.
sleep
(
GAME_INTERVAL
);
}
catch
(
InterruptedException
e
)
{
e
.
printStackTrace
();
}
}
}
public
static
void
setGameState
(
int
gameState
)
{
GameFrame
.
gameState
=
gameState
;
}
}
src/com/bird/main/GameReady.java
View file @
2968bb8e
...
...
@@ -2,9 +2,9 @@ package com.bird.main;
import
java.awt.Graphics
;
import
java.awt.image.BufferedImage
;
import
com.bird.util.Constant
;
import
com.bird.util.GameUtil
;
import
static
com
.
bird
.
util
.
GameUtil
.
drawTitle
;
/**
* 游戏启动界面类
...
...
@@ -14,15 +14,15 @@ import com.bird.util.GameUtil;
*/
public
class
GameReady
{
private
BufferedImage
titleImg
;
private
BufferedImage
noticeImg
;
private
final
BufferedImage
titleImg
;
private
final
BufferedImage
noticeImg
;
private
int
flash
;
// 图像闪烁参数
private
int
flash
=
0
;
// 图像闪烁参数
// 构造器中进行初始化,装载图像资源
public
GameReady
()
{
titleImg
=
GameUtil
.
loadB
U
fferedImage
(
Constant
.
TITLE_IMG_PATH
);
noticeImg
=
GameUtil
.
loadB
U
fferedImage
(
Constant
.
NOTICE_IMG_PATH
);
titleImg
=
GameUtil
.
loadB
u
fferedImage
(
Constant
.
TITLE_IMG_PATH
);
noticeImg
=
GameUtil
.
loadB
u
fferedImage
(
Constant
.
NOTICE_IMG_PATH
);
}
public
void
draw
(
Graphics
g
)
{
...
...
@@ -33,14 +33,10 @@ public class GameReady {
// 使notice的图像闪烁
final
int
COUNT
=
30
;
// 闪烁周期
if
(
flash
++
>
COUNT
)
{
// 计算notice图像的x、y坐标
x
=
Constant
.
FRAME_WIDTH
-
noticeImg
.
getWidth
()
>>
1
;
y
=
Constant
.
FRAME_HEIGHT
/
5
*
3
;
g
.
drawImage
(
noticeImg
,
x
,
y
,
null
);
// 绘制
if
(
flash
==
COUNT
*
2
)
// 重置闪烁参数
if
(
flash
++
>
COUNT
)
drawTitle
(
noticeImg
,
g
);
if
(
flash
==
COUNT
*
2
)
// 重置闪烁参数
flash
=
0
;
}
}
}
src/com/bird/main/Game
Tim
e.java
→
src/com/bird/main/Game
Scor
e.java
View file @
2968bb8e
...
...
@@ -15,35 +15,27 @@ import com.bird.util.MusicUtil;
* @author Kingyu
*
*/
public
class
Game
Tim
e
{
private
static
final
Game
Tim
e
GAME_
TIM
E
=
new
Game
Tim
e
();
public
class
Game
Scor
e
{
private
static
final
Game
Scor
e
GAME_
SCOR
E
=
new
Game
Scor
e
();
private
int
timeState
;
// 计时器的状态
public
static
final
int
STATE_READY
=
0
;
// 计时就绪
public
static
final
int
STATE_START
=
1
;
// 计时开始
public
static
final
int
STATE_OVER
=
2
;
// 计时结束
private
long
startTime
=
0
;
// 开始时间 ms
private
long
endTime
=
0
;
// 开始时间 ms
private
long
score
=
0
;
// 分数
private
long
bestScore
;
// 最高分数
private
GameTime
()
{
timeState
=
STATE_READY
;
private
GameScore
()
{
bestScore
=
-
1
;
try
{
loadBest
Tim
e
();
loadBest
Scor
e
();
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
}
}
public
static
Game
Tim
e
getInstance
()
{
return
GAME_
TIM
E
;
public
static
Game
Scor
e
getInstance
()
{
return
GAME_
SCOR
E
;
}
// 装载最高纪录
private
void
loadBest
Tim
e
()
throws
Exception
{
private
void
loadBest
Scor
e
()
throws
Exception
{
File
file
=
new
File
(
Constant
.
SCORE_FILE_PATH
);
if
(
file
.
exists
())
{
DataInputStream
dis
=
new
DataInputStream
(
new
FileInputStream
(
file
));
...
...
@@ -53,10 +45,10 @@ public class GameTime {
}
// 保存最高纪录
public
void
saveBestScore
(
long
tim
e
)
throws
Exception
{
public
void
saveBestScore
(
long
scor
e
)
throws
Exception
{
File
file
=
new
File
(
Constant
.
SCORE_FILE_PATH
);
DataOutputStream
dos
=
new
DataOutputStream
(
new
FileOutputStream
(
file
));
dos
.
writeLong
(
tim
e
);
dos
.
writeLong
(
scor
e
);
dos
.
close
();
}
...
...
@@ -64,51 +56,20 @@ public class GameTime {
return
bestScore
;
}
public
void
setStartTime
(
long
startTime
)
{
this
.
startTime
=
startTime
;
}
public
void
setOverTime
(
long
endTime
)
{
this
.
endTime
=
endTime
;
public
long
getScore
()
{
return
score
;
}
/**
* 游戏用时,毫秒
*
* @return
*/
public
long
getTime
()
{
if
(
timeState
==
STATE_READY
)
{
return
startTime
;
}
else
if
(
timeState
==
STATE_START
)
{
return
(
System
.
currentTimeMillis
()
-
startTime
);
}
else
{
return
(
endTime
-
startTime
);
public
void
setScore
(
Bird
bird
)
{
if
(!
bird
.
isDead
()){
MusicUtil
.
playScore
();
//每次得分播放音效
score
+=
1
;
//小鸟没死时记分
}
}
//游戏时间转换为秒
public
long
getTimeInSeconds
()
{
return
getTime
()
/
1000
;
}
// 计时器是否就绪
public
boolean
isReadyTiming
()
{
return
timeState
==
STATE_READY
;
}
// 开始计时
public
void
startTiming
()
{
startTime
=
System
.
currentTimeMillis
();
timeState
=
STATE_START
;
}
// 结束计时并判断是否保存记录
public
void
endTiming
()
{
endTime
=
System
.
currentTimeMillis
();
timeState
=
STATE_OVER
;
// 判断本次得分是否为最高分
long
score
=
TimeToScore
();
// 判断是否为最高纪录
public
void
isSaveScore
()
{
long
score
=
getScore
();
if
(
bestScore
<
score
)
bestScore
=
score
;
try
{
...
...
@@ -117,40 +78,79 @@ public class GameTime {
e
.
printStackTrace
();
}
}
private
static
final
int
FIRST_SCORE_TIME
=
6600
;
// 从游戏开始到通过第一根水管的所需时间
private
static
final
int
PER_SCORE_TIME
=
2900
;
// 通过后续每一根水管的间隔的所需时间
//将游戏时间转换为通过水管的数量
public
long
TimeToScore
()
{
long
time
=
getTime
();
long
temp
=
score
;
if
(
time
>=
FIRST_SCORE_TIME
&&
time
<
FIRST_SCORE_TIME
+
PER_SCORE_TIME
)
{
score
=
1
;
//time大于FIRST_SCORE_TIME且未到第二对水管
}
else
if
(
time
>=
FIRST_SCORE_TIME
+
PER_SCORE_TIME
)
{
score
=
(
int
)
(
time
-
FIRST_SCORE_TIME
)
/
PER_SCORE_TIME
+
1
;
}
if
(
score
-
temp
>
0
)
{
MusicUtil
.
playScore
();
//每次得分播放音效
}
return
score
;
}
/**
* 是否正在计时
*
* @return
*/
public
boolean
isTiming
()
{
return
timeState
==
STATE_START
;
}
//重置计时器
//重置计分器
public
void
reset
()
{
timeState
=
STATE_READY
;
startTime
=
0
;
endTime
=
0
;
score
=
0
;
}
// private int timeState; // 计时器的状态
// public static final int STATE_READY = 0; // 计时就绪
// public static final int STATE_START = 1; // 计时开始
// public static final int STATE_OVER = 2; // 计时结束
// 以下为游戏计时的相关方法,因改进了记分方式目前无用
// /**
// * 游戏用时,毫秒
// *
// * @return
// */
// public long getTime() {
// if (timeState == STATE_READY) {
// return startTime;
// } else if (timeState == STATE_START) {
// return (System.currentTimeMillis() - startTime);
// } else {
// return (endTime - startTime);
// }
// }
//
// //游戏时间转换为秒
// public long getTimeInSeconds() {
// return getTime() / 1000;
// }
//
// // 计时器是否就绪
// public boolean isReadyTiming() {
// return timeState == STATE_READY;
// }
//
// // 开始计时
// public void startTiming() {
// startTime = System.currentTimeMillis();
// timeState = STATE_START;
// }
//
// // 结束计时
// public void endTiming() {
// endTime = System.currentTimeMillis();
// timeState = STATE_OVER;
// }
//
// private static final int FIRST_SCORE_TIME = 6600; // 从游戏开始到通过第一根水管的所需时间
// private static final int PER_SCORE_TIME = 2900; // 通过后续每一根水管的间隔的所需时间
//
// //将游戏时间转换为通过水管的数量
// public long TimeToScore() {
// long time = getTime();
// long temp = score;
// if (time >= FIRST_SCORE_TIME && time < FIRST_SCORE_TIME + PER_SCORE_TIME) {
// score = 1; //time大于FIRST_SCORE_TIME且未到第二对水管
// } else if (time >= FIRST_SCORE_TIME + PER_SCORE_TIME) {
// score = (int) (time - FIRST_SCORE_TIME) / PER_SCORE_TIME + 1;
// }
// if (score - temp > 0) {
// MusicUtil.playScore(); //每次得分播放音效
// }
// return score;
// }
//
// /**
// * 是否正在计时
// *
// * @return
// */
// public boolean isTiming() {
// return timeState == STATE_START;
// }
}
src/com/bird/main/MovingPipe.java
View file @
2968bb8e
...
...
@@ -27,11 +27,11 @@ public class MovingPipe extends Pipe {
/**
* 设置水管参数
*
* @param x
* @param y
* @param height
* @param type
* @param visible
* @param x
:x坐标
* @param y
:y坐标
* @param height
:水管高度
* @param type
:水管类型
* @param visible
:水管可见性
*/
public
void
setAttribute
(
int
x
,
int
y
,
int
height
,
int
type
,
boolean
visible
)
{
this
.
x
=
x
;
...
...
src/com/bird/main/Pipe.java
View file @
2968bb8e
...
...
@@ -9,203 +9,200 @@ import com.bird.util.GameUtil;
/**
* 水管类
*
* @author Kingyu
*
* @author Kingyu
*/
public
class
Pipe
{
static
BufferedImage
[]
imgs
;
// 水管的图片,static保证图片只加载一次
static
{
// 静态代码块,类加载的时候,初始化图片
final
int
PIPE_IMAGE_COUNT
=
3
;
imgs
=
new
BufferedImage
[
PIPE_IMAGE_COUNT
];
for
(
int
i
=
0
;
i
<
PIPE_IMAGE_COUNT
;
i
++)
{
imgs
[
i
]
=
GameUtil
.
loadBUfferedImage
(
Constant
.
PIPE_IMG_PATH
[
i
]);
}
}
// 所有水管的宽高
public
static
final
int
PIPE_WIDTH
=
imgs
[
0
].
getWidth
();
public
static
final
int
PIPE_HEIGHT
=
imgs
[
0
].
getHeight
();
public
static
final
int
PIPE_HEAD_WIDTH
=
imgs
[
1
].
getWidth
();
public
static
final
int
PIPE_HEAD_HEIGHT
=
imgs
[
1
].
getHeight
();
int
x
,
y
;
// 水管的坐标,相对于元素层
int
width
,
height
;
// 水管的宽,高
boolean
visible
;
// 水管可见状态,true为可见,false表示可归还至对象池
// 水管的类型
int
type
;
public
static
final
int
TYPE_TOP_NORMAL
=
0
;
public
static
final
int
TYPE_TOP_HARD
=
1
;
public
static
final
int
TYPE_BOTTOM_NORMAL
=
2
;
public
static
final
int
TYPE_BOTTOM_HARD
=
3
;
public
static
final
int
TYPE_HOVER_NORMAL
=
4
;
public
static
final
int
TYPE_HOVER_HARD
=
5
;
// 水管的速度
public
static
final
int
MIN_SPEED
=
1
;
public
static
final
int
MAX_SPEED
=
2
;
int
speed
;
Rectangle
pipeRect
;
// 水管的碰撞矩形
// 构造器
public
Pipe
()
{
this
.
speed
=
MIN_SPEED
;
this
.
width
=
PIPE_WIDTH
;
pipeRect
=
new
Rectangle
();
pipeRect
.
width
=
PIPE_WIDTH
;
}
/**
* 设置水管参数
*
* @param x
* @param y
* @param height
* @param type
* @param visible
*/
public
void
setAttribute
(
int
x
,
int
y
,
int
height
,
int
type
,
boolean
visible
)
{
this
.
x
=
x
;
this
.
y
=
y
;
this
.
height
=
height
;
this
.
type
=
type
;
this
.
visible
=
visible
;
setRectangle
(
this
.
x
,
this
.
y
,
this
.
height
);
}
/**
* 设置碰撞矩形参数
*
* @param x
* @param y
* @param height
*/
public
void
setRectangle
(
int
x
,
int
y
,
int
height
)
{
pipeRect
.
x
=
x
;
pipeRect
.
y
=
y
;
pipeRect
.
height
=
height
;
}
// 判断水管是否位于窗口
public
boolean
isVisible
()
{
return
visible
;
}
// 绘制方法
public
void
draw
(
Graphics
g
,
Bird
bird
)
{
switch
(
type
)
{
case
TYPE_TOP_NORMAL:
drawTopNormal
(
g
);
break
;
case
TYPE_BOTTOM_NORMAL:
drawBottomNormal
(
g
);
break
;
case
TYPE_HOVER_NORMAL:
drawHoverNormal
(
g
);
break
;
}
// //绘制碰撞矩形
// g.setColor(Color.black);
// g.drawRect((int) pipeRect.getX(), (int) pipeRect.getY(), (int) pipeRect.getWidth(), (int) pipeRect.getHeight());
//鸟死后水管停止移动
if
(
bird
.
isDead
())
{
return
;
}
pipeLogic
();
}
// 绘制从上往下的普通水管
private
void
drawTopNormal
(
Graphics
g
)
{
// 拼接的个数
int
count
=
(
height
-
PIPE_HEAD_HEIGHT
)
/
PIPE_HEIGHT
+
1
;
// 取整+1
// 绘制水管的主体
for
(
int
i
=
0
;
i
<
count
;
i
++)
{
g
.
drawImage
(
imgs
[
0
],
x
,
y
+
i
*
PIPE_HEIGHT
,
null
);
}
// 绘制水管的顶部
g
.
drawImage
(
imgs
[
1
],
x
-
((
PIPE_HEAD_WIDTH
-
width
)
>>
1
),
height
-
Constant
.
TOP_PIPE_LENGTHENING
-
PIPE_HEAD_HEIGHT
,
null
);
// 水管头部与水管主体的宽度不同,x坐标需要处理
}
// 绘制从下往上的普通水管
private
void
drawBottomNormal
(
Graphics
g
)
{
// 拼接的个数
int
count
=
(
height
-
PIPE_HEAD_HEIGHT
-
Constant
.
GROUND_HEIGHT
)
/
PIPE_HEIGHT
+
1
;
// 绘制水管的主体
for
(
int
i
=
0
;
i
<
count
;
i
++)
{
g
.
drawImage
(
imgs
[
0
],
x
,
Constant
.
FRAME_HEIGHT
-
PIPE_HEIGHT
-
Constant
.
GROUND_HEIGHT
-
i
*
PIPE_HEIGHT
,
null
);
}
// 绘制水管的顶部
g
.
drawImage
(
imgs
[
2
],
x
-
((
PIPE_HEAD_WIDTH
-
width
)
>>
1
),
Constant
.
FRAME_HEIGHT
-
height
,
null
);
}
// 绘制悬浮的普通水管
private
void
drawHoverNormal
(
Graphics
g
)
{
// 拼接的个数
int
count
=
(
height
-
2
*
PIPE_HEAD_HEIGHT
)
/
PIPE_HEIGHT
+
1
;
// 绘制水管的上顶部
g
.
drawImage
(
imgs
[
2
],
x
-
((
PIPE_HEAD_WIDTH
-
width
)
>>
1
),
y
,
null
);
// 绘制水管的主体
for
(
int
i
=
0
;
i
<
count
;
i
++)
{
g
.
drawImage
(
imgs
[
0
],
x
,
y
+
i
*
PIPE_HEIGHT
+
PIPE_HEAD_HEIGHT
,
null
);
}
// 绘制水管的下底部
int
y
=
this
.
y
+
height
-
PIPE_HEAD_HEIGHT
;
g
.
drawImage
(
imgs
[
1
],
x
-
((
PIPE_HEAD_WIDTH
-
width
)
>>
1
),
y
,
null
);
}
/**
* 普通水管的运动逻辑
*/
private
void
pipeLogic
()
{
x
-=
speed
;
pipeRect
.
x
-=
speed
;
if
(
x
<
-
1
*
PIPE_HEAD_WIDTH
)
{
// 水管完全离开了窗口
visible
=
false
;
}
}
/**
* 判断当前水管是否完全出现在窗口中
*
* @return 若完全出现则返回true,否则返回false
*/
public
boolean
isInFrame
()
{
return
x
+
width
<
Constant
.
FRAME_WIDTH
;
}
// 各参数的写入与获取
public
void
setX
(
int
x
)
{
this
.
x
=
x
;
}
public
void
setY
(
int
y
)
{
this
.
y
=
y
;
}
public
void
setType
(
int
type
)
{
this
.
type
=
type
;
}
public
void
setHeight
(
int
height
)
{
this
.
height
=
height
;
}
public
void
setVisible
(
boolean
visible
)
{
this
.
visible
=
visible
;
}
public
int
getX
()
{
return
x
;
}
// 获取水管的碰撞矩形
public
Rectangle
getPipeRect
()
{
return
pipeRect
;
}
}
\ No newline at end of file
static
BufferedImage
[]
imgs
;
// 水管的图片,static保证图片只加载一次
static
{
// 静态代码块,类加载的时候,初始化图片
final
int
PIPE_IMAGE_COUNT
=
3
;
imgs
=
new
BufferedImage
[
PIPE_IMAGE_COUNT
];
for
(
int
i
=
0
;
i
<
PIPE_IMAGE_COUNT
;
i
++)
{
imgs
[
i
]
=
GameUtil
.
loadBufferedImage
(
Constant
.
PIPE_IMG_PATH
[
i
]);
}
}
// 所有水管的宽高
public
static
final
int
PIPE_WIDTH
=
imgs
[
0
].
getWidth
();
public
static
final
int
PIPE_HEIGHT
=
imgs
[
0
].
getHeight
();
public
static
final
int
PIPE_HEAD_WIDTH
=
imgs
[
1
].
getWidth
();
public
static
final
int
PIPE_HEAD_HEIGHT
=
imgs
[
1
].
getHeight
();
int
x
,
y
;
// 水管的坐标,相对于元素层
int
width
,
height
;
// 水管的宽,高
boolean
visible
;
// 水管可见状态,true为可见,false表示可归还至对象池
// 水管的类型
int
type
;
public
static
final
int
TYPE_TOP_NORMAL
=
0
;
public
static
final
int
TYPE_TOP_HARD
=
1
;
public
static
final
int
TYPE_BOTTOM_NORMAL
=
2
;
public
static
final
int
TYPE_BOTTOM_HARD
=
3
;
public
static
final
int
TYPE_HOVER_NORMAL
=
4
;
public
static
final
int
TYPE_HOVER_HARD
=
5
;
// 水管的速度
public
static
final
int
SPEED
=
1
;
int
speed
;
Rectangle
pipeRect
;
// 水管的碰撞矩形
// 构造器
public
Pipe
()
{
this
.
speed
=
SPEED
;
this
.
width
=
PIPE_WIDTH
;
pipeRect
=
new
Rectangle
();
pipeRect
.
width
=
PIPE_WIDTH
;
}
/**
* 设置水管参数
*
* @param x:x坐标
* @param y:y坐标
* @param height:水管高度
* @param type:水管类型
* @param visible:水管可见性
*/
public
void
setAttribute
(
int
x
,
int
y
,
int
height
,
int
type
,
boolean
visible
)
{
this
.
x
=
x
;
this
.
y
=
y
;
this
.
height
=
height
;
this
.
type
=
type
;
this
.
visible
=
visible
;
setRectangle
(
this
.
x
,
this
.
y
,
this
.
height
);
}
/**
* 设置碰撞矩形参数
*/
public
void
setRectangle
(
int
x
,
int
y
,
int
height
)
{
pipeRect
.
x
=
x
;
pipeRect
.
y
=
y
;
pipeRect
.
height
=
height
;
}
// 判断水管是否位于窗口
public
boolean
isVisible
()
{
return
visible
;
}
// 绘制方法
public
void
draw
(
Graphics
g
,
Bird
bird
)
{
switch
(
type
)
{
case
TYPE_TOP_NORMAL:
drawTopNormal
(
g
);
break
;
case
TYPE_BOTTOM_NORMAL:
drawBottomNormal
(
g
);
break
;
case
TYPE_HOVER_NORMAL:
drawHoverNormal
(
g
);
break
;
}
// //绘制碰撞矩形
// g.setColor(Color.black);
// g.drawRect((int) pipeRect.getX(), (int) pipeRect.getY(), (int) pipeRect.getWidth(), (int) pipeRect.getHeight());
//鸟死后水管停止移动
if
(
bird
.
isDead
())
{
return
;
}
pipeLogic
();
}
// 绘制从上往下的普通水管
private
void
drawTopNormal
(
Graphics
g
)
{
// 拼接的个数
int
count
=
(
height
-
PIPE_HEAD_HEIGHT
)
/
PIPE_HEIGHT
+
1
;
// 取整+1
// 绘制水管的主体
for
(
int
i
=
0
;
i
<
count
;
i
++)
{
g
.
drawImage
(
imgs
[
0
],
x
,
y
+
i
*
PIPE_HEIGHT
,
null
);
}
// 绘制水管的顶部
g
.
drawImage
(
imgs
[
1
],
x
-
((
PIPE_HEAD_WIDTH
-
width
)
>>
1
),
height
-
Constant
.
TOP_PIPE_LENGTHENING
-
PIPE_HEAD_HEIGHT
,
null
);
// 水管头部与水管主体的宽度不同,x坐标需要处理
}
// 绘制从下往上的普通水管
private
void
drawBottomNormal
(
Graphics
g
)
{
// 拼接的个数
int
count
=
(
height
-
PIPE_HEAD_HEIGHT
-
Constant
.
GROUND_HEIGHT
)
/
PIPE_HEIGHT
+
1
;
// 绘制水管的主体
for
(
int
i
=
0
;
i
<
count
;
i
++)
{
g
.
drawImage
(
imgs
[
0
],
x
,
Constant
.
FRAME_HEIGHT
-
PIPE_HEIGHT
-
Constant
.
GROUND_HEIGHT
-
i
*
PIPE_HEIGHT
,
null
);
}
// 绘制水管的顶部
g
.
drawImage
(
imgs
[
2
],
x
-
((
PIPE_HEAD_WIDTH
-
width
)
>>
1
),
Constant
.
FRAME_HEIGHT
-
height
,
null
);
}
// 绘制悬浮的普通水管
private
void
drawHoverNormal
(
Graphics
g
)
{
// 拼接的个数
int
count
=
(
height
-
2
*
PIPE_HEAD_HEIGHT
)
/
PIPE_HEIGHT
+
1
;
// 绘制水管的上顶部
g
.
drawImage
(
imgs
[
2
],
x
-
((
PIPE_HEAD_WIDTH
-
width
)
>>
1
),
y
,
null
);
// 绘制水管的主体
for
(
int
i
=
0
;
i
<
count
;
i
++)
{
g
.
drawImage
(
imgs
[
0
],
x
,
y
+
i
*
PIPE_HEIGHT
+
PIPE_HEAD_HEIGHT
,
null
);
}
// 绘制水管的下底部
int
y
=
this
.
y
+
height
-
PIPE_HEAD_HEIGHT
;
g
.
drawImage
(
imgs
[
1
],
x
-
((
PIPE_HEAD_WIDTH
-
width
)
>>
1
),
y
,
null
);
}
/**
* 普通水管的运动逻辑
*/
private
void
pipeLogic
()
{
x
-=
speed
;
pipeRect
.
x
-=
speed
;
if
(
x
<
-
1
*
PIPE_HEAD_WIDTH
)
{
// 水管完全离开了窗口
visible
=
false
;
}
}
/**
* 判断当前水管是否完全出现在窗口中
*
* @return 若完全出现则返回true,否则返回false
*/
public
boolean
isInFrame
()
{
return
x
+
width
<
Constant
.
FRAME_WIDTH
;
}
// 获取水管的x坐标
public
int
getX
()
{
return
x
;
}
// 获取水管的碰撞矩形
public
Rectangle
getPipeRect
()
{
return
pipeRect
;
}
// 各参数的写入与获取
// public void setX(int x) {
// this.x = x;
// }
//
// public void setY(int y) {
// this.y = y;
// }
//
// public void setType(int type) {
// this.type = type;
// }
//
// public void setHeight(int height) {
// this.height = height;
// }
//
// public void setVisible(boolean visible) {
// this.visible = visible;
// }
}
src/com/bird/main/PipePool.java
View file @
2968bb8e
...
...
@@ -6,25 +6,23 @@ import java.util.List;
import
com.bird.util.Constant
;
/**
* 为了避免反复地创建和销毁对象,使用对象池来提前创建好一些对象,使用时从对象池中获得,使用完
毕
归还
* 为了避免反复地创建和销毁对象,使用对象池来提前创建好一些对象,使用时从对象池中获得,使用完
后
归还
*
* @author Kingyu
*
*/
public
class
PipePool
{
private
static
List
<
Pipe
>
pool
=
new
ArrayList
<
Pipe
>();
// 池中对象的容器
private
static
List
<
MovingPipe
>
movingPool
=
new
ArrayList
<
MovingPipe
>();
// 池中对象的容器
private
static
final
List
<
Pipe
>
pool
=
new
ArrayList
<>();
// 池中对象的容器
private
static
final
List
<
MovingPipe
>
movingPool
=
new
ArrayList
<>();
// 池中对象的容器
public
static
final
int
INIT_PIPE_COUNT
=
(
Constant
.
FRAME_WIDTH
/
(
Pipe
.
PIPE_HEAD_WIDTH
+
GameElementLayer
.
HORIZONTAL_INTERVAL
)
+
2
)
*
2
;
// 根据窗口宽度算得对象池中对象的初始个数
public
static
final
int
MAX_PIPE_COUNT
=
30
;
// 对象池中对象的最大个数,自行定义
// 初始化水管容器
// 初始化水管容器
,初始化水管的数量的计算方式见常量类中的注释
static
{
for
(
int
i
=
0
;
i
<
INIT_PIPE_COUNT
;
i
++)
{
for
(
int
i
=
0
;
i
<
Constant
.
FULL_PIPE
;
i
++)
{
pool
.
add
(
new
Pipe
());
}
for
(
int
i
=
0
;
i
<
INIT_PIPE_COUNT
;
i
++)
{
for
(
int
i
=
0
;
i
<
Constant
.
FULL_PIPE
;
i
++)
{
movingPool
.
add
(
new
MovingPipe
());
}
}
...
...
@@ -54,8 +52,6 @@ public class PipePool {
/**
* 归还对象给容器
*
* @param pipe
*/
public
static
void
giveBack
(
Pipe
pipe
)
{
//判断类的类型
...
...
src/com/bird/util/Constant.java
View file @
2968bb8e
...
...
@@ -3,9 +3,12 @@ package com.bird.util;
import
java.awt.Color
;
import
java.awt.Font
;
import
com.bird.main.GameElementLayer
;
import
com.bird.main.Pipe
;
/**
* 常量类
*
*
* @author Kingyu 后续优化可写入数据库或文件中,便于修改
*/
...
...
@@ -18,15 +21,13 @@ public class Constant {
public
static
final
String
GAME_TITLE
=
"Flappy Bird written by Kingyu"
;
// 窗口位置
public
static
final
int
FRAME_X
=
12
00
;
public
static
final
int
FRAME_X
=
6
00
;
public
static
final
int
FRAME_Y
=
100
;
// 图像资源路径
public
static
final
String
BG_IMG_PATH
=
"resources/img/background.png"
;
// 背景图片
public
static
final
int
HOVER_MOVING_SCORE
=
4
;
//出现移动管道的分数
// 小鸟图片
// 小鸟图片
public
static
final
String
[][]
BIRDS_IMG_PATH
=
{
{
"resources/img/0.png"
,
"resources/img/1.png"
,
"resources/img/2.png"
,
"resources/img/3.png"
,
"resources/img/4.png"
,
"resources/img/5.png"
,
"resources/img/6.png"
,
"resources/img/7.png"
},
...
...
@@ -72,9 +73,11 @@ public class Constant {
public
static
final
int
CLOUD_BORN_PERCENT
=
6
;
// 云朵生成的概率,单位为百分比
public
static
final
int
CLOUD_IMAGE_COUNT
=
2
;
// 云朵图片的个数
public
static
final
int
MAX_CLOUD_COUNT
=
7
;
// 云朵的最大数量
public
static
final
int
CLOUD_DIRCHANGE
=
50
;
// 云朵随机改变方向的概率,越大表示概率越小
public
static
final
Font
TIM
E_FONT
=
new
Font
(
"华文琥珀"
,
Font
.
BOLD
,
32
);
// 字体
public
static
final
Font
CURRENT_SCOR
E_FONT
=
new
Font
(
"华文琥珀"
,
Font
.
BOLD
,
32
);
// 字体
public
static
final
Font
SCORE_FONT
=
new
Font
(
"华文琥珀"
,
Font
.
BOLD
,
24
);
// 字体
}
\ No newline at end of file
// 窗口可容纳的水管数量+2, 由窗口宽度、水管宽度、水管间距算得
public
static
final
int
FULL_PIPE
=
(
Constant
.
FRAME_WIDTH
/
(
Pipe
.
PIPE_HEAD_WIDTH
+
GameElementLayer
.
HORIZONTAL_INTERVAL
)
+
2
)
*
2
;
}
src/com/bird/util/GameUtil.java
View file @
2968bb8e
package
com.bird.util
;
import
java.awt.
Font
;
import
java.awt.
*
;
import
java.awt.image.BufferedImage
;
import
java.io.FileInputStream
;
import
java.io.IOException
;
...
...
@@ -11,76 +11,81 @@ import javax.imageio.ImageIO;
/**
* 工具类,游戏中用到的工具都在此类
*
* @author Kingyu
*
* @author Kingyu
*/
public
class
GameUtil
{
private
GameUtil
()
{
}
// 私有化,防止其他类实例化此类
private
GameUtil
()
{
}
// 私有化,防止其他类实例化此类
/**
* 装载图片的方法
*
* @param imgPath 图片路径
* @return 图片资源
*/
public
static
BufferedImage
loadBufferedImage
(
String
imgPath
)
{
try
{
return
ImageIO
.
read
(
new
FileInputStream
(
imgPath
));
}
catch
(
IOException
e
)
{
e
.
printStackTrace
();
}
return
null
;
}
/**
* 判断任意概率的概率性事件是否发生
*
* @param numerator 分子,不小于0的值
* @param denominator 分母,不小于0的值
* @return 概率性事件发生返回true,否则返回false
*/
public
static
boolean
isInProbability
(
int
numerator
,
int
denominator
)
throws
Exception
{
// 分子分母不小于0
if
(
numerator
<=
0
||
denominator
<=
0
)
{
throw
new
Exception
(
"传入了非法的参数"
);
}
//分子大于分母,一定发生
if
(
numerator
>=
denominator
)
{
return
true
;
}
return
getRandomNumber
(
1
,
denominator
+
1
)
<=
numerator
;
}
/**
* 返回指定区间的一个随机数
*
* @param min 区间最小值,包含
* @param max 区间最大值,不包含
* @return 该区间的随机数
*/
public
static
int
getRandomNumber
(
int
min
,
int
max
)
{
return
(
int
)
(
Math
.
random
()
*
(
max
-
min
)
+
min
);
}
/**
* 装载图片的方法
*
* @param imgPath 图片路径
* @return
*/
public
static
BufferedImage
loadBUfferedImage
(
String
imgPath
)
{
try
{
return
ImageIO
.
read
(
new
FileInputStream
(
imgPath
));
}
catch
(
IOException
e
)
{
e
.
printStackTrace
();
}
return
null
;
}
/**
* 获得指定字符串在指定字体的宽高
*/
public
static
int
getStringWidth
(
Font
font
,
String
str
)
{
AffineTransform
affinetransform
=
new
AffineTransform
();
FontRenderContext
frc
=
new
FontRenderContext
(
affinetransform
,
true
,
true
);
return
(
int
)
(
font
.
getStringBounds
(
str
,
frc
).
getWidth
());
}
/**
* 判断任意概率的概率性事件是否发生
*
* @param numerator 分子,不小于0的值
* @param denominator 分母,不小于0的值
* @return 概率性事件发生返回true,否则返回false
*/
public
static
boolean
isInProbability
(
int
numerator
,
int
denominator
)
throws
Exception
{
// 分子分母不小于0
if
(
numerator
<=
0
||
denominator
<=
0
)
{
throw
new
Exception
(
"传入了非法的参数"
);
}
//分子大于分母,一定发生
if
(
numerator
>=
denominator
)
{
return
true
;
}
public
static
int
getStringHeight
(
Font
font
,
String
str
)
{
AffineTransform
affinetransform
=
new
AffineTransform
();
FontRenderContext
frc
=
new
FontRenderContext
(
affinetransform
,
true
,
true
);
return
(
int
)
(
font
.
getStringBounds
(
str
,
frc
).
getHeight
());
}
return
getRandomNumber
(
1
,
denominator
+
1
)
<=
numerator
;
}
/**
* 返回指定区间的一个随机数
*
* @param min 区间最小值,包含
* @param max 区间最大值,不包含
* @return 该区间的随机数
*/
public
static
int
getRandomNumber
(
int
min
,
int
max
)
{
return
(
int
)
(
Math
.
random
()
*
(
max
-
min
)
+
min
);
}
/**
* 获得指定字符串在指定字体的宽高
*/
public
static
int
getStringWidth
(
Font
font
,
String
str
)
{
AffineTransform
affinetransform
=
new
AffineTransform
();
FontRenderContext
frc
=
new
FontRenderContext
(
affinetransform
,
true
,
true
);
int
textHeight
=
(
int
)(
font
.
getStringBounds
(
str
,
frc
).
getWidth
());
return
textHeight
;
}
// 于屏幕x轴中央、y轴3/5处绘制图像
public
static
void
drawTitle
(
BufferedImage
image
,
Graphics
g
)
{
int
x
=
Constant
.
FRAME_WIDTH
-
image
.
getWidth
()
>>
1
;
int
y
=
Constant
.
FRAME_HEIGHT
/
5
*
3
;
g
.
drawImage
(
image
,
x
,
y
,
null
);
}
public
static
int
getStringHeight
(
Font
font
,
String
str
)
{
AffineTransform
affinetransform
=
new
AffineTransform
();
FontRenderContext
frc
=
new
FontRenderContext
(
affinetransform
,
true
,
true
);
int
textHeight
=
(
int
)(
font
.
getStringBounds
(
str
,
frc
).
getHeight
());
return
textHeight
;
}
}
src/com/bird/util/MusicUtil.java
View file @
2968bb8e
package
com.bird.util
;
import
java.io.FileInputStream
;
import
java.io.FileNotFoundException
;
import
java.io.IOException
;
import
java.io.InputStream
;
...
...
@@ -10,51 +9,44 @@ import sun.audio.AudioStream;
/**
* 音乐工具类
*
* @author Kingyu
wav音频:JDK提供的类可直接解码 mp3音频:JDK没有提供支持,需要使用第三方的工具包
*
*
* @author Kingyu
*
wav音频:JDK提供的类可直接解码 mp3音频:JDK没有提供支持,需要使用第三方的工具包
*/
public
class
MusicUtil
{
private
static
AudioStream
fly
;
private
static
AudioStream
crash
;
private
static
AudioStream
score
;
private
static
InputStream
flyIn
;
private
static
InputStream
crashIn
;
private
static
InputStream
scoreIn
;
//wav播放
public
static
void
playFly
()
{
try
{
// create an audiostream from the inputstream
flyIn
=
new
FileInputStream
(
"resources/wav/fly.wav"
);
fly
=
new
AudioStream
(
flyIn
);
}
catch
(
FileNotFoundException
fnfe
)
{
}
catch
(
IOException
ioe
)
{
}
AudioPlayer
.
player
.
start
(
fly
);
}
public
static
void
playCrash
()
{
try
{
// create an audiostream from the inputstream
crashIn
=
new
FileInputStream
(
"resources/wav/crash.wav"
);
crash
=
new
AudioStream
(
crashIn
);
}
catch
(
FileNotFoundException
fnfe
)
{
}
catch
(
IOException
ioe
)
{
}
AudioPlayer
.
player
.
start
(
crash
);
}
public
static
void
playScore
()
{
try
{
// create an audiostream from the inputstream
scoreIn
=
new
FileInputStream
(
"resources/wav/score.wav"
);
score
=
new
AudioStream
(
scoreIn
);
}
catch
(
FileNotFoundException
fnfe
)
{
}
catch
(
IOException
ioe
)
{
}
AudioPlayer
.
player
.
start
(
score
);
}
private
static
AudioStream
fly
;
private
static
AudioStream
crash
;
private
static
AudioStream
score
;
// wav播放
public
static
void
playFly
()
{
try
{
// create an AudioStream from the InputStream
InputStream
flyIn
=
new
FileInputStream
(
"resources/wav/fly.wav"
);
fly
=
new
AudioStream
(
flyIn
);
}
catch
(
IOException
ignored
)
{
}
AudioPlayer
.
player
.
start
(
fly
);
}
public
static
void
playCrash
()
{
try
{
// create an AudioStream from the InputStream
InputStream
crashIn
=
new
FileInputStream
(
"resources/wav/crash.wav"
);
crash
=
new
AudioStream
(
crashIn
);
}
catch
(
IOException
ignored
)
{
}
AudioPlayer
.
player
.
start
(
crash
);
}
public
static
void
playScore
()
{
try
{
// create an AudioStream from the InputStream
InputStream
scoreIn
=
new
FileInputStream
(
"resources/wav/score.wav"
);
score
=
new
AudioStream
(
scoreIn
);
}
catch
(
IOException
ignored
)
{
}
AudioPlayer
.
player
.
start
(
score
);
}
}
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment