In some games, user has to select some objects in the scene. This happens for instance in RTS type game when user selects his units. Such a selection is done with the mouse : user draws a rectangle area to select all objects located in the area. This tutorial shows how to manage such an area and select the objects it contains.
Create the selection area in the HUD
In the HUD Editor, create a new template (HUD Editor>HUD>Create). Call it “Selection”. In this template, create a new component called “Area”, with the following attributes: type=Label, visibility=false, ZOrder=255 (to display on top of any object), Origin=BottomLeft. In the Appearance section, define the colors: set background color to 50, 50, 100, 150 (Alpha 150 ensures transparency) and border color to 50, 50, 100, 255 (Alpha 255 ensures opacity). Press OK and save the HUD template.
This component is the “rectangle” representing the selection area that user will resize with the mouse. The behevior we want is:
- First, when user clicks the scene, set Area size to 0 and move Area at mouse position.
- Then when user resize Area (initial position won’t move), the opposite corner follows the mouse. Note: in this tutorial, the Bottom-Left corner will not move.
Drag and drop the ‘Selection’ HUD from the Data Explorer > Resources > HUD to the Game Editor > Resources to reference it to your game and be able to instantiate it from the script.
Create the AIModel
In the AIModel editor, create an AIModel named “Selection” and reference it in User Main AIs of your game (in the Game Editor, click on “Edit>User Main AIs>Add” and select the “Selection” AIModel).
Instantiate the HUD
Open the early created AIModel in the AIModel Editor, and create a ‘onInit’ handler. In this handler, instantiate your HUD with:
function Selection.onInit ( ) hud.newTemplateInstance ( this.getUser ( ), "Selection", "Selection" ) end
Update the selection area according to mouse position
We want the selection area to appear only if the left button of the mouse is down. For that, create a variable in the AIModel called “bMouseLeftDown” (set it to false).
Create the 3 mouse handlers: onMouseButtonDown, onMouseButtonUp and onMouseMove (IMPORTANT: they are all predefined handlers, so to create them, click Add Handler > User Handler and select the correct name).
In the onMouseButtonDown, we check if the left button is down (nButton is 0), if yes, we set the bMouseLeftDown variable to true, set the size of your HUD to zero, move it under the mouse and make it visible. This is done as follows:
function Selection.onMouseButtonDown ( nButton, nPointX, nPointY, nRayPntX, nRayPntY, nRayPntZ, nRayDirX, nRayDirY, nRayDirZ ) if ( nButton == 0 ) then this.bMouseLeftDown ( true ) local hUser = application.getCurrentUser ( ) local hSelection = hud.getComponent ( hUser, "Selection.Area" ) hud.setComponentSize ( hSelection, 0, 0 ) hud.setComponentPosition ( hSelection, ( nPointX + 1 ) * 50, ( nPointY + 1 ) * 50 ) hud.setComponentVisible ( hSelection, true ) end end
In the onMouseMove handler, if the mouse button left is down, update the HUD size according to the distance of the HUD position (the bottom left corner which is immobile) and the cursor position. The new size is the difference between the 2 positions. You can also clamp the size to make your component to not go out of the application window.
function Selection.onMouseMove ( nPointX, nPointY, nDeltaX, nDeltaY, nRayPntX, nRayPntY, nRayPntZ, nRayDirX, nRayDirY, nRayDirZ ) if ( this.bMouseLeftDown ( ) ) then local hSelection = hud.getComponent ( this.getUser ( ), "Selection.Area" ) local px, py = hud.getComponentPosition ( hSelection ) local px2, py2 = hud.getCursorPosition ( this.getUser ( ) ) local sx = math.clamp ( px2 - px, -px, 100 - px ) local sy = math.clamp ( py2 - py, -py, 100 - py ) hud.setComponentSize ( hSelection, sx, sy ) end end
In the onMouseButtonUp handler, if this is the left button which is up, make the HUD not visible and toggle the bMouseLeftDown variable.
function Selection.onMouseButtonUp ( nButton, nPointX, nPointY, nRayPntX, nRayPntY, nRayPntZ, nRayDirX, nRayDirY, nRayDirZ ) if ( nButton == 0 ) then local hSelection = hud.getComponent ( this.getUser ( ), "Selection.Area" ) hud.setComponentVisible ( hSelection, false ) this.bMouseLeftDown ( false ) end end
Test the selection
Launch the game and, in the Scene Viewer, create a selection area with the mouse. You should see the area.
Create the scene
In the next sections, you must have a scene with some objects. For that, create a scene called ‘myScene’. Reference this scene in the game (in the Game Editor > Edit > Scene > Add menu, select ‘myScene’. Open the scene in the Scene Viewer, then, in the Scene Explorer right click on the ‘DefaultCamera’ object and choose ‘Edit Selection Tag’. Set this tag to ‘Camera1’. Now, in the Data Explorer > Models, drag some Box models and add them to the scene and save the scene.
Open the scene on init
Modify the ‘onInit’ handler so that it opens the scene and activates the camera as follows:
function Selection.onInit ( ) -- open scene application.setCurrentUserScene ( "myScene" ) -- activate camera local cam = application.getCurrentUserSceneTaggedObject ( "Camera1" ) application.setCurrentUserActiveCamera ( cam ) object.setTranslation ( cam, 15, 15, 15, object.kGlobalSpace ) object.lookAt ( cam, 0, 0, 0, object.kGlobalSpace, 1 ) -- display HUD hud.newTemplateInstance ( this.getUser ( ), "Selection", "Selection" ) end
Now, we are going to modify the onMouseButtonUp so that it detects objects located in the selection area. To store these objects create a table variable (type=table) called “tObjects” in your AIModel.
And modify the ‘onMouseButtonUp’ handler as follows:
function Selection.onMouseButtonUp ( nButton, nPointX, nPointY, nRayPntX, nRayPntY, nRayPntZ, nRayDirX, nRayDirY, nRayDirZ ) -------------------------------------------------------------------------------- if ( nButton == 0 ) then local hSelection = hud.getComponent ( this.getUser ( ), "Selection.Area" ) hud.setComponentVisible ( hSelection, false ) this.bMouseLeftDown ( false ) local nMinX, nMinY = hud.getComponentPosition ( hSelection ) local nSizeX, nSizeY = hud.getComponentSize ( hSelection ) local nMaxX = nMinX + nSizeX local nMaxY = nMinY + nSizeY nMinX = nMinX / 50 - 1 nMinY = nMinY / 50 - 1 nMaxX = nMaxX / 50 - 1 nMaxY = nMaxY / 50 - 1 table.empty ( this.tObjects ( ) ) local hScene = application.getCurrentUserScene ( ) local hCamera = application.getCurrentUserActiveCamera ( ) if ( hScene and hCamera ) then local count = scene.getObjectCount ( hScene ) for i = 0, count - 1 do local hObject = scene.getObjectAt ( hScene, i ) local posX, posY, posZ = object.getBoundingSphereCenter ( hObject ) local x, y = camera.projectPoint ( hCamera, posX, posY, posZ ) if ( ( ( x < nMaxX and x > nMinX ) or ( x > nMaxX and x < nMinX ) ) and ( ( y < nMaxY and y > nMinY ) or ( y > nMaxY and y < nMinY ) ) ) then table.add ( this.tObjects ( ), hObject ) end end end end log.message ( "Mouse up. Table contains " .. table.getSize ( this.tObjects ( ) ) .. " elements" ) -------------------------------------------------------------------------------- end
The new code calculate the minimum and maximum coordinates of the Area component and converts them into screen coordinates (HUD coordinates range from 0 to 100 whereas screen coordinates range from -1 to 1). Then it clears the table, gets the scene in a hScene variable, and the camera in a hCamera variable. If both handles are ok, it runs a loop on all the objects of the scene. For each object, the code gets the object position and projects it onto the screen coordinate space using the camera.projectPoint ( ) function. This computes the (x,y) coordinates of the projected point. If this coordinate is inside the Area, the object handle is added to the table.
Test the complete application
Launch the game. In the Scene Viewer select the objects of the scene. The Log Reporter should display something like:
Mouse down. Mouse up. Table contains 3 elements