0 votes

I'm planning a mouse-driven crpg / rts game where there's units and different interactuables on a map; so a lot of the elements on the screen can be hovered and clicked with different (contextual) actions.
For example, hovering a friendly unit highlights it and clicking it selects it; hovering a door changes the cursor into "open door" mode, and clicking it gives the selected unit the order to open it.

So what would be the best way to structure this in Godot?

My initial ideas include having a a "controller" node, reading mouse input and checking collisions every frame across different collision layers so determine what's highlighted, selected, etc. I like this idea because centralizes the input code, but it doesn't feel very elegant.
I've also thought about giving all the selectable objects a Button node, since it has implemented a lot of the mouse interfacing implemented, but I'd feel weird about having Buttons as children of 2d elements moving across the map. Plus I'm afraid it may interfere with the normal GUI input.

I'd be grateful for any insight on this. If you know of an demo or example project please let me know!

Thanks!

Godot version 3.3.4
in Engine by (191 points)

Did something similar in the past can salvage that and share code when you say whether it's in 3D or 2D

I'm doing 2D, but my question is more about organizing the project / tree structure so I'd gladly take whatever you can share!

3D and 2D have slightly different approaches so wanted to simplify my answer as much as possible.

1 Answer

+1 vote
Best answer

Options:

The easiest:
Add an area node as a child of your object. The use the mouse_entered / mouse_exited / Input_event signals to trigger your action. Then you can control the GUI.

The downside is that this might not work if you have a GUI in place.

Raycast:

This is with a basic Spatial, SpringArm, Camera node setup where the camera rotates around a point. This script goes on your camera node and controls all menus. Verboseness for clarity.

func ray_cast():
    var space_state = get_world().direct_space_state
    var mouse_pos = get_viewport().get_mouse_position()
    var cam = get_node("SpringArm/Camera")
    var cam_origin = cam.project_ray_origin(mouse_pos)
    var cam_end = cam_origin + cam.project_ray_normal(mouse_pos) * max_look_dist
    var result = space_state.intersect_ray(cam_origin, cam_end, any_object_you_might_want_to_ignore_eg_the_player)

if not result.empty():
    if "my_target_object" in result.collider.name:
              #do stuff, eg. spawn 2d menu in 3d space or change your 2d GUI

Sorry for wonky indentation.

by (2,043 points)
selected by

Thanks for the reply. I'm actually using 2D so checking for mouse collisions is a bit easier, but still thank you.

For the record, I have a working prototype of the basic functions I need (click to select unit, click-drag to select units in a rectangle, click on the map to give a basic order.)

My problem is about how to structure all the different elements in this complex UI -- the modal unit selection happening along with modal targetting combined with actions chosen from the GUI and shortcuts...

Ugh... I was convinced you meant 3d... I really should have read the question more carefully. Ok, so you've got it going which is good and you don't want to create an unmanageable monster.

Managing a complex GUI:
- one option is to take a leaf out of modern websites and make a "base" layout with the high level division only. So, say the GUI has elements on top, bottom and right. So in your "base" scene you create the high level vboxes / hboxes and any persistent GUI elements.
- then you make scenes with the various sub-elements so everything is separated out into individual components and is manageable.
- add a script to your base GUI and you can spawn these GUI sub-elements in and out as needed. Add state setter methods.
- When you need functionality specific to one element you add a script to that element which your base GUI script can call.
- You can also consider making a custom class. You don't want to be repeating code.

The advantage is that you get more control but also that your code is now much more manageable. Want to fix a bug in your HP say you can just go to the scene for the element that you need and everything running it is in one place, all making intuitive sense.

Imagine you've got a contextual GUI element scrolling in from the right which changes completely depending on the trigger for example. Now you can spawn the GUI in your base GUI script, trigger the scroll, manage state of sub-elements, etc. You can bring in any number of completely different elements (scenes) depending on the trigger.

It might be a good idea to have the base GUI script holding the state (say, using an enum).

To call the GUI, I'd advise against hard coding paths. Unmanageable and brittle spaghetti code can easily result. Instead use signals (send as few as possible though, make sure you don't send one every frame). Singletons are also an option but you need to be careful with those for encapsulation reasons and personally I don't think there's any need here.

Or, you can make things neater and put everything in the GUI script. When a new object is added you can add it to an array/dict member variable of GUI interactables. That means your GUI script handles everything internally rather than being spread all throughout your code.

So yeah, long story short, break the GUI up into component elements and make it self-contained.

Complex is good, complicated is bad.

Thank you very much for the detailed reply. This is exactly what I needed.

It seems that I'm on the right track, since I have a similar setup to what you are describing. A difference is that I'm using a global (singleton) EventBus for calling the GUI events; I feel this was the simplest approach to manage the behaviour I need -- for example when you select a unit from the map or some part of the UI, all the relevant parts update themselves (sprite on the map shows a selection outline, portrait on the GUI shows a highlighted frame, etc). A problem of this approach is that it's easy to fall into a loop of event reactions since it's not centralized...

I think what I'm missing is how to integrate the GUI with selecting 2D elements like units on the map.
You mentioned states for the GUI controller. I believe that if I use "selectable Areas" inside my units (as described in you first reply), a GUI state machine can freely react to (or discard) relevant mouse hovers and selections. This may work with an existing GUI, correct?

A lot of what you're asking isn't Godot or even game dev specific at all, these are general architectural design principles which hold for all languages and projects. That is a BIG topic... There's a whole section in the library on it... I don't know if you know python but there's a YT channel "Arjan Codes" which is dedicated to discussing design principles. Might be helpful to learn about the principles of good design without worrying about the python specifics (which, fair warning, are on the advanced side).

A lot of people frown on singletons because they are global variables. But the reality is that as long as you're careful with their use, the chances are they can be simple, effective and quick to code. Especially with games. So if you feel a singleton is the best option for you, go with your instincts. If it turns out to have downsides later in the project and you regret it, then next time you'll have better instincts. That's worth its weight in gold.

As for integrating in the mouseover thing into your GUI - you don't have to do this, it's not compulsory by any means - but it would bring advantages. You see my 3d code for grabbing the mouseover object? Well, the 2d equivalent of this code could go in your base GUI script. Then the GUI would pretty much manage itself, no need for signals, singletons or anything else.

But, if you're using selectable areas inside your objects - a perfectly fine approach - then that really does lend itself to signals. Nothing wrong with that at all.

There's no one solution where I declare this is right! Unfortunately, all we have are design principles and then the specifics of your project. Sorry I can't be more help than that.

You were more than helpful. Thanks again for all the input :)

Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read Frequently asked questions and How to use this Q&A? before posting your first questions.
Social login is currently unavailable. If you've previously logged in with a Facebook or GitHub account, use the I forgot my password link in the login box to set a password for your account. If you still can't access your account, send an email to webmaster@godotengine.org with your username.