Unity3D手游開發(fā)實(shí)踐

本文原創(chuàng)版權(quán)歸 博客園?吳秦?所有,此處純粹技術(shù)收藏,如有再轉(zhuǎn),敬請(qǐng)于顯示位置標(biāo)明原創(chuàng)作者及出處,以示尊重??!

作者:吳秦

出處:http://www.cnblogs.com/skynet/p/5406495.html

本次分享總結(jié),起源于騰訊桌球項(xiàng)目,但是不僅僅限于項(xiàng)目本身。雖然基于Unity3D,很多東西同樣適用于Cocos。本文從以下10大點(diǎn)進(jìn)行闡述:

架構(gòu)設(shè)計(jì)

原生插件/平臺(tái)交互

版本與補(bǔ)丁

用腳本,還是不用?這是一個(gè)問題

資源管理

性能優(yōu)化

異常與Crash

適配與兼容

調(diào)試及開發(fā)工具

項(xiàng)目運(yùn)營(yíng)


1.架構(gòu)設(shè)計(jì)

好的架構(gòu)利用大規(guī)模項(xiàng)目的多人團(tuán)隊(duì)開發(fā)和代碼管理,也利用查找錯(cuò)誤和后期維護(hù)。

框架的選擇:需要根據(jù)團(tuán)隊(duì)、項(xiàng)目來進(jìn)行選擇,沒有最好的框架,只有最合適的框架。

框架的使用:統(tǒng)一的框架能規(guī)范大家的行為,互相之間可以比較平滑切換,可維護(hù)性大大提升。除此之外,還能代碼解耦。例如StrangeIOC是一個(gè)超輕量級(jí)和高度可擴(kuò)展的控制反轉(zhuǎn)(IoC)框架,專門為C#和Unity編寫。已知公司內(nèi)部使用StrangeIOC框架的游戲有:騰訊桌球、歡樂麻將、植物大戰(zhàn)僵尸Online。<https://github.com/strangeioc/strangeioc>


依賴注入(Dependency Injection,簡(jiǎn)稱DI),是一個(gè)重要的面向?qū)ο缶幊痰姆▌t來削減計(jì)算機(jī)程序的耦合問題。依賴注入還有一個(gè)名字叫做控制反轉(zhuǎn)(Inversion of Control,英文縮寫為IoC)。依賴注入是這樣一個(gè)過程:由于某客戶類只依賴于服務(wù)類的一個(gè)接口,而不依賴于具體服務(wù)類,所以客戶類只定義一個(gè)注入點(diǎn)。在程序運(yùn)行過程中,客戶類不直接實(shí)例化具體服務(wù)類實(shí)例,而是客戶類的運(yùn)行上下文環(huán)境專門組件負(fù)責(zé)實(shí)例化服務(wù)類,然后將其注入到客戶類中,保證客戶類的正常運(yùn)行。即對(duì)象在被創(chuàng)建的時(shí)候,由一個(gè)運(yùn)行上下文環(huán)境或?qū)iT組件將其所依賴的服務(wù)類對(duì)象的引用傳遞給它。也可以說,依賴被注入到對(duì)象中。所以,控制反轉(zhuǎn)是,關(guān)于一個(gè)對(duì)象如何獲取他所依賴的對(duì)象的引用,這個(gè)責(zé)任的反轉(zhuǎn)。



StrangeIOC采用MVCS(數(shù)據(jù)模型?Model,展示視圖?View,邏輯控制?Controller,服務(wù)Service)結(jié)構(gòu),通過消息/信號(hào)進(jìn)行交互和通信。整個(gè)MVCS框架跟flash的robotlegs基本一致,(忽略語(yǔ)言不一樣)詳細(xì)的參考<http://www.cnblogs.com/skynet/archive/2012/03/21/2410042.html>。

數(shù)據(jù)模型?Model:主要負(fù)責(zé)數(shù)據(jù)的存儲(chǔ)和基本數(shù)據(jù)處理

展示視圖?View:主要負(fù)責(zé)UI界面展示和動(dòng)畫表現(xiàn)的處理

邏輯控制?Controller:主要負(fù)責(zé)業(yè)務(wù)邏輯處理,

服務(wù)Service:主要負(fù)責(zé)獨(dú)立的網(wǎng)絡(luò)收發(fā)請(qǐng)求等的一些功能。

消息/信號(hào):通過消息/信號(hào)去解耦Model、View、Controller、Service這四種模塊,他們之間通過消息/信號(hào)進(jìn)行交互。

綁定器Binder:負(fù)責(zé)綁定消息處理、接口與實(shí)例對(duì)象、View與Mediator的對(duì)應(yīng)關(guān)系。

MVCS?Context:可以理解為MVC各個(gè)模塊存在的上下文,負(fù)責(zé)MVC綁定和實(shí)例的創(chuàng)建工作。

騰訊桌球客戶端項(xiàng)目框架


代碼目錄的組織:一般客戶端用得比較多的MVC框架,怎么劃分目錄?

先按業(yè)務(wù)功能劃分,再按照 MVC 來劃分。"蛋糕心語(yǔ)"就是使用的這種方式。

先按 MVC 劃分,再按照業(yè)務(wù)功能劃分。"D9"、"寶寶斗場(chǎng)"、"魔法花園"、"騰訊桌球"、"歡樂麻將"使用的這種方式。


根據(jù)使用習(xí)慣,可以自行選擇。個(gè)人推薦"先按業(yè)務(wù)功能劃分,再按照 MVC 來劃分",使得模塊更聚焦(高內(nèi)聚),第二種方式用多了發(fā)現(xiàn)隨著項(xiàng)目的運(yùn)營(yíng)模塊增多,沒有第一種那么好維護(hù)。

Unity項(xiàng)目目錄的組織:結(jié)合Unity規(guī)定的一些特殊的用途的文件夾,我們建議Unity項(xiàng)目文件夾組織方式如下。

其中,Plugins支持Plugins/{Platform}這樣的命名規(guī)范:

Plugins/x86

Plugins/x86_64

Plugins/Android

Plugins/iOS

如果存在Plugins/{Platform},則加載Plugins/{Platform}目錄下的文件,否則加載Plugins目錄下的,也就是說,如果存在{Platform}目錄,Plugins根目錄下的DLL是不會(huì)加載的。

另外,資源組織采用分文件夾存儲(chǔ)"成品資源"及"原料資源"的方式處理:防止無關(guān)資源參與打包,RawResource即原始資源,Resource即成品資源。當(dāng)然并不限于RawResource這種形式,其他Unity規(guī)定的特殊文件夾都可以這樣,例如Raw Standard Assets。


公司組件

msdk(sns、支付midas、推送燈塔、監(jiān)控Bugly)

apollo

apollo voice

xlua

目前我們的騰訊桌球、四國(guó)軍棋都接入了apollo,但是如果服務(wù)器不采用apollo框架,不建議客戶端接apollo,而是直接接msdk減少二次封裝信息的丟失和帶來的錯(cuò)誤,方便以后升級(jí)維護(hù),并且減少導(dǎo)入無用的代碼。


第三方插件選型

NGUI

DoTween

GIF

GAF

VectrosityScripts

PoolManager

Mad Level Manger


