【《游戏编程模式》实战03】观察者模式实现更新背包物品和解锁技能

1、观察者模式:

适合多个无关的系统通信时使用,它使得代码能够发出一个消息,并通知对消息感兴趣的对象,而不用关心具体是谁接收到了通知。

观察者系统和事件系统的区别:观察者系统观察某个系统对象里发生的一些事,事件系统观察系统里的某个事件对象是否发生

2、使用工具:

Unity2022.3.10f1c1、visualstudio2019

3、实现内容:

抱着要找两个无关系统观察另一个系统的想法,选择了做背包系统、技能系统观察到物理系统里玩家拾取物品后,更新背包里的物品并解锁技能的功能(并没有完整实现,只是想象了一个应用场景)

4、代码及思路:

本来想照着书上写的那个逻辑做的,但是比较奇怪的一点是书上的被观察者知道观察者的存在,观察者却不知道被观察者的存在(即subject类里有observer类的指针,而observer类里没有subject),并且想着要结合C#的event关键字来做,所以逻辑改为了观察者observer知道被观察者subject的存在,被观察者subject不知道观察者observer的存在。

在C#里要实现这个非常简单,用委托就行了,我这里选择了Action,其实Delegate、Event、Func都行,如果需要在Inspector里添加方法的话用UnityEvent更好。可参考我的这篇C#中的delegate、event、action及匿名函数(lambda表达式)_c# event action-CSDN博客

写了三个脚本,一个subject类,两个Observer类,除此以外还有一个控制玩家移动的Player类,功能是玩家去触碰到场景里的小球就会发出消息告知BagObserver类和SkillObserve类根据类型捡起(销毁)它,并在控制台输出

Player类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(Rigidbody))]
[RequireComponent(typeof(Collider))]
public class Player : MonoBehaviour
{
    public float moveSpeed=1.0f;
    private void OnValidate()//强制设定值保证player能进行碰撞检测
    {
        GetComponent<Rigidbody>().isKinematic = true;
        GetComponent<Collider>().isTrigger = true;
    }
    void Update()
    {
        // 获取输入的水平和垂直轴值
        float moveHorizontal = Input.GetAxis("Horizontal");
        float moveVertical = Input.GetAxis("Vertical");

        // 计算移动方向
        Vector3 movement = new(moveHorizontal, 0.0f, moveVertical);

        // 移动玩家
        transform.Translate(movement * moveSpeed * Time.deltaTime, Space.World);
    }
}

这个脚本已经把一些需要的碰撞体组件以及属性设置好了 

Subject类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
public class Subject : MonoBehaviour
{
    public Action<GameObject> Pickup;

    private void OnTriggerEnter(Collider other)
    {
        //Debug.Log(other.name);
        Pickup?.Invoke(other.gameObject);//检查pickup是否为空再调用   
    }
}

 我使用Action委托来将观察者方法变量化,Subject脚本挂在Player物体上,Player触碰到其他碰撞体时就会发出消息(执行Pickup上的所有方法引用),但是并不关心谁收到了消息

BagObserver类
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BagObserver : MonoBehaviour
{
    public Subject subject;
    private void Start()
    {
        subject.Pickup += PickUpProp;//注意不使用“=”来赋值,因为会覆盖原来的方法
    }
    public void PickUpProp(GameObject prop)
    {
        
        if (prop.CompareTag("Prop"))
        {

            //和ui连起来
            Debug.Log("Pick up prop");
            Destroy(prop);
        }
    }
   

}
SkillObserver类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SkillObserver : MonoBehaviour
{
    public Subject subject;
    private void Start()
    {
        subject.Pickup += PickUpSkill;
    }
    public void PickUpSkill(GameObject skill)
    {
        if (skill.CompareTag("Skill"))
        {
            //和ui连起来
            Debug.Log("Pick up skill");
            Destroy(skill);
        }
    }
}

这两个Observer类长得基本上一模一样,因为我省去了和ui的连接。声明一个subject变量来获得被观察者subject的引用,如果需要的话还可以在脚本里创建很多个被观察者subject,同时观察多个被观察者subject,实现多对多的关系。通过比较拾取物品的标签的方式来判断是否要执行后面的方法。 

 5、Unity里的设置

这一块很简单,对unity比较熟的、看懂代码的同学应该可以直接按自己的想法在场景里设置好,可以不看下面的步骤

1)创建地面(plane)、Player(capsule)、Props(empty)、Skills(empty)、Manager(empty),再在Props和Skills里分别创一些小球(sphere)作为要被拾取的物体

给这些小球创建一些不同的材质进行区分(不设置也行) 

2)将Subject、Player脚本挂在Player物体上

将两个Observer脚本挂在Manager上

将Player上的Subject拖给它们

3)给每个小球设置tag

先添加上Prop和Skill两个tag

一一对应的赋给Props和Skills下面的子物体,每一个都需要哦。

6、运行效果展示

 不知道为什么有一点发黄...

可以看到Player触碰到小球后就会触发不同的观察者的方法

7、感想

总觉得这个观察者模式在C#里实在是过于简单了,不知道是不是我有哪里误会了吗?但是确实达到了观察者模式的功能,或许这就是C#被选作unity的脚本语言的原因吧。如果有问题的话希望评论区大佬指正

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值