Character Switching in Godot

Godot Version

4.2

Question

I’d like to toggle between 3 characters however I have no idea of going about that.
I tried a state system but I’m not sure if that’s the best way to go about it and I couldn’t get it to work. I’d also like a cool-down between switches and once you’re already in a character you can’t switch to that character again until you’re in a different one.

(I’m fairly new to Godot and most likely would need a very simplistic response to aid my comprehension.)

A state machine works well enough for me. If you have to switch characters for a large portion of the game, you may want to make a singleton to handle it.

extends Node2D

var max_time = 1.0
var time_left = 0.0
var can_change = false

enum {CHAR_1, CHAR_2, CHAR_3}
var state

# Called when the node enters the scene tree for the first time.
func _ready():
	time_left = max_time
	state = CHAR_1


# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
	if time_left > 0.0:
		time_left -= delta
		print(time_left)
	elif time_left <= 0.0 and can_change == false:
		print("Can now change characters")
		can_change = true
	
	match state:
		CHAR_1:
			if Input.is_action_just_pressed("ui_accept") and can_change == true:
				state = CHAR_2
				print(state)
				print("char 2 active")
				reset_char_switch_delay()
		CHAR_2:
			if Input.is_action_just_pressed("ui_accept") and can_change == true:
				state = CHAR_3
				print(state)
				print("char 3 active")
				reset_char_switch_delay()
		CHAR_3:
			if Input.is_action_just_pressed("ui_accept") and can_change == true:
				state = CHAR_1
				print(state)
				print("char 1 active")
				reset_char_switch_delay()

func reset_char_switch_delay():
	time_left = max_time
	can_change = false
1 Like

Your method worked perfectly, thank you so much. Just a small pet peeve though, how could I prevent the player from switching to the same character while already in it?
Basically if you’re character 1 and press character 1’s input, you shouldn’t be able to switch to character 1 again until you’re in characters 2 and 3.

(For those who dont want the switch delay upon opening the game, simply remove the “time_left = max” from the ready(): function)

enum CharacterSelect { char_1, char_2, char_3 }

var max_time = 1.0
var time_left = 0.0
var can_switch = false

var currentCharacter = CharacterSelect.char_1

func _process(delta):
	if time_left > 0.0:
		time_left -= delta
		print(time_left)
	elif time_left <= 0.0 and can_switch == false:
		can_switch = true
	
	if Input.is_action_just_pressed("switch_to_1") && can_switch == true:
		switch_to_character(CharacterSelect.char_1)
	if Input.is_action_just_pressed("switch_to_2") && can_switch == true:
		switch_to_character(CharacterSelect.char_2)
	if Input.is_action_just_pressed("switch_to_3") && can_switch == true:
		switch_to_character(CharacterSelect.char_3)

func switch_to_character(current_character: CharacterSelect) -> void:
	currentCharacter = current_character
	
	match currentCharacter:
		CharacterSelect.char_1:
			char_1_data()
		CharacterSelect.char_2:
			char_2_data()
		CharacterSelect.char_3:
			char_3_data()

func char_1_data():
	reset_char_switch_delay()
func char_2_data():
	reset_char_switch_delay()
func char_3_data():
	reset_char_switch_delay()

func reset_char_switch_delay():
	time_left = max_time
	can_switch = false

You could either add another condition to the if statements like lets say make a variable called ‘var current_char = 1’ … and if you switch to char 2 set current_char = 2 and so on…

	if Input.is_action_just_pressed("switch_to_1") && can_switch == true && current_char != 1:
		current_char = 1
		switch_to_character(CharacterSelect.char_1)

… Or make another state machine and keep the input to call the current character out of that state.

Also just a note, personally I’d set my inputs up like this using elif after the first if. This prevents any of the other if’s from activating if you press one of the other input buttons at the same time.


	if Input.is_action_just_pressed("switch_to_1") && can_switch == true:
		switch_to_character(CharacterSelect.char_1)
	elif Input.is_action_just_pressed("switch_to_2") && can_switch == true:
		switch_to_character(CharacterSelect.char_2)
	elif Input.is_action_just_pressed("switch_to_3") && can_switch == true:
		switch_to_character(CharacterSelect.char_3)
1 Like

Thank you again it worked :slight_smile:

As of 4.0, there is something called “await” so another alternative method rather than the timer could be that switching is true until after something like a transition animation plays and then the player could indeed switch again.

ex.

var can_switch = true

func switch():
  await %transitionanimation.animation_finished
  can_switch = false
1 Like

There is a very simple way to do it (although possibly not the most correct):

  • set_physic_process(false) on your character 1 and hide it (visible).
  • Instantiate character 2 at the global position of your character 1.
  • Delete character 1 (queue_free).
1 Like

What is the actual way to switch the active character though? Is there a built-in function? Can there only be one character in the scene? I can’t find where the auto-selection of the character is documented.

My use case is a scene with lots of AI characterBody3D and then one for the player.

There is no built in function for selecting a character

That would seem like a pretty awful design, surely theres a way…

Every game is going to be different and there is really no need for a function to handle something as basic as a character switching.

At the end of the day you are just pausing the inputs to one character and enabling them on another.

It sounds to me like you just need to have a movement script on each character in your scene. Then when one character is switched to player control you set the last player controlled characterBody3D to an AI controlled state (assuming you want the AI to handle its movements).

1 Like

Yes, I misunderstood the design of the character3D, I thought it would automatically sink input from the user, for some reason. Having a character controller singleton with just a reference to the actively controlled charatcer3D would work well