The creator-centric platform for building live games in Unity

Easily add social, commerce, and content management features to your live game with low-code, drag-and-drop prefabs fully integrated with your Unity workflow.

Chat (GPW) - Sample

Demonstrates Beamable's Chat feature, and more!

Overview

Welcome to "Global Price War" (GPW). This downloadable sample game project showcases the Beamable Chat FeatureFeature - An individual aspect of the Beamable product used to create a great user experience.

Buy low, sell high! Finish rich and be top on the Leaderboard.

This documents how to understand and apply the benefits of chat in game development. Or watch this video.

Download

These learning resources provide a better way to build live games in Unity.

Source

Detail

  1. Download the Chat GPW Sample Project
  2. Open in Unity Editor ( Version 2020.3.11f1 )
  3. Open the Beamable Toolbox
  4. Sign-In / Register To Beamable. See Step 1 - Getting Started for more info
  5. Rebuild the Unity Addressables : Unity → Window → Asset Management → Groups, then Build → Update a Previous Build
  6. Open the Scene01Intro Scene
  7. Play The Scene: Unity → Edit → Play
  8. Click the "Start" Button
  9. Enjoy!

Note: Supports Mac & Windows and includes the Beamable SDK

Rules of the Game

  • 1 player starts the game with limited turns
  • After the final turn, the game is over
  • The player's final score is calculated as Cash + Bank - Debt
  • Each turn, the player makes decision to increase the total cash
    • Use money in Cash. Buy items at a low price, Sell items at a high price
    • Move money into the Bank. The bank pays interest to the player after each turn
    • Move money out of Debt. The debtors charge interest to the player after each turn
    • Chat with other players to better understand the market price of items

Pro Tip: Sell all owned items before the final turn to increase the final score.

Screenshots

Scenes

Scene01Intro

Scene02Game

Scene03Chat

Scene04Leaderboard

Design

Many Beamable features are used to implement the game design and game rules.

Features

📘

Related Features

More details are covered in related feature page(s).

Chat - Allow players to communicate in-game
Connectivity - Indicates status of network connection availability
Content - Allow game maker to store project-specific data
Inventory - Allow player to manage inventory
Leaderboards - Allow player to manage leaderboard

Content

Using the Beamable Content feature, this game stores core data with ease and flexibility.

Remote Configuration

Location

Product

Class Organization

Each scene uses the GPWController class to interact with the local data model and the remote services.

Here is a high-level chart showing the partial structure.

Development

Here is an overview of project organization.

  • Project Window - Suggested entry points for game makers include the Scenes folder and Scripts folder
  • Project Configuration - The Configuration class holds settings used throughout the game. Game makers can adjust settings at runtime or edit-time
  • Project Scene (Chat) - The Scene03Chat.unity scene holds all chat-related functionality

Project Window

Project Configuration

Chat Scene

Code

The core chat functionality is centralized in the sample game project's GameServices class.

Here are some highlights of the Beamable SDK's ChatService class.

Subscribe to Chat Changes

IBeamableAPI beamableAPI = await Beamable.API.Instance;
ChatService chatService = beamableAPI.Experimental.ChatService;
chatService.Subscribe(ChatService_OnChanged);

Handle Chat Changes

private void ChatService_OnChanged(ChatView chatView)
{
    _chatView = chatView;
    foreach (RoomHandle roomHandle in _chatView.roomHandles)
    {
        if (IsLocalPlayerInRoom(roomHandle.Name))
        {
            roomHandle.OnMessageReceived -= RoomHandle_MessageReceived;
            roomHandle.OnMessageReceived += RoomHandle_MessageReceived;
        }
    }
}

Send Chat Message

IBeamableAPI beamableAPI = await Beamable.API.Instance;
ChatService chatService = beamableAPI.Experimental.ChatService;
_chatService.SendMessage("MyRoomName", "HelloWorld!");

Embed

The Scene03ChatManager.cs is the main entry point to the chat scene logic. It relies on the GameServices class for the chat-related operations.

