Unreal Engine Integration (C++)

This guide shows how to integrate Scrya ads into an Unreal Engine project using FHttpModule. Works with UE5.x.

Setup

Add HTTP and Json modules to your Build.cs:

// YourGame.Build.cs
PublicDependencyModuleNames.AddRange(new string[] {
    "Core", "CoreUObject", "Engine", "HTTP", "Json", "JsonUtilities"
});

HTTP Helper

// ScryaAds.h
#pragma once
#include "CoreMinimal.h"
#include "Http.h"

class YOURGAME_API FScryaAds
{
public:
    static const FString ApiBase;
    static FString ApiKey;
    static FString GameId;

    static void PostJson(const FString& Endpoint, const FString& JsonBody,
        TFunction<void(FHttpResponsePtr, bool)> Callback);
};

// ScryaAds.cpp
#include "ScryaAds.h"

const FString FScryaAds::ApiBase = TEXT("https://api.scrya.com/api/ads");
FString FScryaAds::ApiKey = TEXT("sk_live_your_key_here");
FString FScryaAds::GameId = TEXT("your_game_id");

void FScryaAds::PostJson(const FString& Endpoint, const FString& JsonBody,
    TFunction<void(FHttpResponsePtr, bool)> Callback)
{
    auto Request = FHttpModule::Get().CreateRequest();
    Request->SetURL(ApiBase + Endpoint);
    Request->SetVerb(TEXT("POST"));
    Request->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
    Request->SetHeader(TEXT("Authorization"), TEXT("Bearer ") + ApiKey);
    Request->SetContentAsString(JsonBody);

    Request->OnProcessRequestComplete().BindLambda(
        [Callback](FHttpRequestPtr Req, FHttpResponsePtr Resp, bool bSuccess)
        {
            Callback(Resp, bSuccess && Resp.IsValid() && Resp->GetResponseCode() == 200);
        });

    Request->ProcessRequest();
}

Step 1: Match an Ad

void UAdComponent::MatchAd(const FString& PlayerId, const FString& Narrative,
    const TArray<FString>& MoodTags)
{
    TSharedPtr<FJsonObject> Root = MakeShareable(new FJsonObject);
    Root->SetStringField("game_id", FScryaAds::GameId);
    Root->SetStringField("user_id", PlayerId);

    // Player geo
    TSharedPtr<FJsonObject> Geo = MakeShareable(new FJsonObject);
    Geo->SetStringField("country_code", "US");
    Root->SetObjectField("player_geo", Geo);

    // Game context
    TSharedPtr<FJsonObject> Context = MakeShareable(new FJsonObject);
    Context->SetStringField("theme_id", "3");
    Context->SetStringField("category", "simulation");
    Context->SetStringField("narrative", Narrative);

    TArray<TSharedPtr<FJsonValue>> Moods;
    for (const auto& Tag : MoodTags)
        Moods.Add(MakeShareable(new FJsonValueString(Tag)));
    Context->SetArrayField("mood_tags", Moods);
    Root->SetObjectField("game_context", Context);

    Root->SetStringField("session_id", FGuid::NewGuid().ToString());

    FString JsonBody;
    auto Writer = TJsonWriterFactory<>::Create(&JsonBody);
    FJsonSerializer::Serialize(Root.ToSharedRef(), Writer);

    FScryaAds::PostJson("/match", JsonBody,
        [this](FHttpResponsePtr Response, bool bSuccess)
        {
            if (!bSuccess)
            {
                UE_LOG(LogTemp, Log, TEXT("No ad matched (normal)"));
                OnAdMatched(false, "", "", "");
                return;
            }

            TSharedPtr<FJsonObject> Json;
            auto Reader = TJsonReaderFactory<>::Create(Response->GetContentAsString());
            FJsonSerializer::Deserialize(Reader, Json);

            FString CampaignId = Json->GetStringField("campaign_id");
            FString CreativeId = Json->GetStringField("creative_id");
            FString Hook = Json->GetStringField("narrative_hook");

            CachedCampaignId = CampaignId;
            CachedCreativeId = CreativeId;

            OnAdMatched(true, CampaignId, CreativeId, Hook);
        });
}

Step 2: Inject the Hook

void UAdComponent::OnAdMatched(bool bMatched, const FString& CampaignId,
    const FString& CreativeId, const FString& NarrativeHook)
{
    if (bMatched)
    {
        // Add to dialogue system
        UDialogueSubsystem* Dialogue = GetWorld()->GetSubsystem<UDialogueSubsystem>();
        if (Dialogue)
        {
            Dialogue->InjectLine(NarrativeHook);
        }
    }
}

Step 3: Record Impression

void UAdComponent::RecordImpression()
{
    if (CachedCampaignId.IsEmpty()) return;

    TSharedPtr<FJsonObject> Root = MakeShareable(new FJsonObject);
    Root->SetStringField("campaign_id", CachedCampaignId);
    Root->SetStringField("creative_id", CachedCreativeId);
    Root->SetStringField("game_id", FScryaAds::GameId);

    FString JsonBody;
    auto Writer = TJsonWriterFactory<>::Create(&JsonBody);
    FJsonSerializer::Serialize(Root.ToSharedRef(), Writer);

    FScryaAds::PostJson("/impressions", JsonBody,
        [](FHttpResponsePtr Response, bool bSuccess)
        {
            UE_LOG(LogTemp, Log, TEXT("Impression %s"),
                bSuccess ? TEXT("recorded") : TEXT("failed"));
        });
}

Next Steps