【程式設計】【C#】【遊戲設計】利用背景捲動實現遊戲場景的動態變化

馬利歐遊戲大概是最家喻互曉的遊戲了:

一般來說,遊戲由靜態的元素與動作組成:

遊戲的靜態元素:

  1. 角色
  2. 場景
  3. 阻礙物、暗樁
  4. 敵人(魔王)
  5. 蘑菇 (好、壞)

遊戲的動態元素

  1. 角色的移動、奔跑
  2. 魔王的移動、奔跑

我們要如何做這樣的遊戲呢?

我們先參考這些紙板遊戲:

一、要開始製作遊戲,我們要有角色、背景材料,所幸下面網站提供了絕佳的製作材料:

  • 遊戲資源,背景圖、角色圖(動作分解圖)、音效等遊戲資源

我們從上面的資源來準備我們要的圖、音效檔。

下載圖片,並用小畫家(或其他繪圖軟體,例PhotoShop)處理圖片,剪下需要的部份。

若圖片需要去背處理(去掉白色/其他顏色背景),在線上搜尋”線上去背”網站來處理去背的工作。

二、接下來,我們開始設計程式,開啟Visual C#。

加入PictureBox元件放置背景圖片

指定Image,載入背景圖片

設定Location為(0,0),讓PictureBox置於Form1左上角。

設定SizeMode為”AutoSize”,讓PictureBox自動設定大小為載入圖片的大小。

加入PictureBox元件放置角色圖片

指定Image,載入角色圖片,角色圖片必須為去背景圖片,才能融入背景畫面。

設定SizeMode為”StretchImahe”,讓角色圖片自動縮放為PictureBox元件的大小。

畫面如下:

此時,角色圖片雖為去背圖片,但是並未融入背景,嗯,我們要在程式加上二條指令:

private void Form1_Load(object sender, EventArgs e)
{
    角色.Parent = 遊戲背景;  //將角色圖片方塊的Parent屬性設為遊戲背景圖片方塊,Parent表示上一層,父層…
    遊戲背景.BackColor = Color.Transparent; //將戲背景的BackColor設為透明,
}

執行時,畫面就能變成完美的遊戲畫面了:

三、接下來,我們要處理奔跑的動作

我們觀察馬利歐遊戲的動作發現,馬利歐的移動分成二個部份,慢速移動與快速移動,當慢速移動時(走),是角色在畫面中移動,當快速移動時(跑),角色固定在螢幕中間,以背景左右移動的方式,產生角色奔跑的效果。

在這邊,我們要處理當玩家按了左鍵與右鍵的對應動作,當玩家按左鍵,我們移動背景圖片向右(與角色動作相反方向),產生角色向左跑的效果;當玩家按右鍵,我們移動背景圖片向左,產生角色向右跑的效果。

背景圖片向右就是將背景圖片的Left屬性加一個固定大小的移動值,比如5(要快一點的話,增量為10)。

背景圖片向左就是將背景圖片的Left屬性減一個固定大小的移動值,比如5(要快一點的話,減量為10)。

OK,了解整個動作的大概後,我們來加上程式碼在表單的KeyDown事件。

int 移動量 = 5;

private void Form1_KeyDown(object sender, KeyEventArgs e)
{
    if (e.KeyCode == Keys.Right) //按了右鍵的話
    {
        角色.Location = new Point(角色.Location.X + 移動量, 角色.Location.Y); //一旦角色的Parent屬性設為遊戲背景圖片時,角色圖片會跟著跑,因此,需要進行水平位置的移動,使得角色保持中間位置
        遊戲背景.Left -= 移動量; //背景向左                       
    }
    if (e.KeyCode == Keys.Left) //按了左鍵的話
    {
        角色.Location = new Point(角色.Location.X - 移動量, 角色.Location.Y);
        遊戲背景.Left += 移動量; //背景向右                
    }
}

上面程式也同時處理角色的移動,原因是一旦角色的Parent屬性設為遊戲背景圖片時,角色圖片會跟著跑,因此,需要進行水平位置的移動,使得角色保持中間位置。

上面的程式,尚未處理背景捲動時,左右二邊的狀況:

因此,下面的程式處理了二邊的狀況:

int 移動量 = 10;