2.原生插件/平臺(tái)交互

雖然大多時(shí)候使用Unity3D進(jìn)行游戲開發(fā)時(shí),只需要使用C#進(jìn)行邏輯編寫。但有時(shí)候不可避免的需要使用和編寫原生插件,例如一些第三方插件只提供C/C++原生插件、復(fù)用已有的C/C++模塊等。有一些功能是Unity3D實(shí)現(xiàn)不了,必須要調(diào)用Android/iOS原生接口,比如獲取手機(jī)的硬件信息(UnityEngine.SystemInfo沒有提供的部分)、調(diào)用系統(tǒng)的原生彈窗、手機(jī)震動(dòng)等等


2.1C/C++插件

編寫和使用原生插件的幾個(gè)關(guān)鍵點(diǎn):

創(chuàng)建C/C++原生插件

導(dǎo)出接口必須是C ABI-compatible函數(shù)

函數(shù)調(diào)用約定

在C#中標(biāo)識(shí)C/C++的函數(shù)并調(diào)用

標(biāo)識(shí) DLL 中的函數(shù)。至少指定函數(shù)的名稱和包含該函數(shù)的 DLL 的名稱。

創(chuàng)建用于容納 DLL 函數(shù)的類。可以使用現(xiàn)有類,為每一非托管函數(shù)創(chuàng)建單獨(dú)的類,或者創(chuàng)建包含一組相關(guān)的非托管函數(shù)的一個(gè)類。

在托管代碼中創(chuàng)建原型。使用DllImportAttribute標(biāo)識(shí) DLL 和函數(shù)。?用staticextern修飾符標(biāo)記方法。

調(diào)用 DLL 函數(shù)。像處理其他任何托管方法一樣調(diào)用托管類上的方法。

在C#中創(chuàng)建回調(diào)函數(shù),C/C++調(diào)用C#回調(diào)函數(shù)

創(chuàng)建托管回調(diào)函數(shù)。

創(chuàng)建一個(gè)委托,并將其作為參數(shù)傳遞給?C/C++函數(shù)。平臺(tái)調(diào)用會(huì)自動(dòng)將委托轉(zhuǎn)換為常見的回調(diào)格式。

確保在回調(diào)函數(shù)完成其工作之前,垃圾回收器不會(huì)回收委托。


那么C#與原生插件之間是如何實(shí)現(xiàn)互相調(diào)用的呢?在弄清楚這個(gè)問題之前,我們先看下C#代碼(.NET上的程序)的執(zhí)行的過程:(更詳細(xì)一點(diǎn)的介紹可以參見我之前寫的博客:http://www.cnblogs.com/skynet/archive/2010/05/17/1737028.html

將源碼編譯為托管模塊;

將托管模塊組合為程序集;

加載公共語(yǔ)言運(yùn)行時(shí)CLR;

執(zhí)行程序集代碼。

注:CLR(公共語(yǔ)言運(yùn)行時(shí),Common Language Runtime)和Java虛擬機(jī)一樣也是一個(gè)運(yùn)行時(shí)環(huán)境,它負(fù)責(zé)資源管理(內(nèi)存分配和垃圾收集),并保證應(yīng)用和底層操作系統(tǒng)之間必要的分離。

為了提高平臺(tái)的可靠性,以及為了達(dá)到面向事務(wù)的電子商務(wù)應(yīng)用所要求的穩(wěn)定性級(jí)別,CLR還要負(fù)責(zé)其他一些任務(wù),比如監(jiān)視程序的運(yùn)行。按照.NET的說法,在CLR監(jiān)視之下運(yùn)行的程序?qū)儆?b>"托管"(managed)代碼,而不在CLR之下、直接在裸機(jī)上運(yùn)行的應(yīng)用或者組件屬于"非托管"(unmanaged)的代碼。

這幾個(gè)過程我總結(jié)為下圖:

圖 .NET上的程序運(yùn)行

回調(diào)函數(shù)是托管代碼C#中的定義的函數(shù),對(duì)回調(diào)函數(shù)的調(diào)用,實(shí)現(xiàn)從非托管C/C++代碼中調(diào)用托管C#代碼。那么C/C++是如何調(diào)用C#的呢?大致分為2步,可以用下圖表示:

將回調(diào)函數(shù)指針注冊(cè)到非托管C/C++代碼中(C#中回調(diào)函數(shù)指委托delegate)

調(diào)用注冊(cè)過的托管C#函數(shù)指針

相比較托管調(diào)用非托管,回調(diào)函數(shù)方式稍微復(fù)雜一些。回調(diào)函數(shù)非常適合重復(fù)執(zhí)行的任務(wù)、異步調(diào)用等情況下使用。

由上面的介紹可以知道CLR提供了C#程序運(yùn)行的環(huán)境,與非托管代碼的C/C++交互調(diào)用也由它來完成。CLR提供兩種用于與非托管C/C++代碼進(jìn)行交互的機(jī)制:

平臺(tái)調(diào)用(Platform Invoke,簡(jiǎn)稱PInvoke或者P/Invoke),它使托管代碼能夠調(diào)用從非托管DLL中導(dǎo)出的函數(shù)。

COM 互操作,它使托管代碼能夠通過接口與組件對(duì)象模型 (COM) 對(duì)象交互。考慮跨平臺(tái)性,Unity3D不使用這種方式。

?平臺(tái)調(diào)用依賴于元數(shù)據(jù)在運(yùn)行時(shí)查找導(dǎo)出的函數(shù)并封送(Marshal)其參數(shù)。?下圖顯示了這一過程。

注意:1.除涉及回調(diào)函數(shù)時(shí)以外,平臺(tái)調(diào)用方法調(diào)用從托管代碼流向非托管代碼,而絕不會(huì)以相反方向流動(dòng)。?雖然平臺(tái)調(diào)用的調(diào)用只能從托管代碼流向非托管代碼,但是數(shù)據(jù)仍然可以作為輸入?yún)?shù)或輸出參數(shù)在兩個(gè)方向流動(dòng)。2.圖中DLL表示動(dòng)態(tài)庫(kù),Windows平臺(tái)指.dll文件、Linux/Android指.so文件、Mac OS X指.dylib/framework文件、iOS中只能使用.a。后文都使用DLL代指,并且DLL使用C/C++編寫。

當(dāng)"平臺(tái)調(diào)用"調(diào)用非托管函數(shù)時(shí),它將依次執(zhí)行以下操作:

查找包含該函數(shù)的DLL。

將該DLL加載到內(nèi)存中。

查找函數(shù)在內(nèi)存中的地址并將其參數(shù)推到堆棧上,以封送所需的數(shù)據(jù)(參數(shù))。

注意

只在第一次調(diào)用函數(shù)時(shí),才會(huì)查找和加載 DLL 并查找函數(shù)在內(nèi)存中的地址。iOS中使用的是.a已經(jīng)靜態(tài)打包到最終執(zhí)行文件中。

將控制權(quán)轉(zhuǎn)移給非托管函數(shù)。


?2.2Android插件

Java同樣提供了這樣一個(gè)擴(kuò)展機(jī)制JNI(Java Native Interface),能夠與C/C++互相通信。

注:

