Hallo zusammen,
ich arbeite gerade an einem kleinen Spiel und habe mir dazu eine kleine "GameEngine"/Grundstruktur auf die Beine gestellt.
Diese sieht folgendermaßen aus:
Ich habe eine Main-Klasse „GameApp“, welche die Applikation kapselt bzw. die eigentliche GameEngine repräsentiert.
In der GameApp-Klasse gibt es eine Methode „GameLoop()“ welche die Hauptschleife des Spiels darstellt (ProcessInput, Update, Render). Diese beinhaltet auch die Win32-Message-Loop, in welcher ich die Inputs vom Betriebssystem beziehe.
Generell besteht meine GameEngine aus GameStates und GameComponents. Ein GameState ist ein Zustand, in welchem sich das Spiel befindet. Beispiel: beim Start des Programms befindet es sich im State „IntroGameState“. Nachdem man beispielsweise den „Play Button“ klickt, findet ein Zustandsübergang zum State „IngameGameState“ statt. Dazu habe ich einfach eine simple StateMachine gebastelt. Außerdem gibt es Komponenten (GameComponents) wie z.B. einem „Player-Objekt“ oder einem „Gegner“, „Grid“, „Menu“, etc.
Ein GameComponent implementiert dabei das Interface IDrawable (renderbar) oder IUpdateable („updatebar“) oder eben beide.
Ein GameState kann wiederum GameComponents enthalten.
Ich möchte nun einen InputManager schreiben. Momentan habe ich dazu ein Objekt der Klasse InputManager, welches von der GameApp-Hauptklasse verwaltet wird.
Der Header des InputManagers sieht wie folgt aus (grob/under construction):
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
enum InputType
{
Keyboard = 0x01,
Mouse = 0x02,
Joystick = 0x04
};
struct InputContext
{
InputType InputType;
std::function<void(void)> Callback; // Signatur ändert sich noch (Input soll dann über die Callback an die jeweilgen Komponenten überreicht werden)
InputContext(::InputType inputType, std::function<void(void)> callback) :
InputType(inputType),
Callback(callback)
{}
};
class InputManager
{
public:
InputManager();
~InputManager();
void ProcessRawInputEvent(const sf::Event& rawEvent);
void Update();
void Dispatch();
bool Register(std::string context, std::function<void(void)> callback);
void SubscribeKeyboardInput(std::string context);
void SubscribeMouseInput(std::string context);
void SubscribeJoystickInput(std::string context);
void SubscribeAll(std::string context);
private:
std::map<std::string, InputContext> m_activeContexts;
};
|
Ich habe mir das folgendermaßen gedacht: Der InputManager wird mit RawInput vom OS gefüttert und leitet den Input an interessierte Abonnenten weiter. Dabei können sich z.B. GameComponents beim InputManager registrieren und einen Funktionpointer an diesen geben, damit der InputManager im Falle eines Inputs die entsprechenden Callbacks rufen kann. Außerdem können die Komponenten sich für bestimmte Input-Typen registrieren (Subscribe...).
Die registrierten Komponenten mit deren Callback werden aktuell in einer map gespeichert. Context ist ein einfacher String-Identifier, welcher die verschiedenen Komponenten/Kontexte auseinanderhalten soll.
Meine Frage ist nun: ist das einigermaßen sinnig? Außerdem weiß ich nicht genau, wie ich den Zugriff auf den InputManager effizient/sauber/schön gewährleisten soll. Dieser wird ja von der GameApp verwaltet. Das Spiel besteht aus mehreren GameStates, welche wiederum aus mehreren/bis zu vielen GameComponents bestehen können. Sowohl GameStates als auch GameComponents sollten in der Lage sein, Inputs zu empfangen. Ich finde es aber „unschön“ eine Referenz des InputManagers von der GameApp an die einzelnen States zu reichen und von dort aus wiederum an die einzelnen/vielen GameComponents. Gibt es da eine elegantere Möglichkeit? Die Referenz muss ich durchreichen, damit ich von den jeweiligen Komponenten aus registrieren kann bzw. die Subscribe-Methoden aufrufen kann. Oder sollte ich das Registrieren und Subscriben aus der GameApp-Klasse aus machen? Wäre aber auch unschön, da ich dann alle GameKomponenten in der GameApp kennen müsste. Extern/Global wäre eine weitere Möglichkeit die mir einfallen würde. Ebenfalls unschön
Habt ihr vielleicht eine Idee/Tipp(s) wie ich das am saubersten umsetzen kann?
Bei Bedarf kann ich auch mehr Source-Code nachreichen.
Danke im Voraus und Gruß,
n2k