using System;
using System.Text;
using System.Threading.Tasks;
using Beamable.Common.Api;
using Beamable.Experimental.Api.Chat;
using Beamable.Samples.Core.Data;
using Beamable.Samples.Core.UI;
using Beamable.Samples.GPW.Data.Storage;
using Beamable.Samples.GPW.Views;
using UnityEngine;

namespace Beamable.Samples.GPW
{
   /// <summary>
   /// Handles the main scene logic: Chat 
   /// </summary>
   public class Scene03ChatManager : MonoBehaviour
   {
      //  Properties -----------------------------------
      public Scene03ChatUIView Scene03ChatUIView { get { return _scene03ChatUIView; } }

      //  Fields ---------------------------------------
      [SerializeField]
      private Scene03ChatUIView _scene03ChatUIView = null;
      
      //  Unity Methods   ------------------------------
      protected async void Start()
      {
         
         // Clear UI
         _scene03ChatUIView.ScrollingText.SetText("");
         _scene03ChatUIView.ScrollingText.HyperlinkHandler.OnLinkClicked.AddListener(HyperlinkHandler_OnLinkClicked);
         
         // Top Navigation
         _scene03ChatUIView.GlobalChatToggle.onValueChanged.AddListener(GlobalChatButton_OnClicked);
         _scene03ChatUIView.LocationChatToggle.onValueChanged.AddListener(LocationChatButton_OnClicked);
         _scene03ChatUIView.DirectChatToggle.onValueChanged.AddListener(DirectChatButton_OnClicked);
         SetChatMode(ChatMode.Global);
         
         // Input
         _scene03ChatUIView.ChatInputUI.OnValueSubmitted.AddListener(ChatInputUI_OnValueSubmitted);
         _scene03ChatUIView.ChatInputUI.OnValueCleared.AddListener(ChatInputUI_OnValueCleared);

         // Bottom Navigation
         _scene03ChatUIView.BackButton.onClick.AddListener(BackButton_OnClicked);
         
         // Load
         _scene03ChatUIView.DialogSystem.DelayBeforeHideDialogBox =
            (int)_scene03ChatUIView.Configuration.DelayAfterDataLoading * 1000;
         
         await ShowDialogBoxLoadingSafe();
         SetupBeamable();
      }


