首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

WinForm 入門小遊戲:坦克大戰 — Button 控制項、ImageList、多執行緒、碰撞檢測基礎實作

     編輯:關於.NET

前言

用 WinForm 控制項 而非繪圖介面 實作簡易坦克遊戲,適合初學者理解控制項操作、鍵盤事件、ImageList 動畫、多執行緒非同步移動、基本碰撞邏輯。本篇完整提供程式碼、圖片資源需求、執行步驟與擴充方向。

遊戲設計概覽

元素實作方式
坦克本體Button 控制項 (設定 ImageList、FlatStyle=Flat、BackColor=Transparent)
四方向圖片ImageList 元件 (4 張圖:上、下、左、右,索引 0-3)
移動控制KeyPress 事件 (W/A/S/D 對應上/左/下/右)
發射子彈K 鍵 → 動態建立 Label (紅色小方塊) + 獨立執行緒移動
子彈移動ParameterizedThreadStart 傳遞 (Label, 方向) → 迴圈移動 + Sleep
邊界檢測螢幕座標超出螢幕範圍 → 執行緒結束、Dispose Label
右鍵選單ContextMenuStrip → 結束遊戲

準備工作

  1. 建立 Windows Forms App (.NET Framework) 專案
  2. 工具箱拖入:Button (name: btnTank)、ImageList (name: imgTank)、ContextMenuStrip (name: cmsMain)
  3. 準備 4 張坦克圖片 (建議 48x48 px,透明背景 PNG):tank_up.png, tank_down.png, tank_left.png, tank_right.png
  4. ImageList 設定:ImageSize = 48, 48、ColorDepth = Depth32Bit、依序加入 4 張圖 (索引 0=下、1=右、2=左、3=上)
  5. Button 設定:ImageList = imgTank, ImageIndex = 0, FlatStyle = Flat, FlatAppearance.BorderSize = 0, BackColor = Transparent, KeyPreview = True (表單層級)
  6. ContextMenuStrip 加入項目「結束遊戲」→ Click 事件呼叫 Application.Exit()
  7. Form 設定:ContextMenuStrip = cmsMain, FormBorderStyle = None (可選全螢幕)、WindowState = Maximized

完整程式碼 (Form1.cs)


using System;
using System.Collections;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;

namespace TankBattle
{
    public partial class Form1 : Form
    {
        private int _screenWidth;
        private int _screenHeight;
        private string _currentDirection = "down"; // 預設朝下

        public Form1()
        {
            InitializeComponent();
            
            // 取得螢幕尺寸
            _screenWidth = Screen.PrimaryScreen.Bounds.Width;
            _screenHeight = Screen.PrimaryScreen.Bounds.Height;
            
            // 允許跨執行緒存取控制項 (教學簡化,正式建議用 Invoke)
            Control.CheckForIllegalCrossThreadCalls = false;
            
            // 表單鍵盤預覽
            this.KeyPreview = true;
            this.KeyPress += Form1_KeyPress;
            
            // 初始坦克位置置中
            btnTank.Left = (_screenWidth - btnTank.Width) / 2;
            btnTank.Top = (_screenHeight - btnTank.Height) / 2;
        }

        private void Form1_KeyPress(object sender, KeyPressEventArgs e)
        {
            int step = 10; // 移動步距
            switch (char.ToLower(e.KeyChar))
            {
                case 'w': // 上
                    _currentDirection = "up";
                    btnTank.ImageIndex = 3;
                    btnTank.Top = Math.Max(0, btnTank.Top - step);
                    break;
                case 's': // 下
                    _currentDirection = "down";
                    btnTank.ImageIndex = 0;
                    btnTank.Top = Math.Min(_screenHeight - btnTank.Height, btnTank.Top + step);
                    break;
                case 'a': // 左
                    _currentDirection = "left";
                    btnTank.ImageIndex = 2;
                    btnTank.Left = Math.Max(0, btnTank.Left - step);
                    break;
                case 'd': // 右
                    _currentDirection = "right";
                    btnTank.ImageIndex = 1;
                    btnTank.Left = Math.Min(_screenWidth - btnTank.Width, btnTank.Left + step);
                    break;
                case 'k': // 發射
                    FireBullet();
                    break;
                case 'x': // 退出 (備選)
                    Application.Exit();
                    break;
            }
        }

