Chat (GPW) - Guide
[SMPL-GPW-02]
This documents how to understand and apply the benefits of Chat - Overview in game development. Or watch this video.
Variations On A Theme
There are two repos for the "Global Price Wars" game each with distinct learning goals.
- Chat GPW Sample Project - Simpler project, the
GPWBasicDataFactory
uses random, local data values. You are currently viewing the documentation for this project.- Chat GPW2 Sample Project With MicroStorage - Complex project, the
GPWMicroStorageDataFactory
uses Beamable Microservices and MicroStorage with data values stored in shared database
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.23f1 ) 3. Open the Beamable Toolbox 4. Sign-In / Register To Beamable. See Installing Beamable for more info 5. Rebuild the Unity Addressables : Unity → Window → Asset Management → Groups, then Build → Update a Previous Build 6. Open the Scene01Intro Scene7. 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
- Use money in
Pro Tip: Sell all owned items before the final turn to increase the final score.
Screenshots
Scenes
Scene01Intro | Scene02Game | Scene03Chat | Scene04Settings | Scene05Leaderboard |
---|---|---|---|---|
Design Overview
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.
Design of Data
The data within the RuntimeDataStorage
is vital for the core game loop.
The game design requires that all users in the same game (e.g. same chat session) to see a shared world of consistent pricing. This is accomplished using the data structure and data factory below.
Data Structure
The structure contains all the locations of the game. Within each location is information about each product; including its price and quantity.
- List < LocationContentView >
- LocationData (e.g. "Africa")
- List < ProductContentViews >
- ProductData (e.g. "Chocolate")
- MarketGoods
- Price (e.g. "10")
- Quantity (e.g. "3")
Data Factory
The IDataFactory
gets data as shown in the diagram below.
- A. The
RuntimeDataStorage
calls toGetLocationContentViews()
at the start of each game session - B. Here in the GPW game the
GPWBasicDataFactory
creates pseudorandom price/quantity for all products. A random-seed value is shared across all game clients for consistent pricing. This solution is easier to understand, but likely not robust enough for a production game.
Development
Here is an overview of project organization.
- Project Window - Suggested entry points for game makers include the
Scenes
folder andScripts
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!");
Chat Manager
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 WindowNote: 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.23f1 ) 3. Open the Beamable Toolbox 4. Sign-In / Register To Beamable. See Installing Beamable for more info 5. Rebuild the Unity Addressables : Unity → Window → Asset Management → Groups, then Build → Update a Previous Build 6. Open the Scene01Intro Scene7. Play The Scene: Unity → Edit → Play 8. Click the "Start" Button 9. Enjoy! Note: Supports Mac & Windows and includes the Beamable SDK |
Updated about 1 year ago