xyz Yes I am familiar with scenes, so all of the nodes I was talking about (except ships) are in one scene. So the parent TurnControl
it determines if it is move phase or attack phase.
I have its children
ShipMove, ShipAttack, MapGrid, AI, GameUI, AIUnits, MyUnits, Camera

(I could combine these into more general code, but I've read a lot of things that talk about breaking code up into small specialized bits rather than having them handle a lot of different aspects)

ShipMove interacts with MapGrid to plot movement, and move ships (either players or AI).
ShipAttack interacts with MapGrid to find eligible targets and to attack them.
MapGrid just plots positions on the Map
AI controls the enemy ships AI logic calling either ShipMove or ShipAttack in order to act.
GameUI handles all UI features and interacts with necessary nodes, mainly communicating with parent.
AIUnits and MyUnits handles receiving attacks and changing variables within ships to reflect the attack.
Camera is just Camera.

This is how I currently have my scene set up. This will be instantiated under my level scene which will handle victory conditions and any unique dialogue or interactions.

    xRegnarokx I don't think you fully understand what scenes are for and how essential they are in building a modular game system. Maybe read again the section on scenes at the link posted by @Toxe.

    Roughly deducing from your description, I'd have each of the following as a scene:

    • main scene
    • dialogue
    • main_ui
    • player and/or player_ui
    • map
    • ship
    • ship_ui
    • camera_rig

    Instances of each can be siblings inside main scene.

    The script functionality you now have distributed through some nodes, should be refactored as follows:
    ShipMove, ShipAttack - goes into -> player and/or ship
    MyUnits, AIUnits -> player
    MapGrid -> map
    Camera -> camera_rig

      xyz Would you suggest having main scene then become my TurnController, or would I control turns through the main_ui?

      Also how would you suggest I handle the AI logic, as its own scene or bundle it up into player?

      Would/should I instantiate these into a scene after I construct them and then save the scene together in order to make loading it simpler for changing levels?

      I've also read the article three times now, and for me it still doesn't make clear how to set up my scene structure. All my nodes are loosely coupled and as far as they can be independent of any code outside themselves. (They require information from the map, and the map from the requesting node for calculating tiles based on ship location)

      I guess naming something player that also controls the enemy for me makes clarity on what they do an issue. If I were to read those I would wonder how the person controls the AI.

      The rest of what you've suggested makes sense and in general bundles like things together.

      So if I understand you would place func() within ship that player would just call to tell it to_move(), to_attack(), damaged() ect... So, player would handle the inputs (and I am guessing passing the needed information from the map to the ship) while the ships code would change the position, health, and determine weapons damage ect...

      • xyz replied to this.

        xRegnarokx You can get answers to those questions by trial and error. The gist is to use scenes (instead of nodes) as main organizing units of your system.

        The global state of the game, including turn management, can go into something that's "above" players, commonly that can be a script in the top node of the main scene, named something like Game.

        The Player scene should keep lists of all the things that a real world player would have in their mind - which units they own, how those units can move, it should be able to query and asses the state of the map etc... It could also have a toggle switch that lets it be controlled either via ui input or by ai logic built into itself. That way you can instantiate the player scene for example twice, and flip the ai switch for one of them. Or you can flip that switch temporarily to play the next move for the human player. Or flip it for both players and let the ai play against itself...

        The actual scene instantiation moment really depends on the needs of your game and purpose of each particular scene type. For some it's more convenient to instantiate them manually in the editor while others may need to be created at runtime. For example if your game can have 2-4 players. You'd instantiate players at runtime, after the number of players has been selected.

        xRegnarokx so all of the nodes I was talking about (except ships) are in one scene.

        And that's the problem. Some of them might well make sense atomized, but in that case they should be their own instantiable scenes.

        You can still assemble a ship assembly out of these atomized scene instances. The whole point of atomizing them is so you can share common logic between similar things. I.e. ships. All need 'engines' but each might have different power output. etc.

          Megalomaniak I guess the difficulty is if I have nodes sending signals or doing callbacks but aren't in the same scene they just return errors. My understanding is you want the scenes to be able to exist without that. However, I can't connect a signal to something that doesn't yet exist.

          I guess is it okay to make seperate scenes that are dependant on other scenes to work? All I've read says it is better that you don't, that is why I was making them all in one scene.

            xRegnarokx However, I can't connect a signal to something that doesn't yet exist.

            When you are about to connect a signal in code make sure to check if the node you are trying to connect to is in tree and/or if it's ready first.

            xRegnarokx I guess the difficulty is if I have nodes sending signals or doing callbacks but aren't in the same scene they just return errors.

            During runtime there really only is the one 'scene' and that is the whole scene tree. The packed scenes are just saved branches that get instanced into it.

            Ignore the 'Scene' in SceneTree and focus more on the Tree part. It could just as well have been named NodeTree by the developers, would be just as valid but take the emphasis off of scenes. And 'Scenes' as I said are just branches of the NodeTree. But since the node scheme is designed to be used as a sort of component system for building and instancing super-sets of nodes or 'scenes' if you will, it's just as valid for it to be named the 'SceneTree' as such.

            Fun fact, at the root of every runtime sits a Viewport node as the root node of the tree and as a general rule all the other nodes are children of it. But through the globals/singleton system you can also create separate node trees that are siblings(not children) of the root viewport and the scene tree it roots. That however is a more advanced topic and can easily be misused to create huge messes.

              xRegnarokx I guess the difficulty is if I have nodes sending signals or doing callbacks but aren't in the same scene they just return errors. My understanding is you want the scenes to be able to exist without that. However, I can't connect a signal to something that doesn't yet exist.

              I guess is it okay to make seperate scenes that are dependant on other scenes to work? All I've read says it is better that you don't, that is why I was making them all in one scene.

              Ah, so that's what's confusing you. Here's how things should be set up, following the principle of object oriented encapsulation I mentioned earlier.

              • Nothing inside the scene should call anything outside that scene
              • Nothing outside the scene should call anything inside the scene.
              • The scene has no business connecting itself to anything outside of it.
              • Nothing outside the scene should connect to signals emitted by nodes inside the scene.

              Instead:

              • Everything must go through the interface script attached to the top node of the scene.
              • The interface script can call only other scripts in nodes "below" it.
              • The interface script should define custom signals relevant to the state of the scene, and emit them accordingly.
              • Nodes below scene's top node should signal only up to that top node, and leave to it to decide what to do with those signals - either change the scene state and/or re-emit them as custom signals.
              • The script that instantiates the scene should take care to connect to signals of interest emitted by the scene.

              The intent is to make a self-contained (encapsulated) scene that has an interface which consists of methods that can be called from the outside and custom signals that anyone from the outside can connect to if they're interested in something that the scene could emit. In other words - you're making a "closed-box" component that can be controlled by its methods and reports its state changes by emitting signals. Or you can look at it this way: you made a complicated bunch of nodes behave and handle like a single node.

              For example.
              You work on a Frogger clone and have the main character Frog scene. This scene consists of an area node, a sprite node and several colliders. The area node, being the top node, has an interface script that defines following methods:

              • jump_left()
              • jump_right()
              • jump_up()
              • jump_down()

              It also defines custom signals that are emitted when something of importance happens with Frog's state:

              • signal jump_done
              • signal fly_eaten
              • signal home_reached(home_id)
              • signal hit_by_car(car_id)
              • signal drowned
              • signal carried_away_on_log
              • signal turtle_dive
              • signal time_expired

              Now at the start of a level the main Game script instantiates the Frog scene, connects itself to Frog's signals of interest and starts forwarding player input to the Frog, by calling its jump_*() methods.

              The beauty of this is that once you have one functional Frog, you can instantiate as many of them as you wish. They'll all work simultaneously, without any additional coding.

              This also facilitates further improvements and additions. New in-scene functionality and new interface methods and signals can be added, without compromising existing communication with the "outside", as long as the interface stays "backward-compatible".

                xRegnarokx However, I can't connect a signal to something that doesn't yet exist.

                The listener should connect to the signal. I.e., call the connect() for the signal in the _ready() method of the script that contains the method being connected.

                  DaveTheCoder Thanks, that is what I do, I poorly explained what I meant. I was confused about how to test my scenes if they needed to recieve calls to initiate actions, but I guess I can test them by instantiating them in the editor and test them that way.

                  xyz Hey, thanks so much that makes it a lot clearer for me I my head what you were talking about.

                  So the goal isn't to have scenes that necessarily do what they are suppose to before being instantiated, but that they aren't needing to be hard connected to anything outside. Signal up, call down, that is a principle I have been trying to keep.

                  How does diagonal work between siblings? Can I signal to siblings? (Using common parent to connect). Also if that is okay can I call back using the signal to pass self to eliminate using NodePath. If not then do I pass all the information to common parent then call down to send the info?

                  This is in relation to my player scene needing information in my tilemap in order to move and attack. A couple of my other nodes need information like UI needing to know where the camera is, in order to fix their position relative to camera.

                  So I guess that is that last bit of confusion I have, is it better to pass the information up to the parent and callback down (I guess it'd be the same amount of signals and callbacks, maybe I amswered my own question).

                  Thanks so much, I feel like I have a much larger comprehension of programming with Godot since I've been engaging with this forum. I've been trying to read tutorials in order to not need to ask questions here, but somethings have been hard to find tutorials on, or that aren't as clear or assume knowledge that I don't know I need.

                  • xyz replied to this.

                    Megalomaniak Yeah I've read tutorials about the globals/singletons/autoloader but haven't bothered with them. I figure I'll try and keep things more basic for now. Focus on creating scenes with nodes and use the OOP principle.

                    Yeah I think I had some error in my thinking about efficiency of signaling up then calling back down when it is the same thing as doing it between two siblings. Also, I had some errors in how I was thinking about each scene. But, I think that is cleared up for the most part and now back to disassembling my code and doing another rebuild with these principles I've learned!

                    xRegnarokx

                    xRegnarokx So the goal isn't to have scenes that necessarily do what they are suppose to before being instantiated, but that they aren't needing to be hard connected to anything outside. Signal up, call down, that is a principle I have been trying to keep.

                    Right. The scene needs to work with its surroundings. You'll typically have scenes that don't do much if just ran by themselves, because they don't get any calls through their interface and the signals they emit are not connected to anything. They need to be plugged-in to do their job, so to speak. When developing/testing a scene, you'd want to have it instantiated and connected in its intended surrounding.

                    xRegnarokx How does diagonal work between siblings? Can I signal to siblings? (Using common parent to connect). Also if that is okay can I call back using the signals to pass self to eliminate using NodePath. If not then do I pass all the information to common parent then call down to send the info?

                    Signaling and calling between siblings is fine. Just take care to establish some hierarchy of control because if everybody calls and signals everybody else, you may end up in a mess. If you, for example, have Player, Map and Unit(s) scenes, even though they can all be siblings, the Player should clearly be higher in control hierarchy, so the Player would typically make majority of calls to other two, while they'd primarily send out eventual signals to the Player. Note that in wouldn't be practical to just make Map and Unit(s) children of the Player because different players take turns in manipulating the same Map and Unit(s).

                    The important thing here is that a scene's reference to a sibling scene should not be hardcoded into scene itself (using a node path). This would break principles of encapsulation and reusability. Instead such a reference should be provided to the scene via its interface by a script that's "above" both siblings, which may typically be the script that instantiated them both. For example, if a Player needs to communicate with the Map, the Player interface should provide a method like assign_map(). Then at startup, the Game script should pass the Map reference to the Player by calling that method. And from then on the Player can communicate with the Map. Same goes for making sibling-to-sibling signal connections.

                    xRegnarokx So I guess that is that last bit of confusion I have, is it better to pass the information up to the parent and callback down (I guess it'd be the same amount of signals and callbacks, maybe I amswered my own question).

                    Player should get data from the Map and Units by calling various get_*() methods you provide in Map's and Unit's interface, use that data to determine how it can move the next Unit in the turn queue, and then move it by calling some move_*() methods in Unit's interface.