JNI wiki-https://en.wikipedia.org/wiki/Java_Native_Interface,這里不深入介紹JNI,有興趣的可以自行去研究。如果你還不知道JNI也不用怕,就像Unity3D使用C/C++庫(kù)一樣,用起來還是比較簡(jiǎn)單的,只需要知道這個(gè)東西即可。并且Unity3D對(duì)C/C++橋接器這塊做了封裝,提供AndroidJNI/AndroidJNIHelper/AndroidJavaObject/AndroidJavaClass/AndroidJavaProxy方便使用等,具體使用后面在介紹。JNI提供了若干的API實(shí)現(xiàn)了Java和其他語(yǔ)言的通信(主要是C&C++)。從Java1.1開始,JNI標(biāo)準(zhǔn)成為java平臺(tái)的一部分,它允許Java代碼和其他語(yǔ)言寫的代碼進(jìn)行交互,保證本地代碼能工作在任何Java?虛擬機(jī)環(huán)境下。"

作為知識(shí)擴(kuò)展,提一下Android Java虛擬機(jī)。Android的Java虛擬機(jī)有2個(gè),最開始是Dalvik,后面Google在Android 4.4系統(tǒng)新增一種應(yīng)用運(yùn)行模式ART。ART與Dalvik 之間的主要區(qū)別是其具有提前 (AOT) 編譯模式。 根據(jù) AOT 概念,設(shè)備安裝應(yīng)用時(shí),DEX 字節(jié)代碼轉(zhuǎn)換僅進(jìn)行一次。 相比于 Dalvik,這樣可實(shí)現(xiàn)真正的優(yōu)勢(shì) ,因?yàn)?Dalvik 的即時(shí) (JIT) 編譯方法需要在每次運(yùn)行應(yīng)用時(shí)都進(jìn)行代碼轉(zhuǎn)換。下文中用Java虛擬機(jī)代指Dalvik/ART。


C#/Java都可以和C/C++通信,那么通過編寫一個(gè)C/C++模塊作為橋接,就使得C#與Java通信成為了可能,如下圖所示:

注:C/C++橋接器本身跟Unity3D沒有直接關(guān)系,不屬于Android和Unity3D,圖中放在Unity3D中是為了代指libunity.so中實(shí)現(xiàn)的橋接器以表示真實(shí)的情況。

通過JNI既可以用于Java代碼調(diào)用C/C++代碼,也可用于C/C++代碼與Java(Dalvik/ART虛擬機(jī))的交互。JNI定義了2個(gè)關(guān)鍵概念/結(jié)構(gòu):JavaVM、JNIENV。JavaVM提供虛擬機(jī)創(chuàng)建、銷毀等操作,Java中一個(gè)進(jìn)程可以創(chuàng)建多個(gè)虛擬機(jī),但是Android一個(gè)進(jìn)程只能有一個(gè)虛擬機(jī)。JNIENV是線程相關(guān)的,對(duì)應(yīng)的是JavaVM中的當(dāng)前線程的JNI環(huán)境,只有附加(attach)到JavaVM的線程才有JNIENV指針,通過JNIEVN指針可以獲取JNI功能,否則不能夠調(diào)用JNI函數(shù)。

C/C++要訪問的Java代碼,必須要能訪問到Java虛擬機(jī),獲取虛擬機(jī)有2中方法:

在加載動(dòng)態(tài)鏈接庫(kù)的時(shí)候,JVM會(huì)調(diào)用JNI_OnLoad(JavaVM* jvm, void* reserved),第一個(gè)參數(shù)會(huì)傳入JavaVM指針。

在C/C++中調(diào)用JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args)創(chuàng)建JavaVM指針

所以,我們只需要在編寫C/C++橋接器so的時(shí)候定義JNI_OnLoad(JavaVM* jvm, void* reserved)方法即可,然后把JavaVM指針保存起來作為上下文使用。

獲取到JavaVM之后,還不能直接拿到JNI函數(shù)去獲取Java代碼,必須通過線程關(guān)聯(lián)的JNIENV指針去獲取。所以,作為一個(gè)好的開發(fā)習(xí)慣在每次獲取一個(gè)線程的JNI相關(guān)功能時(shí),先調(diào)用AttachCurrentThread();又或者每次通過JavaVM指針獲取當(dāng)前的JNIENV:java_vm->GetEnv((void**)&jni_env,?version),一定是已經(jīng)附加到JavaVM的線程。通過JNIENV可以獲取到Java的代碼,例如你想在本地代碼中訪問一個(gè)對(duì)象的字段(field),你可以像下面這樣做:

對(duì)于類,使用jni_env->FindClass獲得類對(duì)象的引用

對(duì)于字段,使用jni_env->GetFieldId獲得字段ID

使用對(duì)應(yīng)的方法(例如jni_env->GetIntField)獲取字段的值

類似地,要調(diào)用一個(gè)方法,你step1.得獲得一個(gè)類對(duì)象的引用obj,step2.是方法methodID。這些ID通常是指向運(yùn)行時(shí)內(nèi)部數(shù)據(jù)結(jié)構(gòu)。查找到它們需要些字符串比較,但一旦你實(shí)際去執(zhí)行它們獲得字段或者做方法調(diào)用是非常快的。step3.調(diào)用jni_env->CallVoidMethodV(obj,?methodID,?args)

從上面的示例代碼,我們可以看出使用原始的JNI方式去與Android(Java)插件交互是多的繁瑣,要自己做太多的事情,并且為了性能需要自己考慮緩存查詢到的方法ID,字段ID等等。幸運(yùn)的是,Unity3D已經(jīng)為我們封裝好了這些,并且考慮了性能優(yōu)化。Unity3D主要提供了一下2個(gè)級(jí)別的封裝來幫助高效編寫代碼:

注:Unity3D中對(duì)應(yīng)的C/C++橋接器包含在libunity.so中。

Level 1:AndroidJNI、AndroidJNIHelper,原始的封裝相當(dāng)于我們上面自己編寫的C# Wrapper。AndroidJNIHelper?和AndroidJNI自動(dòng)完成了很多任務(wù)(指找到類定義,構(gòu)造方法等),并且使用緩存使調(diào)用java速度更快。AndroidJavaObject和AndroidJavaClass基于AndroidJNIHelper?和AndroidJNI創(chuàng)建,但在處理自動(dòng)完成部分也有很多自己的邏輯,這些類也有靜態(tài)的版本,用來訪問java類的靜態(tài)成員。更詳細(xì)接口參考幫助文檔:http://docs.unity3d.com/ScriptReference/AndroidJNI.html,http://docs.unity3d.com/ScriptReference/AndroidJNIHelper.html

Level 2:AndroidJavaObject、AndroidJavaClass、AndroidJavaProxy,這個(gè)3個(gè)類是基于Level1的封裝提供了更高層級(jí)的封裝使用起來更簡(jiǎn)單,這個(gè)在第三部分詳細(xì)介紹。


2.3iOS插件

iOS編寫插件比Android要簡(jiǎn)單很多,因?yàn)镺bjective-C也是 C-compatible的,完全兼容標(biāo)準(zhǔn)C語(yǔ)言。這些就可以非常簡(jiǎn)單的包一層 extern "c"{},用C語(yǔ)言封裝調(diào)用iOS功能,暴露給Unity3D調(diào)用。并且可以跟原生C/C++庫(kù)一樣編成.a插件。C#與iOS(Objective-C)通信的原理跟C/C++完全一樣:



