unity 脚本之间的优雅沟通方式

本文介绍了Unity游戏开发中的一些关键概念,包括RoleManager、MapManager的单例模式,以及Role和Map对象的交互。还讨论了伪构造函数、观察者模式的用途,例如在角色状态改变时通知其他对象。此外,提到了委托和事件在游戏逻辑中的应用,如伤害信息的显示。最后,简要提及了ECS(实体-组件-系统)架构在提高代码复用性和可维护性方面的作用。
摘要由CSDN通过智能技术生成

引用方式

我们现在有一个Role脚本来管理单个角色,一个RoleManager来控制整体的角色以及一个MapManager来变换位置,指示方位什么的

public class RoleManager : MonoBehaviour{
    public static RoleManager Instance;
    [SerializeField] private Role _prefab;
    
    public List<Role> Roles{get; private set;} = new List<Role>();
    
    void Awake() => Instance = this;
   
	void Start(){
		for(int i = 0, i < 10, i++){
			var role = Instantiate(_prefab);
            role.Init(this);
            Roles.Add(role);
        }
    }
    
    void Update(){
		DoSomething();
    }
    
    public void DoSomething{
        //...
    }
}

脚本之间相互调用需要一定的约束:常见的就是使用{get; set;}来封装属性

单例模式

创建一个静态的public类让其他脚本也能以此来调用函数,适用于一大推不同的类访问——比如常见的各种Manager。

public class MapManager : MonoBehaviour{
	public Vector3 point;
	
	void Teleport(){
		foreach(for role in RoleManager.Instance.Roles){
			role.transform.position = point;
		}
	}	
}

伪构造函数

我们要在避免在unity中使用构造函数,不要在构造函数中初始化任何变量,使用Awake或Start实现这个目的。(MonoBehaviour 的构造函数析构函数 都是在另外一个线程调用的,不是游戏的主线程,这里不能调用任何UnityEngine相关的API,因为UnityEngine相关的API都不是线程安全的。构造函数不仅会在无法预料的时刻被调用,它也会为预设或未激活的游戏物体调用。)

public class Role : MonoBehaviour{
	private RoleManager _manager;
	
	public void Init(RoleManager manager){
		_manager = manager;
	}
} 

但我们可以使用伪构造函数,这样只传递对应的引用到确切需要他的脚本中。

观察者模式

被观察的对象称为Subject,观察者称为Observer。

当一个对象被修改时,则会自动通知依赖它的对象。比如一个敌人死亡之后,其余的敌人会进入狂暴状态

使用场景:

  • 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。//比如麦扣里面的玩家死后,敌人做出欢呼动画
  • 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。

缺点:

  • 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。

  • 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

一般是通过列表来存放观察者,特定事件发生后遍历列表中的所有元素来通知订阅者

public class Subject
{
    private readonly List<Observer> _observers = new List<Observer>();

    private int _state;

    public int State
    {
        get => _state;
        set
        {
            _state = value;
            NotifyAllObservers();
        }
    }

    public void AddObserver(Observer observer)
    {
        observer.Subject = this;
        _observers.Add(observer);
    }

    public void NotifyAllObservers() => _observers.ForEach(o => o.Check());
}

Observer.cs

public abstract class Observer
{
    public Subject Subject;
    
    public abstract void Check();
}

HeavenObserver.cs

public class HeavenObserver:Observer
{
    public HeavenObserver(Subject subject)
    {
        subject.AddObserver(this);
    }
    public override void Check()
    {
        //...
    }
}

HellObserver.cs

public class HellObserver:Observer
{
    public HellObserver(Subject subject)
    {
        subject.AddObserver(this);
    }

    public override void Check()
    {
        //...
    }
}

Demo.cs

public void Demo()
{
    Subject subject = new Subject();
    new HeavenObserver(subject);
    new HellObserver(subject);
    Console.WriteLine("state = Bad");
    subject.State = "Bad";
    Console.WriteLine("state = Good");
    subject.State = "Good";
}

