# Project scaffold · Tic Tac Toe on Chromia with Rell and Unity · Learn Chromia

> You will build the skeleton of the Tic Tac Toe game in Unity here. The game mechanics are mocked for now, so the focus is on layout and design.

Source: https://chromia.com/learn/tic-tac-toe/module-two/scaffold/

Tic Tac Toe on Chromia with Rell and UnityLesson 17 of 20

1. [01Tic Tac Toe on Chromia with Rell and Unity](https://chromia.com/learn/tic-tac-toe/introduction/)
2. [02Set up your project](https://chromia.com/learn/tic-tac-toe/setup/)

Module 1: A Rell backend with FT4 accounts

1. [03Overview](https://chromia.com/learn/tic-tac-toe/module-one/)
2. [04Part 1: Database design, operations and queries](https://chromia.com/learn/tic-tac-toe/module-one/data-modeling/)
3. [05Design the data model](https://chromia.com/learn/tic-tac-toe/module-one/data-modeling/tables/)
4. [06Perform basic operations](https://chromia.com/learn/tic-tac-toe/module-one/data-modeling/basic-operations/)
5. [07Write basic queries](https://chromia.com/learn/tic-tac-toe/module-one/data-modeling/write-queries/)
6. [08Part 2: Set up accounts](https://chromia.com/learn/tic-tac-toe/module-one/create-accounts/)
7. [09Configure FT4 accounts](https://chromia.com/learn/tic-tac-toe/module-one/create-accounts/install-configure-ft4/)
8. [10Authentication with FT4 accounts](https://chromia.com/learn/tic-tac-toe/module-one/create-accounts/authentication/)
9. [11Update tests](https://chromia.com/learn/tic-tac-toe/module-one/create-accounts/update-tests/)
10. [12Part 3: How the dapp project is organized](https://chromia.com/learn/tic-tac-toe/module-one/project-structure/)
11. [13Work with Rell modules](https://chromia.com/learn/tic-tac-toe/module-one/project-structure/modules/)
12. [14Add modules to your dapp](https://chromia.com/learn/tic-tac-toe/module-one/project-structure/incorporate-modules/)

Module 2: The Unity project

1. [15Overview](https://chromia.com/learn/tic-tac-toe/module-two/)
2. [16Set up the project](https://chromia.com/learn/tic-tac-toe/module-two/setup/)
3. [17Project scaffold](https://chromia.com/learn/tic-tac-toe/module-two/scaffold/)
4. [18Link the client to the blockchain](https://chromia.com/learn/tic-tac-toe/module-two/connecting-the-client/)
5. [19Summary and manual testing](https://chromia.com/learn/tic-tac-toe/module-two/summary-and-tests/)

1. [20What next?](https://chromia.com/learn/tic-tac-toe/what-next/)

[Learn](https://chromia.com/learn/)[Tic Tac Toe on Chromia with Rell and Unity](https://chromia.com/learn/tic-tac-toe/)

17 / 20Module 2: The Unity project

# Project scaffold

You will build the skeleton of the Tic Tac Toe game in Unity here. The game mechanics are mocked for now, so the focus is on layout and design.

## Overview

The game has three main parts:

1. **Game board**: a 3x3 grid where players place their marks.
2. **UI elements**: one button per grid cell, plus a text display that shows the game status.
3. **Game logic**: scripts that track whose turn it is, detect a winner, and maintain the game state.

## Set up the basic layout

### Pages

1. **Login page**
   * Lets users either register a new account or sign in with an existing one.

     ![Login](/learn/media/login-58836171b0e9fc9dc3817e4bd07939a9.png)
2. **Lobby page**
   * Lists matches that are in progress and matches that have finished.
   * Lets users start a new match or join one.

     ![Lobby](/learn/media/lobby-2e54efee12ee745e4a954c0e2d9d4a43.png)
3. **Game page**
   * Where the Tic-Tac-Toe match itself takes place.
   * Contains the game board, the UI elements, and live updates as the game progresses.

     ![Game](/learn/media/game-4bfee7d8f9104396b6a176d90bdf4dc0.png)

### Set up the `ServiceLocator`

The `ServiceLocator` is a single place where the game's core services and managers are created and held. Other classes get those services from it instead of instantiating them repeatedly, which keeps the code modular.

#### Steps to set up the `ServiceLocator`:

1. **Create a `GameObject`:**

   * Add an empty `GameObject` to your Unity scene.
   * Give it a clear name, such as `ServiceLocator`.
   * Attach the `ServiceLocator` script to it.
2. **Set script execution order:**

   * Open **Edit → Project Settings → Script Execution Order**.
   * Add the `ServiceLocator` script and drag it to the top of the list.
   * Placing it first means it initializes before anything else, so its services are ready whenever other components request them.

     ![Service](/learn/media/service-07e6ae6da2619ff075d6da69197b1f72.png)
3. **Understand the role of `ServiceLocator`:**

   * Open the `ServiceLocator` script and look at what it contains. It creates instances of services such as `BlockchainService`, `GameService`, and `UIManager`, and the script's comments describe each one. A short summary follows:

#### Services in `ServiceLocator`

1. **`UIManager`:**

   * Controls every UI element in the game, including menus, notifications, and interactions with the player interface.
   * Because of the `[SerializeField]` attribute, you can assign the `UIManager` in the Unity editor.

   csharp

   ```nolines
   [field: SerializeField] public UIManager UIManager { get; private set; }
   ```
2. **`LocalStorageService`:**

   * Stores data on the player's device and reads it back.
   * Typical examples are player progress, game settings, and cached data.
   * You create it inside the `SetupServices()` method:

   csharp

   ```nolines
   LocalStorageService = new LocalStorageService();
   LocalStorageService.Initialize();
   ```
3. **`BlockchainService`:**

   * Takes care of blockchain operations, such as connecting to a blockchain network and sending transactions.
   * Its initialization can require network calls, so it runs asynchronously (`await BlockchainService.Initialize()`).

   csharp

   ```nolines
   BlockchainService = new BlockchainService();
   await BlockchainService.Initialize();
   ```
4. **`GameService`:**

   * Holds the core game logic and works with other services, for example using the blockchain to persist game state or submit transactions.
   * It requires the `BlockchainService`, which you pass into its constructor when you create it:

   csharp

   ```nolines
   GameService = new GameService(BlockchainService);
   ```

## Create the game board

Use the following steps to start building the UI for your Tic Tac Toe game.

#### Set up the `UIManager`

First, create a `UIManager` to control the game's views (pages). It switches between views and acts as the parent container for all UI elements.

* **What it does**:
  The `UIManager` contains several views, such as the login screen, the game board, and result screens. It reacts to events like button clicks to move from one page to another.
* **Example**:
  A method in the `LoginView` could look like this:

  csharp

  ```
  public async void OnNewAccountButton()
  {
      var blockchainService = ServiceLocator.Instance.BlockchainService;
      var newAccountData = await blockchainService.OpenStrategyRegisterAccount();
  }
  ```

  Here, the `BlockchainService` is used to create a new blockchain account.

#### Visualize the structure

* Your UI hierarchy should resemble the following:

  ![UI hierarchy](/learn/media/structure-0b184b0fdde657efb7dd61dbd7a43aaf.png)
* Each view has a script containing the logic for its UI interactions, for example:

  ![Scripts](data:image/png;base64,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)

## Set up the game grid in Unity

1. #### Create an object

   * In the `Editor` Hierarchy, add an empty `GameView` object as a child of the `UIManager`.
2. #### Add a `Canvas`

   * Create a new `Canvas` from the **Hierarchy** panel. All of your UI elements will sit under it.
   * Set the **Canvas** render mode to **Screen Space - Overlay**, the usual choice for a standard UI.
3. #### Add a `GridPanel`

   * Add a `Panel` inside the `Canvas` and rename it `GridPanel`.
   * Resize it to cover the area the game grid should occupy.

### Add a grid layout

1. Select `GridPanel` in the **Hierarchy**.
2. In the **Inspector**, add a **Grid Layout Group** component.
3. Configure the Grid Layout Group as follows:
   * **Cell Size**: (100, 100), so every cell is the same size.
   * **Spacing**: (5, 5), to leave a gap between cells.
   * **Constraint**: select **Fixed Column Count** and set **Constraint Count** to `3`, which produces a 3x3 grid.

### Create buttons for the grid cells

1. Add a `Button` under `GridPanel` and rename it "Cell1".
2. Size the button to fit a cell (for example, 100x100).
3. Duplicate it eight times so you have nine buttons in total.
4. Give the copies sequential names ("Cell2", "Cell3", and so on).

### Add a status text

1. Add a `Text` element to the **Canvas** and place it above `GridPanel`.
2. Rename it `StatusText` and give it a starting value such as **"Player X's Turn"**.
3. Tweak the font size, alignment, and color so it is easy to read.

## Connect scripts to the UI

1. Write a script, such as `GameView`, that connects the UI to the game logic.
2. Give it methods that refresh the game grid and manage player turns.
3. Attach the script to an empty `GameObject` called `GameView`.

## Set up Tic Tac Toe game scripts in Unity

### Create the scripts folder

* Right-click in the **Project** panel and choose **Create > Folder**.
* Call it `Scripts` so your game scripts stay in one place.

### Game logic overview

* **`GameView` script**: acts as the game's UI layer. It responds to button clicks and calls the `MakeMove()` method on the `GameService`.
* **`GameService` script**: queries games, submits turns, and keeps track of the state of active games.

### `GameService` and `GameData`

The `GameService` keeps track of active games and their state, running queries and operations such as turn submission. Every game it fetches is stored as a `GameData` model:

csharp

```
/// <summary>
/// The data received from the blockchain queries related to games.
/// </summary>
public class GameData
{
    [JsonProperty("id")] public int RowId { get; set; }

    [JsonProperty("created_by")] [CanBeNull] public byte[] CreatedBy { get; set; }
    [JsonProperty("created_at")] public string CreatedAt { get; set; }

    [JsonProperty("player1_account_id")] [CanBeNull] public byte[] Player1 { get; set; }
    [JsonProperty("player2_account_id")] [CanBeNull] public byte[] Player2 { get; set; }

    [JsonProperty("last_step_account_id")] [CanBeNull] public byte[] LastStepBy { get; set; }
    [JsonProperty("last_step_at")] public string LastStepAt { get; set; }

    [JsonProperty("winner")] [CanBeNull] public byte[] Winner { get; set; }
    [JsonProperty("status")] public GameStatus Status { get; set; }

    [JsonProperty("slot_1")] public SlotState Slot1 { get; set; }
    [JsonProperty("slot_2")] public SlotState Slot2 { get; set; }
    [JsonProperty("slot_3")] public SlotState Slot3 { get; set; }
    [JsonProperty("slot_4")] public SlotState Slot4 { get; set; }
    [JsonProperty("slot_5")] public SlotState Slot5 { get; set; }
    [JsonProperty("slot_6")] public SlotState Slot6 { get; set; }
    [JsonProperty("slot_7")] public SlotState Slot7 { get; set; }
    [JsonProperty("slot_8")] public SlotState Slot8 { get; set; }
    [JsonProperty("slot_9")] public SlotState Slot9 { get; set; }
}
```

### Update slots in `GameView`

Every grid cell has a `TicTacToeSlot` component. The `UpdateSlots` method in the `GameView` script refreshes these slots:

csharp

```
private void UpdateSlots()
{
    var canUserSubmit = _gameData.Status is GameStatus.Active && Buffer.From(_gameData.LastStepBy) != _blockchainService.UserAccountId;

    _slots[0].SetData(_gameData.Slot1, canUserSubmit, (isSelected) => OnSelected(isSelected, 0));
    _slots[1].SetData(_gameData.Slot2, canUserSubmit, (isSelected) => OnSelected(isSelected, 1));
    _slots[2].SetData(_gameData.Slot3, canUserSubmit, (isSelected) => OnSelected(isSelected, 2));
    _slots[3].SetData(_gameData.Slot4, canUserSubmit, (isSelected) => OnSelected(isSelected, 3));
    _slots[4].SetData(_gameData.Slot5, canUserSubmit, (isSelected) => OnSelected(isSelected, 4));
    _slots[5].SetData(_gameData.Slot6, canUserSubmit, (isSelected) => OnSelected(isSelected, 5));
    _slots[6].SetData(_gameData.Slot7, canUserSubmit, (isSelected) => OnSelected(isSelected, 6));
    _slots[7].SetData(_gameData.Slot8, canUserSubmit, (isSelected) => OnSelected(isSelected, 7));
    _slots[8].SetData(_gameData.Slot9, canUserSubmit, (isSelected) => OnSelected(isSelected, 8));
}
```

### Implement the `GameView` script

The `GameView` processes player input and refreshes the game board:

csharp

```
using System.Collections.Generic;
using _TicTacToe.Scripts.Models;
using Chromia;
using TicTacToe.Services;
using TicTacToe.Utils;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

namespace TicTacToe.UI
{
    /// <summary>
    /// The main game view where users play Tic-Tac-Toe and can submit turns.
    /// </summary>
    public class GameView : MonoBehaviour
    {
        [SerializeField] private TMP_Text _header;
        [SerializeField] private TMP_Text _organizerText;
        [SerializeField] private TMP_Text _lastActionAtText;
        [SerializeField] private TMP_Text _currentTurnText;
        [SerializeField] private TMP_Text _player1;
        [SerializeField] private TMP_Text _player2;
        [SerializeField] private TMP_Text _gameStatus;
        [SerializeField] private List<TicTacToeSlot> _slots;
        [SerializeField] private GameObject _submitButton;
        [SerializeField] private Button _refreshButton;

        private UIManager _uiManager;
        private GameService _gameService;
        private BlockchainService _blockchainService;

        private GameData _gameData;
        private int _slotToSubmit = -1;

        private void OnEnable()
        {
            _uiManager ??= ServiceLocator.Instance.UIManager;
            _gameService ??= ServiceLocator.Instance.GameService;
            _blockchainService ??= ServiceLocator.Instance.BlockchainService;
        }

        public void SetData(GameData gameData)
        {
            _gameData = gameData;
            UpdateView();
        }

        public void OnBackButton()
        {
            _uiManager.OpenLobby();
        }

        public void OnRefreshButton()
        {
            Refresh();
        }

        public async void OnSubmitButton()
        {
            if (_slotToSubmit < 0)
                return;

            _uiManager.ToggleLoadingSpinner(true);
            var success = await _gameService.MakeMove(_gameData.RowId, _slotToSubmit);
            _uiManager.ToggleLoadingSpinner(false);

            if (success)
               Refresh();
        }

        private async void Refresh()
        {
            _uiManager.ToggleLoadingSpinner(true);
            _gameData = await _gameService.GetGameById(_gameData.RowId);
            _uiManager.ToggleLoadingSpinner(false);

            UpdateView();
        }

        private void UpdateView()
        {
            _header.text = $"Match: {_gameData.RowId}";

            _organizerText.text = $"Organizer: <b>{UIUtils.GetShortenedAccountId(_gameData.CreatedBy)}";
            _lastActionAtText.text = $"Last update: <b>{UIUtils.GetUnixTimestamp(_gameData.LastStepAt)}";
            _currentTurnText.text = $"Turn: {UIUtils.GetTurn(_gameData)}";

            var isUserPlayer1 = Buffer.From(_gameData.Player1) == _blockchainService.UserAccountId;
            _player1.text = $"1. {UIUtils.GetShortenedAccountId(_gameData.Player1)} ( <b>{(isUserPlayer1 ? "You" : "Opponent")} ) <color=purple> O";
            _player2.text = $"2. {UIUtils.GetShortenedAccountId(_gameData.Player2)} ( <b>{(isUserPlayer1 ? "Opponent" : "You")} ) <color=purple> X";

            _gameStatus.text = UIUtils.GetGameStatus(_gameData, _blockchainService.UserAccountId);

            _submitButton.SetActive(false);
            _slotToSubmit = -1;

            _refreshButton.interactable = _gameData.Status is GameStatus.Active;

            UpdateSlots();
        }

        private void UpdateSlots()
        {
            var canUserSubmit = _gameData.Status is GameStatus.Active && Buffer.From(_gameData.LastStepBy) != _blockchainService.UserAccountId;

            _slots[0].SetData(_gameData.Slot1, canUserSubmit, (isSelected) => OnSelected(isSelected, 0));
            _slots[1].SetData(_gameData.Slot2, canUserSubmit, (isSelected) => OnSelected(isSelected, 1));
            _slots[2].SetData(_gameData.Slot3, canUserSubmit, (isSelected) => OnSelected(isSelected, 2));
            _slots[3].SetData(_gameData.Slot4, canUserSubmit, (isSelected) => OnSelected(isSelected, 3));
            _slots[4].SetData(_gameData.Slot5, canUserSubmit, (isSelected) => OnSelected(isSelected, 4));
            _slots[5].SetData(_gameData.Slot6, canUserSubmit, (isSelected) => OnSelected(isSelected, 5));
            _slots[6].SetData(_gameData.Slot7, canUserSubmit, (isSelected) => OnSelected(isSelected, 6));
            _slots[7].SetData(_gameData.Slot8, canUserSubmit, (isSelected) => OnSelected(isSelected, 7));
            _slots[8].SetData(_gameData.Slot9, canUserSubmit, (isSelected) => OnSelected(isSelected, 8));
        }

        private void OnSelected(bool isSelected, int index)
        {
            _submitButton.SetActive(isSelected);

            if (isSelected)
            {
                _slotToSubmit = index + 1; // Blockchain expects values from 1 to 9

                // Unselect all other slots
                for (int i = 0; i < _slots.Count; i++)
                {
                    if (i == index) continue;
                    _slots[i].SetSelected(false);
                }
            }
        }
    }
}
```

### Set up the game logic

1. **Attach the `GameView` script**:
   * Add an empty `GameObject` in the **Hierarchy** and name it `GameView`.
   * Drag the `GameView` script onto it.
   * In the **Inspector**, fill in the `cells` array (the grid buttons) and the `statusText` field.
2. **Complete the win logic**:
   * Write the `CheckForWinner` method so it recognizes winning combinations and updates the game state.

## Test and refine

1. **Run the game**:
   * Press **Play** in Unity to try the setup.
   * Confirm that the grid updates, turns alternate, and the status text changes.
2. **Debug**:
   * Fix any problems you find in the game mechanics.
3. **Enhance**:
   * Improve gameplay with additions such as animations, sound effects, or a restart button.

Written for this site from Chromia’s public course. Code samples are the course’s own.

[← Previous**Set up the project**](https://chromia.com/learn/tic-tac-toe/module-two/setup/)[Next →**Link the client to the blockchain**](https://chromia.com/learn/tic-tac-toe/module-two/connecting-the-client/)

On this page

* [Overview](#overview)
* [Set up the basic layout](#set-up-the-basic-layout)
* [Create the game board](#create-the-game-board)
* [Set up the game grid in Unity](#set-up-the-game-grid-in-unity)
* [Connect scripts to the UI](#connect-scripts-to-the-ui)
* [Set up Tic Tac Toe game scripts in Unity](#set-up-tic-tac-toe-game-scripts-in-unity)
* [Test and refine](#test-and-refine)
