游戏中的成就系统,我们一般会使用设计模式中的哪种模式来制作?为什么?

在游戏中,成就系统通常会使用观察者模式或策略模式来实现,具体取决于系统的复杂度和扩展性需求。

目录

观察者模式

策略模式

其他可能的模式


观察者模式

观察者模式定义了对象之间的一对多依赖关系,当一个对象(主题)的状态发生变化时,所有依赖它的对象(观察者)都会被自动通知并更新。

适用场景:

  • 当成就系统需要实时监听玩家行为(如击杀敌人、收集道具、完成任务等)并触发条件判断时。
  • 当多个成就可能共享同一行为(如 "击杀 100 只怪物" 和 "连续击杀 5 只怪物")时。

实现逻辑:

  • 将玩家行为(如 "击杀""收集")作为被观察者(Subject) ,成就系统作为观察者(Observer)。
  • 当玩家触发行为时,被观察者通知所有注册的观察者(成就系统)进行条件检查。
  • 成就系统根据预设规则判断是否达成成就,并执行奖励逻辑。

优点:

  • 解耦事件触发与处理:玩家行为与成就系统独立,便于后续扩展新成就。
  • 高效响应:实时监听和触发,适合需要即时反馈的游戏。

事件中心代码示例:

cs 复制代码
        private readonly Dictionary<E_EventType, EventBase> _eventAction = new ();
        private EventCenter() { }
        // 用于表示无参事件的空类型
        public struct Empty { }
        private bool IsEmptyEventType<T>()=>typeof(T) == typeof(Empty);
        private void TriggerEmptyEvent(EventBase value)
        {
            if (value is EventInfo eventInfo) eventInfo.Action?.Invoke();
        }
        private void TriggerTypedEvent<T>(EventBase value, T info)
        {
            if (value is EventInfo<T> typedEventInfo)typedEventInfo.Action?.Invoke(info);
        }
        private void AddEmptyEventAction<T>(E_EventType name, UnityAction<T> eventAction)
        {
            if (_eventAction[name] is EventInfo eventInfo)
                try
                {
                    eventInfo.Action += () => eventAction(default);
                }
                catch (Exception ex)
                {
                    Debug.LogError($"在添加无参事件 {name} 时发生异常: {ex.Message}");
                }
        }
        private void AddTypedEventAction<T>(E_EventType name, UnityAction<T> eventAction)
        {
            if (_eventAction[name] is EventInfo<T> typedEventInfo)
                typedEventInfo.Action += eventAction;
        }
        private void RemoveEmptyEventAction<T>(EventBase value, UnityAction<T> eventAction)
        {
            if (value is EventInfo eventInfo)
                try
                {
                    eventInfo.Action -= () => eventAction(default);
                }
                catch (Exception ex)
                {
                    Debug.LogError($"在移除无参事件时发生异常: {ex.Message}");
                }
        }
        private void RemoveTypedEventAction<T>(EventBase value, UnityAction<T> eventAction)
        {
            if (value is EventInfo<T> typedEventInfo)
                typedEventInfo.Action -= eventAction;
        }
        private void CreateEventInfo<T>(E_EventType name)
        {
            if (IsEmptyEventType<T>()) _eventAction.Add(name, new EventInfo());
            else _eventAction.Add(name, new EventInfo<T>());
        }
        /// <summary>
        /// 触发事件,支持有参和无参事件
        /// </summary>
        /// <typeparam name="T">事件参数类型</typeparam>
        /// <param name="name">事件的名字</param>
        /// <param name="info">事件参数,无参事件时传入 default(T)</param>
        public void EventTrigger<T>(E_EventType name, T info = default)
        {
            if (!_eventAction.TryGetValue(name, out EventBase value))
            {
                Debug.LogWarning($"未找到事件 {name},无法触发。");
                return;
            }
            if (IsEmptyEventType<T>()) TriggerEmptyEvent(value);
            else TriggerTypedEvent(value, info);
        }
        /// <summary>
        /// 添加事件的方法,支持有参和无参事件
        /// </summary>
        /// <typeparam name="T">事件参数类型</typeparam>
        /// <param name="name">事件的名字</param>
        /// <param name="eventAction">具体实现事件的方法</param>
        public void AddEventAction<T>(E_EventType name, UnityAction<T> eventAction)
        {
            if (eventAction == null)
            {
                Debug.LogError($"尝试添加 null 的事件处理方法到事件 {name}");
                return;
            }
            if (!_eventAction.ContainsKey(name))
                CreateEventInfo<T>(name);
            if (IsEmptyEventType<T>()) AddEmptyEventAction(name, eventAction);
            else AddTypedEventAction(name, eventAction);
        }
        /// <summary>
        /// 移除事件的方法,支持有参和无参事件
        /// </summary>
        /// <typeparam name="T">事件参数类型</typeparam>
        /// <param name="name">事件的名字</param>
        /// <param name="eventAction">具体移除事件的方法</param>
        public void RemoveEventAction<T>(E_EventType name, UnityAction<T> eventAction)
        {
            if (eventAction == null)
            {
                Debug.LogError($"尝试移除 null 的事件处理方法从事件 {name}");
                return;
            }
            if (!_eventAction.TryGetValue(name, out EventBase value))
            {
                Debug.LogWarning($"未找到事件 {name},无法移除。");
                return;
            }
            if (IsEmptyEventType<T>())
                RemoveEmptyEventAction(value, eventAction);
            else RemoveTypedEventAction(value, eventAction);
        }
        /// <summary>
        /// 清空事件中心的方法
        /// </summary>
        /// <param name="isAll">是否全部清空</param>
        /// <param name="name">清空的事件名字</param>
        public void ClearEventCenter(bool isAll = true, E_EventType name = E_EventType.E_EventTest)
        {
            if (isAll) _eventAction.Clear();
            else if (_eventAction.ContainsKey(name))
                _eventAction.Remove(name);
        }

