tutorial – The Industrious Squirrel https://blog.chadweisshaar.com Sun, 22 Sep 2019 19:04:29 +0000 en-US hourly 1 https://wordpress.org/?v=6.8.3 https://blog.chadweisshaar.com/wp-content/uploads/2016/07/favicon.png tutorial – The Industrious Squirrel https://blog.chadweisshaar.com 32 32 Board game programming tutorial – Part 3 https://blog.chadweisshaar.com/2017/04/30/board-game-programming-tutorial-part-3/ https://blog.chadweisshaar.com/2017/04/30/board-game-programming-tutorial-part-3/#respond Mon, 01 May 2017 04:05:02 +0000 http://gator3305.temp.domains/~cweissha/blog/?p=1214 Continue reading "Board game programming tutorial – Part 3"]]> This is the third part in my tutorial for converting a board game for a touch table using Unity.

The first part covered all our reuse code and conventions, setup the project and created a main menu.

The second part built the UI for a very simple game called “No Thanks!”.

This part will write the events to control the game, add buttons to the UI for the players to use to play the game, and finally add animations to the game.

There is a set of videos to go along with this post which show all the steps that I perform in the Unity game builder.

I’ve also saved my Unity project as of the last blog post and as a completed game. If something hasn’t worked for you, you can compare with my project.

Information about the re-use scripts
Here are some pages describing each part of the common code. Especially useful for this tutorial are the sections about the game model and the event system.

  • Touch – Describes multi-touch handling in Unity and passing touch events to the Unity UI
  • Utilities – Covers all the utility classes and functions that we’ve created over time
  • Game Model – The “Model” part of the Model/View/Controller pattern
  • View – The skeleton classes for running the GUI
  • Event System – The “Controller” part of the Model/View/Controller pattern
  • Events – The events that are common to all our games.

Step 11 – Add player control buttons and events.

Follow along on the video

The player’s need to be able to tell the game whether they are going to take the currently offered gift or pay a chip.

I’ll add buttons to the Gift and Chip images on the player interface. Optional: highlighted buttons

For this tutorial, I’ll set the Button transition to “None”. It would be nicer to have a ‘highlighted’ version of these icons that would indicate the buttons are touchable. Then I’d use the sprite swap transition.

These buttons will call functions in the PlayerGUI and PlayerDialogGUI when they are clicked. These callback functions will check if the player can really execute the action and then add the control event to the timeline.

In most games I put code to check the validity of a player action either in the button callback or in the draw code. If the game needs to tell the player why a particular action isn’t valid, then I’ll put the code in the button callback and use the WindowsVoice plugin to speak an error message. If a message isn’t needed, then the draw function will simply make the button inactive. I use this second method if I’ve made separate active and inactive graphics for the button.

So the button needs a callback function and the callback function needs to create an event, so I’ll start by creating the events. I use a convention of starting the names of events with either ‘E’ for an engine event and ‘P’ for a player event. So I’ll create two events – PTakeCard and PPayChip.

PTakeCard

PTakeCard is a PlayerEvent. This is the base class for events created by player actions. The constructor requires the player taking the action. The class defaults QContinueUndo to false and provides convenience functions to return the player and the player’s GUI.

The code should look like this:

public class PTakeCard : PlayerEvent
{
  public PTakeCard() : base(Game.theGame.CurrentPlayer) { }

  public override void Do(Timeline timeline)
  {
    _player.NumChips += Game.theGame.NumCenterChips;
    Game.theGame.NumCenterChips = 0;
    _player.TakeGift(Game.theGame.GiftDeck.Pop(0));

    if (Game.theGame.GiftDeck.Count == 0)
      timeline.addEvent(new EEndGame());
  }
  public override float Act(bool qUndo = false)
  {
    GameGUI.theGameGUI.draw();
    return 0;
  }
}

The “Do” function is call whenever the event is executed or re-executed (due to load or undo). This function is responsible for updating the game model.

For PTakeCard, the “Do” function gives the player the chips in the center and the top card. It also checks to see if we are out of cards and, if so, ends the game.

The “Act” function is only called when the event is first executed. This function is responsible for updating the UI to match the new game state. It can also, optionally, animate the UI or play sound effects. Since the “Act” function isn’t called during load or undo, animations and sounds will only happen for real-time events.

For now, I’ll just have the “Act” function re-draw the UI. I’ll add sounds and animations later.

PPayChip

PPayChip is another Player event. The code looks like this:

public class PPayChip : PlayerEvent {

  public PPayChip() : base(Game.theGame.CurrentPlayer) { }

  public override void Do(Timeline timeline)
  {
    --_player.NumChips;
    ++Game.theGame.NumCenterChips;
    Game.theGame.CurrentPlayer = PlayerList.nextPlayer(_player);
  }
  public override float Act(bool qUndo = false)
  {
    GameGUI.theGameGUI.draw();
    return 0;
  }
}

The player pays a chip and the center gets the chip. The CurrentPlayer is set to the next player.

Callbacks

Add the chip callback to the PlayerGUI. This code needs to check if the player can really pay a chip. First of all, it needs to be that player’s turn. Since the chip is always displayed on the player’s UI, we can’t let just any player to click their chip button. Second, the player needs to have chips to spend.

  public void OnPayChipClick()
  {
    if ( Player == Game.theGame.CurrentPlayer )
    {
      if (Player.NumChips > 0)
        Timeline.theTimeline.addEvent(new PPayChip());
      else
        WindowsVoice.speak("Out of chips");
    }
  }

The take card callback in PlayerDialogGUI doesn’t need any check. The Gift icon is only shown for the current player and a player can always accept the card.

  public void OnAcceptGiftClick()
  {
    Timeline.theTimeline.addEvent(new PTakeCard());
  }

Add a button behavior to the Chip on the player UI. Set the transition to none and set the OnClick callback to PlayerGUI.OnPayChipClick. Apply the change.

Similarly, add a button behavior to the GiftIcon in the “playerDialogs” and set the OnClick callback to PlayerDialogGUI.OnAcceptGiftCallback. Apply the change.

Undo/SaveAndExit Buttons

Create buttons on the Game UI to allow the players to quit and undo an action.

When to allow undo?

If new information is revealed by a player’s action, undo may not be appropriate. In this game, if a player takes a card, the next card is going to be revealed. So, should the player be allowed to undo?

In my opinion, the software should not enforce a rule on the players. If the other players want to allow the undo, the software shouldn’t prevent it.

 

Re-shuffle after undo?

When the player does undo, should the deck be re-randomized so that a different card is drawn? This may make it harder for players to take advantage of information they shouldn’t have seen.

The default behavior of the Timeline engine is to leave the random generator alone so that you will get the same result after an undo. If you do want re-shuffle the deck, you need to always re-shuffle before any draw. The game can’t tell if there has been an undo, so you need to have code like:

[code language=”csharp”] public int? Seed;
public override void Do(Timeline c)
{
if( !Seed.HasValue )
Seed = Mathf.Abs(System.DateTime.UtcNow.Ticks.GetHashCode());
UnityEngine.Random.InitState(Seed.Value);

Game.theGame.Deck.shuffle();
Card = Game.theGame.Deck.pop(0);
}
[/code]

The first time this event is executed, it will pick a new seed for the random number generator, save that seed and then use it to shuffle the deck and draw a card.

