【程式設計-C#】打磚塊遊戲 – 動作

【程式設計-C#】打磚塊遊戲 – 動作

【程式設計-C#】打磚塊遊戲 – 動作

(到上一篇【程式設計】打磚塊遊戲 – 處理物件及畫面美工)

打磚塊遊戲設計資源 (請按下面圖片):

接下來,在這個教學裏,我們要實現底下遊戲動作: 打磚塊專案檔20180522

  1. 球的移動及碰到邊界的處理
  2. 處理球拍的移動 (鍵盤 + 滑鼠)
  3. 球拍擊中球的判斷及處理
  4. 球擊中磚塊的判斷及處理
  5. 遊戲結束條件:1.球掉出下邊界;2.按ESC鍵
  6. 進入到下一關:擊中全部磚塊,所有磚塊消失。(你會怎麼做?)

首先,我們所有的動作皆透過計時器物件來實現,將計時器物件拖拉至程式主畫面,設定interval,依照人類視覺暫留特性,每秒只要高於16個影像,看起來的東西就會覺得是連貫的,電影的FPS為24,遊戲則要更高,至少30以上,所以,我們的計時器的interval  = 1 / 40  * 1000 =  25

一、球的移動及碰到邊界的處理 BrickBreakout – 2 – 1

程式一開始先宣告:

int X_Inc = 5, Y_Inc = 5;
// 水平移動增量 X_Inc (正:往右邊,負:往左邊)
// 垂直移動增量 Y_Inc(正:往下邊,負:往上面)

計時器的Tick事件:

private void timer1_Tick(object sender, EventArgs e)
{
    ball.Left += Y_Inc; //水平
    ball.Top += X_Inc; //垂直

    //邊界碰撞
    if (ball.Left < 0 || ball.Left > this.Bounds.Width) Y_Inc = -Y_Inc; //左右邊界
    if (ball.Top > this.Bounds.Height || ball.Top < 0) X_Inc = -X_Inc; //上下邊界
}

二、處理球拍的移動 (鍵盤 + 滑鼠)

在timer1的Tick事件中加入:

private void timer1_Tick(object sender, EventArgs e)
{
    //球的移動處理
    ball.Left += Y_Inc;
    ball.Top += X_Inc;

    //球的邊界碰撞
    if (ball.Left < 0 || ball.Left > this.Bounds.Width) Y_Inc = -Y_Inc; //左右邊界
    if (ball.Top > this.Bounds.Height || ball.Top < 0) X_Inc = -X_Inc; //上下邊界

    //球拍的移動
    if (Cursor.Position.X >= (this.Bounds.Width - racket.Width))//如果游標在右邊界處 
        racket.Left = this.Bounds.Width - racket.Width;//減去 球拍的水平位置,否則的話,球拍會在視窗 
    else racket.Left = Cursor.Position.X; //球拍的水平位置 = 游標的水平位置

}

除了使用滑鼠移動游標來移動球拍,我們另外抓取按鍵事件來移動球拍,作法是在Form表單中的KeyDown事件抓取左右按鍵:

private void Form1_KeyDown(object sender, KeyEventArgs e)
{
    if (e.KeyCode == Keys.Left) Cursor.Position = new Point(Cursor.Position.X  - 20, Cursor.Position.Y);
    if (e.KeyCode == Keys.Right) Cursor.Position = new Point(Cursor.Position.X + 20, Cursor.Position.Y);
}

三、球拍擊中球的判斷及處理 BrickBreakout – 2-2

判斷球拍是否擊中球,並且在確認擊中之後,球的垂直移動方向由往下變往上。

private void timer1_Tick(object sender, EventArgs e)
{
    //球的邊界碰撞
    if (ball.Left <= 0 || ball.Left >= (this.Bounds.Width - ball.Width)) X_Inc = -X_Inc; //左右邊界
    if (ball.Top >= (this.Bounds.Height - ball.Height) || ball.Top <= 0) Y_Inc = -Y_Inc; //上下邊界

    //球拍是否擊中球:
    if (ball.Left >= racket.Left && ball.Left <= (racket.Left + racket.Width) && ball.Top >= (racket.Top - ball.Height)) Y_Inc = -Y_Inc;

    //球拍的移動
    if (Cursor.Position.X >= (this.Bounds.Width - racket.Width))//如果游標在右邊界處 
        racket.Left = this.Bounds.Width - racket.Width;//減去 球拍的水平位置,否則的話,球拍會在視窗 
    else racket.Left = Cursor.Position.X; //球拍的水平位置 = 游標的水平位置

    //球移動
    ball.Left += X_Inc;
    ball.Top += Y_Inc;
}

