Select objects using a selection area – ShiVa Engine

Select objects using a selection area

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

  • slackBanner