日志:打印技巧

一、概览

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

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

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

相关文章

【java】代理

什么是代理 假设有一个核心方法叫转账&#xff0c;为了安全性考虑&#xff0c;不能让用户直接访问接口。此时涉及到了代理&#xff0c;这使得用户只能访问代理类&#xff0c;增加了访问限制。 代理的定义&#xff1a;给目标对象提供一个代理对象&#xff0c;并且由代理对象控…

果蔬经营平台|基于SSM+vue的果蔬经营平台系统的设计与实现(源码+数据库+文档)

果蔬经营平台系统 目录 基于SSM&#xff0b;vue的果蔬经营平台系统的设计与实现 一、前言 二、系统设计 三、系统功能设计 1系统功能模块 2管理员功能模块 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介…

Python面试50题!面试巩固必看!

题目001: 在Python中如何实现单例模式。 点评&#xff1a;单例模式是指让一个类只能创建出唯一的实例&#xff0c;这个题目在面试中出现的频率极高&#xff0c;因为它考察的不仅仅是单例模式&#xff0c;更是对Python语言到底掌握到何种程度&#xff0c;建议大家用装饰器和元类…

【卫星影像三维重建-全流程代码实现】点云Mesh重构

点云—>Mesh模型 1.介绍1.1 背景1.2 效果示意 2 算法实现2.1 依赖库2.2 实验数据2.3 代码实现2.4 实验效果 3.总结 1.介绍 1.1 背景 &#xff08;1&#xff09;本文主要内容是将三维点云&#xff08;离散的三维点&#xff09;进行表面重建生成Mesh网格&#xff0c;之前有篇…

IP跳变是什么,有什么作用?

IP跳变&#xff0c;简单来说&#xff0c;就是用户在使用网络时&#xff0c;不固定使用一个IP地址&#xff0c;而是定期或根据需求切换到另一个IP地址。这种技术为用户在网络环境中提供了一定的灵活性和安全性。 首先&#xff0c;我们来看看IP跳变的具体实现方式。当用户需要切…

UML快速入门篇

目录 1. UML概述 2. 类的表示 2.1. 类的表示 2.2. 抽象类的表示 2.3. 接口的表示 3. 类的属性&#xff0c;方法&#xff0c;访问权限的表示 3.1. 类的属性 3.2. 类的方法 3.3. 类的权限 4. 类的关联 4.1. 单向关联 4.2. 双向关联 4.3. 自关联 4.4. 类的聚合 4.5.…

Lists.partition用法详解

文章目录 一、引入依赖二、用法三、输出 一、引入依赖 依赖于谷歌的guava 包 <!-- guava --><dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>28.1-jre</version></dependency&…

仿照el-upload 封装自己的上传控件(el-upload 移动端无法吊起相机)

input选择图片的那个选择在h5的时候在去年下半年突然无法无法出现唤醒相机的选项 不知道出现的原因 发现el-upload作为h5的时候无法吊起相机 又因为需要对服务端地址图片进行回显&#xff08;处于编辑功能的情况下 非新增 新增el-upload 可以实现回显&#xff09; 两个功…

如文所示:

影响 ConnectWise 的 ScreenConnect 远程桌面访问产品的严重漏洞已被广泛利用来传播勒索软件和其他类型的恶意软件。 ConnectWise 于 2 月 19 日通知客户&#xff0c;它已发布针对关键身份验证绕过缺陷和高严重性路径遍历问题的补丁。该安全漏洞当时没有 CVE 标识符。第二天&am…

绘唐3启动器怎么启动一键追爆款3正式版

绘唐3启动器怎么启动一键追爆款3正式版 工具入口 一.文案助手&#xff1a; 【注意&#xff01;&#xff01;】如果图片无显示&#xff0c;一般情况下被杀毒拦截&#xff0c;需关闭杀毒软件或者信任文件路径。 win10设置排除文件&#xff1a; 1.【新建工程】使用前先新建工程…

