:Countly SDK接入與事件設(shè)計避坑指南)
去年秋天接手了一個跑了快兩年的Unity游戲項目玩法迭代到3.2版本渠道方突然甩給我一個問題次留是多少關(guān)卡3到關(guān)卡5之間的流失率又是多少我當(dāng)場有點發(fā)懵——后臺能看到的總下載量和啟動次數(shù)都有但玩家到底在哪一步放棄了游戲、裝備系統(tǒng)上線后有沒有人真的在用、新手引導(dǎo)卡在了哪一屏這些全是黑的。這不是個例。項目做到中期幾乎每個Unity開發(fā)者都會撞上同樣的尷尬功能做了一堆卻沒有一套“行為數(shù)據(jù)”來告訴團(tuán)隊玩家到底買不買賬。當(dāng)時我花了兩三周專門調(diào)研埋點方案最后選了Countly在正式環(huán)境跑了大半年一直很穩(wěn)。這篇文章就是我整理的一份自用記錄核心是記錄Countly在Unity里的接入過程、事件設(shè)計思路和踩過的坑給同樣在Unity里糾結(jié)埋點選型的同行一個參考。全文基于我實際操作的版本記錄不同SDK版本API細(xì)節(jié)可能略有差異但整體思路是通用的。1. 為什么選了Countly需求倒推出來的方案選埋點方案這件事不能拍腦袋。我當(dāng)時的處境是游戲包體不想被第三方SDK拖重數(shù)據(jù)不想放在別人手里被動看報表但又不想從零手寫一套上報服務(wù)。所以篩選維度其實很明確。1.1 我當(dāng)時對比過的幾條路線市面上能給Unity用的埋點方案大致分四類通用統(tǒng)計分析平臺、海外大廠分析服務(wù)、游戲服務(wù)器自帶日志、開源部署方案。我簡單拉了個對比表方案類型代表核心優(yōu)勢當(dāng)時勸退我的點國內(nèi)通用統(tǒng)計友盟、TalkingData接入快中文后臺自定義事件維度偏基礎(chǔ)數(shù)據(jù)出口不在自己手里海外平臺Firebase Analytics免費額度大生態(tài)完善國內(nèi)訪問不穩(wěn)定后臺習(xí)慣差異自建接口自己寫日志上報完全可控報表、漏斗、留存全部要自己弄工期爆炸開源部署Countly、Matomo等數(shù)據(jù)可控、可二次開發(fā)服務(wù)器要自己維護(hù)有運維成本《自用記錄》到這兒其實已經(jīng)能看出傾向了我想要的是“自由度和完整度”的平衡。自建接口聽起來最可控但一份能看的報表系統(tǒng)從數(shù)據(jù)存儲到查詢到前端展示工作量根本不是一個人一兩周能做完的。而直接用現(xiàn)成統(tǒng)計平臺又總覺得事件模型被鎖死。1.2 Countly讓我最終拍板的三個點第一開源社區(qū)版支持私有化部署。也就是說我可以把它裝在自己的服務(wù)器上數(shù)據(jù)不出自己的機器。這一點對我這種對數(shù)據(jù)敏感的項目很重要不需要依賴第三方平臺的行為規(guī)范。第二Countly對Unity有官方SDK。GitHub上有countly-sdk-unity倉庫一直有人維護(hù)不是那種社區(qū)愛好者順帶寫的半成品。SDK支持事件記錄、用戶屬性、崩潰日志、推送通知這些核心能力Unity里直接能調(diào)C#接口。第三學(xué)習(xí)成本可控。Countly的事件模型是典型的“Event Segmentation Count Sum”結(jié)構(gòu)和我之前的埋點經(jīng)驗幾乎一樣不需要重新理解一套新概念。后臺報表也夠用實時數(shù)據(jù)、留存、漏斗、用戶畫像都有。提示如果你鐵了心不想維護(hù)任何服務(wù)器Countly也提供官方云服務(wù)可以直接注冊賬號使用。差別在于數(shù)據(jù)托管在對方環(huán)境但對小團(tuán)隊快速驗證來說完全夠用。2. 接入前必須確認(rèn)的兩件事服務(wù)端地址和應(yīng)用標(biāo)識Countly的接入流程第一步不是寫代碼而是先把服務(wù)端和應(yīng)用標(biāo)識準(zhǔn)備好。很多人在這一步栽跟頭是因為搞混了“服務(wù)器地址”和“后臺地址”。2.1 自建服務(wù)器還是官方云我選的是自建。官方給了一套安裝腳本準(zhǔn)備一臺2核4G的Linux服務(wù)器裝好后通過IP或域名訪問就是Countly后臺。整個過程大概十幾分鐘難點不在安裝而在服務(wù)器本身的網(wǎng)絡(luò)、域名、HTTPS證書這些常規(guī)配置。如果項目組本身有運維資源這一步很省心。如果不方便自建直接去countly.com注冊一個云賬號創(chuàng)建應(yīng)用后同樣能拿到Server URL和App Key。我建議是正式項目無論如何都先確認(rèn)好服務(wù)端URL不要用默認(rèn)IP或者臨時域名去接SDK不然后面改一次URL歷史數(shù)據(jù)就要做遷移合并。2.2 在后臺創(chuàng)建應(yīng)用、拿App Key登錄Countly后臺后在Management里的Applications菜單下可以創(chuàng)建應(yīng)用。創(chuàng)建完會生成一個App Key這串字符是SDK和服務(wù)端通訊的憑證。同時要注意每個平臺Android、iOS、PC建議單獨創(chuàng)建應(yīng)用方便按平臺過濾數(shù)據(jù)。Server URL的寫法有個細(xì)節(jié)接口地址是HTTP根路徑通常填到不帶斜杠的域名或IP端口即可比如https://metrics.yourgame.com而不是https://metrics.yourgame.com/api。SDK會在內(nèi)部拼接請求路徑你多加了/api反而會導(dǎo)致404。2.3 SDK導(dǎo)入的兩種方式和版本坑Countly Unity SDK的導(dǎo)入方式有兩種通過Unity Package Manager用Git URL導(dǎo)入。在Package Manager窗口選擇“Add package from git URL”填入官方倉庫地址比如https://github.com/Countly/countly-sdk-unity.git。這種方式后面要更新SDK版本時比較方便切換到對應(yīng)tag即可。下載官方發(fā)布頁里的unitypackage文件手動導(dǎo)入。適合離線環(huán)境或者公司網(wǎng)絡(luò)訪問不了GitHub的情況。我項目里用的是2020.3 LTS版本的UnitySDK版本在24.x左右C#語法兼容性沒問題。但后來幫朋友看一個老項目時發(fā)現(xiàn)那個項目還停留在Unity 2019SDK拉最新版后會報編譯錯誤因為新SDK用了比較新的C#語法特性。如果你也是老項目寧可找一個和Unity版本匹配的舊SDK tag也別直接拉最新。提示導(dǎo)入SDK后建議立刻在Unity里做一次全量編譯確認(rèn)沒有報錯再往下走。Clue是SDK依賴的幾個DLL是否和項目里已有的插件沖突這一步越早發(fā)現(xiàn)越好。3. 初始化與第一次事件上報核心API的調(diào)用邏輯SDK導(dǎo)入成功后剩下就是代碼層面的活了。我的做法是把初始化放在一個專門的入口腳本里比如叫StartupManager確保在游戲場景的最開始執(zhí)行。3.1 初始化配置逐項拆解新版SDK的初始化方式是通過CountlyConfiguration配置對象using Countly; var config new CountlyConfiguration { ServerUrl https://metrics.yourgame.com, AppKey 你的AppKey, EnableDebug true, EnableConsoleLogging true, EnableManualSessionHandling false }; Countly.Init(config);這幾項配置的含義分別是ServerUrl服務(wù)端地址必須和后臺應(yīng)用所在服務(wù)器一致。AppKey創(chuàng)建應(yīng)用時生成的標(biāo)識復(fù)制時注意不要帶前后空格。EnableDebug是否開啟SDK內(nèi)部調(diào)試日志開發(fā)階段一定打開能看到請求URL和響應(yīng)狀態(tài)。EnableConsoleLogging是否把日志輸出到Unity Console配合上一條使用。EnableManualSessionHandling是否手動控制會話。保持false時就由SDK根據(jù)應(yīng)用前后臺自動開啟/結(jié)束會話這也是Countly默認(rèn)推薦的模式。如果是舊版本SDK可能會看到類似Countly.Init(https://..., appKey)這種極簡寫法。功能上等價但我個人更喜歡新版配置類因為不用記參數(shù)順序。3.2 會話管理在Unity生命周期里的落點Countly的“會話”概念對應(yīng)的是玩家使用App的一段連續(xù)時間。自動模式下SDK會在應(yīng)用啟動時自動開啟會話應(yīng)用切后臺后暫停重新回前臺再恢復(fù)。這對大多數(shù)游戲項目都是合理的。如果你需要更精準(zhǔn)地定義“一局游戲”的開始和結(jié)束比如玩家從大廳進(jìn)入戰(zhàn)斗才算一個會話那就得開啟手動會話管理Countly.SessionBegin(); // 游戲?qū)诌壿?.. Countly.SessionEnd();我早期接的時候其實踩過一個邏輯坑在場景加載時手動調(diào)用SessionBegin但忘了在退出對局時調(diào)用SessionEnd結(jié)果后臺顯示會話時長越累計越長。如果以手動模式管理會話一定要保證Begin和End成對出現(xiàn)最好的辦法是封裝成一個自定義Manager在進(jìn)入和退出場景的鉤子里統(tǒng)一調(diào)用。3.3 第一次驗數(shù)據(jù)后臺看實時事件初始化一旦跑通先別著急鋪量埋點先發(fā)一個沒有參數(shù)的事件驗證鏈路Countly.RecordEvent(app_started);運行游戲后在Countly后臺左側(cè)菜單打開“實時數(shù)據(jù)”如果看到app_started事件開始跳動就說明SDK到服務(wù)端的鏈路是通的。這一步驗證的意義非常大你會發(fā)現(xiàn)很多問題在第一步就暴露了比如AppKey不對、服務(wù)器URL寫錯、HTTPS證書不被信任等。鏈路通了后面所有埋點才有意義。4. 自定義事件的設(shè)計命名規(guī)范、參數(shù)維度和后續(xù)統(tǒng)計Countly事件模塊是整個埋點系統(tǒng)的核心也是我用得最多的功能。它的模型是一個事件由Key、Count、Sum和Segmentation組成。4.1 RecordEvent重載的幾種用法SDK提供了多個重載按常見需求排列// 最簡單的只記錄事件發(fā)生了一次 Countly.RecordEvent(level_complete); // 帶維度參數(shù)比如記錄關(guān)卡完成時獲得了多少顆星 Countly.RecordEvent(level_complete, new Dictionarystring, object { { level, 3 }, { result, win }, { stars, 2 } }); // 帶計數(shù)和總和比如記錄一次內(nèi)購金額可以累加 Countly.RecordEvent(iap_success, new Dictionarystring, object { { product_id, com.xxx.gem100 } }, 1, 6.0f);第三個參數(shù)count表示事件發(fā)生的次數(shù)權(quán)重第四個參數(shù)sum表示數(shù)值累加量。比如一次性購買了6元的道具count1sum6后臺會根據(jù)sum自動統(tǒng)計總收入。4.2 Segmentation參數(shù)的隱藏約束Segmentation是Countly里最靈活也最容易被濫用的地方。它本質(zhì)上是一個字符串到值的字典支持字符串、數(shù)字、布爾等類型。但要注意Segmentation里的值會在后臺以字符串形式存儲和展示。如果你希望對某個維度做數(shù)值區(qū)間排序或平均值計算建議在上報前自己先做一次預(yù)處理把區(qū)間歸好類再傳。我實際使用的規(guī)律是事件Key用下劃線連接全部小寫比如level_start、level_complete、item_equip、iap_success。Segmentation里的Key也是小寫下劃線而Value盡量保持同一種類型。同一事件里這次傳int下次傳string后臺的聚合報表會顯示得很亂。4.3 事件模型設(shè)計的經(jīng)驗從統(tǒng)計需求反推埋點埋點不是把想得到的都記下來而是先想清楚“我要看什么報表”再決定埋什么事件。舉我們項目當(dāng)時的一個例子運營要評估裝備強化系統(tǒng)的健康度需要回答三個問題有多少玩家進(jìn)入了強化頁面從強化頁到首次強化操作轉(zhuǎn)化率是多少每次強化操作消耗了多少金幣根據(jù)這三個問題我們設(shè)計了三個事件equip_strengthen_view、equip_strengthen_click、equip_strengthen_cost。第一個看頁面曝光第二個看轉(zhuǎn)化第三個帶上gold_cost這個sum值算消耗總量。這就比單純的“strengthen”一個事件覆蓋所有情況清晰很多。4.4 長駐事件和一次性事件的區(qū)分有些事件是高頻且短時的比如每次攻擊觸發(fā)一次有些是低頻但有數(shù)值意義的比如每日登錄。Countly官方建議控制事件上報頻率避免短時間產(chǎn)生海量事件把服務(wù)器寫入打滿。如果確實需要統(tǒng)計高頻行為我建議在客戶端先做聚合攢到一定閾值或者按時間批量上報一次而不是每個動作都調(diào)用RecordEvent。我見過一個最典型的反面例子同事把子彈發(fā)射每個frame都記一次事件上線當(dāng)天服務(wù)器CPU就報警了。所以高頻行為一定要做節(jié)流、聚合或者抽樣。5. 用戶屬性與設(shè)備ID從匿名數(shù)據(jù)到可識別用戶只記錄事件看到的是一堆離散的行為點難以關(guān)聯(lián)到具體一個人。Countly提供用戶屬性模塊可以把某個玩家在不同時間、不同設(shè)備產(chǎn)生的行為串聯(lián)起來。5.1 默認(rèn)匿名標(biāo)識和登錄后切換Countly在玩家首次啟動時會生成一個匿名的設(shè)備ID所有后續(xù)事件都掛在這個ID下面。這在用戶未登錄階段夠用了但游戲通常有賬號體系玩家換設(shè)備登錄后服務(wù)器就需要把前后兩段數(shù)據(jù)合并起來。SDK提供了兩個關(guān)鍵方法// 給用戶設(shè)置自定義ID不走合并邏輯謹(jǐn)慎使用 Countly.ChangeDeviceId(player_uid_12345); // 給用戶設(shè)置自定義ID同時把舊ID的數(shù)據(jù)合并到新ID下 Countly.ChangeDeviceIdWithMerge(player_uid_12345);我的建議是在登錄成功回調(diào)里調(diào)用ChangeDeviceIdWithMerge這樣玩家登錄前的匿名行為數(shù)據(jù)比如新手引導(dǎo)進(jìn)度、首啟時間就能完整掛到真實賬號下面。而如果明確知道這是一個全新的賬號不需要保留之前匿名數(shù)據(jù)那用ChangeDeviceId更干凈。5.2 用戶屬性的常用字段Countly用戶屬性包括內(nèi)置字段和自定義字段兩類。內(nèi)置字段有name、username、email、gender、birth year等直接調(diào)用對應(yīng)SetterCountly.UserData.SetProperty(name, 玩家昵稱); Countly.UserData.SetProperty(level, 12); Countly.UserData.SetProperty(vip_level, 3); Countly.UserData.SetProperty(guild_id, g12345); Countly.UserData.Save();這里有個小細(xì)節(jié)舊版SDK里UserData相關(guān)的API可能長這樣新版我記得有些版本改成了通過配置器統(tǒng)一設(shè)置SDK文檔里有明確說明。代碼里以當(dāng)前SDK版本為準(zhǔn)。但核心邏輯沒變先設(shè)置屬性再調(diào)用Save提交到服務(wù)端。如果不調(diào)用Save屬性不會真正上報。5.3 用戶屬性上報時機不要在每一幀都去Save用戶屬性服務(wù)端會有壓力。常規(guī)做法是關(guān)鍵節(jié)點才做一次整體上報比如登錄成功時、玩家等級變化時、重要信息修改時。我通常會把用戶屬性更新的邏輯收斂到一個方法里所有需要更新的地方調(diào)用同一個入口避免散落各處。注意用戶屬性可能涉及個人信息。如果游戲有隱私合規(guī)要求上報前想清楚哪些字段是非必要不可收集的能不上報就不上報能用匿名標(biāo)識的就別用真實手機號或社交賬號。6. 崩潰日志、后臺數(shù)據(jù)核對和調(diào)試經(jīng)驗埋點工作做得再多如果線上崩潰情況一無所知產(chǎn)品迭代還是會心慌。Countly除了事件統(tǒng)計還內(nèi)置了崩潰日志采集能力這一點在Unity項目里尤其有用。6.1 崩潰采集的接入方式在初始化配置里開啟崩潰采集var config new CountlyConfiguration { ServerUrl https://metrics.yourgame.com, AppKey 你的AppKey, CrashReports true, EnableDebug false }; Countly.Init(config);SDK會監(jiān)聽未捕獲的異常App下一次啟動時上報崩潰堆棧。對Unity來說C#層的異常能被捕獲到一些底層Native崩潰也能部分采集。崩潰信息在后臺Crash菜單下按分類展示能看到崩潰次數(shù)、影響的版本和堆棧信息。6.2 調(diào)試模式幫你核對數(shù)據(jù)鏈路開發(fā)階段把EnableDebug打開Unity Console里會看到類似這樣的日志Countly Request: POST https://metrics.yourgame.com/i Payload: {app_key:...,device_id:...,events:[...]}這段日志價值極高。我每次新增埋點時都會先看請求里是否真的帶上了對應(yīng)的事件名和參數(shù)。不要只上代碼不查包我曾經(jīng)遇到過編譯沒報錯但事件名在另一處被覆蓋導(dǎo)致后臺一直收不到數(shù)據(jù)的情況。Console里的payload是最直接的證據(jù)鏈。6.3 排查后臺收不到數(shù)據(jù)的常見原因如果SDK日志顯示請求成功但后臺就是看不到數(shù)據(jù)按順序排查四件事AppKey是否正確復(fù)制完整有沒有混入空格或換行。ServerUrl是否以HTTP/HTTPS開頭且路徑?jīng)]有多余后綴。服務(wù)器是否開啟了“IP白名單校驗”如果開啟了但SDK所在出口IP不在白名單里請求會被拒絕日志里會看到403。設(shè)備時間是否有問題SDK上報時會附本地時間戳設(shè)備時間被玩家篡改到過去幾年上報的數(shù)據(jù)會落到奇怪的時間區(qū)間后臺默認(rèn)篩選看不到。其中IP白名單校驗這個坑我最開始沒意識到查了整整一個下午后來去后臺安全設(shè)置里才發(fā)現(xiàn)默認(rèn)開啟了IP綁定。自建服務(wù)器時建議直接把IP綁定關(guān)掉或者把相關(guān)IP加進(jìn)去不然很容易在換網(wǎng)絡(luò)環(huán)境后突然收不到數(shù)據(jù)。7. 平臺差異和上線后要注意的坑Unity項目不只在編輯器里跑不同平臺的限制會讓同一套代碼的埋點表現(xiàn)完全不同。下面列的是我實際遇到過的平臺相關(guān)問題也是我覺得每個接Countly的Unity項目都要提前看的注意事項。7.1 Android發(fā)布權(quán)限、混淆和Release驗證Android工程需要確認(rèn)AndroidManifest里有網(wǎng)絡(luò)權(quán)限uses-permission android:nameandroid.permission.INTERNET /如果用了代碼混淆ProGuard/R8需要在混淆配置里保留Countly相關(guān)類-keep class ly.count.** { *; }這條我是在線上版本測試時發(fā)現(xiàn)的Release包打出來后其他功能都正常但埋點數(shù)據(jù)明顯偏少一開始還以為是SDK出問題了后來發(fā)現(xiàn)是混淆把SDK內(nèi)部用于反射的類名改了。加了keep規(guī)則后數(shù)據(jù)立刻恢復(fù)正常。7.2 iOS平臺傳輸安全限制iOS默認(rèn)的App Transport Security要求請求必須走HTTPS如果你的Countly服務(wù)器只支持HTTP需要在Info.plist里做例外配置。但既然有選擇我更建議直接給服務(wù)器配好HTTPS證書一勞永逸不僅省了配置也避免被審核重點盯。7.3 WebGL、桌面和包體影響WebGL上跑Countly會遇到跨域問題服務(wù)器需要配置CORS允許瀏覽器環(huán)境下發(fā)起請求。我自己的項目沒上WebGL這方面只是在社區(qū)看到不少人在問可以先記住這個坑真要接的時候再針對性配置。桌面端Windows和macOS反而最簡單基本引用SDK后就能直接跑。包體方面Countly Unity SDK本身只有幾百KB對包體影響很小我這邊的Android包加入SDK后體積增長基本可以忽略。7.4 開發(fā)版和正式版一定要用不同AppKey我專門吃過一次虧開發(fā)階段和正式版用了同一套AppKey結(jié)果開發(fā)機的調(diào)試事件把正式用戶數(shù)據(jù)沖得很亂漏斗分析完全沒法看。后來在Countly后臺給開發(fā)環(huán)境單獨建了一個應(yīng)用用不同的AppKey開發(fā)數(shù)據(jù)和生產(chǎn)數(shù)據(jù)徹底隔離。建議所有從零接SDK的人都從第一天就這么干。這個改起來不復(fù)雜代碼里用一個宏區(qū)分編譯環(huán)境根據(jù)宏選擇不同配置即可#if DEVELOPMENT_BUILD private const string AppKey 開發(fā)環(huán)境AppKey; #else private const string AppKey 正式環(huán)境AppKey; #endif8. 集成后續(xù)的擴展方向和我的體會Countly接入穩(wěn)定后我又陸續(xù)探索了幾個擴展方向這里一并記錄下來當(dāng)作給未來的自己留個備忘。事件和用戶畫像只是Countly的基礎(chǔ)能力。它還有推送通知模塊Unity SDK支持本地通知和遠(yuǎn)程推送可以做到“給7日未登錄用戶發(fā)push”的運營玩法。雖然我的項目暫時沒接但了解到SDK里已經(jīng)有對應(yīng)模塊后面需求來了不用換方案。另一個方向是服務(wù)端查詢能力。Countly提供了REST API可以用來自動導(dǎo)出報表接入團(tuán)隊自己的數(shù)據(jù)看板。如果哪天團(tuán)隊要從“全能型后臺”轉(zhuǎn)到“內(nèi)部數(shù)據(jù)平臺”這層擴展通道是通的。最后說幾句個人體會。埋點這件事初期投入兩三天就能看到效果但真正的價值在線上的長期積累。最忌諱的是先鋪一百個事件再慢慢看而是應(yīng)該先埋幾個關(guān)鍵事件驗證上報、存儲、展示再逐步擴充。我自己的節(jié)奏是每次版本更新只加五到十個精確定義的事件數(shù)據(jù)質(zhì)量反而比一次性大量埋點高得多。再分享一個小技巧作為收尾把全部事件名集中放在一個靜態(tài)類里管理而不是散落在各業(yè)務(wù)腳本中。這樣做的好處是代碼里查事件名不容易拼錯后臺報表里的命名風(fēng)格也統(tǒng)一。后期要下線某個事件時也能一眼看出哪些地方還在引用它。這個習(xí)慣堅持了半年整個項目的事件體系始終清晰可控比任何花哨工具都管用。