Unity Android Game Development by Example Beginner's Guide
上QQ阅读APP看书,第一时间看更新

Time for action – the dynamic GUI

We will be covering two excellent ways of dynamically adjusting our GUI to meet any screen requirements. The opening screen and the game over screen will both be centered. We will stretch the game board to fill the available space. The turn indicator text will also be set up to automatically change position based on the screen orientation.

  1. Again, let's start with our main menu. Open up the TicTacToeControl script and go to the DrawOpening function.
  2. To center the menu, we will wrap up the contents as a GUI group by adding the following line of code at the beginning of the DrawOpening function. Think of GUI's grouping as picture-in-picture (PIP) that some televisions can do. Pick a rectangle section on screen and draw some other channel in it. So, first we are deciding where to draw our group. We do this by finding the center of the screen, Screen.width and Screen.height is divided by two. But, because GUI content is positioned at the top-left corner, we must subtract half our content's size to find that corner. For the width, that is simply the width of our title image. But the height is a combination of the image and the button below.
    Rect groupRect = new Rect((Screen.width / 2) - (titleImage.width / 2), (Screen.height / 2) - ((titleImage.height + 75) / 2), titleImage.width, titleImage.height + 75);
  3. The BeginGroup function of the GUI is what gives us the PIP effect. Any GUI elements that are drawn after a call to this function are confined to the Rect class that was passed to the function. Instead of positions starting from the top-left corner of the screen, elements within the group will start at the top-left corner of the group Rect. Anything that extends beyond the edges of the group is also not drawn, just as if it extended beyond the edges of the screen.
    GUI.BeginGroup(groupRect);
  4. Before we can see the group in action, we must add a line to the end of our DrawOpening function. EndGroup is the direct counterpart to BeginGroup. If ever you use BeginGroup, there must be a corresponding call to EndGroup. Should you fail to pair up the function calls, Unity will make no end of complaints until the problem is fixed. It is really quite annoying.
    GUI.EndGroup();
  5. With that line added, play the game. The main menu will now center itself. It will do this no matter the screen size. Also, it will do this whether the screen is in landscape or in portrait mode. In this case, the trick to keeping everything on screen is to plan for the smallest screen size and make images and GUI elements that fit accordingly.
  6. Skipping ahead a little, we can use a similar method for centering the game over screen. Add the following line of code to the beginning of the DrawGameOver function. You can see that we are doing the same thing we did a moment ago. Figure out where the center of the screen is and subtract half of the total size of our content. In this case we supplied solid numbers instead of keying off the size of an image. Also, because the math is easy, we already did the divisions to come up with 150 and 75.
    Rect groupRect = new Rect((Screen.width / 2) - 150, (Screen.height / 2) - 75, 300, 150);
    GUI.BeginGroup(groupRect);
  7. Be sure to add your EndGroup function call to the end of the DrawGameOver function.
    GUI.EndGroup();
  8. After that, find the line where we define the winnerRect variable class. We need to change it so it is easier to adjust the size and fit the contents, should we want to. Because of the way we set up the winner label and main menu button, this will cause each to take up the whole width of the group. They will also split the available height evenly; hence the division is by two.
    Rect winnerRect = new Rect(0, 0, groupRect.width, groupRect.height / 2);
  9. Now we have the tricky part to do. For the game board, we want it to expand evenly so that it fills whichever direction is shortest. The turn indicator should be centered in the remaining space. Because the board needs to dynamically expand and the turn indicator needs to be either in the right or bottom of the screen, based on orientation, we can't get away with using our GUI group functions. Instead, we first need to figure out which side of our screen is smaller, the width or the height. This is fairly simple with the following lines of code added to the beginning of the DrawGameBoard function. Recognize the conditional statement, our good old friend? First, we create a variable to hold the result of comparing the width and height of the screen; we will be using it again later. If the width is smaller, obviously the small side is the width; otherwise it is the height.
    bool widthSmaller = Screen.width < Screen.height;
    float smallSide = widthSmaller ? Screen.width : Screen.height;
  10. Next we change our width and height definitions. Because the game board is a 3 x 3 grid, once we have the small side it is a simple matter to figure out how big the squares should be to fill the space. The change to the height is to keep the board squares actually square. Perhaps you remember from your first geometry lessons? The width and height of the sides of a square are equal.
    float width = smallSide / 3;
    float height = width;
  11. Playing the game at this point, we will be able to experience a game board that scales with our game screen. Try it out!
    Time for action – the dynamic GUI
  12. Remember when we were connecting Unity Remote? Use the drop-down menu in the top-left corner of the Game window to select different screen sizes and orientations. This does, however, reveal another small error. The turn indicator text sometimes appears over the top of our game board. Other times it may be beyond the edges of the screen. Or, perhaps you already noticed that one? Either way, to make it better we need to find the Rect class that will cover the remaining negative space.
  13. After the initial definition of our turnRect, add the following code snippet. Using our conditional friends, we figured out all we need to place the Rect class in the negative space. If the width is smaller in portrait mode, the negative space starts at the left side of the screen, zero. The y position of the space begins where the board ends, the equivalent of the width; it is a square board, after all. The total width of the negative space is also equivalent to the width of the screen. The height becomes whatever is left over from the difference between the height and the width. If we are in landscape mode instead, the height being smaller than the width, the positioning is largely determined in the same way.
    turnRect.x = widthSmaller ? 0 : smallSide;
    turnRect.y = widthSmaller ? smallSide : 0;
    turnRect.width = widthSmaller ? Screen.width : Screen.width - Screen.height;
    turnRect.height = widthSmaller ? Screen.height - Screen.width : Screen.height;
  14. This is all well and good. Looks pretty good with the turn text actually positioned where it can easily be seen and read. But, in some of those screen sizes there is an awful lot of empty space. If only there was some way we could scale the text to better fit the space. It just so happens that there is a good way to do just that. After we are done messing with the turn indicator's Rect, we can add the following line of code. This touch of code gets the label GUI Style from the current GUI Skin and creates a duplicate. In code, if we ever create a new GUI Style and pass another one as the argument, all of the values are copied into the new GUI Style. This allows us to cause temporary and dynamic changes without ruining the whole GUI Skin being used.
    GUIStyle turnStyle = new GUIStyle(GUI.skin.GetStyle("label"));
  15. In the next line of code we'll adjust the font size. To do this we have to figure out how much space is left on the long side of the screen after the game board is scaled up. Adding the width and height of the screen results in the total amount of screen distance available. By subtracting the smaller of the two sides, the distance that the game board covers multiplied by two, we are left with the excess negative space. Dividing all of that by one hundred, the amount of space that we had previously used for our turn indicator, will scale the font size to proportionately fit the change in space. It is finally wrapped in an explicit conversion to the integer type because the font size value must be defined as an integer.
    turnStyle.fontSize *= (int)((Screen.width + Screen.height - (smallSide * 2)) / 100);
  16. To actually see this dynamic font size in action, we need to make a change to the line that draws the turn indicator. We change the call to the Label function to use the temporary style. Instead of providing the name of the GUI Style to GUI functions, we can provide a specific GUI Style. The function will then use this style to draw the GUI element.
    GUI.Label(turnRect, turnTitle, turnStyle);
  17. Try it out. By clicking on the Game window tab and dragging it into the Game window, you can undock the window and make it free floating. Changing the aspect ratio, that drop-down menu in the top-right corner of the Game window, to Free Aspect allows us to freely re-size the window and witness our great work in action.
    Time for action – the dynamic GUI

What just happened?

We made our game change dynamically based on the screen of our devices. Both of the menu screens will center themselves. We also caused our game board to grow and shrink until it fills as much of the screen as it can. We then used a carefully applied bit of code magic to make the turn indicator automatically position itself and change font size to fill the remaining space.

Have a go hero – scaling menus

The second challenge is a little tougher. Continue to use the GUI groups, but make the opening screen and the game over screen scale with the screen size. If you want a subchallenge with this one, see what you can do about scaling the text with it as well. And, don't forget about the text used to indicate control of the game board squares.

If you want to prepare for even more devices, change the Rects that we use throughout the section. Wherever we used specific numbers for position or size on screen, change them to percent. You will have to calculate the pixel size using percent and the size of the screen. That calculated amount can then be passed to and used in our Rects.