Skip to content

Sending xAPI Traces

Victorma Perez Colado edited this page Jun 14, 2022 · 6 revisions

Trace submission from Xasu is done using an asynchronous queue. This prevents the game from freezing and allows Xasu managing the trace submission in batches, reducing the connection load and handling the different errors. In addition, it is possible to know when a trace is submitted and if any (handled) error happened while sending the specific trace.

There are two possibilities when sending traces:

  • Using the High-Level API: Xasu High-Level API simplifies the trace creation by using static templates from the Serious Game xAPI Profile and the CMI-5 Profile.
  • Using the TinCan.NET API: Xasu uses the TinCan.NET library to model the traces. Thus, custom traces created using this API can be sent using Xasu too.

Details on each case are found bellow. Note that, when a trace is added to Xasu queue, some parameters may be autocompleted.

Using the High Level API

The High Level API is a simpler way to send xAPI traces that reduces the learning curve and can potentially reduce errors.

There are 4 different APIs for the Serious Games Profile and 1 more API for CMI-5.

To use any of the APIs make sure you include the appropiate namespace in your .cs files.

using UnityTracker.HighLevel;

Here's an example of how can you send one trace using Xasu High-Level API:

    GameObjectTracker.Instance.Interacted("mesita");

Any High Level API will return a TraceTask structure, including the enqueued trace (modifable) and the Task associated to its submission. Since the trace is sent asynchronously, it is possible to use the async/await C# interface to await until the trace is sent. Errors from the trace submission will be thrown as AggregateException (since there could be multiple errors from the different working modes).

Below you can see how to manipulate the trace, await for its result and retrieve errors.

        try
        {
            var statementPromise = GameObjectTracker.Instance.Interacted("mesita");
            
            // Doing any modifications to the Statement in traceTask.Statement property is safe.

            var statement = await statementPromise.Promise;
            Debug.Log("Completed statement sent with id: " + statementPromise.Statement.id);
        }
        catch (AggregateException aggEx)
        {
            Debug.Log("Failed! " + aggEx.GetType().ToString());
            // You can check the inner exceptions from each working mode.
            foreach (var ex in aggEx.InnerExceptions)
            {
                Debug.Log("Inner Exception: " + ex.GetType().ToString());
            }
        }

Below you can see the rest of the APIs.

High-Level architecture

The high-level API just provides a simpler interface to send traces. Inside of each high-level API implementation, traces are enqueued into XasuTracker and a StatementPromise object is returned. This object is awaitable but also works like the Builder pattern, letting you modify the trace before it gets send. Some of these modifications include adding extensions, results or scores.

Arquitectura High-Level API

For instance, to send a trace with score

    var statement = await CompletableTracker.Instance.Completed("tutorial")
        .WithSuccess(true)
        .WithScore(0.8f); // completed successfully with 0.8/1.0 score

    Debug.LogFormat("Statement sent with id!", statement.id);

If you want to implement your own High-Level API and/or contribute it to this repository we encourage you to inherit from the AbstractHighLevelTracker class.

Serious Games APIs from the xAPI for SGs profile

There are four different Serious Games APIs for sending traces related to serious games.

Game Object API

The Game Object API is used to send traces when the player performs an interaction. The Interacted verb should be the main interaction type, but when the element is consumed, Used is more appropiate.

Some examples are listed below:

    // Interacted traces are sent when the player interacts with something
    GameObjectTracker.Instance.Interacted("alarm-trigger");
    
    // Interacted traces are sent when the player uses (and consumes) something
    GameObjectTracker.Instance.Used("potion");

    // Types of the elements can be specified for instance:
    GameObjectTracker.Instance.Interacted("john", GameObjectTracker.TrackedGameObject.Npc);
    GameObjectTracker.Instance.Interacted("grenade", GameObjectTracker.TrackedGameObject.Item);
    GameObjectTracker.Instance.Interacted("demon", GameObjectTracker.TrackedGameObject.Enemy);

Full list of TrackerGameObject types:

TrackedGameObject.GameObject
TrackedGameObject.Npc
TrackedGameObject.Item
TrackedGameObject.Enemy

Accessible API

The Accessible API is used to send a trace whenever the player accesses a new screen. Appart from the Accessed verb, it is possible to send also Skipped traces when the screen is manually skipped.

