从零开发游戏需要学习的c#模块,第十章(设计模式入门)

什么是设计模式?

设计模式就像武术中的"套路"或"招式"。不是每个动作都要自己发明,前人总结了无数战斗经验,归纳出一套套打法。遇到某种敌人(问题),用对应的招式(模式)来应对。

在编程中,设计模式是解决特定问题的经典代码结构。它不是新语法,而是经验的总结。

游戏开发中最常用的有四种:单例模式、状态模式、观察者模式、对象池模式

1. 单例模式 ------ 全局唯一的管家

游戏里有很多"唯一"的东西------GameManager(游戏管理器)、AudioManager(音效管理器)、UIManager(界面管理器)。它们全局只需要一个,多了会乱套。

单例模式保证一个类只有一个实例,并提供全局访问点。

using System;

namespace MyGame

{

// 游戏管理器,必须是唯一的

class GameManager

{

// ★ 静态变量:存储唯一实例

private static GameManager instance;

// ★ 公开属性:外部通过它来获取实例

public static GameManager Instance

{

get

{

// 如果还没创建,就创建一个

if (instance == null)

{

instance = new GameManager();

}

return instance;

}

}

// ★ 私有构造函数:禁止外部用 new 创建

private GameManager()

{

Console.WriteLine("GameManager 诞生了!全局只有我一个。");

}

// 游戏状态数据

public int Score { get; set; }

public int Level { get; set; }

public bool IsGameOver { get; set; }

// 业务方法

public void AddScore(int points)

{

Score += points;

Console.WriteLine($"得分:{Score}");

}

public void NextLevel()

{

Level++;

Console.WriteLine($"进入第 {Level} 关!");

}

}

}

如何使用单例:

cs 复制代码
class Program
{
    static void Main(string[] args)
    {
        // 任何地方,通过 Instance 来访问唯一的 GameManager
        GameManager.Instance.AddScore(100);
        GameManager.Instance.NextLevel();
        GameManager.Instance.AddScore(50);

        Console.WriteLine($"最终得分:{GameManager.Instance.Score}");
        Console.WriteLine($"当前关卡:{GameManager.Instance.Level}");

        Console.ReadKey();
    }
}
  • private static GameManager instance:静态变量,属于类本身而不是某个对象,用来存唯一实例。

  • public static GameManager Instance:全局访问点,外部只通过它来拿实例。

  • private GameManager():私有构造函数,阻止外界用 new 创建第二个实例。

这种方法是相当于当程序调用该程序时,程序才开始生成,还有一种方法,在程序启动时代码就开始生成

cs 复制代码
private static GameManager instance = new GameManager();
public static GameManager Instance => instance;

2. 状态模式 ------ 角色的行为切换

角色有"待机、奔跑、跳跃、攻击"等状态。用 if-else 来判断会非常混乱:

这样的话,每当你添加一种状态时,就得修改大量代码,所以我们需要一种新的方法。

IState.cs ------ 状态接口:

cs 复制代码
namespace MyGame
{
    // 所有状态必须实现这个接口
    interface IState
    {
        void Enter();   // 进入状态时调用一次
        void Update();  // 每帧调用
        void Exit();    // 离开状态时调用一次
    }
}

IdleState.cs ------ 待机状态:

cs 复制代码
using System;

namespace MyGame
{
    class IdleState : IState
    {
        public void Enter()
        {
            Console.WriteLine("站定不动,四处张望...");
        }

        public void Update()
        {
            Console.WriteLine("  (呼吸中...)");
        }

        public void Exit()
        {
            Console.WriteLine("提起精神!");
        }
    }
}

RunningState.cs ------ 奔跑状态:

cs 复制代码
using System;

namespace MyGame
{
    class RunningState : IState
    {
        public void Enter()
        {
            Console.WriteLine("迈开双腿,开始奔跑!");
        }

        public void Update()
        {
            Console.WriteLine("  (哒哒哒...)");
        }

        public void Exit()
        {
            Console.WriteLine("停下脚步。");
        }
    }
}

AttackingState.cs ------ 攻击状态:

cs 复制代码
using System;

namespace MyGame
{
    class AttackingState : IState
    {
        public void Enter()
        {
            Console.WriteLine("⚔️ 挥动武器!");
        }

        public void Update()
        {
            Console.WriteLine("  (收招中...)");
        }

        public void Exit()
        {
            Console.WriteLine("攻击动作结束。");
        }
    }
}

Player.cs ------ 玩家类(持有状态):

cs 复制代码
using System;

namespace MyGame
{
    class Player
    {
        private IState currentState;

        public Player()
        {
            // 初始状态:待机
            currentState = new IdleState();
            currentState.Enter();
        }

        // 切换状态
        public void ChangeState(IState newState)
        {
            // 先退出当前状态
            currentState.Exit();
            // 设置新状态
            currentState = newState;
            // 进入新状态
            currentState.Enter();
        }

        // 每帧更新,委托给当前状态
        public void Update()
        {
            currentState.Update();
        }

        // 快捷方法
        public void Idle()  { ChangeState(new IdleState()); }
        public void Run()   { ChangeState(new RunningState()); }
        public void Attack(){ ChangeState(new AttackingState()); }
    }
}

最后就是

Program.cs 测试:

cs 复制代码
using System;

