News Forums RAIN Sample Projects, How To, and Code Trapwire/Trigger system, read fields from entity

This topic contains 7 replies, has 2 voices, and was last updated by  binary 6 months, 1 week ago.

Viewing 8 posts - 1 through 8 (of 8 total)
  • Author
    Posts
  • #38636

    binary
    Participant

    Hi,

    i could use some tips who to do a trapwire trigger system with Rain. Currently i do it simply with colliders, if an AI hits one, it reads some values from the other collider and adjusts based on those values.

    But i’d like to have that in the behavior tree, so the idea is to have a very small sensor area and entity’s with values.
    How can add fields to entity’s and read them via the sensor into the behavior tree (preferably without the global AI.WorkingMemory) ?

    #38639

    Sigil
    Keymaster

    Well you can use a Custom Aspect combined with a Custom Sensor or Custom Action depending on what you want to do.

    Since even the behavior tree works with the memory directly, it may be hard to avoid that specifically, but perhaps you can get it more within the behavior tree so all your logic is in the same place (I tend to try to do this as well).

    So here is the code to do a custom aspect, I threw in a couple values just for example:

    using RAIN.Entities.Aspects;
    using RAIN.Serialization;
    using UnityEngine;
    [RAINSerializableClass]
    public class MyCustomAspect : VisualAspect
    {
        [RAINSerializableField]
        private float _someFloat = 0.1f;
        [RAINSerializableField]
        private GameObject _someObject = null;
        public float SomeFloat
        {
            get { return _someFloat; }
        }
        public GameObject SomeObject
        {
            get { return _someObject; }
        }
    }

    I inherited from VisualAspect to save some trouble of creating a whole new Aspect Type (built in there are visual and audio).

    Here is a custom action I put together to detect and then read the values out:

    using RAIN.Action;
    using RAIN.Entities.Aspects;
    using RAIN.Perception.Sensors;
    using RAIN.Representation;
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    [RAINAction]
    public class ReadCustomAspect : RAINAction
    {
        // These will show up in the editor, with the expectation that you will put a variable
        // name in them to be filled (much like the target field in some of our built in nodes)
        public Expression SomeFloatVariable = new Expression();
        public Expression SomeObjectVariable = new Expression();
        public override ActionResult Execute(RAIN.Core.AI ai)
        {
            // The BEST match type will only return 1 aspect if it returns any
            IList<RAINAspect> tAspects = ai.Senses.Sense("MySensor", "MyCustomAspect", RAINSensor.MatchType.BEST);
            if (tAspects.Count == 0)
                return ActionResult.FAILURE;
            // If you want to use the values in the behavior tree you will have to do something like this
            if (SomeFloatVariable.IsValid)
                ai.WorkingMemory.SetItem<float>(SomeFloatVariable.VariableName, ((MyCustomAspect)tAspects[0]).SomeFloat);
            else
                ai.WorkingMemory.SetItem<float>("someFloatVariable", ((MyCustomAspect)tAspects[0]).SomeFloat);
            if (SomeObjectVariable.IsValid)
                ai.WorkingMemory.SetItem<GameObject>(SomeObjectVariable.VariableName, ((MyCustomAspect)tAspects[0]).SomeObject);
            else
                ai.WorkingMemory.SetItem<GameObject>("someObjectVariable", ((MyCustomAspect)tAspects[0]).SomeObject);
            // If you don't need them in the behavior tree but instead want to do something directly to the ai
            // you have a lot of options
            // Change my speed
            //ai.Motor.DefaultSpeed = ((MyCustomAspect)tAspects[0]).SomeFloat;
            // Switch bodies?!
            //ai.Body = ((MyCustomAspect)tAspects[0]).SomeObject;
            return ActionResult.SUCCESS;
        }
    }

    If you have questions (or problems) let me know. Full disclosure, I only compiled these, didn’t actually try them out.

    #38654

    binary
    Participant

    That’s perfect, thank you!

    Is there a elegant solution to have those one shot? Like onTriggerEnter?

    #38655

    Sigil
    Keymaster

    Not built in, you could probably do something like this though:

    using RAIN.Action;
    using RAIN.Entities.Aspects;
    using RAIN.Perception.Sensors;
    using System.Collections;
    using System.Collections.Generic;
    [RAINAction]
    public class ReadCustomAspect : RAINAction
    {
        private MyCustomAspect _currentTrigger = null;
        public override void Start(RAIN.Core.AI ai)
        {
            base.Start(ai);
            _currentTrigger = null;
        }
        public override ActionResult Execute(RAIN.Core.AI ai)
        {
            // If we don't have a trigger, try and grab it
            if (_currentTrigger == null)
            {
                IList<RAINAspect> tAspects = ai.Senses.Sense("MySensor", "MyCustomAspect", RAINSensor.MatchType.BEST);
                if (tAspects.Count == 0)
                    return ActionResult.FAILURE;
                _currentTrigger = (MyCustomAspect)tAspects[0];
                // Any code happening here would be the same as OnTriggerEnter
                return ActionResult.RUNNING;
            }
            // If we can't see the trigger anymore, it means we exited
            else if (ai.Senses.IsDetected(_currentTrigger, _currentTrigger.AspectName) == null)
            {
                // Any code happening here would be the same as OnTriggerExit
                return ActionResult.SUCCESS;
            }
            else
            {
                // Any code happening here would be the same as OnTriggerStay
                return ActionResult.RUNNING;
            }
        }
    }
    • This reply was modified 6 months, 2 weeks ago by  Sigil. Reason: fixed code block
    #38706

    binary
    Participant

    Thanks, i’ll give it a try.

    #38725

    binary
    Participant

    Okay, its getting closer.
    The thing is, the trigger can issue a stop command which leaves the sensor sitting on the aspect. And it should still be able to be triggered by other (moving) aspects. So in this situation, in the current version it acts not like OnTriggerEnter but like OnTriggerStay in the first block. The detected aspect should be ignored until !IsDetected.

    Best what i could come up with, was adding flag to the aspect that gets set on detection and then it will be ignored.

    IList<RAINAspect> tAspects = ai.Senses.SenseAll();
            if (tAspects.Count == 0)
                return ActionResult.FAILURE;
            currentTrigger = null;
            for (int i = 0; i < tAspects.Count; i++){ 
                if(!((MyAspect)tAspects[i]).DidExecute){
                    currentTrigger = (MyAspect)tAspects[i];
                    break;
                }
            }
            if(currentTrigger == null){
                return ActionResult.FAILURE;
            }else if ( currentTrigger != null){
            currentTrigger.DidExecute = true;
    [...]
    }
    [...]

    This is not only not nice, but i also cant figure out, how and where to reset the aspect to undetected (currentTrigger.DidExecute = false; ).

    #38726

    Sigil
    Keymaster

    Ah, it looks like the code I originally posted isn’t very useful if you ever have more than one trigger at the same time. So I took that one and expanded it, this should get you closer to what you need if I understand the problem correctly. I renamed the class to be DetectTriggers, since that seems more appropriate.

    using RAIN.Action;
    using RAIN.Entities.Aspects;
    using RAIN.Perception.Sensors;
    using System.Collections;
    using System.Collections.Generic;
    [RAINAction]
    public class DetectTriggers : RAINAction
    {
        private List<MyCustomAspect> _currentTriggers = new List<MyCustomAspect>();
        public override void Start(RAIN.Core.AI ai)
        {
            base.Start(ai);
            _currentTriggers.Clear();
        }
        public override ActionResult Execute(RAIN.Core.AI ai)
        {
            // Test our current triggers
            for (int i = _currentTriggers.Count - 1; i >= 0; i--)
            {
                // If the aspect is no longer detected, remove it
                if (ai.Senses.IsDetected(_currentTriggers[i], _currentTriggers[i].AspectName) == null)
                {
                    // This is OnTriggerExit
                    _currentTriggers.RemoveAt(i);
                }
            }
            // Now look for new ones (and possibly old ones too)
            IList<RAINAspect> tAspects = ai.Senses.Sense("MySensor", "MyCustomAspect", RAINSensor.MatchType.ALL);
            if (tAspects.Count == 0)
                return ActionResult.FAILURE;
            // Go through our aspects, and treat them like triggers
            for (int i = 0; i < tAspects.Count; i++)
            {
                MyCustomAspect tCustomAspect = (MyCustomAspect)tAspects[i];
                // We already detected it
                if (_currentTriggers.Contains(tCustomAspect))
                {
                    // This would be OnTriggerStay now
                }
                // New trigger
                else
                {
                    // This would be OnTriggerEnter now
                    _currentTriggers.Add(tCustomAspect);
                }
            }
            // And we are guaranteed that we still have aspects at this point, so continue RUNNING
            return ActionResult.RUNNING;
        }
    }

    You shouldn’t have to use DidExecute anymore using this, as anything already in your list has been executed, anything added to the list needs to be executed, and anything removed from the list can be executed on next detect. Does this sound like it will work?

    #38772

    binary
    Participant

    Beautiful! Happy! Thank you!

Viewing 8 posts - 1 through 8 (of 8 total)

You must be logged in to reply to this topic.