游戏客户端开发面经
幀同步與狀態同步,它們的區別是什么,什么類型的游戲適合幀同步,什么類型的游戲適合狀態同步。
答:幀同步中,服務器廣播玩家的操作; 狀態同步,服務器同步玩家的屬性;對于玩家數量少,非常需要注重手感的游戲適合幀同步(王者榮耀、魔獸爭霸,格斗類型游戲),對與玩家數量多,手感要求沒那么高適合用狀態同步(qq飛車、荒野行動等);它們的重要區別是戰斗的核心邏輯寫在哪里,幀同步的戰斗邏輯寫在客戶端,狀態同步的戰斗邏輯寫在服務端;由于玩家的屬性比較多,所以狀態同步需要發送的數據比較多,而且通常都是采用分批次發送,所以網絡延遲會更高,而幀同步只需要轉發玩家的操作,延遲比較低。詳細的文章可以參考這里
map和unordered_map的實現原理。
答:unordered_map使用hash表實現的,效率高,無序;map的底層實現是紅黑樹,有序,對于紅黑樹,刪除、插入、查找的時間復雜度都是logN,通過自旋和變色達到平衡;紅黑樹特點有如下:
- 只有紅黑兩種節點;
- 葉子節點和根節點是黑節點;
- 兩個紅節點不能直接相連(每個紅節點的這兩個子節點都是黑子節點);
- 每個葉子節點到根節點所包含的黑節點數目是一樣的;
指針會導致崩潰?
答:首先導致崩潰的原因是非法讀寫內存地址,有如下可能:
- 空指針,當讀寫空指針時,操作系統不允許讀寫空指針,然后程序崩潰,空指針指向的地址一般是0(windows是的),這個地址一般是操系統自己的地址,不允許程序自己去訪問,所以當遇到讀寫時,程序就會崩;
- 指針指向無效地址,例如當指針所指向的地址已經釋放,去讀寫該指針,程序會崩潰。
右值引用的問題,介紹下右值應用。
答:對于左值右值問題,平時開發可能不怎么會用到這個,但是面試有可能會被問到,其實左值右值簡單說就是不同的類型;在STL中,一般會用到move這個函數,它的作用就是把左值轉換為右值,就是一個類型轉換函數,在STL庫中,經常將右值作參數類型,做淺拷貝,提升新性能,例如STL中Vector的push_back,當您傳右值進去的時候,就會作為淺拷貝,eg:
#include<iostream> #include<string> #include<vector>using namespace std;int main() {vector<string> v;string s = "asd";v.push_back(s);cout << "s = " << s << endl; // 輸出 s = asdv.push_back(move(s));cout << "s = " << s << endl; //輸出 s = return 0; }此時相當于做了一次淺拷貝,將s的指針指向的值轉移到了數組s中,并且將s指向空;另外就是在unique_ptr中也經常會用到move,其目的是保證unique_ptr是指向該地址的唯一指針,這樣就可以避免剛開始提到的出現野指針的情況,對于詳細的左右值介紹請看這里, 文中提出了一個重要的結論,就是std:move單獨使用,是不會提升性能的,它只是一個類型轉換函數,效果和 static_cast(x) 是一樣的效果,需要和其他功能配合使用,才有可能會往性能提升方向靠攏。還有一個c++11中提到的中道模板函數 forward,這個模板函數可以用于實現完美轉發,在我們給函數傳參數時,如果這個函數同時可以接受左值和右值,當對應參數傳到這個函數中,無論這個值是左值還是右值,在這個函數里面都是左值(因為有地址了),如果這個時候用std::forward進行轉發,就可以傳遞到接收對應類型的函數里面去 關于完美轉發的詳細介紹可以看這里。
為什么解釋型語言能夠熱更新,編譯型得不能熱更新呢(其實這個問題比較局限)?
答: 對于android手機而言,系統是基于linux開發的,所以可以通過動態連接庫加載熱更的代碼(.so文件)。對于ios手機而言,運行app的時候會為appstore審核過的代碼開辟專用的內存空間,而其他app中的數據或者通過代碼從線上下載的數據加載的時候會將存放的內存空間定義描述符定義為禁止運行,這樣ip寄存器將不能夠跳轉到該空間,因此這部分代碼不能運行;但是為什么解釋型的語言又可以呢,例如lua、python,對于這兩種語言,在不開啟JIT的情況下,是解釋運行的,就是直接將代碼輸入給虛擬機,然后虛擬機邊讀取邊執行,虛擬機這部分代碼在包體提交到appstore之后是合法的,所以對于ios來說,并沒有新的要執行的代碼,只是虛擬機輸入的數據有變化而已。當然,像其他同學說的那樣,使用C++/C#解釋器仍然是可以熱更的。其實使用lua的主要原因是lua的字節碼執行比較快,并且模擬器(虛擬機)運行比較穩定,維護足夠好。同時lua模擬器足夠輕量,方便擴展。也有部分項目組是用IL_RunTime熱更,好處是熱更用的是C#,不用再加其它腳本語言進行開發了,當然可能用的項目不多,需要踩的坑很多,所以大部分項目還是用的C++/C# + 腳本語言(lua、Python)進行開發。
C#以及mono的內存回收方式。
答:它們兩個都是通過GC方式進行回收的,
-
對于C#:首先資源分為托管資源和非托管資源,非托管資源需要自己去實現對應的回收函數接口IDispose;托管資源,指的是引用類型的對象,這一部分是需要GC去管理的;GC時從根集遍歷,把能找到應引用的和不能找到引用的都標記出來,最后將沒有引用的對象清除;GC的算法是采用的分代處理,這樣做的目的是為了加快回收速度,減少GC是的卡頓現象,主要分為0、1、2代,每一代都有自己的內存預算,超過預算之后觸發GC,GC之后0代中還存活的對象變為1代,依次內推,一般GC的調用機制時系統控制的,當然也可在代碼中手動調用GC:GC.Collect();總體思路就是標記+分代處理垃圾對象。
-
對于mono,采用的方式就比較簡單些,mono的內存分為空閑內存+已分配的內存,當需要分配內存時,檢查空閑內存是否夠,如果夠進行分配,如果不夠,進行GC,GC完不夠,向操作系統申請內存;其中mono內存有個特點就是向操作系統申請的內存是不會還給操作系統的;為了避免GC峰值造成的卡頓,unity也可以設置分步GC機制,也叫做步進式GC。
什么是虛擬內存
答:虛擬內存是一種計算機系統內存管理技術,它使得應用程序認為它擁有連續的可用的內存(一個連續完整的地址空間),而實際上,它通常是被分隔成多個物理內存碎片,還有部分暫時存儲在外部磁盤存儲器上,在需要時進行數據交換;例如我們在Windows系統中,通常將虛擬內存設置為真是內存的1.5倍。由于物理內存有限,對于一些大型程序(游戲),需要占用的空間就比較大,此時就可以把一些資源放在磁盤中進行IO,實質上就是為了方便管理程序的地址以及利用磁盤的部分空間增大內存。
C++多態原理,虛表及其重載的實現原理。
答:C++多態分為靜態聯編和動態聯編;靜態聯編就是指在編譯的時候知道調用什么函數,例如函數的重載(參數類型、參數個數不同),動態聯編是需要在程序具體執行的時候才能確定函數的調用,例如父類指針指向子類指針,父類存在虛函數;重載的原理就是編譯器在編譯的時候,會根據函數的參數類型將對應的函數重新命名,這一樣就保證了函數的調用唯一性;對于虛函數,是通過虛表實現的,每一個存在虛函數的類或者其父類存在虛函數,在實例中都會有一個虛表,這個虛表記錄了其指向的虛函數的調用地址,如果是多繼承,就會有多個虛表。如果調用的是虛函數,那么就去看這個類指針實際指向的是誰,如果不是虛函數,調用的就是這個指針類型對應的函數。更詳細的關于C++多態的介紹的內容可以看這里。
二維平面問題及其拓展 ----- 點到直線的距離,點是否在在扇形內,怎么判斷扇形和圓相交。
點到直線的距離:直線上兩個點A、B, 直線外的點P,通過向量法做,求出向量AP在直線AB上的投影向量AM,然后通過向量減法求出點P到線最近點M的向量:PM = AP - AM, C# 代碼如下:
float getPointLineDis(float2 p, float2 a, float2 b) {float2 v1 = p- a; // APfloat2 v2 = b- a; float2 v3 = dot(v1,v2) * v2 / pow(length(v2), 2); // AMreturn length(v1 - v3); // length(PM) }判斷點是否在扇形內部: 首先計算扇形圓心O和所求點P的距離d,如果大于扇形半徑,則一定不在扇形內部,其次,計算點P與圓心的連線向量與扇形角平分線的夾角,這個加角一定是小于扇形角度的一半,當然前提是扇形的角度theta小于180,當大于180時可以轉換為求360 - theta;
判斷扇形和圓是否相交: 通過分離軸的方式,判斷扇形是否和圓相交,大概方式就是通過求圓心到扇形兩條邊(線段)的最短距離以及扇形圓心到所求圓的圓心的距離可以判斷,簡化可以通過對稱性以及坐標轉化求的,具體的詳細算法可以參考這里
物理碰撞計算中,如果速度非常快,那么相鄰兩幀有時候就會出現穿插而導致沒有檢測到碰撞,這種情況怎么處理?
答:可以通過保留上一幀的位置信息,計算是否碰撞,例如:一個子彈飛向一面墻,可以通過子彈這一幀的位置和上一幀的位置連線是否與墻相交,相交則發生了碰撞,沒有相交則沒有發生碰撞。如果是兩個運動很快的物體,則先利用相鄰前后幀位置計算出線段,判斷線段是否相交,如果相交,算出交點,然后再用出發點到交點的距離以及運動速度和碰撞物體體積進行計算。
深拷貝和淺拷貝
答:對于C++指針而言,淺拷貝是指連個指針指向同一個地址,深拷貝是指在內存空間中新開辟一個地址,并且復制需要拷貝的內容;在很多語言中會遇到這種問題,例如Python中的copy與deeppeek,當用copy.copy時,會出現修改復制出來的對象,最后濺射到原來對象的情況,原理和C++的深淺拷貝一樣。
c#中的裝箱和拆箱,性能會有什么損耗
答:
- 裝箱:將值類型轉變為引用類型的過程成為裝箱過程,首先會為該對象分配一個指針,然后為該對象在堆上開辟一個地址空間,損耗:由于需要給引用類型分配空間,所以內存消耗會增加,同時由于操作了c#堆,會造成更多的GC,從而有可能會增加游戲的卡頓。
- 拆箱:將引用類型轉換為值類型,這其中會涉及到數據類型的轉換,例如將一個整形從引用轉換為值,會有一個向整形轉換的過程,int a = (int)(obj),這里也會有一定的性能損耗,相對于裝箱損耗會小一些。
unity生命周期中的fixupdate,什么場合用,及其設置的參數
答:fixupdate固定時間間隔更新的函數,一般為0.02s更新一次;一般在start執行之后,update執行之前,由于其固定的執行頻率,所以適合用于物理邏輯的計算,它不會隨著幀率的變化而變化,例如,由于GC的原因,導致游戲卡頓,本來60幀的,變為了30幀,update的執行會和幀率保持一致,但是fixupdate不會,當幀率從60變為30時,執行到fixupdate時,會執行兩次;設置的參數:Edit->Project Settings->Time 找到彈出面板的Fixed Timestep設置。
C#的異步操作
答:C#中的異步操作,主要是通過 Async 和await關鍵字配合一起使用,以及需要配合using System.Threading.Tasks下面的Task模板類實現多任務。C#異步編程應用舉例可以看這里
多線程從編程,需要注意什么
答:
- 一、線程池,對于多線程編程,一定程度上可以提高程序運行的并發性,但是同一時刻運行的線程數目不能多于CPU核心數,否者會導致線程相互競爭 CPU 資源,進而造成頻繁的上下文切換,上下文切換是資源密集型的過程,因此應盡可能避免,所以超出核心數之后不一定能夠提升程序的性能;
- 二、安全性,多線程編程中特別需要注意的是 race condition,也就是競爭條件,例如多個線程都在寫一個共享變量,這時候的結果是不確定的,所以會有問題;通常解決辦法是通過加鎖或者使得代碼塊的原子性操作。
AB包常用的壓縮方式
LZMA, LZ4;
LZMA壓縮是比較流行的壓縮格式,能使壓縮后文件達到最小,但是解壓相對緩慢,導致加載時需要較長的解壓時間;
LZ4壓縮后的文件大小比LZMA稍大,但是解壓速度快,如果只需要其中的某一部分,可以不用全部解壓。
unity mask增加幾個dp
加倍,先畫原來的,在用模板(遮罩)進行剔除
unity對象的生命周期
awake,OnEnable,start,FixUpdate,Update,LateUpdate,OnDisable,OnDestroy
常用的圖片壓縮方式
etc pvr;etc1 不支持透明通道,解決辦法是通過渲染另外一張圖片作為渲染的透明度。etc2 支持透明通道,但是只有支持opengl3.0的才能用,大部分是支持opengl2.0。
為什么要做場景烘培,對于做烘培的場景有什么要求
不能做實時光照,增加效果,只能對靜態物理做烘培
1024*1024的8位圖片多大
(102410244*8b) 一個通道8位,所以是4m
前向渲染和延遲渲染特點
前向渲染:流水線的各個組成部分是:VS->TS->GS->FX->apphaTest->DepthTest->RT;支持半透明物體的渲染;光源數量不能太多,否者FS階段的計算量會暴增;可以支持MSAA;只需要一個RT。
延遲渲染:流水線的各個組成部分是:VS->TS->GS->FX->MRT;需要設備支持多RT渲染,由于需要多RT,所以帶寬要求高;由于光照計算是在FX之后,所以不支持MSAA,但是可以使用FXAA;不支持半透明度的物體渲染,如果需要,可以和前項渲染混合使用;支持多光源;需要 支持OpenGL ES 3.0以上的設備,metal支持延遲渲染。
UGUI的常用優化方式
動靜分離;不用交互的取消勾選raycast;層級不要太深;有效利用合批(比如加了mask,會打斷合批,以及增加一次dp)
圖集有什么作用
圖集的目的是為了提升渲染性能;例如GameObject A 和GameObject B,他們的材質除了引用的貼圖不一樣,其他的都一致,由于合批是需要GameObject的材質一致,所以這樣會打斷合批;如果這個時候把A和B所需要的貼圖打包到一個大的貼圖上,此時GameObject A 和GameObject B材質就是一致的,就可以使用合批提升渲染新能(這里不一定是減小drawcall,更加詳盡介紹的可以查看這篇文章)
總結
- 上一篇: Win11推出新的媒体播放器程序,旨进一
- 下一篇: 寻找模型最优参数、多模型交叉验证、可视化