ゲームエンジンから収集する

ゲームエンジン上で、Flipで分析するために必要なデータを収集する方法を紹介します。

1. 関数を定義する

初めに、後のステップで利用する関数を定義します。これらの関数はHTTP APIをベースに作成されています。

  • 下のコードを、適当なファイルにコピー&ペーストして下さい。

🚧

現時点では、匿名ユーザーのID(anonymousId)を作成・格納するロジックは実装されていません。実装方法が分かる方がいましたら、[email protected]までご連絡ください。

using UnityEngine;
using UnityEngine.Networking;
using System.Collections;
using System.Collections.Generic;
using System.Text;

public class Flip : MonoBehaviour
{
  private const string INGESTION_KEY = "プロジェクトの収集キー"; // プロジェクトの設定画面に表示されている値に変更して下さい
  private const string TRACK_ENDPOINT = "https://app.flip.inc/v1/track";
  private const string IDENTIFY_ENDPOINT = "https://app.flip.inc/v1/identify";

  public void Track(string name, string userId = null, string anonymousId = null,
    Dictionary<string, object> properties = null, Dictionary<string, object> userProperties = null)
  {
    if (userProperties == null)
      userProperties = new Dictionary<string, object>();
    
    userProperties["platform"] = "unity";
  
    var requestBody = new Dictionary<string, object>
    {
      {"name", name},
      {"originalUserId", userId},
      {"anonymousId", anonymousId},
      {"properties", properties},
      {"userProperties", userProperties},
    };
  
    StartCoroutine(Post(TRACK_ENDPOINT, requestBody));
  }

  public void Identify(string userId = null, string anonymousId = null,
    Dictionary<string, object> properties = null)
  {
    if (properties == null)
      properties = new Dictionary<string, object>();
    
    properties["platform"] = "unity";
  
    var requestBody = new Dictionary<string, object>
    {
      {"originalUserId", userId},
      {"anonymousId", anonymousId},
      {"properties", properties},
    };

    StartCoroutine(Post(IDENTIFY_ENDPOINT, requestBody));
  }

  private IEnumerator Post(string endpoint, Dictionary<string, object> requestBody)
  {
    requestBody["ingestionKey"] = INGESTION_KEY;
    
    var requestBodyJson = JsonUtility.ToJson(requestBody);
    var request = new UnityWebRequest(endpoint, "POST");
    request.uploadHandler = new UploadHandlerRaw(Encoding.UTF8.GetBytes(requestBodyJson));
    request.downloadHandler = new DownloadHandlerBuffer();
    request.SetRequestHeader("Content-Type", "application/json");

    yield return request.SendWebRequest();

    if (request.result == UnityWebRequest.Result.ConnectionError || request.result == UnityWebRequest.Result.ProtocolError)
    {
      Debug.LogError($"[Flip] {request.error}");
    }
    else if (!string.IsNullOrEmpty(request.downloadHandler.text))
    {
      var response = JsonUtility.FromJson<Response>(request.downloadHandler.text);
      if (request.responseCode != 200)
      {
        Debug.LogError($"[Flip] {request.responseCode} {response.message}");
      }
    }
  }

  [System.Serializable]
  private class Response
  {
    public string message;
  }
}

////////////////////////
// Flip.hに書く内容
////////////////////////

#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "JsonUtilities.h"
#include "Flip.generated.h"

UCLASS()
class YOUR_PROJECT_NAME_API UFlip : public UObject // YOUR_PROJECT_NAMEは実際のプロジェクト名に変更して下さい
{
  GENERATED_BODY()

public:
  void Track(FString name, 
             FString userId = TEXT(""), 
             FString anonymousId = TEXT(""), 
             TSharedPtr<FJsonObject> properties = nullptr, 
             TSharedPtr<FJsonObject> userProperties = nullptr);

  void Identify(FString userId = TEXT(""), 
                FString anonymousId = TEXT(""), 
                TSharedPtr<FJsonObject> properties = nullptr);

private:
  static const FString INGESTION_KEY;
  static const FString TRACK_ENDPOINT;
  static const FString IDENTIFY_ENDPOINT;
  
  void Post(FString endpoint, TSharedPtr<FJsonObject> requestBody);

  void OnResponseReceived(FHttpRequestPtr request, FHttpResponsePtr response, bool bWasSuccessful);
};

////////////////////////
// Flip.cppに書く内容
////////////////////////

#include "Flip.h"
#include "Runtime/Online/HTTP/Public/Http.h"
#include "JsonUtilities.h"

const FString UFlip::INGESTION_KEY = TEXT("プロジェクトの収集キー"); // プロジェクトの設定画面に表示されている値に変更して下さい
const FString UFlip::TRACK_ENDPOINT = TEXT("https://app.flip.inc/v1/track");
const FString UFlip::IDENTIFY_ENDPOINT = TEXT("https://app.flip.inc/v1/identify");

void UFlip::Track(FString name, FString userId, FString anonymousId, TSharedPtr<FJsonObject> properties, TSharedPtr<FJsonObject> userProperties)
{
  TSharedPtr<FJsonObject> body = MakeShareable(new FJsonObject());

  body->SetStringField(TEXT("name"), name);

  if (!userId.IsEmpty()) body->SetStringField(TEXT("originalUserId"), userId);
  if (!anonymousId.IsEmpty()) body->SetStringField(TEXT("anonymousId"), anonymousId);

  if (properties.IsValid()) body->SetObjectField(TEXT("properties"), properties);
  
  if (!userProperties.IsValid())
  {
    userProperties = MakeShareable(new FJsonObject());
  }
  userProperties->SetStringField(TEXT("platform"), TEXT("unreal"));
  body->SetObjectField(TEXT("userProperties"), userProperties);

  Post(TRACK_ENDPOINT, body);
}

