Elastyczne alternatywy dla wielu wielu małych klas polimorficznych (do użycia jako właściwości, wiadomości lub zdarzenia) C ++

9

W mojej grze są dwie klasy, które są naprawdę przydatne, ale powoli stają się uciążliwe. Wiadomość i właściwość (właściwość jest zasadniczo składnikiem).

Oba pochodzą z klasy podstawowej i zawierają statyczny identyfikator, dzięki czemu systemy mogą zwracać uwagę tylko na te, które chcą. Działa bardzo dobrze ... oprócz ...

Ciągle tworzę nowe typy wiadomości i typy właściwości, gdy rozszerzam swoją grę. Za każdym razem muszę napisać 2 pliki (hpp i cpp) i tonę bojlera, aby uzyskać zasadniczo identyfikator klasy i jeden lub dwa standardowe typy danych lub wskaźnik.

Zaczyna się od zabawy i testowania nowych pomysłów. Chciałbym, gdy chcę utworzyć nową wiadomość lub typ właściwości, chcę móc po prostu wpisać coś takiego

ShootableProperty:  int gunType, float shotspeed;

ItemCollectedMessage:  int itemType;

zamiast tworzyć plik nagłówka i cpp, pisać konstruktor, w tym klasę nadrzędną itp.

To około 20 - 40 linii (włączając strażników i wszystko) tylko po to, żeby logicznie zrobić 1 lub 2 linie w mojej głowie.

Czy istnieje jakiś wzorzec programowania, aby obejść ten problem?

Co ze skryptami (o których nic nie wiem) ... czy istnieje sposób na zdefiniowanie grupy klas, które są prawie takie same?


Oto dokładnie, jak wygląda jedna klasa:

// Velocity.h

#ifndef VELOCITY_H_
#define VELOCITY_H_

#include "Properties.h"

#include <SFML/System/Vector2.hpp>

namespace LaB
{
namespace P
{

class Velocity: public LaB::Property
{
public:
    static const PropertyID id;

    Velocity(float vx = 0.0, float vy = 0.0)
    : velocity(vx,vy) {}
    Velocity(sf::Vector2f v) : velocity(v) {};

    sf::Vector2f velocity;
};

} /* namespace P */
} /* namespace LaB */
#endif /* LaB::P_VELOCITY_H_ */



// Velocity.cpp

#include "Properties/Velocity.h"

namespace LaB
{
namespace P
{

const PropertyID Velocity::id = Property::registerID();

} /* namespace P */
} /* namespace LaB */

Wszystko to tylko dla wektora 2D i identyfikatora, który mówi, że oczekuje wektora 2D. (Oczywiście niektóre właściwości mają bardziej skomplikowane elementy, ale to ten sam pomysł)

Bryan
źródło
3
Spójrz na to, może ci pomóc. gameprogrammingpatterns.com/type-object.html
1
Wszystko to wygląda na to, że szablony mogą pomóc, ale nie ze statyczną inicjalizacją. W tym przypadku z pewnością można to znacznie ułatwić, ale trudno jest powiedzieć bez dodatkowych informacji o tym, co chcesz zrobić z tymi identyfikatorami i dlaczego w ogóle używasz dziedziczenia. Korzystanie z publicznego dziedziczenia, ale bez funkcji wirtualnych, to zdecydowanie zapach kodu ... To nie jest polimorfizm!
ltjax,
Czy znalazłeś bibliotekę innej firmy, która ją implementuje?
Boris

Odpowiedzi:

1

C ++ jest potężny, ale pełny . Jeśli chcesz mieć wiele małych klas polimorficznych, które są różne, to tak, potrzeba dużo kodu źródłowego do zadeklarowania i zdefiniowania. Naprawdę nie ma z tym nic wspólnego.

Teraz, jak powiedział ltjax, to, co tu robisz, nie jest dokładnie polimorfizmem , przynajmniej dla dostarczonego kodu. Nie widzę wspólnego interfejsu, który ukrywałby określoną implementację podklas. Może z wyjątkiem identyfikatora klasy, ale w rzeczywistości jest to zbędne z prawdziwym identyfikatorem klasy: to nazwa. Wydaje się, że to tylko kilka klas zawierających pewne dane, nic naprawdę skomplikowanego.