谷歌Gboard应用的语言模型创新:提升打字体验的隐私保护技术

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

blender cell fracture制作破碎效果,将一个模型破碎成多个模型

效果&#xff1a; 1.打开编辑-》偏好设置。搜索cell&#xff0c;勾选上如下图所示的&#xff0c;然后点击左下角菜单里的保存设置。 2.选中需要破碎的物体&#xff0c;按快捷键f3&#xff08;快速搜索插件&#xff09;&#xff0c;搜索cell fracture。 3.调整自己需要的参数配置…

广东省网络安全竞赛部分web,misc wp

我的队伍只做了5题&#xff0c;还是太菜了&#xff0c;本来不想发的&#xff0c;但是写都写了&#xff0c;还是水一篇博客吧 这里是我们队的wp misc1 给了一个压缩包&#xff0c;解压需要密码&#xff0c;用纯数字密码没跑出来&#xff0c;感觉可能不是要强跑&#xff0c;看…

今天开发了一款软件,我竟然只用敲了一个字母(文末揭晓)

软件课题&#xff1a;Python实现打印100内数学试题软件及开发过程 一、需求管理&#xff1a; 1.实现语言&#xff1a;Python 2.打印纸张&#xff1a;A4 3.铺满整张纸 4.打包成exe 先看效果&#xff1a; 1. 2.电脑打印预览 3.打印到A4纸效果&#xff08;晚上拍的&#x…

Java后端的接口参数两个interger 类型情况解决

get请求 &#xff0c;对应请求头 RequestParm, post请求&#xff0c; 对应请求体 &#xff0c;RequestBody 两个Integer参数情况 GetMapping("/insertStaffClass")public R<Void> insertStaffClass(RequestParam Integer staffId,RequestParam List<Integ…

Node.js全栈:从一个简单的例子开始

第一章&#xff1a;从一个简单的例子开始第二章&#xff1a;看官方文档的艺术第三章&#xff1a;浏览器显示一个网页 首先&#xff0c;在VSCode编辑器中打开一个没有任何文件的空目录&#xff0c;然后创建一个package.json文件。 为了方便大家复制&#xff0c;我把文件内容放到…

Pytorch的torch.nn.functional.cross_entropy的ignore_index细解

作用 ignore_index用于忽略ground-truth中某些不需要参与计算的类。假设有两类{0:背景&#xff0c;1&#xff1a;前景}&#xff0c;若想在计算交叉熵时忽略背景(0)类&#xff0c;则可令ignore_index0&#xff08;同理忽略前景计算可设ignore_index1&#xff09;。 代码示例 i…

使用Flask的send_file方法实现文件下载功能

文章目录 什么是send_file方法&#xff1f;如何在Flask应用中使用send_file方法&#xff1f;示例拓展处理文件不存在的情况设置下载文件的名称实现文件下载的权限控制 结论 在Web开发中&#xff0c;经常需要实现文件下载功能&#xff0c;而Python的Flask框架提供了方便的send_f…

华为手机恢复出厂设置后怎么还原数据?该如何预防数据丢失?

华为手机恢复出厂设置是将手机恢复到出厂时的初始状态&#xff0c;同时会删除所有用户数据和个人设置。如果不做任何预防措施&#xff0c;在恢复出厂设置后&#xff0c;您将丢失手机上的所有数据。那华为手机恢复出厂设置后怎么还原数据呢&#xff1f;以下是关于如何在华为手机…

新的语言学习系统: 记忆镶嵌

摘要 记忆镶嵌是由多个关联记忆网络协同工作来完成感兴趣的预测任务。与transformer类似,记忆镶嵌具有组合能力和上下文学习能力。与transformer不同,记忆镶嵌以相对透明的方式实现这些能力。该研究在玩具示例上展示了这些能力,并且还表明记忆镶嵌在中等规模语言建模任务上的表…