20151031

UI design

Joel Spolsky wrote about good UI design here: http://www.ikoffice.de/downloads/Books/User_Interface_Design_For_Programmers.pdf (Yes, I know, I'm giving away someone's work for free. I hope that as the CEO of StackOverflow he doesn't mind this.)

I know I've read it at least twice since my boss suggested it at one of my jobs, and I really enjoyed reading it. Most of the points seemed straightforward and logical, served in an easy-to-understand and fun way. However, I can't remember a single point of the book. In spite of this, I'm sure the things I've learnt from it are influencing my UI design methodology to this day.

So here I am, developing a game that should've been finished months ago I want to get on the most types of devices possible, each of which has different input methods, different screen sizes, and different metaphors established. How do I deal with it?

Using an app on a PC is easier than using the same piece of app on a mobile device. You have bigger space on the screen, finer and more diverse input methods, and usually more horsepower. Probably these are the main reasons why apps made for a desktop computer are typically more complex, whereas apps on mobile devices usually focus on one thing.

How does it affect us as an app dev? If you are lazy like me, you don't want to think of different input methods on each platform - you would rather have one, and apply it to all platforms. In this case, it makes sense to customize the UI for the most restrictive platform, and then add convenience features on the others, where there aren't that much restrictions.

So I went ahead and started to design the UI for the smartphone. Key principles were:
  • Easy to use buttons (So they should be as big as possible)
  • As little text as possible (So far the only text is the title of the game)
  • Make the UI minimalistic, make it stand out and make it easy to understand
  • Structure everything in a way that the most used features are the most easy to reach
Currently I'm working on the main menu, which looks like this:


I have asked a few of my friends if they can figure out what each button means, provided this is a strategy game. Results were satisfying. (FWIW: New game, Settings, Map editor, Exit)

If you go to the map editor, you'll see this:


Top right side there is a button, which will contain a minimap. If you tap it, the minimap will expand. Tapping the minimap will minimize it. On the bottom, there is a Menu button, a Settings button and a paint mode. The paint mode button will change on each tap between paint, delete and move mode. (Move mode means you can drag the map around.)

Menu screen:



Self-explanatory. Settings screen:


This is only part of the settings screen. Here, terrain painting mode is selected, and the thinnest brush (one tile wide). You will be able to select town mode (where you can tell for which players will you place towns) and unit mode (same, but you can also change the type of unit being placed).

Please note that this isn't implemented yet, I just put together it in Inkscape. Also, if you are not worried about copyright things, you can google <icon name> icon (e.g. save icon), open one that you like, copy the image, paste it in Inkscape, then click on Path -> Trace bitmap, and you can have an SVG version of it. Then you can press Ctrl-L (or choose Path->Simplify) to have less points.

And now, if you excuse me, I'm going back to read this Joel Spolsky book.

20151004

Handling input

The problem: how to structure the input handling in a game that could run on PC and mobile devices? Each platform has different control schemes. In cases like this, I like to look for the common denominator. In this case, if we use only the left mouse button, the mouse wheel and the mouse itself, we get the same control actions as on the smartphone: tap (click), swipe (drag), pinch (scroll the mousewheel). Since I have experiences only with these two kind of devices, I won't talk about others, but I assume it isn't hard to fit these three actions over them.

Using these input methods, we can define events, like Tap, Drag and Zoom (because fuck naming consistency). However, we can zoom while we are dragging, so we will track the zoom level separately from the other events. We will also need a None event type, for the times nothing happens.

Let's see what kind of event transitions are available: take None, for example: when the user clicks (or touches the screen), it could mean either a start of a Tap, or the start of a drag. How to decide which one? I arbitrarily decided that if the touch (click) lasts no longer than 5 frames, and at the end of it the input device's position is not further than 10 pixels from the original place, I will consider it a tap, otherwise it's a drag. Until it isn't decided, I will call that state Hold.

So None can transform to Hold, Hold can transform to Drag or Tap, and Drag / Tap can transform to None. Maybe introducing Hold isn't a good idea, but I can't see any problems with it yet. Time will tell.

Now, when I worked on other parts of the code, I just threw in some GetMouseButtonDown(0) to check if the mouse was pressed. But now, that I want to use the input, this kind of decentralized input checking is starting to get messy, harder to understand and error-prone. This is why I have created an InputObject, and attached an InputController script to it.

This script has three public variables (scrolling being the fourth, but I will implement that later): current input mode, event position and last position. The current mode can either be None, Hold, Tap or Drag. The event position will always tell where was the cursor when the action happened. In the case of Tap, it is where the action ended, and in the case of drag, where was the cursor dragged to in each frame, and also the last position will tell where the cursor was dragged from. (In all other cases, the last position tells nothing. I know it could be done more elegantly, but this is straightforward enough for now. If I'll make a library out of it, which I seriously doubt, I will do it elegantly.)

Then, in the Update() function, I just use a switch on currentMode to decide what to do next. The cases are bold here.

void Update () {
  leftButtonDown = Input.GetMouseButton(0);
  Vector2 currentPosition = Input.mousePosition;

  switch (currentInputMode) {
  case InputMode.None:
    if (leftButtonDown) {
      currentInputMode = InputMode.Hold;
      lastPosition = currentPosition;
    }
    break;
 
  case InputMode.Hold:
    if (GetFastDistance(currentPosition, lastPosition) < 10 &&
        framesSinceLastClick < 5) {

       if (!leftButtonDown) {
         currentInputMode = InputMode.Tap;
         eventPosition = currentPosition;

       }
    }
    else {
      currentInputMode = InputMode.Drag;
      eventPosition = currentPosition;
    }
    break;
 
  case InputMode.Tap:
    currentInputMode = InputMode.None;
    break;
 
  case InputMode.Drag:
    if (leftButtonDown) {
      framesSinceLastClick++;
      lastPosition = eventPosition;
      eventPosition = currentPosition;
    }
    else {
      framesSinceLastClick = 0;
      currentInputMode = InputMode.None;
    }
    break;
  }
}

Also, since I don't need a precise distance, just something like a pseudo-distance, here is a faster version, that defines a square-shaped area rotated by 45°:

int GetFastDistance(Vector2 p1, Vector2 p2) {
   return Mathf.RoundToInt(
    Mathf.Abs(p1.x - p2.x) + Mathf.Abs(p1.y - p2.y));
}

And this works surprisingly well. Now, the camera controller's update has a code snippet responsible for calculating the amount of displacement for the camera:

 if (inputMode == InputController.InputMode.Drag) {
  Vector3 difference =
    inputController.GetCurrentPosition() -
    inputController.GetPreviousPosition();
  Vector3 pos = myCamera.ScreenToViewportPoint(difference);
  currentMouseDragSpeed =
    new Vector2(-pos.x * mouseDragSpeed, -pos.y * mouseDragSpeed);
}
else {
  currentMouseDragSpeed = currentMouseDragSpeed * 0.8f;
}

(Don't worry, my code is commented, I just don't paste the comments here.)