除此之外,Unity iOS支持插件自動(dòng)集成方式。所有位于Asset/Plugings/iOS文件夾中后綴名為.m , .mm , .c , .cpp的文件都將自動(dòng)并入到已生成的Xcode項(xiàng)目中。然而,最終編進(jìn)執(zhí)行文件中。后綴為.h的文件不能被包含在Xcode的項(xiàng)目樹中,但他們將出現(xiàn)在目標(biāo)文件系統(tǒng)中,從而使.m/.mm/.c/.cpp文件編譯。這樣編寫iOS插件,除了需要對(duì)iOS Objective-C有一定了解之外,與C/C++插件沒有差異,反而更簡(jiǎn)單。


3.版本與補(bǔ)丁

任何游戲(端游、手游)都應(yīng)該提供游戲內(nèi)更新的途徑。一般游戲分為全量更新/整包更新、增量更新、資源更新。

全量

android游戲內(nèi)完整安裝包下載(ios跳轉(zhuǎn)到AppStore下載)


增量:主要指android省流量更新

可以使用bsdiff生成patch包

應(yīng)用寶也提供增量更新sdk可供接入


資源

Unity3D通過使用AssetBundle即可實(shí)現(xiàn)動(dòng)態(tài)更新資源的功能。


手游在實(shí)現(xiàn)這塊時(shí)需要注意的幾點(diǎn):

游戲發(fā)布出一定要提供游戲內(nèi)更新的途徑。即使是刪掉測(cè)試,保不準(zhǔn)這期間需要進(jìn)行資源或者BUG修復(fù)更新。很多玩家并不知道如何更新,而且Android手機(jī)應(yīng)用分發(fā)平臺(tái)多樣,分發(fā)平臺(tái)本身也不會(huì)跟官方同步更新(特別是小的分發(fā)平臺(tái))。

更新功能要提供強(qiáng)制更新、非強(qiáng)制更新配置化選項(xiàng),并指定哪些版本可以不強(qiáng)更,哪些版本必須強(qiáng)更。

當(dāng)游戲提供非強(qiáng)制更新功能之后,現(xiàn)網(wǎng)一定會(huì)存在多個(gè)版本。如果需要針對(duì)不同版本做不同的更新,例如配置文件A針對(duì)1.0.0.1修改了一項(xiàng),針對(duì)1.0.0.2修改了另一項(xiàng),2個(gè)版本需要分別更新對(duì)應(yīng)的修改,需要自己實(shí)現(xiàn)更新策略IIPS不提供這個(gè)功能。當(dāng)需要復(fù)雜的更新策略,推薦自己編寫更新服務(wù)器和客戶端邏輯,不使用iips組件(其實(shí)自己實(shí)現(xiàn)也很簡(jiǎn)單)。

沒有運(yùn)營(yíng)經(jīng)驗(yàn)的人會(huì)選擇二進(jìn)制,認(rèn)為二進(jìn)制安全、更小,這對(duì)端游/手游外網(wǎng)只存在一個(gè)版本的游戲適合,對(duì)一般不強(qiáng)升版本的手游并不適合,反而會(huì)對(duì)更新和維護(hù)帶來很大的麻煩。

配置使用XML或者JSON等文本格式,更利于多版本的兼容和更新。最開始騰訊桌球客戶端使用的二進(jìn)制格式(由excel轉(zhuǎn)換而來),但是隨著運(yùn)營(yíng)配置格式需要增加字段,這樣老版本程序就解析不了新的二進(jìn)制數(shù)據(jù),給兼容和更新帶來了很大的麻煩。這樣就要求上面提到的針對(duì)不同步做不同更新,又或者配置一開始就預(yù)留好足夠的擴(kuò)展項(xiàng),其實(shí)不管怎么預(yù)留擴(kuò)展也很難跟上需求的變化,而且一開始會(huì)把配置表復(fù)雜化但是其實(shí)只有一張或者幾張才會(huì)變更結(jié)構(gòu)。


iOS版本的送審版本需要連接特定的包含新內(nèi)容的服務(wù)器,現(xiàn)網(wǎng)服務(wù)器還不包含新內(nèi)容。送審?fù)ㄟ^之后,上架游戲現(xiàn)網(wǎng)服務(wù)器會(huì)進(jìn)行更新,iOS版本需要連接現(xiàn)網(wǎng)服務(wù)器而非送審服務(wù)器,但是這期間又不能修改客戶度,這個(gè)切換需要通過服務(wù)器下發(fā)開關(guān)進(jìn)行控制。例如通過指定送審的iOS游戲版本號(hào),客戶端判斷本地版本號(hào)是否為送審版本,如果是連接送審服務(wù)器,否則連接現(xiàn)網(wǎng)服務(wù)器。


4.用腳本,還是不用?這是一個(gè)問題

方便更新,減少Crash(特別是使用C++的cocos引擎)

通過上面一節(jié)【版本與補(bǔ)丁】知道要實(shí)現(xiàn)代碼更新是非常困難的,正式這個(gè)原因客戶端開發(fā)的壓力是比較大的,如果出現(xiàn)了比較嚴(yán)重的BUG必須發(fā)強(qiáng)制更新版本,使用腳本可以解決這個(gè)問題。

由于Unity3D手游更新成本比較大,而且目前騰訊桌球要求不能強(qiáng)制更新,這導(dǎo)致新版本的活動(dòng)覆蓋率提升比較慢、出現(xiàn)問題之后難以修復(fù)。針對(duì)這個(gè)情況,考慮引入lua進(jìn)行活動(dòng)開發(fā),后續(xù)發(fā)布活動(dòng)及修復(fù)bug只需要發(fā)布lua資源,進(jìn)行資源更新即可,大大降低了發(fā)布和修復(fù)問題的成本。

可選方案還有使用Html5進(jìn)行活動(dòng)開發(fā),目前游戲中已經(jīng)預(yù)埋了Html5活動(dòng)入口,并且已經(jīng)用來發(fā)過"玩家調(diào)查"、"騰訊棋牌宣傳"等。但是與lua對(duì)比,不能做到與Unity3D的深度融合,體驗(yàn)不如使用lua,例如不能操作游戲中的ui、不能完成復(fù)雜界面的制作、不能復(fù)用已有的功能、玩家付費(fèi)充值跟已有的也會(huì)有差異


游戲腳本之王——Lua

在公司內(nèi)部魔方比較喜歡用lua,火隱忍者(手游)unity+ulua,全民水滸cocos2d-x+lua等等都有使用lua進(jìn)行開發(fā)。我們可以使用公司內(nèi)部的xlua組件,也可以使用ulua<http://ulua.org/>、UniLua<https://github.com/xebecnan/UniLua>等等。


5.資源管理


5.1資源管理器


? 業(yè)務(wù)不要直接使用引擎或者系統(tǒng)原生接口,而是封裝一個(gè)資源管理器負(fù)責(zé):資源加載、卸載

? 兼容Resource.Load與AssetBundle資源互相變更需求,開發(fā)期間使用Resource.Load方式而不必打AB包效率更高

