In this post, we'll integrate a conversational bot development framework into Unity that will work offline. We'll first create the chat interface and then bind it to Oscova to do the bot magic. We'll then create a simple Hello Bot dialog into our bot and see how to generate and display our response in Unity.
Introduction
This article takes the approach of on-device chatbot integration into Unity. Instead of calling some online API to generate a response for a user message, here we will directly integrate an offline chatbot framework so we don't have to rely on any online service or subscription for intelligent response generation. For this, we'll be using a popular on-device bot development tool called Oscova and we'll write an extremely simple Hello Bot dialog to test drive our unity bot integration.
Prerequisites
This article would be a good follow-up read to my previous article in which I created a mock-up table reservation bot. You could use that knowledge and apply it in the knowledge-base creation section of this article and expand on it. The article obviously assumes that the reader has some expertise in unity game development. In the meantime, I'll assume you satisfy the following:
- Basic knowledge of Unity scripting
- Knowledge of working in Visual Studio or Visual Studio Code
- Basic knowledge of programming in C#/.NET
Installing Unity
If you haven't got Unity installed:
- Visit https://unity3d.com/get-unity/download and click on Download Unity Hub to download Unity Hub which will guide you into installing Unity.
- Make sure in the Add-On window, you choose Microsoft Visual Studio Community 2019 if you don't have it already.
Creating a Unity Project
- Launch unity and create a new 3D Project by selecting the 3D project template.
- You can name your project
MyFirstUnityBot
.
- Click on the Create button.
- Your unity project will now be created.
Creating the Chat Bot UI
We'll need three important UI elements to create our Chatbot interface in Unity.
- Display Panel - where all the chats/messages are going to be held
- Input Box - where the user will type in stuff
- Send Button - Button the user will press to submit his message
To create our display system:
- Right click in Hierarchy window, select UI and choose Canvas.
- Keep the Canvas selected, right click, select UI and choose Scroll View.
- Change the width and the height of the Scroll View to 500 and 300 respectively.
Once a user or bot message is generated, we'll need to add that message to the Scroll View
. For this, we'll create a prefab of the Text UI element and use it whenever a message is to be added to the Scroll View.
- Again, right click within Content item of the Viewport, choose
UI
and select Text - Set the orientation of the Text UI element to Bottom-Left, increase font size to 16
- Under Vertifical Overflow select Overflow
- Drag the item to Assets to convert it into a prefab and delete the item in the Hierarchy window.
In the above screenshot, you can see that I've added two new components called Content Size Filter and Vertical Layout Group and adjusted their properties to help fit the messages properly within the Scroll View.
Create a new Input Field and a Button and place them side by side as shown below. We'll use these two UI elements to send the user message to our bot.
Importing OSCOVA - Syn Bot Framework
Unlike how we import libraries in a C# project say via NuGet, a unity project is recompiled every time the script gets changed so we cannot simply work with a NuGet package here. We'll instead deal with a Unity package that contains the framework library and its dependencies.
Download the Syn.Bot Unity Package.
- Right click in Assets, select Import Package and choose Custom Package.
- When the import window shows up, ensure that all the items within the package are selected.
- Click on Import.
Scripting the Bot Interface
To bind the UI elements with our bot, we'll now go ahead and script that in.
- Create an Empty Object named GameManager in Hierarchy window
- Add a Script component to it and name it the same.
- Double click on the newly added GameManager script in the Asset Panel.
- This will open a Visual Studio project.
Remember the Message
object we talked about adding to the Scroll View? Well, let's create a class that will hold the user and bot message and will make it possible for us to display it in Unity UI via the Text
prefab.
The Message
class below will hold the user/bot message as string
, a reference to a Text
prefab and optionally a MessageType
enumeration which will help us decorate the message a bit with different colors.
using Syn.Bot.Oscova;
using Syn.Bot.Oscova.Attributes;
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Message
{
public string Text;
public Text TextObject;
public MessageType MessageType;
}
public enum MessageType
{
User, Bot
}
public class GameManager : MonoBehaviour
{
void Start()
{
}
void Update()
{
}
}
Now that a message holder is ready, we'll go ahead and create some public fields to which we'll bind to from within Unity. We'll also create a OscovaBot
object which will be instantiated when the game runs. Place the code below just above the Start()
method.
OscovaBot MainBot;
List<Message> Messages = new List<Message>();
public GameObject chatPanel, textObject;
public InputField chatBox;
public Color UserColor, BotColor;
Save and close the Visual Studio project and select the GameManager
in Hierarchy window. We'll now connect the fields with their respective components.
- Set the Chat Panel object to Content in Scene
- Text Object to Text prefab from within the Assets
- Chat Box field to Input Field in Scene
We had created a MessageType
property in the Message
class so that users would visually be able to differentiate between the two. Set the User and Bot color object values to something of your choice but remember to set the Alpha value to 255 so it stays visible.
Displaying the User/Bot Message
To display the user and bot message in the Scroll View
's content property, we'll have to position the objects within its transforms. Also, we'll assign a color to each message type.
In the code below, we create a new instance of Message
class, assign the message string to it, instantiate it within the transforms of the chatPanel
. The list Messages
is used to keep a check on the message count. If the count is above 25, we just remove the 25th last item. Lastly, based on the MessageType
, we select a color for the text.<text>
.
public void AddMessage(string messageText, MessageType messageType)
{
if (Messages.Count >= 25)
{
Destroy(Messages[0].TextObject.gameObject);
Messages.Remove(Messages[0]);
}
var newMessage = new Message { Text = messageText };
var newText = Instantiate(textObject, chatPanel.transform);
newMessage.TextObject = newText.GetComponent<Text>();
newMessage.TextObject.text = messageText;
newMessage.TextObject.color = messageType == MessageType.User ? UserColor : BotColor;
Messages.Add(newMessage);
}
Processing the User Message
In our next method, we'll create a logic that will govern what happens after a user posts a message to the Bot. Long story short, we'll send the message to the bot for processing. We'll call this method SendMessageToBot()
.
public void SendMessageToBot()
{
var userMessage = chatBox.text;
if (!string.IsNullOrEmpty(userMessage))
{
Debug.Log($"OscovaBot:[USER] {userMessage}");
AddMessage($"User: {userMessage}", MessageType.User);
var request = MainBot.MainUser.CreateRequest(userMessage);
var evaluationResult = MainBot.Evaluate(request);
evaluationResult.Invoke();
chatBox.Select();
chatBox.text = "";
}
}
In the method above, we first fetch the user message from the Input Field
's text property. We then check if the message isn't an empty string.
Next, we create a user request with the user message, get the evaluation result and call the Invoke()
method on it. This will compel the Bot to select the highest matching Intent and execute it. Which in-turn will generate a response, for which we'll add an event handler later.
In the last part, we just select the Input Field (chatBox
) and clear its content by setting its text value to an empty string.
Connecting to Button Click
This SendMessageToBot()
method needs to be called whenever the user clicks the Send button or presses Enter in the Input Field.
- Select the button element in Hierarchy.
- Under On Click(), select GameManager under Scene.
- Under GameManager object, choose SendMessageToBot().
Connecting to Enter Press
Apart from processing the user message when the Send button is pressed, we would also need to process the user message when the user hits Enter in the Input Field. To do so, we'll just need to change the Update()
method to the following:
void Update()
{
if (Input.GetKeyDown(KeyCode.Return))
{
SendMessageToBot();
}
}
Simple Hello Bot Dialog
We are using Oscova to build the knowledge-base (KB) of the Bot. You can create a KB in SIML, Syn Workspace via Oryzer Studio or directly in pure C# code. For simplicity and to prevent the article from going verbose, we will create a simple dialog in C# directly within our script.
We'll start by creating a BotDialog
class which will inherit the Dialog
class, within it we'll create a void
Hello()
method and decorate it with an Expression
attribute in which we'll specify the sample user message as Hello Bot. This method will then return Hello User! as a bot response.
public class BotDialog: Dialog
{
[Expression("Hello Bot")]
public void Hello(Context context, Result result)
{
result.SendResponse("Hello User!");
}
}
If the above code isn't making much sense, well, that's because there are a few concepts you'll have to familiarize yourself with. Recently, I posted an article that goes a little bit in-depth in creating a Bot's knowledge-base without much C# coding.
You will find the following article a lot more helpful if you wish to learn more about dialog creation:
Now that I've nudged you in the right direction for further learning, let's move on to updating our Start()
method to wrap it all up.
Initializing the Bot Instance
We need to glue a couple of things together for our bot to work. Firstly, we'll need to instantiate our bot object and then we'll need to handle what happens when the bot generates a response.
In our code, we create a new instance of OscovaBot
and assign it to the MainBot
field. We then add our previously created BotDialog
to the Dialogs
collection of the bot and start the training. Finally, we add a event handler to the ResponseReceived
event. Which does nothing but call the AddMessage(
) method with the bot's response.
void Start()
{
try
{
MainBot = new OscovaBot();
OscovaBot.Logger.LogReceived += (s, o) =>
{
Debug.Log($"OscovaBot: {o.Log}");
};
MainBot.Dialogs.Add(new BotDialog());
MainBot.Trainer.StartTraining();
MainBot.MainUser.ResponseReceived += (sender, evt) =>
{
AddMessage($"Bot: {evt.Response.Text}", MessageType.Bot);
};
}
catch (Exception ex)
{
Debug.LogError(ex);
}
}
Well, that's that we are done coding!
Overall Code
Once done, your code should (hopefully) look something like the following:
using Syn.Bot.Oscova;
using Syn.Bot.Oscova.Attributes;
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Message
{
public string Text;
public Text TextObject;
public MessageType MessageType;
}
public enum MessageType
{
User, Bot
}
public class BotDialog: Dialog
{
[Expression("Hello Bot")]
public void Hello(Context context, Result result)
{
result.SendResponse("Hello User!");
}
}
public class GameManager : MonoBehaviour
{
OscovaBot MainBot;
public GameObject chatPanel, textObject;
public InputField chatBox;
public Color UserColor, BotColor;
List<Message> Messages = new List<Message>();
void Start()
{
try
{
MainBot = new OscovaBot();
OscovaBot.Logger.LogReceived += (s, o) =>
{
Debug.Log($"OscovaBot: {o.Log}");
};
MainBot.Dialogs.Add(new BotDialog());
MainBot.Trainer.StartTraining();
MainBot.MainUser.ResponseReceived += (sender, evt) =>
{
AddMessage($"Bot: {evt.Response.Text}", MessageType.Bot);
};
}
catch (Exception ex)
{
Debug.LogError(ex);
}
}
public void AddMessage(string messageText, MessageType messageType)
{
if (Messages.Count >= 25)
{
Destroy(Messages[0].TextObject.gameObject);
Messages.Remove(Messages[0]);
}
var newMessage = new Message { Text = messageText };
var newText = Instantiate(textObject, chatPanel.transform);
newMessage.TextObject = newText.GetComponent<Text>();
newMessage.TextObject.text = messageText;
newMessage.TextObject.color = messageType == MessageType.User ? UserColor : BotColor;
Messages.Add(newMessage);
}
public void SendMessageToBot()
{
var userMessage = chatBox.text;
if (!string.IsNullOrEmpty(userMessage))
{
Debug.Log($"OscovaBot:[USER] {userMessage}");
AddMessage($"User: {userMessage}", MessageType.User);
var request = MainBot.MainUser.CreateRequest(userMessage);
var evaluationResult = MainBot.Evaluate(request);
evaluationResult.Invoke();
chatBox.Select();
chatBox.text = "";
}
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Return))
{
SendMessageToBot();
}
}
}
Test Driving the Bot
Without further adieu, let's save and close the Visual Studio project so that Unity re-compiles and commits the changes.
In Game mode, type in Hello Bot in the Input Field and press the Send button. Voila! your bot's response is on your screen.
Points of Interest
Phew! That was interesting to build. Chatbots in gaming platforms open a lot of new doors. It would be exciting to see what developers would build within their games and how intelligent chat bots would change the gaming experience. In order to be less redundant, I refrained from creating a larger knowledge-base for the bot as a few of my previous articles already discussed and explained how one can creatively use bots in their applications. However, a chat bot in a game environment felt something cool to experiment with.
History
- 6th May, 2020: Initial release