        private void FireBullet()
        {
            // 建立子彈 Label
            var bullet = new Label
            {
                BackColor = Color.Red,
                Size = new Size(4, 4),
                // 子彈從坦克中心發射
                Left = btnTank.Left + btnTank.Width / 2 - 2,
                Top = btnTank.Top + btnTank.Height / 2 - 2,
                Visible = true
            };
            this.Controls.Add(bullet);
            bullet.BringToFront();

            // 參數打包
            var args = new ArrayList { bullet, _currentDirection };

            // 建立並啟動執行緒
            var thread = new Thread(new ParameterizedThreadStart(BulletThreadProc));
            thread.IsBackground = true; // 主程式結束時自動終止
            thread.Start(args);
        }

        private void BulletThreadProc(object parameters)
        {
            var args = (ArrayList)parameters;
            var bullet = (Label)args[0];
            var direction = (string)args[1];
            bool outOfBounds = false;

            while (!outOfBounds)
            {
                // 依方向移動
                switch (direction)
                {
                    case "up":    bullet.Top -= 8; break;
                    case "down":  bullet.Top += 8; break;
                    case "left":  bullet.Left -= 8; break;
                    case "right": bullet.Left += 8; break;
                }

                // 邊界檢測
                if (bullet.Top < 0 || bullet.Top > _screenHeight ||
                    bullet.Left < 0 || bullet.Left > _screenWidth)
                {
                    outOfBounds = true;
                }

                Thread.Sleep(30); // 移動速度控制
            }

            // 清理資源
            try
            {
                if (bullet.IsHandleCreated) bullet.Invoke(new Action(bullet.Dispose));
            }
            catch { /* 忽略 */ }
            Thread.CurrentThread.Abort();
        }

        private void 結束遊戲ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Application.Exit();
        }
    }
}

執行畫面與操作

  • W / S / A / D:坦克上下左右移動,圖片自動切換方向
  • K:發射紅色子彈 (Label),沿當前方向飛行直到螢幕邊界消失
  • 右鍵 → 結束遊戲:離開程式
  • X:鍵盤快速退出

已知限制與改進方向

限制改進方向
Control.CheckForIllegalCrossThreadCalls = false 不安全改用 bullet.Invoke(() => bullet.Top += 8) 或 BeginInvoke 回 UI 執行緒更新
子彈無碰撞檢測、無敵人、無分數加入敵人坦克 (Button)、碰撞判斷 bullet.Bounds.IntersectsWith(enemy.Bounds)、分數 Label
執行緒每顆子彈一條,大量子彈效能差改用單一 Timer/Task 迴圈統一更新所有子彈位置 (物件池模式)
畫面閃爍、無雙重緩衝表單設定 DoubleBuffered = true、或改用 Panel + Paint 事件繪圖
無遊戲迴圈、幀率不固定引入 System.Windows.Forms.Timer (約 60fps) 統一更新邏輯與繪圖

進階擴充:最小遊戲迴圈重構


// 1. 定義遊戲物件基底
abstract class GameObject
{
    public Rectangle Bounds { get; protected set; }
    public abstract void Update();
    public abstract void Draw(Graphics g);
}

// 2. 坦克、子彈、敵人繼承 GameObject
// 3. 主遊戲迴圈 (Timer Tick 事件)
private void GameLoop_Tick(object sender, EventArgs e)
{
    // 更新邏輯
    foreach (var obj in _gameObjects) obj.Update();
    // 碰撞檢測
    CheckCollisions();
    // 觸發重繪
    gamePanel.Invalidate();
}

// 4. Panel Paint 事件統一繪圖
private void GamePanel_Paint(object sender, PaintEventArgs e)
{
    foreach (var obj in _gameObjects) obj.Draw(e.Graphics);
}

教學重點總結

  1. 控制項即遊戲物件:Button、Label、PictureBox 皆可當作 Sprite,適合入門理解座標、Bounds、Visible。
  2. ImageList 動畫:透過 ImageIndex 切換幀,無需手動載入圖片。
  3. 鍵盤事件:KeyPress (字元) vs KeyDown/KeyUp (鍵碼),遊戲常用 KeyDown 支援長按連發。
  4. 多執行緒 UI 更新:必須回 UI 執行緒 (Invoke/BeginInvoke),CheckForIllegalCrossThreadCalls = false 僅供教學。
  5. 參數化執行緒:ParameterizedThreadStart 傳遞多參數 (物件陣列/ArrayList/Tuple)。
  6. 資源清理:動態建立控制項必須 Dispose(),否則記憶體洩漏。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved