How I implemented multiple selection and grouping

The finished product
Until a few days ago, you could only select one ship at a time in Winds of Trade. This, of course, makes managing a large fleet a pain in the butt, so I decided to go ahead and implement multiple ship selection and movement. These were the basic rules I decided on:
  • Player can select multiple ships drawing a rectangular area with the mouse
  • Player can select multiple ships while holding "ctrl" and clicking
  • Player can move all of those ships at once
  • Player can create selection groups using "shift" + number keys
  • Player can then recall those groups
I have to say I pretty much didn't know where to begin. So I decided to start with the most basic thing: replacing the datatype for the variable that holds the currently selected ship. I.e.:

var selectedShip : Ship;

Became:

var selectedShips : List.<Ship>;

After fixing all compile errors the change unleashed I decided to get started in the UI stuff. First step, making sure the user can draw a rectangle by dragging the mouse (that was easy) and then detecting what ships are in the selection (that wasn't so easy) in order to add them to the selectedShips list. In order to do this, I had two possibilities:
  1. Convert the screen coordinates to world coordinates (the rectangle in screen becomes a trapezoid in the surface of the water, I guess) and decide what ships are inside that trapezoid.
  2. Convert the world coordinates of all ships owned by the player (shouldn't be too many, after all) to screen coordinates in order to see what ships are inside the rectangle.
I opted for approach #2, as it was a lot easier, and it worked perfectly at the first try. Basically:

var selectionResult : List.<Ship> = List.<Ship>();
for (var ship : Ship in GetPlayerCompany().ships) {
    var shipPosition : Vector3 = ship.transform.position;
    var screenCoords : Vector3 = mainCamera.WorldToScreenPoint(shipPosition);
    if (screenCoords.x > minX && screenCoords.x < maxX && 
        screenCoords.y > minY && screenCoords.y < maxY) {
            selectionResult.Add(ship);
    }
}

After that I had to spend some time fixing the traditional "click" selection so it still behaved correctly. I think the main issue I faced was the fact that I ignore all clicks done over the UI (so you don't click through a window and end up selecting a ship that is behind it) but at the same time the selection rectangle is a UI element as well. So when you click and release in the same position you are essentially creating a 1x1 pixels UI element that blocks the click and you end up being unable to select cities or ships or doing anything else. My solution? Do not draw the selection rectangle if its size is less than 5x5 pixels. Not an elegant solution, I guess, but it worked like a charm.

Implementing the ship groups was really easy, it was just a matter of adding a property to hold the groups, populating them when shift + a number is pressed and recalling them when just the number is pressed. The data structure to hold them is:

var shipGroups : Dictionary.<KeyCode, List.<Ship> >;

Finally, the last problem I had to face was the fact that, when you move a large number of ships, they all go to the same spot (the point where you clicked) so they end up having the same position and become a huge indistinguishable mess of hulls and sails. My solution: arrange the ships final positions in a spiral, like this:

Image from http://en.wikipedia.org/wiki/Ulam_spiral

In that way, you manage to avoid the overlap and they even look like they are in some sort of formation. Anyway, I won't speak anymore of technical details, here is a gif showing the final result. I am pretty pleased with it!

Comentarios

Publicar un comentario