標籤:style blog http io ar color os 使用 sp
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即遊戲對象)每一個遊戲對象,都由一個單獨的類來建立;在遊戲中,總共只有兩個遊戲對象:小鳥和管道,那麼我們就可以建立兩個類:Bird和Pipe。但是,我們發現小鳥和管道都有一些共同的屬性和方法,例如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
周旭龍
出處:http://www.cnblogs.com/edisonchou/
本文著作權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連結。
自己動手寫遊戲:Flappy Bird