private void Form1_KeyDown(object sender, KeyEventArgs e)
{
    if (e.KeyCode == Keys.Right && 遊戲背景.Left > (this.ClientSize.Width - 遊戲背景.Width)) //按了右鍵的話 而且 …
    {
        角色.Location = new Point(角色.Location.X + 移動量, 角色.Location.Y); //一旦角色的Parent屬性設為遊戲背景圖片時,角色圖片會跟著跑,因此,需要進行水平位置的移動,使得角色保持中間位置
        遊戲背景.Left -= 移動量; //背景向左                       
    }
    if (e.KeyCode == Keys.Left && 遊戲背景.Left < 0) //按了左鍵的話 而且 遊戲背景.Left 超過 畫面左邊的情況下
    {
        角色.Location = new Point(角色.Location.X - 移動量, 角色.Location.Y);
        遊戲背景.Left += 移動量; //背景向右                
    }
}

參考:

Part 1 – Movements – http://www.youtube.com/watch?v=Cc62S6…

Part 2 – Jumping – http://www.youtube.com/watch?v=X1lfRI…

Part 3 – Collision – http://www.youtube.com/watch?v=vBT5Gl…

Part 4 – Character Design – http://www.youtube.com/watch?v=EMOpp1…

【程式設計-C#】乒乓遊戲

【程式設計-C#】乒乓遊戲

【程式設計-C#】乒乓遊戲

【重點1】球的移動

【重點2】球拍的移動

【重點3】測定球碰觸邊界

【重點4】測定球拍是否擊中球,沒擊中的話…

 

程式利用計時器(timer1),每隔一段時間執行幾件工作:

1.依據目前游標(Cursor)的位置來移動球拍(racket)

2.將球(ball)的位置(left, top)各加上(speed_left, speed_top),left是水平方向(左右),top是垂直方向(上下)

3.判斷球拍是否擊中球(也就是說ball的left, bottom, top, right是否落於racket的left, bottom, top, right內)

若是的話,改變球往下的方向,變成往上的方向

改變球移動速度,也就是增加speed_left, speed_top,讓球移動的距離增加

得分+1,更新計分版

4.判斷球是否碰到右、左、上邊界,是的話,改變方向,左變右,右變左,上變下

5.若球超出下邊界,遊戲中止,跳出訊息視窗

未來加上幾個特性:加上背景音樂 (參考資料)、背景色變換、隨機變換球移動的速度…

【V 1.1 平滑版】【專案檔下載:PingPong-smooth

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;
using System.Media;

namespace PingPong
{

    public partial class playground : Form
    {
        int speed_left = 5; //球的水平變換速度
        int speed_top = 5; //球的垂直變換速度
        int point = 0; //得分

        int ball_Left = 50, ball_Top = 50, ball_Width = 48, ball_Height = 48;
        int racket_Left = 0, racket_Top = 0, racket_Width = 150, racket_Height = 20;
        Bitmap bmp = new Bitmap("mushroom.png");
        private SoundPlayer player;

        public playground()
        {
            InitializeComponent();

            player = new SoundPlayer(@"C:\audio_output\2.wav");
            player.PlayLooping(); //背景音樂
            timer1.Enabled = true; //啟動計時器1
            Cursor.Hide(); //將游標隱藏起來
            this.FormBorderStyle = FormBorderStyle.None; //將視窗的邊框設成"無",使視窗的邊框(連同標題列)消失
            this.TopMost = true; //將視窗設為最上層
            this.Bounds = Screen.PrimaryScreen.Bounds; //將視窗設為全螢幕

            racket_Top = this.Bottom - (this.Bottom / 10); //設定球拍的垂直位置
            lblGameOver.Top = (this.Height / 2) - (lblGameOver.Height / 2); //置中遊戲結束的訊息文字
            lblGameOver.Left = (this.Width / 2) - (lblGameOver.Width / 2);
        }

        private void playBom() //播放撞壁時的音樂副程式
        {
            var player1 = new WMPLib.WindowsMediaPlayer();
            player1.URL = @"C:\audio_output\1.wav"; //撞擊聲
        }

