一、概览
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.cs
public 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.cs
public 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");
// 使用UnityWebRequest
UnityWebRequest 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_EDITOR
return obj;
#else
return FmtColor(color, (string)obj);
#endif
}
else
{
#if !UNITY_EDITOR
return obj;
#else
return string.Format("<color={0}>{1}</color>", color, obj);
#endif
}
}
/// <summary>
/// 格式化颜色日志
/// </summary>
private static object FmtColor(string color, string msg)
{
#if !UNITY_EDITOR
return msg;
#else
int 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");
// 使用UnityWebRequest
UnityWebRequest 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