JavaFx实现Flappybird

《flappy bird》是一款由来自越南的独立游戏开发者Dong Nguyen所开发的作品。游戏中玩家必须控制一只小鸟,跨越由各种不同长度水管所组成的障碍。现在用Java还原了这款游戏。

运行截图

首页

1.jpg

游戏中

2.jpg

结算

3.jpg

项目文件

项目文件

项目源码

入口

main.java

package pika.game.flappybird;
/*--------------------------------------
              Flappy Bird
            Code by Pikachu
              Java课程设计
          任浩龙2018141461344
--------------------------------------*/
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.text.Text;
public class Main extends Application {
        //----------------------------------------------全局游戏设置参数----------------------------------------------
        public static final int    game_wind_high = 960;    //全局窗口高度
        public static final int    game_wind_wedh = 864;    //全局窗口宽度
        public static final int    game_fram_fpst =  17;    //全局同步间隔
        public static final int    game_dbug_imgs =   0;    //图像调试标识
        public static final int    game_dbug_taps =   1;    //水管调试标识
        public static final double bird_data_datg =0.45;    //记录小鸟重力
        public static       double bird_data_high = 330;    //记录小鸟位置
        public static       double bird_data_spee =   0;    //记录小鸟速度,每一帧执行后自增的速度
        public static final double bird_data_addq =  10;    //小鸟上升速度,每次按下鼠标设置的速度
        public static       double taps_spee_init =   6;    //记录水管速度
        public static final double taps_spee_adda =   0;    //水管递增速度,每隔多少帧增加多少速度
        public static       double taps_spee_addb = 140;    //水管递增间隔,每隔多少帧增加一根水管
        public static final double taps_spee_addc =   2;    //水管递增递减,每通过一根水管减多少帧
        public static       double taps_spee_addd =   0;    //水管递增计数,统计当前执行的是多少帧
        public static final double taps_spee_adde =  30;    //水管递增下界,记录最少间隔多少帧自增
        public static       double game_scor_data =   0;    //存储游戏分数
        public static       Imgs   main_ctrl = new Imgs();  //全局渲染对象
        public static       Imgs   menu_bgmp = new Imgs();  //菜单背景图片
        public static       Imgs   menu_titl = new Imgs();  //菜单标题图片
        public static       Imgs   menu_star = new Imgs();  //菜单开始图片
        public static       Imgs   menu_bars = new Imgs();  //菜单底纹图片
        public static       Imgs   menu_bart = new Imgs();  //菜单底纹图片
        public static       Imgs   game_bird = new Imgs();  //游戏小鸟图像
        public static       Imgs   ends_titl = new Imgs();  //游戏结束标题
        public static       Imgs   ends_pans = new Imgs();  //游戏结束标题
        public static       Imgs   ends_dold = new Imgs();  //游戏结束标题
        public static       Push   menu_butt = new Push();  //存储按钮数据
        public static       Loop   menu_loop = new Loop();  //存储菜单循环
        public static       Loop   bird_loop = new Loop();  //小鸟下坠循环
        public static       Loop   taps_loop = new Loop();  //水管移动循环
        public static       Taps   taps_maps = new Taps();  //水管图像数据
        public static       Text   text_scor = new Text();  //存储文字信息
    public void start(Stage views) {
        Func.main_init(views);
        Func.main_menu_init();
        Func.main_menu_show();
        Func.main_menu_loop();
        Func.menu_loop.play();
        Func.main_push_init();
        Func.main_mous_loop();
        Func.main_game_loop();
    }
}

功能实现

游戏渲染Func.java

