什么是设计模式?
设计模式就像武术中的"套路"或"招式"。不是每个动作都要自己发明,前人总结了无数战斗经验,归纳出一套套打法。遇到某种敌人(问题),用对应的招式(模式)来应对。
在编程中,设计模式是解决特定问题的经典代码结构。它不是新语法,而是经验的总结。
游戏开发中最常用的有四种:单例模式、状态模式、观察者模式、对象池模式。
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();
}
对象池的核心思想:
-
用完不丢,回收再利用。
-
避免频繁创建/销毁造成的性能开销。
-
在子弹、特效、敌人等大量重复对象上必须使用
最后我们来总结一下
| 模式 | 解决什么问题 | 一句话 |
|---|---|---|
| 单例 | 全局唯一的对象 | "天下只有我一个" |
| 状态 | 对象在不同状态间切换 | "现在该干什么,就干什么" |
| 观察者 | 一个变化通知多方 | "出事了!通知所有相关的人" |
| 对象池 | 频繁创建销毁开销大 | "回收再利用,别乱丢" |
好了,今天的学习内容到此为止,关注我,下期更精彩