日志:打印技巧
一、概览
Unity日志打印技巧
- 常规日志打印
- 彩色日志
- 日志存储与上传
- 日志开关
- 日志双击溯源
二、常规日志打印
1、打印Hello World
调用堆栈可以很好的帮助我们定位问题,特别是报错的Error日志
Debug.Log("Hello World");Debug.Log("This is a log message.");
Debug.LogWarning("This is a warning message!");
Debug.LogError("This is an error message!");


注:这里特别说一下,Console窗口有个Error Pause按钮,意思是如果输出了Error日志,则暂停运行,有时候策划会跑过来说他的Unity运行游戏的时候突然卡死了,感觉就像发现了什么惊天大BUG,其实是他点了Error Pause,然后游戏中输出了一句Error日志。

2、打印任意类型的数据
事实上,Debug.Log的参数是object(即System.Object),
// Debug.cspublic static void Log(object message);
现在,我们自定义一个类,比如:
public class TestClass
{public int a;public string b;public bool c;public TestClass(int a, string b, bool c){this.a = a;this.b = b;this.c = c;}
}
Debug.Log(new TestClass(1, "HaHa", true)); //TestClass
事实上,它是先执行了对象的ToString()方法,然后再输出日志的,我们override(重写)类的ToString()方法,就可以自定义输出啦,比如:
public class TestClass
{public int a;public string b;public bool c;public TestClass(int a, string b, bool c){this.a = a;this.b = b;this.c = c;}// 重写ToString方法public override string ToString(){return string.Format("a:{0}\nb:{1}\nc:{2}", a, b, c);}
}
Debug.Log(new TestClass(1, "HaHa", true));
//它输出的就是a:1
b:HaHa
c:True
3、context参数干嘛的
如果你看Debug类的源码,就会发现,它有一个接收两个参数的Debug.Log方法,这第二个参数context可以帮我们定位到物体的实例
// Debug.cspublic static void Log(object message, Object context);
GameObject go = new GameObject("go");
Debug.Log("Test", go);

如果你的物体是一个还没实例化的预设的引用,则它会直接定位到Project视图中的资源,我们来做下实验。NewBehaviourScript.cs脚本代码如下:
using UnityEngine;public class NewBehaviourScript : MonoBehaviour
{public GameObject cubePrefab;void Start(){Debug.Log("Test", cubePrefab);}
}
挂到Main Camera上,把Cube.prefab预设拖给脚本的cubePrefab成员,如下:

运行,测试效果如下:

4、格式化输出
int a = 100;
float b = 0.6f;
Debug.LogFormat("a is: {0}, b is {1}", a, b);
二、彩色日志打印
我们上面打印出来的日志都是默认的颜色(白色),事实上,我们可以打印出彩色的日志,
格式<color=#rbg颜色值>xxx</color>,例:
Debug.LogFormat("This is <color=#ff0000>{0}</color>", "red");
Debug.LogFormat("This is <color=#00ff00>{0}</color>", "green");
Debug.LogFormat("This is <color=#0000ff>{0}</color>", "blue");
Debug.LogFormat("This is <color=yellow>{0}</color>", "yellow");

三、日志存储与上传
实际项目中,我们一般是需要把日志写成文件,方便出错时通过日志来定位问题。Unity提供了一个事件:Application.logMessageReceived,方便我们来监听日志打印,这样我们就可以把日志的文本内容写到文件里存起来啦~
1、打印日志事件
我们一般在游戏启动的入口脚本的Awake函数中去监听Application.logMessageReceived事件,如下:
// 游戏启动的入口脚本void Awake()
{// 监听日志回调Application.logMessageReceived += OnLogCallBack;
}/// <summary>
/// 打印日志回调
/// </summary>
/// <param name="condition">日志文本</param>
/// <param name="stackTrace">调用堆栈</param>
/// <param name="type">日志类型</param>
private void OnLogCallBack(string condition, string stackTrace, LogType type)
{ // TODO 写日志到本地文件
}
2、写日志到本地文件
Unity提供了一个可读写的路径给我们访问:Application.persistentDataPath,我们可以把日志文件存到这个路径下。
注:Application.persistentDataPath在不同平台下的路径:
Windows:C:/Users/用户名/AppData/LocalLow/CompanyName/ProductName
Android:Android/data/包名/files
Mac:/Users/用户名/Library/Caches/CompanyName/ProductName
iOS:/var/mobile/Containers/Data/Application/APP名称/Documents
需要注意,iOS的需要越狱并且使用Filza软件才能查看文件路径哦

