XNA 4.0 Behavior-driven Development, Chapter 8

2 views
Skip to first unread message

David Wallace

unread,
Dec 26, 2011, 9:59:12 AM12/26/11
to bost...@googlegroups.com
Chapter 8: Lights, camera, action!

Now we get into actions, and they are numerous.  Just as with tests, there are "meta" actions that contain and use other actions.  You were introduced to the first one at the beginning.

    public class ElseAct : ActBase
    {
        ActBase act;

        public ActBase Action
        {
            get { return act; }
        }

        public override void Act(BehaviorArgs args)
        {
            act.Act(args);
        }

        public ElseAct(ContentService service, string source)
        {
            act = service.Acts.Get(source);
        }

        public static ActBase Build(ContentService service, string source)
        {
            return new ElseAct(service, source);
        }
    }

Aside from making its internal action a property, this one is very simple.  Its function is in the behavior class.  The action serves as a prefix and the internal action fires when the test fails as opposed to passes.  That's why it's an ElseAct as it gives the script an effective if-then-else ability.

    public class GoSubAct : ActBase
    {
        List<Behavior> catalog;
        string name;
        Behavior act;
        bool init;

        public List<Behavior> Catalog
        {
            get { return catalog; }
            set { catalog = value; }
        }

        public override void Act(BehaviorArgs args)
        {
            if (!init)
            {
                if (catalog == null) return;
                if (string.IsNullOrWhiteSpace(name)) return;
                act = catalog.Find(b => b.ID == name);
                if (act == null) return;
                init = true;
            }

            act.Update(args, true);
        }

        public GoSubAct(ContentService service, string source)
        {
            name = source;
        }

        public static ActBase Build(ContentService service, string source)
        {
            return new GoSubAct(service, source);
        }
    }

GoSub allows an action to activate a specific behavior, even one marked as manual.  I originally had an IfAct but that only allowed one test and one action for each outcome.  Now I can create a behavior tree for things like RPG dialogue.

    public class ChooseAct : ActBase
    {
        List<ActBase> choices;
        Random selector;

        public override void Act(BehaviorArgs args)
        {
            if (choices.Count == 0) return;
            choices[selector.Next(choices.Count)].Act(args);
        }

        public ChooseAct(ContentService service, IEnumerable<ActBase> data)
        {
            selector = service.Serve<RandomService>().New;
            choices = data.ToList();
        }

        public static ActBase Build(ContentService service, string source)
        {
            var sources = source.SplitNest(',', "()", "~").ToList();
            if (sources.Count() < 1) throw new Exception(
                  "ChooseAct requires at least 1 parameter.");
            return new ChooseAct(service, Get(service, sources));
        }

        private static IEnumerable<ActBase> Get(ContentService service, IEnumerable<string> items)
        {
            foreach (var item in items)
                yield return service.Acts.Get(item);
        }
    }

ChooseAct selects an action randomly from a list.  You can bias it by selecting the same action more than once.  Note that this time Build throws an exception if the input is invalid.

    public class ChanceAct : ActBase
    {
        ActBase doact, elseact;
        Random selector;
        float chance;

        public override void Act(BehaviorArgs args)
        {
            if (selector.NextDouble() < chance)
                doact.Act(args);
            else
                if (elseact != null) elseact.Act(args);
        }

        public ChanceAct(ContentService service, string[] data)
        {
            selector = service.Serve<RandomService>().New;
            data[0].Parse(out chance);
            doact = service.Acts.Get(data[1]);
            if (data.Count() > 2) elseact = service.Acts.Get(data[2]);
        }

        public static ActBase Build(ContentService service, string source)
        {
            var sources = source.SplitNest(',', "()", "~");
            if (sources.Count() < 2) throw new Exception(
                  "ChanceAct requires at least 2 parameters.");
            return new ChanceAct(service, sources);
        }
    }

ChanceAct gives a more granular random probability to an action, but can only pick between two at most.  The second act is optional.

    public class RepeatAct : ActBase
    {
        ActBase copy;
        int amount;

        public override void Act(BehaviorArgs args)
        {
            if (copy == null) return;
            for(int i=0; i<amount; i++)
                copy.Act(args);
        }

        public RepeatAct(ContentService service, string[] data)
        {
            data[0].Parse(out amount);
            copy = service.Acts.Get(data[1]);
        }

        public static ActBase Build(ContentService service, string source)
        {
            var sources = source.SplitNest(',', "()", "~");
            if (sources.Count() != 2) throw new Exception(
                  "RepeatAct requires 2 parameters.");
            return new RepeatAct(service, sources);
        }
    }

This action repeats a single action a fixed number of times.  The nature of the action could mediate this, however.

Reply all
Reply to author
Forward
0 new messages