When this event is saved and re-executed during a load, it will see that it already has a seed and use that seed to init the random number generator. This will result in the same shuffle and draw as the first time.

However, if this event is undone it is deleted, and the next time a card needs to be drawn, a new event will be created which will create a new seed and a new shuffle result.

Add a UI/Button to the “GameCanvas”. Set the sprite to “RectButton_0”, set the transition to “Sprite Swap” and the disabled sprite to “RectButton_1”. Add an OnClick callback of GameGUI.saveAndExit. Delete the child text and replace it with a TextMesh object that is black and has the text “Save and Exit”. Set the rotation to -90 and position it on the right side of the screen.

Repeat that procedure for the Undo button and make the callback GameGUI.undoAction.

Testing

The game is now “complete” in that players can log into the game, play a full game of “No Thanks!”, determine the winner and allow the players to exit the game.

I still need to add animations and sounds to the main game, a load screen to the main menu and probably cleanup the score region on the player area which seems to get a bit crowded when a player has taken lots of cards.

Step 12 – Animations and sound

Follow along with the video

I’m going to start by adding animations to the PPayChip and PTakeCard events. These animations will be added to the act() functions and will replace the calls to GameGUI.draw() which immediately drew the entire UI.

All the draw() functions in the GUI classes immediately draw the UI to match the game model. The act() function is called after the do() function, so any draw() function will draw the updated model.

So, when I animate a UI element moving from the center of the board to a player area or vise-versa, I need to draw the source of the move immediately and the target of the move after the animation is complete.

For example, if I’m moving an accepted gift card from the center to the player area; I can’t draw the player area immediately because it would then show the taken card before it arrives. But I do need to draw the center immediately so that the card disappears.

So here is the basic flow:

  1. Create a copy of the object that is going to move (like the card in the center)
  2. Setup an animation of that copy using DoTween.
  3. Call draw() on the parent/owner of the object being moved.
  4. Wait till the animation is complete and call draw() on the parent/owner of the target of the object.

This pattern can cause problems if there are multiple objects moving some from and others to the same area. In fact, “No Thanks!” will have this problem when the player takes a card. The card and chip will be animated from the center to the player area, but the gift icon will be animating from the player area to the next player.

Self animating GUI pattern

An alternate way to create game animations is to have the draw() functions in the GUI classes do the animations. To do this, the GUI classes need to compare the way the UI is currently drawn to the model and animate from the old UI state to the new model state.

This can work quite well for some types of data, but is not really suited for cases where model changes can be caused by different actions which need to be animated differently.

PPayChip.act()

When the play pays a chip to the center, I want to animate the chip moving from the player area to the center and the gift icon that marks the current player moving from the current player area to the next player area.

Keeping in mind that the game model has already been updated and that any draw() call will draw the new state, the pseudo code to do these animations is:

  1. Make a copy of the chip on the acting player’s area.
  2. Delete the text on the copy
  3. Animate the copy to the center over 1 second
  4. Make a copy of the gift icon on the acting player’s area.
  5. Animate the copy to the next player’s area over 1 second.
  6. Draw the acting player’s area (this updates the chip count and hides the gift icon)
  7. -Wait 1 second-
  8. Delete all the copies
  9. Draw the center area to update the chip count
  10. Draw the next player’s area to show their gift icon

The first step is to break up the GameGUI.draw() function to provide a way to draw the center of the screen without drawing the player areas. I’ll make a function called GameGUI.drawCenter() that the GameGUI.draw() function calls.

Here is the script code to implement the pseudo code above:

More about GameGUI.cloneOnCanvas

cloneOnCanvas creates a copy of the given object on the AnimationCanvas in the same position, size and rotation as the given object. It also resets the anchors and pivot of the copy. Because of the way Unity draws UI elements and the differences between the Transform and the RectTransform, positioning an object that doesn’t pivot around the center is non-intuitive.

  using DG.Tweening;
  public override float Act(bool qUndo = false)
  {
    GameObject chipCopy = GameGUI.cloneOnCanvas(_gui.Chips);
    chipCopy.DestroyChildrenImmediate();
    chipCopy.transform.DOMove(
      GameGUI.theGameGUI.OfferedChips.transform.position, 1f).
      OnComplete(() =>
      {
        GameObject.Destroy(chipCopy);
        GameGUI.theGameGUI.drawCenter();
      });

    PlayerGUI nextPlayerGUI = GameGUI.currentPlayerPad();
    GameObject giftCopy = 
      GameGUI.cloneOnCanvas(_gui.DialogGUI.GiftIcon);
    giftCopy.transform.DOMove(
      nextPlayerGUI.DialogGUI.GiftIcon.transform.position, 1f).
      OnComplete(() =>
      {
        GameObject.Destroy(giftCopy);
        nextPlayerGUI.draw();
      });
    giftCopy.transform.DORotate(
      nextPlayerGUI.transform.rotation.eulerAngles, 1f);

    _gui.draw();

    return 0;
  }

To do the actual animation, I’m using the DoTween plugin. The DOMove and DORotate functions move and rotate the transform to the given position/rotation over the given time. The Tween.OnComplete function creates a callback that is executed after the animation is complete.

PTakeCard.act()

Next I’ll write the act() code for PTakeCard. This is slightly more complicated since there are more moving parts, but the basic idea is the same. I’ll make copies of the center chip and center card. Those will be animated to the player’s area. Then the center area will be drawn. After a second the player’s area will be drawn. I’d also like to animate the new card being drawn from the deck. So, after drawing the center area (which will update the center card with the new number), I’ll move the center card over to the deck and animate it to its normal position.

Another complication is that the chip shouldn’t be animated if there weren’t any chips taken. The only way for the Act() function to know how many chips were taken is for the Do() function to save that data.

  using DG.Tweening;
  private int _numChips = 0;
  public override void Do(Timeline timeline)
  {
    _numChips = Game.theGame.NumCenterChips;
    ...
  }
  public override float Act(bool qUndo = false)
  {
    // Center card
    GameObject movingCard = 
      GameGUI.cloneOnCanvas(GameGUI.theGameGUI.OfferedGift.gameObject);
    movingCard.transform.DOMove(_gui.CardContainer.position, 1f).
      OnComplete(() =>
      {
        GameObject.Destroy(movingCard);
        _gui.draw();
      });
    movingCard.transform.DORotate(_gui.transform.rotation.eulerAngles, 1f);

    // Center chips
    if (_numChips > 0)
    {
      GameObject movingChip = GameGUI.cloneOnCanvas(GameGUI.theGameGUI.OfferedChips);
      movingChip.transform.DOMove(_gui.Chips.transform.position, 1f).
        OnComplete(() => GameObject.Destroy(movingChip));
      movingChip.transform.DORotate(_gui.transform.rotation.eulerAngles, 1f);
    }

    GameGUI.theGameGUI.drawCenter();
    // New center card
    GameGUI.theGameGUI.OfferedGift.transform.DOMove(
      GameGUI.theGameGUI.DeckSizeText.transform.position, 1f).
      From().
      SetDelay(0.5f);
    return 0;
  }

The last line is interesting. I’m using the DoTween.DOMove capability to animate the card like before, but I’m calling Tween.From() to reverse the animation and .SetDelay(1f) to start the animation after 1 second.

Testing this code, I noticed two problems:

