0
\$\begingroup\$

This is going to be a bit tricky but it is really bothering me so I hope you will have the patience to follow along :)

Here is my very basic architecture:

enter image description here

So in the framework, I am creating I have Actions these currently have a list of Conditions or Considerations (same thing different name).

While each Condition share some of the same functionality such as:

  • Returning a float score
  • Using an evaluation formula

They implement these in very different ways depending on the situation.

Take a look at the following code:

[CreateAssetMenu(fileName = "Consider my health vs max health",
    menuName = "AnAppGames/AI/Example/Decisions/Health Consideration")]
public class MyHealth : BaseConsideration
{
    private float health;
    public float maxHealth;

    public override float Consider<T>(BaseAiContext context, BaseAction action, T value)
    {
        health = value as float;
        return EvaluateValues(health / maxHealth);
    }

}

What you are looking at is my first attempt at making some sort of "reusable" Consideration.

Now in the continuation of my development, I had to make the following Consideration:

public class DistanceFromMe : BaseConsideration
{
    public float minRange;
    public float maxRange;

   
    private float ConsiderWithValues(Transform target, Transform caller)
    {
        return EvaluateValues(new FloatData(Vector3.Distance(target.transform.position, caller.position),
            minRange,
            maxRange).GetNormalizedData());
    }

    public override float Consider<T>(BaseAiContext context, BaseAction action, T value)
    {
        Transform target = value as Transform;
        if (target == null)
        {
            return 0;
        }
        return ConsiderWithValues(target.transform, context.transform);
    }

    public override float Consider(float x)
    {
        return EvaluateValues(new FloatData(x,
            minRange,
            maxRange).GetNormalizedData());
    }

}

And now the trouble begins I thought "This is not a scaleable solution" as soon as I need more than 1 generic parameter I am screwed and have to create additional overrides of this Consider function.

So what I need is somehow to add different types of parameters to the Consideration however these parameters will vary a lot.

Another problem I have with this is that when I loop through the list I won't be able to tell what parameters i needed and how to fulfill the needs of the Consideration.

I thought of different ways of dealing with this inside the editor but I have come up empty :(

Can anyone help me out with this architectural mess? :)

It is worth mentioning that at the current point each of these are ScriptableObjects I am not 100 set in the fact that they should be but i am just not sure how to deal with this problem.

\$\endgroup\$
2
\$\begingroup\$

You aren't using generics correctly.

public override float Consider<T>(BaseAiContext context, BaseAction action, T value)
{
    health = value as float;
    return EvaluateValues(health / maxHealth);
}

This function takes a generic type T but expects T to be a float. If the function only works if T is a float, the function should not be generic.

It's hard to grasp the bigger picture because there's no relation between your diagram and your example code, and you haven't shown how you are using the "considerations". My impression is that you are going about the architecture entirely the wrong way. You should use abstraction and polymorphism when they make sense, not just for the sake of using them.

You could do this, but I think this is making your code pointlessly complex, and it doesn't work with multiple values:

abstract public class BaseConsideration<T> {
    abstract public float Consider(BaseAiContext context, BaseAction action, T value);
    public float EvaluateValues(float value) {
        //etc
    }
}

public class MyHealth : BaseConsideration<float>
{
    private float health;
    public float maxHealth;

    public override float Consider(BaseAiContext context, BaseAction action, float value)
    {
        health = value;
        return EvaluateValues(health / maxHealth);
    }
}

You might try something more like this, defining the conditions on each type of action:

public interface IHealth {
    int HP { get; set; }
    float NormalizedHP { get; }
    bool IsAlive { get; }
}

abstract public class Action {
    abstract public bool ShouldBegin();
    abstract public void Begin();
}

public class GetHealthAction {
    public float NormalizedHPThreshold { get; set; } = .2f;
    public IHealth Health { get; set; }
    override public bool ShouldBegin() {
        return Health.NormalizedHP  < NormalizedHPThreshold;
    }
    override public void Begin() {
       //etc
    }
}

or you could do it this way if you prefer the conditions to be defined in reusable classes (it doesn't make sense to use ScriptableObjects here):

abstract public class Condition {
    abstract public bool Evaluate();
}

//C# might already have a built-in enum like this somewhere
public enum Comparison {
    Less, LEqual, Greater, GEqual, Equal, NEqual
}

public class FloatCondition : Condition {
    public float Threshold { get; set; }
    public float CurrentValue { get; set; }
    public Comparison Comparison { get; set; }
    override public bool Evaluate() {
        if (Comparison == Comparison.Equal) return CurrentValue == Threshold;
        else if (Comparison == Comparison.Less) return CurrentValue < Threshold;
        //etc etc
    }
}

public class HealthCondition : FloatCondition {
    public IHealth Health { get; set; }
    override public bool Evaluate() {
        CurrentValue = Health.NormalizedHP;
        return base.Evaluate();
    }
}

abstract public class Action {
    [SerializeField] private List<Condition> conditions = new List<Condition>();
    public bool ShouldBegin() {
        foreach (var condition in conditions) {
            if (!condition.Evaluate()) return false;
        }
        return true;
    }
    abstract public void Begin();
}

public class FindHealthAction : Action {
    public FindHealthAction(IHealth entity) {
        var condition = new HealthCondition();
        condition.Health = entity;
        condition.Comparison = Comparison.Less;
        condition.Threshold = .2f;
    }
    override public void Begin() {
        //etc
    }
}

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.