- jMonkeyEngine 3.0 Cookbook
- Rickard Edén
- 1080字
- 2021-09-03 10:00:49
Selecting units in RTS
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.
Getting ready
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.
How to do it...
Perform the following steps to select units in RTS:
- Let's start by creating the Control class and name it
SelectableControl
. It should extendAbstractControl
. - The class only has two fields: selected, which keeps track of whether the
spatial
field is selected or not (duh), and marker, which is anotherspatial
field to show when selected is true. - The only logic in the class is in the
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); } } }
Note
The method assumes that the
spatial
is actually aNode
. If it is not aNode
, the class can do other things, such as changing the color parameter ofMaterial
to indicate that it is selected. - We might want to display different markers for different types of selections, so let's make it flexible by adding a setter method for the marker.
- Now, we create a new
AppState
class calledSelectAppState
. It should extendAbstractAppState
and implementActionListener
to receive mouse click events. - We'll add two fields, one static string to represent the mouse click, and a
List<Spatial>
calledselectables
where it will store anything that is selectable, as follows:private static String LEFT_CLICK = "Left Click"; private List<Spatial> selectables = new ArrayList<Spatial>();
- The
initialize
method should look familiar if you've read any of the other game control recipes. We add a mapping forLEFT_CLICK
and register it with the application'sInputManager
to ensure it listens for it. - The only thing the
onAction
method will currently do is to trigger theonClick
method when the left mouse button is pressed. - Mouse selection (or picking) works by shooting
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();
- Then, we get the position this represents in the game world as follows:
Vector3f mousePos3D = app.getCamera().getWorldCoordinates(mousePos2D, 0f);
- Now, we can see what direction this would be by extending the position deeper into the camera's projection, as follows:
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: - We define
Ray
usingmousePos3D
as the origin andclickDir
as the direction and aCollisionResults
instance to store any collisions that will occur. - Now, we can define a
for
loop that goes through ourselectables
list and checks whetherRay
intersects with any ofBoundingVolumes
. TheCollisionResults
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();
Tip
It's a good idea to have a look at the
CollisionResults
class as well asCollisionResult
, as these classes already keep track of many useful things that will save valuable coding time. - After this, we can parse through our
selectable
list to see whether thespatial
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);
- Depending on the requirements, we might want to deselect all other spatials at this point. If we're using nodes, we might also need to see whether it is any of the spatial's children that were hit by the ray as well.
- To test this, we can use the same class used in the previous recipe, with a few additional lines.
- First of all, we need to create and attach
SelectAppState
as follows:SelectAppState selectAppState = new SelectAppState(); stateManager.attach(selectAppState);
- Create
SelectableControl
and something that can be used as a marker (in this case, it will be a simple Quad). - Lastly, we need to add
SelectableControl
to our Jaime model, and then add Jaime as a selectable toAppState
as follows:jaime.addControl(selectableControl); selectAppState.addSelectable(jaime);
- If we now run the example and click on Jaime, the Quad should be rendered near his feet.
How it works...
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.
- Advanced Quantitative Finance with C++
- Vue.js 3.x快速入門(mén)
- C語(yǔ)言程序設(shè)計(jì)教程(第2版)
- Building Cross-Platform Desktop Applications with Electron
- Amazon S3 Cookbook
- C語(yǔ)言程序設(shè)計(jì)案例精粹
- PostgreSQL Replication(Second Edition)
- VMware虛擬化技術(shù)
- Linux Device Drivers Development
- C語(yǔ)言課程設(shè)計(jì)
- 從零開(kāi)始學(xué)Linux編程
- Spring Boot+Vue全棧開(kāi)發(fā)實(shí)戰(zhàn)
- 深入理解C指針
- Learning Material Design
- Scratch·愛(ài)編程的藝術(shù)家