The animationCamera needs a “depth” that is larger than the “depth” of the MainCamera so that it is drawn second.

Since I’m not making a copy of the center card, I need it to have a pivot of 0.5,0.5 or it doesn’t animate correctly.

Audio

Next I’m going to add some sound effects to the game. The first step is to add the names of the effects to the AudioPlayer.AudioClipEnum and then fill in the array of clips in the AudioPlayer behavior (which was added to the Control object).

There are three more audio clips for this game:

  • CardFlip – A card being flipped over, for use when a new gift card is drawn
  • Coin – A coin falling on another coin, for use when the player pays a chip
  • Shuffle – A deck of cards being shuffled at the start of the game
public class AudioPlayer : MonoBehaviour
{
  public enum AudioClipEnum
  {
    INVALID = -1, INTRO, CLICK,
    CARD_FLIP, CHIP, SHUFFLE
  }

At the start of the game, the EInitialize function shuffles the deck. The default EInitialize.act() function just draws the UI. I’ll add code to have it play the shuffle sound.

  public override float Act( bool qUndo )
  {
    AudioPlayer.PlayClip(AudioPlayer.AudioClipEnum.SHUFFLE);
    GameGUI.theGameGUI.draw();
    return 0;
  }

When the player pays a chip, I want to play the chip sound once so I’ll add the code: AudioPlayer.PlayClip(AudioPlayer.AudioClipEnum.CHIP); to the PPayChip.Act() function.

When the player takes the card, I want to play the chip sound once per chip taken and the card flip sound once:

  public override float Act(bool qUndo = false)
  {
    AudioPlayer.RepeatClip(AudioPlayer.AudioClipEnum.CHIP, _numChips);
    AudioPlayer.PlayClip(AudioPlayer.AudioClipEnum.CARD_FLIP, 1f);
    ...
  }

The second parameter to AudioPlayer.PlayClip is the delay before playing the sound.

Step 13 – Load and version number

Follow along on the video

When I built the main menu, I skipped a few steps so that I could get into building the game. Now I will go back and finish the main menu by adding a version number and a load screen.

Version Number

First I’ll add a version number. This is just a text UI object with the “Version Info” behavior.

Add a Text object to the top level of the MainMenu, add the “Version Info” behavior, set the version number to 1.0, move the object to the right side of the screen and rotate it to face the left. Make the font bigger and the text white. Hit “play” to force the script to update the text.

Adding a version number seems a bit silly, but it really does save a lot of time once you start building standalone executables, because you can be sure that you have deployed the latest version before testing.

Load Screen

I’ve already built a load canvas, and the Load button is hooked up to the re-use code, so clicking load will show the load canvas. Now I need to create the Load UI and hook up the rest of the member data in the re-use code.

Load/Save works by using Unity’s Application.persistentDataPath. On windows, this variable expands to %APPDATA%/LocalLow/<CompanyName>/<ProductName>. I create a sub-directory there called “/savedGames”. When a game is started, it automatically saves the startup events to a sub-directory named “yyyy_MM_dd_HH_mm_ss”. When the player hits the “Save and Exit” button on the GameGUI, the Timeline is told to save the current events to that same directory. At this time, the re-use code also takes a screenshot of the game and saves that to the same directory.

So, after playing a few games, my save directory looks like this

The MainMenu re-use script has code to parse the save directory and create a game tile on the Load Panel for each saved game. This bit of re-use code is pretty raw and makes some big assumptions about what UI elements are on the LoadCanvas.

First, I’ll add a Panel to the LoadCanvas. This has to be named “Panel”. The panel should take up the whole screen and be fully opaque.

It should have a child panel named “title” that takes up the top 60 pixels. The “title” panel should be a darker grey and have two children:

  • A TextMesh object that is centered and has the text “Saved Games”
  • A button on the far right that is colored red and has a child text element with the text “X”. The callback should be MainMenuGUI.OnLoadButton()

The “Panel” object should have a second child that is a “Scroll View”. Add a “Scroll View” with the Unity menu and leave all the names as their default values. Set it to fill the parent with a Top of 64 pixels. In the content of the viewport, add a grid layout with a size of 400×272 and a spacing of 4×4. When everything is created, it should look like this:

Next we create a prefab for the saved game tile which will be added to the Scroll View/Viewport/Content for each saved game.

Add a button to the Content object. The grid layout will automatically size the button. We don’t need to set the callback since that is done in the re-use script when it creates the button. Name the button “Saved Game Entry”

The “Saved Game Entry” button should have three children

  • A “Raw Image” object named “Screen Capture” sized to fill the area with a border of 48 on the bottom.
  • A “Text” object named “Date Text” positioned on the bottom left and set to Auto Fit the text size
  • A “Button” object named “Load Button”. Remove the button behavior and add a child text object with the text “Load”.

When you are done, the “Saved Game Entry” should look like this:

Make the “Saved Game Entry” a prefab by dragging it into the Prefab directory. Then delete it out of the Scroll View.

Drag that prefab into the MainMenu’s “Saved Game Entry Prefab” member on the “Controller” object.

Hide the “LoadCanvas” and run the game. The load button should now bring up the LoadCanvas populated with the saved games. The red “X” button should close the dialog, and clicking on any of the game tiles should load that game.

Building for Windows

To build an executable that will run on windows, save the scene, then bring up the “Build Settings” window under the file menu.

Click “Add Open Scenes” and change the architecture to “x86_64”. Click the “Build” button and pick a directory to build in.

Step 14 – Cleanup

This tutorial is now complete. You can download a zip of the completed project NoThanks-Final.

There are several more things that I will do for my own version of this game:

  1. Fix the text on the deck of cards so that it says “(0)” instead of “(-1)” at the end of the game.
  2. Change the scoring so that cards are positive and chips are negative to match the way the board game rules are written.
  3. Make sure that the score text still looks good if a lot of cards are taken.
  4. Tweak the new card animation so that it still looks right if a player quickly takes a second card while it is still animating.
  5. Add a couple variants as options on the main menu.
  6. Add a “Credits” button and panel to give credit to the board game’s designer, and to the artists who created the graphics I used.
  7. Re-design the game so that it plays multiple rounds (one per player) and has a scoreboard that adds up the player’s score for each round. The game then ends when the last round is over.
  8. Make a highlighted chip to show when the button is clickable so that it is obviously a button.

 

]]>
https://blog.chadweisshaar.com/2017/04/30/board-game-programming-tutorial-part-3/feed/ 0
Board game programming tutorial – Part 2 https://blog.chadweisshaar.com/2017/04/28/board-game-programming-tutorial-part-2/ https://blog.chadweisshaar.com/2017/04/28/board-game-programming-tutorial-part-2/#comments Fri, 28 Apr 2017 17:44:53 +0000 http://gator3305.temp.domains/~cweissha/blog/?p=1182 Continue reading "Board game programming tutorial – Part 2"]]> This is the second part in my tutorial for converting a board game for a touch table using unity.

The first part covered all our reuse code and conventions, setup the project and created a main menu.

This part will build the game and player UIs for a very simple game called “No Thanks!”.

There is a set of videos to go along with this post which show all the steps that I perform in the Unity game builder.

I’ve also saved my Unity project at a few points along the way so that you can skip ahead or make sure that your project matches mine.