? 加載資源時(shí),不管是同步加載還是異步加載,最好是使用異步編碼方式(回調(diào)函數(shù)或者消息通知機(jī)制)。如果哪一天資源由本地加載改為從服務(wù)器按需加載,而游戲中的邏輯都是同步方式編碼的,改起來將非常痛苦。其實(shí)異步編碼方式很簡(jiǎn)單,不比同步方式復(fù)雜。


5.2資源類型


圖片/紋理(對(duì)性能、包體影響最大因素)

音頻

背景音樂,騰訊桌球使用.ogg/.mp3

音效,騰訊桌球使用.wav

數(shù)據(jù)

文本

二進(jìn)制

動(dòng)畫/特效


5.3圖片-文件格式與紋理格式

常用的圖像文件格式有BMP,TGA,JPG,GIF,PNG等;

常用的紋理格式有R5G6B5,A4R4G4B4,A1R5G5B5,R8G8B8, A8R8G8B8等。


文件格式是圖像為了存儲(chǔ)信息而使用的對(duì)信息的特殊編碼方式,它存儲(chǔ)在磁盤中,或者內(nèi)存中,但是并不能被GPU所識(shí)別,因?yàn)橐韵蛄坑?jì)算見長(zhǎng)的GPU對(duì)于這些復(fù)雜的計(jì)算無能為力。這些文件格式當(dāng)被游戲讀入后,還是需要經(jīng)過CPU解壓成R5G6B5,A4R4G4B4,A1R5G5B5,R8G8B8, A8R8G8B8等像素格式,再傳送到GPU端進(jìn)行使用。

紋理格式是能被GPU所識(shí)別的像素格式,能被快速尋址并采樣。舉個(gè)例子,DDS文件是游戲開發(fā)中常用的文件格式,它內(nèi)部可以包含A4R4G4B4的紋理格式,也可以包含A8R8G8B8的紋理格式,甚至可以包含DXT1的紋理格式。在這里DDS文件有點(diǎn)容器的意味。OpenGL ES 2.0支持以上提到的R5G6B5,A4R4G4B4,A1R5G5B5,R8G8B8,A8R8G8B8等紋理格式,其中 R5G6B5,A4R4G4B4,A1R5G5B5每個(gè)像素占用2個(gè)字節(jié)(BYTE),R8G8B8每個(gè)像素占用3個(gè)字節(jié),A8R8G8B8每個(gè)像素占用 4個(gè)字節(jié)。


基于OpenGL ES的壓縮紋理有常見的如下幾種實(shí)現(xiàn):

1)ETC1(Ericsson texture compression),ETC1格式是OpenGL ES圖形標(biāo)準(zhǔn)的一部分,并且被所有的Android設(shè)備所支持。

2)PVRTC (PowerVR texture compression),支持的GPU為Imagination Technologies的PowerVR SGX系列。

3)ATITC (ATI texture compression),支持的GPU為Qualcomm的Adreno系列。

4)S3TC (S3 texture compression),也被稱為DXTC,在PC上廣泛被使用,但是在移動(dòng)設(shè)備上還是屬于新鮮事物。支持的GPU為NVIDIA Tegra系列。



5.4資源工具

有了規(guī)范就可以做工具檢查,從源頭到打包

資源導(dǎo)入檢查

資源打包檢查


6.性能優(yōu)化

掉幀主要針對(duì)GPU和CPU做分析;內(nèi)存占用大主要針對(duì)美術(shù)資源,音效,配置表,緩存等分析;卡頓也需要對(duì)GPU和CPU峰值分析,另外IO或者GC也易導(dǎo)致。

6.1工欲善其事,必先利其器


Unity Profiler

XCode?instruments

Qualcomm?Adreno Profiler

NVIDIA PerfHUD ES Tegra


6.2CPU:最佳原則減少計(jì)算

復(fù)用,UIScrollView Item復(fù)用,避免頻繁創(chuàng)建銷毀對(duì)象

緩存,例如Transform

運(yùn)算裁剪,例如碰撞檢測(cè)裁剪

粗略碰撞檢測(cè)(劃分空間——二分/四叉樹/八叉樹/網(wǎng)格等,降低碰撞檢測(cè)的數(shù)量)

精確碰撞檢測(cè)(檢查候選碰撞結(jié)果,進(jìn)而確定對(duì)象是否真實(shí)發(fā)生碰撞)

休眠機(jī)制:避免模擬靜止的球

邏輯幀與渲染幀分離

分幀處理

異步/多線程處理


6.3GPU:最佳原則減少渲染

紋理壓縮

批處理減少DrawCall(unity-Static Batching和Dynamic Batching,cocos SpriteBatchNode)

減少無效/不必要繪制:屏幕外的裁剪,F(xiàn)lash臟矩陣算法,

LOD/特效分檔

NGUI動(dòng)靜分離(UIPanel.LateUpdate的消耗)

控制角色骨骼數(shù)、模型面數(shù)/頂點(diǎn)數(shù)

降幀,并非所有場(chǎng)景都需要60幀(騰訊桌球游戲場(chǎng)景60幀,其他場(chǎng)景30幀;天天酷跑,在開始游戲前,F(xiàn)PS被限制為30,游戲開始之后FPS才為60。天天飛車的FPS為30,但是當(dāng)用戶一段時(shí)間不點(diǎn)擊界面后,F(xiàn)PS自動(dòng)降)


6.4內(nèi)存:最佳原則減少內(nèi)存分配/碎片、及時(shí)釋放

紋理壓縮-Android ETC1、iOS PVRTC 4bpp、windows DXT5

對(duì)象池-PoolManager

合并空閑圖集

UI九宮格

刪除不用的腳本(也會(huì)占用內(nèi)存)


6.5IO:最佳原則減少/異步io


資源異步/多線程加載

預(yù)加載

文件壓縮

合理規(guī)劃資源合并打包,并非texturepacker打包成大圖集一定好,會(huì)增加文件io時(shí)間


6.6網(wǎng)絡(luò):其實(shí)也是IO的一種

使用單線程——共用UI線程,通過事件/UI循環(huán)驅(qū)動(dòng);還是多線程——單獨(dú)的網(wǎng)絡(luò)線程?

單線程:由游戲循環(huán)(事件)驅(qū)動(dòng),單線程模式比使用多線程模式開發(fā)、維護(hù)簡(jiǎn)單很多,但是性能比多線程要差一些,所以在網(wǎng)絡(luò)IO的時(shí)候,需要注意別阻塞到游戲循環(huán)。說明,如果網(wǎng)絡(luò)IO不復(fù)雜的情況下,推薦使用該模式。

在UI線程中,別調(diào)用可能阻塞的網(wǎng)絡(luò)函數(shù),優(yōu)先考慮非阻塞IO

這是網(wǎng)絡(luò)開發(fā)者經(jīng)常犯的錯(cuò)誤之一。比如:做一個(gè)簡(jiǎn)單如 gethostbyname() 的調(diào)用,這個(gè)操作在小范圍中不會(huì)存在任何問題,但是在有些情況中現(xiàn)實(shí)世界的玩家卻會(huì)因此阻塞數(shù)分鐘之久!如果你在 GUI 線程中調(diào)用這樣一個(gè)函數(shù),對(duì)于用戶來說,在函數(shù)阻塞時(shí),GUI 一直都處于 frozen 或者 hanged 狀態(tài),這從用戶體驗(yàn)的角度是絕對(duì)不允許的。

