Categories
Game Development

New state design is working well.

I spent most of the morning getting the stupid “Remember Password” button working on the main menu screen. Irrlicht uses wchar_t instead of char*. Eventually I got fed up doing conversions so I added the ability to use setText with char* directly to their engine. My design has every game state totally independent of every […]

I spent most of the morning getting the stupid “Remember Password” button working on the main menu screen. Irrlicht uses wchar_t instead of char*. Eventually I got fed up doing conversions so I added the ability to use setText with char* directly to their engine.

My design has every game state totally independent of every other state. When a state enters it is allocated and when it exits it is actually deleted. From a modularity perspective this is awesome because I don’t have to worry much about states not working if I run them more than once. I also don’t have to worry about new states breaking existing states.

This created a problem with “Remember Password” because the GDD specifies that if the checkbox is checked it still won’t remember the password unless you have a successful login. Since the connection state knows about login-success I have to transfer data between states.

My solution was to include a “StateStack” class which is just a structure that indicates the caller and state-specific derived data. It is passed from the old state to the new state and managed by the state manager. This has worked out very nicely and is equivalent to passing parameters to a function. It is independent of the states themselves, lets me easily pass all the data I want, and I can store all the data in the new state with a memcpy. Since the connection state is used by multiple states (both connecting to the server chain and to a particular server) I also store the followup state in the state stack. This fixes a big problem I had working on SOG. In SOG, the state manager would remember the current and last state. It was only through hacks that we could get shared states to progress to the next state that we wanted. This solution lets me specify all the data I need in the StateStack class so there is no ambiguity.

Some data set by states needs to be known to the entire game, such as the password to save on shutdown or the current resolution. Those are stored in the game class I derive from the application interface. So far there isn’t much data of that sort and it’s worked fine.

In somewhat related news, I designed the network packet handling architecture to pass particular packet types to the states themselves. If a state does not choose to handle a packet, it goes to a default handler, which either handles it or does an assert. This is nice because the state itself handles packets particular to that state, minimizing information exposure, grouping related data, and eliminating the need for an external controller.

In all my prior games there was a network class of some sort that was responsible for all packets and would control the game. In theory this sounds like a good modular approach. In practice it was pointless because there is no functional relation between any of the operations handled in that module. This design created a lot of work because I had to put in custom accessors for every possible multiplayer operation. Changing states was especially a pain in the ass and usually involved polling for a variable changed by multiplayer code. I had cases where I had a while loop inside of a function that did nothing but call NetworkUpdate (to read packets) and wait for a variable to be set. Inside the loop it would check a variable to see if the escape key was pressed to cancel. With my current approach I could do the same thing with a state enum inside the state class and update the enum when the packet arrived in the OnNetworkPacket function. I could exit the state in the OnKeyPress function. This is much easier and cleaner.

Leave a Reply

Your email address will not be published. Required fields are marked *