Unity quickstart
The Unity SDK is two UPM packages for Unity 6000.2 and later: the same engine the
.NET heads use, and a Unity head with a UnityWebRequest transport, a file store under
Application.persistentDataPath, the native review prompts and a UI Toolkit panel.
1. Add the packages
The two packages ship as tarballs. Download both (version 0.3.4):
com.mubold.wordsareflowing.core-0.3.4.tgz— the engine and the wire contracts.com.mubold.wordsareflowing-0.3.4.tgz— the Unity head. It depends on the core package and oncom.unity.nuget.newtonsoft-json.
Commit both .tgz files to your project (for example under
Packages/vendor/) and reference them by file: in
Packages/manifest.json. The paths are relative to the Packages
folder.
{
"dependencies": {
"com.mubold.wordsareflowing.core": "file:vendor/com.mubold.wordsareflowing.core-0.3.4.tgz",
"com.mubold.wordsareflowing": "file:vendor/com.mubold.wordsareflowing-0.3.4.tgz"
}
}
2. Create the loop and the panel
WordsAreFlowingUnity.Create
builds an IFeedbackLoop from a
WordsAreFlowingUnityOptions (the
ApiKey is required). There is no container: you keep the loop
yourself. Call it from Start, not Awake or a constructor — it creates a
hidden GameObject.
using UnityEngine;
using UnityEngine.UIElements;
using WordsAreFlowing.Sdk.Core.Engine;
using WordsAreFlowing.Unity;
using WordsAreFlowing.Unity.Platform;
using WordsAreFlowing.Unity.Ui;
public class Feedback : MonoBehaviour
{
[SerializeField] UIDocument document;
IFeedbackLoop loop;
FeedbackAskPanel panel;
async void Start()
{
loop = WordsAreFlowingUnity.Create(new WordsAreFlowingUnityOptions
{
ApiKey = "waf_live_…",
AppVersion = Application.version
});
panel = new FeedbackAskPanel(loop, CoroutineRunner.Create());
document.rootVisualElement.Add(panel.Root);
await loop.InitializeAsync();
}
public async void OnLevelCompleted()
{
await loop.TrackAsync("level_completed");
}
void OnDestroy() => panel?.Dispose();
}
Create fills in Platform from
Application.platform and reports the SDK version itself.
InitializeAsync loads the cached rules, then fetches fresh
ones if the network allows. TrackAsync works as on the
.NET page, and returns a decision the same way.
3. The private ask: FeedbackAskPanel
FeedbackAskPanel is a plain C# class, not a
MonoBehaviour. Add its Root element wherever you like; it hides itself
and subscribes to DecisionMade, opening when a decision asks
for it. It shows the same copy as the Blazor component, in English or French from the current UI
culture, or a FeedbackAskTexts you pass in. If a send fails,
the player's text is kept and an error line is shown; nothing is retried for you.
For your own "send feedback" door, call Open with a trigger name.
The panel never calls TrackAsync, so neither path can cause a store
prompt. Closed is raised whenever it closes. Call
Dispose when you are done with it, or its handler stays attached to
the loop.
Native review prompts
iOS: the SDK calls Unity's UnityEngine.iOS.Device.RequestStoreReview.
Apple shows the sheet at most three times in 365 days, always in development builds, and never
from TestFlight.
Android: the prompt needs Google's Play In-App Review plugin for Unity
(com.google.play.review). It is optional: the SDK's Android review assembly compiles only
when that plugin is installed. Without it the SDK cannot show a prompt on Android: a store-prompt
decision there ends in None with the reason
prompter_declined. In the editor and on desktop players
the reason is prompter_unavailable, as on the web.
To test the path on a build that is on no store track, pass your own flow as
reviewFlowOverride to Create;
DefaultFlowFor is what it uses otherwise.
Stay on the main thread
Neither the engine nor the Unity head uses ConfigureAwait(false), so when you call
the loop from Unity's main thread, its continuations — counter writes, and your
DecisionMade handler — come back to the main thread. Keep it
that way in your own code: do not add ConfigureAwait(false) to an await whose
continuation touches a Unity object.