多線程:?jiǎn)为?dú)的網(wǎng)絡(luò)線程,使用獨(dú)立的網(wǎng)絡(luò)線程有一個(gè)非常明顯的好處,主線程可以將臟活、累活交給網(wǎng)絡(luò)線程做使得UI更流暢,例如消息的編解碼、加解密工作,這些都是非常耗時(shí)的。但是使用多線程,給開發(fā)和維護(hù)帶來一定成本,并且如果沒有一定的經(jīng)驗(yàn)寫出來的網(wǎng)絡(luò)庫(kù)不那么穩(wěn)定,容易出錯(cuò),甚至導(dǎo)致游戲崩潰。下面是幾點(diǎn)注意事項(xiàng):

千萬(wàn)千萬(wàn)別在網(wǎng)絡(luò)線程中,回調(diào)主線程(UI線程)的回調(diào)函數(shù)。而是網(wǎng)絡(luò)線程將數(shù)據(jù)準(zhǔn)備好,讓主線程主動(dòng)去取,亦或者說網(wǎng)絡(luò)線程將網(wǎng)絡(luò)數(shù)據(jù)作為一個(gè)事件驅(qū)動(dòng)主線程去取。當(dāng)年我在用Cocos2d-x + Lua做魔法花園的手機(jī)demo時(shí),就采用的多線程模式,最初在網(wǎng)絡(luò)線程直接調(diào)用主線程回調(diào)函數(shù),經(jīng)常會(huì)導(dǎo)致莫名其妙的Crash。因?yàn)榫W(wǎng)絡(luò)線程中沒有渲染所必須的opengl上下文,會(huì)導(dǎo)致渲染出問題而Crash。

6.6包大小


使用壓縮格式的紋理/音頻

?盡量不要使用System.Xml而使用較小的Mono.Xml

?啟用Stripping來減小庫(kù)的大小

?Unity strip level(strip by byte code)

?Unity3D輸出APK,取消X86架構(gòu)

?iOS?Xcode?strip開啟


6.7耗電

下面影響耗電的幾個(gè)因素和影響度摘自公司內(nèi)部的一篇文章。



7.異常與Crash

7.1防御式編程


非法的輸入中保護(hù)你的程序

檢查每一輸入?yún)?shù)

檢查來自外部的數(shù)據(jù)/資源

斷言

錯(cuò)誤處理

隔欄


防不勝防,不管如何防御總有失手的時(shí)候,這就需要異常捕獲和上報(bào)。


7.2異常捕獲

異常捕獲已經(jīng)有很多第三組件可供接入,這里不介紹組件的而接入,而是簡(jiǎn)單談一下異常捕獲的原理。

由于很多錯(cuò)誤并不是發(fā)生在開發(fā)工作者調(diào)試階段,而是在用戶或測(cè)試工作者使用階段;這就需要相關(guān)代碼維護(hù)工作者對(duì)于程序異常捕獲收集現(xiàn)場(chǎng)信息。異常與Crash的監(jiān)控和上報(bào),這里不介紹Bugly的使用,按照apollo或者msdk的文檔接入即可,沒有太多可以說的。這里主要透過Bugly介紹手游的幾類異常的捕獲和分析:

Unity3D C#層異常捕獲:比較簡(jiǎn)單使用Application.RegisterLogCallback/Application.RegisterLogCallbackThreaded(在一個(gè)新的線程中調(diào)用委托)注冊(cè)回調(diào)函數(shù)。特別注意:保證項(xiàng)目中只有一個(gè)Application.RegisterLogCallback注冊(cè)回調(diào),否則后面注冊(cè)的會(huì)覆蓋前面注冊(cè)的回調(diào)!回調(diào)函數(shù)中stackTrace參數(shù)包異常調(diào)用棧。

public?void?HandleLog(string?logString,?string?stackTrace,?LogType?type)

{

if?(logString?==?null?||?logString.StartsWith(cLogPrefix))

{

return;

}


ELogLevel?level?=?ELogLevel.Verbose;

switch?(type)

{


case?LogType.Exception:

level?=?ELogLevel.Error;

break;

default:

return;

}


if?(stackTrace?!=?null)

{

Print(level,?ELogTag.UnityLog,?logString?+?"\n"?+?stackTrace);

}

else

{

Print(level,?ELogTag.UnityLog,?logString);

}

}


Android Java層異常捕獲

try…catch顯式的捕獲異常一般是不引起游戲Crash的,它又稱為編譯時(shí)異常,即在編譯階段被處理的異常。編譯器會(huì)強(qiáng)制程序處理所有的Checked異常,因?yàn)镴ava認(rèn)為這類異常都是可以被處理(修復(fù))的。如果沒有try…catch這個(gè)異常,則編譯出錯(cuò),錯(cuò)誤提示類似于"Unhandled?exception?type?xxxxx"。

UnChecked異常又稱為運(yùn)行時(shí)異常,由于沒有相應(yīng)的try…catch處理該異常對(duì)象,所以Java運(yùn)行環(huán)境將會(huì)終止,程序?qū)⑼顺?,也就是我們所說的Crash。那為什么不會(huì)加在try…catch呢?

無法將所有的代碼都加上try…catch

UnChecked異常通常都是較為嚴(yán)重的異常,或者說已經(jīng)破壞了運(yùn)行環(huán)境的。比如內(nèi)存地址,即使我們try…catch住了,也不能明確知道如何處理該異常,才能保證程序接下來的運(yùn)行是正確的。

Uncaught異常會(huì)導(dǎo)致應(yīng)用程序崩潰。那么當(dāng)崩潰了,我們是否可以做些什么呢,就像Application.RegisterLogCallback注冊(cè)回調(diào)打印日志、上報(bào)服務(wù)器、彈窗提示用戶?Java提供了一個(gè)接口給我們,可以完成這些,這就是UncaughtExceptionHandler,該接口含有一個(gè)純虛函數(shù):

public?abstract?void?uncaughtException?(Thread?thread,?Throwableex)

Uncaught異常發(fā)生時(shí)會(huì)終止線程,此時(shí),系統(tǒng)便會(huì)通知UncaughtExceptionHandler,告訴它被終止的線程以及對(duì)應(yīng)的異常,然后便會(huì)調(diào)用uncaughtException函數(shù)。如果該handler沒有被顯式設(shè)置,則會(huì)調(diào)用對(duì)應(yīng)線程組的默認(rèn)handler。如果我們要捕獲該異常,必須實(shí)現(xiàn)我們自己的handler,并通過以下函數(shù)進(jìn)行設(shè)置:

public?static?void?setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler?handler)

特別注意:多次調(diào)用setDefaultUncaughtExceptionHandler設(shè)置handler,后面注冊(cè)的會(huì)覆蓋前面注冊(cè)的,以最后一次為準(zhǔn)。實(shí)現(xiàn)自定義的handler,只需要繼承UncaughtExceptionHandler該接口,并實(shí)現(xiàn)uncaughtException方法即可。