package pika.game.flappybird;
/*--------------------------------------
              渲染事件类
           CODE BY PIKACHUIM
           作用:执行渲染操作
--------------------------------------*/
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.input.MouseEvent;
import javafx.stage.Stage;
import javafx.scene.text.Font;
import javafx.scene.paint.Paint;
public class Func extends Main{
    public static void main_init(Stage inpu) {
        main_ctrl.init(game_wind_high, game_wind_wedh);main_ctrl.show(inpu);
    }
    public static void main_menu_init() {
        //----------------------------------------------主菜单图片初始化----------------------------------------------
        menu_bgmp.setp("Imgs", "PICTS_BGPBT");menu_bgmp.setr(000, 000, 864, 960);
        menu_titl.setp("Imgs", "PICTS_TITLE");menu_titl.setr(170, 100, 534, 144);
        menu_star.setp("Imgs", "PICTS_START");menu_star.setr(315, 330, 242, 146);
        menu_bars.setp("Imgs", "PICTS_LANGS");menu_bars.setr(000, 852, 864, 224);
        menu_bart.setp("Imgs", "PICTS_LANGS");menu_bart.setr(864, 852, 864, 224);
        ends_titl.setp("Imgs", "PICTS_OVERD");ends_titl.setr(130, 140, 612, 108);
        ends_pans.setp("Imgs", "PICTS_PANEL");ends_pans.setr(150, 500, 595, 315);
        ends_dold.setp("icon", "PICTS_MEDA3");ends_dold.setr(219, 625, 110, 110);
        menu_butt = new Push("", menu_star);         menu_butt.setr(295, 300);menu_butt.tran();
    }
    public static void main_game_init(){
        //----------------------------------------------游戏中图片初始化----------------------------------------------
        game_bird.setp("icon", "PICTS_BIRD1");game_bird.setr(340,330,   96,  96);
        main_bird_loop();bird_loop.play();Func.taps_loop.play();
    }
    public static void main_game_loop(){
        //----------------------------------------------游戏中水管主循环----------------------------------------------
        EventHandler<ActionEvent> main_taps_even = r -> {
            Taps.move();
            if(taps_spee_addd==0){
                int temp_addu=(int)System.currentTimeMillis()%150;
                int temp_flag=(int)(Math.random()*101)%2;
                if(Taps.taps_lens>0){
                    int temp_posy=(int)(-Taps.taps_dat1.get(Taps.taps_lens-1).imgs_posy);
                    if(temp_flag==0)
                        Taps.addt(temp_posy+temp_addu);
                    else
                        Taps.addt(temp_posy-temp_addu);
                    System.out.println("[增加水管]"+temp_flag+"[增加位置]"+temp_posy);
                }
                else
                    Taps.addt(700);
                taps_spee_init+=taps_spee_adda;
                taps_spee_addd=taps_spee_addb;
            }
            else{
                taps_spee_addd--;
            }
        };
        taps_loop.setb(game_fram_fpst, main_taps_even);
    }
    public static void main_menu_show(){
        //----------------------------------------------载入菜单图像数据----------------------------------------------
        main_ctrl.clea();/*清空*/menu_bgmp.radd();menu_titl.radd();menu_bars.radd();menu_bart.radd();menu_butt.radd();
    }
    public static void main_game_show(){
        //----------------------------------------------载入游戏图像数据----------------------------------------------
        main_ctrl.clea();/*清空*/menu_bgmp.radd();menu_bars.radd();menu_bart.radd();game_bird.radd();
        text_scor.setFont(Font.font ("Verdana", 42));
        text_scor.setText("SCORE: 0");
        text_scor.setTranslateX(20);
        text_scor.setTranslateY(70);
        text_scor.setFill(Paint.valueOf("#ffffff"));
        main_ctrl.imgs_pane.getChildren().add(text_scor);
    }
    public static void main_push_init(){
        //----------------------------------------------载入菜单按钮事件----------------------------------------------
        EventHandler<ActionEvent> menu_burr_acte=new EventHandler<ActionEvent>(){
            @Override
            public void handle(ActionEvent t){ main_game_init();main_game_show(); }};
        menu_butt.seta(menu_burr_acte);
    }
    public static void main_menu_loop(){
        //----------------------------------------------载入菜单底栏事件----------------------------------------------
        EventHandler<ActionEvent> main_menu_even = r -> {
            menu_bars.setr(menu_bars.imgs_posx -8, menu_bars.imgs_posy);
            menu_bart.setr(menu_bart.imgs_posx -8, menu_bart.imgs_posy);
            if(menu_bars.imgs_posx<=game_wind_wedh*(-1)) menu_bars.setr(  0,menu_bars.imgs_posy);
            if(menu_bart.imgs_posx<=0)                   menu_bart.setr(864,menu_bart.imgs_posy);};
        menu_loop.setb(game_fram_fpst,main_menu_even);
    }
    public static void main_bird_loop(){
        //----------------------------------------------载入小鸟下坠线程----------------------------------------------
        EventHandler<ActionEvent> main_bird_even = r -> {
            bird_data_spee+=bird_data_datg;bird_data_high+=bird_data_spee;
            if(bird_data_high>=778)bird_data_high=778;game_bird.setr(game_bird.imgs_posx,bird_data_high);};
        bird_loop.setb(game_fram_fpst,main_bird_even);
    }
    public static void main_mous_loop(){
        //----------------------------------------------载入小鸟上升线程----------------------------------------------
        main_ctrl.imgs_rend.setOnMousePressed(new EventHandler<MouseEvent>() {
            @Override public void handle(MouseEvent m) { bird_data_spee=-bird_data_addq; }});
    }
    public static void main_game_rset(){
        //----------------------------------------------重置游戏状态函数----------------------------------------------
        bird_data_high = 330;    //记录小鸟位置
        bird_data_spee =   0;    //记录小鸟速度
        taps_spee_init =   6;    //记录水管速度
        taps_spee_addb = 140;    //水管递增间隔
        taps_spee_addd =   0;    //水管递增计数
        game_scor_data =   0;    //存储游戏分数
        for ( int i=Taps.taps_dat1.size()-1; i>=0;i--) {
            Taps.taps_dat1.remove(i);
            Taps.taps_dat2.remove(i);
        }
    }
    public static void main_endl_show(){
        //----------------------------------------------显示结尾标题函数----------------------------------------------
        bird_loop.stop();
        taps_loop.stop();
        main_ctrl.clea();
        menu_bgmp.radd();
        ends_titl.radd();
        ends_pans.radd();
        ends_dold.radd();
        text_scor.setTranslateX(620);
        text_scor.setTranslateY(620);
        main_ctrl.imgs_pane.getChildren().add(text_scor);
        Taps.clen();
        text_scor.setText(Integer.toString((int)game_scor_data));

        main_game_rset();
        menu_butt.radd();
    }

}

