Catlike学习笔记(1.5)-在Unity中统计FPS

周末打了两天 Celeste 蔚蓝终于打穿了,作为 IGN 2018 年首款满分神作还是很名副其实的,至少关卡设计绝对是大师级的~虽然难到爆炸但是总有一种我刚才是失误下一次一定过的错觉。。事实证明像博主这样的手残党也可以在经历十几个小时的磨难后顺利通关~总的来说还是很赞的游戏,十几个小时的游戏时间绝对值回票价~是的没错说了这么多就是为了完美的解释上周末没有更新文章的原因。。好吧总之这周来到了「Catlike Coding 第一章」的最后一篇文章~这篇主要是讲如何使用 Profiler 查看游戏的性能以及写个小工具测量帧率~按照惯例附上『原文链接

PART 1 概述

那么为了使用 Profiler 之类的工具可以有效的查看到性能的变化~我们需要制作一个跑得越来越慢的 Demo 的样子。。所以用 Unity 物理组件模拟一个不断增长的原子核似乎是个不错的想法。所以我们的目标大概有以下这些~

  • 使用 Unity 物理组件模拟原子核并使其不断增长
  • 使用 Profiler 查看游戏性能并稍加分析
  • 制作一个 FPS 指示器实时显示当前 FPS

感觉需求并不是非常复杂~开工!

PART 2 制作模拟原子核

我们并没有打算制作完全符合物理学的原子核模型~只是一些会被吸引到原点的小球而已跟真正的原子核一点关系都没有只是很有趣。所以第一步就是制作两个不同颜色的小球的 Prefab 一个代表质子另一个代表中子这样。首先我们制作一个脚本可以给小球一个由其当前位置指向世界坐标原点的力。

[RequireComponent(typeof(Rigidbody))]
public class Nucleon : MonoBehaviour
{
    public float AttractionForce;
    private Rigidbody _body;

    private void Awake()
    {
        _body = GetComponent<Rigidbody>();
    }

    private void FixedUpdate()
    {
        _body.AddForce(transform.localPosition * -AttractionForce);
    }
}

大概就是这样~代码非常简单想必大家都看得懂就不解释了。。。接下来做两个不同颜色的 Material 以便区分质子和中子~比如像这样:

picture
picture

最后把这些东西拼在一起做成 Prefab。

picture
picture

完成质子和中子的 Prefab 以后我们就可以生成这些质子和中子了,添加一个空 GameObject 并挂上以下代码

public class NucleonSpawner : MonoBehaviour
{
    public float TimeBetweenSpawns;
    public float SpawnDistance;
    public Nucleon[] NucleonPrefabs;

    private float _timeSinceLastSpawn;

    private void FixedUpdate()
    {
        _timeSinceLastSpawn += Time.deltaTime;
        if (_timeSinceLastSpawn >= TimeBetweenSpawns)
        {
            _timeSinceLastSpawn -= TimeBetweenSpawns;
            SpawnNucleon();
        }
    }

    private void SpawnNucleon()
    {
        var prefab = NucleonPrefabs[Random.Range(0, NucleonPrefabs.Length)];
        var spawn = Instantiate<Nucleon>(prefab);
        spawn.transform.localPosition = Random.onUnitSphere * SpawnDistance;
    }
}

最后再设置好合适的参数就可以了~比如这样:

picture

到此为止我们的原子核生成器就完成了~运行效果如图所示:

picture

PART 3 使用 Profiler 分析性能

我们一边运行一边打开 Profiler 看看~发现大概是下图的样子,博主用 Macbook 做的实验因此可以看到偶尔物理处理的部分那根柱子爆表了。。。以及偶尔会出现的 EditorOverhead 之类的干扰项。

picture

我们可以在 Profiler 里面找到很多相关的数据但是并不十分准确~可以尝试打包以后再连接 Profiler 查看更准确的数据。要记得勾选Development BuildAutoconnect Profiler

picture

运行程序再在 Profiler 里面选择正确的要调试的应用。可以看到数据不像在 Editor 里那样疯狂跳动而是变得平滑一些。当然各项消耗的占比也会略有不同,有兴趣的话还可以尝试安卓或 ios 看看是不是会有更显著的差距。

picture

PART 4 计算FPS

首先我们简单的制作一个显示当前 FPS 的脚本,大概代码如下所示