static?class?MyCrashHandler?implements?UncaughtExceptionHandler{??

@Override??

public?void?uncaughtException(Thread?thread,?final?Throwable?throwable)?{??

//?Deal?this?exception

}

}

在任何線程中,都可以通過setDefaultUncaughtExceptionHandler來設(shè)置handler,但在Android應(yīng)用程序中,全局的Application和Activity、Service都同屬于UI主線程,線程名稱默認(rèn)為"main"。所以,在Application中應(yīng)該為UI主線程添加UncaughtExceptionHandler,這樣整個(gè)程序中的Activity、Service中出現(xiàn)的UncaughtException事件都可以被處理。

捕獲Exception之后,我們還需要知道崩潰堆棧的信息,這樣有助于我們分析崩潰的原因,查找代碼的Bug。異常對(duì)象的printStackTrace方法用于打印異常的堆棧信息,根據(jù)printStackTrace方法的輸出結(jié)果,我們可以找到異常的源頭,并跟蹤到異常一路觸發(fā)的過程。

public?static?String?getStackTraceInfo(final?Throwable?throwable)?{

String?trace?=?"";????try?{

Writer?writer?=?new?StringWriter();

PrintWriter?pw?=?new?PrintWriter(writer);

throwable.printStackTrace(pw);

trace?=?writer.toString();

pw.close();

}?catch?(Exception?e)?{????????return?"";

}????return?trace;

}

Android Native Crash:前面我們知道可以編寫和使用C/C++原生插件,除非C++使用try...catch捕獲異常,否則一般會(huì)直接crash,通過捕獲信號(hào)進(jìn)行處理。

iOS 異常捕獲:

跟Android、Unity類似,iOS也提供NSSetUncaughtExceptionHandler?來做異常處理。

#import?"CatchCrash.h"


@implementation?CatchCrash


void?uncaughtExceptionHandler(NSException?*exception)

{

//?異常的堆棧信息

NSArray?*stackArray?=?[exception?callStackSymbols];

//?出現(xiàn)異常的原因

NSString?*reason?=?[exception?reason];

//?異常名稱

NSString?*name?=?[exception?name];

NSString?*exceptionInfo?=?[NSString?stringWithFormat:@"Exception?reason:%@\nException?name:%@\nException?stack:%@",name,?reason,?stackArray];

NSLog(@"%@",?exceptionInfo);


NSMutableArray?*tmpArr?=?[NSMutableArray?arrayWithArray:stackArray];

[tmpArr?insertObject:reason?atIndex:0];


[exceptionInfo?writeToFile:[NSString?stringWithFormat:@"%@/Documents/error.log",NSHomeDirectory()]??atomically:YES?encoding:NSUTF8StringEncoding?error:nil];

}


@end

但是內(nèi)存訪問錯(cuò)誤、重復(fù)釋放等錯(cuò)誤引起崩潰就無能為力了,因?yàn)檫@種錯(cuò)誤它拋出的是信號(hào),所以還必須要專門做信號(hào)處理。

windows crash:同樣windows提供SetUnhandledExceptionFilter函數(shù),設(shè)置最高一級(jí)的異常處理函數(shù),當(dāng)程序出現(xiàn)任何未處理的異常,都會(huì)觸發(fā)你設(shè)置的函數(shù)里,然后在異常處理函數(shù)中獲取程序異常時(shí)的調(diào)用堆棧、內(nèi)存信息、線程信息等。



8.適配與兼容


8.1UI適配


錨點(diǎn)(UIAnchor、UIWidgetAnchor屬性)

NGUI?UIRoot統(tǒng)一設(shè)置縮放比例

UIStretch


8.2兼容


shader兼容:例如if語(yǔ)句有的機(jī)型支持不好,Google nexus 6在shader中使用了if就會(huì)crash

字體兼容:android復(fù)雜的環(huán)境,有的手機(jī)廠商和rom會(huì)對(duì)字體進(jìn)行優(yōu)化,去掉android默認(rèn)字體,如果不打包字體會(huì)不現(xiàn)實(shí)中文字


9.調(diào)試及開發(fā)工具

9.1日志及跟蹤

事實(shí)證明,打印日志(printf調(diào)試法)是非常有效的方法。一個(gè)好用的日志調(diào)試,必備以下幾個(gè)功能:

日志面板/控制臺(tái),格式化輸出

冗長(zhǎng)級(jí)別(verbosity level):ERROR、WARN、INFO、DEBUG

頻道(channel):按功能等進(jìn)行模塊劃分,如網(wǎng)絡(luò)頻道只接收/顯示網(wǎng)絡(luò)模塊的消息,頻道建議使用枚舉進(jìn)行命名。

日志同時(shí)會(huì)輸出到日志文件

日志上報(bào)


9.2調(diào)試用繪圖工具

調(diào)試?yán)L圖用工具指開發(fā)及調(diào)試期間為了可視化的繪圖用工具,如騰訊桌球開發(fā)調(diào)試時(shí)會(huì)使用VectrosityScripts可視化球桌的物理模型(實(shí)際碰撞線)幫助調(diào)試。這類工具可以節(jié)省大量時(shí)間及快速定位問題。通常調(diào)試用繪圖工具包含:

支持繪制基本圖形,如直線、球體、點(diǎn)、坐標(biāo)軸、包圍盒等

