Unity SDK
Framedash Unity SDK를 사용하여 퍼포먼스 텔레메트리를 자동으로 수집하는 방법을 설명합니다.
요구 사항
섹션 제목: “요구 사항”- Unity 2022.3 이상
- .NET Standard 2.1 / .NET Framework 4.x
Unity Package Manager (권장)
섹션 제목: “Unity Package Manager (권장)”- Window > Package Manager를 엽니다. Unity 6.5에서는 이 경로가 Window > Package Management > Package Manager로 이름이 바뀌었습니다. 이전 버전은 짧은 Window > Package Manager 경로를 그대로 유지합니다.
- ”+” > “Add package from git URL…”을 선택
- 다음 URL을 입력합니다:
https://github.com/crane-valley/framedash-unity-sdk.git특정 릴리스로 고정하려면 버전 태그를 추가합니다:
https://github.com/crane-valley/framedash-unity-sdk.git#v0.1.7스크립트나 CI에서 설치하려면 Packages/manifest.json에 의존성을 직접 추가합니다. 패키지 이름은 com.framedash.sdk입니다.
{ "dependencies": { "com.framedash.sdk": "https://github.com/crane-valley/framedash-unity-sdk.git#v0.1.7" }}초기 설정
섹션 제목: “초기 설정”시작 시 SDK를 한 번 초기화합니다. 예를 들어 항상 유지되는 GameObject의 MonoBehaviour에서 초기화합니다:
using System.Collections.Generic;using Framedash;using UnityEngine;
public sealed class GameBootstrap : MonoBehaviour{ // API 키는 Inspector에서 설정하거나, 비워 두어 FRAMEDASH_API_KEY 환경 // 변수로 폴백시킵니다. 해결 순서와 플랫폼 주의 사항은 아래 노트를 // 참고하세요. [SerializeField] private string _apiKey;
private void Awake() { TelemetrySDK.Initialize( apiKey: _apiKey, buildId: Application.version); }}endpointUrl은 선택 사항이며 생략 시 https://ingest.framedash.dev/v1/events가 사용됩니다. 로컬 또는 자체 호스팅 ingest를 지정하려면 명시적으로 전달하세요. playerId는 초기화 시점에 플레이어 ID를 설정하며, 이후에 SetPlayerId를 호출하는 것과 동일합니다. enableOfflineQueue는 기본값이 true이며, false를 전달하면 디스크 오프라인 큐를 비활성화합니다. 전체 시그니처는 Initialize(string apiKey = null, string endpointUrl = null, string buildId = null, string playerId = null, bool enableOfflineQueue = true)입니다.
퍼포먼스 데이터 자동 수집
섹션 제목: “퍼포먼스 데이터 자동 수집”SDK는 다음 데이터를 자동으로 수집합니다:
- FPS: 프레임 레이트
- Frame Time: 프레임당 처리 시간
- Memory: Unity Profiler의 총 할당 메모리
커스텀 이벤트 전송
섹션 제목: “커스텀 이벤트 전송”TelemetrySDK.Instance.Track( eventName: "player_death", mapId: "map_01", position: transform.position);범주형 속성이나 수치 메트릭을 첨부하려면 선택적 딕셔너리를 전달합니다:
// 파일 최상단에 'using System.Collections.Generic;'가 추가되어 있는지 확인하세요.TelemetrySDK.Instance.Track( eventName: "player_death", mapId: "map_01", position: transform.position, attributes: new Dictionary<string, string> { { "cause", "fall_damage" } }, metrics: new Dictionary<string, float> { { "health", 0f } });플레이어 식별 (선택)
섹션 제목: “플레이어 식별 (선택)”기본적으로 이벤트는 익명으로 전송됩니다. 플레이어가 로그인한 후 SetPlayerId를 호출하면 이후 이벤트가 해당 플레이어와 연결됩니다:
TelemetrySDK.Instance.SetPlayerId(playerId);DAU는 비어 있지 않은 플레이어 ID만 계산합니다. 익명 세션도 이벤트 수와 세션 수에는 포함되지만 DAU와 MAU를 늘리지 않습니다. 플레이어 기반 KPI에 포함할 활동 전에 SetPlayerId를 호출하세요.
런타임 샘플링 오버라이드
섹션 제목: “런타임 샘플링 오버라이드”SDK는 전역 샘플링 레이트(기본값 1.0 = 전부 보존)를 적용합니다.
고빈도 이벤트는 런타임에 전역 레이트를 덮어쓰는 이벤트 이름별 레이트를 개별로 지정할 수 있습니다.
TelemetrySDK.Instance.SetEventSamplingRate("ai_pathfind_step", 0.05f); // 약 5%TelemetrySDK.Instance.RemoveEventSamplingRate("ai_pathfind_step"); // 전역 레이트로 복귀SetEventSamplingRate(string eventName, float rate)는 해당 이벤트의 레이트를 설정하며 rate는 [0, 1]로 클램프됩니다.
RemoveEventSamplingRate(string eventName)는 오버라이드를 제거해 이벤트를 전역 레이트로 되돌립니다.
자동 수집 이벤트(session_start, perf_heartbeat)는 샘플링 대상이 아닙니다.
맵 로드 시간 계측
섹션 제목: “맵 로드 시간 계측”Unity SDK 0.1.3 이상에서 사용할 수 있습니다.
SDK는 맵이나 레벨을 로드하는 데 걸린 시간을 계측해 map_load 이벤트로 보고할 수 있습니다.
이 로드 시간은 빌드 비교(perf-diff) 회귀 게이트와 대시보드의 로드 시간 차트에 사용됩니다.
직접 제어하는 로드를 BeginMapLoad / EndMapLoad로 감쌉니다.
TelemetrySDK.Instance.BeginMapLoad("Level_01");// ... 씬 로드 ...TelemetrySDK.Instance.EndMapLoad();BeginMapLoad는 일시정지와 타임 스케일의 영향을 받지 않는 단조 증가 클록에서 타이머를 시작하고, EndMapLoad는 이를 멈추고 이벤트를 발생시킵니다.
EndMapLoad 전에 BeginMapLoad를 다시 호출하면 보류 중인 계측이 교체됩니다.
커스텀 로더나 스트리밍 로더가 이미 시간을 직접 계측한다면, 그 값을 직접 보고할 수 있습니다.
TelemetrySDK.Instance.ReportMapLoad("Level_01", 1234f); // 로드 시간(밀리초)ReportMapLoad는 loadTimeMs가 NaN, 무한대, 음수일 때 해당 샘플을 통째로 버립니다(클램프하지 않습니다).
두 경로 모두 metrics["load_time_ms"]와 attributes["map_name"]를 담은 map_load 이벤트를 발생시킵니다.
이 이벤트는 의도적으로 map_id를 비워 두므로 공간 히트맵과 활성화 게이트의 대상에서 제외됩니다.
이 호출들은 메인 스레드에서 실행되고, 예외를 던지지 않으며, Initialize 전에는 아무 동작도 하지 않습니다.
커스텀 로더나 스트리밍 로더가 워커 스레드에서 완료되는 경우, EndMapLoad나 ReportMapLoad를 호출하기 전에 메인 스레드로 되돌리세요(예: 플레이어 루프 업데이트나 캡처한 SynchronizationContext 사용).
SDK는 스레드 마샬링을 대신 해 주지 않으므로, 다른 스레드에서의 호출은 이벤트를 조용히 버립니다.
디스크 I/O 메트릭
섹션 제목: “디스크 I/O 메트릭”Unity SDK 0.1.3 이상에서 사용할 수 있습니다.
SDK는 디스크 읽기 카운터를 io.read_bytes, io.read_time_ms, io.read_ops 메트릭 키로 perf_heartbeat 이벤트에 첨부할 수 있습니다.
각 값은 이전 heartbeat 이후의 델타이며, 실제 샘플이 들어온 뒤에야 키가 붙습니다(0으로 채우지 않습니다).
다른 성능 메트릭과 마찬가지로 io.*는 perf-diff / builds-compare 회귀 게이트와 대시보드 차트에 사용됩니다.
io.* 임계값 알림은 없습니다.
Unity Editor와 Development Build에서는 SDK가 AsyncReadManagerMetrics를 자동으로 샘플링합니다.
릴리스 플레이어에서는 자동 io.* 샘플이 수집되지 않습니다.
메모리 카테고리 메트릭
섹션 제목: “메모리 카테고리 메트릭”Unity SDK 0.1.4 이상에서 사용할 수 있습니다.
SDK가 heartbeat 주기로 메모리 카테고리별 사용량을 샘플링해 perf_heartbeat 이벤트에 mem.* 메트릭으로 자동 첨부합니다. UE5의 bTrackMemoryDetail과 달리 옵트인이 필요 없습니다.
mem.vram: 그래픽 드라이버가 할당한 메모리(바이트).Profiler.GetAllocatedMemoryForGraphicsDriver에서 읽습니다.mem.heap: 매니지드 힙 사용량(바이트).Profiler.GetMonoUsedSizeLong에서 읽습니다.
읽은 값이 0이면 해당 키를 붙이지 않습니다. 키가 없다는 것은 수집되지 않았음을 뜻하며, SDK는 조작된 0을 보내지 않습니다.
이 키들은 perf_heartbeat 외에 위치가 지정된(map_id가 비어 있지 않은) 이벤트에도 첨부됩니다. perf_heartbeat는 map_id가 비어 있어 공간 히트맵 그리드에 들어가지 않으므로, 셀 단위 메모리 히트맵이 가능하도록 SDK가 동일한 샘플을 위치가 지정된 이벤트에도 싣습니다. 위치가 지정된 이벤트에는 heartbeat 주기로 갱신되는 캐시된 샘플이 실리므로 이벤트 경로에서 엔진을 읽지 않습니다.
호출자가 전달한 메트릭 키는 키 충돌 시에도 용량 면에서도 항상 우선합니다. mem.*는 50개 메트릭 인제스트 상한 아래 남은 슬롯만 채우며, mem.vram을 먼저 넣습니다.
에디터 내 SceneView 히트맵
섹션 제목: “에디터 내 SceneView 히트맵”Unity SDK 0.1.4 이상에서 사용할 수 있습니다.
에디터 전용 Framedash.Editor 어셈블리가 Framedash REST API에서 프로젝트의 맵과 집계된 히트맵 셀을 가져와 Unity 에디터 안에서 렌더링합니다.
analytics:read 스코프를 가진 읽기 API 키(Read API Key)와 프로젝트 ID(Project ID)가 필요하며, 게임이 사용하는 쓰기 전용 Ingest 키가 아닙니다.
Unity SDK 0.1.6 이상에서는 Read API Key를 비워 두고 Unity를 실행하기 전에 FRAMEDASH_ANALYTICS_API_KEY를 설정할 수 있습니다. 환경 변수 값은 UserSettings/에 저장되지 않습니다. Read API Key를 직접 입력하면 그 값이 환경 변수보다 우선합니다.
가져온 히트맵 셀은 기록된 월드 좌표에 맞춰 SceneView에 반투명 쿼드로 그려집니다. 패키지 빌드나 대시보드 없이 에디터 안에서 공간 히트맵을 확인할 수 있습니다.
설정은 프로젝트별로 UserSettings/ 아래에 저장되며, 이 디렉터리는 패키지에 포함되지 않고 버전 관리에도 추적되지 않습니다.
상세 로깅
섹션 제목: “상세 로깅”첫 통합 시에는 상세 로깅을 켜서 전송을 확인하세요.
TelemetrySDK.Instance.VerboseLogging = true;플러시에 성공하면 [Framedash] Flushed N events (HTTP 202)를 기록합니다. SetPlayerId를 호출하기 전까지는 매 세션마다 [Framedash] No player_id set. Events will be sent as anonymous... 경고도 기록되지만, 이는 오류가 아니라 정보성 메시지입니다. 자동 수집 이벤트(초기화 시 session_start, 10초마다 perf_heartbeat)는 수동 이벤트와 같은 배치로 묶이므로, Flushed N events가 직접 트랙한 개수보다 많게 나올 수 있는데 이는 중복이 아니라 정상 동작입니다. 이벤트가 도달하지 않으면 문제 해결을 참고하세요.
헤드리스 / CI
섹션 제목: “헤드리스 / CI”SDK는 Unity 플레이어 루프에서 전송합니다. Flush는 코루틴과 UnityWebRequest를 통해 전송되며, 이들은 루프가 도는 동안에만 진행됩니다. 순수한 Unity.exe -batchmode -executeMethod ... 호출은 Edit 모드에서 실행되어 코루틴이 tick하지 않으므로, 이벤트는 버퍼링되지만 전송되지 않습니다.
CI에서 텔레메트리를 보내려면 PlayMode 테스트(Unity Test Framework)로 플레이어 루프를 구동하세요. Play 모드에 들어가 SDK를 초기화하고, 이벤트를 트래킹하고, 종료 전에 플러시가 완료되도록 테스트를 충분히 오래 실행합니다.
using System.Collections;using Framedash;using NUnit.Framework;using UnityEngine;using UnityEngine.TestTools;
public sealed class TelemetrySmokeTest{ [UnityTest] public IEnumerator SendsAMarkerEvent() { TelemetrySDK.Instance.VerboseLogging = true; // FRAMEDASH_API_KEY 환경 변수에서 키를 읽습니다. 소스에 키를 하드코딩하지 않습니다. TelemetrySDK.Initialize(buildId: "ci-smoke"); // 프로젝트의 전역 SamplingRate가 1.0 미만이면 마커가 샘플링으로 빠질 수 // 있는데, 자동 전송되는 session_start는 HTTP 2xx를 반환하므로 마커 없이도 // 로그 검증만 통과한다(검증 위양성). 이 이벤트의 레이트를 1.0으로 고정해 // 마커가 항상 보존되도록 한다. TelemetrySDK.Instance.SetEventSamplingRate("ci_marker", 1f); TelemetrySDK.Instance.Track(eventName: "ci_marker", mapId: "ci", position: default); // 10초 자동 heartbeat 주기를 넘겨 기다려 perf_heartbeat가 최소 한 번 발생하도록 한 // 뒤, Flush()를 마지막 SDK 호출로 삼아 그 뒤에 아무것도 트래킹하지 않습니다. 기본 // 플러시 주기(30초)가 이 테스트보다 길기 때문에 전송을 강제하고, 종료 전에 HTTP 전송 // 완료를 기다립니다. yield return new WaitForSeconds(12f); TelemetrySDK.Instance.Flush(); // 마지막 플러시. 이벤트 1개로는 배치 임계값에 도달하지 않음 yield return new WaitForSeconds(3f); // 종료 전 최소 대기. 느린 네트워크에서는 전송이 아직 진행 중일 수 있으므로 전달 여부는 아래 HTTP 202 로그 줄로 확인합니다 }}Unity Test Framework가 이 테스트를 컴파일하고 검색할 수 있도록 전용 어셈블리 정의가 필요합니다. 다음 .asmdef를 테스트 파일과 같은 위치에 두세요:
{ "name": "Framedash.SmokeTests", "references": [ "UnityEngine.TestRunner", "Framedash.Runtime" ], "includePlatforms": [], "excludePlatforms": [], "defineConstraints": ["UNITY_INCLUDE_TESTS"], "precompiledReferences": ["nunit.framework.dll"], "autoReferenced": false, "overrideReferences": true}권장 레이아웃은 .asmdef와 테스트 파일을 Assets/Tests/PlayMode/에 함께 두는 것입니다.
Assets/ Tests/ PlayMode/ Framedash.SmokeTests.asmdef TelemetrySmokeTest.csCI에서는 다음 명령으로 테스트를 실행합니다:
Unity.exe -batchmode -nographics -projectPath <path> -runTests -testPlatform PlayMode -testResults <path>\results.xml -logFile -Windows에서는 셸이 실제 종료를 기다리도록 실행하세요. Unity.exe -batchmode ... -runTests는 약 2-3초 만에 셸로 돌아오지만 에디터는 계속 실행되어 테스트는 대략 20초 뒤에야 끝나므로, 즉시 반환된 종료 코드를 읽는 스크립트는 거짓 통과를 보게 됩니다. Start-Process -Wait -PassThru로 실행하고, 종료된 프로세스에서 종료 코드를 읽으세요:
$proc = Start-Process -FilePath "Unity.exe" -Wait -PassThru -ArgumentList @( "-batchmode", "-nographics", "-projectPath", '"<path>"', "-runTests", "-testPlatform", "PlayMode", "-testResults", '"<path>\results.xml"', "-logFile", '"<path>\unity.log"')if ($proc.ExitCode -ne 0) { throw "Unity tests failed (exit code $($proc.ExitCode))" }$resultsPath = "<path>\results.xml"if (-not (Test-Path -LiteralPath $resultsPath)) { throw "Unity did not write test results" }[xml]$results = Get-Content -Raw -LiteralPath $resultsPath$testRun = $results.'test-run'if (-not $testRun) { throw "Invalid test results XML format" }$testCount = if ($testRun.testcasecount) { [int]$testRun.testcasecount } else { [int]$testRun.total }if ($testCount -lt 1) { throw "Unity discovered zero PlayMode tests" }-Wait는 Unity가 실제로 종료될 때까지 블록하고 -PassThru는 프로세스를 반환하므로, $proc.ExitCode가 테스트 결과(0 = 통과)를 반영합니다. 각 경로 자리표시자는 내장 큰따옴표로 감싸므로('"<path>"'), 공백이 포함된 워크스페이스 경로(예: C:\build agent\game)도 Start-Process 인자 분할에서 깨지지 않습니다. -logFile은 -가 아니라 실제 파일로 지정해 나중에 확인할 수 있게 하고, <path>\results.xml을 파싱해 개별 테스트 결과를 확인하세요. 종료 코드만으로는 충분하지 않습니다. Unity는 테스트 0개를 발견해도 성공으로 종료할 수 있으므로 XML에서 최소 1개 결과를 확인해야 합니다.
테스트가 통과했다는 것만으로 텔레메트리가 전달되었다는 증명은 되지 않습니다. 플러시가 아직 전송 중이거나 실패했더라도 테스트는 통과할 수 있기 때문입니다. 파이프라인을 통과로 처리하기 전에 전달을 확인하세요. Unity 로그에서 HTTP 202를 담은 플러시 성공 줄을 grep합니다:
[Framedash] Flushed N events (HTTP 202)또는 REST API로 전송한 마커 이벤트를 조회하세요. 둘 중 하나가 이벤트 도달을 확인한 뒤에만 파이프라인을 통과로 처리하세요.
CI에서는 FRAMEDASH_API_KEY가 키를 공급합니다. 위 샘플은 이에 의존하므로 키를 하드코딩하지 않습니다. TelemetrySDK.Initialize(buildId: ...)(위와 같이) 또는 인자 없는 TelemetrySDK.Initialize()를 호출하세요.
오프라인 큐가 활성화된 경우(기본값), 이벤트는 정상 종료 시 또는 일시적인 전송 실패 시 큐에 보존되며 다음 초기화 시 전송됩니다. 강제 종료 시에는 버퍼에 남은 이벤트가 유실되므로, CI에서는 큐에 의존하지 말고 프로세스를 kill하기 전에 로그의 HTTP 202 줄을 기다리세요. enableOfflineQueue: false인 경우 플러시되지 않은 이벤트는 그대로 폐기됩니다. 문제 해결을 참고하세요.
CI / 자동 세션
섹션 제목: “CI / 자동 세션”자동화 테스트나 프로파일링 실행에서는 세션 전체에 태그를 달아 모든 이벤트가 CI 빌드와 그 branch/commit/scenario를 담도록 하세요. 테스트 진입점에서 자동 세션 API를 한 번 호출합니다:
using Framedash;
// 모든 인수는 선택 사항입니다.TelemetrySDK.Instance.BeginAutomatedSession( buildId: "build-123", branch: "main", commit: "abc1234", scenario: "nightly");// ... 자동화 시나리오 실행 ...TelemetrySDK.Instance.EndAutomatedSession();전체 시그니처는 void BeginAutomatedSession(string buildId = null, string branch = null, string commit = null, string scenario = null)입니다. CI에서는 BeginAutomatedSessionFromEnvironment() 사용을 권장합니다. 이 메서드는 FRAMEDASH_BUILD_ID, FRAMEDASH_GIT_BRANCH, FRAMEDASH_GIT_COMMIT, FRAMEDASH_TEST_SCENARIO(framedash run-profile-test가 내보내는 변수)를 읽습니다:
TelemetrySDK.Instance.BeginAutomatedSessionFromEnvironment();// ... 자동화 시나리오 실행 ...TelemetrySDK.Instance.EndAutomatedSession();자동 세션은 세션 내 모든 이벤트에 build_id 오버라이드와 ci.branch / ci.commit / ci.scenario 속성을 붙이며, 이것이 빌드 비교(perf-diff) CI 게이트에 사용됩니다. 이벤트의 source는 바꾸지 않습니다: SDK 자체의 자동 이벤트(session_start, perf_heartbeat)는 항상 source=automated, 여러분의 Track 이벤트는 항상 source=player이며, 이는 CI에서나 일반 플레이에서나 동일합니다. 전체 파이프라인은 CI 프로파일링을 참고하세요.