查看原文
其他

让游戏调试更简单!老菜喵自用开源!

老菜喵 老菜喵
2024-10-10

使用内置的 UI 组件创建一个调试面板通常是一个缓慢而繁琐的过程,为了解决这个问题,二喵开源了自用的调试面板 easyMenu,兼顾了实用性和美观。

这个调试面板支持批量添加菜单组件,就像使用 tween 一样简单。你可以快速添加多个项目,而且维护起来也非常方便。

调试面板内置了一些有用的调试模块,下面我们来了解一下如何使用这些特性和功能, 快速定位游戏的一些性能问题。

FPS 快捷设置

调试面板允许我们手动设置 FPS(每秒帧数),以便测试性能。30 FPS 的开销和发热会更少,但流畅度会有所降低。

   const Debug = this.menu.addGroup("Debug");
   Debug.addToggle("High FPS"(t) => { game.frameRate = t ? 60 : 30;})

只需要 2 行代码就可以快速添加 FPS 切换,可以点击 Toggle 按钮在 30 FPS 和 60 FPS 间快速切换。

FPS 监控

提到了 FPS 切换,我们再来讲下 FPS 监控,引擎默认只提供了当前 FPS 数值,无法监控 FPS 的变化。

eMenu 提供了 eGraph 图形监控面板,使用简单,可以快速监控如 FPS 变化,场景内敌人数量变换等,下面我们快速设置一下 FPS 监控。

我们首先获取 graph 节点的实例,如下图所示,代表最高数值 60,最多14段数据。

   const Debug = this.menu.addGroup("Debug");
   Debug.addGraph("FPS"null6014);
   this.graph = Debug.node.getChildByName("FPS").getComponent(eGraph);

然后可以在 update 内统计每秒内的帧数,并推送给 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 添加点击事件,通过点击面板,可以获取历史的曲线节点数据。

这些数据也会复制到剪切板,方便粘贴查看。

通过Graph模块,可以方便观察FPS的波动。

Overdraw 查看

Overdraw 是指同一个像素点被渲染多次,这通常是由内容冗余、层级混乱或逻辑错误导致的。通过调试面板,我们可以使用代码批量替换精灵的材质,以便更清楚地看到 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;
                }
            })

        })
    }

比如下图的这个 Banner 看起来很正常:

然而这个 Banner 下面隐藏了很多其他精灵:

在实际项目中,这些情况经常发生,脚本逻辑错误,或者忘记删除测试节点都有可能导致 Overdraw,通过点击 Overdraw 按钮,我们可以查看那个像素点有多个精灵图片渲染。

重复渲染的次数越多,红色会越深,针对 Overdraw 的部分,我们可以通过以下方法解决。

  • 隐藏背景部分不需要渲染的精灵
  • 把大图切割,避免中间大面积的透明像素

养成查看 Overdraw 的习惯,可以降低 GPU 渲染的压力,减轻手机的发热情况。

全局游戏速度控制

通过重写引擎的 tick 函数,我们可以放慢全局速度,这对动画调试非常有帮助。

@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);
        }
    }
}

这里可以使用调试面板控制全局速度,方便调试动画和特效。

图片内存开销和查看

我们可以通过调试面板历遍 assets 中所有的图片资源,并按照尺寸排序,这样就可以找出内存开销最高的图片。结果会自动复制到剪贴板内,方便进一步分析和处理。

 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;
    }

这里会支持打印所有图片的路径和内存占用到剪切板。

我们可以通过路径和图片的 UUID 名字定位到开销高的图片,针对内存占用高的图片可以这么优化。

  • JPG 格式由于没有Alpha透明通道,内存占用要比PNG少25%,针对背景图片,这里也推荐使用JPG格式。
  • 针对一些1920分辨的大图,可以压缩分辨率到1280,配合JPG使用效果更佳。
  • 比较常用的按钮和UI图可以使用九宫格,切除中间区域,来减少图片分辨率。

环境变量设置

在 3D 游戏开发中,有大量的环境变量,我们可以通过 globals 环境变量获取大部分设置。

        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)

通过动态调试这些参数,可以快速定位性能和画面问题。

免费获取源码

此外,easyMenu 也是一个非常有用的工具,可以用于快速添加菜单组件。它内置了按钮,listeditboxslidergraph 等多种组件,可以点击【阅读原文】免费获取源码。

具体的使用教程可以在商店说明页面查看。

Github 和 Store同步更新,方便的话可以给个 Star:https://github.com/iwae/easyMenu

谢谢大家的支持,二喵会再接再厉,输出更多对大家有用的内容。


继续滑动看下一个
老菜喵
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存