【效率开发】游戏开发Debug效率方法总结

"程序员的一半生命都浪费在了调试上。"

        ——Brian Kernighan(计算机科学家,曾参与开发C语言)

图片

(图片来源:forbesindia)

Debug无疑是程序员最头疼,也是耗费时间最多的一个环节,因此如何提高Debug效率至关重要。本文将会根据小棋自身多年的开发经验,总结和分享五种常用的调试方式,帮助你提高开发效率。

图片

注:案例中会使用到C#和python语言,其他语言基本同理。


一、Log

> 看起来平平无奇,却是最基础最简洁的debug方式,在没有Vscode等编辑器之前,是最常用的调试方式啦。O(∩_∩)O~

1. python

>>> print("hello world")hello world

2. C#

// C# Console.WriteLine("hello world")
// Unity对此进行了封装Debug.Log("hello world");print("hello world");

其实Unity中print底层调用的也是Debug.Log

图片

3. Log如何帮助我们得到有用信息?

学会如何打Log也是一个重要编程能力,举个简单的例子:Unity开发游戏过程中经常会提示空指针异常。

NullReferenceException: Object reference not set to an instance of an object

这时候我们可以把Log打在报错附近,查看报错附近的代码语义:

ResManager.LoadPrefab(path);

可以看到这里尝试加载某个路径的物体,尝试打印一些关键信息,比如:​​​​​​​