Information about the re-use scripts
Here are some pages describing each part of the common code. Especially useful for this tutorial are the sections about the game model and the event system.

  • Touch – Describes multi-touch handling in Unity and passing touch events to the Unity UI
  • Utilities – Covers all the utility classes and functions that we’ve created over time
  • Game Model – The “Model” part of the Model/View/Controller pattern
  • View – The skeleton classes for running the GUI
  • Event System – The “Controller” part of the Model/View/Controller pattern
  • Events – The events that are common to all our games.

 

Rules for the version of No Thanks! that I am implementing

Play starts with a deck of 33 “gifts” with a value of 3-35. High numbers are the worst gifts, but all gifts are bad. The deck is shuffled, 9 cards are removed and the first gift is turned face up.

Players start with 11 chips (less for 6 or 7 players) and, in turn order, can either pay a chip to the center or take the current gift and however many chips are in the center.

When a player takes a gift, they flip over the next card and must again pay a chip or take the card.

After all gift cards are taken, players throw away any gift card that is one greater than another gift they have taken. So if a player has taken 5,20,21,22,30,31. They would throw out 21,22 and 31. A player’s score is their chips minus the sum of the remaining cards.

Highest score wins.

Step 7 – Create a player area

Follow along with the video

The player area sits right in front of the player on the screen and contains UI elements to display their pieces/cards/etc and also has controls that player can use to interact with the game.

In “No Thanks!” the player area needs to show how many chips the player has, the cards that they have taken, their current score, and (at the end of the game) what place (1st, 2nd, etc) they got. The player area needs to have controls so that the player can pay a chip or accept the card. It is also nice for the player area to show the player’s chosen color and an indicator that it is their turn to play.

Switch to the GameCanvas by deactivating the MainMenu and activating the GameCanvas. Make a background image like we did for the main menu by adding an Image with the “background” sprite and set it to fill the canvas.

To make the Player Area, I’ll start by making an image with the border_0 sprite the same size as the login area (~600×200). Then I’ll add an image with the chip sprite with a child TextMesh object to display the count. I’ll put that on the left side. Then I’ll add an area to show the cards. That will be on the right side, take up most of the space (~425×128), and have a horizontal layout. Set the “Child Force Expand” to just height and “Control Child Size” to Both. Set the alignment to “Middle Center”.

Next I’ll make a card to go into the card area. I have a card sprite asset to use for the image, and I’ll make the card 96×128. That should be very visible, but leave room for a player to take quite a few cards. I added a “Layout Element” with a preferred width of 96 to the card since I’m planning to add it to a Horizontal Layout.

I’ll put the value of the card in the center and upper left. The center text can be large so that it can be easily seen in the center of the table when players are bidding. The text on the upper left is so that the cards can be packed tightly together while the value is still visible.

Now I’ll make that card a prefab since I’m going to instantiate cards as needed during the game.

more detail about prefabs

In Unity, a prefab is a “master copy” of something that you have several instances of. It allows changes made to one instance to be replicated to the others. I wouldn’t have to make the card a Prefab for this game. Instead I could have the “master copy” be the card in the center of the screen. The player areas could just make copies of this master whenever they need to add a card.

I want to group the cards that are part of a sequence together in the player area. To do this, I’m going to set the spacing of the horizontal layout to -68. This will push all the cards together. Then I’ll add a blank “spacer” element between groups of cards.

more about layouts

The layout system is one of the best features of Unity. It allows dynamic layout of UI elements during the game without having to write a bunch of code.

It is well worth spending some time reading the Unity manual about layout.

What I have done for this game is create a container for the cards and given it the Horizontal layout. This container will now arrange its children horizontally within it.

When I tell the layout to control the child width and height, the parent container will change the size of the children so that they all fit in the parent. The layout looks at the “Layout Element” behavior on the children for hints about how to do the layout and what size they should be.

Here, I’ve told the card and the spacer how wide I want them to be. If there is room, they will be set to that width. If there are too many cards to fit in the area, the horizontal layout will shrink them proportionally till they fit.

To make the spacer, simply create an empty object and add the “Layout Element” behavior and set the preferred width to 160.

Add a few cards and spacers to the card region of the player area to see if it looks right:

That is about what I was imagining. Next I want to show they player’s current score. This could just be a couple text elements, but I have plenty of space in this game, so I’m going to also display how the score is calculated. Below the chips and cards I’m going to add a score calculation. It will look something like:

“score: chips – lowest card of sequence 1 – sequence 2 – lowest card of sequence 3 = total”

The “Score” text on the left and the “= -99” text on the right are just TextMesh objects.

The only tricky part of this is to get the numbers for the cards to line up under the card itself, since I’m not manually positioning the cards. To accomplish this, I’ll just add the score text element to the Card object itself. I’ll turn off that text if the card is part of a sequence. Apply the changes to the card prefab after adding the score text.

So now the player area looks like this:

The final steps are to setup the scripting hooks for drawing this area dynamically. I set the “Tag” to “Player”, add the PlayerGUI behavior, remove all the cards and spacers from the card region, and make the area into a Prefab.

Now I copy the player area just like I did with the login area while building the main menu, arranging three pads on the bottom, one on the left and three on the top. I set the position member variable the same way as I did for the main menu.

why not just reuse the same UI for login and play?

A simple game like “No Thanks!” probably doesn’t need a separate Main Menu canvas and Game Canvas. Instead, I could put all the Main Menu controls and Game elements onto the same canvas and just hide one or the other based on the game mode.

Then I could have also re-used the player login area and play area, again hiding UI elements based on the game mode.

For a larger game, I like keeping things separate the way that I am demonstrating here. The player area is usually a busy space with a large amount of code behind it and it is even bigger with the login elements.

I used to make separate Unity scenes for the main menu and game. One advantage was that I could re-load the whole scene to reset the game (for undo. More about undo later). But that leads to even more duplication of effort as much of the controller has to be put into both scenes.

Step 8 – Create the Player Dialogs

Follow along with the video.

In most games, I use dialogs to request information from the player and to display information that is situational and that I don’t have room for on the main display. I’ll typically make these dialogs draggable so that the player can move them out to show information on the board or player area.

The dialogs are created on the dialogCanvas which keeps them above the rest of the board elements. I normally have a prefab called the playerDialog that has the PlayerDialogGUI behavior attached. When the PlayerAreaGUI initializes, it instantiates the prefab on the “dialogCanvas”. Add the tag “dialogCanvas” and set the “dialogCanvas” Tag to “dialogCanvas”. This tag is how the PlayerGUI script finds the canvas in the scene.

For “No Thanks!” I’ll put the place ribbon and the turn marker in the dialog. The place ribbon is shown at the end of the game. The turn marker will use used to both mark the current player, and as a button to take the current card and chips.

To create the player dialog prefab, make an empty object the same size and position as the player area.  Add the behavior “PlayerDialogGUI” to this object.

The “PlayerDialogGUI” expects a ribbon object and a help dialog object. These are elements that I use in almost every game, so I leave the code to control them in the re-use. This game wont have a help dialog, so simply create an empty object as a child of the playerDialog and assign it to the helpDialog member.

For the ribbon, create an image with a ribbon sprite and position it in a reasonable place. Add the “Draggable” behavior so that the player can move it.

More about the Draggable behavior

By adding “Draggable” to any object, that object responds to drag events and follows the cursor/finger. I added to any object that the player should be able to move around freely. Typically these are dialog windows and the place ribbon because the player may want to see information obscured by these objects.

