本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:“打地鼠游戏源代码(C#)”是一个基于C#语言开发的轻量级游戏项目,旨在通过实践提升编程技能。作为面向对象、类型安全的编程语言,C#广泛应用于Windows平台的应用与游戏开发。该项目虽为个人娱乐之作且尚未完善,但完整展示了游戏开发的基本结构,包括游戏逻辑、用户界面、类设计、资源管理、输入处理和时间控制等核心模块。源码开放有利于学习者理解C#在游戏开发中的应用,适合初学者进行二次开发与功能扩展,如添加计分系统、难度等级、音效支持等,是掌握C#桌面应用与小游戏开发的优质入门案例。

C# 打地鼠游戏开发实战:从面向对象设计到完整架构落地 🎮

在智能家居设备日益复杂的今天,确保无线连接的稳定性已成为一大设计挑战。等等……这好像不是我们要聊的内容?😄 咳咳,抱歉手滑了——我们今天要讲的可不是物联网芯片,而是 用 C# 从零开始打造一个经典又有趣的打地鼠游戏!

别小看这个“老古董”级别的小游戏,它背后藏着现代软件工程的核心思想: 封装、继承、多态、事件驱动、MVC 架构、状态机、定时调度、资源管理…… 听起来是不是有点吓人?别担心,咱们就一边敲代码,一边把这些高大上的术语变成你指尖流淌的逻辑流 😎。

准备好了吗?让我们拿起 Visual Studio,开启这场“锤子与地鼠”的编程之旅吧!🔨🐭


🧱 类与对象:给每只地鼠发一张身份证

想象一下,你面前有 9 个洞口,每个洞口都可能钻出一只地鼠。它们长得差不多,但位置不同、出现时间不同、甚至行为也可能不一样(比如有的会逃跑,有的会反击)。那我们怎么在程序里表示它们?

答案就是: 类(Class)和对象(Object)

我们可以定义一个 Mole 类,作为所有地鼠的“蓝图”:

public class Mole
{
    public PictureBox HostControl { get; set; } // 绑定哪个 PictureBox 显示我?
    public bool IsVisible { get; private set; }   // 我现在露头了吗?

    public void Show()
    {
        HostControl.Visible = true;
        IsVisible = true;
    }

    public void Hide()
    {
        HostControl.Visible = false;
        IsVisible = false;
    }
}

💡 小贴士 :这里的 IsVisible 使用了 private set ,意味着外部只能读取它的值,不能直接修改。想让我消失?请调用 Hide() 方法——这就是 封装性 的魅力!

然后呢?实例化九个对象就行了:

var mole1 = new Mole { HostControl = picHole1 };
var mole2 = new Mole { HostControl = picHole2 };
// ...一直到 mole9

你看,每只地鼠都有自己的“身份”,互不干扰,却又遵循同一套规则。是不是很像现实世界中的“个体差异 + 共同规律”?

而且更妙的是,未来你想加个“超级地鼠”怎么办?简单,继承一下就行:

public class SuperMole : Mole
{
    public override void Hide()
    {
        // 延迟0.5秒再隐藏,气死你!
        Task.Delay(500).ContinueWith(_ => base.Hide());
    }
}

这样一来,主控制器根本不需要知道它是普通地鼠还是超级地鼠,统一调用 .Hide() 就行了——这就是 多态性 的实际应用!


🔔 事件与委托:让 UI 和逻辑说拜拜👋

接下来是个关键问题:用户点击了一个洞口,该谁来处理?

如果让 PictureBox 直接去改分数、播音效、更新UI……那整个系统就会变成一团乱麻,牵一发而动全身。

聪明的做法是: 解耦!

C# 提供了强大的 事件(Event)和委托(Delegate)机制 ,完美解决这个问题。

我们可以在 Mole 类中声明一个事件:

public event EventHandler<MoleEventArgs> OnHit;

private void PictureBox_Click(object sender, EventArgs e)
{
    // 只有正在露头的地鼠才能被打中
    if (IsVisible)
    {
        OnHit?.Invoke(this, new MoleEventArgs(HitScore)); // 触发事件!
    }
}

注意这里传了个自定义参数类:

public class MoleEventArgs : EventArgs
{
    public int Score { get; }
    public MoleEventArgs(int score) => Score = score;
}

那么谁来响应这个事件呢?当然是负责全局管理的 GameController 啦!

mole.OnHit += (sender, args) =>
{
    gameState.AddScore(args.Score);
    audioManager.Play("hit.wav");
    particleSystem.SpawnAt(((Mole)sender).Position);
};

看到没?地鼠只管“我被打了”,至于加分、放特效、播音乐,那是别人的事。这种“发布-订阅”模式让模块之间几乎零依赖,后期维护爽到飞起 ✈️!


🏗️ 整体架构设计:搭好骨架才能撑得起血肉

现在我们知道怎么造零件了,但怎么把这些零件组装成一辆能跑的车?

这就需要清晰的 系统架构设计

对于打地鼠这种小型桌面游戏,推荐采用经典的 MVC 模式 (Model-View-Controller),把数据、界面和逻辑彻底分开。

🔄 MVC 是什么?一张图说明一切

graph TD
    A[玩家点击] --> B(GameController)
    B --> C[更新 GameState]
    C --> D{通知变化}
    D --> E[UIManager 更新 Label/PictureBox]
    D --> F[AudioManager 播放音效]
    D --> G[ParticleSystem 发射粒子]
    E --> H[玩家看到结果]
  • Model(模型) GameState Mole 等类,存的是数据。
  • View(视图) :WinForms 上的控件,负责展示。
  • Controller(控制器) GameController ,协调一切。

这样做的好处显而易见:
- 改皮肤?只动 View。
- 调难度?只改 Model。
- 加新功能?Controller 插个回调就行。

完全不会互相污染,团队协作也轻松多了。


🔍 核心类职责一览表

类名 干啥的?
GameController 大脑中枢,启动游戏、调度地鼠、处理输入
Mole 单个地鼠的行为建模
GameState 存分数、时间、关卡等全局状态
UIManager 包装所有对控件的操作,避免到处写 label.Text=

举个例子, UIManager 长这样:

public class UIManager
{
    private readonly Label _scoreLabel;
    private readonly Label _timeLabel;
    private readonly PictureBox[] _holes;

    public UIManager(Label score, Label time, PictureBox[] holes)
    {
        _scoreLabel = score;
        _timeLabel = time;
        _holes = holes;
    }

    public void UpdateScore(int score) 
        => _scoreLabel.Text = $"得分: {score}";

    public void ShowMole(int index, Image img)
    {
        if (index >= 0 && index < _holes.Length)
            _holes[index].Image = img;
    }
}

以后哪怕你把 WinForms 换成 WPF 或 Unity,只要接口不变,其他模块根本不用改!


⚙️ 主循环与流程控制:游戏的心跳引擎

很多新手以为游戏就是一堆按钮点击事件拼凑起来的,其实不然。

真正流畅的游戏必须有一个持续运行的“心跳”——也就是 主循环(Game Loop)

虽然 WinForms 本身是事件驱动的,但我们可以通过 Timer 模拟出类似 Unity 的 Update/Render 分离结构:

private Timer gameLoop;

public void StartGameLoop()
{
    gameLoop = new Timer();
    gameLoop.Interval = 16; // ~60 FPS
    gameLoop.Tick += GameLoop_Update;
    gameLoop.Start();
}

private void GameLoop_Update(object sender, EventArgs e)
{
    UpdateLogic();   // 更新地鼠状态、倒计时等
    Render();        // 刷新画面
}

📊 主循环工作流

graph LR
    A[启动] --> B{是否进行中?}
    B -- 是 --> C[更新逻辑]
    C --> D[渲染UI]
    D --> E[等待下一帧]
    E --> B
    B -- 否 --> F[暂停或结束]

这个循环就像永不停歇的齿轮,推动着整个游戏向前运转。

你可以在这个循环里做很多事情:
- 检查地鼠是否超时未击中;
- 更新连击计数器;
- 控制背景音乐节奏;
- 甚至模拟简单的物理效果!


🕹️ 地鼠行为建模:让它活得像个真家伙

最怕的就是地鼠“机械式”地一个个轮流出现,毫无挑战性。我们需要让它看起来像是随机、不可预测的。

怎么做?两个关键词: 随机算法 + 时间调度

🔁 随机出现机制

我们设定每次地鼠出现的时间间隔在 800ms ~ 2000ms 之间波动,并且最多同时出现 3 只:

private Random _rand = new Random();
private Timer _spawnTimer;

private void StartSpawning()
{
    _spawnTimer = new Timer();
    _spawnTimer.Interval = GetNextSpawnDelay(); // 随机延迟
    _spawnTimer.Tick += SpawnRandomMole;
    _spawnTimer.Start();
}

private int GetNextSpawnDelay() 
    => _rand.Next(800, 2000);

private void SpawnRandomMole(object sender, EventArgs e)
{
    var visibleCount = moles.Count(m => m.IsVisible);
    if (visibleCount >= 3) return; // 上限控制

    var hiddenOnes = moles.Where(m => !m.IsVisible).ToList();
    if (!hiddenOnes.Any()) return;

    var target = hiddenOnes[_rand.Next(hiddenOnes.Count)];
    target.Show();

    // 重新设置下次生成时间
    _spawnTimer.Interval = GetNextSpawnDelay();
}

这样出来的节奏忽快忽慢,玩家根本摸不准规律,紧张感立马拉满!


🎭 状态机加持:让地鼠也有情绪

你以为地鼠只会“出现 → 被打 → 消失”?太单调了!

我们可以引入 状态枚举 ,让它拥有完整的生命周期:

public enum MoleState
{
    Hidden,     // 藏着
    Emerging,   // 正在冒出
    Visible,    // 完全露出,可被击中
    Hit,        // 被命中
    Retreating  // 缩回去
}

配合状态切换动画:

public void Appear()
{
    State = MoleState.Emerging;
    UpdateVisual(); // 显示冒头动画

    // 300ms 后完全露出
    DelayAction(300, () => 
    {
        if (State == MoleState.Emerging)
        {
            State = MoleState.Visible;
            UpdateVisual();
        }
    });
}

还可以加上“被打中后抖一下”、“缩回前闪个红光”之类的细节,瞬间提升品质感 💯!


🖱️ 输入响应与命中判定:快准狠才是王道

玩家最讨厌的就是“我明明点到了,为啥不算分?”——所以我们的点击检测必须又快又准。

✅ 如何绑定点击事件?

两种方式:

方式一:逐个绑定(推荐)
foreach (var mole in moles)
{
    mole.HostControl.Click += (_, __) => HandleClick(mole);
}

优点:直观、调试方便、性能好。

方式二:容器监听(适合动态布局)
panel.MouseClick += (s, e) =>
{
    var ctrl = panel.GetChildAtPoint(e.Location);
    var mole = moles.FirstOrDefault(m => m.HostControl == ctrl);
    if (mole != null) HandleClick(mole);
};

无论哪种方式,核心逻辑都在 HandleClick 里:

private void HandleClick(Mole mole)
{
    if (CurrentPhase != GamePhase.Playing) return;

    if (mole.State == MoleState.Visible || mole.State == MoleState.Emerging)
    {
        mole.OnHit(); // 触发事件

        combo++;
        AddScore(CalculateScore(combo));

        PlayHitEffect(mole);
        ShakeScreen(); // 小震动增强打击感
    }
}

🎯 连击奖励算法:鼓励连续输出

为了激励玩家保持专注,我们加入连击系统:

private Dictionary<int, int> ComboBonuses = new()
{
    { 3, 50 },  // 连击3次额外+50
    { 5, 100 },
    { 10, 200 }
};

private void CheckComboBonus()
{
    if (ComboBonuses.TryGetValue(combo, out int bonus))
    {
        AddScore(bonus);
        FlashLabel(lblScore, Color.Gold); // 金色闪烁提示
        PlaySound("bonus.wav");
    }
}

当连击达到阈值时,不仅分数暴涨,还有视觉+听觉双重反馈,那种成就感简直不要太爽!🎉


🎨 UI 设计与资源管理:好看又好用

再强的内核,配上丑陋的界面也会劝退玩家。所以我们得好好打扮一下这位“数字地鼠”。

🖼️ 图像资源嵌入式打包

不想让用户下载一堆 .png 文件?那就把图片做成 嵌入式资源 吧!

操作步骤:
1. 把图片放进 Resources/ 文件夹;
2. 在属性面板设置“生成操作 = Embedded Resource”;
3. 代码中通过流加载:

private Image LoadEmbeddedImage(string name)
{
    return Image.FromStream(
        Assembly.GetExecutingAssembly()
                .GetManifestResourceStream($"WhackAMole.Resources.{name}")
    );
}

// 使用
pic.Image = LoadEmbeddedImage("mole_up.png");

这样编译完就是一个独立的 .exe ,随便拷贝都能玩!


🔊 音效同步播放技巧

短音效建议用 SoundPlayer 异步播放:

private SoundPlayer hitSound = new SoundPlayer(Properties.Resources.hit_wav);

private void PlayHitSound() => hitSound.Play(); // 非阻塞

如果是背景音乐,可以用 PlayLooping() 实现循环播放。

千万别用 PlaySync() ,否则主线程会被卡住,界面直接冻结 ❌。


🚀 性能优化小技巧

WinForms 默认绘图容易闪屏?加一行双缓冲搞定:

this.SetStyle(
    ControlStyles.AllPaintingInWmPaint |
    ControlStyles.UserPaint |
    ControlStyles.DoubleBuffer,
    true
);

刷新频率也不宜过高,16ms(60FPS)足够了,太高反而浪费 CPU。


🔧 可扩展接口预留:为未来留扇门

好的架构不仅要满足当前需求,还得为将来铺路。

📦 高分榜持久化存储

用 JSON 记录历史成绩:

public class HighScoreEntry
{
    public string Name { get; set; }
    public int Score { get; set; }
    public DateTime Time { get; set; }
}

// 保存
File.WriteAllText("scores.json", 
    JsonConvert.SerializeObject(entries, Formatting.Indented));

下次打开自动加载,还能加个排行榜界面,社交属性++!


🎚️ 配置参数集中管理

不要到处写魔法数字!建个配置类:

public static class GameConfig
{
    public static int MinSpawnInterval = 800;
    public static int MaxSpawnInterval = 2000;
    public static int MaxActiveMoles = 3;
    public static int GameDurationSeconds = 60;
    public static int BaseScorePerHit = 10;
}

以后调难度?改几个数就行,不用翻遍代码找常量。


📝 日志调试支持

开发阶段开启 Trace 输出:

Trace.WriteLine($"[DEBUG] Mole spawned at {DateTime.Now:HH:mm:ss}");

在 Visual Studio 的“输出”窗口就能实时看到运行轨迹,比 MessageBox 强多了。


🧪 最终集成流程图:一切尽在掌握

让我们看看整个游戏是如何一步步启动并运行的:

flowchart TD
    A[启动程序] --> B[加载资源配置]
    B --> C{加载成功?}
    C -- 否 --> D[弹出错误提示]
    C -- 是 --> E[初始化UI控件]
    E --> F[创建 GameController]
    F --> G[绑定事件监听]
    G --> H[显示主菜单]
    H --> I[用户点击开始]
    I --> J[进入 Playing 状态]
    J --> K[启动主循环 Timer]
    K --> L[随机生成地鼠]
    L --> M[等待用户点击]
    M --> N[判定是否命中]
    N --> O[更新分数 & 特效]
    O --> P{时间到?}
    P -- 否 --> L
    P -- 是 --> Q[切换至 GameOver]
    Q --> R[显示结算界面]
    R --> S[保存高分记录]
    S --> T[等待用户选择重试/退出]

这张图涵盖了从启动到结束的完整生命周期,任何一个环节都可以单独测试和替换。


🌟 写在最后:为什么 C# 是桌面游戏的好伙伴?

说了这么多,你可能会问:为什么不选 Python?为什么不用 Unity?

答案很简单: 对于中小型 2D 桌面游戏,C# + WinForms 的组合简直是黄金搭档!

开发效率极高 :拖控件 + 写逻辑,几分钟就能跑通原型。
语法现代化 :LINQ、async/await、属性、事件……应有尽有。
类型安全 :编译期就能发现大部分错误,不怕运行时报空指针。
BCL 库丰富 :文件、网络、序列化、加密……开箱即用。
部署简单 :单文件发布,绿色运行,无需安装框架(.NET 6+ Self-contained)。

更重要的是,它教会你如何用 面向对象思维 去构建一个真实可用的系统——而这,正是成为高级开发者的关键一步。


🎯 所以,别再只写“Hello World”了!
拿起锤子,敲响属于你的第一行游戏代码吧!💥

“伟大的程序员,往往都是从打地鼠开始的。” —— 某不愿透露姓名的CTO 😏


项目源码已托管 GitHub github.com/example/whack-a-mole-csharp
📥 欢迎 Fork & Star,一起打造最强打地鼠引擎!

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:“打地鼠游戏源代码(C#)”是一个基于C#语言开发的轻量级游戏项目,旨在通过实践提升编程技能。作为面向对象、类型安全的编程语言,C#广泛应用于Windows平台的应用与游戏开发。该项目虽为个人娱乐之作且尚未完善,但完整展示了游戏开发的基本结构,包括游戏逻辑、用户界面、类设计、资源管理、输入处理和时间控制等核心模块。源码开放有利于学习者理解C#在游戏开发中的应用,适合初学者进行二次开发与功能扩展,如添加计分系统、难度等级、音效支持等,是掌握C#桌面应用与小游戏开发的优质入门案例。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