四、球擊中磚塊的判斷及處理 BrickBreakout – 2-3

球擊中磚塊的判斷跟球拍是否擊中球作法類似,擊中確認後,我們只需要將該磚塊的Visible設為false,使磚塊看不見就好了!(簡單吧?!)

程式在timer1的Tick事件中加了這個部份的處理:

//一一測試每顆磚塊是否被球擊中
for (int i = 0; i < bricks.GetLength(0); i++)
{
    for (int j = 0; j < bricks.GetLength(1); j++)
    {
        if (bricks[i, j].Visible == true) // 若磚塊是可見的話…,表示磚塊尚未被擊中
        {
            if (ball.Left >= bricks[i, j].Left && ball.Left <= (bricks[i, j].Left + bricks[i, j].Width) && ball.Top <= (bricks[i, j].Top + bricks[i, j].Height))
            {
                Y_Inc = -Y_Inc;
                bricks[i, j].Visible = false; //判定擊中後,球的垂直移動方向改變,並將磚塊的Visible屬性設false,使其看不見
                goto HitBrickExit; //一旦擊中,就不用測試其他的磚塊…,跳離這個測試以節省時間
            }
        }
    }
}
HitBrickExit:

另外,再加上球掉出下邊界外的處理:

//球掉到下邊界外,也就是球拍沒擊中球…,遊戲結束
if (ball.Top >= (this.Bounds.Height - ball.Height))
{
    timer1.Enabled = false; //中止計時器1
    lblGameOver.Visible = true; //顯示出遊戲結束訊息
}

再加上幾個按鍵的處理:

private void Form1_KeyDown(object sender, KeyEventArgs e)
{
    if (e.KeyCode == Keys.Left) Cursor.Position = new Point(Cursor.Position.X - 20, Cursor.Position.Y);
    if (e.KeyCode == Keys.Right) Cursor.Position = new Point(Cursor.Position.X + 20, Cursor.Position.Y);
    if (e.KeyCode == Keys.Escape) this.Close(); //按Esc離開遊戲
    if (e.KeyCode == Keys.F1) //重新開始遊戲
    {
        ball.Left = (this.Bounds.Width - ball.Width) / 2; //球位置,置於視窗中心
        ball.Top = (this.Bounds.Height - ball.Height) / 2;
        timer1.Enabled = true; //重新啟動計時器1
        lblGameOver.Visible = false; //遊戲訊息隱藏
        place_bricsk(); //重新排列所有的磚塊
    }
}

遊戲錄影:

音效的處理請參考下一篇:【程式設計】打磚塊遊戲 – 播放背景音樂與音效 (同時)

問題、意見、遊戲改進提議等歡迎在文章下面的迴響交流。謝謝。

過河問題:農夫、羊、狼、青菜

過河問題:農夫、羊、狼、青菜

過河問題:農夫、羊、狼、青菜

一名農夫帶著一隻狼、一隻羊和一顆高麗菜渡河,河上只有一艘船,一次只能載農夫和一樣東西到對岸,只有農夫會/能划船,如果農夫沒看著,狼會吃羊,羊會吃菜,農夫要如何成功的將狼、羊、高麗菜帶到另一岸,而不會有損失?

程式畫面

【程式列表】

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        string farmerLocation = "L"; //農夫的位置,一開始在左邊

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            lstBoxL.Enabled = true; L.Text = "西岸(可選取)";
            lstBoxR.Enabled = false; R.Text = "東岸(不可選取)";
        }

        /*
         * 當按了往右邊移動,有三個狀況:
         * 1.若農夫已在右邊,不需要移動,離開
         * 2.只有農夫自己一人過河
         * 3.農夫帶著一物過河,選取的物品從左邊移入右邊(Add),並從左邊移除(Remove)
         * 
         * 2與3共同步驟:
         * 設定農夫位值在右邊
         * 對調左右2個清單方塊的可選取狀態
         */
        private void btn2Right_Click(object sender, EventArgs e) //按下往右移動
        {
            if (farmerLocation == "R") return; //如果農夫已在右邊的話就離開

            if (lstBoxL.SelectedItem != null) //若左邊方塊有選取
            {
                lstBoxR.Items.Add(lstBoxL.SelectedItem); //將左邊方塊的選取項目加到右邊方塊
                lstBoxL.Items.Remove(lstBoxL.SelectedItem); //將左邊方塊選取項目移除掉(移到另一邊的意思)
            }
            lstBoxL.Enabled = false; L.Text = "西岸(不可選取)";
            lstBoxR.Enabled = true; R.Text = "東岸(可選取)";

            btnFarmer.Location = new Point(312, 120); //移動農夫 
            farmerLocation = "R"; //標記農夫的位置在右邊
        }

        private void btn2Left_Click(object sender, EventArgs e)
        {
            if (farmerLocation == "L") return; //如果農夫已在右邊的話就離開

            if (lstBoxR.SelectedItem != null)
            {
                lstBoxL.Items.Add(lstBoxR.SelectedItem);
                lstBoxR.Items.Remove(lstBoxR.SelectedItem);
            }
            lstBoxR.Enabled = false; R.Text = "東岸(不可選取)";
            lstBoxL.Enabled = true; L.Text = "西岸(可選取)";

            btnFarmer.Location = new Point(192, 120);
            farmerLocation = "L";
        }

        private void button1_Click(object sender, EventArgs e)
        {  //取消二邊清單方塊的選取
            lstBoxL.SelectedItem = null;
            lstBoxR.SelectedItem = null;
        }
    }
}

