easyMenu 開源,讓游戲調(diào)試更簡單!
easyMenu 簡介
使用內(nèi)置的 UI 組件創(chuàng)建一個(gè)調(diào)試面板通常是一個(gè)緩慢而繁瑣的過程,為了解決這個(gè)問題,二喵開源了自用的調(diào)試面板 easyMenu,兼顧了實(shí)用性和美觀。
這個(gè)調(diào)試面板支持批量添加菜單組件,就像使用 tween 一樣簡單。你可以快速添加多個(gè)項(xiàng)目,而且維護(hù)起來也非常方便。
調(diào)試面板內(nèi)置了一些有用的調(diào)試模塊,下面我們來了解一下如何使用這些特性和功能, 快速定位游戲的一些性能問題。
FPS 快捷設(shè)置
調(diào)試面板允許我們手動(dòng)設(shè)置 FPS(每秒幀數(shù)),以便測試性能。30 FPS 的開銷和發(fā)熱會(huì)更少,但流暢度會(huì)有所降低。
const Debug = this.menu.addGroup("Debug");
Debug.addToggle("High FPS", (t) => { game.frameRate = t ? 60 : 30;})
只需要 2 行代碼就可以快速添加 FPS 切換,可以點(diǎn)擊 Toggle 按鈕在 30 FPS 和 60 FPS 間快速切換。
FPS 監(jiān)控
提到了 FPS 切換,我們再來講下 FPS 監(jiān)控,引擎默認(rèn)只提供了當(dāng)前 FPS 數(shù)值,無法監(jiān)控 FPS 的變化。
eMenu 提供了 eGraph 圖形監(jiān)控面板,使用簡單,可以快速監(jiān)控如 FPS 變化,場景內(nèi)敵人數(shù)量變換等,下面我們快速設(shè)置一下 FPS 監(jiān)控。
我們首先獲取 graph 節(jié)點(diǎn)的實(shí)例,如下圖所示,代表最高數(shù)值 60,最多14段數(shù)據(jù)。
const Debug = this.menu.addGroup("Debug");
Debug.addGraph("FPS", null, 60, 14);
this.graph = Debug.node.getChildByName("FPS").getComponent(eGraph);
然后可以在 update 內(nèi)統(tǒng)計(jì)每秒內(nèi)的幀數(shù),并推送給 eGraph 面板
time = 0;
counter = 0;
update(dt) {
this.counter += 1;
this.time += dt;
if (this.time >= 1) {
const graph = this.graph;
if (!graph) return;
graph.updateData(this.counter)
this.time -= 1;
graph.NameLable.string = "FPS: " + this.counter;
this.counter = 0;
}
}
我們還可以給 Graph 添加點(diǎn)擊事件,通過點(diǎn)擊面板,可以獲取歷史的曲線節(jié)點(diǎn)數(shù)據(jù)。
這些數(shù)據(jù)也會(huì)復(fù)制到剪切板,方便粘貼查看。 
Overdraw 查看
Overdraw 是指同一個(gè)像素點(diǎn)被渲染多次,這通常是由內(nèi)容冗余、層級混亂或邏輯錯(cuò)誤導(dǎo)致的。通過調(diào)試面板,我們可以使用代碼批量替換精靈的材質(zhì),以便更清楚地看到 overdraw 的情況。
testOverdraw() {
this.overdrawMode = !this.overdrawMode;
const children = this.canvasNode.children;
const material: Material = this.overdrawMode ? this.overdrawMat : this.defaultMaterial;
children.forEach((child) => {
if (child == this.menu.node) return;
const sprites = child.getComponentsInChildren(Sprite);
sprites.forEach((sprite) => {
if (!this.defaultMaterial) {
this.defaultMaterial = new Material();
this.defaultMaterial.copy(sprite.material)
}
if (sprite.node.name !== this.node.name) {
sprite.material = material;
}
})
})
}
比如下圖的這個(gè) Banner 看起來很正常:
然而這個(gè) Banner 下面隱藏了很多其他精靈:
在實(shí)際項(xiàng)目中,這些情況經(jīng)常發(fā)生,腳本邏輯錯(cuò)誤,或者忘記刪除測試節(jié)點(diǎn)都有可能導(dǎo)致 Overdraw,通過點(diǎn)擊 Overdraw 按鈕,我們可以查看那個(gè)像素點(diǎn)有多個(gè)精靈圖片渲染。
重復(fù)渲染的次數(shù)越多,紅色會(huì)越深,針對 Overdraw 的部分,我們可以通過以下方法解決。
- 隱藏背景部分不需要渲染的精靈
- 把大圖切割,避免中間大面積的透明像素
養(yǎng)成查看 Overdraw 的習(xí)慣,可以降低 GPU 渲染的壓力,減輕手機(jī)的發(fā)熱情況。
游戲速度控制
通過重寫引擎的 tick 函數(shù),我們可以放慢全局速度,這對動(dòng)畫調(diào)試非常有幫助。并且還能用它實(shí)現(xiàn)子彈時(shí)間、慢動(dòng)作、加速回放等特殊需求。
@ccclass('TimeScale')
export class TimeScale extends Component {
static scale = 1
start () {
const originalTick = director.tick;
director.tick = (dt: number) => {
dt *= TimeScale.scale;
originalTick.call(director, dt);
}
}
}
這里可以使用調(diào)試面板控制全局速度,方便調(diào)試動(dòng)畫和特效。
圖片內(nèi)存占用分析
我們可以通過調(diào)試面板歷遍 assets 中所有的圖片資源,并按照尺寸排序,這樣就可以找出內(nèi)存開銷最高的圖片。結(jié)果會(huì)自動(dòng)復(fù)制到剪貼板內(nèi),方便進(jìn)一步分析和處理。
getImageMemory(): string {
const assets = assetManager.assets;
let images: ImageAsset[] = [];
assets.forEach((asset) => {
if (asset instanceof ImageAsset) {
images.push(asset);
}
})
images.sort(function (a, b) {
return b.height * b.width - a.height * a.width;
});
let output = "";
let total = 0;
/* get all imagessets mem */
images.forEach((image, i) => {
const self = image;
const native = self._native;
const url = self.url;
const num = Math.floor((self.width * self.height * (native.indexOf('jpg') > 0 ? 3 : 4) / 1024 / 1024) * 10000) / 10000;
total += num;
output = output + "\n" + url + "...." + num + "M";
})
total = Math.floor(total * 10000) / 10000;
output = "Total Image Mem...." + total + "M" + output;
console.log("Image Mem==", output)
this.copyToClipboard(output);
return output;
}
這里會(huì)支持打印所有圖片的路徑和內(nèi)存占用到剪切板。
我們可以通過路徑和圖片的 UUID 名字定位到開銷高的圖片,針對內(nèi)存占用高的圖片可以這么優(yōu)化。
- JPG 格式由于沒有 Alpha 透明通道,內(nèi)存占用要比 PNG 少 25%,針對背景圖片,這里也推薦使用 JPG 格式。
- 針對一些 1920 分辨的大圖,可以壓縮分辨率到 1280,配合 JPG 使用效果更佳。
- 比較常用的按鈕和UI圖可以使用九宮格,切除中間區(qū)域,來減少圖片分辨率。
環(huán)境變量設(shè)置
在 3D 游戲開發(fā)中,有大量的環(huán)境變量,我們可以通過 globals 環(huán)境變量獲取大部分設(shè)置。
const scene = director.getScene();
const globals = scene.globals;
const light = scene.getComponentInChildren(DirectionalLight);
const ambientScale = globals.ambient.skyIllum / 100000;
const lightScale = light.illuminance / 100000;
this.menu
.addGroup("Env")
.addToggle("Shadow", (t) => {
globals.shadows.enabled = t;
})
.addToggle("IBL", (t) => {
globals.skybox.useIBL = t;
})
.addToggle("CSM", (t) => {
light.enableCSM = t;
})
.addSlider("Ambient", (p) => {
globals.ambient.skyIllum = p * 100000
}, ambientScale)
.addSlider("Light", (p) => {
light.illuminance = p * 100000
}, lightScale)
通過動(dòng)態(tài)調(diào)試這些參數(shù),可以快速定位性能和畫面問題。
免費(fèi)獲取源碼
此外,easyMenu 也是一個(gè)非常有用的工具,可以用于快速添加菜單組件。它內(nèi)置了按鈕, list , editbox , slider , graph 等多種組件,可以點(diǎn)擊【閱讀原文】免費(fèi)獲取源碼。
具體的使用教程可以在商店說明頁面查看。
Github 和 Store同步更新,方便的話可以給個(gè) Star:https://github.com/iwae/easyMenu
謝謝大家的支持,二喵會(huì)再接再厲,輸出更多對大家有用的內(nèi)容。