namespace MyGame
{
    class Program
    {
        static void Main(string[] args)
        {
            Player player = new Player();

            Console.WriteLine("\n--- 玩家开始行动 ---\n");

            player.Update(); // 待机中

            Console.WriteLine("\n[按下方向键]");
            player.Run();
            player.Update(); // 奔跑中

            Console.WriteLine("\n[按下攻击键]");
            player.Attack();
            player.Update(); // 攻击中

            Console.WriteLine("\n[攻击结束,自动回待机]");
            player.Idle();
            player.Update();

            Console.ReadKey();
        }
    }
}

状态模式的好处

  • 每个状态是独立的类,互不干扰。

  • 加新状态(如"跳跃")只需新建一个类,不改其他代码。

  • 避免了复杂的 if-else 嵌套。

3. 观察者模式

这个我们已经学习过了,我们来复习一下(委托和事件)

主题(敌人) 观察者(成就系统)

| |

|-- 订阅列表 ←------- 注册

| |

|-- 触发事件 → ------ 收到通知

4. 对象池模式 ------ 重复利用,减少垃圾

射击游戏每秒生成几十颗子弹,打中后销毁。频繁的 new 和垃圾回收会导致游戏卡顿。

对象池预先创建一批对象,用完不销毁,而是"休眠",下次再用时"唤醒"。

cs 复制代码
using System;
using System.Collections.Generic;

namespace MyGame
{
    // 池中的对象
    class Bullet
    {
        public bool IsActive { get; private set; }

        public void Activate()
        {
            IsActive = true;
            Console.WriteLine("子弹发射!");
        }

        public void Deactivate()
        {
            IsActive = false;
            Console.WriteLine("子弹回收。");
        }

        public void Fly()
        {
            if (IsActive)
            {
                Console.WriteLine("  (飞过屏幕...)");
            }
        }
    }

    // 对象池
    class BulletPool
    {
        private List<Bullet> pool;
        private int maxSize;

        public BulletPool(int size)
        {
            maxSize = size;
            pool = new List<Bullet>(size);

            // 预先创建所有子弹
            for (int i = 0; i < size; i++)
            {
                pool.Add(new Bullet());
            }
            Console.WriteLine($"子弹池已创建,共 {size} 发子弹。\n");
        }

        // 从池中获取一个可用子弹
        public Bullet GetBullet()
        {
            foreach (Bullet bullet in pool)
            {
                if (!bullet.IsActive)
                {
                    bullet.Activate();
                    return bullet;
                }
            }
            Console.WriteLine("子弹用完了!");
            return null;
        }

        // 回收子弹
        public void ReturnBullet(Bullet bullet)
        {
            bullet.Deactivate();
        }
    }
}

测试:

cs 复制代码
static void Main(string[] args)
{
    BulletPool pool = new BulletPool(3); // 只有3发子弹的池

    Bullet b1 = pool.GetBullet(); // 取第一发
    Bullet b2 = pool.GetBullet(); // 取第二发
    Bullet b3 = pool.GetBullet(); // 取第三发
    Bullet b4 = pool.GetBullet(); // 没有了!

    Console.WriteLine("\n回收两发子弹...");
    pool.ReturnBullet(b1);
    pool.ReturnBullet(b2);

    Console.WriteLine("\n再次射击:");
    Bullet b5 = pool.GetBullet(); // 拿到回收的子弹
    Bullet b6 = pool.GetBullet(); // 拿到回收的子弹

    Console.ReadKey();
}

对象池的核心思想

  • 用完不丢,回收再利用。

  • 避免频繁创建/销毁造成的性能开销。

  • 在子弹、特效、敌人等大量重复对象上必须使用

最后我们来总结一下

模式 解决什么问题 一句话
单例 全局唯一的对象 "天下只有我一个"
状态 对象在不同状态间切换 "现在该干什么,就干什么"
观察者 一个变化通知多方 "出事了!通知所有相关的人"
对象池 频繁创建销毁开销大 "回收再利用,别乱丢"

好了,今天的学习内容到此为止,关注我,下期更精彩

相关推荐
V搜xhliang02463 小时前
AI智能体的数据安全与合规实践
人工智能·学习·数据分析·自动化·ai编程
无敌的牛3 小时前
redis学习过程
数据库·redis·学习
z落落5 小时前
C#WinForm 窗体切换与窗体传值(登录跳转案例)+WinForm 窗体传值(从上往下传、从下往上传)
开发语言·windows·c#
旅僧6 小时前
Π环境部署(运行 且 无理论讲解)
学习
jushi89996 小时前
Lucas Chess R国际象棋、中国象棋、日本将棋、五子棋训练学习工具游戏软件
学习
自传.6 小时前
尚硅谷 Vibe Coding|第一章 AI 编程基础理论 学习笔记
笔记·学习·尚硅谷·vibe coding
吃好睡好便好7 小时前
改变时间轴的跨度
学习·生活
fox_lht7 小时前
15.3.改进我们之前的输入、输出项目
开发语言·后端·学习·rust
chase。7 小时前
【学习笔记】SimpleVLA-RL:通过强化学习扩展 VLA 训练
笔记·学习
众乐乐_20088 小时前
用claude Fabel5一句话生成的游戏:三国天命(有资源包)
游戏