Ale to nie rozwiązuje twojego problemu: chcesz stworzyć wiele wiadomości i właściwości przy minimalnej ilości kodu. Mniej kodu oznacza mniej błędów, więc jest to mądra decyzja. Niestety dla ciebie nie ma jednego rozwiązania. Trudno powiedzieć, co najbardziej odpowiada Twoim potrzebom, nie wiedząc dokładnie, co zamierzasz zrobić z tymi wiadomościami i właściwościami . Pozwól, że przedstawię twoje opcje:

  1. Użyj szablonów C ++ . Szablony to świetny sposób, aby kompilator „napisał kod” za Ciebie. Staje się coraz bardziej widoczny w świecie C ++, ponieważ język ewoluuje, aby lepiej go wspierać (np. Możesz teraz używać zmiennej liczby parametrów szablonu ). Istnieje cała dyscyplina poświęcona zautomatyzowanemu generowaniu kodu za pomocą szablonów: metaprogramowanie szablonów . Problemem jest: to trudne. Nie oczekuj, że osoby, które nie są programistami, będą mogły same dodawać nowe właściwości, jeśli planujesz użyć takiej techniki.

  2. Użyj zwykłego starych makr C . Jest to stara szkoła, łatwa do nadużywania i podatna na błędy. Są również bardzo proste do utworzenia. Makra to po prostu uwielbione kopie-pasty wykonywane przez preprocesora, więc w rzeczywistości nadają się do tworzenia ton rzeczy, które są prawie takie same z małymi wariacjami. Ale nie oczekuj, że inni programiści pokochają cię za ich używanie, ponieważ makra są często używane do ukrywania błędów w ogólnym projekcie programu. Czasami jednak są pomocni.

  3. Inną opcją jest użycie zewnętrznego narzędzia do wygenerowania kodu. Zostało to już wspomniane w poprzedniej odpowiedzi, więc nie będę się nad tym rozwijał.

  4. Są inne języki, które są mniej gadatliwe i pozwolą ci łatwiej tworzyć klasy. Więc jeśli masz już powiązania skryptu (lub planujesz je mieć), zdefiniowanie tych klas w skrypcie może być opcją. Dostęp do nich z C ++ będzie jednak dość skomplikowany i stracisz większość korzyści z uproszczonego tworzenia klas. Prawdopodobnie jest to wykonalne tylko wtedy, gdy planujesz wykonywać większość logiki gry w innym języku.

  5. Na koniec należy rozważyć zastosowanie projektu opartego na danych . Możesz zdefiniować te „klasy” w prostych plikach tekstowych przy użyciu układu podobnego do tego, który sam zaproponowałeś. Musisz utworzyć dla niego niestandardowy format i analizator składni lub użyć jednej z kilku już dostępnych opcji (.ini, XML, JSON i tak dalej). Następnie po stronie C ++ musisz stworzyć system, który obsługuje kolekcję różnych rodzajów obiektów. Jest to prawie równoważne z podejściem skryptowym (i prawdopodobnie wymaga jeszcze więcej pracy), z tym wyjątkiem, że będziesz w stanie dostosować go bardziej precyzyjnie do swoich potrzeb. A jeśli uprościsz to, nieprogramiści mogą samodzielnie tworzyć nowe rzeczy.

Laurent Couvidou
źródło
0

Co powiesz na narzędzie do generowania kodu, które Ci pomoże?

na przykład możesz zdefiniować typ wiadomości i członków w małym pliku tekstowym, a narzędzie gen gen parsuje je i zapisuje wszystkie pliki C ++ na płycie głównej jako krok przed kompilacją.

Istnieją istniejące rozwiązania, takie jak ANTLR lub LEX / YACC, i nie byłoby trudno wdrożyć własne w zależności od złożoności właściwości i komunikatów.

Chroniczny programista gier
źródło
Po prostu alternatywa dla podejścia LEX / YACC, kiedy muszę generować bardzo podobne pliki z niewielkimi modyfikacjami, używam prostego i małego skryptu Pythona oraz pliku szablonu kodu C ++ zawierającego tagi. Skrypt Pythona szuka tych znaczników w szablonie, zastępując je reprezentatywnymi tokenami tworzonego elementu.
zwycięzca
0

Radzę zapoznać się z buforami protokołów Google ( http://code.google.com/p/protobuf/ ). Są bardzo sprytnym sposobem obsługi ogólnych komunikatów. Musisz tylko określić właściwości we wzorcu przypominającym strukturę, a generator kodu wykona za Ciebie zadanie wygenerowania klas (Java, C ++ lub C #). Wszystkie generowane klasy mają parser tekstowy i binarny, co czyni je dobrymi zarówno do inicjowania wiadomości tekstowej, jak i serializacji.

dsilva.vinicius
źródło
Moje centrum przesyłania wiadomości jest rdzeniem mojego programu - czy wiesz, czy bufory protokołów wiążą się z dużym obciążeniem?
Bryan
Nie sądzę, aby poniosły one duży narzut, ponieważ API jest tylko klasą Builder dla każdej wiadomości i klasą dla samej wiadomości. Google wykorzystuje je jako rdzeń swojej infrastruktury. Na przykład wszystkie encje Google App Engine są konwertowane na bufory protokołów przed utrwaleniem. W razie wątpliwości radzę zaimplementować test porównawczy między bieżącą implementacją a buforami protokołów. Jeśli uważasz, że narzut jest do zaakceptowania, użyj ich.
dsilva.vinicius