The “Draggable” behavior can be copied and customized to handle start/end drag events if you want something to happen based on when/where an object is dragged.

Finally, add another Image object as a child of the PlayerDialog for the turn marker. Use the present sprite. Eventually we will add a button behavior to this object so the player can tap the present icon to accept the current card and chips.

Drag the “playerDialogs” to the prefab directory and then move it down below the visible area of the screen. I leave a copy of the “playerDialogs” out of the way on the screen because I usually have to modify it frequently during development.

Step 9 – Create the Center UI

Follow along with the video.

In the center of the screen I need to show the gift card deck, the current card that players are bidding on and the number of chips already bid.

Create a container for all of these elements. Putting a set of objects into a parent container keeps the hierarchy cleaner and allows you to move and rotate a group of objects together. Make this object 200×175

Create an image object and name it “deck”. Set the sprite to the card back. Set the size to 96×128 (the same size we used for a card in the player area). Set the position and anchor to the upper left (use the ALT key).

Add a TextMesh child of the deck and make it white with a black outline by dragging the outline material that we created while making the title onto the TextMesh behavior. Set the font size as large as possible while still fitting the text “(33)” on the card. Make the text centered on the card.

Drag a card prefab object from the Prefab directory onto the center container. Set its position and anchor to upper right and hide the “scoreNumber” child.

Add an Image object, name it “chips” and give it the Chip sprite. Position it in the bottom right. Give it a child TextMesh object named “count”. Make the text black and large enough to show “22”. Why 22?

I use two or three for all placeholder text because it is typically the largest digit in a non-fixed-width font. It is always a good idea to change the text to a whole range of numbers to see what it will look like with a variety of valid values. But when picking the font size, the important thing is to keep the largest value from overflowing.

I’ll also add a “deck description” element to the center. This will be a TextMesh object positioned in the bottom left with the text “3-35 with 9 cards removed”. This is just a helpful reminder of how we built the deck at the start of the game.

Finally, move the parent object to the center of the screen and set its rotation to -90.

The “drawnCard” child is blue because it is a prefab. I’m using the same card prefab in both the center and in the player areas. If I want to change how the card looks, I can change it in the center and “apply” those changes to the prefab so that the player areas will display it the same way.

Step 10 – Add code to model the game and draw the UI

Follow along with the video.

Now that we have the UI laid out, it is time to add code to draw the UI based on the state of the game.

Game Model

Before I can write code to draw the UI, I need to add code to model the state of the game.

State that we need for “No Thanks!”:

  • Game state (state that is non-player specific)
    • The shuffled deck of gift cards
    • The current card being bid on.
    • The number of chips already bid.
  • Player state
    • Number of chips remaining.
    • Gift cards taken.

To model this state, I’ll add code to the re-use classes Game.cs and Player.cs

For Game.cs, I’m going to model both the deck of cards and the current card as a single List<int>. The only important thing about a card is its value, so I’m not going to make a class to represent a card. And there is always a card showing, so the first element of the list can be assumed to be the card we are currently bidding on. The number of chips in the center is just another int.

public List<int> GiftDeck = null;
public int NumCenterChips = -1;
public void init()
{
  // Reset any state here. When we undo, all the events are re-executed and the first event will
  // call this function to cleanup the old game state.
  GiftDeck = new List<int>();
  NumCenterChips = 0;
}

For  Player.cs, I’m also going to add a List<int> to represent the cards taken and an integer number of chips remaining. The PlayerGUI is also going to need to draw the total score, so I’ll add a function in the Player to calculate that:

public int NumChips = -1;
public List<int> AcceptedGifts = null;

public Player() {
  NumChips = 0;
  AcceptedGifts = new List<int>();

  for (int i = 0; i < (int)Statistic.NUM_TYPES; ++i)
    Statistics[(Statistic)i] = 0;
}
  public int totalScore()
  {
    int giftTotal = 0;
    int priorGift = -1;
    foreach ( int gift in AcceptedGifts )
    {
      if (gift != priorGift + 1)
        giftTotal += gift;
      priorGift = gift;
    }
    return NumChips - giftTotal;
  }

The totalScore() function assumes that the list of AcceptedGifts is sorted. I’ll also want to draw the cards in sorted order, so I’ll keep the AcceptedGifts list sorted:

public void TakeGift(int gift)
{
  AcceptedGifts.Add(gift);
  AcceptedGifts.Sort();
}
More about model init functions.

You may have noticed that the Game object has an init function while the Player object uses a constructor.

This is just an artifact about how a game is “reset” or initialized. The Game behavior is added to the Control object in the scene and persists during an undo/reset. So the EResetGame event, which is the first event in the timeline, calls Game.init() to reset the game state.

The Player isn’t a behavior and is created by the event system when processing the EAddPlayer events. So, when we reset and reprocess the events during an undo, the PlayerList is cleared and new Player objects are created.

EInitialize

Next, I’ll initialize the game model. This is handled by the EInitialize event which is executed after the game has been created and the players have been added.

For “No Thanks!”, I need to build and shuffle the deck, remove 9 cards (or keep 24) and give the players chips. The number of chips the players get depends on the number of players in the game.

public void initializeModel()
{
  Player startPlayer = PlayerList.Players.GetRandom();
  PlayerList.setOrderToClockwiseWithStartAt(startPlayer);
  Game.theGame.CurrentPlayer = startPlayer;
  Game.theGame.CurrentGameState = Game.GameState.PLAY;

  List<int> allCards = Enumerable.Range(3, 33).ToList();
  allCards.Shuffle();
  Game.theGame.GiftDeck = allCards.Take(24).ToList();
  Game.theGame.NumCenterChips = 0;

  foreach (Player player in PlayerList.Players)
  {
    if (PlayerList.Players.Count == 7)
      player.NumChips = 7;
    else if (PlayerList.Players.Count == 6)
      player.NumChips = 9;
    else
      player.NumChips = 11;
  }
}

Card UI

Since I need to draw cards in both the player area and the main UI, I’ll create a CardGUI class that I can add to the card prefab to handle drawing.

In Unity, create a new script in the Assets/Scripts/gui directory called CardGUI.

This class is going to need to set the three text elements to match the value and will need to hide the “score” text element for some cards.

public class CardGUI : MonoBehaviour {

  public TMPro.TextMeshProUGUI CenterNumber;
  public TMPro.TextMeshProUGUI ULNumber;
  public TMPro.TextMeshProUGUI Score;
  public void draw( int number, bool showScore)
  {
    CenterNumber.text = ULNumber.text = number.ToString();
    Score.text = showScore ? "-" + number : "";
  }
}

Switch back to Unity and add this behavior to the Card in the center. Drag in the three text objects. Hit “apply” to save these changes to the prefab.

Game UI

The GameGUI script is going to need to draw the number of cards in the deck, the number of chips in the center and the card being bid on.
I’ll add those as members of the GameGUI:

  public TMPro.TextMeshProUGUI DeckSizeText;
  public GameObject OfferedChips;
  public CardGUI OfferedGift;

