@mistscale/unreal-sdk
v0.0.1-beta
Published
MistScale Unreal SDK — a UE5 plugin for NPC chat, voice, and spatial context over the MistScale REST and WebSocket APIs. Published on npm for versioned/CDN distribution; Unreal itself installs this by copying MistscaleSDK/ into a project's Plugins/ folder
Downloads
15
Maintainers
Readme
MistScale Unreal SDK
An Unreal Engine 5 plugin for NPC chat, voice, and spatial context over the same MistScale REST
and WebSocket APIs @mistscale/web-sdk and the MistScale Godot SDK use. Same wire contract, same
event model, C++ and Blueprint both fully supported.
Full raw protocol reference: ../web/docs/rest-api.md and
../web/docs/websocket-api.md — not duplicated here since the
contract is identical across every MistScale client.
No example project is bundled. A .uproject/content-pack example needs binary .uasset
files (a level, Blueprint graphs) that can't be authored as text — see "Validation note" below.
This README documents both the C++ and Blueprint usage instead, which is enough to build a
working example directly in your own project.
Install
Copy MistscaleSDK/ into your project's Plugins/ folder (create it if it doesn't exist), then
regenerate project files and rebuild. Enable "MistScale SDK" in Edit → Plugins if it isn't
already enabled by default.
Requires the engine's HTTP, Json, JsonUtilities, and WebSockets modules — all built into
UE5, no extra dependencies to install.
Quick start (C++)
#include "MistscaleSubsystem.h"
#include "MistscaleNPCConnection.h"
void AMyGameMode::BeginPlay()
{
Super::BeginPlay();
UMistscaleSubsystem* Mistscale = GetGameInstance()->GetSubsystem<UMistscaleSubsystem>();
FMistscaleError ConfigError;
if (!Mistscale->Configure(TEXT("ms_..."), ConfigError)) // Project Settings -> API Keys
{
UE_LOG(LogTemp, Error, TEXT("%s"), *ConfigError.Message);
return;
}
FMistscaleListNPCsResult Callback;
Callback.BindUFunction(this, FName("OnNPCsLoaded"));
Mistscale->ListNPCs(Callback);
}
UFUNCTION() // dynamic delegates can only bind to UFUNCTIONs, even from C++
void AMyGameMode::OnNPCsLoaded(bool bSuccess, const TArray<FMistscaleNPCSummary>& Npcs, const FMistscaleError& Error)
{
if (!bSuccess || Npcs.Num() == 0)
{
UE_LOG(LogTemp, Warning, TEXT("No NPCs: %s"), *Error.Message);
return;
}
UMistscaleSubsystem* Mistscale = GetGameInstance()->GetSubsystem<UMistscaleSubsystem>();
UMistscaleNPCConnection* Connection = Mistscale->ConnectNPC(Npcs[0].Id);
Connection->OnChatChunk.AddDynamic(this, &AMyGameMode::OnChatChunk); // streamed tokens
Connection->OnChatMessage.AddDynamic(this, &AMyGameMode::OnChatMessage); // settled turn
Connection->OnOpened.AddDynamic(this, &AMyGameMode::OnConnectionOpened);
}
UFUNCTION()
void AMyGameMode::OnConnectionOpened()
{
// find the connection again, or keep it as a member — omitted for brevity
}ListNPCs/VerifyKey take a DECLARE_DYNAMIC_DELEGATE-typed callback parameter rather than a
TFunction/lambda — this is what makes them Blueprint-callable, but it means even C++ callers
bind to a UFUNCTION (via BindUFunction), not a lambda. This is a deliberate simplicity
trade-off over a full UBlueprintAsyncActionBase-style async node (multiple exec pins,
Success/Failure branches) — the delegate-parameter pattern is less flashy in the Blueprint graph
(one exec pin, drag off the delegate pin and "Add Custom Event" to bind) but is far less code to
maintain, and both are equally valid, supported UE patterns for a one-shot async call.
Quick start (Blueprint)
- Get Game Instance Subsystem (class
MistscaleSubsystem) → gives you the SDK object. - Configure —
Api Key= yourms_...key, leave the URL params on their defaults. - List NPCs — drag off the
Callbackpin → Add Custom Event, wire the event'sNpcsoutput into Connect NPC (Npc Id=Npcs[0].Id). - On Connect NPC's returned connection: bind Assign On Chat Chunk / Assign On Chat Message from the node's output pin (or drag off the connection variable and pick the event from the dropdown) to stream/display replies.
- Call Send Chat on the connection with your player's message.
Public API
UMistscaleSubsystem (GameInstanceSubsystem — get via GetSubsystem<UMistscaleSubsystem>())
bool Configure(FString ApiKey, out FMistscaleError OutError, FString ControlPlaneUrl = "https://mistscale.com", FString NpcServiceUrl = "https://npc.mistscale.com", FString PlayerId = "")void ListNPCs(FMistscaleListNPCsResult Callback)—Callback(bool bSuccess, TArray<FMistscaleNPCSummary> Npcs, FMistscaleError Error)void VerifyKey(FMistscaleVerifyKeyResult Callback)—Callback(bool bSuccess, bool bVerified, FMistscaleError Error)UMistscaleNPCConnection* ConnectNPC(FString NpcId, FString PlayerId = "", FString InstanceId = "", bool bAutoReconnect = true, int32 MaxReconnectDelayMs = 30000)
UMistscaleNPCConnection (UObject, returned by ConnectNPC)
Methods: SendChat(Message, SenderId = ""), SendVoiceChunk(TArray<uint8> Data, bEnd = false, SenderId = ""), SetSpatialContext(Location, TimeOfDay = "", Weather = ""), GetEvolutionStatus(), GetQuotaStatus(), Close(). Property: GetState() (Connecting/Open/Closing/Closed).
Delegates (all BlueprintAssignable):
| Delegate | Params | When |
|---|---|---|
| OnOpened | — | socket connected |
| OnClosed | Code, Reason, bExpected | socket closed |
| OnSdkError | FMistscaleError Error | transport-level error |
| OnChatChunk | ChatId, Delta | one streamed token — append |
| OnChatRevision | ChatId, Text | grounding rewrote the reply — replace |
| OnChatMessage | ChatId, Text, bFinalizedByMetadata, FMistscaleChatMetadata Metadata | turn settled |
| OnTranscript | Text | voice message transcribed |
| OnChatBlocked | ChatType, Reason, Limit, Used | quota exceeded |
| OnQuotaStatus | FMistscaleQuotaBucket TextBucket, VoiceBucket | reply to GetQuotaStatus() |
| OnEvolutionStatus | FMistscaleChatMetadata Metadata | reply to GetEvolutionStatus() |
| OnAudio | TArray<uint8> Bytes | synthesized speech (voice replies) |
| OnReconnecting | Attempt, DelayMs | auto-reconnect about to fire |
Same streaming contract as the Web/Godot SDKs: append OnChatChunk deltas, replace on
OnChatRevision, OnChatMessage is the settled state.
FMistscaleError
Kind (Config/Api/Auth/RateLimit/Timeout/Connection), Message, Status, Code,
RetryAfterSeconds. A USTRUCT, not a thrown exception — UE code generally avoids C++
exceptions, so this is returned by value or carried on a delegate, same information the
TypeScript SDK's error classes and the Godot SDK's MistscaleError carry.
Lifetime notes
UMistscaleSubsystemlives for the whole game instance (standardGameInstanceSubsystembehavior) — configure it once, e.g. in your game mode'sBeginPlayor an early Blueprint function library call.- Every
UMistscaleNPCConnectionthe subsystem creates is held with a strongUPROPERTYreference for the subsystem's lifetime, so it won't be garbage collected out from under you even if you don't keep your own reference. CallClose()when you're done with an NPC — this idles the connection (suppresses auto-reconnect, closes the socket) but doesn't destroy the object. For a game holding many short-lived NPC conversations across a long session, be aware this array only grows; nothing currently prunes closed connections out of it.
Validation note
This plugin was written and reviewed against the documented wire protocol and Unreal Engine 5's
documented HTTP/WebSockets/Json/Subsystem C++ APIs, but could not be compiled in the
environment it was written in — no Unreal Engine or Visual Studio/UBT toolchain was available.
Before shipping: drop the plugin into a real UE5 project, regenerate project files, build, and
smoke-test Configure → ListNPCs → ConnectNPC → SendChat against a real project API key.
The riskiest surface for a first compile is Source/MistscaleSDK/Private/MistscaleSubsystem.cpp's
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> usage and
MistscaleNPCConnection.cpp's FTSTicker-based reconnect delay — both were written from memory
of the HTTP/WebSockets/Ticker APIs rather than verified against a live engine install.
