Skip to content

Stratkit Army Input Drag

Why it exists

On mobile the player taps an army, picks an action from a screen, then taps the target. On a desktop that is three steps for something the mouse can say in one. This package gives desktop players the gesture they expect from an RTS: grab your own army, drag to where it should go, let go. The game works out on its own whether that means move, attack, fly or patrol, so the player never opens an action menu to say it.

The path is drawn under the pointer the whole time, so the player sees the result before committing. The gesture itself sends nothing to the server, the game still asks for confirmation the same way it does after a normal click.

What the package does

  • Detects a press and drag started on one of the player's own armies, on the map or on an army label.
  • Locks the map while the gesture runs, and pans it when the pointer nears a screen edge.
  • Resolves what the pointer is over into one command: Move, Attack, Fly or Patrol.
  • Feeds that target into the normal command pipeline, so the same draft commands and the same path preview appear as after a click.
  • Publishes the outcome once the pointer goes up.

How to use

1. Add the module

Create Stratkit/Modules/ArmyDragCommandModule and add it to the bootstrap module list. The asset holds the tuning values (see Configuration). The module registers ArmyDragStartDetectorSystem and creates the config and the on/off singletons.

2. Wire the state into your selection state machine

The gesture is one state plus three transitions. Register them and connect the state to whatever states the player can drag from.

StateDraggingArmyTarget dragging = new(queryCache);
TransitionOnArmyDragStarted onStarted = new(queryCache);
TransitionOnArmyDragFinished onFinished = new(queryCache);
TransitionOnArmyDragCancelled onCancelled = new(queryCache);

fsmSystem.Register(dragging);
fsmSystem.Register(onStarted);
fsmSystem.Register(onFinished);
fsmSystem.Register(onCancelled);

Entity draggingState = dragging.CreateStateEntity(onUpdate: true);

onStarted.CreateTransitionEntity(stateUnselected, draggingState);
onStarted.CreateTransitionEntity(stateSelectedArmy, draggingState);
onFinished.CreateTransitionEntity(draggingState, stateWaitingForArmyAction);
onCancelled.CreateTransitionEntity(draggingState, stateUnselected);

Finished fires when the gesture ended on a usable target, Cancelled when it did not.

3. React to the outcome

ArmyDragFinishedEvent is published once, when the player lifts the pointer. This is the only thing most games need.

Producer<ArmyDragFinishedEvent> producer = World
    .GetExistingSystemManaged<ProducerSystem>()
    .GetOrCreate<ArmyDragFinishedEvent>();
_consumerEntity = EntityManager.CreateConsumerEntity<ArmyDragFinishedEvent>();
producer.RegisterConsumer(_consumerEntity);
ConsumerEnumerator<ArmyDragFinishedEvent> events = new(EntityManager, _consumerEntity);
while (events.Next(out Entity _, out ArmyDragFinishedEvent evt)) {
    if (evt.HasValidTarget) {
        OpenConfirmationScreen(evt.Kind);
    }
}

The first block belongs in OnCreate, the second in OnUpdate. Take the event out of the buffer before any structural change, a created or destroyed entity invalidates the enumerator.

4. Follow the gesture while it runs

While the gesture is active there is exactly one gesture entity. Query it to know that a drag is happening, and to read what the pointer currently resolves to.

_gestureQuery = new EntityQueryBuilder(Allocator.Temp)
    .WithAll<ArmyDragTargetingActiveTag, ArmyDragResolvedCommand>()
    .Build(this);
Entity gestureEntity = _gestureQuery.GetSingletonEntity();
ArmyDragCommandKind kind = EntityManager.GetComponentData<ArmyDragResolvedCommand>(gestureEntity).Kind;

An empty query means no drag is running. Use this to hold back anything that would fight the gesture, a popup, a selection sound, an action bar. The entity is destroyed when the gesture ends, so nothing has to be cleaned up by hand.

Components on the gesture entity:

Component Meaning
ArmyDragTargetingActiveTag A drag is running.
ArmyDragResolvedCommand The command the pointer resolves to this frame.
ArmyDragSnappingPointsMode Which snapping points stay visible for the whole gesture.

5. Turn the gesture on and off

The feature starts enabled for desktop players and disabled elsewhere. In the editor it waits for an opt in, because the editor runs whatever build target happens to be selected. Turn it on for your machine with Tools/Stratkit/Always Enable Army Drag In Editor.

At runtime the switch is the enableable ArmyDragCommandEnabledTag singleton, useful for cheats or a settings screen. Disabled means the feature behaves as if it was not installed.

Entity switchEntity = _enabledTagQuery.GetSingletonEntity();
EntityManager.SetComponentEnabled<ArmyDragCommandEnabledTag>(switchEntity, isEnabled);

The tag is enableable, so it cannot be read through the singleton API. Build a query with WithAll<ArmyDragCommandEnabledTag>() and check IsEmpty instead.

How the command is picked

For whatever sits under the pointer, the first rule that matches wins:

  1. A foreign army that is not an ally, Attack.
  2. A province with a usable airfield, when commanding air armies, Fly.
  3. A province that is not an ally's, Attack.
  4. Air armies anywhere else, Patrol.
  5. Anything else, Move.

The rules reuse the target resolution and the diplomacy checks of com.stratkit.army-input-core, so a dragged command lands on the same target a click would.

Requirements

The game has to provide the pieces the gesture leans on: an ArmyInputTargetOverride singleton and ArmyInputUtilsSystem from com.stratkit.army-input-core, a selection state machine built on com.stratkit.selection, and army command drafts through com.stratkit.army-command. See package.json for the full dependency list.