I’m now ready to write the code to draw the center area. I’ll add this to the GameGUI.draw() function:

  public void draw()
  {
    LoadOverlay.SetActive(Game.theGame.CurrentGameState == Game.GameState.LOGIN);
    GameCanvas.SetActive(Game.theGame.CurrentGameState != Game.GameState.LOGIN);

    OfferedChips.GetComponentOnChild<TMPro.TextMeshProUGUI>("count").text = Game.theGame.NumCenterChips.ToString();
    OfferedChips.SetActive(Game.theGame.NumCenterChips > 0);

    DeckSizeText.text = "(" + (Game.theGame.GiftDeck.Count - 1) + ")";

    if ( Game.theGame.GiftDeck.Count > 0 )
    {
      OfferedGift.gameObject.SetActive(true);
      OfferedGift.draw(Game.theGame.GiftDeck[0], false);
    }
    else
      OfferedGift.gameObject.SetActive(false);

    foreach (PlayerGUI pad in theGameGUI.PlayerPads)
    {
      pad.draw();
    }
  }

The function GetComponentOnChild<T>(name) that I use to find the TextMesh child of the chips is a convenient, but error prone way to locate an object in the scene. It saves having to create a variable and drag in each UI element that the script code is going to modify. However, it then constrains the naming and potentially the organization of the hierarchy. If the name of the object doesn’t match, or is changed later, the code will break. Make sure that you use the same name in the script code as you used on the UI.

Switch back to Unity and link up the deck size text, the “chips” image and the “drawnCard” to the members of the GameGUI behavior on the “Control” object. Also add all the “PlayerPad” objects to the GameGUI’s member “Player Pads”. Remember that you can lock the inspector and drag all the objects in at once.

More about the draw() function

The draw() function provided by the GameGUI (and other UI classes) is called by the act() function of events. When an event happens in real time (as opposed to load or undo), the timeline controller calls the act() function right after the do() function. The do() function updates the model and the act() function updates the UI. The act() function will often do some kind of animation and play sounds. When the animation is complete, the act() function will call the draw() function of whatever UI element has been changed.

PlayerPad UI

Like the GameGUI, the PlayerGUI will need to modify several UI elements. It is going to need links to the total score text element, the number of chips, the card container and both the card and spacer prefabs.

  public TMPro.TextMeshProUGUI TotalScore;
  public GameObject Chips;
  public GameObject CardPrefab;
  public GameObject SpacerPrefab;
  public Transform CardContainer;

Next I’ll add the custom draw code. I’ll set the color of the Image to the player’s color so that the player area shows their color.
I’ll set both child text elements of the chip to the number of chips (this is the text on the chip itself and the text in the score calculation). I’ll set the total score text to the current score.

Then, the tricky part is to draw the card area. I don’t want to re-draw this region if nothing has changed, so I’ll keep the list of cards that I last drew and if it hasn’t changed, I’ll leave the area alone. However, I’m going to be lazy about how I draw this region when things have changed. I’d really just need to add the new card in the correct location, but I’ll just re-create the whole region. draw() isn’t something that is called every frame. Instead it is just called to update the UI when it changes due to a player action.

  bool _isAnimating = false;
  List<int> _drawnCards = new List<int>();
  public void draw()
  {
    if (_isAnimating) return;
    if (Player == null) return;

    // Set the player area to match the player color
    GetComponent<Image>().color = Player.solidColor();

    foreach (var text in Chips.GetComponentsInChildren<TMPro.TextMeshProUGUI>())
      text.text = Player.NumChips.ToString();

    TotalScore.text = "= "+Player.totalScore();

    if (Player.AcceptedGifts.Count != _drawnCards.Count)
    {
      CardContainer.gameObject.DestroyChildrenImmediate();
      _drawnCards = new List<int>(Player.AcceptedGifts);
      int priorGift = -1;
      foreach (int gift in _drawnCards )
      {
        if ( priorGift != -1 && gift > priorGift+1 )
        {
          GameObject spacer = Instantiate(SpacerPrefab);
          spacer.transform.SetParent(CardContainer, false);
        }
        GameObject card = Instantiate(CardPrefab);
        card.GetComponent<CardGUI>().draw(gift, gift > priorGift + 1);
        card.transform.SetParent(CardContainer, false);

        priorGift = gift;
      }
    }

    DialogGUI.draw();
  }

Switch back to Unity and link up the parts of the player area with the new members that we have created. Also set the “Player Dialog Prefab” by dragging in the “playerDialogs” from the prefab directory. Remember to apply the changes to the prefab.

Player Dialog UI

The re-use code for the PlayerDialogGUI will draw the ribbon when the game state is GAME_OVER, but I need to add code to draw the Gift Icon when it is the player’s turn:

  public GameObject GiftIcon;

  public void draw()
  {
    gameObject.SetActive(true);
    Ribbon.gameObject.SetActive(Game.theGame.CurrentGameState == Game.GameState.GAME_OVER);
    Ribbon.sprite = GameGUI.ribbonSprite(ParentGUI.Player.Place);

    GiftIcon.SetActive(Game.theGame.CurrentGameState == Game.GameState.PLAY &&
                       ParentGUI.Player == Game.theGame.CurrentPlayer);
  }

The last line in the draw() function will make the Gift icon visible if it is the current player’s turn and we are playing the game (not GAME_OVER or INIT).

More about game states

The re-use game class has a member variable and an enumeration for storing the state of the game. In complex games, single state variable can be replaced with a “state stack” to keep track of state like: TAKE_ACTION -> MARKET_ACTION -> SELL. In a very complex game, I’ll replace the enum with an actual state class to store state specific game state.

For this game, there are really only two states, PLAY and GAME_OVER. The EInitialize event puts the game in PLAY state. Eventually, another event will detect that the gift deck is exhausted and put the game in the GAME_OVER state.

Switch back to Unity and set the Gift Icon member of the Player Dialog GUI. Apply the change to the prefab.

Run

You should now be able to hit play and see the UI drawn to match the initial state of the game. Running with the MainMenu Canvas turned off and the GameCanvas turned on will automatically load the last game as though you started on the MainMenu and hit the “Resume” button.

If something isn’t working and you can’t figure it out. You can download a zip of my project at this point: NoThanks-UIBuilt

]]>
https://blog.chadweisshaar.com/2017/04/28/board-game-programming-tutorial-part-2/feed/ 1
Board game programming tutorial https://blog.chadweisshaar.com/2017/04/22/board-game-programming-tutorial/ https://blog.chadweisshaar.com/2017/04/22/board-game-programming-tutorial/#comments Sun, 23 Apr 2017 02:34:25 +0000 http://gator3305.temp.domains/~cweissha/blog/?p=1120 Continue reading "Board game programming tutorial"]]> Now that I have created several board game conversions in Unity, I thought that it might be useful to create a tutorial that describes the process that I go through to make a game.

In this tutorial, I’ll start with a common set of code from prior games and some basic art. I’ll create a new Unity project, import all the reuse code and plugins that I use, and make a complete game.

There is a set of videos to go along with this post which show all the steps that I perform in the Unity game builder.

I’ve also saved my Unity project at a few points along the way so that you can skip ahead or make sure that your project matches mine.

In this post, I’m going to cover all the reuse code and the reasoning behind it. I’ll setup the project, import all the plugins, scripts and assets that I’ll need. Then I’ll build the main menu.

In the next post, I’ll build the UI for the game itself and create the scripts for modeling the game. In the third and final part, I’ll add game control events and animations.

Step 0 – Unity

