no thanks – The Industrious Squirrel https://blog.chadweisshaar.com Thu, 04 May 2017 01:18:48 +0000 en-US hourly 1 https://wordpress.org/?v=6.8.3 https://blog.chadweisshaar.com/wp-content/uploads/2016/07/favicon.png no thanks – 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 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