Some examples are listed below:

    // Accessed traces are sent when the player accesses an screen
    AccessibleTracker.Instance.Accessed("stage-1");
    
    // Skipped traces are sent when the player skips an screen
    AccessibleTracker.Instance.Skipped("tutorial");

    // Types of the elements can be specified for instance:
    AccessibleTracker.Instance.Accessed("main-menu", AccessibleTracker.AccessibleType.Screen);
    AccessibleTracker.Instance.Skipped("tutorial", AccessibleTracker.AccessibleType.Cutscene);
    AccessibleTracker.Instance.Accessed("storage-box-1", AccessibleTracker.AccessibleType.Inventory);

Full list of Accessible types:

AccessibleType.Accessible;
AccessibleType.Area;
AccessibleType.Cutscene;
AccessibleType.Inventory;
AccessibleType.Screen;
AccessibleType.Zone;

Alternative API

The Alternative API is used to send a trace whenever the player makes an election. Appart from the Selected verb, it is possible to send also Unlocked traces when an new option is unlocked in the game.

Some examples are listed below:

    // Accessed traces are sent when the player accesses an screen
    AlternativeTracker.Instance.Selected("alternative-1", "option-2");
    
    // Skipped traces are sent when the player skips an screen
    AlternativeTracker.Instance.Unlocked("alternative-1", "super-secret-option-3");

    // Types of the elements can be specified for instance:
    AlternativeTracker.Instance.Selected("main-menu", "start-game", AlternativeTracker.AlternativeType.Menu);
    AlternativeTracker.Instance.Unlocked("stage-1", "secret-door", AlternativeTracker.AlternativeType.Path);
    AlternativeTracker.Instance.Selected("dialog-1", "option-1", AlternativeTracker.AlternativeType.Dialog);

    // In this tracker is also recommended to include the success extension by using the simplified sintax
    AlternativeTracker.Instance.Selected("alternative-1", "option-2").WithSuccess(true);

Full list of Alternative types:

AlternativeType.Alternative;
AlternativeType.Arena;
AlternativeType.Dialog;
AlternativeType.Menu;
AlternativeType.Path;
AlternativeType.Question;

Completable API

The Completable API is the most abstract of the SGs APIs. It can be used to track the different tasks the player has to do in the game.

A Completable can be Initialized, Progressed and Completed (verbs).

Some examples are listed below:

    CompletableTracker.Instance.Initialized("tutorial");
    CompletableTracker.Instance.Progressed("tutorial", 0.5f); // 50% progress
    CompletableTracker.Instance.Completed("tutorial").WithSuccess(true).WithScore(0.8f); // completed successfully with 0.8/1.0 score

Full list of Completable types:

CompletableType.Game,
CompletableType.Session,
CompletableType.Level,
CompletableType.Quest,
CompletableType.Stage,
CompletableType.Combat,
CompletableType.StoryNode,
CompletableType.Race,
CompletableType.Completable,
CompletableType.DialogNode,
CompletableType.DialogFragment

CMI5 API

The CMI5 High-Level API is explained later in section.

Using the TinCan.NET API

The TinCan.NET API is the most flexible API for sending traces.

More details about the TinCan.NET API can be found in their repository at https://rusticisoftware.github.io/TinCan.NET/

To use this API, first include the TinCan.NET library in your .cs file;

using TinCan;

To use this API you must create an Statement and enqueue it in Xasu as explained below:

    var actor = new Agent();
    actor.mbox = "mailto:[email protected]";

    var verb = new Verb();
    verb.id = new Uri ("http://adlnet.gov/expapi/verbs/experienced");
    verb.display = new LanguageMap();
    verb.display.Add("en-US", "experienced");

    var activity = new Activity();
    activity.id = "http://rusticisoftware.github.io/TinCan.NET";

    var statement = new Statement();
    statement.actor = actor;
    statement.verb = verb;
    statement.target = activity;

    await Xasu.Instance.Enqueue(statement);

    Debug.LogFormatted("Statement {0} sent!", statement.id);

Parameters autocompleted by Xasu

When using Xasu, it is possible to avoid fulfilling some parameters in the traces when they are not present. These parameters include:

  • ID: The trace id is added using the .NET Guid library.
  • Actor: The trace actor is added using the Xasu.Instance.DefaultActor value.
  • Context: The trace context is added using the Xasu.Instance.DefaultContext value. This context is automatically configured when using CMI5 so traces are CMI5 allowed.
  • Timestamp: When the trace has no timestamp, DateTime.Now is setted as Timestamp.