        private void timer1_Tick(object sender, EventArgs e) //計時器1的跳動事件
        {
            racket_Left = Cursor.Position.X - (racket_Width / 2); //將球拍的中心位置 設為 游標的水平位置

            ball_Left += speed_left; //移動球-水平
            ball_Top += speed_top; //移動球-垂直

            if ((ball_Top + ball_Height) >= racket_Top && ball_Left >= racket_Left) //判斷球拍是否擊中球
            {
                Random rnd = new Random();

                int r = rnd.Next(1, 10);

                if (speed_top >= 0) speed_top += r; //加速
                else speed_top -= r;

                if (speed_left >= 0) speed_left += r;
                else speed_left -= r;

                speed_top = -speed_top; //改變球的移動方向
                point += 1; //得分+1
                lblPoints.Text = point.ToString();

                playBom();

            }

            if (ball_Left <= this.Left)
            {
                speed_left = -speed_left; //若球的左邊緣在左邊界的左邊,變換水平移動的方向
                playBom();
            }
            if (( ball_Left+ball_Width) >= this.Right)
            {
                speed_left = -speed_left;
                playBom();
            }
            if (ball_Top <= this.Top)
            {
                speed_top = -speed_top; //球超出上邊界…
                playBom();
            }
                if ((ball_Top + ball_Height) >= this.Bottom)  //球出界,遊戲結束
            {
                timer1.Enabled = false; //中止計時器1
                lblGameOver.Visible = true; //顯示出遊戲結束訊息
            }
            Invalidate();
        }

        private void PingPong_KeyDown(object sender, KeyEventArgs e) //當按下按鍵時…
        {
            if (e.KeyCode == Keys.Escape) this.Close(); //按Esc離開遊戲
            if (e.KeyCode == Keys.F1) //重新開始遊戲
            {
                ball_Top = 50;
                ball_Left = 50;
                speed_left = 5;
                speed_top = 5;
                point = 0;
                lblPoints.Text = "0";
                timer1.Enabled = true;
                lblGameOver.Visible = false;
            }

        }

        private void playground_Paint(object sender, PaintEventArgs e)
        {
            e.Graphics.FillRectangle(Brushes.BlueViolet, racket_Left, racket_Top, racket_Width, racket_Height);
            e.Graphics.DrawImage(bmp, ball_Left, ball_Top, 64, 64);
        }
    }
}

【V 1.0】【專案檔下載: PingPong

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 PingPong
{
    public partial class PingPong : Form
    {
        public int speed_left = 5; //球的水平變換速度
        public int speed_top = 5; //球的垂直變換速度
        public int point = 0; //得分

        public PingPong()
        {
            InitializeComponent();

            timer1.Enabled = true; //啟動計時器1
            Cursor.Hide(); //將游標隱藏起來
            this.FormBorderStyle = FormBorderStyle.None; //將視窗的邊框設成"無",使視窗的邊框(連同標題列)消失
            this.TopMost = true; //將視窗設為最上層
            this.Bounds = Screen.PrimaryScreen.Bounds; //將視窗設為全螢幕

            racket.Top = playground.Bottom - (playground.Bottom / 10); //設定球拍的垂直位置
            lblGameOver.Top = (playground.Height / 2) - (lblGameOver.Height / 2); //置中遊戲結束的訊息文字
            lblGameOver.Left = (playground.Width / 2) - (lblGameOver.Width / 2);
        }

        private void timer1_Tick(object sender, EventArgs e) //計時器1的跳動事件
        {
            racket.Left = Cursor.Position.X - (racket.Width / 2); //將球拍的中心位置 設為 游標的水平位置
            ball.Left += speed_left; //移動球-水平
            ball.Top += speed_top; //移動球-垂直

            if (ball.Top <= racket.Top && ball.Bottom >= racket.Bottom && ball.Left >= racket.Left && ball.Right <= racket.Right) //判斷球拍是否擊中球
            {
                speed_top += 2; //加速
                speed_left += 2;
                speed_top = -speed_top; //改變球的移動方向
                point += 1; //得分+1
                lblPoints.Text = point.ToString();
            }

            if (ball.Left <= playground.Left) speed_left = -speed_left; //若球的左邊緣在左邊界的左邊,變換水平移動的方向
            if (ball.Right >= playground.Right) speed_left = -speed_left;
            if (ball.Top <= playground.Top) speed_top = -speed_top; //球超出上邊界…
            if (ball.Bottom >= playground.Bottom)  //球出界,遊戲結束
            {
                timer1.Enabled = false; //中止計時器1
                lblGameOver.Visible = true; //顯示出遊戲結束訊息
            }

        }

        private void PingPong_KeyDown(object sender, KeyEventArgs e) //當按下按鍵時…
        {
            if (e.KeyCode == Keys.Escape) this.Close(); //按Esc離開遊戲
            if (e.KeyCode == Keys.F1) //重新開始遊戲
            {
                ball.Top = 50;
                ball.Left = 50;
                speed_left = 5;
                speed_top = 5;
                point = 0;
                lblPoints.Text = "0";
                timer1.Enabled = true;
                lblGameOver.Visible = false;
            }

        }
    }
}

 

 

【Another 乒乓遊戲】

Pingpong-2