图片载入Imgs.java

package pika.game.flappybird;
/*--------------------------------------
              图片渲染类
           CODE BY PIKACHUIM
作用:载入图片并执行渲染,并绑定面板布局
用法:Imgs->setp->setr->load->uppo->radd
全局:init传入场景->show一次性绑定节点值
--------------------------------------*/
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Pane;

public class Imgs {
    public static Pane       imgs_pane;         //存储全局布局
    public static Scene      imgs_rend;         //存储全局场景
    public        String     imgs_path;         //存储图片位置
    public        Image      imgs_imgs;         //存储图形数据
    public        ImageView  imgs_view;         //存储图片参数
    public        double     imgs_posx;         //存储垂直坐标
    public        double     imgs_posy;         //存储水平坐标
    public        double     imgs_posw;         //存储水平宽度
    public        double     imgs_posh;         //存储垂直宽度
    //--------------------------构造函数--------------------------
    public Imgs() { this.news(); }              //默认构造函数
    public Imgs(String dirs, String name){      //只设置文件名
       this.setp(dirs,name);this.news();}
    public Imgs(String dirs, String name,       //同时设置状态
                double datx, double daty,
                double datw, double dath){
        this.setp(dirs,name);
        this.setr(datx,daty, datw,dath);}
    public void news() {                        //默认参数初始
        this.imgs_posx = 0;
        this.imgs_posw = 100;
        this.imgs_posy = 0;
        this.imgs_posh = 100; }
    //--------------------------设置函数--------------------------
    public void setp(String dirs,String name)  {//设置文件路径
        imgs_path = "file:" +
                System.getProperty("user.dir")
                + "\\" + dirs
                + "\\" + name + ".PNG";
        this.load(); }
    public void setr(double datx, double daty,
                     double datw, double dath) {//设置大小位置
        this.imgs_posw = datw;
        this.imgs_posh = dath;
        this.setr(datx, daty); }
    public void setr(double datx, double daty) {//改变图片位置
        this.imgs_posx = datx;
        this.imgs_posy = daty;
        this.uppo(); }
    //--------------------------载入函数--------------------------
    public void load() {
        this.imgs_imgs = new Image(imgs_path);  //载入图片文件
        this.imgs_view = new ImageView();
        this.imgs_view.setImage(imgs_imgs);
        if(Main.game_dbug_imgs==1)
            System.out.println(imgs_path);
        this.uppo(); }
    public void radd() {                        //向面板加节点
        this.imgs_pane.getChildren().add(imgs_view); }
    public void uppo() {                        //更新图片位置
        this.imgs_view.setFitHeight(imgs_posh);
        this.imgs_view.setFitWidth(imgs_posw);
        this.imgs_view.setX(imgs_posx);
        this.imgs_view.setY(imgs_posy); }
    //--------------------------全局函数--------------------------
    public void init(int high, int widh) {      //初始窗口界面
        this.imgs_pane = new Pane();
        this.imgs_rend = new Scene(
             imgs_pane,widh,high); }
    public void show(Stage runt) {              //更新窗口画面
        runt.setScene(imgs_rend);
        runt.show(); }
    public void clea() {                        //清空渲染内容
        this.imgs_pane.getChildren().clear(); }
    //------------------------------------------------------------
}

事件载入Loop.java

package pika.game.flappybird;
/*--------------------------------------
               事件循环类
           CODE BY PIKACHUIM
           作用:读写事件操作
--------------------------------------*/
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.util.Duration;

