Wzór do wykonywania akcji w grze


11

Czy istnieje ogólnie przyjęty wzorzec wykonywania różnych czynności w grze? Sposób, w jaki gracz może wykonywać akcje, a także sztuczna inteligencja może wykonywać akcje, takie jak ruch, atak, autodestrukcja itp.

Obecnie mam abstrakcyjną funkcję BaseAction, która używa ogólnych .NET do określania różnych obiektów zwracanych przez różne akcje. Wszystko to jest zaimplementowane we wzorcu podobnym do polecenia, w którym każde działanie jest odpowiedzialne za siebie i robi wszystko, czego potrzebuje.

Moje rozumowanie jako abstrakcyjne jest takie, że mogę mieć pojedynczy ActionHandler, a AI może po prostu ustawiać w kolejce różne działania implementujące baseAction. Powodem tego jest to, że różne akcje mogą zwracać informacje o wynikach istotne dla akcji (ponieważ różne akcje mogą mieć zupełnie inne wyniki w grze), a także niektóre typowe implementacje przed i po akcji.

Więc ... czy istnieje bardziej akceptowany sposób na zrobienie tego, czy to brzmi dobrze?


Brzmi dobrze, pytanie brzmi: co masz na myśli przez kolejkę? Większość gier ma bardzo szybką reakcję? „AI może
ustawiać w

Słuszna uwaga. Nie ma kolejki. Musi tylko wiedzieć, czy jest zajęty, a jeśli nie, wykonać akcję.
Arkiliknam,

Odpowiedzi:


18

Nie sądzę, aby istniał jeden zaakceptowany sposób wdrożenia tej koncepcji, ale naprawdę chciałbym podzielić się tym , jak zwykle sobie z tym radzę w moich grach. To trochę kombinacja polecenia wzorca projektowego i wzorca projektowego Composite .

Mam abstrakcyjną klasę bazową dla akcji, która jest niczym więcej niż opakowaniem wokół Updatemetody, która jest wywoływana dla każdej ramki, iFinished flagą wskazującą, kiedy akcja się zakończyła.

abstract class Action
{
    abstract void Update(float elapsed);
    bool Finished;
}

Używam również wzorca projektowania złożonego do tworzenia rodzaju akcji, które są w stanie hostować i wykonywać inne akcje. To także jest klasa abstrakcyjna. Sprowadza się do:

abstract class CompositeAction : Action
{
    void Add(Action action) { Actions.Add(action); }
    List<Action> Actions;
}

Następnie mam dwie implementacje akcji złożonych, jedną do wykonywania równoległego, a drugą do wykonywania sekwencyjnego . Ale piękno polega na tym, że ponieważ równoległość i sekwencja są działaniami, można je łączyć , aby tworzyć bardziej złożone przepływy wykonania.

class Parallel : CompositeAction
{
    override void Update(float elapsed) 
    {
        Actions.ForEach(a=> a.Update(elapsed));
        Actions.RemoveAll(a => a.Finished);
        Finished = Actions.Count == 0;
    }
}

I ten, który rządzi sekwencyjnymi działaniami.

class Sequence : CompositeAction
{
    override void Update(float elapsed) 
    {
        if (Actions.Count > 0) 
        {
            Actions[0].Update(elapsed);
            if (Actions[0].Finished)
                Actions.RemoveAt(0);
        }
        Finished = Actions.Count == 0;
    }
 }

W tym miejscu wystarczy po prostu stworzyć konkretne implementacje akcji i użyć ParalleliSequence do kontrolowania przepływu wykonania. Zakończę przykładem:

// Create a parallel action to work as an action manager
Parallel actionManager = new Parallel();

// Send character1 to destination
Sequence actionGroup1 = new Sequence();
actionGroup1.Add(new MoveAction(character1, destination));
actionGroup1.Add(new TalkAction(character1, "Arrived at destination!"));
actionManager.Add(actionGroup1);

// Make character2 use a potion on himself
Sequence actionGroup2 = new Sequence();
actionGroup2.Add(new RemoveItemAction(character2, ItemType.Potion));
actionGroup2.Add(new SetHealthAction(character2, character2.MaxHealth));
actionGroup2.Add(new TalkAction(character2, "I feel better now!"));
actionManager.Add(actionGroup2);

// Every frame update the action manager
actionManager.Update(elapsed);

Z powodzeniem używałem tego systemu do prowadzenia całej rozgrywki w graficznej przygodzie, ale prawdopodobnie powinien on działać praktycznie na wszystko. Łatwo było również dodać inne typy akcji złożonych, które były używane do tworzenia pętli wykonawczych i warunkowych.


To wygląda na bardzo fajne rozwiązanie. Z ciekawości, w jaki sposób pozwalasz interfejsowi użytkownika wiedzieć, co rysować? Czy twoje obiekty gry (takie jak postacie) zawierają stan, który służy do identyfikacji tego, co wydarzyło się w celach renderowania, czy też sama akcja to robi?
Arkiliknam,

1
Zwykle moje działania zmieniają tylko stan bytów, a wszelkie zmiany renderowanego wyjścia następują w wyniku zmiany tego stanu, a nie za pomocą samych działań. Na przykład z rendererem trybu natychmiastowego nie jest wymagany żaden dodatkowy krok, ponieważ Drawmetoda jest już wbudowana w stan jednostki, a zmiany są automatyczne. W module renderującym w trybie zatrzymanym, takim jak Flash, można użyć obserwowalnego wzorca, aby wprowadzić zmiany w obiektach propagowane do obiektów wyświetlanych lub nawiązać połączenie ręcznie wewnątrz samego obiektu.
David Gouveia,

1
W pierwszej sytuacji powiedzmy, że twoja Characterklasa ma Positionwłaściwość i Drawmetodę, która odczytuje aktualną wartość Positioni rysuje tam poprawny obraz. W tej sytuacji wystarczy zaktualizować wartość Position, aby wynik był automatycznie widoczny na ekranie.
David Gouveia,

1
Druga sytuacja to sytuacja, gdy twoja właściwość Characterma Positionwłaściwość, ale przekazuje renderowanie do jakiegoś Spriteobiektu, który jest automatycznie renderowany przez wykres sceny lub coś takiego. W tej sytuacji musisz upewnić się, że zarówno pozycja postaci, jak i pozycja duszka są zawsze zsynchronizowane, co wymaga nieco więcej pracy. Jednak w obu przypadkach nie rozumiem, dlaczego menedżer akcji miałby mieć z tym coś wspólnego. :)
David Gouveia,

1
Obie metody mają zalety i wady. Poszedłem z drugą metodą do mojej gry 2D i czasami tego żałowałem, ponieważ znacznie trudniej jest zsynchronizować wszystko. Ale są też zalety, na przykład przy próbie wykrycia, który element został kliknięty, lub co należy narysować, ponieważ wszystko, co zostanie wyrenderowane, jest zawarte w tej samej strukturze danych zamiast rozproszonych między N typami jednostek.
David Gouveia,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.