【程式英文】

1.ListBox 清單方塊

2.Item 項目 (清單方塊裏的)

3.Items 項目集合

4.Enabled  啟用的

5.SelectedItem 選取的項目

6.null 未指定/空

7.Location 位置

8.point 點

【遊戲判斷】

1.測試清單方塊裏的項目是否同時出現? 當農夫在另一邊時,此時,遊戲失敗!

a.羊和高麗菜,羊會吃了高麗菜

b.狼和羊,狼會吃了羊

2.若羊、狼、高麗菜同時出現在右邊清單方塊時,此時,遊戲過關!

【程式判斷是否失敗】

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApplication2
{
    public partial class Form1 : Form
    {
        string farmerLocation = "L"; //農夫的位置,一開始在左邊

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            lstBoxL.Enabled = true; L.Text = "西岸(可選取)";
            lstBoxR.Enabled = false; R.Text = "東岸(不可選取)";
        }

        private void lstBoxL_SelectedIndexChanged(object sender, EventArgs e)
        {

        }

        private bool isGameFailed()
        {
            bool hasSheep = false, hasWolf = false, hasCab = false;
            if (farmerLocation == "L")
            {
                foreach (object o in lstBoxR.Items)
                {
                    string s = o.ToString();
                    if (s == "羊") hasSheep = true;
                    if (s == "狼") hasWolf = true;
                    if (s == "高麗菜") hasCab = true;
                }

            } else
            {
                foreach (object o in lstBoxL.Items)
                {
                    string s = o.ToString();
                    if (s == "羊") hasSheep = true;
                    if (s == "狼") hasWolf = true;
                    if (s == "高麗菜") hasCab = true;
                }


            }

            bool gameFailed = false;
            if (hasSheep && hasCab) gameFailed = true;
            if (hasSheep && hasWolf) gameFailed = true;
            return gameFailed;
        }

        private void btn2Left_Click(object sender, EventArgs e)
        {
            if (farmerLocation == "L") return; //如果農夫已在右邊的話就離開

            if (lstBoxR.SelectedItem != null)
            {
                lstBoxL.Items.Add(lstBoxR.SelectedItem);
                lstBoxR.Items.Remove(lstBoxR.SelectedItem);
            }
            lstBoxR.Enabled = false; R.Text = "東岸(不可選取)";
            lstBoxL.Enabled = true; L.Text = "西岸(可選取)";

            btnFarmer.Location = new Point(168, 101);
            farmerLocation = "L";

            if (isGameFailed()) MessageBox.Show("You losed!");
        }

        private void btn2Right_Click(object sender, EventArgs e)
        {
            if (farmerLocation == "R") return; //如果農夫已在右邊的話就離開

            if (lstBoxL.SelectedItem != null) //若左邊方塊有選取
            {
                lstBoxR.Items.Add(lstBoxL.SelectedItem); //將左邊方塊的選取項目加到右邊方塊
                lstBoxL.Items.Remove(lstBoxL.SelectedItem); //將左邊方塊選取項目移除掉(移到另一邊的意思)
            }
            lstBoxL.Enabled = false; L.Text = "西岸(不可選取)";
            lstBoxR.Enabled = true; R.Text = "東岸(可選取)";
            
            btnFarmer.Location = new Point(312, 101); //移動農夫 
            farmerLocation = "R"; //標記農夫的位置在右邊

            if (isGameFailed()) MessageBox.Show("You losed!");
        }

        private void button1_Click(object sender, EventArgs e)
        {
            //取消二邊清單方塊的選取
            lstBoxL.SelectedItem = null;
            lstBoxR.SelectedItem = null;
        }
    }
}