Implementazione di una serie di azioni indipendenti disparate ma con accessibilità comune

2

Ho l'obbligo di implementare un tipo di IA per un progetto, il problema è che ho problemi a visualizzare come l'IA dovrebbe essere effettivamente implementata.

Essenzialmente, voglio che l'intelligenza artificiale sia la più dinamica possibile. Cioè, se decido in futuro di aggiungere un altro tipo di azione AI (come la chiameremo noi), non ho bisogno di riscrivere la classe usando l'AI o l'AI.

Quello che ho ora, è un'idea molto approssimativa, ma dovrebbe aiutare a chiarire.

public interface IAiAction
{
    List<IAiProperty> Properties { get; }

    DateTime LastUpdate { get; }

    bool Update(Entity e);
}

public interface IAiProperty
{

}

Allora ho qualcosa del tipo:

public class WanderAction : IAiAction
{
    private List<IAiProperty> _Properties = new List<IAiProperty>();
    public List<IAiProperty> Properties { get { return _Properties; } }

    private DateTime _LastUpdate;
    public DateTime LastUpdate { get { return _LastUpdate; } }

    private Point _WanderTo;

    public bool Update(Entity e)
    {
        // Do stuff here

        return true;
    }
}

public struct WanderDistance : IAiProperty
{
    private Point _Home;
    private float _MaxDistance;

    public Point Home { get { return _Home; } private set { _Home = value; } }
    public float MaxDistance { get { return _MaxDistance; } private set { _MaxDistance = value; } }

    public WanderDistance(Point h, float m)
    {
        _Home = h;
        _MaxDistance = m;
    }
}

Quindi, nella classe con le azioni:

private List<IAiAction> _AiActions = new List<IAiAction>();
public List<IAiAction> AiActions { get { return _AiActions; } }

public override void Update(Vector2F force)
{
    foreach (IAiAction action in AiActions)
        action.Update(this);

    base.Update(force);
}

Il problema è che questo non si estende bene. Non sono nemmeno sicuro di come sono arrivato a questo livello, perché non ho mai dovuto fare qualcosa del genere prima.

Questo codice non è affatto un requisito. Se qualcuno ha altre idee su come può essere fatto (anche se sono molto distanti da ciò che questo indica), sono pronto a farlo.

Ad esempio, se avessi un IAiAction che doveva determinare ciò che il Entity avrebbe detto a caso, questo lascia molto spazio per farlo. Inoltre, come esempio di WanderAction , devo restituire un Vector2F alla classe chiamante, per aggiungerlo al parametro force del metodo Update(Vector2F) .

Se è necessaria qualche chiarimento oltre a questo, fammi sapere. Questo non ostacola il progetto, ma mi sta causando problemi.

L'unico altro metodo che posso pensare è quello di implementare una sorta di stato che viene trasportato attraverso il programma. Questo sarebbe tecnicamente in grado di soddisfare le mie esigenze, ma sono curioso di sapere se esiste un modo migliore.

    
posta 202_accepted 05.07.2015 - 03:55
fonte

1 risposta

2

Se sto capendo correttamente, hai un Entity , che può fare cose (come camminare, parlare, e così via), e vuoi usare le classi per essere in grado di fornire questa entità con i comportamenti dell'IA. Questi verranno attivati in qualche ciclo (o secondo programma o altro) e causeranno quindi un comportamento nell'entità (camminare, parlare, ecc.).

Ci sono alcuni che ti serviranno due cose:

  • Un'interfaccia per le azioni AI, che consente loro di essere attivate
  • Un modo in cui l'azione AI può effettivamente influire sul cambiamento di comportamento

Per il primo, hai la tua interfaccia IAiAction . Diamo un'occhiata a come è usato:

foreach (IAiAction action in AiActions)
    action.Update(this);

Quindi da questo sembra che in realtà, l'unico membro su IAiAction dovrebbe essere:

void Update(Entity entity);

Questo ti allevia anche dal bisogno di IAiProperty . WanderDistance , ad esempio, può essere solo un normale membro privato su WanderAction .

Guardando questa interfaccia, non sembra esserci nulla di specifico per l'IA. Potrebbe funzionare altrettanto bene per le azioni controllate dall'utente, o per azioni così stupide da non poter essere chiamate AI (come l'attesa sul posto per sempre). Perché non solo IAction o IInstructor o qualcosa?

È possibile che mentre implementi più di queste classi AI, trovi una struttura comune. Potresti inserirlo in una dipendenza o in una classe base. Ma non c'è bisogno di imporre preventivamente la struttura, e certamente non c'è bisogno di esporla pubblicamente. Basta esporre ciò di cui i consumatori hanno effettivamente bisogno. Ad esempio, forse stavi considerando l'utilizzo di LastUpdated in modo che Entity potesse solo chiamare azioni che sono state aggiornate più di un secondo fa, o qualsiasi altra cosa. Ma perché non mantenere LastUpdated privato e lasciare che le azioni possano prendere da sole questa decisione?

Per quanto riguarda il secondo punto, sarebbe difficile provare ad avere un modo generale per l'azione AI per restituire un'istruzione per Entity da comprendere. Invece, Entity dovrebbe esporre ciò che può fare attraverso i metodi su un'interfaccia:

void SetDestination(Vector2D destination){ /*...*/ }
void Say(string utterance) { /*...*/ }

Ora un'azione AI può chiamare metodi, invece di dover restituire qualcosa e assumere che Entity abbia la logica per gestirlo. Ad esempio, potresti facilmente aggiungere un ReactToDangerAction che urla e scappa via dal pericolo, dati i due metodi precedenti.

Questo è un punto di partenza, ma un miglioramento del design che può essere lungo è non passare il Entity nel metodo Update , ma invece passarlo attraverso il costruttore. Dopotutto, non ha senso che il tuo esempio WanderAction sia condiviso da due entità diverse, poiché probabilmente avrebbero case diverse. Quindi potrebbe anche essere legato a una particolare entità in modo permanente, piuttosto che accettarne una ogni volta che viene chiamato il metodo e sperando che ottenga sempre la stessa. Ciò richiederebbe probabilmente un piccolo numero di piastre (classi di produzione, probabilmente), ma consentirebbe alcuni miglioramenti del design.

Ad esempio, puoi aderire meglio al principio di segregazione dell'interfaccia suddividendo l'interfaccia di Entity in più ( IDirectable , IVocal , ecc.). Oppure, se non ti piace l'idea di Entity esponendo i metodi che consentono di ordinarlo in giro per tutti, puoi addirittura rendere privati i metodi SetDestination e Say e passarli nei costruttori delle azioni come delegati. Darebbe anche flessibilità se in futuro avessi scoperto che alcune azioni non dovrebbero essere legate a particolari entità, o dovrebbero essere legate a più elementi.

    
risposta data 05.07.2015 - 04:41
fonte

Leggi altre domande sui tag