Download Unity version 5.6 (I’m using 5.6.0f3). Install the 64 bit version with Visual Studio 2017 and the windows desktop/standalone module. You wont need any of the other modules for this tutorial.

Step 1 – Reuse

It is useful to have a basic understanding of the way that we build games and of the common code that I import into a new project. To cover that background information, I’ve created a series of pages describing each part of the common code:

  • Touch – Describes multi-touch handling in Unity and passing touch events to the Unity UI
  • Utilities – Covers all the utility classes and functions that we’ve created over time
  • Game Model – The “Model” part of the Model/View/Controller pattern
  • View – The skeleton classes for running the GUI
  • Event System – The “Controller” part of the Model/View/Controller pattern
  • Events – The events that are common to all our games.

Step 2 – Import plugins and reuse

Follow along with the video

To start a new project, start Unity and create a new 2D project. Then add the DoTween (for animation) and TextMesh Pro (for better text) plugins. These are free assets on the Unity asset store and can be added to the project from the Unity editor.

I also usually add the UniExtender plugin. This asset is useful, but isn’t free, so I’m going to skip it for this tutorial (see the Utilities page for more detail).

Then add Newtonsoft.JSON, Windows Voice and all the reuse code to the project. You can download a zip of the Newtonsoft.JSON dll and my Windows Voice plugin here. Finally you can download the reuse code here. Drag these files into the Assets directory.

Step 3 – Art and Audio assets

Video at time: 02:40

The next thing that I do for any project is to collect the art assets that I am going to need for the project. I typically pull images from TheNounProject.com and other sources of free art assets. images.google.com has a filter for images that are tagged for reuse. I am also often able to re-use art and sound from other games.

For this tutorial, I’m going to keep the art to a minimum. I’ll import a standard button and panel image for the UI elements. I’ll re-use our standard place ribbons, a felt background, a playing card and a poker chip image from another card game I made. I found a wrapped present image from wikimedia that I’ll use for the icon.

For sound, I’ll grab a button click, shuffle, card flip and coin click sound from another game. I pulled an intro clip from a free Unity asset. My normal source for audio are a couple of Unity audio assets that we have bought along with sites like freesound.org and pond5.com. Often a google search for the specific sound and the term “free sound effect” works best.

For a normal project, collecting the images and audio that I want and tweaking/coloring/compositing them in Photoshop takes 8-10 hours.

Here is a zip of the Image and Audio directories that I’m going to start with. These should be placed under the Assets folder. ArtAssets

In the Unity editor, I need to setup the button, panel, card and ribbon images. They are sprite sheets and have multiple images in one file. Click on the Image in the Project panel and then change the “Sprite Mode” to “Multiple”. Hit “Apply” and bring up the Sprite Editor window. Select “Slice” then “Grid by Cell Count”. Enter the number of rows and columns and hit apply.

The Panel and Button images should also be setup as a 9-box. A 9-box or “sliced” image is a convenient way to use one image for many differently sized images.

9-box source sprite. The green lines divide the sprite into 9 regions: 4 corners, a top/botton/left/right edge and the center

When an Image is set to “sliced”, Unity will draw the four corners of the source sprite in the four corners of the new image. Then it will stretch the top and bottom horizontally to the size of the new image. The left and right are stretched vertically and the center is stretched both vertically and horizontally to fill the middle.

No special processing is needed for the audio.

After this step, your project window should now look like this:

Step 4 – Project settings

Video at time: 04:45

Edit the “Player” settings which is under the “Edit” menu. Set the Company and product names. Select the wrapped preset as the Icon. Under “Resolution and Presentation” set the “Display Resolution Dialog” to “Hidden by Default”. Turn off the splash screen.

We use subversion for version control and backups. Git works too. If you are going to use any version control software, you’ll want to limit the size of the diffs by storing everything in text.  In the “Editor” settings, change the “Version Control” to “Visible Meta Files” and the “Asset Serialization” to “Force Text”. To do your first “add” to version control, save the Scene, exit Unity, and add the “Assets” and “Project Settings” directories to your version control.

If you’d like to make sure that you have the same setup as I do, or would like to skip these first four steps, you can download a setup project: No-Thanks-Setup

Step 5 – Setup the Cameras and Canvases

Follow along with the video

Cameras

Change the default “Main Camera” to have a Size of 10 and a clipping plane of 1 to 100. Add an “Aspect Utility” behavior. The “Aspect Utility” will add black bars to the left and right or top and bottom if the actual screen resolution doesn’t match the desired 16:9 aspect ratio. (For more info, see Board Game Programming Tutorial – Utilities)

Duplicate this camera (CTRL-D) and call the new camera “animationCamera”. Change its “Clear Flags” to “Don’t Clear”. Remove the “Audio Listener” behavior since we only need one of those in the scene. Set the “Depth” to 1. This will cause the “animationCamera” to render on top of the “MainCamera”. Setting the “Clear Flags” to “don’t clear” keeps this camera from deleting anything rendered by the “MainCamera”

The animation Camera will render the animation canvas. Having separate cameras for the main canvas and the animation canvas allows the trail renderer which I use to highlight moving sprites to be drawn above the main GUI. In general, it can be tricky to use both 2D UI elements and 3D elements with the same camera. I’ve had a hard time getting the z-ordering correct if I try to use the same camera and canvases with different layer orders. It works best to have one camera for 2D and another for 3D if possible.

Canvases

Create a Canvas called MainMenuCanvas at the top level. Set it as screen space – camera, pixel perfect, distance of 10, scale with screen size, 1920×1080, screen match to expand. When you are done it should look like this:

Setting the “Render Mode” to “Screen Space – Camera” places the UI in the 3D world instead of on top of it. This allows 3D elements to be above the board. We always design for 1920×1080 because our target hardware is televisions and that is the standard HD resolution. 4K TVs are exactly twice that resolution, so if we use image assests that are twice as large as needed for 1920×1080, everything will look correct and twice as sharp on a 4K TV.

The first time that we create a canvas, Unity makes an EventSystem object. This object handles mouse and touch input and passes mouse and touch events to the UI elements. I don’t change anything in this object.

Duplicate this Canvas and name it “LoadCanvas”. Change the “Order in Layer” to 1. This will cause it to be rendered after the MainMenuCanvas, so it will always appear on top of the Main Menu. These canvases are only used for the main menu and are set inactive once a game is started or loaded.

Duplicate the MainMenuCanvas and name it GameCanvas. Duplicate the GameCanvas twice and name them DialogCanvas and AnimationCanvas. On the AnimationCanvas, change the “Render Camera” to “animationCamaera”. On the DialogCanvas, set the “Order in Layer” to 1. Now we have three canvases that will be used during the game. The main GameCanvas is rendered by the main camera and will display all the static pieces and the board. Above the board, the main camera will render the dialog canvas. Then, the AnimationCamera will render the AnimationCanvas. This will keep all dialogs above the board and all animations (which includes a 3D element) above everything.

Audio

The main camera had an audio listener behavior. This is half of the audio system. To play sounds, we need to add an Audio Source. To do that, create an empty game object at the top level and name it “Controller”. Add the re-use script called “Audio Player” to that object. Adding the audio player behavior will automatically add the dependent Audio Source behavior. (Read more about the AudioPlayer in Board Game Programming Tutorial- Utilities)