支持自定義配置,如顏色、粒度(線的粗細(xì)/球體半徑/點(diǎn)的大?。┑?/p>


9.3游戲內(nèi)置菜單/作弊工具

在開發(fā)調(diào)試期間提供游戲進(jìn)行中的一些配置選項(xiàng)及作弊工具,以方便調(diào)試和提高效率。例如騰訊桌球游戲中提供:

游戲內(nèi)物理引擎參數(shù)調(diào)整菜單;

修改球桿瞄準(zhǔn)線長(zhǎng)度/反射線數(shù)量、修改簽到獎(jiǎng)勵(lì)領(lǐng)取天數(shù)等作弊工具

注意游戲內(nèi)的所有開發(fā)調(diào)試用的工具,都需要通過編譯宏開關(guān),保證發(fā)布版本不會(huì)把工具代碼包含進(jìn)去。


9.4Unity擴(kuò)展

Untiy引擎提供了非常強(qiáng)大的編輯器擴(kuò)展功能,基于Unity Editor可以實(shí)現(xiàn)非常多的功能。公司內(nèi)部、外部都有非常的開源擴(kuò)展可用

公司外部,如GitHub上的:

UnityEditor-MiniExtension

Unity-Resource-Checker

UnityEditorHelper

MissingReferencesUnity

Unity3D-ExtendedEditor

公司內(nèi)部:

TUTBeautyUnity、UnityDependencyBy



10.項(xiàng)目運(yùn)營(yíng)


自動(dòng)構(gòu)建

版本號(hào)——主版本號(hào).特性版本號(hào).修正版本號(hào).構(gòu)建版本號(hào)

[構(gòu)建版本號(hào)]應(yīng)用分發(fā)平臺(tái)升級(jí)判斷基準(zhǔn)

?自動(dòng)構(gòu)建

Android

iOS —?XUPorter


?公司內(nèi)部接入SODA即可,建議搭建自己的構(gòu)建機(jī),開發(fā)期間每日N Build排隊(duì)會(huì)死人的,另外也可以搭建自己的搭建構(gòu)建平臺(tái)


統(tǒng)計(jì)上報(bào)

Tlog上報(bào)

玩家轉(zhuǎn)化關(guān)鍵步驟統(tǒng)計(jì)(重要)

Ping統(tǒng)計(jì)上報(bào)

游戲業(yè)務(wù)的統(tǒng)計(jì)上報(bào)(例如桌球球局相關(guān)的統(tǒng)計(jì)上報(bào))

?燈塔自定義上報(bào)


運(yùn)營(yíng)模板

配置化

服務(wù)器動(dòng)態(tài)下發(fā)

CDN拉取圖片并緩存


上線前的checklist

項(xiàng)目

要點(diǎn)

說明

指標(biāo)

燈塔上報(bào)

1. 燈塔自帶統(tǒng)計(jì)信息

2. 自定義信息上報(bào)

燈塔里面包含很多統(tǒng)計(jì)數(shù)據(jù),需要檢查是否ok

1. 版本/渠道分布

2. 使用頻率統(tǒng)計(jì)

3. 留存統(tǒng)計(jì)(1天留存、3天留存、7天留存、14天留存)

4. 用戶結(jié)構(gòu)統(tǒng)計(jì)(有效用戶、沉默用戶、流失用戶、回流用戶、升級(jí)用戶、新增用戶)

5. 硬件統(tǒng)計(jì)(機(jī)型+版本、分辨率、操作系統(tǒng)、內(nèi)存、cpu、gpu)

6. Crash統(tǒng)計(jì)(Crash版本、Crash硬件、Crash次數(shù)等)

等等

信鴿推送


能夠針對(duì)單個(gè)玩家,所有玩家推送消息

米大師支付


正常支付

安全組件

1. TSS組件接入

2. 隱藏內(nèi)部符號(hào)表:C++開發(fā)的代碼使用strip編繹選項(xiàng),抹除程序的符號(hào)

3. 關(guān)鍵數(shù)據(jù)加密,如影子變量+異或加密算法

根據(jù)安全中心提供的文檔完成所有項(xiàng)

接入安全組件,并通過安全中心的驗(yàn)收

穩(wěn)定性

crash率

用戶crash率:發(fā)生CRASH的用戶數(shù)/使用用戶數(shù)

啟動(dòng)crash率:?jiǎn)?dòng)5S內(nèi)發(fā)生crash用戶數(shù)/使用用戶數(shù)

低于3%

弱網(wǎng)絡(luò)


斷線重連考慮,緩存消息,重發(fā)機(jī)制等等

客戶端的核心場(chǎng)景必須有斷線重連機(jī)制,并在有網(wǎng)絡(luò)抖動(dòng)、延時(shí)、丟包的網(wǎng)絡(luò)場(chǎng)景下,客戶端需達(dá)到以下要求:

一. 不能出現(xiàn)以下現(xiàn)象:

1、游戲中不能出現(xiàn)收支不等、客戶端卡死/崩潰等異常情況;

2、游戲核心功能(如登錄、單局、支付等)不能有導(dǎo)致游戲無法正常進(jìn)行的UI、交互問題;

3、不能有損害玩家利益或可被玩家額外獲利的問題;

4、需要有合理的重連機(jī)制,避免每次重連都返回到登錄界面。

二. 需要對(duì)延時(shí)的情況有相應(yīng)的提示

兼容性


通過適配測(cè)試

游戲更新

1. 整包更新

2. 增量更新


特別說明:iOS送審版本支持連特定環(huán)境,與正式環(huán)境區(qū)別開,需要通過服務(wù)器開關(guān)控制

性能

內(nèi)存、CPU、幀率、流量、安裝包大小


【內(nèi)存占用要求】

Android平臺(tái):在對(duì)應(yīng)檔次客戶端最低配置以上,均需滿足以下內(nèi)存消耗指標(biāo)(PSS):

1檔機(jī)型指標(biāo):最高PSS<=300MB (PSS高于這個(gè)標(biāo)準(zhǔn)會(huì)影響28%用戶的體驗(yàn),約1800萬(wàn))

2檔機(jī)型指標(biāo):最高PSS<=200MB(PSS高于這個(gè)標(biāo)準(zhǔn)會(huì)影響45%用戶的體驗(yàn),約3000萬(wàn))

3檔機(jī)型指標(biāo):最高PSS<=150MB(PSS高于這個(gè)標(biāo)準(zhǔn)會(huì)影響27%用戶的體驗(yàn),約1800萬(wàn))

iOS平臺(tái):在對(duì)應(yīng)檔次客戶端最低配置以上,均需滿足以下內(nèi)存消耗指標(biāo)(PSS):

1檔機(jī)型指標(biāo):消耗內(nèi)存(real mem)不大于250MB(高于這個(gè)標(biāo)準(zhǔn)會(huì)影響53%用戶的體驗(yàn),約1900萬(wàn))

2檔機(jī)型指標(biāo):消耗內(nèi)存(real mem)不大于200MB(高于這個(gè)標(biāo)準(zhǔn)會(huì)影響47%用戶的體驗(yàn),約1700萬(wàn))

【CPU占用要求】

Android平臺(tái):CPU占用(90%)小于60%

iOS平臺(tái):CPU占用(90%)小于80%

【幀率要求】

1檔機(jī)型(CPU為四核1.4GHZ,RAM為2G)或以上機(jī)型:游戲核心玩法中,最小FPS應(yīng)不小于25幀/秒

2檔機(jī)型(CPU為兩核1.1GHZ,RAM為768M)或以上機(jī)型:游戲核心玩法中,最小FPS應(yīng)不小于25幀/秒

3檔機(jī)型(CPU為1GHZ,RAM為768M)或以上機(jī)型:游戲核心玩法中,最小FPS應(yīng)不小于18幀/秒

【流量消耗要求】

游戲核心玩法流量消耗情況(非一次性消耗)應(yīng)滿足以下條件:

1.對(duì)于分局的游戲場(chǎng)景,單局消耗流量不超過200KB

2.對(duì)于不分局游戲場(chǎng)景或流量與局時(shí)有關(guān)的場(chǎng)景,10分鐘消耗流量不超過500KB

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 《騰訊桌球:客戶端總結(jié)》 本次分享總結(jié),起源于騰訊桌球項(xiàng)目,但是不僅僅限于項(xiàng)目本身。雖然基于Unity3D,很多東...
    吳秦閱讀 25,250評(píng)論 12 143
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,765評(píng)論 25 709
  • 原鏈接:http://www.ibm.com/developerworks/cn/java/j-jni/ 使用 J...
    王朋6閱讀 8,213評(píng)論 0 8
  • IJKPlayer只能支持http或者h(yuǎn)ttps其中之一,默認(rèn)支持http,如果使用https那么需要做額外的配置...
    天蠶閱讀 5,466評(píng)論 11 10
  • 1 以前經(jīng)常做白日夢(mèng),我穿越回到初中,然后我就像《夏洛特?zé)馈防锏纳蝌v那樣,用“現(xiàn)在的我”去過“當(dāng)初的我”的生活:...
    黃小師閱讀 269評(píng)論 0 0

友情鏈接更多精彩內(nèi)容