{"id":90,"date":"2009-10-31T07:57:41","date_gmt":"2009-10-30T18:57:41","guid":{"rendered":"http:\/\/www.paradicesoftware.com\/blog\/?p=90"},"modified":"2009-11-02T17:59:29","modified_gmt":"2009-11-02T04:59:29","slug":"controlling-gamestate","status":"publish","type":"post","link":"http:\/\/www.paradicesoftware.com\/blog\/2009\/10\/controlling-gamestate\/","title":{"rendered":"Controlling Gamestate"},"content":{"rendered":"<p>Virtually all programs have this, a central &#8216;game&#8217; loop that grabs user input, updates game logic, redraws the screen. At it&#8217;s simplest possible level, it usually looks something like this:<\/p>\n<blockquote>\n<pre>repeat\r\n   Input;\r\n   Update;\r\n   Redraw;\r\nuntil Quit;<\/pre>\n<\/blockquote>\n<p>The problem mainly comes in that middle section, <em>Update<\/em>. For any game, there are a variety of different states you can be in (main menu, options screen, credits, playing game) and for most games, &#8220;playing game&#8221; breaks down into a whole lot more states by itself. For my simple 500 Card Game, I had states &#8220;bidding&#8221;, &#8220;dealing&#8221;, &#8220;choosing kitty&#8221;, as well as playing. If you&#8217;re not careful, your <em>Update<\/em> procedure can end up looking like this:<\/p>\n<blockquote>\n<pre>Procedure Update;\r\nbegin\r\n   case State of\r\n      STATE_MENU:\r\n         \/\/ wait for a menu click\r\n      STATE_OPTIONS:\r\n         \/\/ handle user input\r\n      STATE_CREDITS:\r\n         \/\/ roll some credits\r\n      STATE_GAME:\r\n         case GameState of\r\n            GS_PLAYING:\r\n               \/\/ handle user input\r\n               \/\/ update some monsters\r\n            \/\/ etc\r\n         end;\r\n      \/\/ STATE_ even more stuff:\r\n   end;\r\nend;<\/pre>\n<\/blockquote>\n<p>So far, so big and clunky. It gets worse when you have to handle nested state transitions: in my card game, someone could be playing, then go to the Menu and come back. So I had to have a variable &#8216;LastState&#8217; that remembered where they were, so they could go back to it. But it&#8217;s worse than that, because they might go to the Menu, then go to the Options screen: and when they came back, they&#8217;d still want their current game to not be lost! My card game was simple, so I managed this as simply as I could: when entering the &#8216;menu&#8217; state, the menu grabbed it&#8217;s own copy of LastState, and it restored this value when the other screens (Options, Credits etc) returned to it, so there was always a way back to the game. This was the simplest case,\u00a0because every state had exactly one other state that it could be transitioned from (you couldn&#8217;t get to Options from Game without going through Menu).<\/p>\n<p>For Icefall though, the stakes were raised. I wanted the Options screen accessible from in-game, as well as in the initial menu. And there are\u00a0many more states that the game can be in, and some of them should not be returned to\u00a0(e.g\u00a0the &#8220;create character&#8221; screen), so rather than a gigantic and ugly\u00a0Update loop, I went back to good old OOP and implemented a class-based state stack.<\/p>\n<p>Icefall has a central &#8220;State Engine&#8221; that looks after the current state stack, handles transitions between them, and calls\u00a0whichever state is &#8216;on top&#8217; during the main loop. The State class looks something like this:<\/p>\n<blockquote>\n<pre>TIceState = class (TIceClass)\r\n   constructor Create\r\n      (StateEngine: TIceStateEngine; \r\n       Transition: TStateTransition);\r\n   procedure OnEnter; virtual;\r\n   procedure OnLeave; virtual;\r\n   procedure Update; virtual;\r\n   procedure Redraw;\r\n   \/\/ other useful state methods\r\n   destructor Destroy; override;\r\nend;<\/pre>\n<\/blockquote>\n<p>Essentially, for every state the game can be in, I have a corresponding class that defines it&#8217;s specific behaviour in <em>Update<\/em>. I can also do other &#8216;maintenence&#8217; (e.g. ensure the right music is playing) by overriding the OnEnter and OnLeave procedures. The game can change states by constructing a new <em>TIceState<\/em> instance, passing it a reference to the state engine and describing the type of transition to use (e.g. whether to nest the state [for a trip to a Menu] or just progress to the new state with no way back [like a character create screen]). You can also leave a state just by destructing the class instance: the state engine will pop back to the next highest state on the state stack, or quit the game if there are no states left (e.g., you just hit &#8216;Exit&#8217; from the Main Menu).\u00a0<\/p>\n<p>The state engine takes care of nested states and transitions, and someone&#8217;s\u00a0<strong>exact<\/strong> state in the game can be saved (if needed) by storing the entire state stack: while I&#8217;m unlikely to want the game to be saved while on the\u00a0Character Create screen, it&#8217;s good to know I can safely save the game while in shops, or at any other point, without even worrying about special handling code to cover all the situations.<\/p>\n<p>Another interesting thing I have done is move the game&#8217;s &#8220;loader&#8221; (the part that loads textures, sounds, music, fonts, databases) into it&#8217;s own\u00a0state, and just made it the very first state that&#8217;s pushed onto the stack, and it takes a parameter to the state to transition to when it&#8217;s done. It takes the whole &#8216;loading&#8217; step out of the main game code, which makes that much simpler, and I can make the loader just load the resources I&#8217;m going to need right now: some other state (like the credits) can always call it again later to load specific credit-y images.<\/p>\n<p>I can also use the the same game engine to build other tools like the Map Editor: the only difference between the map editor and the game itself is that it compiles in a different set of states: when the map editor has loaded, it launches itself into a <em>TMapEditorState<\/em>\u00a0instead of a <em>TMainMenuState<\/em>.<\/p>\n<p>Overall, having a <strong>state stack<\/strong> is\u00a0very\u00a0flexible, quite simple to use and maintain, and overall a vast improvement from the &#8216;giant case statement&#8217; I was using previously.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Virtually all programs have this, a central &#8216;game&#8217; loop that grabs user input, updates game logic, redraws the screen. At it&#8217;s simplest possible level, it usually looks something like this: repeat Input; Update; Redraw; until Quit; The problem mainly comes in that middle section, Update. For any game, there are a variety of different states<\/p>\n","protected":false},"author":2722,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[6,3],"tags":[77,15,76],"class_list":["post-90","post","type-post","status-publish","format-standard","hentry","category-code","category-icefall","tag-code","tag-gamestate","tag-icefall"],"_links":{"self":[{"href":"http:\/\/www.paradicesoftware.com\/blog\/wp-json\/wp\/v2\/posts\/90","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/www.paradicesoftware.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/www.paradicesoftware.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/www.paradicesoftware.com\/blog\/wp-json\/wp\/v2\/users\/2722"}],"replies":[{"embeddable":true,"href":"http:\/\/www.paradicesoftware.com\/blog\/wp-json\/wp\/v2\/comments?post=90"}],"version-history":[{"count":11,"href":"http:\/\/www.paradicesoftware.com\/blog\/wp-json\/wp\/v2\/posts\/90\/revisions"}],"predecessor-version":[{"id":112,"href":"http:\/\/www.paradicesoftware.com\/blog\/wp-json\/wp\/v2\/posts\/90\/revisions\/112"}],"wp:attachment":[{"href":"http:\/\/www.paradicesoftware.com\/blog\/wp-json\/wp\/v2\/media?parent=90"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.paradicesoftware.com\/blog\/wp-json\/wp\/v2\/categories?post=90"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.paradicesoftware.com\/blog\/wp-json\/wp\/v2\/tags?post=90"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}