策略模式

策略模式也有可能,因为不同的成就可能有不同的判定策略。比如,有的成就需要累积次数,有的需要特定时间内完成,使用策略模式可以方便地替换不同的条件检查方法,提高扩展性。

状态模式可能不太适用,因为成就系统更多是条件触发,而不是状态转换。不过,如果成就有不同的阶段,比如进行中、完成,可能需要状态管理,但主要问题还是条件触发。

适用场景:

  • 当成就系统包含多种复杂条件(如 "30 秒内通关""不使用技能通关")时。
  • 当需要灵活切换或组合不同成就判定逻辑时。

实现逻辑:

  • 将每个成就的条件判定封装为独立的策略类,例如 "累积计数策略""限时挑战策略"。
  • 成就系统根据成就类型动态调用对应的策略类进行条件验证。
  • 不同策略之间可自由替换,互不影响。

优点:

  • 扩展性强:新增成就只需添加新策略类,无需修改现有代码。
  • 逻辑清晰:复杂条件分离为独立策略,便于维护和调试。

在实际开发中,观察者模式和策略模式常结合使用:

  1. 观察者模式负责监听玩家行为并触发事件。
  2. 策略模式负责具体的成就条件判定。

例如:

  • 玩家击杀敌人时,观察者通知成就系统。
  • 成就系统根据成就类型(如 "总击杀数" 或 "连续击杀数")选择对应的策略类进行判断。

其他可能的模式

  • 工厂模式(Factory Pattern):用于创建不同类型的成就对象。
  • 组合模式(Composite Pattern):用于处理组合条件(如 "同时满足 A 和 B 条件")。

但核心逻辑仍以观察者和策略模式为主。(关于使用这两种模式结合的成就系统以及其他可能的设计模式设计的成就系统,后续会根据具体案例出一篇额外的博客,详细说明,敬请期待)

相关推荐
小羊没烦恼!4 天前
初探性能优化——2个月到4小时的性能提升
java·开发语言·windows·算法·c#
神仙别闹4 天前
基于C#+MySQL实现(WinForm)个人聊天室软件
c#
晨米酱4 天前
AGENTS.md:Agent 的上下文策略层
面试·架构·agent
彧azz4 天前
Linux 环境下 Redis 学习总结:数据类型、持久化、锁、事务、主从与缓存问题
linux·redis·笔记·学习·面试
kybs19914 天前
全球灾害数据分析可视化 毕业设计-附源码66794
vue.js·spring boot·mysql·安全·django·c#·asp.net
CoderYanger5 天前
A.每日一题:835. 图像重叠
java·开发语言·程序人生·leetcode·面试·职场和发展·学习方法
JQweryuiop5 天前
中小型游戏陪练工作室数字化管理实践:基于七彩屋电竞护航小程序订单、履约与绩效系统的落地复盘
大数据·游戏·小程序·架构
爱学习的执念5 天前
助力金九银十1000道软件测试面试题(功能、接口、自动化、WEB、APP.......)附答案
软件测试·面试
小的~~5 天前
银河麒麟V10 ARM部署TDengine(涛思)工业时序数据库安装与测试
面试·程序员创富
Zane19945 天前
一个 new 就能创建对象,为什么还要拆出单例、工厂、建造者、原型四种模式?
设计模式