例:
using System.IO;
using System.Text;
using UnityEngine;public class Main: MonoBehaviour
{// 使用StringBuilder来优化字符串的重复构造StringBuilder m_logStr = new StringBuilder();// 日志文件存储位置string m_logFileSavePath;void Awake(){// 当前时间var t = System.DateTime.Now.ToString("yyyyMMddhhmmss");m_logFileSavePath = string.Format("{0}/output_{1}.log", Application.persistentDataPath, t);Debug.Log(m_logFileSavePath);Application.logMessageReceived += OnLogCallBack;Debug.Log("日志存储测试");}/// <summary>/// 打印日志回调/// </summary>/// <param name="condition">日志文本</param>/// <param name="stackTrace">调用堆栈</param>/// <param name="type">日志类型</param>private void OnLogCallBack(string condition, string stackTrace, LogType type){m_logStr.Append(condition);m_logStr.Append("\n");m_logStr.Append(stackTrace);m_logStr.Append("\n");if (m_logStr.Length <= 0) return;if (!File.Exists(m_logFileSavePath)){var fs = File.Create(m_logFileSavePath);fs.Close();}using (var sw = File.AppendText(m_logFileSavePath)){sw.WriteLine(m_logStr.ToString());}m_logStr.Remove(0, m_logStr.Length);}
}
我们可以在Application.persistentDataPath路径下看到日志文件,

3、日志上传到服务器
实际项目中,我们可能需要把日志上传到服务端,方便进行查询定位。
上传文件我们可以使用UnityWebRequest来处理,这里需要注意,我们的日志文件可能很小也可能很大,正常情况下都比较小,但是有时候报错了是会循环打印日志的,导致日志文件特别大,所以我们要考虑到大文件读取的情况,否则读取日志文件时会很卡,建议使用字节流读取。