public class FPSDisplay : MonoBehaviour
{
    [SerializeField] private Text _fpsText;

    // Update is called once per frame
    void Update()
    {
        _fpsText.text = ((int)(1f / Time.unscaledDeltaTime)).ToString();
    }
}

然后发现,我们每一帧把 int 转换成 string 都会产生一些额外的 GC 开销,

picture

因此我们尝试提前建立 int 到 string 的索引,首先把帧数显示限制在 0-100 的范围内,然后从 List 中取出相应的字符串。

public class FPSDisplay : MonoBehaviour
{
    [SerializeField] private Text _fpsText;

    private static readonly List<string> _fpsStrings = new List<string>
    {
        "00", "01", "02", "03", "04", "05", "06", "07", "08", "09",
        "10", "11", "12", "13", "14", "15", "16", "17", "18", "19",
        "20", "21", "22", "23", "24", "25", "26", "27", "28", "29",
        "30", "31", "32", "33", "34", "35", "36", "37", "38", "39",
        "40", "41", "42", "43", "44", "45", "46", "47", "48", "49",
        "50", "51", "52", "53", "54", "55", "56", "57", "58", "59",
        "60", "61", "62", "63", "64", "65", "66", "67", "68", "69",
        "70", "71", "72", "73", "74", "75", "76", "77", "78", "79",
        "80", "81", "82", "83", "84", "85", "86", "87", "88", "89",
        "90", "91", "92", "93", "94", "95", "96", "97", "98", "99"
    };

    // Update is called once per frame
    void Update()
    {
        var curFps = Mathf.Clamp((int) (1f / Time.unscaledDeltaTime), 0, 99);
        _fpsText.text = _fpsStrings[curFps];
    }
}

再次使用 Profiler 发现讨厌的 GC 消失不见了~

picture

不过我们的 FPS 指示器还有另外一个缺陷,就是每帧都在跳动如果变化非常剧烈的话基本上看不清显示的是什么,尽管我们可以改成每秒计算一次之类的,不过这样就没有办法感受到 FPS 在一秒之内产生怎样的变化。因此我们的做法就是求一定过去一定帧数之内的平均值。

public class FPSDisplay : MonoBehaviour
{
    [SerializeField] private Text _fpsText;
    [SerializeField] private int _fpsRange = 60;
    
    private int[] _fpsBuffer;
    private int _fpsBufferIndex;

    ...

    private void Awake()
    {
        _fpsBuffer = new int[_fpsRange];
    }

    // Update is called once per frame
    private void Update()
    {
        UpdateBuffer();
        CalcFPS();
    }

    private void UpdateBuffer()
    {
        var curFps = (int) (1f / Time.unscaledDeltaTime);
        _fpsBuffer[_fpsBufferIndex] = curFps;
        _fpsBufferIndex++;
        if (_fpsBufferIndex >= _fpsRange)
        {
            _fpsBufferIndex = 0;
        }
    }

    private void CalcFPS()
    {
        var sum = 0;
        foreach (var fps in _fpsBuffer)
        {
            sum += fps;
        }
        _fpsText.text = _fpsStrings[Mathf.Clamp(sum / _fpsRange, 0, 99)];
    }
}

这样就可以求过去 60 帧之内的 FPS 的平均值了。我们还可以顺手把过去 60 帧之内的最大最小 FPS 分别显示出来,稍微改一改 UI 增加两个 Text 分别用于显示最大和最小值再修改代码如下:

public class FPSDisplay : MonoBehaviour
{
    [SerializeField] private Text _lowFpsText;
    [SerializeField] private Text _fpsText;
    [SerializeField] private Text _highFpsText;
    [SerializeField] private int _fpsRange = 60;
    
    ...

    private void CalcFPS()
    {
        var lowest = int.MaxValue;
        var highest = 0;
        var sum = 0;
        foreach (var fps in _fpsBuffer)
        {
            if (fps < lowest)
            {
                lowest = fps;
            }

            if (fps > highest)
            {
                highest = fps;
            }
            sum += fps;
        }

        _lowFpsText.text = _fpsStrings[Mathf.Clamp(lowest, 0, 99)];
        _fpsText.text = _fpsStrings[Mathf.Clamp(sum / _fpsRange, 0, 99)];
        _highFpsText.text = _fpsStrings[Mathf.Clamp(highest, 0, 99)];
    }
}

