2
\$\begingroup\$

When we look at complex RTSs where a simple left button mouse click can mean twenty or more different things, depending on the game and UI state, the code handling this interaction and assigning the right interaction action to the input can easily end up messy and tightly coupled to everything, creating a ball-of-mud architecture/The Class That Does Everything related to interaction.

How can I deal with this situation?

Here's an example of the complex selection mechanics that I'm looking for:

  • Single left click to select a friendly unit.

    • If friendly unit is in a group, the group is selected.

    • If the group is already selected, select the unit.

  • Single left click to select an enemy unit.

    • If friendly unit/group already selected, then issue attack order on unit.

    • If friendly unit/group already selected and enemy unit is in an enemy group, then issue attack order on enemy group.

  • Same thing but if target is allied non-controllable, then friendlies move towards target.

Etc.

How can I handle this complex selection system, taking into account complex states?

\$\endgroup\$
5
  • \$\begingroup\$ Is your overall software architecture more component-oriented or object-oriented? \$\endgroup\$ – Philipp Oct 6 '20 at 12:55
  • 2
    \$\begingroup\$ Your design is likely possible, but I would like to point out that many RTS games use both mouse buttons to break down the complexity for this, and that also provides consistency in the way a particular button behaves for the player. In your design, if a player is used to left-clicking on something to select it they may accidentally order an attack that they didn't intend to! It is not a good idea to make one button change behaviour based on context unless that is the only button you have. Is this a mobile RTS with touch controls? \$\endgroup\$ – Romen Oct 6 '20 at 13:40
  • \$\begingroup\$ It's a desktop 3-axis RTS. I'm currently using the right mouse button for camera rotation and mouse scroll for zoom. \$\endgroup\$ – Ahmed Tawfik Oct 6 '20 at 13:51
  • \$\begingroup\$ @Philipp Not 100% sure if I'm answering accurately but I think it's more component-oriented as I'm using classical Unity components to handle game behaviour. \$\endgroup\$ – Ahmed Tawfik Oct 6 '20 at 13:52
  • \$\begingroup\$ @AhmedTawfik Adding the Unity-tag, then. \$\endgroup\$ – Philipp Oct 6 '20 at 14:12
1
\$\begingroup\$

I would solve this by creating click handler behaviors for everything which is clickable. But the primary purpose of that behavior would not be to actually act on the click event but rather find out what the users intention is, convert it into a command and then delegate the execution of that command to a central command handler.

For example, the behavior I would put on enemy-controlled units would look something like this:

public class EnemyUnitClickHandler : MonoBehaviour, IPointerClickHandler
{
    public void OnPointerClick(PointerEventData pointerEventData)
    {
        UnitGroup myGroup = GetComponent<GroupMembership>()?.group;
        UIManager uiManager = UIManager.GetInstance();
        CommandHandler commandHandler = CommandHandler.GetInstance();
       
        if (uiManager.currentlySelected?.faction == Factions.PLAYER) {
            // player-unit is selected - give an attack command
            if (myGroup == null) {
                commandHandler.giveAttackOrder(uiManager.currentlySelected, this);
            } else {
                commandHandler.giveAttackOrder(uiManager.currentlySelected, myGroupMembership.group);
            }
        } else {
            // something else selected - select this unit or its group
            if (myGroup == null || myGroup == uiManager.currentlySelected) {
                commandHandler.selectUnit(this);
            } else {
                commandHandler.selectGroup(myGroup);
            }                 
        }
        
    }
}

The reason for this layer of indirection is that commandHandler.selectUnit or commandHandler.giveAttackOrder are now no longer explicitly mouse click events. Decoupling them from the input method which was used to trigger them allows you to reuse them for other ways in which commands can be given. Like via a hotkey, via the AI controller, via another player connected via network or as part of the tutorial.

The purpose of the CommandHandler would then be to route those events back to the objects which concern them. What exactly it means to "giveAttackOrder(subject, target)" should be handled by the component which handles unit AI. What exactly it means to "change the selected entity" should be implemented in the class(es) which control the UI.

\$\endgroup\$
7
  • \$\begingroup\$ Thank you Philipp. IPointerClickHandler is new to me. One question though: Do you still recommend this solution if we have 200+ gameobjects that are running OnPointerClick on every update as well as a whole bunch of other stuff (AI, PostRender, etc)? I've been trying to avoid code in my components to reduce their memory footprint and trying to use manually-defined (non-ECS) systems going through Lists on every Update to do most processing. \$\endgroup\$ – Ahmed Tawfik Oct 6 '20 at 14:52
  • 1
    \$\begingroup\$ OnPointerClick isn't a polling function that's called every update. It's an event that's fired once when the player clicks. \$\endgroup\$ – DMGregory Oct 6 '20 at 15:30
  • \$\begingroup\$ @AhmedTawfik No, having an IPointerEventClickHandler for every object in the game should not be much of a performance hook. When the user makes a click, then the EventSystem performs a Physics.Raycast from the camera through the cursor position into the scene. Then it checks if it hits a collider, if the object with that collider has behaviors which implement that interface and if so calls the OnPointerClick methods of those behaviours. The most expensive part of that procedure is usually the raycast. \$\endgroup\$ – Philipp Oct 6 '20 at 15:46
  • \$\begingroup\$ If I were to add modifier keys to the left-click, would you recommend testing for the keys within the function, or would you change the approach? Also I haven't tested it yet but I expect I'll first have to check the PointerEventData object to see if it's a left-click. \$\endgroup\$ – Ahmed Tawfik Oct 6 '20 at 20:20
  • \$\begingroup\$ @AhmedTawfik Checking if the player holds down a modifier key during the OnPointerClick method is pretty trivial. Just check with Input.GetKey. And indeed, you need to check PointerEventData.button to see which mouse button the user pressed. \$\endgroup\$ – Philipp Oct 6 '20 at 20:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.