例:
// 读取日志文件的字节流
byte[] ReadLogFile()
{byte[] data = null;using(FileStream fs = File.OpenRead("你的日志文件路径")) {int index = 0;long len = fs.Length;data = new byte[len];// 根据你的需求进行限流读取int offset = data.Length > 1024 ? 1024 : data.Length;while (index < len) {int readByteCnt = fs.Read(data, index, offset);index += readByteCnt;long leftByteCnt = len - index;offset = leftByteCnt > offset ? offset : (int)leftByteCnt;}Debug.Log ("读取完毕");}return data;
}// 将日志字节流上传到web服务器
IEnumerator HttpPost(string url, byte[] data)
{WWWForm form = new WWWForm();// 塞入描述字段,字段名与服务端约定好form.AddField("desc", "test upload log file");// 塞入日志字节流字段,字段名与服务端约定好form.AddBinaryData("logfile", data, "test_log.txt", "application/x-gzip");// 使用UnityWebRequestUnityWebRequest request = UnityWebRequest.Post(url, form);var result = request.SendWebRequest();while (!result.isDone){yield return null;//Debug.Log ("上传进度: " + request.uploadProgress);}if (!string.IsNullOrEmpty(request.error)){GameLogger.LogError(request.error);}else{GameLogger.Log("日志上传完毕, 服务器返回信息: " + request.downloadHandler.text);}request.Dispose();
}
调用:
byte[] data = ReadLogFile();
StartCoroutine(HttpPost("http://你的web服务器", data));
四、日志开关
实际项目中,我们可能需要做日志开关,比如开发阶段日志开启,正式发布后则关闭日志。Unity并没有给我们提供一个日志开关的功能,那我们就自己封装一下吧~
例:
using UnityEngine;public class GameLogger
{// 普通调试日志开关public static bool s_debugLogEnable = true;// 警告日志开关public static bool s_warningLogEnable = true;// 错误日志开关public static bool s_errorLogEnable = true;public static void Log(object message, Object context = null){if (!s_debugLogEnable) return;Debug.Log(message, context);}public static void LogWarning(object message, Object context = null){if (!s_warningLogEnable) return;Debug.LogWarning(message, context);}public static void LogError(object message, Object context){if (!s_errorLogEnable) return;Debug.LogError(message, context);}
}
我们所有使用Debug打印日志的地方,都是改用GameLogger来打印,这样就可以统一通过GameLogger来开关日志的打印了~不过,这里会有一个问题,就是我们在Console日志窗口双击日志的时候,它只会跳转到GameLogger里,而不是跳转到我们调用GameLogger的地方。
比如我们在Test脚本中调用GameLogger.Log,如下:
// Test.cs using UnityEngine;public class Test : MonoBehaviour
{void Start(){GameLogger.Log("哈哈哈哈哈");}
}
看,它是跳到GameLogger里,而不是跳到我们的Test脚本了,这个是显然的,但我们能不能让它跳到Test脚本里呢?

下面我就来给大家表演一下!请往下看~
五、日志双击溯源问题
要解决上面的问题,有两种方法:
方法一:把GameLogger编译成dll放在工程中,把源码删掉;
方法二:通过反射分析日志窗口的堆栈,拦截Unity日志窗口的打开文件事件,跳转到我们指定的代码行处。
下面我来说下具体操作。
方一、GameLogger编译为dll
事实上,我们写的C#代码都会被Unity编译成dll放在工程目录的Library/ScriptAssemblies目录中,默认是Assembly-CSharp.dll,

我们可以使用ILSpy.exe反编译一下它,
注:ILSpy反编译工具可以从GitHub下载:https://github.com/icsharpcode/ILSpy

不过我们看到,这个dll包含了其他的C#代码,我们能不能专门只为GameLogger生成一个dll呢?可以滴,只需要把GameLogger.cs单独放在一个子目录中,并创建一个Assembly Definition,如下,

把Assembly Definition重命名为GameLogger,如下,

我们再回到Library/ScriptAssemblies目录中,就可以看到生成了一个GameLogger.dll啦,

把它剪切到Unity工程的Plugins目录中,把我们的GameLogger.cs脚本删掉或是移动到工程外备份(Assembly Definition文件也删掉),如下:

这样,就大功告成了,我们测试一下日志双击溯源,如下,可以看到,现在可以正常跳转到我们想要的地方了。

方二、详见
六、日志上传服务器案例
1、界面制作
使用UGUI简单做下界面,

保存为MainPanel.prefab预设,

2、C#代码
三个脚本,如下

- GameLogger.cs:封装Debug日志打印,实现日志开关控制、彩色日志打印、日志文件存储等功能;
- LogUploader.cs:实现日志上传到服务器的功能;
- Main.cs:程序入口脚本,同时实现UI界面交互。
原理我上文都有讲,这里就不赘述代码细节了,可以看注释,我都写得比较详细。
2.1、GameLogger.cs代码
/// <summary>
/// 封装Debug日志打印,实现日志开关控制、彩色日志打印、日志文件存储等功能
/// 作者:林新发 博客:https://blog.csdn.net/linxinfa
/// </summary>using UnityEngine;
using System.Text;
using System.IO;public class GameLogger
{// 普通调试日志开关public static bool s_debugLogEnable = true;// 警告日志开关public static bool s_warningLogEnable = true;// 错误日志开关public static bool s_errorLogEnable = true;// 使用StringBuilder来优化字符串的重复构造private static StringBuilder s_logStr = new StringBuilder();// 日志文件存储位置private static string s_logFileSavePath;/// <summary>/// 初始化,在游戏启动的入口脚本的Awake函数中调用GameLogger.Init/// </summary>public static void Init(){// 日期var t = System.DateTime.Now.ToString("yyyyMMddhhmmss");s_logFileSavePath = string.Format("{0}/output_{1}.log", Application.persistentDataPath, t);Application.logMessageReceived += OnLogCallBack;}/// <summary>/// 打印日志回调/// </summary>/// <param name="condition">日志文本</param>/// <param name="stackTrace">调用堆栈</param>/// <param name="type">日志类型</param>private static void OnLogCallBack(string condition, string stackTrace, LogType type){s_logStr.Append(condition);s_logStr.Append("\n");s_logStr.Append(stackTrace);s_logStr.Append("\n");if (s_logStr.Length <= 0) return;if (!File.Exists(s_logFileSavePath)){var fs = File.Create(s_logFileSavePath);fs.Close();}using (var sw = File.AppendText(s_logFileSavePath)){sw.WriteLine(s_logStr.ToString());}s_logStr.Remove(0, s_logStr.Length);}public static void UploadLog(string desc){LogUploader.StartUploadLog(s_logFileSavePath, desc);}/// <summary>/// 普通调试日志/// </summary>public static void Log(object message, Object context = null){if (!s_debugLogEnable) return;Debug.Log(message, context);}/// <summary>/// 格式化打印日志/// </summary>/// <param name="format">例:"a is {0}, b is {1}"</param>/// <param name="args">可变参数,根据format的格式传入匹配的参数,例:a, b</param>public static void LogFormat(string format, params object[] args){if (!s_debugLogEnable) return;Debug.LogFormat(format, args);}/// <summary>/// 带颜色的日志/// </summary>/// <param name="message"></param>/// <param name="color">颜色值,例:green, yellow,#ff0000</param>/// <param name="context">上下文对象</param>public static void LogWithColor(object message, string color, Object context = null){if (!s_debugLogEnable) return;Debug.Log(FmtColor(color, message), context);}/// <summary>/// 红色日志/// </summary>public static void LogRed(object message, Object context = null){if (!s_debugLogEnable) return;Debug.Log(FmtColor("red", message), context);}/// <summary>/// 绿色日志/// </summary>public static void LogGreen(object message, Object context = null){if (!s_debugLogEnable) return;Debug.Log(FmtColor("green", message), context);}/// <summary>/// 黄色日志/// </summary>public static void LogYellow(object message, Object context = null){if (!s_debugLogEnable) return;Debug.Log(FmtColor("yellow", message), context);}/// <summary>/// 青蓝色日志/// </summary>public static void LogCyan(object message, Object context = null){if (!s_debugLogEnable) return;Debug.Log(FmtColor("#00ffff", message), context);}/// <summary>/// 带颜色的格式化日志打印/// </summary>public static void LogFormatWithColor(string format, string color, params object[] args){if (!s_debugLogEnable) return;Debug.LogFormat((string)FmtColor(color, format), args);}/// <summary>/// 警告日志/// </summary>public static void LogWarning(object message, Object context = null){if (!s_warningLogEnable) return;Debug.LogWarning(message, context);}/// <summary>/// 错误日志/// </summary>public static void LogError(object message, Object context = null){if (!s_errorLogEnable) return;Debug.LogError(message, context);}/// <summary>/// 格式化颜色日志/// </summary>private static object FmtColor(string color, object obj){if (obj is string){
#if !UNITY_EDITORreturn obj;
#elsereturn FmtColor(color, (string)obj);
#endif}else{
#if !UNITY_EDITORreturn obj;
#elsereturn string.Format("<color={0}>{1}</color>", color, obj);
#endif}}/// <summary>/// 格式化颜色日志/// </summary>private static object FmtColor(string color, string msg){
#if !UNITY_EDITORreturn msg;
#elseint p = msg.IndexOf('\n');if (p >= 0) p = msg.IndexOf('\n', p + 1);// 可以同时显示两行if (p < 0 || p >= msg.Length - 1) return string.Format("<color={0}>{1}</color>", color, msg);if (p > 2 && msg[p - 1] == '\r') p--;return string.Format("<color={0}>{1}</color>{2}", color, msg.Substring(0, p), msg.Substring(p));
#endif}#region 解决日志双击溯源问题
#if UNITY_EDITOR[UnityEditor.Callbacks.OnOpenAssetAttribute(0)]static bool OnOpenAsset(int instanceID, int line){string stackTrace = GetStackTrace();if (!string.IsNullOrEmpty(stackTrace) && stackTrace.Contains("GameLogger:Log")){// 使用正则表达式匹配at的哪个脚本的哪一行var matches = System.Text.RegularExpressions.Regex.Match(stackTrace, @"\(at (.+)\)",System.Text.RegularExpressions.RegexOptions.IgnoreCase);string pathLine = "";while (matches.Success){pathLine = matches.Groups[1].Value;if (!pathLine.Contains("GameLogger.cs")){int splitIndex = pathLine.LastIndexOf(":");// 脚本路径string path = pathLine.Substring(0, splitIndex);// 行号line = System.Convert.ToInt32(pathLine.Substring(splitIndex + 1));string fullPath = Application.dataPath.Substring(0, Application.dataPath.LastIndexOf("Assets"));fullPath = fullPath + path;// 跳转到目标代码的特定行UnityEditorInternal.InternalEditorUtility.OpenFileAtLineExternal(fullPath.Replace('/', '\\'), line);break;}matches = matches.NextMatch();}return true;}return false;}/// <summary>/// 获取当前日志窗口选中的日志的堆栈信息/// </summary>/// <returns></returns>static string GetStackTrace(){// 通过反射获取ConsoleWindow类var ConsoleWindowType = typeof(UnityEditor.EditorWindow).Assembly.GetType("UnityEditor.ConsoleWindow");// 获取窗口实例var fieldInfo = ConsoleWindowType.GetField("ms_ConsoleWindow",System.Reflection.BindingFlags.Static |System.Reflection.BindingFlags.NonPublic);var consoleInstance = fieldInfo.GetValue(null);if (consoleInstance != null){if ((object)UnityEditor.EditorWindow.focusedWindow == consoleInstance){// 获取m_ActiveText成员fieldInfo = ConsoleWindowType.GetField("m_ActiveText",System.Reflection.BindingFlags.Instance |System.Reflection.BindingFlags.NonPublic);// 获取m_ActiveText的值string activeText = fieldInfo.GetValue(consoleInstance).ToString();return activeText;}}return null;}
#endif#endregion 解决日志双击溯源问题
}
2.2、LogUploader.cs代码
/// <summary>
/// 实现日志上传到服务器的功能
/// 作者:林新发 博客:https://blog.csdn.net/linxinfa
/// </summary>using System.Collections;
using System.IO;
using UnityEngine;
using UnityEngine.Networking;public class LogUploader : MonoBehaviour
{private static string LOG_UPLOAD_URL = "http://127.0.0.1:7890/upload_log.php";public static void StartUploadLog(string logFilePath, string desc){var go = new GameObject("LogUploader");var bhv = go.AddComponent<LogUploader>();bhv.StartCoroutine(bhv.UploadLog(logFilePath, LOG_UPLOAD_URL, desc));}/// <summary>/// 上报日志到服务端/// </summary>/// <param name="url">http接口</param>/// <param name="desc">描述</param>private IEnumerator UploadLog(string logFilePath, string url, string desc){var fileName = Path.GetFileName(logFilePath);var data = ReadLogFile(logFilePath);WWWForm form = new WWWForm();// 塞入描述字段,字段名与服务端约定好form.AddField("desc", desc);// 塞入日志字节流字段,字段名与服务端约定好form.AddBinaryData("logfile", data, fileName, "application/x-gzip");// 使用UnityWebRequestUnityWebRequest request = UnityWebRequest.Post(url, form);var result = request.SendWebRequest();while (!result.isDone){yield return null;//Debug.Log ("上传进度: " + request.uploadProgress);}if (!string.IsNullOrEmpty(request.error)){GameLogger.LogError(request.error);}else{GameLogger.Log("日志上传完毕, 服务器返回信息: " + request.downloadHandler.text);}request.Dispose();}private byte[] ReadLogFile(string logFilePath){byte[] data = null;using (FileStream fs = File.OpenRead(logFilePath)){int index = 0;long len = fs.Length;data = new byte[len];// 根据你的需求进行限流读取int offset = data.Length > 1024 ? 1024 : data.Length;while (index < len){int readByteCnt = fs.Read(data, index, offset);index += readByteCnt;long leftByteCnt = len - index;offset = leftByteCnt > offset ? offset : (int)leftByteCnt;}}return data;}
}
2.3 Main.cs代码
using UnityEngine;
using UnityEngine.UI;public class Main : MonoBehaviour
{/// <summary>/// 日志开关/// </summary>public Toggle logEnableTgl;/// <summary>/// 打印日志按钮/// </summary>public Button logBtn;/// <summary>/// 上传日志按钮/// </summary>public Button uploadBtn;/// <summary>/// 日志文本Text/// </summary>public Text logText;void Awake(){GameLogger.Init();// 监听日志,输出到logText中Application.logMessageReceived += (string condition, string stackTrace, LogType type) => {switch(type){case LogType.Log:{if (!GameLogger.s_debugLogEnable) return;}break;case LogType.Warning:{if (!GameLogger.s_warningLogEnable) return;}break;case LogType.Error:{if (!GameLogger.s_errorLogEnable) return;}break;}logText.text += condition + "\n";};}private void Start(){logText.text = "";uploadBtn.onClick.AddListener(() =>{GameLogger.UploadLog("上传日志测试");});logBtn.onClick.AddListener(() =>{GameLogger.Log("打印一行日志");});logEnableTgl.onValueChanged.AddListener((v) => {GameLogger.s_debugLogEnable = v;});GameLogger.Log("大家好,我是林新发");GameLogger.LogCyan("我的CSDN博客:https://blog.csdn.net/linxinfa");GameLogger.LogYellow("欢迎关注、点赞,感谢支持~");GameLogger.LogRed("❤❤❤❤❤❤❤❤❤❤❤");}
}
3、挂Main脚本
给MainPanel.prefab预设挂上Main.cs脚本,并赋值UI成员,如下:

4、Web服务器
我们的日志要上传到Web服务器,所以我们需要搭建一个Web服务器。简单的做法是使用PHP小皮~
4.1、Web服务器
关于小皮,可以看我之前写的这篇文章:https://blog.csdn.net/linxinfa/article/details/103033142
我们设置一下端口号,比如7890,

启动Apache,

这样,我们就已经启动了一个Web服务器了,通过http://127.0.0.1:7890即可访问
4.2、PHP脚本:upload_log.php
我们打开Web服务器的根目录,

在根目录中创建一个upload_log.php,

php代码如下:
<?php
// 获取描述
$desc = $_POST["desc"];
// 获取临时文件路径
$tmp = $_FILES["logfile"]["tmp_name"];
// 文件保存位置
$savePath = "upload/" . $_FILES["logfile"]["name"];
// 判断文件是否已存在
if (file_exists($savePath))
{// 文件已存在,删除它unlink($savePath);
}
// 保存文件到savePath的路径下
move_uploaded_file($tmp, $savePath);
echo "文件上传成功";
?>
我们再创建一个upload文件夹,用于存放上传上来的日志,

5、运行测试
运行Unity,测试效果如下:

日志成功上传到了Web服务器的upload目录中,

日志文件内容如下:
大家好,我是林新发
UnityEngine.Debug:Log (object,UnityEngine.Object)
GameLogger:Log (object,UnityEngine.Object) (at Assets/Scripts/GameLogger/GameLogger.cs:72)
Main:Start () (at Assets/Scripts/Main.cs:70)<color=#00ffff>我的CSDN博客:https://blog.csdn.net/linxinfa</color>
UnityEngine.Debug:Log (object,UnityEngine.Object)
GameLogger:LogCyan (object,UnityEngine.Object) (at Assets/Scripts/GameLogger/GameLogger.cs:131)
Main:Start () (at Assets/Scripts/Main.cs:71)<color=yellow>欢迎关注、点赞,感谢支持~</color>
UnityEngine.Debug:Log (object,UnityEngine.Object)
GameLogger:LogYellow (object,UnityEngine.Object) (at Assets/Scripts/GameLogger/GameLogger.cs:122)
Main:Start () (at Assets/Scripts/Main.cs:72)<color=red>❤❤❤❤❤❤❤❤❤❤❤</color>
UnityEngine.Debug:Log (object,UnityEngine.Object)
GameLogger:LogRed (object,UnityEngine.Object) (at Assets/Scripts/GameLogger/GameLogger.cs:104)
Main:Start () (at Assets/Scripts/Main.cs:73)打印一行日志
UnityEngine.Debug:Log (object,UnityEngine.Object)
GameLogger:Log (object,UnityEngine.Object) (at Assets/Scripts/GameLogger/GameLogger.cs:72)
Main/<>c:<Start>b__5_1 () (at Assets/Scripts/Main.cs:62)
UnityEngine.EventSystems.EventSystem:Update () (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/EventSystem.cs:501)打印一行日志
UnityEngine.Debug:Log (object,UnityEngine.Object)
GameLogger:Log (object,UnityEngine.Object) (at Assets/Scripts/GameLogger/GameLogger.cs:72)
Main/<>c:<Start>b__5_1 () (at Assets/Scripts/Main.cs:62)
UnityEngine.EventSystems.EventSystem:Update () (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/EventSystem.cs:501)打印一行日志
UnityEngine.Debug:Log (object,UnityEngine.Object)
GameLogger:Log (object,UnityEngine.Object) (at Assets/Scripts/GameLogger/GameLogger.cs:72)
Main/<>c:<Start>b__5_1 () (at Assets/Scripts/Main.cs:62)
UnityEngine.EventSystems.EventSystem:Update () (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/EventSystem.cs:501)打印一行日志
UnityEngine.Debug:Log (object,UnityEngine.Object)
GameLogger:Log (object,UnityEngine.Object) (at Assets/Scripts/GameLogger/GameLogger.cs:72)
Main/<>c:<Start>b__5_1 () (at Assets/Scripts/Main.cs:62)
UnityEngine.EventSystems.EventSystem:Update () (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/EventSystem.cs:501)打印一行日志
UnityEngine.Debug:Log (object,UnityEngine.Object)
GameLogger:Log (object,UnityEngine.Object) (at Assets/Scripts/GameLogger/GameLogger.cs:72)
Main/<>c:<Start>b__5_1 () (at Assets/Scripts/Main.cs:62)
UnityEngine.EventSystems.EventSystem:Update () (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/EventSystem.cs:501)
6、工程源码
本文工程源码我一上传到CODE CHINA,感兴趣的同学可自行下载下来学习。
地址:林新发 / UnityLoggerDemo · GitCode
注:我使用的Unity版本为:2021.1.7f1c1
相关文章:
日志:打印技巧
一、概览 Unity日志打印技巧 常规日志打印彩色日志日志存储与上传日志开关日志双击溯源 二、常规日志打印 1、打印Hello World 调用堆栈可以很好的帮助我们定位问题,特别是报错的Error日志 Debug.Log("Hello World");Debug.Log("This is a log m…...
二叉树的常见操作
建立树 复制二叉树 计算深度 计算总结点数 计算叶子结点数...
CSS 根据子元素选择父元素,并设置父元素的样式
场景举例:当子元素有增加了一个class时,需要影响其父元素的样式 可以使用":has"伪类来实现选择父元素的效果 <style>.parent:has(.child){background-color: #eee;}p{width:100px;border:1px solid #000;} </style> <body>…...
onnx转trt时,关于动态shape自动配置默认值的脚本
onnx转trt时,关于动态shape自动配置默认值,一般需要指定3个shape,分别是最小最优与最大。但是我们在测试时不想写那么多的代码,能否自动实现3个shape的配置,这里实现了一版。 import osimport tensorrt as trt import…...
实验室无法培养的菌,原来可以这么研究!
厌氧氨氧化(anammox)细菌在全球氮循环和废水氮去除中发挥着至关重要的作用,由于anammox细菌生长缓慢、难以培养等特点,对其生态学和生物学特性知之甚少。近日,凌恩生物合作客户重庆大学陈猷鹏教授团队在《Science of t…...
Xed编辑器开发第一期:使用Rust从0到1写一个文本编辑器
这是一个使用Rust实现的轻量化文本编辑器。学过Rust的都知道,Rust 从入门到实践中间还隔着好几个Go语言的难度,因此,如果你也正在学习Rust,那么恭喜你,这个项目被你捡到了。本项目内容较多,大概会分三期左右陆续发布&a…...
农业自动气象监测站:赋能智慧农业的新动力
在信息化、智能化快速发展的今天,农业领域也迎来了前所未有的变革。其中,农业自动气象监测站作为智慧农业的重要组成部分,正在发挥着越来越重要的作用。它们如同农业生产的“眼睛”和“耳朵”,实时感知和记录着大气的微妙变化&…...
2-6 任务 猜数小游戏(单次版)
本任务要求编写一个猜数小游戏(单次版),游戏规则是计算机产生一个0到100之间的随机整数,用户通过输入猜测的数字进行猜测,根据猜测情况给出提示,直到猜对为止。编程思路是利用while循环和多分支结构实现永真…...
springboot 定时任务解决方案
Scheduled (springboot 自带的 注解) 基于注解Scheduled默认为单线程,开启多个任务时,任务的执行时机会受上一个任务执行时间的影响。 EnableScheduling注解: 在配置类上使用,开启计划任务的支持(类上)。…...
谷粒商城实战(024 业务-订单模块-分布式事务1)
Java项目《谷粒商城》架构师级Java项目实战,对标阿里P6-P7,全网最强 总时长 104:45:00 共408P 此文章包含第284p-第p290的内容 简介 模拟积分服务出异常,前方的锁库存事务未回滚,这时候就需要分布式事务 本地事务 事务的隔离…...
.NET使用Microsoft.IdentityModel.Tokens对SAML2.0登录断言校验
如题。使用SAML单点登录对IDP返回的Response断言使用微软提供的Microsoft.IdentityModel.Tokens对断言(Assertion)进行校验。 首先需要安装Muget包,Microsoft.IdentityModel.Tokens和Microsoft.IdentityModel.Tokens.Saml。 简易示例代码如…...
性能测试学习二
瓶颈的精准判断 TPS曲线 tps图 响应时间图 拐点在哪里呢? 这是一个阶梯式增加的场景,拐点在第二个压力阶梯上就出现了,因为响应时间增加了,tps增加的却不多,在第三个阶段时,tps增加的就更少了,响应时间也在不断增加,所以性能瓶颈在加剧,越往后越明显【tps的增长,…...
小丑的身份证和复印件 (BFS + Floyd)
本题链接:登录—专业IT笔试面试备考平台_牛客网 题目: 样例: 输入 2 10 (JOKERjoke #####asdr) 输出 12 思路: 根据题意,要求最短时间,实际上也可以理解为最短距离。 所以应该联想到有关最短距离的算法&…...
C++类与对象(上)
C类与对象 面向过程和面向对象初步认识类的引入类的定义类的两种定义方式: 类的访问限定符及封装访问限定符 封装类的作用域类的实例化类对象模型如何计算类对象的大小结构体内存对齐规则: this指针 面向过程和面向对象初步认识 C语言是面向过程的&…...
Exchanger的 常用场景及使用示例
Exchanger的 常用场景及使用示例 Exchanger是Java并发包中的一个工具类,它用于两个线程之间交换数据。当两个线程都到达同步点并调用exchange()方法时,它们会交换数据然后继续执行。Exchanger特别适用于那些需要两个线程进行协作,交换数据或…...
Spring AI项目Open AI对话接口开发指导
文章目录 创建Spring AI项目配置项目pom、application文件controller接口开发接口测试 创建Spring AI项目 打开IDEA创建一个新的spring boot项目,填写项目名称和位置,类型选择maven,组、工件、软件包名称可以自定义,JDK选择17即可…...
决策规划仿真平台的搭建
以下内容笔记据来自于b站up主忠厚老实的老王,视频;链接如下: 自动驾驶决策规划算法第二章第一节 决策规划仿真平台搭建_哔哩哔哩_bilibili 使用到的软件有matlab、prescan、carsim以及visual stadio。 我电脑上软件的版本是matlab2022a&am…...
RustGUI学习(iced/iced_aw)之扩展小部件(十八):如何使用badge部件来凸显UI元素?
前言 本专栏是学习Rust的GUI库iced的合集,将介绍iced涉及的各个小部件分别介绍,最后会汇总为一个总的程序。 iced是RustGUI中比较强大的一个,目前处于发展中(即版本可能会改变),本专栏基于版本0.12.1. 概述 这是本专栏的第十八篇,主要讲述badge标记部件的使用,会结合实…...
触摸播放视频,并用iframe实现播放外站视频
效果: html: <div:style"{ height: homedivh }"class"rightOne_content_div_div"mouseenter"divSeenter(i)"mouseleave"divLeave(i)"click"ItemClick(i)"><!-- isUser是否是用户上传 --><divv-if…...
接口自动化-requests库
requests库是用来发送请求的库,本篇用来讲解requests库的基本使用。 1.安装requests库 pip install requests 2.requests库底层方法的调用逻辑 (1)get / post / put / delete 四种方法底层调用 request方法 注意:data和json都…...
【Axure高保真原型】引导弹窗
今天和大家中分享引导弹窗的原型模板,载入页面后,会显示引导弹窗,适用于引导用户使用页面,点击完成后,会显示下一个引导弹窗,直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…...
React Native 导航系统实战(React Navigation)
导航系统实战(React Navigation) React Navigation 是 React Native 应用中最常用的导航库之一,它提供了多种导航模式,如堆栈导航(Stack Navigator)、标签导航(Tab Navigator)和抽屉…...
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...
Spring AI 入门:Java 开发者的生成式 AI 实践之路
一、Spring AI 简介 在人工智能技术快速迭代的今天,Spring AI 作为 Spring 生态系统的新生力量,正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务(如 OpenAI、Anthropic)的无缝对接&…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
Device Mapper 机制
Device Mapper 机制详解 Device Mapper(简称 DM)是 Linux 内核中的一套通用块设备映射框架,为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程,并配以详细的…...
docker 部署发现spring.profiles.active 问题
报错: org.springframework.boot.context.config.InvalidConfigDataPropertyException: Property spring.profiles.active imported from location class path resource [application-test.yml] is invalid in a profile specific resource [origin: class path re…...
【分享】推荐一些办公小工具
1、PDF 在线转换 https://smallpdf.com/cn/pdf-tools 推荐理由:大部分的转换软件需要收费,要么功能不齐全,而开会员又用不了几次浪费钱,借用别人的又不安全。 这个网站它不需要登录或下载安装。而且提供的免费功能就能满足日常…...
【Go语言基础【12】】指针:声明、取地址、解引用
文章目录 零、概述:指针 vs. 引用(类比其他语言)一、指针基础概念二、指针声明与初始化三、指针操作符1. &:取地址(拿到内存地址)2. *:解引用(拿到值) 四、空指针&am…...
RabbitMQ入门4.1.0版本(基于java、SpringBoot操作)
RabbitMQ 一、RabbitMQ概述 RabbitMQ RabbitMQ最初由LShift和CohesiveFT于2007年开发,后来由Pivotal Software Inc.(现为VMware子公司)接管。RabbitMQ 是一个开源的消息代理和队列服务器,用 Erlang 语言编写。广泛应用于各种分布…...
