How do you do the state machine for your game?

Started by Babar, Sat 24/11/2018 14:00:17

Previous topic - Next topic

Babar

The description for this sub-forum mentioned not being technical, and I'm not sure this counts as non-technical, but it isn't as technical as something I'd put in the technical forum, and it isn't a problem that I'm looking for a solution for a specific project just now, either, so I figured it'd fit here. If it fits somewhere else better, I'm sure a mod will move it.

So, a lot of my recent projects are rush jobs for jams or MAGS, and as such, they end up with a lot of ad hoc checks which I know shouldn't be the way I did them if I had planned properly.
For example, if ((player.HasInventory(iThing) && (oPlant.Visible)) { dialog1.Start(); } else { dialog2.Start(); }

As you can imagine, things get messy, especially when considering conflicts and finding situations where something should or shouldn't work.
When there's no possibility of using a combination of such explicit conditions, I end up creating a global variable, so then my code is littered with numerous different bools or ints also included in these ifs. I don't feel this is a good solution either.


  • I remember there used to be a state machine module or something similar by SSH from years ago- but I'm not sure how usable it'd be today.
  • I also had a thought of creating a global array where each index would be a decision point, and the value at that index would represent what choice the player took- the problem here is that I'd have to remember or comment down what each index and each value at each index meant.
  • Another thought I had was to use the score, perhaps even multiplying prime values to it, and then checking the mod of the total score to see what actions the player had taken- same problem as the last idea, I'd have to remember what each value meant.
How do you handle tracking the exact point of progress the player is at for your games? Is there some standard way of doing it that I'm missing?
The ultimate Professional Amateur

Now, with his very own game: Alien Time Zone

Snarky

Since I haven't actually programmed a proper adventure game, take this with a grain of salt, but maybe more experienced developers will chime in to contradict me.

I suspect most AGS games are programmed like yours, with very little structure to the representation of the overall game state and its logic. And in a lot of cases, with games that are fairly linear, not too complex, and not subject to a lot of revision during development, that might even be a workable approach. Yeah, if your code is getting messy and hard to follow it could probably do with some more organization, but I doubt there's a silver bullet that will render writing game logic instantly straightforward: some things are just inherently complex.

I tend to think you usually don't want a formal state machine (with a single state variable manipulated only through a single function) in the code, but that multiple state variables/flags are more convenient. Instead, I would start by moving all global game-state variables into a struct in a separate module script. For state variables with several possible values (representing e.g. different branching outcomes of a decision, different stages of a puzzle solution, or different chapters in a plot thread), I would use enums with descriptive values. If you have tests that are complex, that reoccur throughout the code, or that you think might change as the game develops, you can factor them out into this module as well. E.g.:

Code: ags
bool GameState::IsWelcomeAtHome()
{
  // You can go to your house during the first few chapters of the game as long as your relationship with your wife is at least neutral,
  // or in the "Fake Your Own Death" chapter, as long as you have your keys and haven't burned it down yet.
  return ( (GameState.Chapter < eChFakeYourOwnDeath && GameState.CindyMood >= 0) ||
           (GameState.Chapter == eChFakeYourOwnDeath && !GameState.HasBurnedDownHouse && player.HasInventory(iKeys)) );
}


It doesn't solve anything by itself, but keeping as much of the state and logic as possible in one place by itself lets you more easily see if you have redundancies or inconsistencies.

Dave Gilbert

#2
I can think of dozens of ways this can be organized. It really depends on what kind of game you're making. Since my game Unavowed involved progression through various story missions (as opposed to going through a puzzle dependency chart) I used global enums to designate where in each story mission the player was. For example, here's what I used for the chinatown section of the game:

Code: ags

enum eChinaPoint
{
  eChinatownStart=0, 
  eChinatownSawVision=1, 
  eChinatownLearnKevin=2, 
  eChinatownAuthorized=3, 
  eChinatownSawEvidence=4, 
  eChinatownEnterTree=5, 
  eChinatownReadWing=6, 
  eChinaTownLearnGhost=7, 
  eChinatownReadGhost=8, 
  eChinatownInVision=9, 
  eChinatownKevinCop=13, 
  eChinatownComplete=20, 
};


I created a global integer called ChinaTownPoint, and I'd set it to whatever point I needed it to. Like if the player passed the "saw vision" plotpoint, I'd use the command "ChinaTownPoint=eChinaTownSawVision;".

Various ad hoc checks were inevitable, but this helped keep it organized by story beats.

ManicMatt

That's really handy Dave, should I make another adventure game (My current project is mostly coded now) I will use this system should the need arise!

selmiak

@Dave: does this involve lots of if / else if / else statements where you check for e.g. eChinaPoint == eChinatownAuthorized or do you have to check for the number with enums? checking for these eGamePoint points makes the code a lot more readable while checking for numbers doesn't.

Snarky

#5
The example code I posted shows two ways you can use an enum value (in that case the variable is GameState.Chapter). You can do both exact comparison, as in if(ChinaTownPoint == eChinatownAuthorized), but you can also use inequalities, e.g. if(ChinaTownPoint < eChinaTownLearnGhost). In that case, it will be based on the number values assigned to each enum value. You never need to use the numbers directly: that's the whole point of an enum.

And remember that AGS supports switch/case now, so that's a cleaner alternative to a bunch of if/elseif clauses.

Radiant

Quote from: Babar on Sat 24/11/2018 14:00:17
So, a lot of my recent projects are rush jobs for jams or MAGS, and as such, they end up with a lot of ad hoc checks which I know shouldn't be the way I did them if I had planned properly.
For example, if ((player.HasInventory(iThing) && (oPlant.Visible)) { dialog1.Start(); } else { dialog2.Start(); }

This kind of checks does help in making your game less linear, which I consider a good thing (even if it's harder on the programmer).

Personally I use inventory a lot, including NPC inventory. For instance if you have given the McGuffin to Jack, then check if(Jack.HasInventory(iMcGuffin)) for his dialogue. For other things I use one state variable per character, which basically allows the player to solve each NPC's quest in the order he likes. Ideally you'd use enums for the values, like in Dave's example. Finally, I use a lot of disabled dialog options that get enabled in one place, e.g. when you talk to Jack it enables the options to ask other NPCs about Jack; this is manageable because this is all at the start of Jack's dialogue (or if you enter the fountain room, it enables the options to ask NPCs about the fountain).

HTH!

Gurok

I just want to point out that if you're talking about THE state machine for a graphical adventure, you're talking about things like player position, current room, inventory, that kind of thing. For instance, you could compare the complexity of a graphical adventure state machine to a choose-your-own-adventure state machine with its rather simple current page and variables (if any). Most of the answers here involve marking scenes or chapters with a state variable. That's fine, but it's not THE state machine, it's an additional one.
[img]http://7d4iqnx.gif;rWRLUuw.gi

SMF spam blocked by CleanTalk