我可以创建一个指针来轮询来自任何来源的浮点数吗?

Can I create a pointer to poll a float from any source?

有没有一种方法可以在不对对象进行硬编码的情况下使用对象中的相同引用来访问从同一 class 继承的所有不同类型的对象?

我正在开发 unity,我想在我的游戏中添加一个模块,该模块可以观察 GameObject 中的任何特定浮点数,然后在达到特定值后更改另一个 GameObject 中的另一个浮点数。

举个例子:A "Trigger" Object/Module 当胃Object中的Fullness<0.5值达到时,在大脑Object中设置Hunger=1的值。

因为我会有大量可能的组合,所以我不想通过为每个组合创建触发器 Class 的女儿来对其进行硬编码。

我最初的想法是在初始化时使用指向 watch/change 的良好浮点数的指针。但显然,我们不能在迭代器 (IEnumerator) 中使用不安全的代码,所以我不确定如何轮询 Fullness 的值。

举一个我想要的例子:

public Class Trigger{
    private float* ToPoll;
    private float* ToChange;

    public void SetTrigger(float* poll, float* change){
        ToPoll = poll;
        ToChange = change;

        // the loop would be a IEnumerator, not a litteral loop
        while(*ToPoll < 0.5f){
            sleep(0.1)
        }
        *ToChange = 1f
    }
}

void Main(){
    Trigger trigger1, trigger2;
    trigger1.SetTrigger(&Stomach.fullness, &Brain.hunger)
    trigger2.SetTrigger(&Sun.activityLevel, &Earth.radiationLevel)
    // ^ Literally any float from any object
}

您有任何想法或更好的实施方法吗?

我不太确定你想做什么,但听起来你想给你的对象添加触发器。在我的理解中,触发器在这种情况下应该是一个委托。

这是一个如何定义委托类型并将触发器列表添加到您的 Brain 的示例-class。

现在每个大脑都有不同的触发器。我设置了两个派生大脑来向您展示如何使用它:

public class TestBrain
{
    private static int NextId = 1;
    public TestBrain(List<MyTrigger> triggers)
    {
        this.Triggers = triggers;
        this.Id = NextId++;
    }

    public int Id { get; private set; }
    public int Hunger { get; set; }
    public int StomachFullness { get; set; }
    public List<MyTrigger> Triggers { get; private set; }

    public void FireTriggers()
    {
        foreach (MyTrigger t in this.Triggers)
        {
            t.Invoke(this);
            this.StomachFullness = 100;
        }
    }

    public delegate void MyTrigger(TestBrain b);
}

public class HumanBrain : TestBrain
{
    static readonly List<MyTrigger> defaultHumanTriggers = new List<MyTrigger>()
    {
        b => { if (b.StomachFullness < 50) { b.Hunger = 1; Console.WriteLine("{0} is hungry..", b.Id); } }
    };

    public HumanBrain() : base(defaultHumanTriggers)
    {

    }
}

public class RobotBrain : TestBrain
{
    static readonly List<MyTrigger> defaultRobotTriggers = new List<MyTrigger>()
    {
        b => { if (b.StomachFullness < 50) { Console.WriteLine("{0} ignores hunger only want's some oil..", b.Id); } }
    };

    public RobotBrain() : base(defaultRobotTriggers)
    {

    }
}

static void Main()
{
    // Create some test-data
    List<TestBrain> brains = new List<TestBrain>()
    {
        new HumanBrain(),
        new HumanBrain(),
        new RobotBrain(),
        new HumanBrain(),
    };

    Console.WriteLine(" - - - Output our Testdata - - -");
    foreach (TestBrain b in brains)
    {
        Console.WriteLine("Status Brain {0} - Stomachfulness: {1} Hunger: {2}", b.Id, b.StomachFullness, b.Hunger);
    }

    Console.WriteLine(" - - - Empty stomachs - - -");
    foreach (TestBrain b in brains)
    {
        b.StomachFullness = 0;
    }

    Console.WriteLine(" - - - Fire triggers - - -");
    foreach (TestBrain b in brains)
    {
        b.FireTriggers();
    }

    Console.WriteLine(" - - - Output our Testdata - - -");
    foreach (TestBrain b in brains)
    {
        Console.WriteLine("Status Brain {0} - Stomachfulness: {1} Hunger: {2}", b.Id, b.StomachFullness, b.Hunger);
    }

}

扩展@kara 的答案,以下代码实现了独立的 StomachBrain 对象,并使用 Being 将它们连接起来。

BeingStomach 的了解:

  • 它有一个 NeedsFoodEvent

BeingBrain 的了解

  • 有一个 OnRaiseIsHungryEvent(即 "hungry" 信号——谁在乎它来自哪里)
  • 它有一个 IsHungryEvent