这样我们就可以愉快的把一定时间内最大最小以及平均 FPS 显示出来了~效果不错。。。

picture

最后我们为不同数值范围的 FPS 上色,使得玩家可以更直观的感受到当前 FPS 正常还是过低。首先添加一个 Struct 里面保存一个颜色以及该颜色对应最低 FPS 值。

[Serializable]
struct FPSColor {
    public Color color;
    public int minimumFPS;
}

然后再稍微重构一下代码,在CalcFps()中每帧计算出来的 FPS 保存在类的成员变量中记录下来,然后把显示 FPS 的代码提取出来变成一个函数DisplayFps()每帧调用,分别用于刷新三个 Text 组件的颜色以及 FPS 数值。

public class FPSDisplay : MonoBehaviour
{
    ...
    [SerializeField] private int _fpsRange = 60;

    [SerializeField] private FPSColor[] _fpsColors;
    
    private int _lowFps;
    private int _averageFps;
    private int _highFps;
    
    private int[] _fpsBuffer;
    private int _fpsBufferIndex;
    
    ...
    
    private void Update()
    {
        UpdateBuffer();
        CalcFps();
        DisplayFps(_lowFpsText, _lowFps);
        DisplayFps(_averageFpsText, _averageFps);
        DisplayFps(_highFpsText, _highFps);
    }
    
    ...
    
    private void CalcFps()
    {
        ...
        _lowFps = lowest;
        _averageFps = sum / _fpsRange;
        _highFps = highest;
    }

    private void DisplayFps(Text label, int fps)
    {
        label.text = _fpsStrings[Mathf.Clamp(fps, 0, 99)];
        for (var i = 0; i < _fpsColors.Length; i++) {
            if (fps < _fpsColors[i].minimumFPS) continue;
            label.color = _fpsColors[i].color;
            break;
        }
    }
}

最后再在 Inspector 里设置好各种颜色如图所示:

picture

这样一个完美的 FPS 指示器就完成了~

picture

PART 5 总结

至此「Catlike Coding 第一章」内容已经全部结束~目前为止都是非常基础的课程,博主大部分时间都是在整理文字并没有花太多时间在 Unity 和 c# 上,很多地方也都是大概看一下原作者的思路就差不多自己去实现了并没有完全照搬代码,根据自己的理解写一遍下来感觉还是收获颇丰的虽然很多地方有点懒就跳过了,尤其是关于 Profiler 的部分感觉有些枯燥而且博主水平有限就没有深入,感兴趣或者有不太清楚的同学可以自行前往『原文链接』寻找更详细的讲解。顺便补上「Github项目地址」,懒得码代码的同学可以直接下载下来运行哦~希望下一篇文章不要再拖更两个礼拜了嗯就这样~


原文链接:https://snatix.com/2018/07/17/023-frames-per-second/

本文由 sNatic 发布于『大喵的新窝』 转载请保留本申明

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,837评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,551评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,417评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,448评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,524评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,554评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,569评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,316评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,766评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,077评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,240评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,912评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,560评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,176评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,425评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,114评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,114评论 2 352

推荐阅读更多精彩内容

  • 第一章 我们的宇宙图象 早在公元前340年,亚里士多德提出地心说。公元2世纪,托勒密精制成一个完整的地心说宇宙学模...
    飞子_870f阅读 3,292评论 5 8
  • 现在是两个孩子的父亲,而且还是俩男孩。感觉很有压力。 一个是物质上的压力,给他们娶媳妇的压力,现在娶老婆的成本是越...
    王冬冬_e052阅读 190评论 0 0
  • 看了刘自鸿老师的精彩讲演,感受到他身上所展现出来的一个科技创新的年轻企业家的实干、坚持和笃定,也从他的讲演...
    春风_e2d2阅读 166评论 0 0
  • 学习总结 系统主界面的制作1 1.菜单栏控件:MenuStrip2.工具栏控件:ToolStrip3.状态栏控件:...
    谭佑阅读 356评论 0 0
  • 初春的合肥,早晚温差变化还是比较明显的。带着大学的闲适,喜欢外出走走看看。一路的人潮翻拥,让我拖着身子到处寻找能供...
    荪haohao阅读 184评论 1 1