委托

回调函数广泛应用在观察者模式中,而C#通过委托来实现回调函数的机制。我们button,slider都能在inspector窗口中拖入对应的游戏物体来响应事件

举个例子,在游戏当中,一个单位攻击后会显示此次的伤害数字。为了降低耦合性,我们应该让一个单独的类来处理(叫作HurtInformation吧)。

大概的思路就是在被攻击单位中声明一个公开的掉血事件OnSubHP,让HurtInformation的实例来订阅该次事件。这样的话单位掉血时就能触发掉血事件,而订阅了这个事件的对象就会受到通知,调用对应方法来响应

public class HurtInformation : MonoBehaviour
{
    public BaseRole role;

    void Start()
    {
        role = gameObject.GetComponent<BaseRole>();
        AddListener();
    }

    private void AddListener()
    {
        role.OnSubHp += new BaseRole.SubHpHandler(OnSubHp);
    }
    
    private void OnSubHp(BaseRole source, float hpDecrease, HurtType hurtType)
    {
        string damageNumber = hpDecrease.ToString();
        string damageType = hurtType.ToString();
        string name = source.Name;
        Debug.Log(name + damageType + damageNumber);
    }
}
public enum HurtType
{
    Water,
    Fire,
    Rock,
    Wind,
}

public class BaseRole : MonoBehaviour
{
    public delegate void SubHpHandler(BaseRole source, float HpDecrease, HurtType hurtType);
    public event SubHpHandler OnSubHp;
    public void BeAttacked()
    {
        float hurtNumber = 30000f;
        OnBeAttacked(hurtNumber);
    }

    private void OnBeAttacked(float hurtNumber)
    {
        //HurtType hurtType = CheckShowType();
        HurtType hurtType = HurtType.Fire;
        if(OnSubHp != null)
        {
            OnSubHp(this, hurtNumber, hurtType);
        }
    }

    public string Name
    {
        get
        {
            return "name";
        }
    }
}

脚本架构

ECS

  • Entity 是实例,作为承载组件的载体,也是框架中维护对象的实体.

  • Component 只包含数据,具备这个组件便具有这个功能.

  • System 作为逻辑维护,维护对应的组件执行相关操作.

    而且使用ECS,可以让你从面向对象转向数据导向设计,这意味着重用代码更容易,并且更容易让其他人掌握并做出贡献。

img

Unity中,脚本之间修改变量有几种常用的方法和技巧。 一种方法是使用公共变量。在Unity中,我们可以在一个脚本中定义一个变量,并将其声明为公共(public)的。这样,在其他脚本中就可以直接访问这个变量,并修改它的值。例如,我们可以在一个脚本A中定义一个公共变量score,然后在另一个脚本B中进行修改,通过脚本B修改score的值,从而实现脚本之间的变量修改。 另一种常用的方法是使用事件监听。在Unity中,我们可以使用事件系统来实现脚本之间的通信。我们可以在一个脚本中定义一个事件,并在另一个脚本中进行监听。当某个条件满足时,触发事件,从而执行监听中的方法,并在该方法中修改变量。例如,我们可以在一个脚本A中定义一个事件ScoreUpdated,在另一个脚本B中进行监听,并在ScoreUpdated事件发生时,执行监听的方法来修改变量。 还有一种方法是使用单例模式。单例模式是一种常用的设计模式,可以确保一个类只有一个实例。我们可以在一个脚本中定义一个单例类,并在其他脚本中通过获取该实例来修改变量。例如,我们可以在一个GameManager脚本中定义一个GameManager类的单例实例,其他脚本通过GameManager.Instance来访问该实例,并通过该实例修改GameManager中的变量。 以上是几种常用的在Unity中实现脚本之间修改变量的方法。具体使用哪种方法取决于你的需求和场景。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值