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

对象池的核心思想

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

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

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

最后我们来总结一下

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

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

相关推荐
用户356302904871 小时前
【设计模式】组合模式——树形结构的统一处理
设计模式
GEO从入门到精通1 小时前
GEO学习能帮我提高AI搜索排名吗?
人工智能·学习
ᰔᩚ. 一怀明月ꦿ1 小时前
MySQL 学习目标
学习·mysql·adb
吃好睡好便好1 小时前
用if…end…语句计算分段函数
开发语言·人工智能·学习·算法·matlab
风继续吹..2 小时前
C# 文件 IO 实操练习题 5道
开发语言·c#
爱睡懒觉的焦糖玛奇朵2 小时前
【从视频到数据集:焦糖玛奇朵的魔法工具Video To YOLO Dataset】
人工智能·python·学习·yolo·音视频
苦荞米2 小时前
C#中,Ticks不能作为时间戳使用。
c#
2501_940041742 小时前
探索非主流游戏机制的AI生成指南
人工智能·游戏
Dust-Chasing2 小时前
Claude Code源码剖析 - Phase3
开发语言·人工智能·学习