For now, set the number of clips to 2 and add the Intro to the first and the Click to the second. If you look at the AudioPlayer script, those are the labels that I have given to the first two elements of the clip enumeration. These clips are expected to exist. The Intro clip will be played when the game starts, and the Click sound is used for buttons on the main menu.

Step 6 – Main Menu

Follow along with the video

Background

The goal of the main menu in a game is to allow players to join the game and pick a color. I also allows players to choose game options or load a previous game.

For a touch table, the players are sitting around the screen which is laying horizontally. So some of the players are going to be looking at the screen “upside-down” from the developer’s perspective. This means that player controls need to be correctly oriented for where the player is sitting. It also means that the “preferred” orientation for common game elements may not be up and down on a monitor.

More about accommodating a flat screen

In some games, we have automatically rotated the board to be up for the current player. Lately, I’ve been orienting games so that most players are sitting at the top or bottom of the screen and the top of the game board is on the right side.

Another thing to keep in mind is that the standard “fake 3D” effects like drop shadow, bevel and emboss will not look right for players looking at the object upside down.

“No Thanks!” plays seven, so I’m going to arrange three player positions on the bottom and top of the screen and one position on the left. In the main game, I’ll have the current card and pile of chips in the center-right pointing toward the left side of the screen. I’ll put the “undo” and “exit” buttons on the right facing the left.

I always use a set of four buttons: Start, Resume, Load and Exit. Start begins the game and is grayed out until there are enough players. Resume loads the last saved game. Load brings up the load dialog which lists all the saved games.

If a game has options, I’ll put an option panel right on the main menu.

Player Colors

I will typically allow players to pick from a set of colors that work best with colors I’ve chosen for the game board and common pieces. I usually end up with players being able to choose between the primary and secondary colors (red, green, blue, yellow, purple, cyan). The if a game has more players I’ll add orange or brown.

Since I typically use the same colors for players, I’ve defined color names in PlayerList.cs and the color definitions in GameGUI.cs. The color names are used by the voice system to reference a player by their color. The MainMenu.cs script will use these color definitions to automatically color the color selection buttons.

I allow players to select their color from all the colors even if another player has chosen that color. The code gives the color to the selecting player and gives the old player a new color.

Controller

First lets setup the controller object. This object contains all the “singleton” type game control scripts. This includes the Windows Voice, Timeline, Game, PlayerList, MainMenu and GameGUI.

We can populate the members of the GameGUI now, and we’ll populate the MainMenu members after we have finished laying out the UI.

For the GameGUI, we can drag in the canvases that we created in the last step and drag in the ribbon images. The Load Overlay member is an Object to show while the GameUI is being drawn. For now, create an Image under the dialogCanvas that is black and fills the screen. Set it inactive and add it to the GameGUI. We’ll do the PlayerPads and Game Pieces when we start on the main game UI.

UI Elements

Next lets create the UI elements that make up the main menu.

  • Background is an image that fills the whole canvas and has the felt background image.
  • Title is a large TextMesh UI element with the name of the game. Normally I’d have an image for the title, but here I made a light gradient and an outline on text for a simple title.
    • I also gave the title text an outline. To do that, first create a new material and name it outline. If you don’t make a new material, all TextMesh objects will be outlined. Then turn on the outline and set a thickness. Then set the “Dialate” under “Face” to 0.15 to make the text thicker. 
  • 4 buttons. All the buttons use the RectButton image in “sliced” mode with a transition of “sprite swap” to the grey RectButton image for the inactive state. All the buttons have an OnClick callback to the MainMenu object. The MainMenu re-use class already has the callback functions defined. The buttons have a TextMesh child element with the name of the button.
  • Player areas. The player area is going to be a Prefab so that changes made to one can be quickly replicated to the other player positions. Make the first player area by creating an Image element with the rounded “borders_0” image and a size of 600×200
    • Add the PlayerLoginArea behavior to the top level player area.
    • Add a child element called “joinArea”. This can be an empty element. Set it to take up the top half of the login area. Add a button behavior and set the transition to “None”. Add an OnClick callback and have it call PlayerLoginArea.OnJoinClicked
      • Give it a child TextMesh element on the left with the text “Tap here to join”.
      • And a child Image element on the right called “chosenColor” with the chip image.
    • Add another child element called “colorButtons” to the player area. This can also be an empty element and should take up the bottom half of the area. Give it a horizontal layout so that the child chip images are laid out for us.
      • Set the left and right padding to 8 and turn on control child size width.
      • Add seven child Button elements with the chip image and turn on “Preserve Aspect”. They should each an OnClick of PlayerLoginArea.OnColorClicked. This function takes a parameter and it should be an index from 0 through 7.
    • Setup the PlayerLoginArea behavior by dragging in the “Controller” object as MainMenu, the “chosenColor” in the “joinArea” for Chosen Color, and the TextMesh in the “joinArea” for the “Join Text”. Set the position to 0.
    • Now make the player area that you’ve created a prefab by creating a Prefab directory under the Assets directory and dragging the player area into the Prefab directory.
    • Position the player area in the bottom right corner. Duplicate it twice and put #1 in the bottom center and #2 in the bottom right. Duplicate it again, set the rotation to Z=-90 and put it on the left. Duplicate the first three area, set their rotation to Z=180 and flip the sign of their PosY. This should arrange them on the top of the screen.
    • Set the Position value in the PlayerLoginArea behavior on all the areas. The first should be 0 and the rest should go up by one each (matching the autogenerated number in their name).

MainMenu behavior

Finally, lets populate the members of the MainMenu behavior by dragging in the UI elements that we have created. Drag in the LoadCanvas, MainMenuCanvas, StartButton, and ResumeButton. For now we will skip the credits dialog and saved game entry prefab. Add the player areas by clicking the lock button on the inspector, then selecting all seven player areas in the hierarchy and dragging them onto the “PlayerLoginAreas” member.

Before the game will run, de-activate the GameCanvas and LoadCanvas. At this point we can press play and test out our main menu. When the game starts up, the MainMenu.Start() function is called. This code activates the “Resume” button if there is a saved game and plays the Intro music. It also has the player areas draw themselves. The player areas fill in the available colors from the defined colors in GameGUI.

When a player clicks on the join area or on a colored join button, the main menu assigns colors based on the request and draws the affected player areas. It then sets the Start button active/inactive based on how many players have joined and the minimum number of players defined in PlayerList.cs. For this to work, bring up Visual Studio by double clicking on Assets/Scripts/model/PlayerList.cs and set MIN_PLAYER to 3 and MAX_PLAYERS to 7.

When the Start button is clicked, the MainMenu stops the intro music, de-activates the MainMenuCanvas, tells the GameGUI to start the game, adds EAddPlayer events to the timeline for each joined player, and an EInitialize event to the timeline, then finally saves the game. The EAddPlayer events hold the player’s position at the table and color. (Read more about the event system in Board Game Programming Tutorial – Event System)

The GameGUI sets the GameCanvas to visible. When the GameCanvas becomes visible, Start() functions for all the UI elements on that canvas are called. When the timeline processes the EInitialize event, the game model is setup and the game UI is drawn.

If you’d like to make sure that your project matches mine, you can download my project after completing the main menu. NoThanks – MainMenu

]]>
https://blog.chadweisshaar.com/2017/04/22/board-game-programming-tutorial/feed/ 2