飞翔的小鸟 小游戏 可以作为 java入门阶段的收尾作品 ;
需要掌握 面向对象的使用以及了解 多线程,IO流,异常处理,一些java基础等相关知识。
(1)先让窗口显示出来,然后绘制 游戏的背景
(2)让小鸟显示在屏幕中,并且可以上下飞
(3)屏幕出现闪烁,解决闪烁问题 : 用双缓冲,就是将内容绘制到 一张图片上,然后再显示出来
(4)让障碍物显示出来,并且让障碍物可以移动起来
(5)碰撞检测
(6)绘制刚开始的页面和死亡后的页面
(7)让 障碍物 动起来
(1)app 包 :(游戏启动类)
GameApp 类 作为 游戏启动类 。
(2)main 包 : (游戏主要类)
① Bird 类
② Barrier 类
③ BarrierPool 类
④ GameBackGround 类
⑤ GameBarrierLayer 类
⑥ GameFrame 类
⑦ GameReady 类
⑧ GameTime 类
(3)util 包 :(游戏工具类)
① Constant 类
② Gameutil 类
③ MusicUtil 类
import main.GameFrame;
public class GameApp {
public static void main(String[] args) {
new GameFrame();
}
}
package main;
import util.Constant;
import util.GameUtil;
import util.MusicUtil;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
public class Bird {
// 图片的张数
public static final int IMG_COUNT=8;
private Image[] imgs;
// 将 计时 的 方法 引入进来
private GameTime gameTime;
// 定义 记分牌 和 结束 时候的 图片
private BufferedImage over;
private BufferedImage score;
// 定义 一下 小鸟 位置 属性,即起始位置
private static int x;
private static int y;
// 定义 小鸟 状态的变量
private int state = STATE_NORMAL;
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_DIE = 3;
public static final int STATE_DIED = 4;
// 定义小鸟在 Y 轴 上的速度
private int dealtY;
// 小鸟 向上 飞的 最大 速度
public static final int MAX_UP_DEALTY=20;
// 小鸟 向下 飞的 最大 速度
public static final int MAX_DOWN_DEALTY=15;
public Rectangle getRect() {
return rect;
}
// 给 小鸟 添加 小框 做碰撞测试
private Rectangle rect; // 设置 矩形 特定的 方法
public static final int RECT_DESC = 2; // 让小鸟矩形框 在原来的基础上 再减少 2, 让矩形框 再 小一点
// 对 小鸟 照片进行 初始化
public Bird(){
imgs=new Image [IMG_COUNT];
gameTime=GameTime.getInstance();
for (int i=0;i> 1 ;
this.y=Constant.FRAME_HEIGHT >> 1 ;
// 给 鸟 加上 小框框
int w = imgs[0].getWidth(null);
int h = imgs[0].getHeight(null);
int x=this.x-w/2;
int y=this.y-h/2;
rect = new Rectangle(x+RECT_DESC,y+RECT_DESC,w-RECT_DESC*2,h-RECT_DESC*2);
// 初始时候的 x坐标 y坐标 宽度 高度
}
// 画 小鸟 此时的 状态的
public void draw(Graphics g){
flyLogic();
if (gameTime.getSecondTime() < 10){
int index=state >STATE_DIE ?STATE_DIE :state; // 判断 小鸟 现在是 怎样的 状态
int halfImgW = imgs[index].getWidth(null) >> 1 ;
int halfImgH = imgs[index].getHeight(null) >> 1 ;
g.drawImage(imgs[index],x-halfImgW,y-halfImgH,null);
}else{
int index=state >STATE_DIE ?STATE_DIE :state; // 判断 小鸟 现在是 怎样的 状态
int halfImgW = imgs[index].getWidth(null) >> 1 ;
int halfImgH = imgs[index].getHeight(null) >> 1 ;
g.drawImage(imgs[index+4],x-halfImgW,y-halfImgH,null);
}
// 做碰撞测试用的,先 把鸟 加上 小框框
// g.setColor(Color.BLACK);
// g.drawRect( (int)rect.getX(), (int)rect.getY(), (int)rect.getWidth(), (int)rect.getHeight());
// 开始 绘制 游戏结束 和 记分牌
if (state == STATE_DIED){
drawGameOver(g);
}else{ // 此时 小鸟 还没有死亡,需要 绘制 计时器
drawTime(g);
}
}
// 绘制 游戏 结束 时候 的画面 的 方法
private void drawGameOver(Graphics g){
// 绘制 over 的 牌子
int x =Constant.FRAME_WIDTH-over.getWidth() >>1 ;
int y =Constant.FRAME_HEIGHT/3;
g.drawImage(over,x,y,null);
// 绘制 score 的牌子
x=Constant.FRAME_WIDTH-score.getWidth() >>1;
y=Constant.FRAME_HEIGHT/2;
g.drawImage(score,x,y,null);
// 将 最终 的 分数 绘制 在 记分牌上
g.setFont(Constant.TIME_FONT); // 设置字体属性
g.setColor(Color.WHITE); // 设置 颜色
g.drawString(Long.toString(gameTime.getSecondTime()),480,500); // 这里的 x 和 y 是 分数 最终 的显示 位置
}
// 绘制 游戏 上方 的 计时器
private void drawTime(Graphics g){
// 显示 小鸟的飞行时间
g.setFont(Constant.TIME_FONT);
g.setColor(Color.WHITE);
g.drawString(Long.toString(gameTime.getSecondTime()),500,88);
}
// 按空格 调用 这个方法 fly()和down() 主要作用是 改变小鸟上下飞的速度
public void fly(){
// 状态 只 改变一次
if (state == STATE_UP || state == STATE_DIE || state == STATE_DIED) {
return;
}
// 开始 计时
if(gameTime.isReady()){
// MusicUtil.playFly(); // 调用 音乐
gameTime.startTiming();
}
// MusicUtil.tiao();
state=STATE_UP;
dealtY=0;
}
public void down(){
if (state == STATE_DOWN || state == STATE_DIE || state==STATE_DIED)
return;
state=STATE_DOWN;
dealtY=0;
}
// 改变 小鸟 在 y 轴 上的 位移
private void flyLogic (){
switch (state){
case STATE_NORMAL : break;
case STATE_UP :
dealtY+=3;
if (dealtY > MAX_UP_DEALTY){
dealtY = MAX_UP_DEALTY;
}
y -= dealtY;
rect.y -= dealtY;
// 撞上 游戏上侧 则死亡
if (y < (imgs[state].getHeight(null) >> 1)+ Constant.TOP_BAR_HEIGHT){
die();
}
break;
case STATE_DOWN :
dealtY+=2;
if (dealtY > MAX_DOWN_DEALTY){
dealtY = MAX_DOWN_DEALTY;
}
y+=dealtY;
rect.y += dealtY;
// 撞上 游戏下侧 则死亡
if (y > Constant.FRAME_HEIGHT-(imgs[state].getHeight(null) >> 1)){
die();
}
break;
case STATE_DIE : // 死亡 下路 过程
dealtY++;
if (dealtY > MAX_DOWN_DEALTY){
dealtY = MAX_DOWN_DEALTY;
}
y+=dealtY;
// 最后 静止 在 游戏框 下侧
if (y > Constant.FRAME_HEIGHT-(imgs[state].getHeight(null) >> 1)){
y=Constant.FRAME_HEIGHT-(imgs[state].getHeight(null) >> 1);
died();
}
break;
case STATE_DIED :
break;
}
}
// 重置 小鸟
public void reset(){
state=STATE_NORMAL;
dealtY=0;
x=Constant.FRAME_WIDTH >> 1 ;
y=Constant.FRAME_HEIGHT >> 1 ;
int h=imgs[0].getHeight(null);
rect.y=this.y-h/2+RECT_DESC;
gameTime.reset();
}
// 小鸟 死亡 的过程
public void die(){
state=STATE_DIE;
// 结束 计时
gameTime.endTiming();
// 加载 游戏结束 时候的 资源
over=GameUtil.loadBufferedImage(Constant.OVER_IMG_PATH);
score=GameUtil.loadBufferedImage(Constant.SCORE_IMG_PATH);
}
// 小鸟 死亡了
public void died(){
state = STATE_DIED;
GameFrame.setGameState(GameFrame.STATE_OVER);
}
// 判断 小鸟 是否 死亡
public boolean isDied(){
return state==STATE_DIED;
}
}
/*
这个类里面 绘制了 四种 类型的 障碍物
设置了 三大 类型的 障碍物 绘制的 方法
给 悬浮在 中间的 障碍物 添加 逻辑,使其 能 移动
设置了 所有 障碍物的 属性
给 障碍物 添加了 矩形框
*/
import util.Constant;
import util.GameUtil;
import java.awt.*;
import java.awt.image.BufferedImage;
public class Barrier {
private static BufferedImage [] imgs;
static { // 静态代码块,最先被执行
final int COUNT = 3;
imgs = new BufferedImage[COUNT];
for (int i=0;i>1),y,null);
}
/*
绘制 在 中间的 障碍物
*/
private void drawHoverNormal(Graphics g){
// 先绘制 上面的 头
g.drawImage(imgs[1],x-(BARRIER_HEAD_WIDTH-BARRIER_WIDTH>>1),y+dealtY,null);
// 绘制 中间的 部分
int count = (height - BARRIER_HEAD_HEIGHT*2)/BARRIER_HEIGHT+1;
for (int i = 0; i < count; i++) {
g.drawImage(imgs[0], x, y + i * BARRIER_HEIGHT + BARRIER_HEAD_HEIGHT+dealtY, null);
}
// 绘制 下面的 头
int y=this.y+dealtY+height-BARRIER_HEAD_HEIGHT;
g.drawImage(imgs[2],x-(BARRIER_HEAD_WIDTH-BARRIER_WIDTH>>1),y,null);
}
/*
障碍物 的 逻辑 部分
*/
private void logic(){
x -= speed;
rect.x -=speed;
// 障碍物 完全移除了 屏幕
if (x<-BARRIER_WIDTH){
visible =false;
}
// 给 移动的 障碍物 添加 逻辑
switch (type){
case TYPE_HOVER_HARD :
if (isDown){ // 障碍物 下移
dealtY++;
if(dealtY > MAX_DEALY){
isDown=false;
}
}
else{ // 障碍物 上移
dealtY--;
if (dealtY == 0){
isDown=true;
}
}
rect.y=this.y+dealtY;
}
}
/*
判断 障碍物 是否 完全 进入到 游戏 窗口 中
即 此时柱子的x坐标加上柱子的宽度 如果小于 整个屏幕的宽度,那么 说明 障碍物完全出现在了 游戏窗口中
*/
public boolean isInFrame(){
return x+BARRIER_WIDTH < Constant.FRAME_WIDTH;
}
public int getX(){ // 此时 该 障碍物的 X 轴的位置
return x;
} // 返回 当前 障碍物的 x 坐标, 以方便控制 两个障碍物 之间的距离
public boolean isVisible() {
return visible;
} // 返回障碍物 是否可见,即是否还在 画面内
public Rectangle getRect() {
return rect;
}
// 设置 障碍物的 属性 (x坐标, y坐标, 高度 , 类型, 是否可见)
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(x,y,height);
dealtY=0;
isDown=true;
}
// 设置 障碍物 矩形框 的 属性
public void setRectangle(int x,int y,int height) {
rect.x=x;
rect.y=y;
rect.height=height;
}
}
import java.util.ArrayList;
import java.util.List;
/*
* 障碍物的 对象池
* 为了 避免 反复 创建和 销毁 对象
*/
public class BarrierPool {
static List pool =new ArrayList();
// 对象 池中 初始 的对象的个数
public static final int INIT_BARRIER_COUNT = 16;
// 最大个数
public static final int MAX_BARRIER_COUNT = 20;
static {
// 初始化 池中的 对象
for (int i=0;i0) {
return pool.remove(size-1);
}else {
// 池塘被 掏空,只能返回 一个新对象
return new Barrier();
}
}
public static void giveBack(Barrier barrier) {
if (pool.size()
/*
这个类里面 填充了 游戏页面你的 背景颜色
将 最下面 草地的 照片 添加进去
*/
import util.Constant;
import util.GameUtil;
import java.awt.*;
public class GameBackGround {
private Image bkImg;
public GameBackGround(){
bkImg=GameUtil.loadBufferedImage(Constant.BK_IMG_PATH);
}
public void draw(Graphics g){
g.setColor(Constant.BK_COLOR); // 设置 背景填充 颜色
g.fillRect(0,0,Constant.FRAME_WIDTH,Constant.FRAME_HEIGHT); // 这个是 完全 填充 背景颜色;
// 得到 游戏 地面 图片 的 大小
int imgW=bkImg.getWidth(null);
int imgH=bkImg.getHeight(null);
int count=Constant.FRAME_WIDTH/imgW+1; // 需要重复 叠加的 图片 个数
for (int i=0;i
/*
绘制 障碍物
将 上下型 和 中间型 的 柱子 添加到 barriers 集合中
对 障碍物 和 小鸟 进行 碰撞测试
重新开始游戏,对 障碍物 进行 清空
*/
import util.Constant;
import util.GameUtil;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;
public class GameBarrierLayer {
// 障碍物 出现的 规则
public static final int TYPE1_VER_INTERVAL =Constant.FRAME_HEIGHT>>2; // 两个 上下 障碍物中间 空 的距离
public static final int TYPE1_MIN_HEIGHT =Constant.FRAME_HEIGHT>>3;
public static final int TYPE1_MAX_HEIGHT =(Constant.FRAME_HEIGHT>>3)*5;
private List barriers; // 障碍物的集合
public GameBarrierLayer(){
barriers = new ArrayList<>();
}
public void draw(Graphics g,Bird bird) {
for (int i=0;i
/*
初始化 游戏窗口
增加 按键响应
重新开始 游戏
初始化 游戏中 用到的 类
双缓冲 解决 游戏的 闪屏
多线程 提高 程序运行 效率
*/
import org.w3c.dom.ls.LSOutput;
import util.Constant;
import util.MusicUtil;
import static util.Constant.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.net.BindException;
public class GameFrame extends Frame implements Runnable{
// 游戏 刚开始 的界面
private GameReady ready;
// 创建 一个 游戏 背景
private GameBackGround backGround;
// 创建 小鸟
private Bird bird;
// 创建 碰撞物 数据
private GameBarrierLayer barrierLayer;
// 游戏进行 状态 有 不用的 状态
private static int gameState;
public static final int STATE_READY = 0;
public static final int STATE_PLAYING = 1;
public static final int STATE_OVER =2;
public GameFrame(){
initFrame();
initGame();
}
// 设置 窗口 属性 ( 初始化 窗口 )
public void initFrame(){
//设置是否可见
setVisible(true);
// 设置 标题
setTitle("飞翔的小鸟");
// 设置 大小
setSize(FRAME_WIDTH,FRAME_HEIGHT);
// 设置 不可 自己 放大 或 缩小
setResizable(false);
// 设置 初始 位置
setLocation(FRAME_X,FRAME_Y);
// 初始化 开始界面
ready=new GameReady();
// 添加 窗口 关闭事件
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
// 结束程序
System.exit(0);
}
});
// 添加 按键 事件
addKeyListener(new MyKeyListener());
}
// 按键 感应 即 按空格 和 松开 空格 的方法
class MyKeyListener implements KeyListener{
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyPressed(KeyEvent e) {
// 捕获 系统 传入的 按键虚拟值
int keyCode = e.getKeyCode();
switch (gameState){
case STATE_READY :
if (keyCode == KeyEvent.VK_P){
setGameState(STATE_PLAYING);
}
break;
case STATE_PLAYING :
if (keyCode == KeyEvent.VK_SPACE){
bird.fly();
}
break;
case STATE_OVER :
if (keyCode == KeyEvent.VK_O){
resetGame();
}
break;
}
}
@Override
public void keyReleased(KeyEvent e) {
// 捕获 系统 传入的 按键虚拟值 (这个是 按键后 松开的状态)
int keyCode = e.getKeyCode();
switch (gameState){
case STATE_READY :
break;
case STATE_PLAYING :
if (keyCode == KeyEvent.VK_SPACE){
bird.down();
}
break;
}
}
}
// 重新开始 游戏
private void resetGame(){
setGameState(STATE_READY);
barrierLayer.reset();
bird.reset();
}
// 对游戏 对象 进行 初始化
private void initGame(){
backGround = new GameBackGround();
barrierLayer=new GameBarrierLayer();
bird = new Bird();
// 音乐资源
MusicUtil.load();
setGameState(STATE_READY); // 设置 默认值为 准备状态
// 启动多线程 , 用于刷新 窗口
new Thread(this).start();
}
/*
所有 需要 绘制的内容, 都需要 在此 方法 中 绘制
update 方式 是 jvm 调用的
该方法 绘制的 所有内容,在调用的时候,都会别 绘制到 Frame 上来。
update 何时 被 jvm 调用. 当 repaint 方法 被调用时 它 被调用
g 是 画笔 , 系统 自带的
*/
/*
用 双缓冲 解决 屏幕闪烁的问题
单独定义一张 图片,然后将 需要绘制 的内容,都绘制到 这张图片上来
然后一次性的 将 图片 绘制 到窗口 中
*/
// 这是单独定义的一张图片, 然后将需要 绘制的东西 先绘制到 这张图片上面,然后在 绘制到 窗口中
private BufferedImage bufImg = new BufferedImage(FRAME_WIDTH,FRAME_HEIGHT,BufferedImage.TYPE_4BYTE_ABGR_PRE);
@Override
public void update(Graphics g) {
Graphics bufG = bufImg.getGraphics();
backGround.draw(bufG);
if (gameState==STATE_READY){ // 游戏准备阶段
// 绘制 鸟
bird.draw(bufG);
ready.draw(bufG);
}else{ // 对 游戏 中 阶段 进行一个 绘制
// 绘制 碰撞物
barrierLayer.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_INTERAVL); // 刷新率
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 设置 游戏进行的 状态的
public static void setGameState(int gameState) {
GameFrame.gameState = gameState;
}
}
/*
绘制 游戏的 开始界面
*/
import util.Constant;
import util.GameUtil;
import java.awt.*;
import java.awt.image.BufferedImage;
public class GameReady {
private BufferedImage titleImg;
private BufferedImage noticeImg;
public GameReady(){
titleImg= GameUtil.loadBufferedImage(Constant.TITLE_IMG_PATH);
noticeImg=GameUtil.loadBufferedImage(Constant.NOTICE_IMG_PATH);
}
public void draw(Graphics g){
int x =Constant.FRAME_WIDTH - titleImg.getWidth() >>1 ; // 开始 牌的 x坐标
int y =Constant.FRAME_HEIGHT / 3; // 开始 牌的 y坐标
g.drawImage(titleImg,x,y,null); // fly_bird 的 牌子
x=Constant.FRAME_WIDTH-noticeImg.getWidth() >>1 ; // 牌子 下面 的东西
y=Constant.FRAME_HEIGHT /3 <<1;
g.drawImage(noticeImg,x,y,null); // press 的 牌子
}
}
/*
设置 游戏上方 出现的 计时器
*/
public class GameTime {
private static final GameTime GAME_TIME = new GameTime();
// 出现 悬浮障碍物的 时间
public static final int HOVER_BARRIER_TIME=5;
public static final int MOVING_BARRIER_TIME=12;
// 游戏时间 状态
private int timeState;
// 还没 开始 计时
public static final int STATE_READY =0;
// 开始 计时
public static final int STATE_RUN = 1;
// 结束 计时
public static final int STATE_OVER = 2;
// 游戏 的 计时 开始和结束
private long startTime,endTime;
private GameTime(){
timeState = STATE_READY;
}
// 是否 准备好开始计时
public boolean isReady(){
return timeState==STATE_READY;
}
// 开始 计时 的 方法
public void startTiming(){
startTime=System.currentTimeMillis();// 获取 当前系统时间
timeState=STATE_RUN;
}
// 结束 计时 的 方法
public void endTiming(){
endTime=System.currentTimeMillis(); // 获取 当前系统时间
timeState=STATE_OVER;
}
/*
游戏 用的 毫秒 来计时
*/
public long getTime(){
if (timeState==STATE_RUN){
return System.currentTimeMillis()-startTime;// 游戏 运行时 的时间
}
return endTime-startTime; // 游戏 结束时 的时间
}
// 将游戏 用的 毫秒 转化 为秒 来计时
public long getSecondTime(){
return getTime()/1000;
}
// 这个等于 返回 一个 对象,和 new 一个对象道理差不多
public static GameTime getInstance(){
return GAME_TIME;
}
// 重新开始后 , 重置时间
public void reset() {
timeState=STATE_READY;
startTime=0;
endTime=0;
}
}
import java.awt.*;
public class Constant {
// 窗口 大小
public static final int FRAME_WIDTH=1000;
public static final int FRAME_HEIGHT=690;
// 窗口 初始位置
public static final int FRAME_X=500;
public static final int FRAME_Y=200;
// 游戏 地面 背景 图片
public static final String BK_IMG_PATH="img/ground.png";
// 游戏 屏幕 刷新率
public static final int GAME_INTERAVL=33;
// 游戏 背景颜色
public static final Color BK_COLOR=new Color(0x4bc4cf);
// 小鸟 状态 图片 资源
public static final String [] BIRDS_IMG_PATH=
{"img/normal.png","img/up.png","img/down.png","img/die.png","img/bb.png","img/bb1.png","img/bb2.png","img/bb3.png"};
// 标题栏的 高度
public static final int TOP_BAR_HEIGHT = 25;
// 障碍物 的 三种 情况 照片
public static final String [] BARRIERS_IMG_PATH =
{"img/barrier.png","img/barrier_up.png","img/barrier_down.png"};
// 添加 游戏 刚开始和 结束 的照片
public static final String TITLE_IMG_PATH = "img/title.png";
public static final String NOTICE_IMG_PATH = "img/start.png";
public static final String OVER_IMG_PATH = "img/over.png";
public static final String SCORE_IMG_PATH = "img/score1.jpg";
// 设置 计时牌 的字体
public static final Font TIME_FONT = new Font("华文琥珀",Font.BOLD,40);
}
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.FileInputStream;
import java.io.IOException;
public class GameUtil {
/*
插图 必备 方法
其中 BufferedImage 为 Image 的 子类 方法
插入的 照片 需要 引用 这个方法
*/
public static BufferedImage loadBufferedImage(String imgPath){
try{
return ImageIO.read(new FileInputStream(imgPath)) ;
} catch (IOException e){
e.printStackTrace();
}
return null;
}
// 随机出 两个数 之间的数字, 区间 包括左边 不包括右边
public static int getRandomNumber(int min,int max) {
return (int)(Math.random()*(max-min)+min);
}
/*
判断是否大于这个 概率。
左边为 分子,右边为 分母
*/
public static boolean isInAnyProbability(int numrator , int denominator) throws Exception {
// 处理的特殊情况
if (numrator <=0 || denominator <=0){
throw new Exception("传入了非法的参数!");
}
// 一定 发生的 事件
if (numrator >= denominator){
return true;
}
return getRandomNumber(1,denominator+1) <= numrator;
}
}
import java.applet.Applet;
import java.applet.AudioClip;
import java.io.File;
public class MusicUtil {
private static AudioClip fly;
private static AudioClip fly1;
// 装载音乐资源
public static void load(){
try{
fly = Applet.newAudioClip(new File(("music/cf.wav")).toURL());
fly1 = Applet.newAudioClip(new File(("music/tiao2.wav")).toURL());
}catch (Exception e){
e.printStackTrace();
}
}
// wav 音乐的 播放
public static void playFly(){
fly.play();
}
public static void tiao(){
fly1.play();
}
}
代码可能过长,但是每行代码都有详细的解释哟!!!
学完了java基础,可以自己尝试做个小游戏作为检测哟!!!
希望能给您带来帮助!