I don’t know the best way, but something I do and which works for me.
Adopt a mindset where everything not static can be spawned at runtime. So when a game object needs any reference, it can never rely on it being hardwired into it.
Internal ReferenceAll
internal references to other game objects within the own hierarchy can be handled in two ways:
1) use the Start state as initialisation (ini) state that is only used once on actual start, and the functionality of the FSM never goes back to it. In this state, you can Set GameObjects explicitly into variables, and from then only use those variables. By making it this way, you can always view the Ini state and set it up correctly. You can also refactor easily and have the game object found by tag (though this is regarded as expensive. Doing it on once start should be no problem). Don't use find by name, because that can easily break.
2) Use the category field in variables and mark variables as "Ini" to be set when setting up the new prefab (variant etc). I also use Config and Runtime as recurring categories that make clear what is configurable (like running speed) and what shouldn't be touched as it's handled by the game. This is the same thing as above, only that you set defaults into the "ini" variables right away. I suggest that you at least make game object null check at start to tell you quickly and easily when you forgot this.
Broadcasting Try to architect with elements that are ignorant of each other. For example, the health FSM can broadcast to all inside the player that it took damage and not care at all about anything else. Another FSMs gets triggered through the broadcast event and updates the UI, another one plays the FX and sounds and so on. None need to care who send the event and why. If the FX FSM is missing, you have no problems except that simply the FX are missing, but no errors. Make sure that such events aren’t spammed from random directions in random order. There should be one sender and a number of listeners subscribed to it.
Singleton StyleInstead of a game object finding other objects in the scene, you can try a singleton style approach. You make one game object, a “manager” and pay extra attention that it always exists in the scene, and that it can only exist
once and once only (else, your game breaks!). Next, newly spawned entities can inscribe themselves to the manager’s variables. Because the manager exists once, you can easily find it with a tag. The player, upon spawning, can look for the manager and can set itself into the manager’s playerGameObj variable. You can also combine it with the previous idea, and broadcast an event, and the manager listens. When it hears the player spawned, it can get the event info and set the game object variable.
I hope you get the idea.