从零开发游戏需要学习的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();
}

对象池的核心思想

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

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

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

最后我们来总结一下

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

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

相关推荐
咖啡八杯2 天前
GoF设计模式——备忘录模式
java·后端·spring·设计模式
槑有老呆2 天前
从 Prompt Engineering 到 Harness Engineering:AI 编程的下一次跃迁
设计模式
HjhIron2 天前
从Prompt到Context:大模型应用开发的范式转移
设计模式·aigc·ai编程
咖啡八杯4 天前
GoF设计模式——中介者模式
java·后端·spring·设计模式
hez20104 天前
在 .NET 上构建超大托管数组
c#·.net·.net core·gc·clr
胡萝卜术4 天前
从“分数打架”到“排名投票”:为什么你的ChatBI必须用RRF?
算法·设计模式·面试
亦暖筑序5 天前
Java 8老系统Browser Agent实战:三层拦截把AI操作后台变成可审计流程
java·后端·设计模式
金銀銅鐵6 天前
[Python] 模 n 乘法的逆元计算器
python·数学·游戏
金銀銅鐵7 天前
借助 Pygame 探索最大公约数的规律
python·数学·游戏
青禾网络7 天前
Web 前端如何接入 AI 音效生成:从零到可用的完整方案
人工智能·设计模式