void UFlip::Identify(FString userId, FString anonymousId, TSharedPtr<FJsonObject> properties)
{
  TSharedPtr<FJsonObject> body = MakeShareable(new FJsonObject());
  
  if (!userId.IsEmpty()) body->SetStringField(TEXT("originalUserId"), userId);
  if (!anonymousId.IsEmpty()) body->SetStringField(TEXT("anonymousId"), anonymousId);
  
  if (!properties.IsValid())
  {
    properties = MakeShareable(new FJsonObject());
  }
  properties->SetStringField(TEXT("platform"), TEXT("unreal"));
  body->SetObjectField(TEXT("properties"), properties);

  Post(IDENTIFY_ENDPOINT, body);
}

void UFlip::Post(FString endpoint, TSharedPtr<FJsonObject> requestBody)
{
  requestBody->SetStringField(TEXT("ingestionKey"), INGESTION_KEY);
  
  FString contentString;
  TSharedRef<TJsonWriter<>> writer = TJsonWriterFactory<>::Create(&contentString);
  FJsonSerializer::Serialize(requestBody.ToSharedRef(), writer);

  TSharedRef<IHttpRequest, ESPMode::ThreadSafe> request = FHttpModule::Get().CreateRequest();
  request->SetVerb("POST");
  request->SetURL(endpoint);
  request->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
  request->SetContentAsString(contentString);
  request->OnProcessRequestComplete().BindUObject(this, &UFlip::OnResponseReceived);
  request->ProcessRequest();
}

void UFlip::OnResponseReceived(FHttpRequestPtr request, FHttpResponsePtr response, bool bWasSuccessful)
{
  if (!bWasSuccessful || !response.IsValid())
  {
    UE_LOG(LogTemp, Error, TEXT("[Flip] Failed to get a valid response"));
    return;
  }

  if (!EHttpResponseCodes::IsOk(response->GetResponseCode()))
  {
    TSharedPtr<FJsonObject> body;
    TSharedRef<TJsonReader<>> reader = TJsonReaderFactory<>::Create(response->GetContentAsString());
    if (FJsonSerializer::Deserialize(reader, body) && body.IsValid())
    {
      FString message = body->GetStringField("message");
      UE_LOG(LogTemp, Error, TEXT("[Flip] %d %s"), response->GetResponseCode(), *message);
    }
  }
}

2. イベントを収集する

イベントを収集することで、ユーザーの一つ一つの操作を記録できるようになります。各引数の具体的な役割に関しては、HTTP APIを参考にして下さい。

  • ユーザーが特定の操作を実行した直後(例. 機能を利用)に、次のコードが呼ばれるようにペーストして下さい。コードの内容は自由に変更して構いません。
Flip flip = GetComponent<Flip>();

Dictionary<string, object> properties = new Dictionary<string, object>
{
  {"message_type", "画像付きテキスト"},
  {"character_count", 32}
};
Dictionary<string, object> userProperties = new Dictionary<string, object>
{
  {"email", "[email protected]"},
  {"name", "Tanaka Masaki"},
  {"position", "プロダクトマネージャー"}
};

flip.Track(
  name: "message_sent",
  userId: "usr_GXfryD0JQivyGa97TgGsT",
  properties: properties,
  userProperties: userProperties
);
TSharedPtr<FJsonObject> properties = MakeShareable(new FJsonObject());
properties->SetStringField(TEXT("message_type"), TEXT("画像付きテキスト"));
properties->SetNumberField(TEXT("character_count"), 32);

TSharedPtr<FJsonObject> userProperties = MakeShareable(new FJsonObject());
userProperties->SetStringField(TEXT("email"), TEXT("[email protected]"));
userProperties->SetStringField(TEXT("name"), TEXT("Tanaka Masaki"));
userProperties->SetStringField(TEXT("position"), TEXT("プロダクトマネージャー"));

UFlip* Flip = NewObject<UFlip>();

Flip->Track(TEXT("message_sent"), TEXT("usr_GXfryD0JQivyGa97TgGsT"), TEXT(""), properties, userProperties);

3. ユーザー情報を更新する

下のコードを追加することで、イベントを収集することなく、特定のユーザーの情報を更新できます。各引数の具体的な役割に関しては、HTTP APIを参考にして下さい。

  • ユーザーの情報が変更された直後(例. 新規登録・オンボーディング)に、次のコードが呼ばれるように下のコードをペーストして下さい。コードの内容は自由に変更して構いません。
Flip flip = GetComponent<Flip>();

Dictionary<string, object> properties = new Dictionary<string, object>
{
  {"email", "[email protected]"},
  {"name", "Tanaka Masaki"},
  {"position", "プロダクトマネージャー"}
};

flip.Identify(
  userId: "usr_GXfryD0JQivyGa97TgGsT",
  properties: properties
);
TSharedPtr<FJsonObject> properties = MakeShareable(new FJsonObject());
properties->SetStringField(TEXT("email"), TEXT("[email protected]"));
properties->SetStringField(TEXT("name"), TEXT("Tanaka Masaki"));
properties->SetStringField(TEXT("position"), TEXT("プロダクトマネージャー"));

UFlip* Flip = NewObject<UFlip>();

Flip->Identify(TEXT("usr_GXfryD0JQivyGa97TgGsT"), TEXT(""), properties);