引用方式
我们现在有一个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,可以让你从面向对象转向数据导向设计,这意味着重用代码更容易,并且更容易让其他人掌握并做出贡献。