Party-Based Strategy RPG Combat Script (Godot-4, GDScript)

Godot Version

v4.2.1.stable.official [b09f793f5]

Question

Hello, I was recently inspired to try making a game for the first time by Pirate Software’s content / recommendation of Godot and I’m having a lot of fun, but I think I could use some help. I have some general questions I want to ask, but I also have a specific question that I think can help me move forward. I’ll ask the specific question first. How can I structure combat code to process unit turns one at a time (e.g. player character 1, enemy 1, pc2, pc3, e2, e3, e4, pc4, etc.)? I have attempted a few things and this is my most recent attempt:

combat_engine.gd

func _ready():      
    # "party" is an array of Definitions retrieved from a PartyVariables Singleton which contains information about party members (stats, personal details).
	party = PartyVariables["characters_in_party"]

# I am starting with this simple "confirmed" event to try to trigger changing which character is being controlled.
signal confirmed
func _input(event: InputEvent) -> void:
	if event.is_action_pressed("ui_accept"):
		confirmed.emit()

# This function is called when combat is triggered (battle happens on the same map as exploration and I want to trigger with a signal). Right now I am triggering combat with a button.
func combat():
	#start processing combat
	var round : int = 0
	while(true):
		# "party" is sorted by the speed stat stored in the Singleton.
		party.sort_custom(sort_characters_by_speed)
		
		# For each character in the active party...
		for character in party:
			# ...check if there is an active character...
			if(active_character):
				# ...disable them if there is...
				active_character["is_active_character"] = false
			# ...set the new character as the active character.
			# I am switching which character is being controlled by user input with this bool.
			character["is_active_character"] = true
			active_character = character
			print(character)
			
            # Wait until the player hits 'ui_accept' to move to the next character in the turn order.
			# Doesn't seem to work; only waits once per loop?
			await confirmed
			print("Character Turn Ended")
		
		# Increment round count each time every character has acted
		round += 1
		#if combat over return (eventually will check victory condition is met)
		return

Some further context: I want to make a strategy RPG with 2 combat layers, (1) a map (same as exploration map) where units move around and select general actions (attack, wait, magic, use item) and (2) a combat interface that appears when attack is selected. Right now, I am working on (1).

I think I must be using a bad design pattern (I suspect I am using await incorrectly at least). This is what I’m printing to the console:

{ “name”: “Aristotle”, “is_in_party”: true, “is_active_character”: true, “stats”: { “health”: 10, “strength”: 5, “speed”: 2, “move”: 5 } }
Character Turn Ended
{ “name”: “Thomas Aquinas”, “is_in_party”: true, “is_active_character”: true, “stats”: { “health”: 10, “strength”: 5, “speed”: 1, “move”: 4 } }
{ “name”: “Aristotle”, “is_in_party”: true, “is_active_character”: true, “stats”: { “health”: 10, “strength”: 5, “speed”: 2, “move”: 5 } }
— Debugging process stopped —

When I start combat the character Aristotle is given control (because he is fastest). When I hit ‘enter’, I print ‘Character Turn Ended’ as expected, but then it bypasses the second active party member (of 2 total) and goes back to the top of the turn order.

I think if I figure out a good general approach to processing turns in sequence, I can start making some progress on combat, but for anyone who is still reading, I could also use general feedback on best design practices. ^^

My project is structured like this so far:

Assets
–Art (contains all my sprites, tilesets)
Scenes
–Characters
----Enemies (scenes representing enemies)
----Players (scenes representing the party members)
–Levels (scenes representing maps)
–UIs
Scripts
–Characters (scripts attached to characters)
–UIs (scripts attached to UIs)

General questions:
(A) I have a generic “character_map_sprite.tscn” that contains a Sprite2D, CollisionShape2D, and RayCast2D that I am dropping into my specific player scenes and enemy scenes. I then drop the character scene into the level scene. Is this a good pattern?

(B) Additionally, this is how my first level is laid out and I suspect it is not well designed. Any suggestions for better organizing the scene?
image

  • The buttons are just for testing (e.g. setting active character, starting combat).
  • “combat” has a script called “combat_engine.gd” which I was planning to put all of the combat logic into.
  • The player scenes (aristotle, thomas) were children of combat because I was directly referencing them in the script before, but now that I moved all their attributes to the Singleton I can move them back to being children of level_1.
  • “character_map_sprite” has a script called “character_map_sprite.gd” that contains movement & collision logic (_undhandled_input function where if the character the script is attached to is the active character (stored in Singleton), then it will check for collision / move that sprite).

(C) My PartyVariables Singleton contains a bunch of Dictionaries which are grouped into arrays by _ready(). Then I use the arrays in other scripts (e.g. the party variable in the combat function). Is this good? Should I define a custom class for a “Character” rather than using a generic dictionary / can I create a complex dictionary with nested dictionaries in a class?

(D) I was planning to have another Singleton for storing enemy stats, but is this necessary since they won’t be persistent? Should I just define the enemy stats in the enemy scene (“goblin.tscn”, “zombie.tscn”, etc.)? I tried to create a generic class (“character.tscn”), which would have all the relevant variables for any character (enemy or player), but I was having trouble figuring out how to do this and couldn’t find a tutorial that went over it. Is there a good tutorial for this concept? (i.e. multiple monster scenes that extend a generic character class that contains all the stats needed for a monster, how to get and set those stats, etc.)

(E) It seems tedious & risky (typos) to access party Dictionaries like character[“stats”][“speed”] every time I want to reference player information. Is there a better way to do this? Ideally I would like to type “character.” and have the editor auto-fill the potential properties of the character (“stats”, “is_active_character”, etc.).

I know that is a lot of dense questions! If you have any resources you think would help me (video or text tutorials, specific pages of the Godot documentation), I would be very happy to learn from them.

I found this tutorial (https://www.youtube.com/watch?v=ifXGvlAn0bY&ab_channel=JonTopielski) that answered my question about (D), so now I have a script / class that exports variables for character stats that I can easily apply to resources or scenes.

I need to read up on resources vs scenes, now!