请记住,在实际实现中可能会有其他对象监听这些事件。例如也许你有一个会切换到 "hangry" 的情感系统和一个会切换到寻食模式的基于目标的 AI。两个系统都不需要知道对方,但两者都可以响应来自 Brain 的信号。在这个简单的实现中,Being 响应 Stomach 信号,同时通知和响应 Brain.

从中得出的重要结论不是引发和响应事件的具体方法(在这种情况下是默认的 .Net 机制),而是两个对象都不知道另一个对象的内部结构的事实(参见HumanStomachZombieStomach 的不同实现),而是在更合适的级别连接连接(在本例中为 Being)。还要注意对接口的依赖,它允许我们做一些事情,比如创建混合生物(即将 ZombieBrainHumanStomach 配对)。

代码 written/tested 将 .Net Core CLI 作为控制台应用程序,但它应该与 .Net > 3.5 的大多数任何版本兼容。

using System;
using System.Linq;
using System.Threading;

namespace so_example
{
    public class Program
    {
        static void Main(string[] args)
        {
            var person1 = new Being("Human 1", new HumanBrain(), new HumanStomach());
            var zombie1 = new Being("Zombie 1", new ZombieBrain(), new ZombieStomach());
            var hybrid1 = new Being("Hybrid 1", new ZombieBrain(), new HumanStomach());
            var hybrid2 = new Being("Hybrid 2", new HumanBrain(), new ZombieStomach());

            Console.WriteLine("Hit any key to exit");
            Console.ReadKey();
        }
    }

    public class HungryEventArgs : EventArgs
    {
        public string Message { get; set; }
    }

    public interface IStomach
    {
        event EventHandler<HungryEventArgs> NeedsFoodEvent;
    }

    public class Stomach : IStomach
    {
        public event EventHandler<HungryEventArgs> NeedsFoodEvent;

        protected virtual void OnRaiseNeedsFoodEvent(HungryEventArgs e)
        {
            EventHandler<HungryEventArgs> handler = NeedsFoodEvent;

            if (handler != null)
            {
                handler(this, e);
            }
        }
    }

    public class HumanStomach : Stomach
    {
        private Timer _hungerTimer;

        public HumanStomach()
        {
            _hungerTimer = new Timer(o =>
            {
                // only trigger if breakfast, lunch or dinner (24h notation)
                if (new [] { 8, 13, 19 }.Any(t => t == DateTime.Now.Hour))
                {
                    OnRaiseNeedsFoodEvent(new HungryEventArgs { Message = "I'm empty!" });
                }
                else
                {
                    Console.WriteLine("It's not mealtime");
                }
            }, null, 1000, 1000);
        }
    }

    public class ZombieStomach : Stomach
    {
        private Timer _hungerTimer;

        public ZombieStomach()
        {
            _hungerTimer = new Timer(o =>
            {
                OnRaiseNeedsFoodEvent(new HungryEventArgs { Message = "Need brains in stomach!" });
            }, null, 1000, 1000);
        }
    }

    public interface IBrain
    {
        event EventHandler<HungryEventArgs> IsHungryEvent;
        void OnRaiseIsHungryEvent();
    }

    public class Brain : IBrain
    {
        public event EventHandler<HungryEventArgs> IsHungryEvent;
        protected string _hungryMessage;

        public void OnRaiseIsHungryEvent()
        {
            EventHandler<HungryEventArgs> handler = IsHungryEvent;

            if (handler != null)
            {
                var e = new HungryEventArgs
                {
                Message = _hungryMessage
                };

                handler(this, e);
            }
        }
    }

    public class HumanBrain : Brain
    {
        public HumanBrain()
        {
            _hungryMessage = "Need food!";
        }
    }

    public class ZombieBrain : Brain
    {
        public ZombieBrain()
        {
            _hungryMessage = "Braaaaaains!";
        }
    }

    public class Being
    {
        protected readonly IBrain _brain;
        protected readonly IStomach _stomach;
        private readonly string _name;

        public Being(string name, IBrain brain, IStomach stomach)
        {
            _stomach = stomach;
            _brain = brain;
            _name = name;

            _stomach.NeedsFoodEvent += (s, e) =>
            {
                Console.WriteLine($"{_name}: {e.Message}");
                _brain.OnRaiseIsHungryEvent();
            };

            _brain.IsHungryEvent += (s, e) =>
            {
                Console.WriteLine($"{_name}: {e.Message}");
            };
        }
    }
}

一些笔记

为了提供一些输出,我在 2 IStomach 实现中伪造了一些东西。 HumanStomach 在构造函数中创建一个定时器回调,每 1 秒触发一次并检查当前时间是否是用餐时间。如果是,它会引发 NeedsFoodEventZombieStomach 也每 1 秒使用一次回调,但它每次都会触发 NeedsFoodEvent。在真正的 Unity 实现中,您可能会基于来自 Unity 的某些事件触发偶数——玩家在预设时间后采取的行动等。