设计模式之游戏--观察者模式详解

引入问题

在我们的游戏开发中,我们常常会遇见这种场景,比如在战斗系统中如果敌人死亡,我们要通知UI系统,成就系统,关卡系统等一些其它系统做出相应的行为。你可能做出如下设计

方法1:让战斗系统亦或者敌人持有其它系统的的引用,然后在敌人死亡时调用其它系统的相应方法。
方法2:让各系统时时监听敌人的相应状态,敌人死亡时调用系统的相应方法

这两种方法看起来没多大毛病,但仔细分析我们就就会发现其实是有所欠缺的。
方法1:当我们后期需求变更,增加了一个新的系统或者一个新的功能,那么我们就需要修改战斗系统或者敌人的代码,使之符合我们的要求,但这显然不符合我们的开闭原则。
方法2:时时监听,在我们的Unity具体来说就是Update(),FixedUpdate()等函数,在这些函数写入过多的诸如此类的判断,很可能会引发性能瓶颈,常见的就是卡顿,掉帧。

为了解决上述诸如此类的问题,我们引入的观察者模式。

定义

它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。当这个主题对象在状态变化时,会通知所有的观察者对象并使它们能够自动更新。所以说这种模式有时又称作发布-订阅模式。

UML

在这里插入图片描述

Subject:抽象主题(抽象被观察者),抽象主题对象把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供相应接口,可以增加和删除观察者对象。
ConcreteSubject:具体主题(具体被观察者),它实现抽象目标中的通知方法,在具体主题的对应状态发生改变时,给所有注册过的观察者发送通知。
Observer:抽象观察者,是观察者者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。
ConcrereObserver:具体观察者,实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。

具体示例:

下面我们以游戏中的敌人死亡后,通知各个系统做出相应响应为例, 实现观察者模式。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;


public abstract class GameEventSubjects
{
    protected List<GameEventObserver> observers = new List<GameEventObserver>();

    public virtual void AddOBS(GameEventObserver observer)
    {
        observers.Add(observer);
    }
    public virtual void RemoveOBS(GameEventObserver observer)
    {
        observers.Add(observer);
    }
    public abstract void Notify();
   
}

public class EnemyDeathSubject : GameEventSubjects
{
    public override void Notify()
    {
        foreach (var item in observers)
        {
            item.Update();
        }
    }
}



public abstract class GameEventObserver
{
    protected string system;
    public GameEventObserver(string system)
    {
        this.system = system;
    }

    public abstract void Update();

}

/// <summary>
/// 关卡系统订阅者
/// </summary>
public class EnemyDeathObs_StageSys : GameEventObserver
{
    public EnemyDeathObs_StageSys(string system) : base(system)
    {
    }
    public override void Update()
    {
        MonoBehaviour.print(system + "接收敌人死亡消息,调用"+ system+"的判断方法,是否进入下一关");
    }
}


/// <summary>
/// UI系统订阅者
/// </summary>
public class EnemyDeathObs_UISys : GameEventObserver
{
    public EnemyDeathObs_UISys(string system) : base(system)
    {
    }
    public override void Update()
    {
        MonoBehaviour.print(system + "接收敌人死亡消息,调用" + system + "的更新方法");
    }
}


/// <summary>
/// 成就系统订阅者
/// </summary>
public class EnemyDeathObs_ArchievementSys : GameEventObserver
{
    public EnemyDeathObs_ArchievementSys(string system) : base(system)
    {
    }
    public override void Update()
    {
        MonoBehaviour.print(system + "接收敌人死亡消息,调用" + system + "的记录方法");
    }
}

public class Client_0 : MonoBehaviour
{

    private void Start()
    {
        EnemyDeathSubject enemyDeathEvent = new EnemyDeathSubject();

        GameEventObserver enemyDeathObs_StageSys = new EnemyDeathObs_StageSys("关卡系统");
        GameEventObserver enemyDeathObs_UISys = new EnemyDeathObs_UISys("UI系统");
        GameEventObserver enemyDeathObs_ArchievementSys = new EnemyDeathObs_ArchievementSys("成就系统");
        enemyDeathEvent.AddOBS(enemyDeathObs_StageSys);
        enemyDeathEvent.AddOBS(enemyDeathObs_UISys);
        enemyDeathEvent.AddOBS(enemyDeathObs_ArchievementSys);
        enemyDeathEvent.Notify();
    }
}

优点:

1.降低了耦合度:通过观察我们发现,观察者和被观察者都是依赖于抽象 ,它们都通过抽象进行联系,复合我们的依赖倒置原则。
2.符合我们的开闭原则:当我们后期引进的新功能与主题状态变化有关时,只需要新建相应的观察者类。

缺点:

在顺序执行通知观察者时,也就是这个函数

  public override void Notify()
    {
        foreach (var item in observers)
        {
            item.Update();
        }
    }

那么一个观察者卡顿,会影响整体的执行效率,所以说在这种情况下,一般会采用异步实现。

使用场景:

1.跨系统的消息交换场景,如我们上面的那个示例相当于就相当于战斗系统和UI,成就,关卡等系统的消息交换。
2.一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。如我们上面的那个示例,UI敌人数量的显示,成就中累积杀敌数的更新,是否进入下一个关卡都依赖于敌人死亡这个事件。

在Unity中的使用

很常见的就是我们的UI事件的监听,比如我们的按钮, button.onClick.AddListener()这个方法就是为某个按钮添加我们的观察更新方法。

最后如果本文有什么不妥之处还望指出,谢谢!

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值