print(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");print(path);

其中,第一行是一个显眼的标识,方便快速定位log,第二行是关键信息。最终结果:​​​​​​​

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Panel/MenuPanel1

然后到加载路径一看,原来路径下只有Panel/MenuPanel,没有Panel/MenuPanel1,修改为正确路径,这样问题就得到解决了。


二、traceback

traceback是我入职当年,我认为导师教我的最简单又好用的功能。有时候游戏在运行过程中,莫名其妙执行了一个操作:比如玩家在某个时间执行了两次攻击逻辑,又或者在错误的时间某个UI界面弹出了。

总结下,适合traceback调试有两个基本要求:

  1. 攻击和UI打开的执行逻辑你知道在哪里 (*^▽^*)

  2. 为什么会执行,谁执行了这个逻辑,不知道 o(╥﹏╥)o

这时候怎么做呢?

图片

1、Python

在python中非常简洁:

在被调用的方法中执行,traceback.print_stack。

当这个方法被执行的时候,我们就清楚的知道是谁执行了这个代码。

图片

2. Unity

Unity游戏引擎深知traceback对于开发效率的提升,因此在编辑器中我们甚至无需做任何处理,就能得到调用栈的信息。

图片

下方有完整的traceback调用栈,双击log信息还能跳转到指定的代码行数,非常方便。

但是这有个前提,需要再Project Settings -> Player -> Other 对Stack Trace进行设置,将其统一设置为ScriptOnly就ok了。(这个设置是默认的)

图片


三、把Log输出到文件中

当你把游戏打开输出之后,或者把python编译成exe运行时,没有了编辑器,也没有log窗口了。这时候我们该如何调试项目呢?

把Log输出到文本文件中就是一个不错的方法,市面上主流的软件也都会执行这一操作。

这里有两种办法供大家选择:

1. 方法一,写一个新的Log.Print方法,调用这个方法的log会输出到文件中。

图片

2. 方法二,用户可以继续使用系统内置的打印方法,不改变用户习惯,我们只监听或者劫持系统的打印方法,并将其输出到log文件中。

显然,第二种方法是更好的。其中Unity用于监听默认打印事件的方法是:

Application.logMessageReceived

而python对应的事件是:

sys.stdout

其他语言也有类似的输出流。

这里我以Unity为例,提供一个参考案例(抄了代码记得点点在看和收藏呀,求求您啦~):​​​​​​​

using UnityEngine;using System.IO;using System;using System.Diagnostics;
public class LogToFile : MonoBehaviour{    private string logFilePath;    private StreamWriter streamWriter;
    void Start()    {        // 创建一个新的文件名,包含时间戳        string fileName = "log_" + DateTime.Now.ToString("yyyyMMdd_HHmmss") + ".txt";        // 获取日志文件夹的路径        string logFolder = Application.persistentDataPath + "/log";        // 确保日志文件夹存在        Directory.CreateDirectory(logFolder);        // 拼接出完整的日志文件路径        logFilePath = Path.Combine(logFolder, fileName);
        // 开始写入日志        streamWriter = File.AppendText(logFilePath);
        // 监听日志事件        Application.logMessageReceived += LogMessageHandler;    }
    void OnDestroy()    {        // 关闭文件流        if (streamWriter != null)        {            streamWriter.Close();        }    }
    void LogMessageHandler(string logString, string stackTrace, LogType type)    {        // 获取当前时间        string currentTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");                // 使用 System.Diagnostics.StackTrace 获取调用栈信息        StackTrace trace = new StackTrace(true);        // 获取最后一个帧的栈帧        StackFrame frame = trace.GetFrame(trace.FrameCount - 1);        // 获取调用的脚本路径        string scriptPath = Path.GetFileName(frame.GetFileName());        // 获取调用的行号        int lineNumber = frame.GetFileLineNumber();
        // 将日志信息写入文件        streamWriter.WriteLine(currentTime + " [" + type + "] " + scriptPath + "(" + lineNumber + "): " + logString);        // 强制刷新缓冲区,确保日志信息立即写入文件        streamWriter.Flush();    }}

其中,关键代码是:​​​​​​​

// 监听日志事件        Application.logMessageReceived += LogMessageHandler;

其他代码主要是对log信息进行整理,提供:

  • 事件信息

  • 调用栈信息

  • 调用行信息

把LogToFile文件放到场景中的某个物体上后,来看看执行后的效果:​​​​​​​

2024-03-31 16:55:37 [Log] LoginScene.cs(11): >>>>>>>>>>>>>>>>>>>>>> openUI: MenuPanel2024-03-31 16:55:37 [Exception] (0): ArgumentException: The Object you want to instantiate is null.2024-03-31 16:55:37 [Log] MenuPanel.cs(55): >>>> start2024-03-31 16:55:37 [Exception] (0): NullReferenceException: Object reference not set to an instance of an object

非常方便的可以定位到问题。

图片

图片

图片


四、点击调试

点击调试主要讲游戏开发,这里以Unity举例。

对于用户输入来说,鼠标和手指点击是非常重要的一个操作,但是开发过程中经常发现点不到我们想要的物体,这时候可以通过射线检测判断下当前点击事件被哪些物体拦截了。

下面提供代码:​​​​​​​

using UnityEngine;using UnityEngine.EventSystems;
public class ClickDetector : MonoBehaviour{    void Update()    {        // 检测鼠标左键点击        if (Input.GetMouseButtonDown(0))        {            // 检查是否点击在 UI 上            if (EventSystem.current.IsPointerOverGameObject())            {                // 获取点击的 UI 元素                GameObject clickedObject = EventSystem.current.currentSelectedGameObject;
                // 打印 UI 元素的名称                if (clickedObject != null)                {                    Debug.Log("Clicked on UI element: " + clickedObject.name);                }                else                {                    Debug.Log("Clicked on UI element, but no object found.");                }            }            else            {                // 如果没有点击在 UI 上,则检查物体                Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);                RaycastHit hit;
                if (Physics.Raycast(ray, out hit))                {                    // 打印物体的名称                    Debug.Log("Clicked on object: " + hit.collider.gameObject.name);                }                else                {                    Debug.Log("Clicked, but no object found.");                }            }        }    }}

具体使用方法,就是把这个脚本放到场景中,然后鼠标点到某个物体,就会打印出指定的物体名称。如果是因为某个物体挡在前面导致被拦截,就能很快的找到问题所在了。

图片

图片

图片

五、断点调试

断点调试是一种在程序执行过程中暂停执行并允许程序员检查程序状态的调试技术。

在大多数集成开发环境(IDE)和调试器中,你可以通过点击编辑器的某一行代码旁边的区域(通常是左侧)来设置断点

当程序执行到这一行时,会自动停止执行,然后你可以逐步执行代码、观察变量值、检查堆栈跟踪等。

图片

因为之前在其他平台已经发过了,所以这里就简单贴下链接,感兴趣的同学可以跳转过去看看。

图片

图片

图片


 

欢迎大家后台私信补充更多效率开发的方法,目前公众号还不支持留言功能,会尽快想办法哒~

 想了解更多游戏开发知识,可以扫描下方二维码,免费领取游戏开发4天训练营课程


好啦,以上就是本期分享的内容。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/691451.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【linux】详解linux基本指令

目录 cat more less head tail 时间 cal find grep zip/unzip tar bc uname –r 关机 小编一共写了两篇linux基本指令,这两篇涵盖了大部分初学者的必备指令,这是第二篇,第一篇详见http://t.csdnimg.cn/HRlVt cat 适合查看小文…

VP Codeforces Round 944 (Div 4)

感受&#xff1a; A~G 其实都不难&#xff0c;都可以试着补起来。 H看到矩阵就放弃了。 A题&#xff1a; 思路&#xff1a; 打开编译器 代码&#xff1a; #include <iostream> #include <vector> #include <algorithm> #define int long long using na…

一件事做了十年

目录 一、背景二、过程1.贫困山区的心理悲哀2.基础差的客观转变3.对于教育的思考4.持续做这件事在路上5.同行人有很早就完成的&#xff0c;有逐渐放弃的&#xff0c;你应该怎么办&#xff1f;6.回头看&#xff0c;什么才是最终留下的东西? 三、总结 一、背景 在哪里出生我们无…

leetcode刷题指南

本文我将分享给大家一套我自己使用良久并觉得非常高效的 学习论&#xff0c;它可以运用到 Leetcode 上的刷题&#xff0c;也可以 generalize 到生活中涉及到学习以及记忆的方方面面。当然&#xff0c;本文将以 Leetcode 刷题为 case study 去进行讲解。 更具体一点, 我会教大家…

python的import导入规则

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、pycharm只能看到当前工作路径父目录下所有文件和项目根目录下所有文件二、sys或者图形界面添加解释器路径&#xff08;搜寻路径&#xff09;三、import导入…

【密码学原语介绍】PPRF(可穿孔伪随机函数)

在现代密码学中&#xff0c;伪随机函数&#xff08;PRF&#xff09;是构建各种加密协议和系统的基石。它们提供了一种方式&#xff0c;通过它&#xff0c;给定一个密钥和一个输入&#xff0c;可以生成一个无法预测的伪随机输出。这种机制对于确保数据加密、身份验证和完整性验证…

通过acl设置阻止数据包通过

实验拓扑和信息如图&#xff08;配置信息参考上一章内容&#xff09; acl设置代码 AR4 系统是视图下 acl 2000 rule 5 deny source 10.10.10.1 0 接口0视图下 数据接收时 traffic-filter inbound acl 2000 测试结果

nacos命名空间的配置

给微服务配置namespace 给微服务配置namespace只能通过修改配置来实现。 例如&#xff0c;修改order-service的application.yml文件&#xff1a; spring:cloud:nacos:server-addr: localhost:8848discovery:cluster-name: HZnamespace: 492a7d5d-237b-46a1-a99a-fa8e98e4b0f…

第二步->手撕spring源码之bean操作

本步骤目标 本步骤继续完善 Spring Bean 容器框架的功能开发&#xff0c;在这个开发过程中会用到较多的接口、类、抽象类&#xff0c;它们之间会有类的实现、类的继承。 这一次我们把 Bean 的创建交给容器&#xff0c;而不是我们在调用时候传递一个实例化好的 Bean 对象&#x…

《Fundamentals of Power Electronics》——转换器的传递函数

转换器的工程设计过程主要由以下几个主要步骤组成&#xff1a; 1. 定义了规范和其他设计目标。 2. 提出了一种电路。这是一个创造性的过程&#xff0c;利用了工程师的物理洞察力和经验。 3. 对电路进行了建模。组件和系统的其他部分适当建模&#xff0c;通常使用供应商提供的…

网络安全等级保护的发展历程

1994年国务院147号令第一次提出&#xff0c;计算机信息系统实行安全等级保护&#xff0c;这也预示着等保的起步。 2007年《信息安全等级保护管理办法》的发布之后。是等保在各行业深耕落地的时代。 2.0是等保版本的俗称&#xff0c;不是等级。等保共分为五级&#xff0c;二级…

NodeMCU ESP8266 获取I2C从机地址

文章目录 前言关于地址位读写位程序总结前言 I2C总线上可以挂载很多的从设备,每个设备都会有一个自己唯一的一个地址; 关于地址位 通常地址位占7位数据,主设备如果需要向从机发送/接收数据,首先要发送对应从机的地址,然后会匹配总线上挂载的从机的地址; 读写位 该位…