自己動手寫游戲:Flappy Bird


  START:最近閑來無事,看了看一下《C#開發Flappy Bird游戲》的教程,自己也試着做了一下,實現了一個超級簡單版(十分簡陋)的Flappy Bird,使用的語言是C#,技術采用了快速簡單的WindowsForm,圖像上主要是采用了GDI+,游戲對象的創建控制上使用了單例模式,現在我就來簡單地總結一下。

一、關於Flappy Bird

  《Flappy Bird》是由來自越南的獨立游戲開發者Dong Nguyen所開發的作品,游戲中玩家必須控制一只小鳥,跨越由各種不同長度水管所組成的障礙,而這只鳥其實是根本不會飛的……所以玩家每點擊一下小鳥就會飛高一點,不點擊就會下降,玩家必須控制節奏,拿捏點擊屏幕的時間點,讓小鳥能在落下的瞬間跳起來,恰好能夠通過狹窄的水管縫隙,只要稍一分神,馬上就會失敗陣亡。簡單但不粗糙的8比特像素畫面、超級馬里奧游戲中的水管、眼神有點呆滯的小鳥和幾朵白雲,白天夜晚兩種模式便構成了游戲的一切。玩家需要不斷控制點擊屏幕的頻率來調節小鳥的飛行高度和降落速度,讓小鳥順利通過畫面右方的管道縫隙。如果小鳥不小心擦碰到了管子的話,游戲便宣告結束。

二、游戲設計

2.1 總結游戲印象

  玩過的Flappy Bird的童鞋們應該都對這款游戲有印象,現在我們來看看這款游戲的特點:

  (1)這款游戲的畫面很簡單:一張背景圖,始終就沒有變過;

  (2)這款游戲的對象只有倆:一個小鳥(有三種揮動翅膀的狀態)以及一對管道(有管道向上和向下兩個方向);

    小鳥:①

    管道:

2.2 總結設計思路

  (1)萬物皆對象

  在整個游戲中,我們看到的所有內容,我們都可以理解為游戲對象;(在Unity中,GameObject即游戲對象)每一個游戲對象,都由一個單獨的類來創建;在游戲中,總共只有兩個游戲對象:小鳥和管道,那么我們就可以創建兩個類:BirdPipe。但是,我們發現小鳥和管道都有一些共同的屬性和方法,例如X,Y軸坐標,長度和寬度,以及繪制(Draw())和移動(Move())的方法,這時我們可以設計一個抽象類,將共有的東西封裝起來,減少開發時的冗余代碼,提高程序的可擴展性,符合面向對象設計的思路:

  (2)計划生育好

  在整個游戲中,我們的小鳥對象只有一個,也就是說在內存中只需要存一份即可。這時,我們想到了偉大的計划生育政策,於是我們想到了使用單例模式。借助單例模式,可以保證只生成一個小鳥的實例,即為程序提供一個全局訪問點,避免重復創建浪費不必要的內存。

  (3)對象的運動

  在整個游戲中,小鳥會受重力默認向下墜落,而用戶可以根據點擊或按鍵盤Space鍵使小鳥向上飛,從圖像呈現上其本質就是更改游戲對象在Y軸的位置,使其從下往上移動;而管道則會從屏幕右側出現,從屏幕左側消失,又從屏幕右側出現,再從屏幕左側消失,一直循環往復。可以看到,從圖像呈現上期本質就是更改管道對象在X軸的位置,使其從右往左移動。

  (4)設計流程圖

  在整個開發設計過程中,我們可以根據優先級設計開發流程,根據流程一步一步地實現整個游戲。

三、關鍵代碼

3.1 設計抽象父類封裝共有屬性

    /// <summary>
    /// 游戲對象基類
    /// </summary>
    public abstract class GameObject
    {
        #region 01.構造函數及屬性
        public int X { get; set; }

        public int Y { get; set; }

        public int Width { get; set; }

        public int Height { get; set; }

        public GameObject(int x, int y)
        {
            this.X = x;
            this.Y = y;

            this.Width = this.Height = 0;
        }

        public GameObject(int x, int y, int width, int height)
        {
            this.X = x;
            this.Y = y;
            this.Width = width;
            this.Height = height;
        } 
        #endregion

        #region 02.抽象方法
        /// <summary>
        /// 抽象方法1:繪制自身
        /// </summary>
        public abstract void Draw(Graphics g);

        /// <summary>
        /// 抽象方法2:移動自身
        /// </summary>
        public abstract void Move(); 
        #endregion

        #region 03.實例方法
        public Rectangle GetRectangeleArea()
        {
            return new Rectangle(this.X, this.Y, this.Width, this.Height);
        } 
        #endregion
    }
View Code

  一切皆對象,這里封裝了游戲對象小鳥和管道共有的屬性,以及兩個抽象方法,讓小鳥和管道自己去實現。

3.2 設計單例模式減少對象創建

    /// <summary>
    /// 小鳥對象單例模式類
    /// </summary>
    public class SingleObject
    {
        private SingleObject() { }

        private static SingleObject singleInstance;

        public static SingleObject GetInstance()
        {
            if (singleInstance == null)
            {
                singleInstance = new SingleObject();
            }
            return singleInstance;
        }

        public Bird SingleBird
        {
            get;
            set;
        }

        /// <summary>
        /// 添加游戲對象
        /// </summary>
        /// <param name="parentObject">游戲對象父類</param>
        public void AddGameObject(GameObject parentObject)
        {
            if(parentObject is Bird)
            {
                SingleBird = parentObject as Bird;
            }
        }

        /// <summary>
        /// 繪制游戲對象
        /// </summary>
        /// <param name="g"></param>
        public void DrawGameObject(Graphics g)
        {
            SingleBird.Draw(g);
        }
    }
