Unreal Engine Integration Guide

Connect Unreal Engine to a Sticklytics relay session for real-time 3D lacrosse stick visualization.

Architecture

BLE sensor → Sticklytics app (processing + physics) → backend relay → Unreal Engine (visualization only).

Sticklytics performs all sensor processing. Unreal polls the relay for processed packets and renders them.

1. Session Setup

In the Sticklytics Devices page, click Start Unreal Visualization. The relay returns a session response:

{
  "sessionId": "session-uuid",
  "status": "active",
  "latestPacketUrl": "/sessions/session-uuid/latest",
  "eventsUrl": "/sessions/session-uuid/events",
  "recommendedPollingIntervalMs": 50,
  "accessToken": "temporary-session-token",
  "expiresAt": "2026-07-29T16:54:21Z"
}

Copy the Session ID and Access Token from the panel into your Unreal project.

2. Authentication

All Unreal requests use the temporary session token:

Authorization: Bearer {accessToken}

Tokens are session-scoped, expire automatically (default 60s of inactivity), and are never the admin key. Do not log or persist the token beyond the session lifetime.

3. Polling the Latest Packet

Unreal polls at a 50ms interval (recommended). Use afterSequence to fetch only newer data:

GET https://api.base44.com/v1/functions/unrealRelay/sessions/{sessionId}/latest?afterSequence=1841
Authorization: Bearer {accessToken}

New data response:

{
  "status": "active",
  "hasNewData": true,
  "latestSequence": 1842,
  "packetAgeMs": 25,
  "serverTimestamp": "ISO timestamp",
  "packet": { ... }
}

No newer data:

{
  "status": "active",
  "hasNewData": false,
  "latestSequence": 1842,
  "packet": null
}

When hasNewData is false, do not re-apply the previous packet — skip the frame.

4. JSON Parsing & Quaternion Application

The packet contains orientation as a quaternion (preferred) and euler degrees (debug):

"orientation": {
  "quaternion": { "w": 0.91, "x": 0.12, "y": -0.18, "z": 0.35 },
  "eulerDegrees": { "pitch": 12.4, "roll": -4.8, "yaw": 87.2 }
}

Unreal uses a left-handed coordinate system (X=forward, Y=right, Z=up). Sticklytics already converts sensor axes to Unreal space via the coordinate profile. Apply the quaternion directly:

// Unreal C++ (pseudocode)
FQuat StickQuat;
StickQuat.W = Packet.orientation.quaternion.w;
StickQuat.X = Packet.orientation.quaternion.x;
StickQuat.Y = Packet.orientation.quaternion.y;
StickQuat.Z = Packet.orientation.quaternion.z;
StickMesh->SetRelativeRotation(StickQuat);

For euler fallback: FRotator(Packet.eulerDegrees.pitch, Packet.eulerDegrees.yaw, Packet.eulerDegrees.roll).

5. Motion Events

Fetch events newer than a sequence:

GET https://api.base44.com/v1/functions/unrealRelay/sessions/{sessionId}/events?afterSequence=1840
Authorization: Bearer {accessToken}

Event types: session_started, calibration_completed, cradle_started, cradle_stopped, pass_detected, shot_windup, shot_released, peak_velocity, impact_detected, sensor_disconnected, sensor_reconnected, session_paused, session_resumed, session_ended.

Trigger Unreal effects (particles, sounds, UI) based on eventType and metrics.

6. Stale Packet Detection & Reconnection

  • If packetAgeMs exceeds 500ms, treat the stick as stale (lerp toward idle).
  • If status is "paused", keep the last pose frozen.
  • If the request returns 410 (SESSION_EXPIRED or SESSION_ENDED), stop polling and prompt the user to start a new session.
  • Network errors: retry with exponential backoff (50ms → 100ms → 200ms). Do not crash Unreal.
  • Sensor disconnects do NOT terminate the relay session — Sticklytics continues processing on reconnect.

7. Session Expiration

Sessions expire after sessionTimeoutSeconds of inactivity (default 60s). The expiresAt field gives the exact expiry. On expiry, all endpoints return 410 with SESSION_EXPIRED.

8. Blueprint Implementation Steps

  1. Create an HTTP GET request node to https://api.base44.com/v1/functions/unrealRelay/sessions/{sessionId}/latest?afterSequence={LastSeq}.
  2. Add the Authorization: Bearer {accessToken} header.
  3. Parse the JSON response; branch on hasNewData.
  4. If new data, extract packet.orientation.quaternion.
  5. Construct an FQuat and call SetRelativeRotation on your stick mesh.
  6. Update LastSeq = latestSequence.
  7. Delay 50ms, loop.
  8. Separately poll /events?afterSequence= at 200ms to trigger effects.

9. Unreal C++ Pseudocode

void AStickVisualizer::Tick(float DeltaTime)
{
    if (bIsPolling) return;
    bIsPolling = true;

    FString Url = FString::Printf(TEXT("%s/sessions/%s/latest?afterSequence=%d"),
        *EndpointBase, *SessionId, LastSequence);

    FHttpRequest* Req = FHttpModule::CreateRequest();
    Req->SetURL(Url);
    Req->SetHeader(TEXT("Authorization"),
        FString::Printf(TEXT("Bearer %s"), *AccessToken));
    Req->OnProcessRequestComplete().BindUObject(this,
        &AStickVisualizer::OnLatestResponse);
    Req->ProcessRequest();
}

void AStickVisualizer::OnLatestResponse(FHttpRequest* Req,
    FHttpResponse* Resp)
{
    bIsPolling = false;
    if (Resp->GetResponseCode() != 200) return;

    const FString Json = Resp->GetContentAsString();
    FPacket Packet;
    if (!FJsonObjectConverter::JsonObjectStringToUStruct(
        Json, &Packet, nullptr, 0))
    {
        return;
    }

    if (!Packet.bHasNewData || !Packet.Packet.IsValid())
    {
        return; // no new data — keep current pose
    }

    LastSequence = Packet.LatestSequence;

    const FQuat StickQuat(
        Packet.Packet.Orientation.Quaternion.X,
        Packet.Packet.Orientation.Quaternion.Y,
        Packet.Packet.Orientation.Quaternion.Z,
        Packet.Packet.Orientation.Quaternion.W);

    StickMesh->SetRelativeRotation(StickQuat);

    // Derived metrics for HUD / particle intensity
    const float SpeedMph =
        Packet.Packet.DerivedMetrics.StickSpeedMph;
}

10. Error Handling

Structured errors:

{
  "error": {
    "code": "SESSION_NOT_FOUND",
    "message": "The requested Unreal relay session does not exist.",
    "retryable": false
  }
}

Codes: SESSION_NOT_FOUND (404), SESSION_EXPIRED (410), SESSION_ENDED (410), STALE_SEQUENCE (409), UNAUTHORIZED (401), FORBIDDEN (403), REPLAY_NOT_ENABLED (400), VALIDATION_ERROR (400).

11. Session Replay

When Record for Replay is enabled, packets and events are stored. Retrieve them:

GET https://api.base44.com/v1/functions/unrealRelay/sessions/{sessionId}/replay?startMs=0&endMs=10000
Authorization: Bearer {accessToken}

Returns an ordered array of packets and events with original timestamps and sequence numbers.