      //  Other Methods  -----------------------------
      private async void SetupBeamable()
      {
         // Setup Storage
         GPWController.Instance.PersistentDataStorage.OnChanged.AddListener(PersistentDataStorage_OnChanged);
         GPWController.Instance.RuntimeDataStorage.OnChanged.AddListener(RuntimeDataStorage_OnChanged);
         GPWController.Instance.GameServices.OnChatViewChanged.AddListener(GameServices_OnChatViewChanged);
         
         // Every scene initializes as needed (Max 1 time per session)
         if (!GPWController.Instance.IsInitialized)
         {
            await GPWController.Instance.Initialize(_scene03ChatUIView.Configuration);
         }
         else
         {
            GPWController.Instance.PersistentDataStorage.ForceRefresh();
            GPWController.Instance.RuntimeDataStorage.ForceRefresh();
         }
      }

      
      private async Task<EmptyResponse> ShowDialogBoxLoadingSafe()
      {
         // Get roomname, and fallback to blank
         string roomName = "";
         if (!_scene03ChatUIView.DialogSystem.HasCurrentDialogUI && GPWController.Instance.HasCurrentRoomHandle)
         {
            RoomHandle roomHandle = GPWController.Instance.GetCurrentRoomHandle();
            roomName = roomHandle.Name;
         }

         if (_scene03ChatUIView.DialogSystem.HasCurrentDialogUI)
         {
            await _scene03ChatUIView.DialogSystem.HideDialogBoxImmediate();
         }
         _scene03ChatUIView.DialogSystem.ShowDialogBoxLoading(roomName);
         return new EmptyResponse();
      }
      
      
      private async void SetChatMode(ChatMode chatMode)
      {
         // Change mode
         GPWController.Instance.RuntimeDataStorage.RuntimeData.ChatMode = chatMode;

         // THis mode can be reached by clicking chat text too
         // so update the button to look selected
         if (chatMode == ChatMode.Direct)
         {
            _scene03ChatUIView.DirectChatToggle.isOn = true;
         }
         
         // Show mode specific prompt
         await ShowDialogBoxLoadingSafe();
         
         // Update
         GPWHelper.PlayAudioClipSecondaryClick();
         GPWController.Instance.RuntimeDataStorage.ForceRefresh();
      }
      
      
      private async void RenderChatOutput()
      {
         if (!GPWController.Instance.GameServices.HasChatView)
         {
            return;
         }
         
         if (!GPWController.Instance.HasCurrentRoomHandle)
         {
            return;
         }
         
         RoomHandle roomHandle = GPWController.Instance.GetCurrentRoomHandle();

         ChatMode chatMode = GPWController.Instance.RuntimeDataStorage.RuntimeData.ChatMode;
         StringBuilder stringBuilder = new StringBuilder();
         stringBuilder.AppendLine("---- RenderChatOutput Room ----");
         stringBuilder.AppendLine($"Title: {roomHandle.Name}");
         stringBuilder.AppendLine($"Topic: {GPWHelper.GetChatRoomTopic (chatMode)}");
         stringBuilder.AppendLine($"Players: {roomHandle.Players.Count}");
         stringBuilder.AppendLine($"Messages: {roomHandle.Messages.Count}");
         stringBuilder.AppendLine().AppendLine();
         
         foreach (Message message in roomHandle.Messages)
         {
            long playerDbid = message.gamerTag;
            string alias = "";
            try
            {
               alias = await GPWController.Instance.GameServices.GetOrCreateAlias(playerDbid);
            }
            catch (Exception e)
            {
               Debug.Log("E: " + e.Message);
            }
            
            //Temporarily override alias to reduce confusion. Its by the local player but 
            //from a previous account. 
            if (!GPWController.Instance.GameServices.IsLocalPlayerDbid(playerDbid) &&
                alias == GPWHelper.DefaultLocalAlias)
            {
               alias = MockDataCreator.CreateNewRandomAlias(GPWHelper.DefaultRemoteAliasPrefix);
            }
                         
            if (GPWController.Instance.RuntimeDataStorage.RuntimeData.ChatMode == ChatMode.Direct ||
                GPWController.Instance.GameServices.IsLocalPlayerDbid(playerDbid))
            {
               stringBuilder.AppendLine($"[{alias}]: " + message.content);
            }
            else
            {
               // When NOT in direct chat, and NOT the local player, renders clickable text
               // Clicks are handled above by "HyperlinkHandler_OnLinkClicked"
               stringBuilder.AppendLine($"[{TMP_HyperlinkHandler.WrapTextWithLink(alias, playerDbid.ToString())}]: " + message.content);
            }
         }
         
         _scene03ChatUIView.ScrollingText.SetText(stringBuilder.ToString());
         
         await _scene03ChatUIView.DialogSystem.HideDialogBox();
         _scene03ChatUIView.ChatInputUI.Select();
      }
      
      
      //  Event Handlers -------------------------------
      private async void HyperlinkHandler_OnLinkClicked(string href)
      {
         if (GPWController.Instance.RuntimeDataStorage.RuntimeData.ChatMode == ChatMode.Direct)
         {
            throw new Exception("HyperlinkHandler_OnLinkClicked() ChatMode cannot be ChatMode.Direct. ");
         }

         SetChatMode(ChatMode.Direct);
         
         RoomHandle roomHandle = GPWController.Instance.GetCurrentRoomHandle();
         long dbid1 = GPWController.Instance.GameServices.LocalPlayerDbid;
         long dbid2 = long.Parse(href);
         await GPWController.Instance.GameServices.JoinDirectRoomWithOnly2Players(dbid1, dbid2);
      }
      
      
      private async void ChatInputUI_OnValueSubmitted(string message)
      {
         RoomHandle roomHandle = GPWController.Instance.GetCurrentRoomHandle();
         await GPWController.Instance.GameServices.SendMessage(roomHandle.Name, message);
         _scene03ChatUIView.ChatInputUI.Select();
      }