View Code

  這里借助單例模式使小鳥實例始終只有一個,實現上主要是將小鳥類和單例模式聚合。

3.3 設計重力輔助類使小鳥能夠自動下落

  (1)設計重力輔助類

    /// <summary>
    /// 重力輔助類
    /// </summary>
    public class Gravity
    {
        public static float gravity = 9.8f;

        /// <summary>
        /// s = 1/2*gt^2+vt
        /// </summary>
        /// <param name="speed">速度</param>
        /// <param name="second">時間</param>
        /// <returns>位移量</returns>
        public static float GetHeight(float speed, float time)
        {
            float height = (float)(0.5 * gravity * time * time)
                + speed * time;
            return height;
        }
    }
View Code

  在Unity游戲引擎中給游戲對象增加一個剛體組件就可以使游戲對象受重力影響,但是在普通的程序中需要自己設計重力類使游戲對象受重力影響下落。這里使用中學物理的知識:求重力加速度的位移量;

  (2)在定時器事件中使小鳥承受重力影響始終下落

        private void GravityTimer_Tick(object sender, EventArgs e)
        {
            Bird singleBird = SingleObject.GetInstance().SingleBird;
            // Step1:獲得小鳥下降的高度
            float height = Gravity.GetHeight(singleBird.CurrentSpeed,
                singleBird.DurationTime * 0.001f);
            // singleBird.DurationTime * 0.001f => 將毫秒轉換成幀
            // Step2:獲得小鳥下落后的坐標
            int y = singleBird.Y + (int)height;
            // Step3:將新Y軸坐標賦給小鳥
            int min = this.Size.Height - this.pbxGround.Height
                - 60;
            if (y > min)
            {
                // 限定小鳥不要落到地面下
                y = min;
            }
            singleBird.Y = y;
            // Step4:使小鳥按照加速度下降 [ 公式:v=v0+at ]
            singleBird.CurrentSpeed = singleBird.CurrentSpeed
                + Gravity.gravity * singleBird.DurationTime * 0.001f;
        }
View Code

  這里重點是將毫秒轉換為幀,實現上是使DurationTime*0.001f使速度減慢;

3.4 設計碰撞檢測方法使游戲能夠終結

  (1)Rectangle的IntersectsWith方法

  在游戲界面中,任何一個游戲對象我們都可以視為一個矩形區域(Rectangle類實例),它的坐標是X軸和Y軸,它還有長度和寬度,可以輕松地確定一個它所在的矩形區域。那么,我們可以通過Rectangle的IntersectsWith方法確定兩個Rectangle是否存在重疊,如果有重疊,此方法將返回 true;否則將返回 false。那么,在FlappyBird中主要是判斷兩種情況:一是小鳥是否飛到邊界(屏幕的上方和下方),二是小鳥是否碰到了管道(向上的管道和向下的管道)。

  (2)在定時器事件中循環判斷小鳥是否碰到邊界或管道

        private void PipeTimer_Tick(object sender, EventArgs e)
        {
            // 移動管道
            this.MovePipeLine();
            // 碰撞檢測
            Bird bird = SingleObject.GetInstance().SingleBird;
            if (bird.Y == 0 || bird.Y == this.pbxGround.Height ||
                bird.GetRectangeleArea()
                .IntersectsWith(pipeDown.GetRectangeleArea()) ||
                bird.GetRectangeleArea()
                .IntersectsWith(pipeUp.GetRectangeleArea()))
            {
                // 暫停游戲
                this.PauseGame();
                if (MessageBox.Show("您已掛了,是否購買王胖子的滑板鞋繼續暢玩?",
                    "溫馨提示", MessageBoxButtons.YesNo,
                    MessageBoxIcon.Question) == DialogResult.Yes)
                {
                    // 重新初始化游戲對象
                    this.InitialGameObjects();
                    // 重新開始游戲
                    this.RestoreGame();
                }
                else
                {
                    MessageBox.Show("您的選擇是明智的,王胖子的滑板鞋太挫了!",
                        "溫馨提示", MessageBoxButtons.OK, 
                        MessageBoxIcon.Information);
                    Environment.Exit(0);
                }
            }
        }
View Code

四、開發小結

  從運行效果可以看出,此次DEMO主要完成了幾個比較核心的內容:一是小鳥和管道的移動,二是小鳥和邊界(最上方和最下方以及管道)的碰撞檢測。當然,還有很多核心的內容沒有實現,比如:計算通過的管道數量、游戲歡迎界面和結束界面等。希望有興趣的童鞋可以去繼續完善實現,這里提供一個我的Flappy Bird實現僅供參考,謝謝!

參考資料

  趙劍宇,《C#開發史上最虐人游戲-Flappy Bird像素鳥》:http://bbs.itcast.cn/thread-42245-1-1.html

附件下載

  SimpleFlappyBirdDemo:http://pan.baidu.com/s/1hqtcHIs

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM