Handling NPCs for dialogue, cutscenes.

I’ve recently been making quite a few state machines, and I’m wondering if it’s a viable approach to make a StateMachine for NPCs for controlling and moving them during the cutscenes.

Then maybe on top of that have cutscene class that I make children of and just manually setup a chain of events that plays out the statemachines of all the present NPCs in the order I code them.

EG: “MoveTowardsPosition”, “SayDialogue”, “PlayAttackAnimation”.

I’m worried since I’ve only been programming for 3 months now that I’m handling this in an impractical way, and any advice or direction to some helpful articles would be much appreciated. :sweat_smile:

IMHO this is indeed a viable and reasonable approach, but it all depends on the complexity and placement of your cut scenes. For instance, do they flow from gameplay to cutscene seamlessly or do you fade out to the cut scene and then back. For complexity, if you just have PacMan chase MrsPacMan accross the screen, then that is not complex enough to justify the complexity of classes and specific states for NPC animation, just hard code it, or use an animation player.

Also, you probably have state machines for your NPC’s already. Why not just re-use them (unless you have also hard coded behaviours into them as well). Then your code could be a long list of signals and responses like you described. “Move to target”, signal on arrival, “Say Dialogue”, signal on dialogue complete then play animation. But this comes back to how complex your scenes are, and how much of this cut scene code you can or want to re-use.

So in truth I don’t think that without more info I can offer much advice, apart from ‘don’t overcomplicate things’ and ‘DRY is a rule of thumb, not a law, sometimes it is better, easier and perfectly fine to repeat yourself’.

Here for instance is a little cut scene with signal and response in gameplay at the start of each level:

func start_level() -> void:
	...Some code here
	# Wait for signal ui_level_title_displayed


func _on_ui_level_title_displayed() -> void:
	... more code here
	# Wait for signal: ui_level_text_displayed


func _on_ui_level_text_displayed() -> void:
	... more code here
	# Wait for signal: ui_level_help_displayed


func _on_ui_level_help_displayed() -> void:
	... more code here
	# Wait for ui_ok_btn_pressed signal


func _on_ui_ok_btn_pressed() -> void:
	... more code here
	# Wait for signal: ui_level_text_hidden


func _on_ui_level_title_hidden() -> void:
	... more code here
	# Wait for signal: entry_wormhole_opened


func _on_entry_wormhole_opened() -> void:
	... more code here
	# wait for signal player_exited_wormhole


func _on_player_exited_wormhole() -> void:
	... more code here
	# Game started - player now has control

This uses all the player and enemy states that already exist, except they all have behaviours disabled, so that the normal behaviours are suspended until I release them back. In affect I override the normal behaviour managers with hard coded steps. (I took out the code as it would mean nothing to you here). This approach works during gameplay as well when I cut to scenes like when my player is frozen or out of power or a big boss is appearing etc. It may not be the cleanest of theoretical approaches but it lets me get on with it, and it is very easy to debug.