FAtiMA as a C# Library
Here we will describe how FAtiMA works as an independent C# library. This post serves as a guide for developers that want to learn how to use FAtiMA in their projects and how to use the scenarios created using the Authoring Tool with their code.
Additionally, at the end of the post we will also show you how to “bypass” the authoring tool and create scenarios and characters through code only.
Please note that the code and methods presented here can be used in any C# powered project, including Unity and other game engines.
FAtiMA works as a C# library as a result in order to use it we need its .dll files.
There are 2 different ways of obtaining/use them:
- Download the files from our NAS repository
- Rebuild the Fatima Toolkit solution (FAtiMA-Toolkit.sln) on your IDE of preference and then, using Windows PowerShell, use the following command:
.\ExportDlls.bat debug ‘path of desired directory’
These are the FAtiMA .dll files:
The integration of external C# libraries are simple however they might change from application to application, for instance, in Unity, to use an external library the .dll files need to be in a sub-folder called “Plugins” in the “Asset ” folder of the Unity Project.
In a simple Visual Studio C# project all it needs is for the dll files to be used as a reference and FAtiMA is integrated.
A simple way to test if the integration is properly working is to create a new C# script in the Unity Project and try to import a FAtiMA-specific component, such as Integrated Authoring Tool Asset, as seen in the figure below:
Now that the library is properly integrated let’s by loading a scenario. In this case we will use the one we created in the “Using the Integrated Authoring Tool” post.
In short, the code should follow the process below:
- Load the IAT scenario
- Load its characters and World Model
- Start the cycle:
- Decision Making process, does any agent desire to perform an action?
- If there is a decision, perform it using the Dialogue Editor
- Check its consequences using the World Model
- Enact those consequences by updating the beliefs of the agents
It is important to note that in most of our examples, if the character that decides to execute an action is the one the Player is playing as, we add another step where the player must chose what dialogue he/she wants to perform.
So, let’s start by the beginning, by loading the scenario itself, the “.iat” file. This might vary from platform to platform but as long as it is correctly pointing to the .iat file the asset should be able to properly load it. In this case:
iat = IntegratedAuthoringToolAsset.LoadFromFile("/PepeSilvia/Pepe.iat");
In some cases, this path only works in the Editor as such, after building the game this path might need some tuning. In this example we used a Unity project as a basis therefore if we were to build the project we might want to use the the Application.streamingAssets method to point to the “StreamingAssets” folder.
Now that we have our Integrated Authoring Tool file loaded we need to set up its characters (RPCs) and World Model, which will look something like this:
// Store the iat file private IntegratedAuthoringToolAsset _iat; //Store the characters private List<RolePlayCharacterAsset> _rpcList; //Store the World Model private WorldModelAsset _worldModel; // Use this for initialization void Start () { _iat = IntegratedAuthoringToolAsset.LoadFromFile( Application.dataPath + "/StreamingAssets/PepeSilvia/Pepe.iat"); //Initialize the List _rpcList = new List<RolePlayCharacterAsset>(); foreach (var characterSouce in _iat.GetAllCharacterSources()) { var rpc = RolePlayCharacterAsset.LoadFromFile(characterSouce.Source); // RPC must load its "sub-assets" rpc.LoadAssociatedAssets(); // Iat lets the RPC know all the existing Meta-Beliefs / Dynamic Properties _iat.BindToRegistry(rpc.DynamicPropertiesRegistry); // A debug message to make sure we are correctly loading the characters Debug.Log("Loaded Character " + rpc.CharacterName); _rpcList.Add(rpc); } //Loading the WorldModel _worldModel = WorldModelAsset.LoadFromFile(_iat.m_worldModelSource.Source); }
As mentioned before the scenario used here is the one we created in the Using Integrated Authoring Tool post, which features Charlie RPC and a Player RPC. Now we want to know what they want to do. Because of how we set up their beliefs only one of them will actually decide to do anything:
// Update is called once per frame void Update() { IAction finalDecision = null; String agentName = ""; // a simple cycle to go through all the agents and get their decision foreach (var rpc in _rpcList) { // From all the decisions the rpc wants to perform we want the first one var decision = rpc.Decide().FirstOrDefault(); if (decision != null) { agentName = rpc.CharacterName.ToString(); finalDecision = decision; break; } } //If there was a decision I want to print it if(finalDecision != null) Debug.Log(" The agent " + agentName + " decided to perform " + finalDecision.Name); }
The Decide() Method returns a list of all the actions the agent wants to perform ordered by their priority value. If you try to run the code it will print “The agent Charlie decided to perform Speak(Start, *, *, *)”. The next step is to let’s try to get the corresponding dialogue from those available in the current IAT scenario using the GetDialogueActions method:
if (finalDecision != null) { Debug.Log(" The agent " + agentName + " decided to perform " + finalDecision.Name); // If it is a speaking action it is composed by Speak ( [ms], [ns] , [m}, [sty]) var currentState = finalDecision.Name.GetNTerm(1); var nextState = finalDecision.Name.GetNTerm(2); var meaning = finalDecision.Name.GetNTerm(3); var style = finalDecision.Name.GetNTerm(4); // Returns a list of all the dialogues given the parameters var dialogs = _iat.GetDialogueActions(currentState, nextState, meaning, style); //Let's print all available dialogues: foreach (var d in dialogs) { Debug.Log(agentName + " says: " + d.Utterance + " towards " + finalDecision.Target); } }
The code above will display the following: “The agent Charlie says: Hi how are you? towards Player”. Using the “GetDialogueAction” methods we are able retrieve the dialogue corresponding to the action chosen. You might use these methods together with your game engine. For instance in Unity we might create a Canvas with a text of the dialogue Charlie says to the Player.
Once the agent “has talked” we need to inform the World of what happened. The agent performing dialogue, in FAtiMA, corresponds to an Event, an Action-End event, which has the following template:
Event(Action-End, Subject , Action, Target)
Taking into account what happened the event name will be:
Event(Action-End, Player, Speak(Start, S1 , * ,*)
As mentioned before, to help the authoring process, the author is capable of
defining what are the consequences of an agent performing a specific action in the World Editor
It is important to note that the effects of actions might be programmed directly in the game itself and communicated to the agents via events. The main benefit of using the World Model Asset is that these effects are configurable without having to recompile the game. Additionally, the action effects defined in the World Model will be visible to the Simulator. In this case we will use it and here is how it looks in the Authoring Tool.
In order to retrieved what are the consequences of the actions we need to inform the World Model of what happened. This is done through the “Simulate” method:
var eventName = EventHelper.ActionEnd((Name)agentName, (Name)actualActionName, finalDecision.Target); var consequences = _worldModel.Simulate(new Name[] {eventName} ); foreach (var eff in consequences) { Debug.Log("Effect: " + eff.PropertyName + " " + eff.NewValue + " " + eff.ObserverAgent); foreach (var rpc in _rpcList) { if (eff.ObserverAgent == rpc.CharacterName || eff.ObserverAgent == (Name) "*") { rpc.Perceive(EventHelper.PropertyChange(eff.PropertyName, eff.NewValue, eff.ObserverAgent)); } } }
The “Simulate” method returns a list of Effects. Effects are defined by a Property Name, its New Value, and the agent that is affected by that change, which can be anyone (“*”) or a specific agent, i. e. [x], the target of the action. Once the list is returned we can iterate through it and apply update the correct properties with its new values.
Additionally, changes to the world and its characters might be manually coded. Each Role Player Character has several different methods that developers may want to use. For instance, retrieving the value of a certain belief:
var agent = _rpcList.FirstOrDefault(); var val = agent.GetBeliefValue("DialogueState(John)");
Updating a specific belief value:
agent.UpdateBelief("DialogueState(John)", "Sarah");
The functions shown above are only two of the many functions available to the Role Play Character Asset class. It is important to note that each component acts as its own class and as a consequence has its own methods. These include adding and removing rules, debugging tools and computational functions such as Simulate or Perceive methods. By accessing each component (Emotional Appraisal, Emotional Decision Making etc…) developers can use those methods in their code.
It is also important to note that authors don’t necessarily need to use the Authoring Tool to build its scenario. Using each component and their methods developers are able to create its own rules, conditions, characters and scenarios by code and during run-time. To learn more about it we suggest taking a look at the Tutorials folder in FAtiMA. The Emotional Decision Making Tutorial does a particular good job on creating an EDM and editing it at run-time:
//First we construct a new instance of the EmotionalDecisionMakingAsset class var edm = new EmotionalDecisionMakingAsset(); //Then, we have to register an existing knowledge base to the asset so it can check for //beliefs are true var kb = new KB((Name)"John"); kb.Tell((Name)"LikesToFight(SELF)", (Name)"True"); edm.RegisterKnowledgeBase(kb); //create an action rule var actionRule = new ActionRuleDTO {Action = Name.BuildName("Kick"), Priority = Name.BuildName("4"), Target = (Name)"Player" }; //add the reaction rule var id = edm.AddActionRule(actionRule); edm.AddRuleCondition(id, "LikesToFight(SELF) = True"); var actions = edm.Decide(Name.UNIVERSAL_SYMBOL); Console.WriteLine("Decisions: "); foreach (var a in actions) { Console.WriteLine(a.Name.ToString() + " p: " + a.Utility); }
Additionally we also use this particular feature to test and debug FAtiMA, as such, if you need examples you can take a peek at our Unit Tests and their testing methods.