public class Loop {
    public EventHandler<ActionEvent> loop_even;
    public Timeline                  loop_line;
    public double                    loop_time;
    public void setb(double inpu_time,EventHandler<ActionEvent>inpu_even){          //绑定事件
        loop_time=inpu_time;
        loop_even=inpu_even;
    }
    public void play(){
        loop_line=new Timeline(new KeyFrame(Duration.millis(loop_time),loop_even)); //执行动画
        loop_line.setCycleCount(Timeline.INDEFINITE);
        loop_line.play();
    }
    public void stop(){                                                             //停止执行
        loop_line.stop();
    }
}

按钮事件Push.java

package pika.game.flappybird;
/*--------------------------------------
               按钮事件类
           CODE BY PIKACHUIM
     作用:显示按钮并绑定触发事件
     用法:Push->setr->radd->seta
--------------------------------------*/
import javafx.scene.control.Button;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
public class Push extends Imgs {
    public Button push_push;
    public Push(){}
    public Push(String text, Imgs imga){
        push_push=new Button(text,imga.imgs_view);
    }
    public void setr(double datx,double daty){
        push_push.setLayoutX(datx);
        push_push.setLayoutY(daty);
    }
    public void radd(){
        this.imgs_pane.getChildren().add(push_push);
    }
    public void seta(EventHandler<ActionEvent> inpu){
        push_push.setOnAction(inpu);
    }
    public void tran(){
        push_push.setStyle("-fx-background-color: #4ec0ca");
    }
}

水管事件Taps.java

package pika.game.flappybird;
import java.util.ArrayList;
import java.util.List;
/*--------------------------------------
               水管移动类
           CODE BY PIKACHUIM
     作用:操作水管移动并判断
--------------------------------------*/
public class Taps extends Main{
    public static List<Imgs> taps_dat1;
    public static List<Imgs> taps_dat2;
    public static int        taps_lens;
    public Taps(){
        taps_lens=0;
        taps_dat1= new ArrayList<Imgs>();
        taps_dat2= new ArrayList<Imgs>();
    }
    public static void clen(){
        taps_lens=0;
        taps_dat1= new ArrayList<Imgs>();
        taps_dat2= new ArrayList<Imgs>();
    }
    public static void addt(int high){
        if(high<300) high=300;
        if(high>600) high=600;
        taps_lens++;
        Imgs tmp1=new Imgs("icon","PICTS_PIPUP",
                820,      -high,        52,768);
        Imgs tmp2=new Imgs("icon","PICTS_PIPDO",
                820,-high+768+300,52,768);
        taps_dat1.add(tmp1);taps_dat1.get(taps_dat1.size()-1).radd();
        taps_dat2.add(tmp2);taps_dat2.get(taps_dat2.size()-1).radd();
    }
    public static void delt(int inde){
        if(taps_dat1.size()>=1){
            main_ctrl.imgs_pane.getChildren().remove(taps_dat1.get(inde).imgs_view);
            main_ctrl.imgs_pane.getChildren().remove(taps_dat2.get(inde).imgs_view);
            taps_dat1.remove(taps_dat1.get(inde));
            taps_dat2.remove(taps_dat2.get(inde));
            taps_lens--;
        }
    }
    public static void move(){
        if(taps_lens>0)
            for(int lotp=0;lotp<taps_lens;lotp++){
                taps_dat1.get(lotp).setr(taps_dat1.get(lotp).imgs_posx-taps_spee_init,taps_dat1.get(lotp).imgs_posy);
                taps_dat2.get(lotp).setr(taps_dat2.get(lotp).imgs_posx-taps_spee_init,taps_dat2.get(lotp).imgs_posy);
                if(taps_dat1.get(lotp).imgs_posx<=-64) {
                    game_scor_data++;
                    String temp="SCORE: "+Integer.toString((int)game_scor_data);
                    text_scor.setText(temp);
                    if(Main.game_dbug_taps==1)
                        System.out.println("[当前得分]"+game_scor_data
                                          +"[帧率间隔]"+taps_spee_addb
                                          +"[水管速度]"+taps_spee_init
                                          +"[水管数量]"+taps_lens);
                    taps_spee_addb-=taps_spee_addc;
                    if(taps_spee_addb<=taps_spee_adde)taps_spee_addb=taps_spee_adde;
                }
                if(taps_dat1.get(lotp).imgs_posx<=-64){
                    delt(lotp);
                }
                if(taps_dat1.get(lotp).imgs_posx>=244&&taps_dat1.get(lotp).imgs_posx<=436){
                    if(bird_data_high<= taps_dat1.get(lotp).imgs_posy+750
                    || bird_data_high>= taps_dat2.get(lotp).imgs_posy-80){
                        System.out.println("[撞到柱子]"+lotp);
                        Func.main_endl_show();
                    }
                }
            }
    }
}
Last modification:April 4, 2023
If you think my article is useful to you, please feel free to appreciate