      private void ChatInputUI_OnValueCleared()
      {
         GPWHelper.PlayAudioClipSecondaryClick();
      }
      
         
      private void GlobalChatButton_OnClicked(bool isOn)
      {
         if (isOn)
         {
            SetChatMode(ChatMode.Global);
         }
      }
      
      
      private void LocationChatButton_OnClicked(bool isOn)
      {
         if (isOn)
         {
            SetChatMode(ChatMode.Location);
         }
      }
      
      
      private void DirectChatButton_OnClicked(bool isOn)
      {
         if (isOn)
         {
            SetChatMode(ChatMode.Direct);
         }
      }
      
      
      private void BackButton_OnClicked()
      {
         StartCoroutine(GPWHelper.LoadScene_Coroutine(
            _scene03ChatUIView.Configuration.Scene02GameName,
            _scene03ChatUIView.Configuration.DelayBeforeLoadScene));
      }
      
      
      private void PersistentDataStorage_OnChanged(SubStorage subStorage)
      {
         PersistentDataStorage persistentDataStorage = subStorage as PersistentDataStorage;
         _scene03ChatUIView.PersistentData = persistentDataStorage.PersistentData;
         _scene03ChatUIView.LocationContentView = GPWController.Instance.LocationContentViewCurrent;
         RenderChatOutput();
      }
      
      
      private void RuntimeDataStorage_OnChanged(SubStorage subStorage)
      {
         RuntimeDataStorage runtimeDataStorage = subStorage as RuntimeDataStorage;
         _scene03ChatUIView.RuntimeData = runtimeDataStorage.RuntimeData;
         RenderChatOutput();
      }
      
      
      private void GameServices_OnChatViewChanged(ChatView chatView)
      {
         RenderChatOutput();
      }
   }
}

Additional Experiments

Here are some optional experiments game makers can complete in the sample project.

Did you complete all the experiments with success? We'd love to hear about it. Contact us.

Difficulty

Scene

Name

Detail

Beginner

Game

Tweak Configuration

• Update the Configuration.asset values in the Unity Inspector Window

Note: Experiment and have fun!

Beginner

Various

Add New Graphics

• Change the colors, fonts, and sprites for cosmetics

Intermediate

Game

Add a New Product item

• Open the Content Manager
• Duplicate an existing ProductContent
• Update all field values
• Update the existing RemoteConfiguration
• Publish the content

Intermediate

Game

Add a New Location item

• Open the Content Manager
• Duplicate an existing LocationContent
• Update all field values
• Update the existing RemoteConfiguration
• Publish the content

Advanced

Chat

Add user-to-user item transactions

• Design and implement new functionality

Advanced

Various

Store player progress between sessions

• Add Beamable Cloud Save to store the existing PersistentData object

Advanced

Various

Add shared database so the local price and local quantity update across all players

• Add Beamable Microservices With a DLL of any 3rd party database

Advanced

This section contains any advanced configuration options and workflows.

Learning Resources

These learning resources provide a better way to build live games in Unity.

Source

Detail

  1. Download the Chat GPW Sample Project
  2. Open in Unity Editor ( Version 2020.3.11f1 )
  3. Open the Beamable Toolbox
  4. Sign-In / Register To Beamable. See Step 1 - Getting Started for more info
  5. Rebuild the Unity Addressables : Unity → Window → Asset Management → Groups, then Build → Update a Previous Build
  6. Open the Scene01Intro Scene
  7. Play The Scene: Unity → Edit → Play
  8. Click the "Start" Button
  9. Enjoy!

Note: Supports Mac & Windows and includes the Beamable SDK

Updated 13 days ago


Chat (GPW) - Sample


Demonstrates Beamable's Chat feature, and more!

Suggested Edits are limited on API Reference Pages

You can only suggest edits to Markdown body content, but not to the API spec.