2

I do state appears to, because I do believe I'm missing something obvious. But I've been chasing this problem for quite a while now and I've run out of ideas.

I'm basically trying to solve the problem of transitioning between various scenes; and keeping a little bit of control over my signalling. To this end I've implemented a very basic Finite State Machine as follows:

class_name GameStateMachine
extends Node

@export var initial_state: GameState

var current_state: GameState
var _states: Dictionary = {}

func _ready() -> void:
    for c in get_children():
        if c is GameState:
            _states[c.name.to_lower()] = c
            c.transitioning.connect(_on_state_transition)
            
    if initial_state:
        current_state = initial_state
        initial_state.enter()

func _process(delta: float) -> void:
    if current_state:
        current_state.update(delta)

func _physics_process(delta: float) -> void:
    if current_state:
        current_state.physics_update(delta)

func _on_state_transition(state: GameState, new_state_identifier: String) -> void:
    print("transition signal from: %s" % state.name)
    #SceneTransition.fade_to_black()
    
    if state != current_state:
        print("not in current state, odd")
        return
    
    print_debug("Switching State: %s to %s" % [state.name, new_state_identifier])
    var new_state = _states.get(new_state_identifier.to_lower())
    if not new_state:
        print("Unknown state: %s" % new_state_identifier)
        return

    if current_state:
        current_state.exit()
    current_state = new_state
    new_state.enter()

And my base GameState class like this.

class_name GameState
extends Node

signal transitioning

func enter() -> void:
    pass

func exit() -> void:
    pass

func update(delta: float) -> void:
    pass

func physics_update(delta: float) -> void:
    pass

The FSM kicks off in "Initial" state which looks like this.

extends GameState

func enter() -> void:
    transitioning.emit(self, "title")

I know. But there's going to be some more stuff going on before kicking off the title screen; it's just not the problem I'm trying to solve right now :D

And then the "title" state, which looks like this:

extends GameState

const TITLE_SCENE: String = "res://game/main_scenes/title_screen/title_screen.tscn"

func enter() -> void:
    ResourceLoader.load_threaded_request(TITLE_SCENE, "", true)
    get_tree().change_scene_to_packed(ResourceLoader.load_threaded_get(TITLE_SCENE))

Very little going on, it's about as basic as it could get. Now here's the thing. I've sprinkled in a few print statements. And this is what I get:

transition signal from: Initial
Switching State: Initial to title
   At: res://game/services/game_engine_core/state_machine.gd:35:_on_state_transition()
transition signal from: Initial
Switching State: Initial to title
   At: res://game/services/game_engine_core/state_machine.gd:35:_on_state_transition()

And I cannot work out, HOW I end up getting that signal fired twice.

The FSM sits as a Node under my root "Game" Node which is loaded as an autoload.

Any insights as to how I can end up with double signals here? :D

Godot Version 4.1.1

Godot v4.1.1.stable - Windows 10.0.22621 - Vulkan (Compatibility) - NVIDIA GeForce RTX 4090 (NVIDIA; 31.0.15.3699) - 12th Gen Intel(R) Core(TM) i9-12900K (24 Threads)

Mark Cassidy
  • 5,829
  • 1
  • 22
  • 33

1 Answers1

1

Dearie me; rubber duck in full effect :D

So I did find my answer. It lies here:

The FSM sits as a Node under my root "Game" Node which is loaded as an autoload.

So initially I just had my game autorun my "title" scene. This was until my recent attempts at cleaning things up a bit, and having an overarching game controller that decided which scenes to pull in, and where.

So I switched my startup object from "title" to my "game" scene. Where all of the above code sits.

And by now it's probably obvious where this is going :D

I now had the "game" sitting as both an autoload AND as the godot startup object. And yea... well they're called autoloads, not singletons, for a reason :D

Hope this helps someone else at some point. I'll facepalm a bit longer, and then get on with it.

Mark Cassidy
  • 5,829
  • 1
  • 22
  • 33
  • 1
    Sadly they are called "singletons" in the official documentation: [Signletons (Autoload)](https://docs.godotengine.org/en/stable/tutorials/scripting/singletons_autoload.html). *I try to avoid calling them that, unless it is to help people find them in the documentation.* – Theraot Aug 13 '23 at 21:22
  • Yea, they're definitely not. As my headache above is a clear testament to :D – Mark Cassidy Aug 14 '23 at 18:20