Shared State Variables?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By tyo

Apologies if this is an inappropriate category/question for this category.

I’m curious how some of you guys manage your state machine variables shared across different states, particularly for a character? For example, velocity is something you might want to have several states mess with, so how do you allow those states to mess with that variable?

To be clear, I don’t mean for the new animation state machine, I’m talking about the state machine design pattern as it applies to godot.

Currently, I just have all those kinds of values stored in the “oldest parent” of the hierarchy so to speak, and all my states reference that parent to read or modify any desirable variable. I was thinking about using some sort of singleton to store it, as that script is getting quite cluttered… Hoping I can get some insight into how to organize some of this data better, really.

I’m working in GDscript currently, but if it would be smarter to do this sort of thing in C# or C++ I’m comfortable with implementing it in one of those to achieve what I want to achieve in some better way.

Thanks!

I think there is an underlying assumption that I want to ask about. Are your states in your state machine individual nodes?

iron_weasel | 2019-04-26 17:22

Indeed they are.

tyo | 2019-04-26 18:24

Hmm, I do it a totally different way. I build my states out of a combinations of functions and little wrapper objects. Common variables are just part of my player script. State specific variables are usually just part of the functions (which are co-routines). And any variables required for smooth transition are stored in the little wrapper objects.

I wonder if I should post a tutorial.

iron_weasel | 2019-04-27 17:11

:bust_in_silhouette: Reply From: Zylann

Currently the way you describe is also the way I do it. I store shared variables in a parent node, or whatever object being separate from the states themselves, and have states modify them.

One way to make this more clean would be to make it so states only have access to the variables relevant to them, so that could be done using wrapper functions or interfaces (but only C# has interfaces). There are many other ways, more or less complicated, ranging from getters to entire dependency frameworks. It depends which problem you are actually facing.

I would not advise singletons or statics unless you want those states to be unique and persist for the entire execution time of the game (including menus, and restart level mechanic, which typically screws you if you abused static variables).

:bust_in_silhouette: Reply From: iron_weasel

This might be a good candidate for a blog post, but I will explain how I store variables at the moment. First off, I use a lot of coroutines. For example, this function does most of the work of attacking.

func _punch_think(delta):
	$FlipMe/RunFX.emitting = false
	$AnimationPlayer.play(_think_state.attack_anim)
	while $AnimationPlayer.is_playing() or not is_on_floor():
		vel.y = min(vel.y + grav * delta, -jump_speed)
		if is_on_floor():
			vel.x = sign(vel.x) * max(abs(vel.x) - walk_accel * 0.5 * delta, 0)
		else:
			vel.x = _calc_horizontal_navigation(delta, false)
		vel = move_and_slide(vel, Vector2(0, -1))
		delta = yield()
	swap_think_to(Walk_State.new(self), delta)

So the first thing you notice is the answer to your question, where are the common variables stored. Common variables are just members of the script. State specific variables are just declared locally in the coroutine. For example, vel is a shared variable.

I have some local objects to help things out:

class Think_State:
#warning-ignore:unused_class_variable
	var owner
	var _think_func #function to call if no coroutine state
	var _next_think = null #store coroutine state
#	var _input_func #might be good in the future
	
	func process(delta):
		if _next_think:
			_next_think = _next_think.resume(delta)
		else:
			_next_think = _think_func.call_func(delta)
	
	func _cleanup():
		pass

class Punch_State extends Think_State:
#warning-ignore:unused_class_variable
	var str_repr = '[Punch_State %s]' % self
	var attack_anim
	
	func _init(owner, attack_anim):
		self.owner = owner
		self.attack_anim = attack_anim
		self._think_func = funcref(owner, '_punch_think')

In this example, the constructor is over ridden to store a transition variable in attack_anim.

Then the other critical functions are my process and swap think functions:

func _physics_process(delta):
	if cached_button_input:
		cached_button_input['time'] -= delta
		if cached_button_input['time'] < 0:
			cached_button_input = {}
	if not _think_state:
		swap_think_to(Walk_State.new(self), delta)
	_think_state.process(delta)

func swap_think_to(new_think_state, delta = 0):
	if _think_state:
		_think_state._cleanup()
	_think_state = new_think_state
	if delta > 0:
		_think_state.process(delta)

Don’t know if this helps…

Sounds pretty interesting. I’ll try my hand at implementing something like this in a naked project and see how it compares for my workflow. Appreciate the thorough explination!

tyo | 2019-04-29 11:58