什么是设计模式?

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

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

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

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} 关!");
        }
    }
}

如何使用单例:

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 创建第二个实例。

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

private static GameManager instance = new GameManager();
public static GameManager Instance => instance;

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

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

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

IState.cs —— 状态接口:

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

IdleState.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 —— 奔跑状态:

using System;

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

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

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

AttackingState.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 —— 玩家类(持有状态):

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 测试:

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 和垃圾回收会导致游戏卡顿。

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

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();
        }
    }
}

测试:

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();
}

对象池的核心思想:

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

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

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

最后我们来总结一下

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

“回收再利用,别乱丢”

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

更多推荐