___
Vice President of Marketing, Romeo Pie Software
Level 10
|
|
« on: July 21, 2009, 09:53:29 PM » |
|
Hello gents I'm approaching a rather complicated platforming game project in my near future, (dealing with game maker here) and I'm trying to think of the best way to go about this. I've just learned about the terms single state machine, and overlapping states, and I am intrigued! It seems since I've been making games, I've just been using a bunch of booleans and timers all independent of each other that affect how my character behaves. Stuff like "on_ground", "has_jumped", "grappled", "punching", etc. I guess this is referred to as overlapping states...? But now I'm starting to explore having a single variable "STATE" control a lot of stuff. So it would be things like "STATE_NORMAL", "STATE_DASH", "STATE_GRAPPLED", "STATE_SWING", "STATE_CROUCHING", "STATE_STANDING", etc. Having all the code for each state separate could be really handy, but I'm unsure if it will make things more complicated, and which states I should even be separating from each other. (Should I just have "STATE_NORMAL" cover basic standing, running, and jumping -- or have "STATE_STAND", "STATE_RUN", "STATE_JUMP"...?) I've been moving forward with perhaps a mix of a little from both sides, as both methods have advantages and disadvantages... but hopefully if anyone out there has any tips or experiences they'd like to share, it'd give me a clearer picture of what I'm doing. Okaaaaay GO.
|
|
|
Logged
|
|
|
|
Xion
|
|
« Reply #1 on: July 21, 2009, 10:56:55 PM » |
|
This interests me as well.
|
|
|
Logged
|
|
|
|
Martin 2BAM
|
|
« Reply #2 on: July 22, 2009, 05:00:55 AM » |
|
If things get overcomplicated, don't use it.
Use states to define actions that are mutually exclusive (like Moving left and Moving right). You can use two directional states to define movement (horizontal direction/vertical direction) That way you'll have 3 possible states in two variables. (left/right/still, up/down/still)
If you want to define a single direction state, you'll have to define several options (3*3)
still left left-up left-down right right-up ...
And that's not productive. Add to those jumping and shooting boolean states. The states will be multiplied by 2, twice (making you define 36 unique states!)
Nevertheless, for AI is a good way to make the entity follow some actions. You think the action in one part, set the entity state, and then on other part you perform it. I think it's pretty useful there.
For example, thinking states could be: Actions: Idle, Move towards target, Dodge, Shoot target, etc. Targets: PowerUp, Enemy, Dodge position
The state could go like this (pseudocode): if(action = Idle) { if(find enemy) { target = found enemy target type = enemy if(enemy in reach) action = shooting else action = move towards target } else if(find power up) { target = found power up target type = power up action = move towards target } } else if(action = shooting) { if(target is dead) action = idle }
Well, you get the picture.
Regards -Martín
|
|
|
Logged
|
|
|
|
Overkill
|
|
« Reply #3 on: July 22, 2009, 05:55:24 AM » |
|
Hmm, my platformers so far have used separate boolean "flags" instead of states. But I have wanted to simplify things a bit.
I would opt for a hybrid, kind of.
Directional state definitely needs to be an enum separate from motion state.
I think that vertical movement needs to have its own state variables. And horizontal movement would have separate states. This way your player can be VerticalState.JUMP and HorizontalState.MOVE, with Direction.RIGHT.
Also, if you think about, you probably still wanna have your guy fall rather than float there if he's swinging a sword. So that needs multiple states.
Avoids having horrendous unmaintainable messes by trying to mash every state in to one variable.
Also you'll still need additional flags probably. For instance, if you're in VerticalState.JUMP, you might keep a counter of how much more the player can jump before they reach their max height. If you're in VerticalState.FALL, you might keep a counter of how long they've been falling so you know if you need to introduce a "heavy landing" animation. If you're in HorizontalState.MOVE, you probably want to know if they're obstructed, so you can possibly introduce grappling near walls.
I dunno, this is stuff I *just* thought of, it might be flawed. I think states definitely clean things up, but make sure you stuff it into reasonably separated states, and still have those extra variables to track stuff. Yeah.
I will need to come back to this thread later! I'm sure it'll help a bunch.
|
|
|
Logged
|
|
|
|
Kadoba
|
|
« Reply #4 on: July 22, 2009, 06:46:14 AM » |
|
The only thing I use state machines for is for ai (really helpful) and "conditions". Or what I call mental and physical states. Mental is the ai state like "chase player" or "wander aimlessly". Only npcs have this state. On the other hand all entities have a physical state which deal with stuff like hitstun, attacking, anything that would take control away from the entitiy. In fact, all input is generally ignored if the state is anything but normal. There are three rules I have for physical states: 1) The entity only has control in the normal physical state 2) hitstun overrides all other states (unless your game doesn't have hitstun) 3) all states eventually return to normal. I usually set a timer that resets the state to normal. This way you dont have to handle transitions going back to the normal state. You just set the timer when you go into a state. States that have different phases are still one state. For example you wouldn't have a "state_attacking_pt1", "state_attacking_pt2", etc. Instead you would check the state timer to see how much time is left before reverting back to normal. so it would be like if (timer[0] > 10) //ready attack else if (timer[0] = 5) //attack happens else if (timer[0] < 5) // attack lag Put the logic for each state inside a different code action. At the top of have the line "if (state!= this_state) exit;" The problem with having states for every tiny thing, like running, jumping, ect, is that your code is everywhere and it gets really confusing in a hurry. States are suppose to alleviate complexity, not induce it. Don't bother declaring your states as constants. You're going to find a lot of your entities have uniqe states, especially with mental states (if you go that route) and since you have to declare all constants in the game in one place it gets messy. I just declare the states in the creation event mental states starting at 200 and physical at 100. so something like: PST_NORMAL = 100 PST_HITSTUN = 101 PST_ATTACKING = 102
MST_IDLE = 200 MST_CHASE = 201 MST_SHOOT = 203 (For those of you who just cringed Game Maker doesn't have enumerators) I just use boolean operators for jumping and such. Anyway, this all works for me but I would love to see how other people handle it.
|
|
« Last Edit: July 22, 2009, 06:49:40 AM by Kadoba »
|
Logged
|
|
|
|
Ishi
|
|
« Reply #5 on: July 22, 2009, 10:36:51 AM » |
|
I use states in platformers, usually reduced to "on ground" "in air" and then any specialist ones specific to the game (like standing in front of the exit playing an animation - a time when the player is invincible and can't move at all so it's handy for it to be seperated into its own state). "on ground" covers running, walking and standing in both directions just based on the velocity value and a direction bool.
Stuff like crouching I would handle with a bool as well. I find it's easier to adjust acceleration and animations when crouching rather than giving it a whole seperate state, since it shares the majority of things with standing/running like wall collision, falling off platforms, triggering crumbly platforms etc.
|
|
|
Logged
|
|
|
|
Zaknafein
|
|
« Reply #6 on: July 22, 2009, 10:45:29 AM » |
|
I know next to nothing about Game Maker, but in Fez I use a state-ish design pattern for the player actions. I have an ActionType enumeration that defines which state the player is in, it can only have one of these : None, // Default value (not initialized) Idle, // Not doing crap Walking, // Walking slow Running, // Walking fast Jumping, // Jumping Suffering, // Bouncing off a Hurter actor Falling, // Falling down (not grounded nor climbing, velocity.y < 0) Bouncing, // Bouncing off a Bouncer actor Flying, // Debug mode only, using "jetpack" Dropping // Dropping from a top-only-collision trile There are 41 of these right now and I keep adding to them whenever I need to. Then I have a series of Action classes that can apply to one or more ActionTypes, and additional conditions about the player like whether he is grounded or not, whether he stands in front of a particular object, etc. Each Action class has TestConditions(), Begin() and Act() methods. I have an instance of each Action class in a container, and every update I go through each of them, test the conditions, begin if it started to be active, and act if it's active. Oh and I have an extension method on ActionType which defines which sprite animation I should be using for each type. It's been working well for me up to now!
|
|
|
Logged
|
|
|
|
___
Vice President of Marketing, Romeo Pie Software
Level 10
|
|
« Reply #7 on: July 22, 2009, 05:03:30 PM » |
|
Thanks for the responses so far! I'm a little... scatter brained at the moment so here is my best shot at replying. Oh ho, interesting ZAKNAFEIN!... if that is your real name. Does gravity and friction and all that happen separate from these states, or does each one handle them on it's own? nitram_cero: I have done something like this for enemy AI before, and it keeps everything clear cut. thanks for the example. Overkill: having separate horizontal and vertical states might be a better way to approach it than I was thinking. Kadoba: That sounds like a good method. Right now I have two motions that are pretty separate from each other, regular platforming with gravity and such, and swinging on a grappling hook (the rules sort of change when rotating around a point. no gravity, input affects the object differently, etc), so these work quite well as separate states entirely. I also define states in the create events as integers, I didn't even think about using constants... those are always a pain to edit anyway. Using a timer is also something I do a lot. I don't know about making each state a separate code action entirely... maybe if I get to a point where stuff is looking out of control though it's a possibility. Ishi: So you just go with overlapping states then? A lot of booleans? I started working on my platforming code somewhat. I combined my player's grappling code with my base actor's platforming code and put the grapple code in state_swing, while the regular platforming is in state_normal. I tried adding a state_dash for dashing to the left or right (like a Mega Man X style dash) but adding that as a separate state seemed immediately dumb to me after I implemented it because gravity and stuff only applies when the state is state_normal. So... dashing might be a boolean instead of a state, or ... I'd have to place all the normal code in with the dash state, or switch the state == state_normal into state == state_normal || state == state_dashing. gah! I think my problem lately is thinking too much...
|
|
|
Logged
|
|
|
|
Zaknafein
|
|
« Reply #8 on: July 22, 2009, 05:44:56 PM » |
|
I prefer to be called... Renaudbedardrenaudbedard. As per my MIGS pass.
Gravity happens in the "Falling" Action class, i.e. its Act() method is called on every ActionType that doesn't defy gravity (like climing ladders for instance).
Friction is another thing,... After looping through all the Actions, all the player physics and collision are applied based on the modifications they've made on the Velocity vector. So I try to keep it separate.
|
|
|
Logged
|
|
|
|
andy wolff
|
|
« Reply #9 on: July 22, 2009, 06:05:33 PM » |
|
I used an offshoot of what you've described as single-state machines when I was experimenting with a turn-based strategy sort of game with 2d platforming aspects. Instead of having one state, I found it was better to have two or three different states in a hierarchy of sorts, using them as an action tree with both branching and converging limbs, as it were.
I, however, would contend that the best way to code anything is the way you're most comfortable with, be it more or less versatile than another way or not.
|
|
|
Logged
|
|
|
|
Ishi
|
|
« Reply #10 on: July 23, 2009, 12:28:54 PM » |
|
Ishi: So you just go with overlapping states then? A lot of booleans? I started working on my platforming code somewhat. I combined my player's grappling code with my base actor's platforming code and put the grapple code in state_swing, while the regular platforming is in state_normal. I tried adding a state_dash for dashing to the left or right (like a Mega Man X style dash) but adding that as a separate state seemed immediately dumb to me after I implemented it because gravity and stuff only applies when the state is state_normal. So... dashing might be a boolean instead of a state, or ... I'd have to place all the normal code in with the dash state, or switch the state == state_normal into state == state_normal || state == state_dashing. gah! I think my problem lately is thinking too much... Sort of a combination of the two methods. Kind of like you said there with the dash, if it's not that different from the normal state I'll do it as overlapping. It does soon get quite messy though. I'm liking the sound of Renaud's mega-OOP system. All nicely stuctured, and it's easy to create new states that reuse elements of others like Falling. I should make more effort to think up clean solutions like that rather than just coding-as-I-go all the time.
|
|
|
Logged
|
|
|
|
___
Vice President of Marketing, Romeo Pie Software
Level 10
|
|
« Reply #11 on: July 23, 2009, 07:25:49 PM » |
|
I'm pretty dumb, Renaud... you have just the falling state that adds gravity? So are your states .. overlapping? Like does the falling state also happen with say... "bouncer"? (I'm interpreting bouncer as an object I'd collide with and be thrown off in some vector, so possibly into the air -- so then gravity would have to act to pull me down)
|
|
|
Logged
|
|
|
|
Ivan
Owl Country
Level 10
alright, let's see what we can see
|
|
« Reply #12 on: July 23, 2009, 07:29:14 PM » |
|
Gravity should be applied always, no? no matter what state?
|
|
|
Logged
|
|
|
|
Zaknafein
|
|
« Reply #13 on: July 23, 2009, 07:51:43 PM » |
|
xerus : States overlap, yeah. Each Action class is run if its conditions are met, more than one can be run, but the current ActionType mostly just defines which sprite animation should be displayed. And it's the main condition upon which Action classes decide to act or not.
Ivan : I suppose that climbing a ladder should just "friction out" gravity, or cancel it out somehow. I just decide not to apply it.
And I wouldn't say that my stuff is a justifiable and proper way of doing things. It's a way, and it's hacky in some areas... but it keeps things nice and separate most of the time. Which comes back to what Andy said last : do whatever you're most comfortable with.
|
|
|
Logged
|
|
|
|
___
Vice President of Marketing, Romeo Pie Software
Level 10
|
|
« Reply #14 on: July 23, 2009, 08:13:58 PM » |
|
xerus : States overlap, yeah. Each Action class is run if its conditions are met, more than one can be run, but the current ActionType mostly just defines which sprite animation should be displayed. And it's the main condition upon which Action classes decide to act or not.
Oh okay, gotcha. Right... earlier you said each one has testconditions, begin, and act. Hm! An interesting approach for sure... THANKS
|
|
|
Logged
|
|
|
|
Ishi
|
|
« Reply #15 on: August 07, 2011, 02:19:22 AM » |
|
This thread is a couple of years old, but I dug it up because I fancied having another look at it. Thought I'd bump it because it's pretty interesting. I'm about to embark on a bit of character movement today, and I'm gonna class the hell out of it.
|
|
|
Logged
|
|
|
|
|