In this recipe, we'll show you how the selection of units in an RTS can work and also implement functionalities to show you when a unit has been selected. We'll use AppState
, which handles mouse selection and we will also create a new Control
class to be used by any spatial
we want to be made selectable. In the recipe, Control will display a marker at the feet of the selected spatial
, but it can easily be extended to do other things as well.
This recipe will work fine if you have already started creating a game where you would like to select things by clicking on them or if you've completed the previous recipe. The least you will need for this recipe is a scene with something to click on. In the text, we will refer to TestScene
, which was created in Chapter 1, SDK Game Development Hub, and the Jaime model which is used in it. It is assumed that you have some experience in action handling. If not, it's recommended that you refer to the Attaching an input AppState object recipe of this chapter to get an introduction to it.
Perform the following steps to select units in RTS:
SelectableControl
. It should extend AbstractControl
.spatial
field is selected or not (duh), and marker, which is another spatial
field to show when selected is true.setSelected
method; we let it handle attaching or detaching the marker:public void setSelected(boolean selected) { this.selected = selected; if (marker != null) { if (this.selected) { ((Node) spatial).attachChild(marker); } else { ((Node) spatial).detachChild(marker); } } }
AppState
class called SelectAppState
. It should extend AbstractAppState
and implement ActionListener
to receive mouse click events.List<Spatial>
called selectables
where it will store anything that is selectable, as follows:private static String LEFT_CLICK = "Left Click"; private List<Spatial> selectables = new ArrayList<Spatial>();
initialize
method should look familiar if you've read any of the other game control recipes. We add a mapping for LEFT_CLICK
and register it with the application's InputManager
to ensure it listens for it.onAction
method will currently do is to trigger the onClick
method when the left mouse button is pressed.Ray
from the position of the mouse cursor into the screen. We begin by getting the position of the mouse cursor on the screen as follows:private void onClick() { Vector2f mousePos2D = inputManager.getCursorPosition();
Vector3f mousePos3D = app.getCamera().getWorldCoordinates(mousePos2D, 0f);
Vector3f clickDir = mousePos3D.add(app.getCamera().getWorldCoordinates(mousePos2D, 1f)).normalizeLocal();
The following figure shows you how BoundingVolume
, in the shape of a box, can enclose the character:
Ray
using mousePos3D
as the origin and clickDir
as the direction and a CollisionResults
instance to store any collisions that will occur.for
loop that goes through our selectables
list and checks whether Ray
intersects with any of BoundingVolumes
. The CollisionResults
instance adds them to a list, and we can then retrieve the closest collision which, for most cases, is the most relevant one, as follows:for (Spatial spatial : selectables) { spatial.collideWith(ray, results); } CollisionResult closest = results.getClosestCollision();
selectable
list to see whether the spatial
that was clicked on has any of the items in the list. If it is, we call the following code:spatial.getControl(SelectableControl.class).setSelected(true);
SelectAppState
as follows:SelectAppState selectAppState = new SelectAppState(); stateManager.attach(selectAppState);
SelectableControl
and something that can be used as a marker (in this case, it will be a simple Quad).SelectableControl
to our Jaime model, and then add Jaime as a selectable to AppState
as follows:jaime.addControl(selectableControl); selectAppState.addSelectable(jaime);
This example shows you one of the strengths of using Control
and AppState
, as it's easy to add functionalities to a spatial
object as long as the logic is kept modular. Another (although possibly less effective) way of performing the selection would be to run a collision check against all spatial
objects in a scene and use Spatial.getControl (SelectableControl.class)
to see whether any of the spatials should be possible to select.
In this recipe, the items in the selectables
list extend the Spatial
class, but the only actual requirement is that the objects implement the Collidable
interface.
When shooting the ray, we get the position of the mouse cursor from InputManager
. It's a Vector2f
object, where 0,0
is the bottom-left corner, and the top-right corner equals the height and width of the screen (in units). After this, we use Camera.getWorldCoordinates
to give us a 3D position of the mouse click (or any position on the screen). To do this, we must supply a depth value. This is between 0, which is closest to the screen, and 1f, into infinity. The direction would then be the difference between the nearest and farthest value, and it would be normalized.
3.12.153.212