<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>LianBai</title>
  
  <subtitle>手握日月摘星辰，世间无我这般人。</subtitle>
  <link href="http://yoursite.com/atom.xml" rel="self"/>
  
  <link href="http://yoursite.com/"/>
  <updated>2023-10-18T08:20:19.513Z</updated>
  <id>http://yoursite.com/</id>
  
  <author>
    <name>LianBai</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Unity杂文——本地存储管理</title>
    <link href="http://yoursite.com/2023/10/18/Unity%E6%9D%82%E6%96%87/Unity%E6%9D%82%E6%96%87%E2%80%94%E2%80%94%E6%9C%AC%E5%9C%B0%E5%AD%98%E5%82%A8%E7%AE%A1%E7%90%86/"/>
    <id>http://yoursite.com/2023/10/18/Unity%E6%9D%82%E6%96%87/Unity%E6%9D%82%E6%96%87%E2%80%94%E2%80%94%E6%9C%AC%E5%9C%B0%E5%AD%98%E5%82%A8%E7%AE%A1%E7%90%86/</id>
    <published>2023-10-18T07:57:58.000Z</published>
    <updated>2023-10-18T08:20:19.513Z</updated>
    
    <content type="html"><![CDATA[<h1 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h1><p>在Unity开发中，我们经常需要将一些数据存储在本地，以便在游戏或应用程序的不同运行周期中使用。虽然Unity提供了PlayerPrefs来满足这个需求，但是PlayerPrefs有一些限制，例如数据类型的限制，以及存储容量的限制。因此，我开发了一种新的本地存储结构，它使用Json文件来存储数据，既可以存储全局信息，也可以存储每个账户的个人信息。此外，这种结构还支持对Json文件进行加密，以保护用户的数据安全。  </p><h1 id="特性"><a href="#特性" class="headerlink" title="特性"></a>特性</h1><h2 id="全局和个人信息存储"><a href="#全局和个人信息存储" class="headerlink" title="全局和个人信息存储"></a>全局和个人信息存储</h2><p>这种结构允许存储全局信息和每个账户的个人信息。这对于需要在设备上保存用户特定数据的游戏或应用程序非常有用。  </p><h2 id="Json文件存储"><a href="#Json文件存储" class="headerlink" title="Json文件存储"></a>Json文件存储</h2><p>所有的数据都存储在一个Json文件中，这使得数据的读取和写入变得非常方便。同时，由于Json是一种轻量级的数据交换格式，所以这种存储方式也非常高效。  </p><h2 id="文件加密"><a href="#文件加密" class="headerlink" title="文件加密"></a>文件加密</h2><p>为了保护用户的数据安全，这种结构还支持对Json文件进行加密。这样，即使有人能够访问到存储文件，也无法读取其中的内容。  </p><h1 id="代码如下"><a href="#代码如下" class="headerlink" title="代码如下"></a>代码如下</h1><p>注: 在使用这种存储结构时，你需要注意的是，你需要将Json的序列化和反序列化替换成你自己的接口。此外，这种结构使用了RijndaelManaged进行文件加密，你可能需要根据你的实际需求来调整加密算法。  </p><pre><code>public static class DataStorage&#123;    private const string kStorageFilename = &quot;storage.json&quot;;    private const string kVersion = &quot;version&quot;;    private static Hashtable s_Root = new Hashtable();    private static Hashtable s_Current;    private static bool s_Refresh = false;    private static bool s_IsInit = false;#if USE_ENCRYPT    private static RijndaelManaged m_Managed;    private static ICryptoTransform m_Encryptor;    private static ICryptoTransform m_Decryptor;#endif    #region 加载、保存    /// &lt;summary&gt;    /// 初始化数据    /// &lt;/summary&gt;    public static void Init()    &#123;        var path = GetPath();#if USE_ENCRYPT        m_Managed = new RijndaelManaged        &#123;            Key = Encoding.UTF8.GetBytes(&quot;sd@#^%&amp;*^&amp;?*68(7hK%&amp;fd&quot;),            IV = Encoding.UTF8.GetBytes(&quot;^%&amp;*^&amp;?*682K$d&quot;),            Mode = CipherMode.CBC,            Padding = PaddingMode.Zeros        &#125;;        m_Encryptor = m_Managed.CreateEncryptor();        m_Decryptor = m_Managed.CreateDecryptor();#endif        if (File.Exists(path))        &#123;            try            &#123;#if USE_ENCRYPT            var bytes = File.ReadAllBytes(path);            var data = m_Decryptor.TransformFinalBlock(bytes, 0, bytes.Length);#else                var data = File.ReadAllBytes(path);#endif                s_Root = JsonUtils.ToHashtable(data);            &#125;            catch (Exception e)            &#123;                File.Delete(path);                AddNewData();            &#125;        &#125;        else        &#123;            AddNewData();        &#125;        s_Current = s_Root;        s_IsInit = true;    &#125;    /// &lt;summary&gt;    /// 增加新的数据    /// &lt;/summary&gt;    public static void AddNewData()    &#123;        s_Root[kVersion] = &quot;1.0.0&quot;;    &#125;    /// &lt;summary&gt;    /// 更新数据    /// &lt;/summary&gt;    public static void Update()    &#123;        if (!s_IsInit) return;        if (s_Refresh)        &#123;            s_Refresh = false;            Save();        &#125;    &#125;    /// &lt;summary&gt;    /// 卸载数据    /// &lt;/summary&gt;    public static void UnInit()    &#123;        s_IsInit = false;        if (s_Refresh)        &#123;            s_Refresh = false;            Save();        &#125;    &#125;    /// &lt;summary&gt;    /// 保存数据    /// &lt;/summary&gt;    private static void Save()    &#123;        if (!s_IsInit) return;                var path = GetPath();        PathUtils.MakeDirectory(path);#if USE_ENCRYPT        var bytes = JsonUtils.ToBytes(s_Root);        var data = m_Encryptor.TransformFinalBlock(bytes, 0, bytes.Length);#else        var data = JsonUtils.ToBytes(s_Root);#endif        File.WriteAllBytes(path, data);    &#125;    /// &lt;summary&gt;    /// 获取文件地址(存储文件存放的地址)    /// &lt;/summary&gt;    /// &lt;returns&gt;&lt;/returns&gt;    private static string GetPath()    &#123;        return PathUtils.GetExternalPath(kStorageFilename);    &#125;    #endregion    #region 增删改查        /// &lt;summary&gt;    /// 设置数据（如果数据是默认值就会被移除掉）    /// &lt;/summary&gt;    /// &lt;param name=&quot;table&quot;&gt;&lt;/param&gt;    /// &lt;param name=&quot;primaryKey&quot;&gt;&lt;/param&gt;    /// &lt;param name=&quot;target&quot;&gt;&lt;/param&gt;    /// &lt;typeparam name=&quot;T&quot;&gt;&lt;/typeparam&gt;    private static void Set&lt;T&gt;(Hashtable table, string primaryKey, T target) where T : IEquatable&lt;T&gt;    &#123;        if (null == target || target.Equals(default))        &#123;            table.Remove(primaryKey);        &#125;        else if (table.ContainsKey(primaryKey))        &#123;            table[primaryKey] = target;        &#125;        else        &#123;            table.Add(primaryKey, target);        &#125;        s_Refresh = true;    &#125;    /// &lt;summary&gt;    /// 强制设置数据    /// &lt;/summary&gt;    /// &lt;param name=&quot;table&quot;&gt;&lt;/param&gt;    /// &lt;param name=&quot;primaryKey&quot;&gt;&lt;/param&gt;    /// &lt;param name=&quot;target&quot;&gt;&lt;/param&gt;    /// &lt;typeparam name=&quot;T&quot;&gt;&lt;/typeparam&gt;    private static void SetForce&lt;T&gt;(Hashtable table, string primaryKey, T target) where T : IEquatable&lt;T&gt;    &#123;        if (table.ContainsKey(primaryKey))        &#123;            table[primaryKey] = target;        &#125;        else        &#123;            table.Add(primaryKey, target);        &#125;        s_Refresh = true;    &#125;        /// &lt;summary&gt;    /// 读取数据    /// &lt;/summary&gt;    /// &lt;param name=&quot;table&quot;&gt;&lt;/param&gt;    /// &lt;param name=&quot;primaryKey&quot;&gt;&lt;/param&gt;    /// &lt;typeparam name=&quot;T&quot;&gt;&lt;/typeparam&gt;    /// &lt;returns&gt;&lt;/returns&gt;    private static T Get&lt;T&gt;(Hashtable table, string primaryKey)    &#123;        if (table.ContainsKey(primaryKey))        &#123;            return (T)Convert.ChangeType(table[primaryKey], typeof(T));        &#125;        if(table == s_Root &amp;&amp; PlayerPrefs.HasKey(primaryKey))        &#123;            switch (Type.GetTypeCode(typeof(T)))            &#123;                case TypeCode.Int32:                    return (T)Convert.ChangeType(PlayerPrefs.GetInt(primaryKey), typeof(T));                case TypeCode.Single:                    return (T)Convert.ChangeType(PlayerPrefs.GetFloat(primaryKey), typeof(T));                case TypeCode.String:                    return (T)Convert.ChangeType(PlayerPrefs.GetString(primaryKey), typeof(T));                case TypeCode.Boolean:                    return (T)Convert.ChangeType(PlayerPrefs.GetInt(primaryKey) == 1, typeof(T));                default:                    Logging.Error($&quot;SetRoot &#123;primaryKey&#125; failed, type &#123;typeof(T)&#125; not support.&quot;);                    break;            &#125;        &#125;        return default;    &#125;    /// &lt;summary&gt;    /// 读取数据    /// &lt;/summary&gt;    /// &lt;param name=&quot;primaryKey&quot;&gt;&lt;/param&gt;    /// &lt;typeparam name=&quot;T&quot;&gt;&lt;/typeparam&gt;    /// &lt;returns&gt;&lt;/returns&gt;    public static T Get&lt;T&gt;(string primaryKey)    &#123;        Logging.Assert(s_Current != s_Root, $&quot;get &#123;primaryKey&#125; must after call Enter(..)&quot;);        return Get&lt;T&gt;(s_Current, primaryKey);    &#125;    /// &lt;summary&gt;    /// 设置根目录数据    /// &lt;/summary&gt;    /// &lt;param name=&quot;primaryKey&quot;&gt;&lt;/param&gt;    /// &lt;param name=&quot;target&quot;&gt;&lt;/param&gt;    /// &lt;param name=&quot;isForce&quot;&gt;&lt;/param&gt;    /// &lt;typeparam name=&quot;T&quot;&gt;&lt;/typeparam&gt;    public static void SetRoot&lt;T&gt;(string primaryKey, T target, bool isForce = false) where T : IEquatable&lt;T&gt;    &#123;        if (s_IsInit)        &#123;            if (isForce)            &#123;                SetForce(s_Root, primaryKey, target);            &#125;            else            &#123;                Set(s_Root, primaryKey, target);            &#125;        &#125;        else        &#123;            // 根据T的类型使用PlayerPrefs进行保存            switch (Type.GetTypeCode(typeof(T)))            &#123;                case TypeCode.Int32:                    PlayerPrefs.SetInt(primaryKey, Convert.ToInt32(target));                    break;                case TypeCode.Single:                    PlayerPrefs.SetFloat(primaryKey, Convert.ToSingle(target));                    break;                case TypeCode.String:                    PlayerPrefs.SetString(primaryKey, Convert.ToString(target));                    break;                case TypeCode.Boolean:                    PlayerPrefs.SetInt(primaryKey, Convert.ToBoolean(target) ? 1 : 0);                    break;                default:                    Logging.Error($&quot;SetRoot &#123;primaryKey&#125; failed, type &#123;typeof(T)&#125; not support.&quot;);                    break;            &#125;        &#125;    &#125;        /// &lt;summary&gt;    /// 设置当前用户数据数据    /// &lt;/summary&gt;    /// &lt;param name=&quot;primaryKey&quot;&gt;&lt;/param&gt;    /// &lt;param name=&quot;target&quot;&gt;&lt;/param&gt;    /// &lt;param name=&quot;isForce&quot;&gt;&lt;/param&gt;    /// &lt;typeparam name=&quot;T&quot;&gt;&lt;/typeparam&gt;    public static void Set&lt;T&gt;(string primaryKey, T target, bool isForce = false) where T : IEquatable&lt;T&gt;    &#123;        Logging.Assert(s_Current != s_Root, $&quot;set &#123;primaryKey&#125; must after call Enter(..)&quot;);        if (isForce)        &#123;            SetForce(s_Current, primaryKey, target);        &#125;        else        &#123;            Set(s_Current, primaryKey, target);        &#125;    &#125;    /// &lt;summary&gt;    /// 设置根目录数据(含两个关键字的查找)    /// &lt;/summary&gt;    /// &lt;param name=&quot;primaryKey&quot;&gt;&lt;/param&gt;    /// &lt;param name=&quot;secondaryKey&quot;&gt;&lt;/param&gt;    /// &lt;param name=&quot;target&quot;&gt;&lt;/param&gt;    /// &lt;param name=&quot;isForce&quot;&gt;&lt;/param&gt;    /// &lt;typeparam name=&quot;T&quot;&gt;&lt;/typeparam&gt;    public static void SetRoot&lt;T&gt;(string primaryKey, string secondaryKey, T target, bool isForce = false)        where T : IEquatable&lt;T&gt;    &#123;        Hashtable hashtable;        if (s_Root.ContainsKey(primaryKey))        &#123;            hashtable = (Hashtable)s_Root[primaryKey];        &#125;        else        &#123;            hashtable = new Hashtable();            s_Root.Add(                primaryKey,                hashtable);        &#125;        if (isForce)        &#123;            SetForce(hashtable, secondaryKey, target);        &#125;        else        &#123;            Set(hashtable, secondaryKey, target);        &#125;    &#125;    /// &lt;summary&gt;    /// 获取根目录数据    /// &lt;/summary&gt;    /// &lt;param name=&quot;primaryKey&quot;&gt;&lt;/param&gt;    /// &lt;typeparam name=&quot;T&quot;&gt;&lt;/typeparam&gt;    /// &lt;returns&gt;&lt;/returns&gt;    public static T GetRoot&lt;T&gt;(string primaryKey)    &#123;        return Get&lt;T&gt;(s_Root, primaryKey);    &#125;    /// &lt;summary&gt;    /// 获取根目录数据    /// &lt;/summary&gt;    /// &lt;param name=&quot;primaryKey&quot;&gt;&lt;/param&gt;    /// &lt;param name=&quot;secondaryKey&quot;&gt;&lt;/param&gt;    /// &lt;typeparam name=&quot;T&quot;&gt;&lt;/typeparam&gt;    /// &lt;returns&gt;&lt;/returns&gt;    public static T GetRoot&lt;T&gt;(string primaryKey, string secondaryKey)    &#123;        return s_Root.ContainsKey(primaryKey) ? Get&lt;T&gt;((Hashtable)s_Root[primaryKey], secondaryKey) : default;    &#125;    /// &lt;summary&gt;    /// 判断是否包含数据    /// &lt;/summary&gt;    /// &lt;param name=&quot;primaryKey&quot;&gt;&lt;/param&gt;    /// &lt;param name=&quot;isRoot&quot;&gt;&lt;/param&gt;    /// &lt;returns&gt;&lt;/returns&gt;    public static bool ContainsKey(string primaryKey, bool isRoot = false)    &#123;        return isRoot            ? s_Root.ContainsKey(primaryKey) || PlayerPrefs.HasKey(primaryKey)            : s_Current.ContainsKey(primaryKey);    &#125;        /// &lt;summary&gt;    /// 进入当前用户数据(用于多角色存储不同用户数据)    /// &lt;/summary&gt;    /// &lt;param name=&quot;primaryKey&quot;&gt;&lt;/param&gt;    public static void Enter(string primaryKey)    &#123;        Debug.Log(&quot;primaryKey = &quot;+ primaryKey);        if (s_Current.ContainsKey(primaryKey))        &#123;            s_Current = (Hashtable)s_Current[primaryKey];        &#125;        else        &#123;            var hashtable = new Hashtable();            s_Current.Add(                primaryKey,                hashtable);            s_Current = hashtable;        &#125;        s_Refresh = true;    &#125;    #endregion&#125;</code></pre><h1 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h1><p>这种新的本地存储结构提供了一种更灵活、更安全的方式来存储和管理本地数据。如果你在使用Unity进行开发，并且需要一种更好的本地存储方案，那么你可以尝试使用这种结构。  </p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;简介&quot;&gt;&lt;a href=&quot;#简介&quot; class=&quot;headerlink&quot; title=&quot;简介&quot;&gt;&lt;/a&gt;简介&lt;/h1&gt;&lt;p&gt;在Unity开发中，我们经常需要将一些数据存储在本地，以便在游戏或应用程序的不同运行周期中使用。虽然Unity提供了PlayerPrefs来</summary>
      
    
    
    
    <category term="Unity杂文" scheme="http://yoursite.com/categories/Unity%E6%9D%82%E6%96%87/"/>
    
    
    <category term="Unity" scheme="http://yoursite.com/tags/Unity/"/>
    
  </entry>
  
  <entry>
    <title>Unity杂文——循环的字节序列Stream</title>
    <link href="http://yoursite.com/2023/09/27/Unity%E6%9D%82%E6%96%87/Unity%E6%9D%82%E6%96%87%E2%80%94%E2%80%94%E5%BE%AA%E7%8E%AF%E7%9A%84%E5%AD%97%E8%8A%82%E5%BA%8F%E5%88%97Stream/"/>
    <id>http://yoursite.com/2023/09/27/Unity%E6%9D%82%E6%96%87/Unity%E6%9D%82%E6%96%87%E2%80%94%E2%80%94%E5%BE%AA%E7%8E%AF%E7%9A%84%E5%AD%97%E8%8A%82%E5%BA%8F%E5%88%97Stream/</id>
    <published>2023-09-27T12:41:59.000Z</published>
    <updated>2023-09-28T03:13:00.956Z</updated>
    
    <content type="html"><![CDATA[<p><a href="https://lianbai.icu/2023/09/27/Unity%E6%9D%82%E6%96%87/Unity%E6%9D%82%E6%96%87%E2%80%94%E2%80%94%E5%BE%AA%E7%8E%AF%E7%9A%84%E5%AD%97%E8%8A%82%E5%BA%8F%E5%88%97Stream/">原文地址</a>  </p><h1 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h1><p>这篇博客主要介绍了一个字节序列类CircularBuffer，它是用于频繁创建Stream数据的循环利用。CircularBuffer类继承自Stream类，并实现了其各种方法，包括读写、查找、设置长度等。</p><p>在CircularBuffer类中，主要定义了一些关键的属性，如字节数组、偏移值、开始坐标、结束坐标、长度、当前位置、容量、限制读取长度和标记等。其中，字节数组用于存储数据，偏移值用于定位数据，开始坐标和结束坐标用于定义数据的起始和结束位置，长度表示数据的长度，当前位置表示当前读写的位置，容量表示最大可以存储的数据量，限制读取长度用于限制读取的数据长度，标记用于标记当前对象。</p><p>在CircularBuffer类中，还定义了一些方法，如初始化、设置限制读取长度、读取字节、查找字节、设置长度、写入字节、收缩、扩展、开始接收、结束接收、开始发送、结束发送、重置等。这些方法主要用于操作和管理数据。</p><p>CircularBuffer类的主要优点是可以有效地管理和操作数据，特别是在需要频繁创建Stream数据的情况下，可以有效地提高性能和效率。同时，CircularBuffer类的设计也非常灵活和通用，可以适应各种不同的应用场景。</p><p>总的来说，CircularBuffer类是一个非常实用和高效的字节序列类，值得大家在实际的开发中使用和学习。</p><h1 id="代码如下"><a href="#代码如下" class="headerlink" title="代码如下"></a>代码如下</h1><pre><code>public class CircularBuffer : Stream&#123;    /// &lt;summary&gt;    /// 字节数组    /// &lt;/summary&gt;    private byte[] m_Buffer;    /// &lt;summary&gt;    /// 偏移值    /// &lt;/summary&gt;    private int m_Offset; // []    /// &lt;summary&gt;    /// 开始坐标    /// &lt;/summary&gt;    private int m_Begin;  // [0, m_Capacity)    /// &lt;summary&gt;    /// 结束坐标    /// &lt;/summary&gt;    private int m_End;    // [0, m_Capacity)    /// &lt;summary&gt;    /// 长度    /// &lt;/summary&gt;    private int m_Length;    /// &lt;summary&gt;    /// 当前位置    /// &lt;/summary&gt;    private int m_Position;    /// &lt;summary&gt;    /// 容量    /// &lt;/summary&gt;    private int m_Capacity;    /// &lt;summary&gt;    /// 限制读取长度    /// &lt;/summary&gt;    private int m_LimitReadLength;    /// &lt;summary&gt;    /// 标记    /// &lt;/summary&gt;    private string m_Tag;    /// &lt;summary&gt;    /// 缓存    /// &lt;/summary&gt;    private byte[] m_Cached = new byte[4];    /// &lt;summary&gt;    /// 是否刻度    /// &lt;/summary&gt;    public override bool CanRead =&gt; true;    /// &lt;summary&gt;    /// 是否可定位    /// &lt;/summary&gt;    public override bool CanSeek =&gt; true;    /// &lt;summary&gt;    /// 是否可写入    /// &lt;/summary&gt;    public override bool CanWrite =&gt; true;    /// &lt;summary&gt;    /// 长度    /// &lt;/summary&gt;    public override long Length =&gt; m_Length;    /// &lt;summary&gt;    /// 当前位置坐标    /// &lt;/summary&gt;    public override long Position    &#123;        get =&gt; m_Position;        set        &#123;            if (m_Position == value) return;            Seek(value, SeekOrigin.Begin);        &#125;    &#125;    /// &lt;summary&gt;    /// 容器长度    /// &lt;/summary&gt;    public int Capacity =&gt; m_Capacity;    /// &lt;summary&gt;    /// 标记    /// &lt;/summary&gt;    public string Tag =&gt; m_Tag;        /// &lt;summary&gt;    /// 索引读取    /// &lt;/summary&gt;    /// &lt;param name=&quot;index&quot;&gt;&lt;/param&gt;    /// &lt;exception cref=&quot;ArgumentOutOfRangeException&quot;&gt;&lt;/exception&gt;    public byte this[int index]    &#123;        get        &#123;            if (index &lt; 0 || index &gt;= m_Length)            &#123;                throw new ArgumentOutOfRangeException($&quot;index &#123;index&#125; length &#123;m_Length&#125;&quot;);            &#125;            var begin = m_Begin;            var end = m_End;            if (end &gt; begin)            &#123;                //    0    m_Begin  m_End   m_Capacity                //    |_______|_______|_______|                //                |                //            m_Position                return m_Buffer[m_Offset + begin + index];            &#125;            else            &#123;                //    0     m_End  m_Begin   m_Capacity                //    |_______|_______|_______|                //        |               |                //       (1)  m_Position (2)                var blockLen = m_Capacity - begin;                return blockLen &gt; index ?                    //(2)                    m_Buffer[m_Offset + begin + index] :                    //(1)                    m_Buffer[m_Offset + index - blockLen];            &#125;        &#125;        set        &#123;            if (index &lt; 0 || index &gt;= m_Length)            &#123;                throw new ArgumentOutOfRangeException($&quot;index &#123;index&#125; length &#123;m_Length&#125;&quot;);            &#125;            var begin = m_Begin;            var end = m_End;            if (end &gt; begin)            &#123;                //    0    m_Begin  m_End   m_Capacity                //    |_______|_______|_______|                //                |                //            m_Position                m_Buffer[m_Offset + begin + index] = value;            &#125;            else            &#123;                //    0     m_End  m_Begin   m_Capacity                //    |_______|_______|_______|                //        |               |                //       (1)  m_Position (2)                var blockLen = m_Capacity - begin;                if (blockLen &gt; index)                &#123;                    //(2)                    m_Buffer[m_Offset + begin + index] = value;                &#125;                else                &#123;                    //(1)                    m_Buffer[m_Offset + index - blockLen] = value;                &#125;            &#125;        &#125;    &#125;    /// &lt;summary&gt;    /// 初始化    /// &lt;/summary&gt;    /// &lt;param name=&quot;tag&quot;&gt;&lt;/param&gt;    /// &lt;param name=&quot;capcity&quot;&gt;&lt;/param&gt;    public CircularBuffer(string tag, int capcity)    &#123;        m_Capacity = capcity;        m_Buffer = new byte[m_Capacity];        m_Offset = 0;        m_Begin = 0;        m_End = 0;        m_Length = 0;        m_Position = 0;        m_Tag = tag;    &#125;    /// &lt;summary&gt;    /// 设置限制读取长度    /// &lt;/summary&gt;    /// &lt;param name=&quot;length&quot;&gt;&lt;/param&gt;    public void SetLimitReadLength(int length)    &#123;        Trace($&quot;&#123;length&#125;&quot;);        m_LimitReadLength = length;    &#125;    #region Override    /// &lt;summary&gt;    /// 继承的类必须实现此方法，以便在流中写入字节。    /// &lt;/summary&gt;    /// &lt;exception cref=&quot;NotImplementedException&quot;&gt;&lt;/exception&gt;    public override void Flush()    &#123;        throw new NotImplementedException();    &#125;    /// &lt;summary&gt;    /// 打印日志    /// &lt;/summary&gt;    /// &lt;param name=&quot;msg&quot;&gt;&lt;/param&gt;    /// &lt;param name=&quot;memberName&quot;&gt;&lt;/param&gt;    /// &lt;param name=&quot;lineNum&quot;&gt;&lt;/param&gt;    [Conditional(&quot;DEBUG_STREAM_TRACE&quot;)]    private static void Trace(string msg, [CallerMemberName] string memberName = &quot;&quot;, [CallerLineNumber] int lineNum = 0)    &#123;        //Logging.Log($&quot;&#123;m_Tag&#125; &#123;memberName&#125;:&#123;lineNum&#125;:&#123;Thread.CurrentThread.ManagedThreadId&#125; &#123;m_Begin&#125; &#123;m_End&#125; &#123;m_Position&#125; &#123;m_LimitReadLength&#125; &#123;m_Length&#125;/&#123;m_Capacity&#125; &#123;msg&#125;&quot;);    &#125;    /// &lt;summary&gt;    /// 读取字节未见    /// &lt;/summary&gt;    /// &lt;param name=&quot;buffer&quot;&gt;&lt;/param&gt;    /// &lt;param name=&quot;offset&quot;&gt;&lt;/param&gt;    /// &lt;param name=&quot;count&quot;&gt;&lt;/param&gt;    /// &lt;returns&gt;&lt;/returns&gt;    /// &lt;exception cref=&quot;Exception&quot;&gt;&lt;/exception&gt;    public override int Read(byte[] buffer, int offset, int count)    &#123;        Trace($&quot;&#123;offset&#125; &#123;count&#125;&quot;);        var position = m_Position;        var dataLen = (int)Math.Min(count, (0 == m_LimitReadLength ? m_Length : m_LimitReadLength) - position);        if (0 == dataLen) return 0;        try        &#123;            var begin = m_Begin;            var end = m_End;            if (end &gt; begin)            &#123;                //    0    m_Begin  m_End   m_Capacity                //    |_______|_______|_______|                //                |                //            m_Position                Buffer.BlockCopy(m_Buffer, m_Offset + begin + position, buffer, offset, dataLen);            &#125;            else            &#123;                //    0     m_End  m_Begin   m_Capacity                //    |_______|_______|_______|                //        |               |                //       (1)  m_Position (2)                var blockLen = m_Capacity - begin;                if (blockLen &gt; position)                &#123;                    //(2)                    var copyLen = blockLen - position;                    var remain = dataLen - copyLen;                    if (remain &gt; 0)                    &#123;                        Buffer.BlockCopy(m_Buffer, m_Offset + begin + position, buffer, offset, copyLen);                        Buffer.BlockCopy(m_Buffer, m_Offset, buffer, offset + copyLen, remain);                    &#125;                    else                    &#123;                        Buffer.BlockCopy(m_Buffer, m_Offset + begin + position, buffer, offset, dataLen);                    &#125;                &#125;                else                &#123;                    //(1)                    Buffer.BlockCopy(m_Buffer, m_Offset + position - blockLen, buffer, offset, dataLen);                &#125;            &#125;        &#125;        catch (Exception e)        &#123;            UnityEngine.Debug.LogError($&quot;read &#123;m_Position&#125; &#123;m_LimitReadLength&#125; &#123;m_Length&#125; &#123;m_Begin&#125; &#123;m_End&#125; &#123;m_Offset&#125; &#123;m_Capacity&#125;&quot;);            throw e;        &#125;        m_Position += dataLen;        return dataLen;    &#125;    /// &lt;summary&gt;    /// 查找字节    /// &lt;/summary&gt;    /// &lt;param name=&quot;offset&quot;&gt;&lt;/param&gt;    /// &lt;param name=&quot;origin&quot;&gt;&lt;/param&gt;    /// &lt;returns&gt;&lt;/returns&gt;    /// &lt;exception cref=&quot;ArgumentOutOfRangeException&quot;&gt;&lt;/exception&gt;    public override long Seek(long offset, SeekOrigin origin)    &#123;        Trace($&quot;&#123;offset&#125; &#123;origin&#125;&quot;);        var length = m_Length;        long position = m_Position;        switch (origin)        &#123;            case SeekOrigin.Begin:                position = offset;                break;            case SeekOrigin.End:                position = offset + length;                break;            case SeekOrigin.Current:                position += offset;                break;            default:                throw new ArgumentOutOfRangeException(nameof(origin), origin, null);        &#125;        m_Position = (int)Math.Max(0, Math.Min(position, length));        return m_Position;    &#125;    /// &lt;summary&gt;    /// 设置长度    /// &lt;/summary&gt;    /// &lt;param name=&quot;value&quot;&gt;&lt;/param&gt;    /// &lt;exception cref=&quot;NotImplementedException&quot;&gt;&lt;/exception&gt;    public override void SetLength(long value)    &#123;        throw new NotImplementedException();    &#125;        /// &lt;summary&gt;    /// 写入字节    /// &lt;/summary&gt;    /// &lt;param name=&quot;length&quot;&gt;&lt;/param&gt;    /// &lt;returns&gt;&lt;/returns&gt;    public StringBuilder GetHexData(int length = -1)    &#123;        var sb = new StringBuilder();        var begin = m_Begin;        var end = m_End;        var count = 0;        var maxCount = -1 == length ? m_Length : length;        if (end &gt; begin || 0 == m_Length)        &#123;            for (var i = begin; i &lt; end &amp;&amp; count &lt; maxCount; i++, count++)            &#123;                sb.Append($&quot;&#123;m_Buffer[m_Offset + i]:x2&#125; &quot;);            &#125;        &#125;        else        &#123;            for (var i = begin; i &lt; m_Capacity &amp;&amp; count &lt; maxCount; i++, count++)            &#123;                sb.Append($&quot;&#123;m_Buffer[m_Offset + i]:x2&#125; &quot;);            &#125;            for (var i = 0; i &lt; m_End &amp;&amp; count &lt; maxCount; i++, count++)            &#123;                sb.Append($&quot;&#123;m_Buffer[m_Offset + i]:x2&#125; &quot;);            &#125;        &#125;        return sb;    &#125;    /// &lt;summary&gt;    /// 获取字节    /// &lt;/summary&gt;    /// &lt;param name=&quot;buffer&quot;&gt;&lt;/param&gt;    /// &lt;param name=&quot;offset&quot;&gt;&lt;/param&gt;    /// &lt;param name=&quot;count&quot;&gt;&lt;/param&gt;    /// &lt;returns&gt;&lt;/returns&gt;    private StringBuilder GetHexData(byte[] buffer, int offset, int count)    &#123;        var sb = new StringBuilder();        for (var i = 0; i &lt; count; i++)        &#123;            sb.Append($&quot;&#123;buffer[offset + i]:x2&#125; &quot;);        &#125;        return sb;    &#125;    /// &lt;summary&gt;    /// 写入字节    /// &lt;/summary&gt;    /// &lt;param name=&quot;buffer&quot;&gt;&lt;/param&gt;    /// &lt;param name=&quot;offset&quot;&gt;&lt;/param&gt;    /// &lt;param name=&quot;count&quot;&gt;&lt;/param&gt;    /// &lt;exception cref=&quot;OutOfMemoryException&quot;&gt;&lt;/exception&gt;    /// &lt;exception cref=&quot;Exception&quot;&gt;&lt;/exception&gt;    public override void Write(byte[] buffer, int offset, int count)    &#123;        if (0 == count) return;        Trace($&quot;&#123;GetHexData(buffer, offset, count)&#125;&quot;);        var position = m_Position;        var length = m_Length;        if (m_Capacity - position &lt; count)        &#123;            throw new OutOfMemoryException();        &#125;        try        &#123;            var begin = m_Begin;            var end = m_End;            var replaceLen = length - position;            if (end &gt; begin || 0 == m_Length)            &#123;                //    0    m_Begin  m_End   m_Capacity                //    |_______|_______|_______|                //                |                //            m_Position                var copyLen = m_Capacity - (begin + position);                var remain = count - copyLen;                if (remain &gt; 0)                &#123;                    Buffer.BlockCopy(buffer, offset, m_Buffer, m_Offset + begin + position, copyLen);                    Buffer.BlockCopy(buffer, offset + copyLen, m_Buffer, m_Offset, remain);                &#125;                else                &#123;                    Buffer.BlockCopy(buffer, offset, m_Buffer, m_Offset + begin + position, count);                &#125;            &#125;            else            &#123;                //    0     m_End  m_Begin   m_Capacity                //    |_______|_______|_______|                //        |               |                //       (1)  m_Position (2)                var blockLen = m_Capacity - begin;                if (blockLen &gt; position)                &#123;                    //(2)                    var copyLen = blockLen - position;                    var remain = count - copyLen;                    if (remain &gt; 0)                    &#123;                        Buffer.BlockCopy(buffer, offset, m_Buffer, m_Offset + begin + position, copyLen);                        Buffer.BlockCopy(buffer, offset + copyLen, m_Buffer, m_Offset, remain);                    &#125;                    else                    &#123;                        Buffer.BlockCopy(buffer, offset, m_Buffer, m_Offset + begin + position, count);                    &#125;                &#125;                else                &#123;                    //(1)                    Buffer.BlockCopy(buffer, offset, m_Buffer, m_Offset + position - blockLen, count);                &#125;            &#125;            if (count &gt; replaceLen)            &#123;                Expand(count - replaceLen);            &#125;            else            &#123;                m_Position += count;            &#125;        &#125;        catch (Exception e)        &#123;            UnityEngine.Debug.LogError($&quot;write &#123;m_Position&#125; &#123;m_LimitReadLength&#125; &#123;m_Length&#125; &#123;m_Begin&#125; &#123;m_End&#125; &#123;m_Offset&#125; &#123;m_Capacity&#125; &#123;count&#125;&quot;);            throw e;        &#125;        Trace($&quot;&#123;GetHexData()&#125;&quot;);    &#125;    /// &lt;summary&gt;    /// 释放    /// &lt;/summary&gt;    /// &lt;param name=&quot;disposing&quot;&gt;&lt;/param&gt;    protected override void Dispose(bool disposing)    &#123;        base.Dispose(disposing);        if (m_Capacity &gt; 0)        &#123;            m_Buffer = null;            m_Capacity = 0;        &#125;    &#125;    #endregion    /// &lt;summary&gt;    /// 收缩    /// &lt;/summary&gt;    /// &lt;param name=&quot;length&quot;&gt;&lt;/param&gt;    public void Shrink(int length)    &#123;        Trace($&quot;start &#123;length&#125;&quot;);        m_Begin += length;        if (m_Begin &gt;= m_Capacity)        &#123;            m_Begin -= m_Capacity;        &#125;        if (m_Position &lt;= length)        &#123;            m_Position = 0;        &#125;        else        &#123;            m_Position -= length;        &#125;        m_Length -= length;        Trace($&quot;end &#123;length&#125;&quot;);    &#125;    /// &lt;summary&gt;    /// 扩展    /// &lt;/summary&gt;    /// &lt;param name=&quot;length&quot;&gt;&lt;/param&gt;    public void Expand(int length)    &#123;        Trace($&quot;start &#123;length&#125;&quot;);        m_End += length;        if (m_End &gt;= m_Capacity)        &#123;            m_End -= m_Capacity;        &#125;        m_Length += length;        m_Position = m_Length;        Trace($&quot;end &#123;length&#125;&quot;);    &#125;    /// &lt;summary&gt;    /// 开始接收    /// &lt;/summary&gt;    /// &lt;returns&gt;&lt;/returns&gt;    /// &lt;exception cref=&quot;Exception&quot;&gt;&lt;/exception&gt;    public ArraySegment&lt;byte&gt;[] BeginRcv()    &#123;        Trace(&quot;&quot;);        try        &#123;            var length = m_Length;            if (m_Capacity == length)            &#123;                return Array.Empty&lt;ArraySegment&lt;byte&gt;&gt;();            &#125;            var begin = m_Begin;            var end = m_End;            if (end &gt; begin || 0 == m_Length)            &#123;                //    0    m_Begin  m_End   m_Capacity                //    |_______|_______|_______|                if (0 == begin)                &#123;                    return new ArraySegment&lt;byte&gt;[]                    &#123;                        new ArraySegment&lt;byte&gt;(m_Buffer, m_Offset + end, m_Capacity - end)                    &#125;;                &#125;                return new ArraySegment&lt;byte&gt;[]                &#123;                    new ArraySegment&lt;byte&gt;(m_Buffer, m_Offset + end, m_Capacity - end),                    new ArraySegment&lt;byte&gt;(m_Buffer, m_Offset, begin)                &#125;;            &#125;            else            &#123;                //    0     m_End  m_Begin   m_Capacity                //    |_______|_______|_______|                return new ArraySegment&lt;byte&gt;[]                &#123;                    new ArraySegment&lt;byte&gt;(m_Buffer, m_Offset + end, begin - end)                &#125;;            &#125;        &#125;        catch (Exception ex)        &#123;            UnityEngine.Debug.LogError($&quot;&#123;m_Length&#125; &#123;m_Capacity&#125; &#123;m_Begin&#125; &#123;m_End&#125; &#123;m_Offset&#125;&quot;);            throw ex;        &#125;    &#125;    /// &lt;summary&gt;    /// 结束接收    /// &lt;/summary&gt;    /// &lt;param name=&quot;length&quot;&gt;&lt;/param&gt;    /// &lt;exception cref=&quot;OutOfMemoryException&quot;&gt;&lt;/exception&gt;    public void EndRcv(int length)    &#123;        Trace($&quot;&#123;length&#125;&quot;);        if (m_Capacity &gt;= m_Length + length)        &#123;            Expand(length);        &#125;        else        &#123;            throw new OutOfMemoryException();        &#125;    &#125;    /// &lt;summary&gt;    /// 开始发送    /// &lt;/summary&gt;    /// &lt;returns&gt;&lt;/returns&gt;    public ArraySegment&lt;byte&gt;[] BeginSend()    &#123;        if (0 == m_Length) return Array.Empty&lt;ArraySegment&lt;byte&gt;&gt;();        var begin = m_Begin;        var end = m_End;        if (end &gt; begin)        &#123;            Trace($&quot;offset &#123;m_Offset + begin&#125; len &#123;end - begin&#125;&quot;);            //    0    m_Begin  m_End   m_Capacity            //    |_______|_______|_______|            return new ArraySegment&lt;byte&gt;[]            &#123;                new ArraySegment&lt;byte&gt;(m_Buffer, m_Offset + begin, end - begin)            &#125;;        &#125;        else        &#123;            Trace($&quot;offset &#123;m_Offset + begin&#125; len &#123;m_Capacity - begin&#125; offset &#123;m_Offset&#125; len &#123;end&#125;&quot;);            //    0     m_End  m_Begin   m_Capacity            //    |_______|_______|_______|            return new ArraySegment&lt;byte&gt;[]            &#123;                new ArraySegment&lt;byte&gt;(m_Buffer, m_Offset + begin, m_Capacity - begin),                new ArraySegment&lt;byte&gt;(m_Buffer, m_Offset, end)            &#125;;        &#125;    &#125;    /// &lt;summary&gt;    /// 结束发送    /// &lt;/summary&gt;    /// &lt;param name=&quot;length&quot;&gt;&lt;/param&gt;    /// &lt;exception cref=&quot;IndexOutOfRangeException&quot;&gt;&lt;/exception&gt;    public void EndSend(int length)    &#123;        Trace($&quot;&#123;GetHexData(length)&#125;&quot;);        if (m_Length &gt;= length)        &#123;            Shrink(length);        &#125;        else        &#123;            throw new IndexOutOfRangeException();        &#125;    &#125;    /// &lt;summary&gt;    /// 重置    /// &lt;/summary&gt;    public void Reset()    &#123;        Trace(&quot;&quot;);        m_Length = 0;        m_Begin = 0;        m_End = 0;        m_Position = 0;    &#125;    /// &lt;summary&gt;    /// 写入    /// &lt;/summary&gt;    /// &lt;param name=&quot;v&quot;&gt;&lt;/param&gt;    public void Write(int v)    &#123;        m_Cached[0] = (byte)(v &gt;&gt; 0);        m_Cached[1] = (byte)(v &gt;&gt; 8);        m_Cached[2] = (byte)(v &gt;&gt; 16);        m_Cached[3] = (byte)(v &gt;&gt; 24);        Write(m_Cached, 0, 4);    &#125;    /// &lt;summary&gt;    /// 读取    /// &lt;/summary&gt;    /// &lt;returns&gt;&lt;/returns&gt;    public int ReadInt32()    &#123;        Read(m_Cached, 0, 4);        var result = 0;        result |= (int)m_Cached[0];        result |= (int)(m_Cached[1] &lt;&lt; 8);        result |= (int)(m_Cached[2] &lt;&lt; 16);        result |= (int)(m_Cached[3] &lt;&lt; 24);        return result;    &#125;    /// &lt;summary&gt;    /// 写入    /// &lt;/summary&gt;    /// &lt;param name=&quot;v&quot;&gt;&lt;/param&gt;    public void Write(uint v)    &#123;        m_Cached[0] = (byte)(v &gt;&gt; 0);        m_Cached[1] = (byte)(v &gt;&gt; 8);        m_Cached[2] = (byte)(v &gt;&gt; 16);        m_Cached[3] = (byte)(v &gt;&gt; 24);        Write(m_Cached, 0, 4);    &#125;    /// &lt;summary&gt;    /// 读取    /// &lt;/summary&gt;    /// &lt;returns&gt;&lt;/returns&gt;    public uint ReadUInt32()    &#123;        Read(m_Cached, 0, 4);        uint result = 0;        result |= (uint)m_Cached[0];        result |= (uint)(m_Cached[1] &lt;&lt; 8);        result |= (uint)(m_Cached[2] &lt;&lt; 16);        result |= (uint)(m_Cached[3] &lt;&lt; 24);        return result;    &#125;    /// &lt;summary&gt;    /// 写入    /// &lt;/summary&gt;    /// &lt;param name=&quot;v&quot;&gt;&lt;/param&gt;    public void Write(ushort v)    &#123;        m_Cached[0] = (byte)(v &gt;&gt; 0);        m_Cached[1] = (byte)(v &gt;&gt; 8);        Write(m_Cached, 0, 2);    &#125;    /// &lt;summary&gt;    /// 读取    /// &lt;/summary&gt;    /// &lt;returns&gt;&lt;/returns&gt;    public uint ReadUInt16()    &#123;        Read(m_Cached, 0, 2);        ushort result = 0;        result |= (ushort)m_Cached[0];        result |= (ushort)(m_Cached[1] &lt;&lt; 8);        return result;    &#125;    /// &lt;summary&gt;    /// 写入    /// &lt;/summary&gt;    /// &lt;param name=&quot;v&quot;&gt;&lt;/param&gt;    public void Write(short v)    &#123;        m_Cached[0] = (byte)(v &gt;&gt; 0);        m_Cached[1] = (byte)(v &gt;&gt; 8);        Write(m_Cached, 0, 2);    &#125;    /// &lt;summary&gt;    /// 读取    /// &lt;/summary&gt;    /// &lt;returns&gt;&lt;/returns&gt;    public int ReadInt16()    &#123;        Read(m_Cached, 0, 2);        short result = 0;        result |= (short)m_Cached[0];        result |= (short)(m_Cached[1] &lt;&lt; 8);        return result;    &#125;    /// &lt;summary&gt;    /// 写入    /// &lt;/summary&gt;    /// &lt;param name=&quot;v&quot;&gt;&lt;/param&gt;    public void Write(byte v)    &#123;        m_Cached[0] = v;        Write(m_Cached, 0, 1);    &#125;    /// &lt;summary&gt;    /// 读取    /// &lt;/summary&gt;    /// &lt;returns&gt;&lt;/returns&gt;    public byte ReadUInt8()    &#123;        Read(m_Cached, 0, 1);        return m_Cached[0];    &#125;&#125;</code></pre>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;&lt;a href=&quot;https://lianbai.icu/2023/09/27/Unity%E6%9D%82%E6%96%87/Unity%E6%9D%82%E6%96%87%E2%80%94%E2%80%94%E5%BE%AA%E7%8E%AF%E7%9A%84%E5%A</summary>
      
    
    
    
    <category term="Unity杂文" scheme="http://yoursite.com/categories/Unity%E6%9D%82%E6%96%87/"/>
    
    
    <category term="Unity" scheme="http://yoursite.com/tags/Unity/"/>
    
  </entry>
  
  <entry>
    <title>Unity杂文——python开发的导表工具</title>
    <link href="http://yoursite.com/2023/09/21/Unity%E6%9D%82%E6%96%87/Unity%E6%9D%82%E6%96%87%E2%80%94%E2%80%94python%E5%BC%80%E5%8F%91%E7%9A%84%E5%AF%BC%E8%A1%A8%E5%B7%A5%E5%85%B7/"/>
    <id>http://yoursite.com/2023/09/21/Unity%E6%9D%82%E6%96%87/Unity%E6%9D%82%E6%96%87%E2%80%94%E2%80%94python%E5%BC%80%E5%8F%91%E7%9A%84%E5%AF%BC%E8%A1%A8%E5%B7%A5%E5%85%B7/</id>
    <published>2023-09-21T08:54:36.000Z</published>
    <updated>2023-09-23T08:35:43.051Z</updated>
    
    <content type="html"><![CDATA[<h1 id="Excel2Bytes-v0-1-0"><a href="#Excel2Bytes-v0-1-0" class="headerlink" title="Excel2Bytes(v0.1.0)"></a>Excel2Bytes(v0.1.0)</h1><p><a href="https://github.com/LBGTeam/Excel2Bytes"><strong>Excel2Bytes仓库</strong></a>  </p><p>py写的游戏开发用的导表工具。目前支持C#脚本。</p><h2 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h2><p>该工具的原理是将表格数据转换为二进制数据，并生成对应的bytes文件。同时，它还会生成可供访问的C#脚本，使得在游戏中可以通过C#按照二进制字节的方式读取所需的数据。</p><p>这个工具的优势在于它可以提高游戏的加载速度和性能。通过将表格数据转换为二进制格式，可以减少文件大小和加载时间。同时，使用C#脚本读取二进制数据可以更高效地访问和处理数据。</p><p>该工具目前正在开发中，预计会提供更多功能和优化。敬请期待！  </p><h2 id="工具配置"><a href="#工具配置" class="headerlink" title="工具配置"></a>工具配置</h2><p>首次运行会在当前目录的Config文件夹下生成一个Config.json配置文件，这个文件里面是导表工具的配置。<br>这里的配置<strong>后续开发</strong>将支持QT面板进行修改，不用修改文件。  </p><p>示例  </p><pre><code>&#123;    &quot;IsUpdateAllLNG&quot;: true,    &quot;TablePath&quot;: &quot;F:\\MyPrograme\\Excel2Bytes\\Excel2Bytes\\Save\\Table&quot;,    &quot;BytesPath&quot;: &quot;F:\\MyPrograme\\Excel2Bytes\\Excel2Bytes\\Save\\Bytes&quot;,    &quot;ScriptsPath&quot;: &quot;F:\\MyPrograme\\Excel2Bytes\\Excel2Bytes\\Save\\Scripts&quot;,    &quot;CorePath&quot;: &quot;F:\\MyPrograme\\Excel2Bytes\\Excel2Bytes\\Save\\Core&quot;,    &quot;LNGBytesPath&quot;: &quot;F:\\MyPrograme\\Excel2Bytes\\Excel2Bytes\\Save\\Bytes&quot;,    &quot;ResRefFileListPath&quot;: &quot;F:\\MyPrograme\\Excel2Bytes\\Excel2Bytes\\Save\\reslist.json&quot;,    &quot;LanguageXlsxPath&quot;: &quot;F:\\MyPrograme\\Excel2Bytes\\Excel2Bytes\\Save\\Table\\Languages.xlsx&quot;,    &quot;CNLanguage&quot;: &quot;cn&quot;,    &quot;TableLanguageCSName&quot;: &quot;Languages&quot;,    &quot;BytesExportPath&quot;: &quot;&quot;,    &quot;ScriptsExportPath&quot;: &quot;&quot;,    &quot;CoreExportPath&quot;: &quot;&quot;,    &quot;TableRootNamespace&quot;: &quot;LBRuntime&quot;,    &quot;TableResLoadAssembly&quot;: &quot;LBUnity&quot;,    &quot;ResLoadScripts&quot;: &quot;ResManager.OpenFile(\&quot;&#123;0&#125;\&quot;)&quot;,    &quot;LanguageKey&quot;: [        &quot;tw&quot;,        &quot;en&quot;    ],    &quot;SupportExcelFormats&quot;: [        &quot;.xlsx&quot;,        &quot;.csv&quot;    ],    &quot;TableSheetInfo&quot;: &#123;        &quot;ScriptsName&quot;: &quot;&quot;,        &quot;ImportType&quot;: &quot;NoneType&quot;,        &quot;ExtraNamespace&quot;: &quot;&quot;    &#125;&#125;</code></pre><table><thead><tr><th align="left">字段</th><th align="left">描述</th></tr></thead><tbody><tr><td align="left">IsUpdateAllLNG</td><td align="left">表示是否要更新多语言，如果为False，只有中文语言表会更新，其他的语言不会更新</td></tr><tr><td align="left">TablePath</td><td align="left">表格存放的目录</td></tr><tr><td align="left">BytesPath</td><td align="left">生成的字节文件存放的目录（非项目内路径，后面有项目内的路径配置，会把这里的文件复制到后面配置的项目路径）</td></tr><tr><td align="left">ScriptsPath</td><td align="left">生成的脚本文件存放目录（和BytesPath一样是临时存放目录)</td></tr><tr><td align="left">CorePath</td><td align="left">核心脚本目录，初次导入的时候会生成TableManager等管理脚本存放目录（也是临时存放目录）</td></tr><tr><td align="left">LNGBytesPath</td><td align="left">多语言的字节生成目录，所有的语言生成的字节文件都是一样的名字，会放在与语言页签同样名字的文件夹下，开发者加载的时候处理不同语言访问的字节文件。</td></tr><tr><td align="left">ResRefFileListPath</td><td align="left">收集配置的资源名字的json文件路径（里面存放的都是表里配置的资源的名字）</td></tr><tr><td align="left">LanguageXlsxPath</td><td align="left">多语言的表格路径，这个是自动生成的。<font color="Red">只有处理其他语言显示文字才会修改这个表，这个表的中文页签是会自动更新的</font></td></tr><tr><td align="left">CNLanguage</td><td align="left">多语言表中文页签的页签名</td></tr><tr><td align="left">TableLanguageCSName</td><td align="left">读取语言的脚本名字，这里不需要带前缀”Table”（如上图配置，游戏中访问语言就是：TableLanguages.Find(10)）</td></tr><tr><td align="left">BytesExportPath</td><td align="left">项目里表格字节文件存放的路径，工具会把临时存放的字节文件复制到这个文件里</td></tr><tr><td align="left">ScriptsExportPath</td><td align="left">项目里表格脚本文件存放的路径，同上</td></tr><tr><td align="left">CoreExportPath</td><td align="left">表格核心脚本存放路径，同上</td></tr><tr><td align="left">TableRootNamespace</td><td align="left">表格脚本所在的程序集名字（把导表生成的脚本都放在这个程序集下）</td></tr><tr><td align="left">TableResLoadAssembly</td><td align="left">脚本加载字节文件的时候需要用到的项目资源加载脚本所在的命名空间</td></tr><tr><td align="left">ResLoadScripts</td><td align="left">加载字节文件用到的项目里的方法</td></tr><tr><td align="left">LanguageKey</td><td align="left">多语言中其他语言的页签名字</td></tr><tr><td align="left">SupportExcelFormats</td><td align="left">表格文件支持的后缀</td></tr><tr><td align="left">TableSheetInfo</td><td align="left">页签文件要包含的信息，扩展使用（正常情况下不要修改，增加需要修改代码以作支持）</td></tr></tbody></table><h2 id="表格类型"><a href="#表格类型" class="headerlink" title="表格类型"></a>表格类型</h2><p>首次运行会在当前目录的Config文件夹下生成一个TableConfig.json配置文件，这个文件里面是导表工具的配置。  </p><p>示例  </p><pre><code>&#123;    &quot;Global.xlsx&quot;: &#123;        &quot;Global&quot;: &#123;            &quot;ScriptsName&quot;: &quot;TableGlobal&quot;,            &quot;ImportType&quot;: &quot;CustomTypeField&quot;,            &quot;ExtraNamespace&quot;: &quot;&quot;        &#125;    &#125;,    &quot;Languages.xlsx&quot;: &#123;        &quot;cn&quot;: &#123;            &quot;ScriptsName&quot;: &quot;TableLanguagescn&quot;,            &quot;ImportType&quot;: &quot;LNGType&quot;,            &quot;ExtraNamespace&quot;: &quot;&quot;        &#125;,        &quot;tw&quot;: &#123;            &quot;ScriptsName&quot;: &quot;TableLanguagestw&quot;,            &quot;ImportType&quot;: &quot;LNGType&quot;,            &quot;ExtraNamespace&quot;: &quot;&quot;        &#125;,        &quot;en&quot;: &#123;            &quot;ScriptsName&quot;: &quot;TableLanguagesen&quot;,            &quot;ImportType&quot;: &quot;LNGType&quot;,            &quot;ExtraNamespace&quot;: &quot;&quot;        &#125;    &#125;,    &quot;LanguagesUI.xlsx&quot;: &#123;        &quot;UI&quot;: &#123;            &quot;ScriptsName&quot;: &quot;TableLanguagesUIUI&quot;,            &quot;ImportType&quot;: &quot;NoExportLNGType&quot;,            &quot;ExtraNamespace&quot;: &quot;&quot;        &#125;    &#125;,    &quot;Level.xlsx&quot;: &#123;        &quot;Level&quot;: &#123;            &quot;ScriptsName&quot;: &quot;TableLevel&quot;,            &quot;ImportType&quot;: &quot;FindType&quot;,            &quot;ExtraNamespace&quot;: &quot;&quot;        &#125;,        &quot;Chase&quot;: &#123;            &quot;ScriptsName&quot;: &quot;TableLevelChase&quot;,            &quot;ImportType&quot;: &quot;FindType&quot;,            &quot;ExtraNamespace&quot;: &quot;&quot;        &#125;    &#125;,    &quot;ResName.xlsx&quot;: &#123;        &quot;ResName&quot;: &#123;            &quot;ScriptsName&quot;: &quot;TableResName&quot;,            &quot;ImportType&quot;: &quot;FieldType&quot;,            &quot;ExtraNamespace&quot;: &quot;&quot;        &#125;,        &quot;ArrayResName&quot;: &#123;            &quot;ScriptsName&quot;: &quot;TableResNameArrayResName&quot;,            &quot;ImportType&quot;: &quot;FieldType&quot;,            &quot;ExtraNamespace&quot;: &quot;&quot;        &#125;    &#125;,    &quot;Text.xlsx&quot;: &#123;        &quot;System&quot;: &#123;            &quot;ScriptsName&quot;: &quot;TableTextSystem&quot;,            &quot;ImportType&quot;: &quot;FieldType&quot;,            &quot;ExtraNamespace&quot;: &quot;&quot;        &#125;,        &quot;Network&quot;: &#123;            &quot;ScriptsName&quot;: &quot;TableTextNetwork&quot;,            &quot;ImportType&quot;: &quot;NoneType&quot;,            &quot;ExtraNamespace&quot;: &quot;&quot;        &#125;,        &quot;SystemFind&quot;: &#123;            &quot;ScriptsName&quot;: &quot;TableTextSystemFind&quot;,            &quot;ImportType&quot;: &quot;NoneType&quot;,            &quot;ExtraNamespace&quot;: &quot;&quot;        &#125;,        &quot;SystemArray&quot;: &#123;            &quot;ScriptsName&quot;: &quot;TableTextSystemArray&quot;,            &quot;ImportType&quot;: &quot;FieldType&quot;,            &quot;ExtraNamespace&quot;: &quot;&quot;        &#125;,        &quot;Rich&quot;: &#123;            &quot;ScriptsName&quot;: &quot;TableTextRich&quot;,            &quot;ImportType&quot;: &quot;NoneType&quot;,            &quot;ExtraNamespace&quot;: &quot;&quot;        &#125;    &#125;&#125;</code></pre><table><thead><tr><th align="left">key</th><th align="left">描述</th></tr></thead><tbody><tr><td align="left">ScriptsName</td><td align="left">表示生成的脚本的名字，也是字节文件的名字</td></tr><tr><td align="left">ImportType</td><td align="left">表示导入类型，NoneType代表不导入，其他类型下文会介绍</td></tr><tr><td align="left">ExtraNamespace</td><td align="left">表示生成脚本文件需要额外引用的命名空间</td></tr></tbody></table><h3 id="导入类型"><a href="#导入类型" class="headerlink" title="导入类型"></a>导入类型</h3><p>每个表格得每个页签都需要选择一个导入类型，没选择导入类型得页签代表不进行导入。导入类型决定了生成得表格数据得bytes文件和对应得脚本文件。  </p><p>类型总览：  </p><table><thead><tr><th align="left">导入类型</th><th align="left">描述</th></tr></thead><tbody><tr><td align="left">FieldType</td><td align="left">字段类型，用于配置游戏中动态替换的数据（注：只允许含有c的字段两列，一列是代码访问关键字，一列是值）</td></tr><tr><td align="left">FindType</td><td align="left">查找类型，用于配置游戏中的数据，根据关键字进行查找对应数据。</td></tr><tr><td align="left">LNGType</td><td align="left">语言类型，用于存放所有语言的表，这个表工具会自动生成，不需要手动创建</td></tr><tr><td align="left">NoExportLNGType</td><td align="left">无导入语言类型，用于游戏中固定的文字，非代码访问的，需要玩家自动创建并收集游戏中的文字（注：导表是不会生成脚本和bytes文件，只是为了收集游戏中语言到语言表里）</td></tr><tr><td align="left">CustomTypeField</td><td align="left">针对此类型表专门做的类型。(注：只允许含有c的字段三列，一列是代码访问关键字，一列是值得类型，一列是值)</td></tr></tbody></table><h4 id="FieldType"><a href="#FieldType" class="headerlink" title="FieldType"></a>FieldType</h4><p>字段的导入类型，用于根据字段的关键字来访问对应的值。此类型只允许有两列第一行包含’c’的列（第一行包含’c’代表要导入的数据），一列是代码访问关键字，一列是值。   </p><p>示例  </p><table><thead><tr><th align="left">c</th><th align="left">c</th></tr></thead><tbody><tr><td align="left">ID</td><td align="left">text</td></tr><tr><td align="left">int</td><td align="left">LNGRef</td></tr><tr><td align="left">名称</td><td align="left">内容</td></tr><tr><td align="left">NetworkUnreachable</td><td align="left">连接出错，请检查您的网络！</td></tr><tr><td align="left">GetServerListFailed</td><td align="left">拉取服务器列表失败，请稍后再试！</td></tr><tr><td align="left">TipsFormatInitParamFailed</td><td align="left">初始化参数请求失败，请检查您的网络！{0}</td></tr></tbody></table><p>在这个示例中，我们展示了一个普通表格的结构。每一列都有一个对应的数据类型和字段描述。<br>在第一行中，我们使用了字符c来表示是否要导入该列的数据。只有含有c的列才会被导入到数据文件中。<br>在第二行中，我们列出了每一列的字段名字。这些字段名字用于标识每一列所代表的数据。<br>在第三行中，我们指定了每一列数据的类型。这些类型可以是整数(int)、字符串(string)、长整型(long)等。<br>在第四行中，我们提供了对每一列所代表的字段的描述。这些描述可以帮助读者更好地理解每一列数据的含义。<br>接下来的行中，第一列表示的是我们要搜索的关键字，第二列就是关键字对应的数值</p><h4 id="FindType"><a href="#FindType" class="headerlink" title="FindType"></a>FindType</h4><p>查找的导入类型，用于根据数据的key查找对应的数据，此类型包含’c’的列至少是两列，一列是查找的时候的关键字，后面就是一系列的配置数据了。  </p><p>示例  </p><table><thead><tr><th align="left">c</th><th align="left">c</th><th align="left"></th><th align="left">c</th></tr></thead><tbody><tr><td align="left">ID</td><td align="left">Level</td><td align="left">des</td><td align="left">Exp</td></tr><tr><td align="left">int</td><td align="left">int</td><td align="left">string</td><td align="left">long</td></tr><tr><td align="left">关键key</td><td align="left">等级</td><td align="left">描述</td><td align="left">经验</td></tr><tr><td align="left">1</td><td align="left">1</td><td align="left">练气</td><td align="left">100</td></tr></tbody></table><p>在这个示例中，我们展示了一个普通表格的结构。每一列都有一个对应的数据类型和字段描述。<br>在第一行中，我们使用了字符c来表示是否要导入该列的数据。只有含有c的列才会被导入到数据文件中。<br>在第二行中，我们列出了每一列的字段名字。这些字段名字用于标识每一列所代表的数据。<br>在第三行中，我们指定了每一列数据的类型。这些类型可以是整数(int)、字符串(string)、长整型(long)等。<br>在第四行中，我们提供了对每一列所代表的字段的描述。这些描述可以帮助读者更好地理解每一列数据的含义。<br>接下来的行中，我们可以输入对应的数据。在这个示例中，我们输入了一条数据，其中ID为1，等级为1，描述为”练气”，经验为100。<br>以上是对普通表格的描述和示例。你可以根据需要调整表格的结构和内容。 </p><h4 id="LNGType"><a href="#LNGType" class="headerlink" title="LNGType"></a>LNGType</h4><p>这个是语言表类型，是导表工具自动生成的多语言表内的页签用的类型，这个类型的表在生成多语言的时候每页都会生成一个语言的字节文件，游戏可以在加载字节文件里做处理，只需要加载对应语言即可。  </p><p>示例  </p><table><thead><tr><th align="left">c</th><th align="left">c</th></tr></thead><tbody><tr><td align="left">ID</td><td align="left">text</td></tr><tr><td align="left">uint</td><td align="left">string</td></tr><tr><td align="left">编号</td><td align="left">文本</td></tr><tr><td align="left">1</td><td align="left">多语言1</td></tr><tr><td align="left">2</td><td align="left">多语言2</td></tr></tbody></table><p>这个语言表会自动生成，不需要创建，只需要添加要生成语言的配置即可</p><h4 id="NoExportLNGType"><a href="#NoExportLNGType" class="headerlink" title="NoExportLNGType"></a>NoExportLNGType</h4><p>这个类型的表也是语言类型表，这个表需要玩家自己收集游戏中不是动态变化的语言，类似Unity的prefab上的语言，导表的时候会把这些语言收集到多语言表中，并且不会生成脚本和字节文件，游戏中开发者只需要通过id访问语言表的字节文件即可。  </p><p>示例  </p><table><thead><tr><th align="left">c</th><th align="left">c</th></tr></thead><tbody><tr><td align="left">ID</td><td align="left">text</td></tr><tr><td align="left">uint</td><td align="left">LNGRef</td></tr><tr><td align="left">编号</td><td align="left">文本</td></tr><tr><td align="left">1</td><td align="left">多语言1</td></tr><tr><td align="left">2</td><td align="left">多语言2</td></tr></tbody></table><h4 id="CustomTypeField"><a href="#CustomTypeField" class="headerlink" title="CustomTypeField"></a>CustomTypeField</h4><p>自定义类型的字段导入类型，这个和字段导入类型差不多，但是要求有效列(第一行含’c’)的列必须是三列。  </p><p>示例  </p><table><thead><tr><th align="left">c</th><th align="left">c</th><th align="left">c</th></tr></thead><tbody><tr><td align="left">关键字</td><td align="left">类型</td><td align="left">值</td></tr><tr><td align="left">currencyFormatSplit</td><td align="left">nt[]</td><td align="left">5|7</td></tr><tr><td align="left">currencyNumSplit</td><td align="left">slc|int</td><td align="left">4|7</td></tr><tr><td align="left">currencyFormatSplitMap</td><td align="left">map|int</td><td align="left">string</td></tr><tr><td align="left">currencyFormatSplitDic</td><td align="left">Dictionary&lt;int,LNGRef&gt;</td><td align="left">1:你好|2:我不好</td></tr></tbody></table><p>在这个示例中，我们展示了一个多类型表格的结构。每一行都有一个对应的数据类型和字段描述。<br>在第一行中，我们使用了字符c来表示是否要导入该列的数据。只有含有c的列才会被导入到数据文件中。<br>在第二行中，我们提供了对每一列所代表的字段的描述。这些描述可以帮助读者更好地理解每一列数据的含义。<br>以上是对多类型表格的描述和示例。你可以根据需要调整表格的结构和内容。  </p><h2 id="导入命令"><a href="#导入命令" class="headerlink" title="导入命令"></a>导入命令</h2><p>后续版本会支持界面按钮调用，目前仅支持命令。(注：需要配置python环境)  </p><table><thead><tr><th align="left">命令</th><th align="left">解释</th></tr></thead><tbody><tr><td align="left">python Excel2Bytes.py -a</td><td align="left">表示要导入表格配置里的所有表格数据，并且会生成多语言</td></tr><tr><td align="left">python Excel2Bytes.py -e</td><td align="left">表示只导入NoExportLNGType类型的表，用于游戏中固定文本的多语言生成</td></tr><tr><td align="left">python Excel2Bytes.py -c [“Global.xlsx”,”Level.xlsx”]</td><td align="left">表示只导入我传入的第二个参数里面包含的表，只会生成中文的语言，其他多语言不会生成</td></tr></tbody></table><h2 id="支持的数据格式"><a href="#支持的数据格式" class="headerlink" title="支持的数据格式"></a>支持的数据格式</h2><table><thead><tr><th align="left">类型</th><th align="left">写法</th><th>描述</th></tr></thead><tbody><tr><td align="left">uint8</td><td align="left">1</td><td>基础类型</td></tr><tr><td align="left">byte</td><td align="left">1</td><td>基础类型</td></tr><tr><td align="left">int</td><td align="left">1</td><td>基础类型</td></tr><tr><td align="left">uint</td><td align="left">1</td><td>基础类型</td></tr><tr><td align="left">float</td><td align="left">1</td><td>基础类型</td></tr><tr><td align="left">double</td><td align="left">1</td><td>基础类型</td></tr><tr><td align="left">bool</td><td align="left">false、true或者空</td><td>基础类型</td></tr><tr><td align="left">long</td><td align="left">1</td><td>基础类型</td></tr><tr><td align="left">ulong</td><td align="left">1</td><td>基础类型</td></tr><tr><td align="left">int64</td><td align="left">1</td><td>基础类型</td></tr><tr><td align="left">uint</td><td align="left">64</td><td>基础类型</td></tr><tr><td align="left">short</td><td align="left">1</td><td>基础类型</td></tr><tr><td align="left">ushort</td><td align="left">1</td><td>基础类型</td></tr><tr><td align="left">string</td><td align="left">文本描述</td><td>基础类型</td></tr><tr><td align="left">基础类型[]</td><td align="left">1|1</td><td>3</td></tr><tr><td align="left">slc|基础类型</td><td align="left">1|1</td><td>3</td></tr><tr><td align="left">基础类型[][]</td><td align="left">1:2:3|1:2</td><td>1:2:3:4</td></tr><tr><td align="left">double_slc|基础类型</td><td align="left">1:2:3|1:2</td><td>1:2:3:4</td></tr><tr><td align="left">map|基础类型</td><td align="left">基础类型</td><td>1:2|3:4</td></tr><tr><td align="left">Dictionary&lt;基础类型，基础类型&gt;</td><td align="left">1:2|3:4</td><td>5:6</td></tr><tr><td align="left">LNGRef</td><td align="left">这段话多语言</td><td>字符串类型，会在多语言表生成，用于游戏语言切换功能</td></tr><tr><td align="left">ResName</td><td align="left">Icon.png</td><td>资源类型，这里会收集表里配置的所有资源，可根据自身需求来获取，Save目录会生成reslist.json，记录了所有的资源</td></tr></tbody></table>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;Excel2Bytes-v0-1-0&quot;&gt;&lt;a href=&quot;#Excel2Bytes-v0-1-0&quot; class=&quot;headerlink&quot; title=&quot;Excel2Bytes(v0.1.0)&quot;&gt;&lt;/a&gt;Excel2Bytes(v0.1.0)&lt;/h1&gt;&lt;p&gt;&lt;a h</summary>
      
    
    
    
    <category term="Unity杂文" scheme="http://yoursite.com/categories/Unity%E6%9D%82%E6%96%87/"/>
    
    
    <category term="Unity" scheme="http://yoursite.com/tags/Unity/"/>
    
  </entry>
  
  <entry>
    <title>Python杂文——CShap脚本生成辅助工具</title>
    <link href="http://yoursite.com/2023/09/17/Python%E6%9D%82%E6%96%87/Python%E6%9D%82%E6%96%87%E2%80%94%E2%80%94CShap%E8%84%9A%E6%9C%AC%E7%94%9F%E6%88%90%E8%BE%85%E5%8A%A9%E5%B7%A5%E5%85%B7/"/>
    <id>http://yoursite.com/2023/09/17/Python%E6%9D%82%E6%96%87/Python%E6%9D%82%E6%96%87%E2%80%94%E2%80%94CShap%E8%84%9A%E6%9C%AC%E7%94%9F%E6%88%90%E8%BE%85%E5%8A%A9%E5%B7%A5%E5%85%B7/</id>
    <published>2023-09-17T12:42:54.000Z</published>
    <updated>2023-09-28T03:21:13.457Z</updated>
    
    <content type="html"><![CDATA[<p><a href="https://lianbai.icu/2023/09/17/Python%E6%9D%82%E6%96%87/Python%E6%9D%82%E6%96%87%E2%80%94%E2%80%94CShap%E8%84%9A%E6%9C%AC%E7%94%9F%E6%88%90%E8%BE%85%E5%8A%A9%E5%B7%A5%E5%85%B7/">原文地址</a>  </p><h1 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h1><p>本文介绍了一个用于辅助生成C#脚本的Python工具。该工具可以帮助用户快速生成C#脚本，并提供了一些常用的代码生成方法。  </p><h1 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h1><pre><code>import osfrom PathUtil import ScriptsPathclass CSScriptBuilder(list):    BlackNum = 0    def Append(self, message):        if self.BlackNum &gt; 0:            self.append(&#39;\t&#39; * self.BlackNum + message)        else:            self.append(message)    def AppendEnter(self):        self.Append(&#39;\n&#39;)    def AppendLine(self, message):        if len(self) &gt; 0:            self.append(&#39;\n&#39;)        if self.BlackNum &gt; 0:            self.append(&#39;\t&#39; * self.BlackNum + message)        else:            self.append(message)    def AppendEmptyLine(self):        self.AppendLine(&#39;&#39;)    def AppendUsing(self, namespace):        self.AppendLine(f&#39;using &#123;namespace&#125;;&#39;)    def BeginNamespace(self, namespace):        self.AppendLine(f&#39;namespace &#123;namespace&#125;&#39;)        self.BeginBrace()    def EndNamespace(self):        self.EndBrace()    def BeginClass(self, className, modifier=&quot;public&quot;, superclass=None):        if superclass is not None:            self.AppendLine(f&#39;&#123;modifier&#125; class &#123;className&#125; : &#123;superclass&#125;&#39;)        else:            self.AppendLine(f&#39;&#123;modifier&#125; class &#123;className&#125;&#39;)        self.BeginBrace()    def EndClass(self):        self.EndBrace()    def BeginStruct(self, structName, modifier=&quot;public&quot;):        self.AppendLine(f&#39;&#123;modifier&#125; struct &#123;structName&#125;&#39;)        self.BeginBrace()    def EndStruct(self):        self.EndBrace()    def BeginEnum(self, enumName, modifier=&quot;public&quot;):        self.AppendLine(f&#39;&#123;modifier&#125; enum &#123;enumName&#125;&#39;)        self.BeginBrace()    def EndEnum(self):        self.EndBrace()    def AppendEnumField(self, fieldName, fieldValue):        self.AppendLine(f&#39;&#123;fieldName&#125; = &#123;fieldValue&#125;,&#39;)    def BeginInterface(self, interfaceName, modifier=&quot;public&quot;):        self.AppendLine(f&#39;&#123;modifier&#125; interface &#123;interfaceName&#125;&#39;)        self.BeginBrace()    def EndInterface(self):        self.EndBrace()    def AppendInterfaceMethod(self, methodName, returnType=&#39;void&#39;, parameters=&#39;&#39;):        self.AppendLine(f&#39;&#123;returnType&#125; &#123;methodName&#125;(&#123;parameters&#125;);&#39;)    def AppendField(self, propertyName, propertyType, modifier=&quot;public&quot;, init=&#39;&#39;):        if len(init) &gt; 0:            self.AppendLine(f&#39;&#123;modifier&#125; &#123;propertyType&#125; &#123;propertyName&#125; = &#123;init&#125;;&#39;)        else:            self.AppendLine(f&#39;&#123;modifier&#125; &#123;propertyType&#125; &#123;propertyName&#125;;&#39;)    def AppendProperty(self, propertyName, propertyType, field=None, modifier=&quot;public&quot;):        if field is not None:            self.AppendLine(f&#39;&#123;modifier&#125; &#123;propertyType&#125; &#123;propertyName&#125; =&gt; &#123;field&#125;;&#39;)        else:            self.AppendLine(f&#39;&#123;modifier&#125; &#123;propertyType&#125; &#123;propertyName&#125; &#123;&#123; get; set; &#125;&#125;&#39;)    def BeginProperty(self, propertyName, propertyType, modifier=&quot;public&quot;):        self.AppendLine(f&#39;&#123;modifier&#125; &#123;propertyType&#125; &#123;propertyName&#125;&#39;)        self.BeginBrace()    def EndProperty(self):        self.EndBrace()    def BeginMethod(self, methodName, modifier=&quot;public&quot;, returnType=&#39;void&#39;, parameters=&#39;&#39;):        self.AppendLine(f&#39;&#123;modifier&#125; &#123;returnType&#125; &#123;methodName&#125;(&#123;parameters&#125;)&#39;)        self.BeginBrace()    def BeginConstructionMethod(self, methodName, modifier=&quot;public&quot;, parameters=&#39;&#39;):        self.AppendLine(f&#39;&#123;modifier&#125; &#123;methodName&#125;(&#123;parameters&#125;)&#39;)        self.BeginBrace()    def EndMethod(self):        self.EndBrace()    def BeginIf(self, condition):        self.AppendLine(f&#39;if (&#123;condition&#125;)&#39;)        self.BeginBrace()    def BeginElif(self, condition):        self.AppendLine(f&#39;else if (&#123;condition&#125;)&#39;)        self.BeginBrace()    def EndIf(self):        self.EndBrace()    def BeginWhile(self, condition):        self.AppendLine(f&#39;while (&#123;condition&#125;)&#39;)        self.BeginBrace()    def EndWhile(self):        self.EndBrace()    def BeginTry(self):        self.AppendLine(&#39;try&#39;)        self.BeginBrace()    def EndTry(self):        self.EndBrace()    def BeginCatch(self, exceptionType):        self.AppendLine(f&#39;catch (&#123;exceptionType&#125;)&#39;)        self.BeginBrace()    def BeginForEach(self, name, collection, singleType=&#39;var&#39;):        self.AppendLine(f&#39;foreach (&#123;singleType&#125; &#123;name&#125; in &#123;collection&#125;)&#39;)        self.BeginBrace()    def EndForEach(self):        self.EndBrace()    def BeginFor(self, condition):        self.AppendLine(f&#39;for (&#123;condition&#125;)&#39;)        self.BeginBrace()    def EndFor(self):        self.EndBrace()    def BeginForRange(self, name, start, end):        self.AppendLine(f&#39;for (int &#123;name&#125; = &#123;start&#125;; &#123;name&#125; &lt; &#123;end&#125;; &#123;name&#125;++)&#39;)        self.BeginBrace()    def EndForRange(self):        self.EndBrace()    def EndCatch(self):        self.EndBrace()    def BeginBlank(self):        self.BlackNum += 1    def EndBlank(self):        self.BlackNum -= 1    def BeginBrace(self):        self.AppendLine(&#39;&#123;&#39;)        self.BeginBlank()    def EndBrace(self):        self.EndBlank()        self.AppendLine(&#39;&#125;&#39;)    def BeginRegion(self, regionName):        self.AppendLine(f&#39;#region &#123;regionName&#125;&#39;)        self.AppendEmptyLine()    def EndRegion(self):        self.AppendEmptyLine()        self.AppendLine(&#39;#endregion&#39;)        self.AppendEmptyLine()    def ToString(self):        return &#39;&#39;.join(self)    def GenerateScript(self, fileName, isDefPath=True):        fileName = fileName.replace(&#39;.cs&#39;, &#39;&#39;)        if isDefPath:            fileName = os.path.join(ScriptsPath, fileName)        with open(f&#39;&#123;fileName&#125;.cs&#39;, &#39;w&#39;, encoding=&#39;utf-8&#39;) as f:            f.write(self.ToString())</code></pre><h1 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h1><pre><code>def CreateTableManagerCs():    script = CSScriptBuilder()    script.AppendUsing(&#39;System.Collections.Generic&#39;)    script.AppendUsing(&#39;UnityEngine&#39;)    script.BeginNamespace(TableLoadAssembly)    script.BeginInterface(&#39;ITable&#39;, &#39;public&#39;)    script.AppendInterfaceMethod(&#39;Dispose&#39;)    script.EndInterface()    script.AppendEmptyLine()    script.BeginClass(&#39;TableManager&#39;, &#39;public static&#39;)    script.AppendField(&#39;s_Inited&#39;, &#39;bool&#39;, &#39;private static&#39;)    script.AppendField(&#39;s_Cached&#39;, &#39;List&lt;ITable&gt;&#39;, &#39;private static&#39;, &#39;new List&lt;ITable&gt;()&#39;)    # 添加方法    script.AppendEmptyLine()    script.BeginMethod(&quot;Add&quot;, parameters=&quot;ITable table&quot;)    script.AppendLine(&quot;Debug.Assert(s_Inited, \&quot;add but TableManager not init \&quot;);&quot;)    script.AppendLine(&quot;s_Cached.Add(table);&quot;)    script.EndMethod()    script.AppendEmptyLine()    script.BeginMethod(&quot;Remove&quot;, parameters=&quot;ITable table&quot;)    script.AppendLine(&quot;Debug.Assert(s_Inited, \&quot;remove but TableManager not init\&quot;);&quot;)    script.AppendLine(&quot;if (s_Cached.Remove(table))&quot;)    script.BeginBrace()    script.AppendLine(&quot;table.Dispose();&quot;)    script.EndBrace()    script.EndMethod()    script.AppendEmptyLine()    script.BeginMethod(&quot;Init&quot;)    script.AppendLine(&quot;Debug.Assert(!s_Inited, \&quot;TableManager already init\&quot;);&quot;)    script.AppendLine(&quot;s_Inited = true;&quot;)    script.EndMethod()    script.AppendEmptyLine()    script.BeginMethod(&quot;UnInit&quot;)    script.AppendLine(&quot;Debug.Assert(s_Inited, \&quot;TableManager not init\&quot;);&quot;)    script.AppendLine(&quot;s_Inited = false;&quot;)    script.AppendLine(&quot;foreach (var table in s_Cached)&quot;)    script.BeginBrace()    script.AppendLine(&quot;table.Dispose();&quot;)    script.EndBrace()    script.AppendLine(&quot;s_Cached.Clear();&quot;)    script.EndMethod()    script.EndClass()    script.EndNamespace()    script.GenerateScript(os.path.join(ScriptsPath, &#39;TableManager&#39;))</code></pre><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>以上是本文介绍的C#脚本生成辅助工具的代码示例。工具的核心是一个名为CSScriptBuilder的类，它继承自list，并提供了一系列用于生成C#脚本的方法。</p><p>通过使用这个工具，用户可以更加方便地生成C#脚本，提高开发效率。</p><p>希望本文对您理解这个工具的用途和使用方法有所帮助。如果您有任何问题或建议，欢迎留言讨论。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;&lt;a href=&quot;https://lianbai.icu/2023/09/17/Python%E6%9D%82%E6%96%87/Python%E6%9D%82%E6%96%87%E2%80%94%E2%80%94CShap%E8%84%9A%E6%9C%AC%E7%94%</summary>
      
    
    
    
    <category term="Python杂文" scheme="http://yoursite.com/categories/Python%E6%9D%82%E6%96%87/"/>
    
    
    <category term="Python" scheme="http://yoursite.com/tags/Python/"/>
    
  </entry>
  
  <entry>
    <title>个人作品展示</title>
    <link href="http://yoursite.com/2023/09/04/%E4%B8%AA%E4%BA%BA%E7%AC%94%E8%AE%B0/%E4%B8%AA%E4%BA%BA%E4%BD%9C%E5%93%81%E5%B1%95%E7%A4%BA/"/>
    <id>http://yoursite.com/2023/09/04/%E4%B8%AA%E4%BA%BA%E7%AC%94%E8%AE%B0/%E4%B8%AA%E4%BA%BA%E4%BD%9C%E5%93%81%E5%B1%95%E7%A4%BA/</id>
    <published>2023-09-04T02:35:34.000Z</published>
    <updated>2023-09-18T13:00:35.041Z</updated>
    
    <content type="html"><![CDATA[<h1 id="Unity参与项目"><a href="#Unity参与项目" class="headerlink" title="Unity参与项目"></a>Unity参与项目</h1><h2 id="Game-Of-Honor"><a href="#Game-Of-Honor" class="headerlink" title="Game Of Honor"></a>Game Of Honor</h2><details>    <summary> 点击展开 </summary> <p><img src="https://gitee.com/lian_bai/img-warehouse/raw/master/BlogImg/1694956179652-2023-9-1721:09:40.png" alt="1694956179652-2023-9-1721:09:40.png">  </p></details> <h1 id="Unity工具"><a href="#Unity工具" class="headerlink" title="Unity工具"></a>Unity工具</h1><h2 id="布NPC和怪物编辑器"><a href="#布NPC和怪物编辑器" class="headerlink" title="布NPC和怪物编辑器"></a>布NPC和怪物编辑器</h2><details>    <summary> 点击展开 </summary> <p><img src="https://gitee.com/lian_bai/img-warehouse/raw/master/BlogImg/20230917_210529-2023-9-1721:06:52.gif" alt="20230917_210529-2023-9-1721:06:52.gif">  </p></details> <h2 id="UI动画编辑器"><a href="#UI动画编辑器" class="headerlink" title="UI动画编辑器"></a>UI动画编辑器</h2><details>    <summary> 点击展开 </summary> <p><img src="https://gitee.com/lian_bai/img-warehouse/raw/master/BlogImg/20230917_211925-2023-9-1721:20:32.gif" alt="20230917_211925-2023-9-1721:20:32.gif">  </p></details> <h1 id="Python工具"><a href="#Python工具" class="headerlink" title="Python工具"></a>Python工具</h1><p><a href="https://github.com/LianBai/PyTools.git"><strong>仓库地址</strong></a>  </p><h2 id="C-工程配置导表工具"><a href="#C-工程配置导表工具" class="headerlink" title="C#工程配置导表工具"></a>C#工程配置导表工具</h2><p><a href="https://github.com/LBGTeam/Excel2Bytes"><strong>Excel2Bytes仓库</strong></a></p><h2 id="项目工具"><a href="#项目工具" class="headerlink" title="项目工具"></a>项目工具</h2><details>    <summary> 点击展开 </summary> <h3 id="演示视频"><a href="#演示视频" class="headerlink" title="演示视频"></a>演示视频</h3><p><img src="https://gitee.com/lian_bai/img-warehouse/raw/master/BlogImg/20230904_100721-2023-9-410:36:14.gif" alt="20230904_100721-2023-9-410:36:14.gif"></p><h3 id="工具截图"><a href="#工具截图" class="headerlink" title="工具截图"></a>工具截图</h3><p><img src="https://gitee.com/lian_bai/img-warehouse/raw/master/BlogImg/1693795113797-2023-9-410:38:34.png" alt="1693795113797-2023-9-410:38:34.png"><br><img src="https://gitee.com/lian_bai/img-warehouse/raw/master/BlogImg/1693795142194-2023-9-410:39:03.png" alt="1693795142194-2023-9-410:39:03.png"><br><img src="https://gitee.com/lian_bai/img-warehouse/raw/master/BlogImg/1693795157340-2023-9-410:39:18.png" alt="1693795157340-2023-9-410:39:18.png"><br><img src="https://gitee.com/lian_bai/img-warehouse/raw/master/BlogImg/1693795175142-2023-9-410:39:35.png" alt="1693795175142-2023-9-410:39:35.png"><br><img src="https://gitee.com/lian_bai/img-warehouse/raw/master/BlogImg/1693795225733-2023-9-410:40:26.png" alt="1693795225733-2023-9-410:40:26.png">  </p></details>  <h2 id="博客工具"><a href="#博客工具" class="headerlink" title="博客工具"></a>博客工具</h2><details>    <summary> 点击展开 </summary> <p><img src="https://gitee.com/lian_bai/img-warehouse/raw/master/BlogImg/1693795268866-2023-9-410:41:09.png" alt="1693795268866-2023-9-410:41:09.png">  </p><p><img src="https://gitee.com/lian_bai/img-warehouse/raw/master/BlogImg/1693795304153-2023-9-410:41:45.png" alt="1693795304153-2023-9-410:41:45.png">  </p></details> ]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;Unity参与项目&quot;&gt;&lt;a href=&quot;#Unity参与项目&quot; class=&quot;headerlink&quot; title=&quot;Unity参与项目&quot;&gt;&lt;/a&gt;Unity参与项目&lt;/h1&gt;&lt;h2 id=&quot;Game-Of-Honor&quot;&gt;&lt;a href=&quot;#Game-Of-Hono</summary>
      
    
    
    
    <category term="个人笔记" scheme="http://yoursite.com/categories/%E4%B8%AA%E4%BA%BA%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="个人笔记" scheme="http://yoursite.com/tags/%E4%B8%AA%E4%BA%BA%E7%AC%94%E8%AE%B0/"/>
    
  </entry>
  
  <entry>
    <title>Unity杂文——Editor的Tree</title>
    <link href="http://yoursite.com/2023/09/02/Unity%E6%9D%82%E6%96%87/Unity%E6%9D%82%E6%96%87%E2%80%94%E2%80%94Editor%E7%9A%84Tree/"/>
    <id>http://yoursite.com/2023/09/02/Unity%E6%9D%82%E6%96%87/Unity%E6%9D%82%E6%96%87%E2%80%94%E2%80%94Editor%E7%9A%84Tree/</id>
    <published>2023-09-02T07:23:59.000Z</published>
    <updated>2023-09-28T03:09:39.332Z</updated>
    
    <content type="html"><![CDATA[<p><a href="https://lianbai.icu/2023/09/02/Unity%E6%9D%82%E6%96%87/Unity%E6%9D%82%E6%96%87%E2%80%94%E2%80%94Editor%E7%9A%84Tree/">原文地址</a>  </p><h1 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h1><p>本文介绍了一种用于编辑器开发过程中树形结构数据进行渲染的辅助脚本。</p><h1 id="TreeElementGUI"><a href="#TreeElementGUI" class="headerlink" title="TreeElementGUI"></a>TreeElementGUI</h1><p>TreeElementGUI是树形结构元素渲染的基类，包含了节点深度、父节点、子节点、节点名字、节点ID等属性，并且提供了无参构造函数和有参构造函数。 </p><details>    <summary> TreeElementGUI </summary>  <pre><code>/// &lt;summary&gt;/// 树结构的元素/// &lt;/summary&gt;public abstract class TreeElementGUI&#123;    private int m_ID;    private string m_Name;    private int m_Depth;    [NonSerialized] private TreeElementGUI m_Parent;    [NonSerialized] private List&lt;TreeElementGUI&gt; m_Children;    /// &lt;summary&gt;    /// 深度    /// &lt;/summary&gt;    public int Depth    &#123;        get =&gt; m_Depth;        set =&gt; m_Depth = value;    &#125;    /// &lt;summary&gt;    /// 父节点    /// &lt;/summary&gt;    public TreeElementGUI Parent    &#123;        get =&gt; m_Parent;        set =&gt; m_Parent = value;    &#125;    /// &lt;summary&gt;    /// 子节点    /// &lt;/summary&gt;    public List&lt;TreeElementGUI&gt; Children    &#123;        get =&gt; m_Children;        set =&gt; m_Children = value;    &#125;        /// &lt;summary&gt;    /// 是否有子节点    /// &lt;/summary&gt;    public bool HasChildren =&gt; m_Children != null &amp;&amp; m_Children.Count &gt; 0;    /// &lt;summary&gt;    /// 节点名字    /// &lt;/summary&gt;    public string Name    &#123;        get =&gt; m_Name;        set =&gt; m_Name = value;    &#125;    /// &lt;summary&gt;    /// 节点ID    /// &lt;/summary&gt;    public int Id    &#123;        get =&gt; m_ID;        set =&gt; m_ID = value;    &#125;    /// &lt;summary&gt;    /// 无参构造函数    /// &lt;/summary&gt;    protected TreeElementGUI() :this(-1, -1, &quot;&quot;)    &#123;            &#125;    /// &lt;summary&gt;    /// 有参构造函数    /// &lt;/summary&gt;    /// &lt;param name=&quot;id&quot;&gt;&lt;/param&gt;    /// &lt;param name=&quot;depth&quot;&gt;&lt;/param&gt;    /// &lt;param name=&quot;name&quot;&gt;&lt;/param&gt;    protected TreeElementGUI(int id, int depth, string name)    &#123;        m_Name = name;        m_ID = id;        m_Depth = depth;    &#125;    public abstract void OnGUI(Rect rect, int columnIndex);    public abstract bool IsMatchSearch(string search);&#125;</code></pre></details><h1 id="TreeViewItemGUI"><a href="#TreeViewItemGUI" class="headerlink" title="TreeViewItemGUI"></a>TreeViewItemGUI<T></T></h1><p>TreeViewItemGUI<T>是节点元素显示的类，继承自TreeViewItem，并且包含了一个泛型参数T，其中T必须是TreeElementGUI的子类。TreeViewItemGUI<T>包含了一个Data属性，用于获取节点元素的数据，同时提供了OnGUI和IsMatchSearch方法，用于在UI上绘制节点元素和进行搜索匹配。  </T></T></p><details>    <summary> TreeViewItemGUI<T> </T></summary>   <pre><code>/// &lt;summary&gt;/// 树结构编辑器显示/// &lt;/summary&gt;/// &lt;typeparam name=&quot;T&quot;&gt;&lt;/typeparam&gt;public class TreeViewItemGUI&lt;T&gt; : TreeViewItem where T : TreeElementGUI&#123;    private readonly T m_Data;    public T Data =&gt; m_Data;    public TreeViewItemGUI(int id, int depth, string displayName, T data) : base(id, depth, displayName)    &#123;        m_Data = data;    &#125;    public void OnGUI(Rect rect, int columnIndex)    &#123;        m_Data.OnGUI(rect, columnIndex);    &#125;    public bool IsMatchSearch(string search)    &#123;        return m_Data.IsMatchSearch(search);    &#125;&#125;</code></pre></details><h1 id="TreeGUIUtility"><a href="#TreeGUIUtility" class="headerlink" title="TreeGUIUtility"></a>TreeGUIUtility</h1><p>TreeGUIUtility是一个用于编辑器开发过程中树形结构数据进行渲染的辅助脚本。该脚本包含了一些常用的方法，可以帮助开发者处理树形结构数据。  </p><p>其中，TreeToList<T>方法可以将树形结构数据转换为列表形式，Find<T>方法可以在树形结构数据中查找符合条件的节点元素，ListToTree<T>方法可以将列表形式的数据转换为树形结构数据，ValidateDepthValues<T>方法可以检查列表中的深度值是否合法，UpdateDepthValues<T>方法可以更新树形结构数据中的深度值，FindCommonAncestorsWithinList<T>方法可以查找列表中共同的祖先节点。   </T></T></T></T></T></T></p><details>    <summary> TreeGUIUtility<T> </T></summary>  <pre><code>public static class TreeGUIUtility&#123;    public static void TreeToList&lt;T&gt;(T root, IList&lt;T&gt; result) where T : TreeElementGUI    &#123;        if (result == null)            throw new NullReferenceException(&quot;The input &#39;IList&lt;T&gt; result&#39; list is null&quot;);        result.Clear();        var stack = new Stack&lt;T&gt;();        stack.Push(root);        while (stack.Count &gt; 0)        &#123;            var current = stack.Pop();            result.Add(current);            if (current.Children != null &amp;&amp; current.Children.Count &gt; 0)            &#123;                for (var i = current.Children.Count - 1; i &gt;= 0; i--)                &#123;                    stack.Push((T)current.Children[i]);                &#125;            &#125;        &#125;    &#125;        public static T Find&lt;T&gt;(T root, Func&lt;T, bool&gt; comparer) where T : TreeElementGUI    &#123;        var stack = new Stack&lt;T&gt;();        stack.Push(root);        while (stack.Count &gt; 0)        &#123;            var current = stack.Pop();            if(root != current &amp;&amp; comparer(current))            &#123;                return current;            &#125;            if (current.Children != null &amp;&amp; current.Children.Count &gt; 0)            &#123;                for (var i = current.Children.Count - 1; i &gt;= 0; i--)                &#123;                    stack.Push((T)current.Children[i]);                &#125;            &#125;        &#125;        return null;    &#125;    /// &lt;summary&gt;    /// List转成树结构    /// &lt;/summary&gt;    /// &lt;param name=&quot;list&quot;&gt;&lt;/param&gt;    /// &lt;typeparam name=&quot;T&quot;&gt;&lt;/typeparam&gt;    /// &lt;returns&gt;&lt;/returns&gt;    public static T ListToTree&lt;T&gt;(IList&lt;T&gt; list) where T : TreeElementGUI    &#123;        // 验证深度的值        ValidateDepthValues(list);        // 清理状态        foreach (var element in list)        &#123;            element.Parent = null;            element.Children = null;        &#125;        // 设置子节点和父节点        for (var parentIndex = 0; parentIndex &lt; list.Count; parentIndex++)        &#123;            var parent = list[parentIndex];            var alreadyHasValidChildren = parent.Children != null;            if (alreadyHasValidChildren)                continue;            var parentDepth = parent.Depth;            var childCount = 0;            // Count children based depth value, we are looking at children until it&#39;s the same depth as this object            for (var i = parentIndex + 1; i &lt; list.Count; i++)            &#123;                if (list[i].Depth == parentDepth + 1)                    childCount++;                if (list[i].Depth &lt;= parentDepth)                    break;            &#125;            // Fill child array            List&lt;TreeElementGUI&gt; childList = null;            if (childCount != 0)            &#123;                childList = new List&lt;TreeElementGUI&gt;(childCount); // Allocate once                childCount = 0;                for (var i = parentIndex + 1; i &lt; list.Count; i++)                &#123;                    if (list[i].Depth == parentDepth + 1)                    &#123;                        list[i].Parent = parent;                        childList.Add(list[i]);                        childCount++;                    &#125;                    if (list[i].Depth &lt;= parentDepth)                        break;                &#125;            &#125;            parent.Children = childList;        &#125;        return list[0];    &#125;    /// &lt;summary&gt;    /// 检查List的深度值    /// &lt;/summary&gt;    /// &lt;param name=&quot;list&quot;&gt;&lt;/param&gt;    /// &lt;typeparam name=&quot;T&quot;&gt;&lt;/typeparam&gt;    /// &lt;exception cref=&quot;ArgumentException&quot;&gt;&lt;/exception&gt;    public static void ValidateDepthValues&lt;T&gt;(IList&lt;T&gt; list) where T : TreeElementGUI    &#123;        if (list.Count == 0)            throw new ArgumentException(&quot;list should have items, count is 0, check before calling ValidateDepthValues&quot;, nameof(list));        if (list[0].Depth != -1)            throw new ArgumentException(&quot;list item at index 0 should have a depth of -1 (since this should be the hidden root of the tree). Depth is: &quot; + list[0].Depth, nameof(list));        for (var i = 0; i &lt; list.Count - 1; i++)        &#123;            var depth = list[i].Depth;            var nextDepth = list[i + 1].Depth;            if (nextDepth &gt; depth &amp;&amp; nextDepth - depth &gt; 1)                throw new ArgumentException(string.Format(&quot;Invalid depth info in input list. Depth cannot increase more than 1 per row. Index &#123;0&#125; has depth &#123;1&#125; while index &#123;2&#125; has depth &#123;3&#125;&quot;, i, depth, i + 1, nextDepth));        &#125;        for (var i = 1; i &lt; list.Count; ++i)            if (list[i].Depth &lt; 0)                throw new ArgumentException(&quot;Invalid depth value for item at index &quot; + i + &quot;. Only the first item (the root) should have depth below 0.&quot;);        if (list.Count &gt; 1 &amp;&amp; list[1].Depth != 0)            throw new ArgumentException(&quot;Input list item at index 1 is assumed to have a depth of 0&quot;, nameof(list));    &#125;    /// &lt;summary&gt;    /// 更新深度值    /// &lt;/summary&gt;    /// &lt;param name=&quot;root&quot;&gt;&lt;/param&gt;    /// &lt;typeparam name=&quot;T&quot;&gt;&lt;/typeparam&gt;    /// &lt;exception cref=&quot;ArgumentNullException&quot;&gt;&lt;/exception&gt;    public static void UpdateDepthValues&lt;T&gt;(T root) where T : TreeElementGUI    &#123;        if (root == null)            throw new ArgumentNullException(nameof(root), &quot;The root is null&quot;);        if (!root.HasChildren)            return;        var stack = new Stack&lt;TreeElementGUI&gt;();        stack.Push(root);        while (stack.Count &gt; 0)        &#123;            var current = stack.Pop();            if (current.Children != null)            &#123;                foreach (var child in current.Children)                &#123;                    child.Depth = current.Depth + 1;                    stack.Push(child);                &#125;            &#125;        &#125;    &#125;    /// &lt;summary&gt;    /// 判断是否是子节点    /// &lt;/summary&gt;    /// &lt;param name=&quot;child&quot;&gt;&lt;/param&gt;    /// &lt;param name=&quot;elements&quot;&gt;&lt;/param&gt;    /// &lt;typeparam name=&quot;T&quot;&gt;&lt;/typeparam&gt;    /// &lt;returns&gt;&lt;/returns&gt;    static bool IsChildOf&lt;T&gt;(T child, IList&lt;T&gt; elements) where T : TreeElementGUI    &#123;        while (child != null)        &#123;            child = (T)child.Parent;            if (elements.Contains(child))                return true;        &#125;        return false;    &#125;    /// &lt;summary&gt;    /// 查找共同的祖先节点    /// &lt;/summary&gt;    /// &lt;param name=&quot;elements&quot;&gt;&lt;/param&gt;    /// &lt;typeparam name=&quot;T&quot;&gt;&lt;/typeparam&gt;    /// &lt;returns&gt;&lt;/returns&gt;    public static IList&lt;T&gt; FindCommonAncestorsWithinList&lt;T&gt;(IList&lt;T&gt; elements) where T : TreeElementGUI    &#123;        if (elements.Count == 1)            return new List&lt;T&gt;(elements);        var result = new List&lt;T&gt;(elements);        result.RemoveAll(g =&gt; IsChildOf(g, elements));        return result;    &#125;&#125;</code></pre></details><h1 id="TreeGUIModel"><a href="#TreeGUIModel" class="headerlink" title="TreeGUIModel"></a>TreeGUIModel<T></T></h1><p>TreeGUIModel<T>是一个用于管理树形结构数据的类，包含了增加、移除、移动、清空、更新等方法，可以帮助开发者更方便地处理树形结构数据。  </T></p><details>    <summary> TreeGUIModel<T> </T></summary>  <pre><code>public class TreeGUIModel&lt;T&gt; where T : TreeElementGUI, new()&#123;    private T m_Root;    private int m_MaxID;    private bool m_IsDirty;    public T Root =&gt; m_Root;    public event Action&lt;T&gt; added;    public event Action&lt;T&gt; removed;    public int Count =&gt; m_Root.Children.Count;    public bool IsDirty =&gt; m_IsDirty;    public TreeGUIModel()    &#123;        m_Root = new T        &#123;            Id = GenerateUniqueID(), Depth = -1, Name = $&quot;&#123;typeof(T).Name&#125; - Root&quot;,            Children = new List&lt;TreeElementGUI&gt;()        &#125;;        m_IsDirty = true;    &#125;    /// &lt;summary&gt;    /// 根据id查找元素    /// &lt;/summary&gt;    /// &lt;param name=&quot;id&quot;&gt;&lt;/param&gt;    /// &lt;returns&gt;&lt;/returns&gt;    public T Find(int id)    &#123;        return (T)m_Root.Children.FirstOrDefault(element =&gt; element.Id == id);    &#125;    /// &lt;summary&gt;    /// 自动生成唯一ID    /// &lt;/summary&gt;    /// &lt;returns&gt;&lt;/returns&gt;    public int GenerateUniqueID()    &#123;        return ++m_MaxID;    &#125;    /// &lt;summary&gt;    /// 获得所有的子节点    /// &lt;/summary&gt;    /// &lt;param name=&quot;id&quot;&gt;&lt;/param&gt;    /// &lt;returns&gt;&lt;/returns&gt;    public IList&lt;int&gt; GetAncestors(int id)    &#123;        var parents = new List&lt;int&gt;();        var item = Find(id);        if (item != null)        &#123;            while (item.Parent != null)            &#123;                parents.Add(item.Parent.Id);                item = (T)item.Parent;            &#125;        &#125;        return parents;    &#125;    /// &lt;summary&gt;    /// 获得有子节点的所有子节点    /// &lt;/summary&gt;    /// &lt;param name=&quot;id&quot;&gt;&lt;/param&gt;    /// &lt;returns&gt;&lt;/returns&gt;    public IList&lt;int&gt; GetDescendantsThatHaveChildren(int id)    &#123;        var searchFromThis = Find(id);        return searchFromThis != null ? GetParentsBelowStackBased(searchFromThis) : new List&lt;int&gt;();    &#125;    /// &lt;summary&gt;    /// 获得基于栈的所有子节点    /// &lt;/summary&gt;    /// &lt;param name=&quot;searchFromThis&quot;&gt;&lt;/param&gt;    /// &lt;returns&gt;&lt;/returns&gt;    private IList&lt;int&gt; GetParentsBelowStackBased(TreeElementGUI searchFromThis)    &#123;        var stack = new Stack&lt;TreeElementGUI&gt;();        stack.Push(searchFromThis);        var parentsBelow = new List&lt;int&gt;();        while (stack.Count &gt; 0)        &#123;            var current = stack.Pop();            if (current.HasChildren)            &#123;                parentsBelow.Add(current.Id);                foreach (var T in current.Children)                &#123;                    stack.Push(T);                &#125;            &#125;        &#125;        return parentsBelow;    &#125;    /// &lt;summary&gt;    /// 移除元素    /// &lt;/summary&gt;    /// &lt;param name=&quot;elementID&quot;&gt;&lt;/param&gt;    public void RemoveElements(int elementID)    &#123;        var elements = m_Root.Children.Where(element =&gt; element.Id == elementID).Cast&lt;T&gt;().ToArray();        RemoveElements(elements);    &#125;    /// &lt;summary&gt;    /// 移除元素    /// &lt;/summary&gt;    /// &lt;param name=&quot;elementIDs&quot;&gt;&lt;/param&gt;    public void RemoveElements(IList&lt;int&gt; elementIDs)    &#123;        var elements = m_Root.Children.Where(element =&gt; elementIDs.Contains(element.Id)).Cast&lt;T&gt;().ToArray();        RemoveElements(elements);    &#125;    /// &lt;summary&gt;    /// 移除元素    /// &lt;/summary&gt;    /// &lt;param name=&quot;elements&quot;&gt;&lt;/param&gt;    public void RemoveElements(IList&lt;T&gt; elements)    &#123;        var commonAncestors = TreeGUIUtility.FindCommonAncestorsWithinList(elements);        foreach (var element in commonAncestors)        &#123;            element.Parent.Children.Remove(element);            element.Parent = null;            removed?.Invoke(element);        &#125;        SetDirty();    &#125;    /// &lt;summary&gt;    /// 增加元素    /// &lt;/summary&gt;    /// &lt;param name=&quot;elements&quot;&gt;&lt;/param&gt;    /// &lt;param name=&quot;parent&quot;&gt;&lt;/param&gt;    /// &lt;param name=&quot;insertPosition&quot;&gt;&lt;/param&gt;    /// &lt;param name=&quot;isNew&quot;&gt;&lt;/param&gt;    /// &lt;exception cref=&quot;ArgumentNullException&quot;&gt;&lt;/exception&gt;    public void AddElements(IList&lt;T&gt; elements, TreeElementGUI parent, int insertPosition, bool isNew = false)    &#123;        if (elements == null)            throw new ArgumentNullException(nameof(elements), &quot;elements is null&quot;);        if (elements.Count == 0)            throw new ArgumentNullException(nameof(elements), &quot;elements Count is 0: nothing to add&quot;);        if (parent == null)            throw new ArgumentNullException(nameof(parent), &quot;parent is null&quot;);        parent.Children ??= new List&lt;TreeElementGUI&gt;();        parent.Children.InsertRange(insertPosition, elements);        foreach (var element in elements)        &#123;            element.Parent = parent;            element.Depth = parent.Depth + 1;            TreeGUIUtility.UpdateDepthValues(element);            if(isNew)            &#123;                added?.Invoke(element);            &#125;        &#125;        SetDirty();    &#125;    /// &lt;summary&gt;    /// 增加元素    /// &lt;/summary&gt;    /// &lt;param name=&quot;root&quot;&gt;&lt;/param&gt;    /// &lt;param name=&quot;isNew&quot;&gt;&lt;/param&gt;    public void AddElement(T root, bool isNew = false)    &#123;        root.Id = GenerateUniqueID();        root.Depth = -1;        root.Parent = m_Root;        m_Root.Children.Add(root);        if(isNew)        &#123;            added?.Invoke(root);        &#125;        SetDirty();    &#125;    /// &lt;summary&gt;    /// 增加元素    /// &lt;/summary&gt;    /// &lt;param name=&quot;element&quot;&gt;&lt;/param&gt;    /// &lt;param name=&quot;parent&quot;&gt;&lt;/param&gt;    /// &lt;param name=&quot;insertPosition&quot;&gt;&lt;/param&gt;    /// &lt;param name=&quot;isNew&quot;&gt;&lt;/param&gt;    public void AddElement(T element, T parent, int insertPosition, bool isNew = false)    &#123;        parent.Children ??= new List&lt;TreeElementGUI&gt;();        parent.Children.Insert(insertPosition, element);        element.Parent = parent;        TreeGUIUtility.UpdateDepthValues(parent);        if (isNew)        &#123;            added?.Invoke(element);        &#125;        SetDirty();    &#125;    /// &lt;summary&gt;    /// 移动元素    /// &lt;/summary&gt;    /// &lt;param name=&quot;parentElement&quot;&gt;&lt;/param&gt;    /// &lt;param name=&quot;insertionIndex&quot;&gt;&lt;/param&gt;    /// &lt;param name=&quot;elements&quot;&gt;&lt;/param&gt;    /// &lt;exception cref=&quot;ArgumentException&quot;&gt;&lt;/exception&gt;    public void MoveElements(TreeElementGUI parentElement, int insertionIndex, List&lt;TreeElementGUI&gt; elements)    &#123;        if (insertionIndex &lt; 0)            throw new ArgumentException(&quot;Invalid input: insertionIndex is -1, client needs to decide what index elements should be reparented at&quot;);        // Invalid reparenting input        if (parentElement == null)            return;        // We are moving items so we adjust the insertion index to accomodate that any items above the insertion index is removed before inserting        if (insertionIndex &gt; 0)            insertionIndex -= parentElement.Children.GetRange(0, insertionIndex).Count(elements.Contains);        // Remove draggedItems from their parents        foreach (var draggedItem in elements)        &#123;            draggedItem.Parent.Children.Remove(draggedItem);    // remove from old parent            draggedItem.Parent = parentElement;                 // set new parent        &#125;        parentElement.Children ??= new List&lt;TreeElementGUI&gt;();        // Insert dragged items under new parent        parentElement.Children.InsertRange(insertionIndex, elements);        TreeGUIUtility.UpdateDepthValues(Root);        SetDirty();    &#125;    /// &lt;summary&gt;    /// 标记为脏    /// &lt;/summary&gt;    private void SetDirty()    &#123;        m_IsDirty = true;    &#125;    /// &lt;summary&gt;    /// 清理    /// &lt;/summary&gt;    public void Clear()    &#123;        m_Root.Children.Clear();        SetDirty();    &#125;    /// &lt;summary&gt;    /// 更新    /// &lt;/summary&gt;    internal void Update()    &#123;        m_IsDirty = true;    &#125;&#125;</code></pre></details><h1 id="TreeViewWithGUIModel"><a href="#TreeViewWithGUIModel" class="headerlink" title="TreeViewWithGUIModel"></a>TreeViewWithGUIModel<T></T></h1><p>TreeViewWithGUIModel<T>是一个抽象类，继承自TreeView，用于在编辑器中渲染树形结构数据。该类包含了一些常用的方法，可以帮助开发者处理树形结构数据。  </T></p><p>该类还包含了一些属性，用于控制树形结构数据的外观和行高等。  </p><p>TreeViewWithGUIModel<T>类的子类可以通过实现抽象方法来自定义树形结构数据的渲染和行为。  </T></p><details>    <summary> TreeViewWithGUIModel<T> </T></summary>  <pre><code>public abstract class TreeViewWithGUIModel&lt;T&gt; : TreeView where  T : TreeElementGUI, new()&#123;    protected TreeGUIModel&lt;T&gt; m_TreeModel;    private readonly List&lt;TreeViewItem&gt; m_Rows = new List&lt;TreeViewItem&gt;(100);    public bool ShowAlternatingRowBackgrounds    &#123;        get =&gt; showAlternatingRowBackgrounds;        set =&gt; showAlternatingRowBackgrounds = value;    &#125;    public bool ShowBorder    &#123;        get =&gt; showBorder;        set =&gt; showBorder = value;    &#125;    public float RowHeight    &#123;        get =&gt; rowHeight;        set =&gt; rowHeight = value;    &#125;    protected TreeViewWithGUIModel(TreeViewState state, TreeGUIModel&lt;T&gt; model) : base(state)    &#123;        Init(model);    &#125;    protected TreeViewWithGUIModel(TreeViewState state, MultiColumnHeader multiColumnHeader, TreeGUIModel&lt;T&gt; model) : base(state, multiColumnHeader)    &#123;        Init(model);        multiColumnHeader.sortingChanged += OnSortingChanged;    &#125;    private void Init(TreeGUIModel&lt;T&gt; model)    &#123;        m_TreeModel = model;    &#125;    private void OnSortingChanged(MultiColumnHeader _multiColumnHeader)    &#123;        SortIfNeeded(rootItem, m_Rows);    &#125;    private void SortIfNeeded(TreeViewItem root, List&lt;TreeViewItem&gt; rows)    &#123;        if( null == multiColumnHeader) return;        if ( rows.Count &lt;= 1)            return;        if (multiColumnHeader.sortedColumnIndex == -1)        &#123;            return; // No column to sort for (just use the order the data are in)        &#125;        // Sort the roots of the existing tree items        rootItem.children = SortByMultipleColumns(rows);        TreeToList(root, rows);        Repaint();    &#125;    public static void TreeToList(TreeViewItem root, IList&lt;TreeViewItem&gt; result)    &#123;        if (root == null)            throw new NullReferenceException(&quot;root&quot;);        if (result == null)            throw new NullReferenceException(&quot;result&quot;);        result.Clear();        if (root.children == null)            return;        var stack = new Stack&lt;TreeViewItem&gt;();        for (var i = root.children.Count - 1; i &gt;= 0; i--)            stack.Push(root.children[i]);        while (stack.Count &gt; 0)        &#123;            var current = stack.Pop();            result.Add(current);            if (current.hasChildren &amp;&amp; current.children[0] != null)            &#123;                for (var i = current.children.Count - 1; i &gt;= 0; i--)                &#123;                    stack.Push(current.children[i]);                &#125;            &#125;        &#125;    &#125;    protected override TreeViewItem BuildRoot()    &#123;        return null == m_TreeModel.Root            ? new TreeViewItemGUI&lt;T&gt;(0, -1, &quot;Root&quot;, null)            : new TreeViewItemGUI&lt;T&gt;(m_TreeModel.Root.Id, -1, m_TreeModel.Root.Name, m_TreeModel.Root);    &#125;    protected override bool DoesItemMatchSearch(TreeViewItem item, string search)    &#123;        var target = (TreeViewItemGUI&lt;T&gt;)item;        return target.IsMatchSearch(search);    &#125;    protected override void RowGUI(RowGUIArgs args)    &#123;        var item = (TreeViewItemGUI&lt;T&gt;) args.item;        if (null == multiColumnHeader)        &#123;            item.OnGUI(args.rowRect, 0);        &#125;        else        &#123;            var columns = args.GetNumVisibleColumns();            for (var i = 0; i &lt; columns; i++)            &#123;                var rt = args.GetCellRect(i);                CenterRectUsingSingleLineHeight(ref rt);                item.OnGUI(rt, args.GetColumn(i));            &#125;        &#125;    &#125;    protected override IList&lt;TreeViewItem&gt; BuildRows(TreeViewItem root)    &#123;        m_Rows.Clear();        if (m_TreeModel.Root == null)        &#123;            return m_Rows;        &#125;        if (hasSearch)        &#123;            Search(m_TreeModel.Root, searchString, m_Rows);        &#125;        else if (m_TreeModel.Root.HasChildren)        &#123;            AddChildrenRecursive(root, m_TreeModel.Root, 0, m_Rows);        &#125;        SortIfNeeded(root, m_Rows);        return m_Rows;    &#125;    private void AddChildrenRecursive(TreeViewItem root, T parent, int depth, IList&lt;TreeViewItem&gt; newRows)    &#123;        foreach (var treeElement in parent.Children)        &#123;            var child = (T) treeElement;            var item = new TreeViewItemGUI&lt;T&gt;(child.Id, depth, child.Name, child);            newRows.Add(item);            root.AddChild(item);            if (child.HasChildren)            &#123;                if (IsExpanded(child.Id))                &#123;                    AddChildrenRecursive(item, child, depth + 1, newRows);                &#125;                else                &#123;                    item.children = CreateChildListForCollapsedParent();                &#125;            &#125;        &#125;    &#125;    private void Search(T searchFromThis, string search, List&lt;TreeViewItem&gt; result)    &#123;        if (string.IsNullOrEmpty(search))            throw new ArgumentException(&quot;Invalid search: cannot be null or empty&quot;, nameof(search));        var stack = new Stack&lt;T&gt;();        foreach (var element in searchFromThis.Children)            stack.Push((T)element);        while (stack.Count &gt; 0)        &#123;            var current = stack.Pop();            // Matches search?            if (current.IsMatchSearch(search))            &#123;                result.Add(new TreeViewItemGUI&lt;T&gt;(current.Id, 0, current.Name, current));            &#125;            if (current.Children != null &amp;&amp; current.Children.Count &gt; 0)            &#123;                foreach (var element in current.Children)                &#123;                    stack.Push((T)element);                &#125;            &#125;        &#125;        SortSearchResult(result);    &#125;    public override IList&lt;TreeViewItem&gt; GetRows()    &#123;        return m_Rows;    &#125;    protected virtual void SortSearchResult(List&lt;TreeViewItem&gt; rows)    &#123;        // sort by displayName by default, can be overriden for multColumn solutions        rows.Sort((x, y) =&gt; EditorUtility.NaturalCompare(x.displayName, y.displayName));    &#125;    protected virtual List&lt;TreeViewItem&gt; SortByMultipleColumns(List&lt;TreeViewItem&gt; children)    &#123;        return children;    &#125;    public Rect DoLayout(params GUILayoutOption[] options)    &#123;        if(m_TreeModel.IsDirty)        &#123;            m_TreeModel.Update();            Reload();        &#125;        GUILayout.BeginVertical();        var rect = GUILayoutUtility.GetRect(GUIContent.none,                                           GUIStyle.none,                                           options);        OnGUI(rect);        GUILayout.EndVertical();        return rect;    &#125;&#125;</code></pre></details>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;&lt;a href=&quot;https://lianbai.icu/2023/09/02/Unity%E6%9D%82%E6%96%87/Unity%E6%9D%82%E6%96%87%E2%80%94%E2%80%94Editor%E7%9A%84Tree/&quot;&gt;原文地址&lt;/a&gt;  </summary>
      
    
    
    
    <category term="Unity杂文" scheme="http://yoursite.com/categories/Unity%E6%9D%82%E6%96%87/"/>
    
    
    <category term="Unity编辑器" scheme="http://yoursite.com/tags/Unity%E7%BC%96%E8%BE%91%E5%99%A8/"/>
    
  </entry>
  
  <entry>
    <title>Unity杂文——FPS计算并显示</title>
    <link href="http://yoursite.com/2023/08/30/Unity%E6%9D%82%E6%96%87/Unity%E6%9D%82%E6%96%87%E2%80%94%E2%80%94FPS%E8%AE%A1%E7%AE%97%E5%B9%B6%E6%98%BE%E7%A4%BA/"/>
    <id>http://yoursite.com/2023/08/30/Unity%E6%9D%82%E6%96%87/Unity%E6%9D%82%E6%96%87%E2%80%94%E2%80%94FPS%E8%AE%A1%E7%AE%97%E5%B9%B6%E6%98%BE%E7%A4%BA/</id>
    <published>2023-08-30T08:48:17.000Z</published>
    <updated>2023-09-28T03:08:18.843Z</updated>
    
    <content type="html"><![CDATA[<p><a href="https://lianbai.icu/2023/08/30/Unity%E6%9D%82%E6%96%87/Unity%E6%9D%82%E6%96%87%E2%80%94%E2%80%94FPS%E8%AE%A1%E7%AE%97%E5%B9%B6%E6%98%BE%E7%A4%BA/">原文地址</a></p><h1 id="自制游戏FPS显示工具"><a href="#自制游戏FPS显示工具" class="headerlink" title="自制游戏FPS显示工具"></a>自制游戏FPS显示工具</h1><p>在游戏开发中，FPS（Frames Per Second）是一个非常重要的指标，它可以反映出游戏的流畅度和性能。为了帮助开发者更好地优化游戏性能，我们可以使用一个自制的游戏FPS显示工具，实时显示当前的帧率。</p><h1 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h1><p>以下是自制的游戏FPS显示工具的代码实现：  </p><pre><code>public class FPSUtils : MonoBehaviour&#123;    #region 实例化        private static FPSUtils s_FPS = null;    /// &lt;summary&gt;    /// 实例化一个FPS单位    /// &lt;/summary&gt;    /// &lt;param name=&quot;parent&quot;&gt;&lt;/param&gt;    public static void CreateInstance(GameObject parent = null)    &#123;        if (s_FPS != null) return;        var go = new GameObject(&quot;FPS&quot;);        s_FPS = go.AddComponent&lt;FPSUtils&gt;();        if (null == parent)        &#123;            DontDestroyOnLoad(go);        &#125;        else        &#123;            go.SetParent(parent);            go.SetAsFirstSibling();        &#125;    &#125;    /// &lt;summary&gt;    /// 销毁一个FPS单位    /// &lt;/summary&gt;    public static void DestroyInstance()    &#123;        if (null != s_FPS)        &#123;            Destroy(s_FPS.gameObject);            s_FPS = null;        &#125;    &#125;    #endregion    #region FPS统计    /// &lt;summary&gt;    /// 是否显示FPS    /// &lt;/summary&gt;    private bool m_IsShowFPS = true;    /// &lt;summary&gt;    /// 帧数计算刷新时间（s）    /// &lt;/summary&gt;    private const float m_ShowTime = 1f;    /// &lt;summary&gt;    /// 当前时间    /// &lt;/summary&gt;    private float m_CurTime = 0f;    /// &lt;summary&gt;    /// 当前帧数    /// &lt;/summary&gt;    private static int m_Frames = 0;    /// &lt;summary&gt;    /// 临时FPS    /// &lt;/summary&gt;    private int m_TempFPS = 0;    /// &lt;summary&gt;    /// 帧数变化比较容忍度    /// &lt;/summary&gt;    private const int TOLERANCE = 5;    /// &lt;summary&gt;    /// 当前FPS    /// &lt;/summary&gt;    private int m_CurFPS;    /// &lt;summary&gt;    /// 当前显示的帧数    /// &lt;/summary&gt;    private int m_CurShowFPS;    /// &lt;summary&gt;    /// 品质当前时间    /// &lt;/summary&gt;    private float m_QualityCurTime = 0f;    /// &lt;summary&gt;    /// 品质的容忍度    /// &lt;/summary&gt;    private const int m_TotalTime = 10;    /// &lt;summary&gt;    /// 帧数容忍度    /// &lt;/summary&gt;    private const int m_MaxFrameCount = 200;    /// &lt;summary&gt;    /// 最小FPS容忍度    /// &lt;/summary&gt;    private const int m_MinFPS = 10;        /// &lt;summary&gt;    /// 当前最小帧数    /// &lt;/summary&gt;    private int m_CurMinFrmCount = 0;    private void Update()    &#123;        m_CurTime += Time.unscaledDeltaTime;        m_Frames++;        m_CurFPS = (int)(1.0f / Time.unscaledDeltaTime);        if (m_CurTime &gt;= m_ShowTime)        &#123;            m_CurShowFPS = (int)(m_Frames / m_CurTime);            if (Math.Abs(m_CurShowFPS - m_TempFPS) &gt; TOLERANCE)            &#123;                m_TempFPS = m_CurShowFPS;            &#125;            m_CurTime = 0;            m_Frames = 0;        &#125;    &#125;    private void FixedUpdate()    &#123;        m_QualityCurTime += Time.unscaledDeltaTime;        if (m_QualityCurTime &gt;= m_TotalTime)        &#123;            m_CurMinFrmCount = 0;            m_QualityCurTime = 0;        &#125;        if (m_CurFPS &gt;= m_MinFPS) return;        m_CurMinFrmCount++;        if (m_CurMinFrmCount &gt; m_MaxFrameCount)        &#123;            //检测到帧率过低，可以做切换品质的功能        &#125;    &#125;    /// &lt;summary&gt;    /// 绘制FPS    /// &lt;/summary&gt;    private void OnGUI()    &#123;        if (m_IsShowFPS)        &#123;            var fpsStr = $&quot;FPS:&#123;m_CurShowFPS&#125;&quot;;            GUI.Label(new Rect(0, 0, 100, 20), fpsStr);        &#125;    &#125;    #endregion&#125;</code></pre><h1 id="代码解释"><a href="#代码解释" class="headerlink" title="代码解释"></a>代码解释</h1><p>以上代码中，FPSUtils类继承自MonoBehaviour，用于实现FPS统计和显示。  </p><p>首先，定义了一个静态的CreateInstance方法，用于创建一个FPSUtils实例。在该方法中，首先判断是否已经存在一个FPSUtils实例，如果存在则直接返回。如果不存在，则创建一个新的GameObject对象，并将FPSUtils组件添加到该对象上。如果指定了parent参数，则将新创建的对象设置为该parent对象的第一个子对象，否则将其设置为不会被销毁的对象。  </p><p>然后，定义了一个静态的DestroyInstance方法，用于销毁FPSUtils实例。在该方法中，首先判断是否存在FPSUtils实例，如果存在则销毁该实例，并将其设置为null。  </p><p>接下来，定义了一些用于FPS统计的变量，包括是否显示FPS、帧数计算刷新时间、当前时间、当前帧数、临时FPS、帧数变化比较容忍度、当前FPS、当前显示的帧数、品质当前时间、品质的容忍度、帧数容忍度、最小FPS容忍度、当前最小帧数等。  </p><p>然后，在Update方法中，每帧更新当前时间、帧数和当前FPS。如果当前时间超过了帧数计算刷新时间，则计算当前显示的帧数，并将临时FPS设置为当前显示的帧数。在计算当前显示的帧数时，如果当前显示的帧数与临时FPS的差值超过了帧数变化比较容忍度，则将临时FPS设置为当前显示的帧数，并将当前时间和帧数重置为0。  </p><p>在FixedUpdate方法中，每帧更新品质当前时间和当前最小帧数。如果当前FPS大于等于最小FPS，则直接返回。如果当前FPS小于最小FPS，则将当前最小帧数加1。如果当前最小帧数超过了帧数容忍度，则可以做一些切换品质的功能。  </p><p>最后，在OnGUI方法中，如果需要显示FPS，则绘制当前显示的帧数。  </p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;&lt;a href=&quot;https://lianbai.icu/2023/08/30/Unity%E6%9D%82%E6%96%87/Unity%E6%9D%82%E6%96%87%E2%80%94%E2%80%94FPS%E8%AE%A1%E7%AE%97%E5%B9%B6%E</summary>
      
    
    
    
    <category term="Unity杂文" scheme="http://yoursite.com/categories/Unity%E6%9D%82%E6%96%87/"/>
    
    
    <category term="Unity" scheme="http://yoursite.com/tags/Unity/"/>
    
  </entry>
  
  <entry>
    <title>Python杂文——Label文字自适应大小</title>
    <link href="http://yoursite.com/2023/08/19/Python%E6%9D%82%E6%96%87/Python%E6%9D%82%E6%96%87%E2%80%94%E2%80%94Label%E6%96%87%E5%AD%97%E8%87%AA%E9%80%82%E5%BA%94%E5%A4%A7%E5%B0%8F/"/>
    <id>http://yoursite.com/2023/08/19/Python%E6%9D%82%E6%96%87/Python%E6%9D%82%E6%96%87%E2%80%94%E2%80%94Label%E6%96%87%E5%AD%97%E8%87%AA%E9%80%82%E5%BA%94%E5%A4%A7%E5%B0%8F/</id>
    <published>2023-08-19T03:50:43.000Z</published>
    <updated>2023-09-28T03:15:25.949Z</updated>
    
    <content type="html"><![CDATA[<p><a href="https://lianbai.icu/2023/08/19/Python%E6%9D%82%E6%96%87/Python%E6%9D%82%E6%96%87%E2%80%94%E2%80%94Label%E6%96%87%E5%AD%97%E8%87%AA%E9%80%82%E5%BA%94%E5%A4%A7%E5%B0%8F/">原文地址</a>  </p><h1 id="PyQt5中Label自适应大小的实现"><a href="#PyQt5中Label自适应大小的实现" class="headerlink" title="PyQt5中Label自适应大小的实现"></a>PyQt5中Label自适应大小的实现</h1><p>在PyQt5中，我们经常需要在widget中添加label来显示文本信息。但是，由于文本长度和label大小的不确定性，有时候会出现文本显示不全或者字体过小难以阅读的问题。本文将介绍如何使用Python编写脚本来实现label的自适应大小。  </p><h1 id="单行文本的自适应"><a href="#单行文本的自适应" class="headerlink" title="单行文本的自适应"></a>单行文本的自适应</h1><p>对于单行文本，我们可以使用以下脚本来实现自适应大小：  </p><pre><code>def AutoSingleLabelFontSize(label):    # 创建一个QFont对象    font = label.font()    # 计算字体大小的范围    fm = QFontMetrics(font)    min_size = 1    max_size = 20    # 二分查找适应的字体大小    low, high = min_size, max_size    while low &lt;= high:        mid = (low + high) // 2        font.setPointSize(mid)        fm = QFontMetrics(font)        rect = fm.boundingRect(label.text())        if rect.width() &lt;= label.width():            low = mid + 1        else:            high = mid - 1    # 设置QLabel的字体    font.setPointSize(high)    label.setFont(font)</code></pre><p>这个脚本使用二分查找来适应字体大小。我们可以通过调整min_size和max_size参数来控制字体大小的范围。  </p><h1 id="多行文本自适应"><a href="#多行文本自适应" class="headerlink" title="多行文本自适应"></a>多行文本自适应</h1><p>对于多行文本，我们可以使用以下脚本来实现自适应大小：  </p><pre><code>def AutoMultipleLabelFontSize(label):    # 创建一个QFont对象    font = label.font()    # 计算字体大小的范围    fm = QFontMetrics(font)    min_size = 1    max_size = 20    # 二分查找适应的字体大小    low, high = min_size, max_size    while low &lt;= high:        mid = (low + high) // 2        font.setPointSize(mid)        fm = QFontMetrics(font)        rect = fm.boundingRect(label.rect(), Qt.TextWordWrap, label.text())        if rect.width() &lt;= label.width() and rect.height() &lt;= label.height():            low = mid + 1        else:            high = mid - 1    # 设置QLabel的字体    font.setPointSize(high)    label.setFont(font)</code></pre><p>这个脚本与单行文本的脚本类似，但是我们可以通过word_wrap参数来控制文本是否自动换行。</p><p>以上就是使用Python编写的label自适应大小的脚本。希望这篇文章能够帮助您解决label大小自适应的问题。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;&lt;a href=&quot;https://lianbai.icu/2023/08/19/Python%E6%9D%82%E6%96%87/Python%E6%9D%82%E6%96%87%E2%80%94%E2%80%94Label%E6%96%87%E5%AD%97%E8%87%</summary>
      
    
    
    
    <category term="Python杂文" scheme="http://yoursite.com/categories/Python%E6%9D%82%E6%96%87/"/>
    
    
    <category term="Python" scheme="http://yoursite.com/tags/Python/"/>
    
  </entry>
  
  <entry>
    <title>解决无法显示图床图片问题</title>
    <link href="http://yoursite.com/2023/08/06/%E6%90%AD%E5%BB%BA%E5%8D%9A%E5%AE%A2/%E8%A7%A3%E5%86%B3%E6%97%A0%E6%B3%95%E6%98%BE%E7%A4%BA%E5%9B%BE%E5%BA%8A%E5%9B%BE%E7%89%87%E9%97%AE%E9%A2%98/"/>
    <id>http://yoursite.com/2023/08/06/%E6%90%AD%E5%BB%BA%E5%8D%9A%E5%AE%A2/%E8%A7%A3%E5%86%B3%E6%97%A0%E6%B3%95%E6%98%BE%E7%A4%BA%E5%9B%BE%E5%BA%8A%E5%9B%BE%E7%89%87%E9%97%AE%E9%A2%98/</id>
    <published>2023-08-06T08:55:00.000Z</published>
    <updated>2023-08-06T09:34:35.657Z</updated>
    
    <content type="html"><![CDATA[<h1 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h1><p>Hexo 博客中使用的图床无法访问，经查询是图床拦截问题，需要修改 referrer 信息。</p><h2 id="解决方法"><a href="#解决方法" class="headerlink" title="解决方法"></a>解决方法</h2><ol><li><p>找到博客文件中的 head.ejs 或 header.ejs，路径一般在 {博客路径}\themes\3-hexo\layout_partial\ 下。</p></li><li><p>在 <head> 标签中添加以下代码：</head></p> <meta name="referrer" content="no-referrer"></li></ol><p>这将禁用 referrer 信息，从而绕过图床的防盗链限制。</p><p><img src="https://gitee.com/lian_bai/img-warehouse/raw/master/BlogImg/1691314374889-2023-8-617:32:55.png" alt="1691314374889-2023-8-617:32:55.png"></p><p>通过以上优化，可以使文章更加清晰易懂，让读者更容易理解和操作。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;问题&quot;&gt;&lt;a href=&quot;#问题&quot; class=&quot;headerlink&quot; title=&quot;问题&quot;&gt;&lt;/a&gt;问题&lt;/h1&gt;&lt;p&gt;Hexo 博客中使用的图床无法访问，经查询是图床拦截问题，需要修改 referrer 信息。&lt;/p&gt;
&lt;h2 id=&quot;解决方法&quot;&gt;&lt;a hr</summary>
      
    
    
    
    <category term="搭建博客" scheme="http://yoursite.com/categories/%E6%90%AD%E5%BB%BA%E5%8D%9A%E5%AE%A2/"/>
    
    
    <category term="博客图床" scheme="http://yoursite.com/tags/%E5%8D%9A%E5%AE%A2%E5%9B%BE%E5%BA%8A/"/>
    
  </entry>
  
  <entry>
    <title>Python杂文——PyCharm配置QT环境</title>
    <link href="http://yoursite.com/2023/08/06/Python%E6%9D%82%E6%96%87/Python%E6%9D%82%E6%96%87%E2%80%94%E2%80%94PyCharm%E9%85%8D%E7%BD%AEQT%E7%8E%AF%E5%A2%83/"/>
    <id>http://yoursite.com/2023/08/06/Python%E6%9D%82%E6%96%87/Python%E6%9D%82%E6%96%87%E2%80%94%E2%80%94PyCharm%E9%85%8D%E7%BD%AEQT%E7%8E%AF%E5%A2%83/</id>
    <published>2023-08-06T07:07:46.000Z</published>
    <updated>2023-09-28T03:14:41.141Z</updated>
    
    <content type="html"><![CDATA[<p><a href="https://lianbai.icu/2023/08/06/Python%E6%9D%82%E6%96%87/Python%E6%9D%82%E6%96%87%E2%80%94%E2%80%94PyCharm%E9%85%8D%E7%BD%AEQT%E7%8E%AF%E5%A2%83/">原文地址</a>  </p><h1 id="安装pyqt5对应的库"><a href="#安装pyqt5对应的库" class="headerlink" title="安装pyqt5对应的库"></a>安装pyqt5对应的库</h1><h2 id="使用PyCharm安装（方法一）"><a href="#使用PyCharm安装（方法一）" class="headerlink" title="使用PyCharm安装（方法一）"></a>使用PyCharm安装（方法一）</h2><h3 id="安装pyqt5库"><a href="#安装pyqt5库" class="headerlink" title="安装pyqt5库"></a>安装pyqt5库</h3><ol><li>打开 PyCharm，进入安装源位置：File -&gt; Settings -&gt; Project -&gt; Python Interpreter  </li><li>在搜索框中输入 pyqt5，点击安装  </li><li>在搜索框中输入 pyqt5-tools，点击安装<br><img src="https://gitee.com/lian_bai/img-warehouse/raw/master/BlogImg/1691306206191-2023-8-615:16:46.png" alt="这是一张图片"></li></ol><h3 id="安装PyQt5-tool库"><a href="#安装PyQt5-tool库" class="headerlink" title="安装PyQt5-tool库"></a>安装PyQt5-tool库</h3><p>同样的方法搜索PyQt5-tool这个库进行安装即可  </p><h2 id="使用终端命令安装（方法二）"><a href="#使用终端命令安装（方法二）" class="headerlink" title="使用终端命令安装（方法二）"></a>使用终端命令安装（方法二）</h2><p>在终端中输入以下两个命令即可安装对应的库：</p><pre><code>pip install pyqt5  pip install pyqt5-tool  </code></pre><h1 id="配置QT-Designer-界面设计-和PyUIC-界面转代码"><a href="#配置QT-Designer-界面设计-和PyUIC-界面转代码" class="headerlink" title="配置QT Designer(界面设计)和PyUIC(界面转代码)"></a>配置QT Designer(界面设计)和PyUIC(界面转代码)</h1><h2 id="配置QT-Designer"><a href="#配置QT-Designer" class="headerlink" title="配置QT Designer"></a>配置QT Designer</h2><ol><li>打开 PyCharm，进入配置位置：File -&gt; Settings -&gt; Tools -&gt; External Tools</li><li>点击“+”号，弹出一个编辑配置的框</li><li>配置扩展工具的参数：  <blockquote><p><strong>Name</strong>: 自己随便起个名字，一般都是<strong>QT Designer</strong><br><strong>Program</strong>: designer.exe软件所在的路径，路径位置一般为：{python路径}\Lib\site-packages\qt5_applications\Qt\bin\designer.exe<br><strong>Working directory</strong>: <strong>$FileDir$</strong></p></blockquote></li></ol><p><img src="https://gitee.com/lian_bai/img-warehouse/raw/master/BlogImg/1691307145291-2023-8-615:32:26.png" alt="这是一张图片"></p><h2 id="配置PyUIC"><a href="#配置PyUIC" class="headerlink" title="配置PyUIC"></a>配置PyUIC</h2><ol><li>打开 PyCharm，进入配置位置：File -&gt; Settings -&gt; Tools -&gt; External Tools</li><li>点击“+”号，弹出一个编辑配置的框</li><li>配置扩展工具的参数： <blockquote><p>Name: 自己随便起个名字，一般都是<strong>配置PyUIC</strong><br> Program: python.exe软件所在的路径，路径位置一般为：{python路径}\python.exe<br> Arguments: $FileName$ -o $FileNameWithoutExtension$.py<br> Working directory: <strong>$FileDir$</strong></p></blockquote></li></ol><p><img src="https://gitee.com/lian_bai/img-warehouse/raw/master/BlogImg/1691375885378-2023-8-710:38:06.png" alt="1691375885378-2023-8-710:38:06.png"></p><h1 id="使用配置"><a href="#使用配置" class="headerlink" title="使用配置"></a>使用配置</h1><p><img src="https://gitee.com/lian_bai/img-warehouse/raw/master/BlogImg/1691307415934-2023-8-615:36:56.png" alt="这是一张图片">  </p><h2 id="制作界面"><a href="#制作界面" class="headerlink" title="制作界面"></a>制作界面</h2><p>点击上图编号①就可以打开编辑界面 UI 的编辑器了，也可以使用快捷键 Ctrl + Alt + Shift + D<br><img src="https://gitee.com/lian_bai/img-warehouse/raw/master/BlogImg/1691307518635-2023-8-615:38:39.png" alt="这是一张图片">  </p><h2 id="生成代码"><a href="#生成代码" class="headerlink" title="生成代码"></a>生成代码</h2><p>制作好界面后，在 PyCharm 中选中刚才制作的 UI 文件，右键 -&gt; External Tools -&gt; PyUIC 即可生成对应的代码。<br><img src="https://gitee.com/lian_bai/img-warehouse/raw/master/BlogImg/1691307659438-2023-8-615:41:00.png" alt="这是一张图片">  </p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;&lt;a href=&quot;https://lianbai.icu/2023/08/06/Python%E6%9D%82%E6%96%87/Python%E6%9D%82%E6%96%87%E2%80%94%E2%80%94PyCharm%E9%85%8D%E7%BD%AEQT%E7</summary>
      
    
    
    
    <category term="Python杂文" scheme="http://yoursite.com/categories/Python%E6%9D%82%E6%96%87/"/>
    
    
    <category term="Python" scheme="http://yoursite.com/tags/Python/"/>
    
  </entry>
  
  <entry>
    <title>Unity杂文——宏管理脚本</title>
    <link href="http://yoursite.com/2023/05/21/Unity%E6%9D%82%E6%96%87/Unity%E6%9D%82%E6%96%87%E2%80%94%E2%80%94%E5%AE%8F%E7%AE%A1%E7%90%86%E8%84%9A%E6%9C%AC/"/>
    <id>http://yoursite.com/2023/05/21/Unity%E6%9D%82%E6%96%87/Unity%E6%9D%82%E6%96%87%E2%80%94%E2%80%94%E5%AE%8F%E7%AE%A1%E7%90%86%E8%84%9A%E6%9C%AC/</id>
    <published>2023-05-21T13:56:48.000Z</published>
    <updated>2023-09-28T03:07:59.413Z</updated>
    
    <content type="html"><![CDATA[<p><a href="https://lianbai.icu/2023/05/21/Unity%E6%9D%82%E6%96%87/Unity%E6%9D%82%E6%96%87%E2%80%94%E2%80%94%E5%AE%8F%E7%AE%A1%E7%90%86%E8%84%9A%E6%9C%AC/">原文地址</a>  </p><h1 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h1><p>这是一个管理Unity宏设置的脚本工具，能快速的增删改查项目的脚本。<br>下面是效果图：<br><img src="https://gitee.com/lian_bai/img-warehouse/raw/master/BlogImg/1684677675196-2023-5-2122:01:15.png"></p><h1 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h1><h2 id="依赖的代码"><a href="#依赖的代码" class="headerlink" title="依赖的代码"></a>依赖的代码</h2><details>    <summary> ResCatalog </summary>  <pre><code>/// &lt;summary&gt;/// 资源路径/// &lt;/summary&gt;public static class ResCatalog&#123;    public const string DefaultRes = &quot;Library/unity default resources&quot;;    public const string Temporary = &quot;Assets/_Temporary&quot;;    public const string Library = &quot;Assets/_Library&quot;;&#125;</code></pre></details>  <details>    <summary> BuildTargetUtility </summary>  <pre><code>/// &lt;summary&gt;/// 构建的辅助工具/// &lt;/summary&gt;public class BuildTargetUtility&#123;    /// &lt;summary&gt;    /// 获取对应平台的组    /// &lt;/summary&gt;    /// &lt;param name=&quot;p&quot;&gt;&lt;/param&gt;    /// &lt;returns&gt;&lt;/returns&gt;    public static BuildTargetGroup PlatformToGroup(RuntimePlatform p)    &#123;        return p switch        &#123;            RuntimePlatform.OSXEditor =&gt; BuildTargetGroup.Standalone,            RuntimePlatform.OSXPlayer =&gt; BuildTargetGroup.Standalone,            RuntimePlatform.WindowsPlayer =&gt; BuildTargetGroup.Standalone,            RuntimePlatform.WindowsEditor =&gt; BuildTargetGroup.Standalone,            RuntimePlatform.IPhonePlayer =&gt; BuildTargetGroup.iOS,            RuntimePlatform.Android =&gt; BuildTargetGroup.Android,            RuntimePlatform.LinuxPlayer =&gt; BuildTargetGroup.Standalone,            RuntimePlatform.LinuxEditor =&gt; BuildTargetGroup.Standalone,            RuntimePlatform.WebGLPlayer =&gt; BuildTargetGroup.WebGL,            RuntimePlatform.PS4 =&gt; BuildTargetGroup.PS4,            RuntimePlatform.XboxOne =&gt; BuildTargetGroup.XboxOne,            RuntimePlatform.tvOS =&gt; BuildTargetGroup.tvOS,            RuntimePlatform.Switch =&gt; BuildTargetGroup.Switch,            _ =&gt; BuildTargetGroup.Unknown        &#125;;    &#125;&#125;</code></pre></details> <details>    <summary> AssetLibrary </summary>  <pre><code>/// &lt;summary&gt;/// 资源的Library/// &lt;/summary&gt;public static class AssetLibrary&#123;    /// &lt;summary&gt;    /// 判断文件是否存在    /// &lt;/summary&gt;    /// &lt;param name=&quot;path&quot;&gt;&lt;/param&gt;    /// &lt;param name=&quot;temporary&quot;&gt;&lt;/param&gt;    /// &lt;returns&gt;&lt;/returns&gt;    public static bool Exists(string path, bool temporary)    &#123;        return File.Exists(GetPath(path, temporary));    &#125;    /// &lt;summary&gt;    /// 获取文件的地址    /// &lt;/summary&gt;    /// &lt;param name=&quot;path&quot;&gt;文件名字&lt;/param&gt;    /// &lt;param name=&quot;temporary&quot;&gt;是否是临时文件&lt;/param&gt;    /// &lt;returns&gt;&lt;/returns&gt;    public static string GetPath(string path, bool temporary)    &#123;        return Path.Combine(temporary ? ResCatalog.Temporary : ResCatalog.Library, path);    &#125;    /// &lt;summary&gt;    /// 保存文件    /// &lt;/summary&gt;    /// &lt;param name=&quot;path&quot;&gt;文件地址&lt;/param&gt;    /// &lt;param name=&quot;text&quot;&gt;文件内容&lt;/param&gt;    /// &lt;param name=&quot;temporary&quot;&gt;是否是临时文件&lt;/param&gt;    public static void Save(string path, string text, bool temporary)    &#123;        WriteAllText(path, text, temporary);    &#125;    /// &lt;summary&gt;    /// 保存文件    /// &lt;/summary&gt;    /// &lt;param name=&quot;path&quot;&gt;文件地址&lt;/param&gt;    /// &lt;param name=&quot;bytes&quot;&gt;写入的字节&lt;/param&gt;    /// &lt;param name=&quot;temporary&quot;&gt;是否是临时资源&lt;/param&gt;    public static void Save(string path, byte[] bytes, bool temporary)    &#123;        WriteAllBytes(path, bytes, temporary);    &#125;    /// &lt;summary&gt;    /// 获取文件内的所有内容    /// &lt;/summary&gt;    /// &lt;param name=&quot;path&quot;&gt;文件地址&lt;/param&gt;    /// &lt;param name=&quot;temporary&quot;&gt;是否是临时文件&lt;/param&gt;    /// &lt;returns&gt;&lt;/returns&gt;    public static string GetString(string path, bool temporary)    &#123;        return File.ReadAllText(GetPath(path, temporary));    &#125;    /// &lt;summary&gt;    /// 获取文件的所有字节    /// &lt;/summary&gt;    /// &lt;param name=&quot;path&quot;&gt;文件地址&lt;/param&gt;    /// &lt;param name=&quot;temporary&quot;&gt;是否是临时资源&lt;/param&gt;    /// &lt;returns&gt;&lt;/returns&gt;    public static byte[] GetBytes(string path, bool temporary)    &#123;        return File.ReadAllBytes(GetPath(path, temporary));    &#125;    /// &lt;summary&gt;    /// 写入所有的字节    /// &lt;/summary&gt;    /// &lt;param name=&quot;path&quot;&gt;文件路径&lt;/param&gt;    /// &lt;param name=&quot;bytes&quot;&gt;写入字节&lt;/param&gt;    /// &lt;param name=&quot;temporary&quot;&gt;是否是临时资源&lt;/param&gt;    private static void WriteAllBytes(string path, byte[] bytes, bool temporary)    &#123;        var realPath = GetPath(path, temporary);        PathUtils.MakeDirectory(realPath);        File.WriteAllBytes(realPath, bytes);    &#125;    /// &lt;summary&gt;    /// 写入所有的文本内容    /// &lt;/summary&gt;    /// &lt;param name=&quot;path&quot;&gt;文件路径&lt;/param&gt;    /// &lt;param name=&quot;text&quot;&gt;写入字符串&lt;/param&gt;    /// &lt;param name=&quot;temporary&quot;&gt;是否是临时资源&lt;/param&gt;    private static void WriteAllText(string path, string text, bool temporary)    &#123;        var realPath = GetPath(path, temporary);        PathUtils.MakeDirectory(realPath);        File.WriteAllText(realPath, text);    &#125;&#125;</code></pre></details>  <details>    <summary> SymbolsUtility </summary>  <pre><code>/// &lt;summary&gt;/// 宏的辅助工具/// &lt;/summary&gt;public static class SymbolsUtility&#123;    /// &lt;summary&gt;    /// 设置宏    /// &lt;/summary&gt;    /// &lt;param name=&quot;symbols&quot;&gt;&lt;/param&gt;    public static void SetSymbols(params string[] symbols)    &#123;        var result = string.Join(&quot;;&quot;, symbols);        PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Standalone, result);        PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.iOS, result);        PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Android, result);    &#125;    /// &lt;summary&gt;    /// 获取所有的宏    /// &lt;/summary&gt;    /// &lt;returns&gt;&lt;/returns&gt;    public static string[] GetSymbols()    &#123;        var group = BuildTargetUtility.PlatformToGroup(Application.platform);        var symbols = PlayerSettings.GetScriptingDefineSymbolsForGroup(group);        return symbols.Split(&#39;;&#39;);    &#125;    /// &lt;summary&gt;    /// 增加宏    /// &lt;/summary&gt;    /// &lt;param name=&quot;symbols&quot;&gt;宏的组&lt;/param&gt;    public static void AddSymbols(params string[] symbols)    &#123;        SetSymbols(GetSymbols().Union(symbols).ToArray());    &#125;    /// &lt;summary&gt;    /// 移除宏    /// &lt;/summary&gt;    /// &lt;param name=&quot;symbols&quot;&gt;宏的组&lt;/param&gt;    public static void RemoveSymbols(params string[] symbols)    &#123;        SetSymbols(GetSymbols().Except(symbols).ToArray());    &#125;    /// &lt;summary&gt;    /// 判断是否拥有宏    /// &lt;/summary&gt;    /// &lt;param name=&quot;symbol&quot;&gt;宏的名字&lt;/param&gt;    /// &lt;returns&gt;&lt;/returns&gt;    public static bool HasSymbols(string symbol)    &#123;        return -1 != Array.IndexOf(GetSymbols(), symbol);    &#125;&#125;</code></pre></details> <h2 id="管理工具代码"><a href="#管理工具代码" class="headerlink" title="管理工具代码"></a>管理工具代码</h2><pre><code>internal class SymbolsManager : EditorWindow&#123;    /// &lt;summary&gt;    /// 宏的配置    /// &lt;/summary&gt;    public class SymbolsConfig    &#123;        /// &lt;summary&gt;        /// 宏的名字        /// &lt;/summary&gt;        public string name;        /// &lt;summary&gt;        /// 是否使用        /// &lt;/summary&gt;        [NonSerialized]        public bool used;    &#125;    /// &lt;summary&gt;    /// 宏的组    /// &lt;/summary&gt;    public class SymbolsGroup    &#123;        /// &lt;summary&gt;        /// 组的名字        /// &lt;/summary&gt;        public string name;        /// &lt;summary&gt;        /// 组的宏列表        /// &lt;/summary&gt;        public List&lt;SymbolsConfig&gt; list;        /// &lt;summary&gt;        /// 用于显示的List        /// &lt;/summary&gt;        [NonSerialized]        public ReorderableList reorderableList;        /// &lt;summary&gt;        /// 是否已经删除        /// &lt;/summary&gt;        [NonSerialized]        public bool deleted;    &#125;    /// &lt;summary&gt;    /// 组的列表    /// &lt;/summary&gt;    private List&lt;SymbolsGroup&gt; m_Groups;    /// &lt;summary&gt;    /// 宏管理窗口    /// &lt;/summary&gt;    [MenuItem(&quot;Tools/SymbolsManager&quot;, false)]    private static void OpenWindow()    &#123;        var window = GetWindow&lt;SymbolsManager&gt;(&quot;SymbolsManager&quot;);        window.minSize = new Vector2(350, 100);    &#125;    private void OnEnable()    &#123;        var path = GetSettingsFileName();        if (AssetLibrary.Exists(path, false))        &#123;            var text = AssetLibrary.GetString(path, false);            JsonUtils.ToObject(text, out m_Groups);            foreach (var group in m_Groups)            &#123;                if (null == group.list)                &#123;                    group.list = new List&lt;SymbolsConfig&gt;();                &#125;                else                &#123;                    foreach (var config in group.list)                    &#123;                        config.used = SymbolsUtility.HasSymbols(config.name);                    &#125;                &#125;            &#125;        &#125;        m_Groups ??= new List&lt;SymbolsGroup&gt;();    &#125;    /// &lt;summary&gt;    /// 绘制面板    /// &lt;/summary&gt;    private void OnGUI()    &#123;        for (var i = 0; i &lt; m_Groups.Count;)        &#123;            var group = m_Groups[i];            GUILayout.BeginVertical(EditorStyles.helpBox);            &#123;                if (null == group.reorderableList)                &#123;                    group.list ??= new List&lt;SymbolsConfig&gt;();                    group.reorderableList = new ReorderableList(group.list, typeof(SymbolsConfig))                    &#123;                        drawHeaderCallback = rect =&gt;                        &#123;                            var textRt = rect;                            textRt.width /= 2f;                            group.name = EditorGUI.TextField(textRt, group.name, EditorStyles.boldLabel);                            var btnRt = rect;                            btnRt.x = btnRt.xMax - Styles.closeButton.lineHeight;                            if (GUI.Button(btnRt, GUIContent.none, Styles.closeButton))                            &#123;                                if (EditorUtility.DisplayDialog(                                    &quot;提示&quot;, &quot;是否确认删除?&quot;, &quot;确认&quot;, &quot;取消&quot;))                                &#123;                                    group.deleted = true;                                &#125;                            &#125;                        &#125;,                        drawElementCallback = (rect, index, active, focused) =&gt;                        &#123;                            var item = group.list[index];                            var textRect = new Rect(                                rect.position + Vector2.up * 2,                                new Vector2(rect.width - 100, EditorGUIUtility.singleLineHeight));                            item.name = GUI.TextField(textRect, item.name)?.Trim();                            const int btnWidth = 80;                            var oldColor = GUI.color;                            GUI.color = SymbolsUtility.HasSymbols(item.name) ? Color.green : Color.red;                            var btnRect = new Rect(                                rect.position + Vector2.up + Vector2.right * (rect.width - btnWidth),                                new Vector2(btnWidth, EditorGUIUtility.singleLineHeight));                            item.used = GUI.Toggle(btnRect, item.used,                                item.used ? Styles.used : Styles.unused,                                EditorStyles.toolbarButton);                            GUI.color = oldColor;                        &#125;,                        elementHeight = 22,                    &#125;;                &#125;                group.reorderableList.DoLayoutList();                GUILayout.BeginHorizontal();                GUILayout.FlexibleSpace();                if (GUILayout.Button(Styles.apply, GUILayout.Height(30), GUILayout.Width(100)))                &#123;                    foreach (var config in group.list)                    &#123;                        if (config.used)                        &#123;                            SymbolsUtility.AddSymbols(config.name);                        &#125;                        else                        &#123;                            SymbolsUtility.RemoveSymbols(config.name);                        &#125;                    &#125;                    AssetDatabase.SaveAssets();                    AssetDatabase.Refresh();                &#125;                GUILayout.FlexibleSpace();                GUILayout.EndHorizontal();                GUILayout.Space(5);            &#125;            GUILayout.EndVertical();            if (group.deleted)            &#123;                m_Groups.RemoveAt(i);            &#125;            else            &#123;                ++i;            &#125;        &#125;        //        GUILayout.BeginHorizontal();        GUILayout.FlexibleSpace();        if (GUILayout.Button(Styles.addGroup, GUILayout.Height(30), GUILayout.Width(200)))        &#123;            m_Groups.Add(new SymbolsGroup            &#123;                name = Styles.customName,                list = new List&lt;SymbolsConfig&gt;()            &#125;);        &#125;        GUILayout.FlexibleSpace();        GUILayout.EndHorizontal();        var e = Event.current;        if (e.keyCode == KeyCode.S &amp;&amp; e.type == EventType.KeyUp)        &#123;            e.Use();            Save();        &#125;    &#125;    /// &lt;summary&gt;    /// 销毁的时候保存    /// &lt;/summary&gt;    private void OnDisable()    &#123;        Save();    &#125;    /// &lt;summary&gt;    /// 保存    /// &lt;/summary&gt;    private void Save()    &#123;        JsonResolver.NotSerializeDefault = true;        var text = JsonUtils.ToPrettyString(m_Groups);        AssetLibrary.Save(GetSettingsFileName(), text, false);        JsonResolver.NotSerializeDefault = false;    &#125;    /// &lt;summary&gt;    /// 序列化Json文件的名字，采用 “类名Settings.json”格式    /// &lt;/summary&gt;    /// &lt;returns&gt;&lt;/returns&gt;    private string GetSettingsFileName()    &#123;        return $&quot;&#123;GetType().Name&#125;Settings.json&quot;;    &#125;    #region 显示风格Style    /// &lt;summary&gt;    /// 编辑器的风格    /// &lt;/summary&gt;    private static class Styles    &#123;        /// &lt;summary&gt;        /// 已经使用        /// &lt;/summary&gt;        public static GUIContent used;        /// &lt;summary&gt;        /// 未使用        /// &lt;/summary&gt;        public static GUIContent unused;        /// &lt;summary&gt;        /// 应用        /// &lt;/summary&gt;        public static GUIContent apply;        /// &lt;summary&gt;        /// 新增分组        /// &lt;/summary&gt;        public static GUIContent addGroup;        /// &lt;summary&gt;        /// 自定义名称(点击修改)        /// &lt;/summary&gt;        public static string customName;        /// &lt;summary&gt;        /// WinBtnClose        /// &lt;/summary&gt;        public static GUIStyle closeButton;        static Styles()        &#123;            used = new GUIContent(&quot;已使用&quot;);            unused = new GUIContent(&quot;未使用&quot;);            apply = new GUIContent(&quot;应用&quot;);            addGroup = new GUIContent(&quot;新增分组&quot;);            customName = &quot;自定义名称(点击修改)&quot;;            closeButton = new GUIStyle(&quot;WinBtnClose&quot;);        &#125;    &#125;    #endregion&#125;</code></pre>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;&lt;a href=&quot;https://lianbai.icu/2023/05/21/Unity%E6%9D%82%E6%96%87/Unity%E6%9D%82%E6%96%87%E2%80%94%E2%80%94%E5%AE%8F%E7%AE%A1%E7%90%86%E8%8</summary>
      
    
    
    
    <category term="Unity杂文" scheme="http://yoursite.com/categories/Unity%E6%9D%82%E6%96%87/"/>
    
    
  </entry>
  
  <entry>
    <title>Unity杂文——脚本创建辅助ScriptBuilder</title>
    <link href="http://yoursite.com/2023/05/17/Unity%E6%9D%82%E6%96%87/Unity%E6%9D%82%E6%96%87%E2%80%94%E2%80%94%E8%84%9A%E6%9C%AC%E5%88%9B%E5%BB%BA%E8%BE%85%E5%8A%A9ScriptBuilder/"/>
    <id>http://yoursite.com/2023/05/17/Unity%E6%9D%82%E6%96%87/Unity%E6%9D%82%E6%96%87%E2%80%94%E2%80%94%E8%84%9A%E6%9C%AC%E5%88%9B%E5%BB%BA%E8%BE%85%E5%8A%A9ScriptBuilder/</id>
    <published>2023-05-16T16:38:43.000Z</published>
    <updated>2023-09-28T03:06:40.723Z</updated>
    
    <content type="html"><![CDATA[<p><a href="https://lianbai.icu/2023/05/17/Unity%E6%9D%82%E6%96%87/Unity%E6%9D%82%E6%96%87%E2%80%94%E2%80%94%E8%84%9A%E6%9C%AC%E5%88%9B%E5%BB%BA%E8%BE%85%E5%8A%A9ScriptBuilder/">原文地址</a>  </p><h1 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h1><p>一个辅助快速生成需要的C#脚本的脚本。主要是快速生成引用，命名空间，类和方法一些。</p><h1 id="脚本和解析"><a href="#脚本和解析" class="headerlink" title="脚本和解析"></a>脚本和解析</h1><pre><code>public class ScriptBuilder&#123;    /// &lt;summary&gt;    /// 脚本的字符串    /// &lt;/summary&gt;    private StringBuilder m_Builder = new StringBuilder();    /// &lt;summary&gt;    /// 空白地字符串    /// &lt;/summary&gt;    private StringBuilder m_Blank = new StringBuilder();    /// &lt;summary&gt;    /// 用于判断是否在方法内生成脚本    /// &lt;/summary&gt;    public bool MarkInMethod &#123; get; set; &#125;    /// &lt;summary&gt;    /// 增加一行数据    /// &lt;/summary&gt;    /// &lt;param name=&quot;message&quot;&gt;&lt;/param&gt;    public void AppendLine(string message)    &#123;        m_Builder.Append(m_Blank);        m_Builder.AppendLine(message);    &#125;    /// &lt;summary&gt;    /// 开始增加空白    /// &lt;/summary&gt;    private void BeginBlank()    &#123;        m_Blank.Append(&#39;\t&#39;);    &#125;    /// &lt;summary&gt;    /// 结束增加空白    /// &lt;/summary&gt;    private void EndBlank()    &#123;        m_Blank.Remove(m_Blank.Length - 1, 1);    &#125;    /// &lt;summary&gt;    ///开始增加命名空间    /// &lt;/summary&gt;    /// &lt;param name=&quot;name&quot;&gt;命名空间的名字&lt;/param&gt;    public void BeginNamespace(string name)    &#123;        BeginBrace($&quot;namespace &#123;name&#125;&quot;);    &#125;    /// &lt;summary&gt;    /// 结束增加命名空间    /// &lt;/summary&gt;    public void EndNamespace()    &#123;        EndBrace();    &#125;    /// &lt;summary&gt;    /// 开始增加类    /// &lt;/summary&gt;    /// &lt;param name=&quot;modifier&quot;&gt;类的修饰字符串&lt;/param&gt;    /// &lt;param name=&quot;className&quot;&gt;类的名字&lt;/param&gt;    /// &lt;param name=&quot;superclass&quot;&gt;继承类的名字&lt;/param&gt;    public void BeginClass(string modifier, string className, string superclass)    &#123;        BeginBrace($&quot;public &#123;modifier&#125; class &#123;className&#125; : &#123;superclass&#125;&quot;);    &#125;    /// &lt;summary&gt;    /// 开始增加类    /// &lt;/summary&gt;    /// &lt;param name=&quot;modifier&quot;&gt;类的修饰字符串&lt;/param&gt;    /// &lt;param name=&quot;className&quot;&gt;类的名字&lt;/param&gt;    public void BeginClass(string modifier, string className)    &#123;        BeginBrace($&quot;public &#123;modifier&#125; class &#123;className&#125;&quot;);    &#125;    /// &lt;summary&gt;    /// 开始增加类    /// &lt;/summary&gt;    /// &lt;param name=&quot;className&quot;&gt;类的名字&lt;/param&gt;    public void BeginClass(string className)    &#123;        BeginBrace($&quot;public class &#123;className&#125;&quot;);    &#125;    /// &lt;summary&gt;    /// 结束类    /// &lt;/summary&gt;    public void EndClass()    &#123;        EndBrace();    &#125;    /// &lt;summary&gt;    /// 开始增加方法    /// &lt;/summary&gt;    /// &lt;param name=&quot;method&quot;&gt;方法名&lt;/param&gt;    /// &lt;param name=&quot;permission&quot;&gt;访问权限&lt;/param&gt;    /// &lt;param name=&quot;returnType&quot;&gt;返回类型&lt;/param&gt;    /// &lt;param name=&quot;modifier&quot;&gt;修饰符&lt;/param&gt;    public void BeginMethod(string method, string permission = &quot;public&quot;, string returnType = &quot;void&quot;,        string modifier = &quot;&quot;)    &#123;        MarkInMethod = true;        BeginBrace(string.IsNullOrEmpty(modifier)            ? $&quot;&#123;permission&#125; &#123;returnType&#125; &#123;method&#125;&quot;            : $&quot;&#123;permission&#125; &#123;modifier&#125; &#123;returnType&#125; &#123;method&#125;&quot;);    &#125;    /// &lt;summary&gt;    /// 结束方法    /// &lt;/summary&gt;    public void EndMethod()    &#123;        EndBrace();        MarkInMethod = false;    &#125;    /// &lt;summary&gt;    /// 开始属性    /// &lt;/summary&gt;    /// &lt;param name=&quot;property&quot;&gt;属性名字&lt;/param&gt;    public void BeginProperty(string property)    &#123;        BeginBrace(property);    &#125;    /// &lt;summary&gt;    /// 结束属性    /// &lt;/summary&gt;    public void EndProperty()    &#123;        EndBrace();    &#125;    /// &lt;summary&gt;    /// 大括号的开始    /// &lt;/summary&gt;    /// &lt;param name=&quot;code&quot;&gt;&lt;/param&gt;    public void BeginBrace(string code)    &#123;        AppendLine($&quot;&#123;code&#125;&quot;);        AppendLine(&quot;&#123;&quot;);        BeginBlank();    &#125;    /// &lt;summary&gt;    /// 结束大括号    /// &lt;/summary&gt;    public void EndBrace()    &#123;        EndBlank();        AppendLine(&quot;&#125;&quot;);    &#125;    /// &lt;summary&gt;    /// 开始字段    /// &lt;/summary&gt;    /// &lt;param name=&quot;code&quot;&gt;Field的名字&lt;/param&gt;    public void BeginField(string code)    &#123;        AppendLine($&quot;&#123;code&#125;&quot;);        AppendLine(&quot;&#123;&quot;);        BeginBlank();    &#125;    /// &lt;summary&gt;    /// 结束字段    /// &lt;/summary&gt;    public void EndField()    &#123;        EndBlank();        AppendLine(&quot;&#125;;&quot;);    &#125;    /// &lt;summary&gt;    /// 开始Region    /// &lt;/summary&gt;    /// &lt;param name=&quot;name&quot;&gt;Region的名字&lt;/param&gt;    public void BeginRegion(string name)    &#123;        AppendLine($&quot;#region &#123;name&#125;&quot;);    &#125;    /// &lt;summary&gt;    /// 结束Region    /// &lt;/summary&gt;    public void EndRegion()    &#123;        AppendLine(&quot;#endregion&quot;);    &#125;    /// &lt;summary&gt;    /// 转成字符串    /// &lt;/summary&gt;    /// &lt;returns&gt;&lt;/returns&gt;    public override string ToString()    &#123;        return m_Builder.ToString();    &#125;&#125;</code></pre><h1 id="用法"><a href="#用法" class="headerlink" title="用法"></a>用法</h1><p>上述方法就是辅助的脚本，下面是举例调用方法作为参考  </p><pre><code>// 获取类脚本的路径var scriptPath = AssetDatabase.GUIDToAssetPath(kGUID);var sb = new ScriptBuilder();sb.BeginNamespace(typeof(StorageKeyConst).Namespace);sb.BeginClass(&quot;static&quot;, &quot;StorageKeyConst&quot;);if (DataStorageEditorTools.Instance.DataStorageEditorDataRootList.Count &gt; 0)&#123;    sb.BeginRegion(&quot;RootKeys&quot;);    sb.AppendLine(string.Empty);    AddListScripts(sb, DataStorageEditorTools.Instance.DataStorageEditorDataRootList);    sb.AppendLine(string.Empty);    sb.EndRegion();    sb.AppendLine(string.Empty);&#125;if (DataStorageEditorTools.Instance.DataStorageEditorDataList.Count &gt; 0)&#123;    sb.BeginRegion(&quot;Keys&quot;);    sb.AppendLine(string.Empty);    AddListScripts(sb, DataStorageEditorTools.Instance.DataStorageEditorDataList);    sb.AppendLine(string.Empty);    sb.EndRegion();&#125;sb.EndClass();sb.EndNamespace();File.WriteAllText(scriptPath, sb.ToString(), Encoding.UTF8);AssetDatabase.SaveAssets();AssetDatabase.Refresh();</code></pre>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;&lt;a href=&quot;https://lianbai.icu/2023/05/17/Unity%E6%9D%82%E6%96%87/Unity%E6%9D%82%E6%96%87%E2%80%94%E2%80%94%E8%84%9A%E6%9C%AC%E5%88%9B%E5%B</summary>
      
    
    
    
    <category term="Unity杂文" scheme="http://yoursite.com/categories/Unity%E6%9D%82%E6%96%87/"/>
    
    
  </entry>
  
  <entry>
    <title>Unity杂文——阿拉伯数字转罗马数字</title>
    <link href="http://yoursite.com/2022/12/29/Unity%E6%9D%82%E6%96%87/Unity%E6%9D%82%E6%96%87%E2%80%94%E2%80%94%E9%98%BF%E6%8B%89%E4%BC%AF%E6%95%B0%E5%AD%97%E8%BD%AC%E7%BD%97%E9%A9%AC%E6%95%B0%E5%AD%97/"/>
    <id>http://yoursite.com/2022/12/29/Unity%E6%9D%82%E6%96%87/Unity%E6%9D%82%E6%96%87%E2%80%94%E2%80%94%E9%98%BF%E6%8B%89%E4%BC%AF%E6%95%B0%E5%AD%97%E8%BD%AC%E7%BD%97%E9%A9%AC%E6%95%B0%E5%AD%97/</id>
    <published>2022-12-29T09:40:31.000Z</published>
    <updated>2023-06-03T05:16:35.115Z</updated>
    
    <content type="html"><![CDATA[<h1 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h1><p>在游戏开发中，美术人员有时需要将一些数字转换为罗马数字。本文介绍了一种将阿拉伯数字转换为罗马数字的方法，可以帮助美术人员快速完成转换。如果您是游戏开发人员或对数字转换感兴趣，本文也会对您有所帮助。 </p><h1 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h1><pre><code>/// &lt;summary&gt;/// 数字转罗马数字/// &lt;/summary&gt;/// &lt;param name=&quot;num&quot;&gt;&lt;/param&gt;/// &lt;returns&gt;&lt;/returns&gt;public static string IntToRoman(int num)&#123;    var res = string.Empty;    var val = new List&lt;int&gt; &#123; 1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1 &#125;;    var str = new List&lt;string&gt; &#123; &quot;M&quot;, &quot;CM&quot;, &quot;D&quot;, &quot;CD&quot;, &quot;C&quot;, &quot;XC&quot;, &quot;L&quot;, &quot;XL&quot;, &quot;X&quot;, &quot;IX&quot;, &quot;V&quot;, &quot;IV&quot;, &quot;I&quot; &#125;;    for(var i = 0;i &lt; val.Count; ++i)    &#123;        while(num &gt;= val[i])        &#123;            num -= val[i];            res += str[i];        &#125;    &#125;    return res;&#125;</code></pre>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;简介&quot;&gt;&lt;a href=&quot;#简介&quot; class=&quot;headerlink&quot; title=&quot;简介&quot;&gt;&lt;/a&gt;简介&lt;/h1&gt;&lt;p&gt;在游戏开发中，美术人员有时需要将一些数字转换为罗马数字。本文介绍了一种将阿拉伯数字转换为罗马数字的方法，可以帮助美术人员快速完成转换。如果您</summary>
      
    
    
    
    <category term="Unity杂文" scheme="http://yoursite.com/categories/Unity%E6%9D%82%E6%96%87/"/>
    
    
    <category term="字符串处理" scheme="http://yoursite.com/tags/%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%A4%84%E7%90%86/"/>
    
  </entry>
  
  <entry>
    <title>Unity杂文——编辑器Foldout右键菜单</title>
    <link href="http://yoursite.com/2022/12/28/Unity%E6%9D%82%E6%96%87/Unity%E6%9D%82%E6%96%87%E2%80%94%E2%80%94%E7%BC%96%E8%BE%91%E5%99%A8Foldout%E5%8F%B3%E9%94%AE%E8%8F%9C%E5%8D%95/"/>
    <id>http://yoursite.com/2022/12/28/Unity%E6%9D%82%E6%96%87/Unity%E6%9D%82%E6%96%87%E2%80%94%E2%80%94%E7%BC%96%E8%BE%91%E5%99%A8Foldout%E5%8F%B3%E9%94%AE%E8%8F%9C%E5%8D%95/</id>
    <published>2022-12-28T03:09:37.000Z</published>
    <updated>2023-09-28T03:04:25.420Z</updated>
    
    <content type="html"><![CDATA[<p><a href="https://lianbai.icu/2022/12/28/Unity%E6%9D%82%E6%96%87/Unity%E6%9D%82%E6%96%87%E2%80%94%E2%80%94%E7%BC%96%E8%BE%91%E5%99%A8Foldout%E5%8F%B3%E9%94%AE%E8%8F%9C%E5%8D%95/">原文地址</a>  </p><h1 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h1><p>在用编辑器开发工具的时候，我们经常会用到折叠的Foldout，这里就不对Foldout做详细的介绍了，本文主要分享如何对Foldout做一个扩展，笔者在开发中常常需要的一个右键菜单的功能，这样我们就可以添加很多功能而不需要增加按钮，右键选择对应的菜单就行了。  </p><h1 id="演示"><a href="#演示" class="headerlink" title="演示"></a>演示</h1><p><img src="https://gitee.com/lian_bai/img-warehouse/raw/master/BlogImg/FoldoutRightClick-2022-12-2811:17:09.gif"></p><h1 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h1><pre><code>var rect = GUILayoutUtility.GetRect(EditorGUIUtility.fieldWidth, EditorGUIUtility.fieldWidth, 18f, 18f,    EditorStyles.foldout);m_IsFoldout = EditorGUI.Foldout(rect, m_IsFoldout, &quot;标题&quot;);CreateNewGenericMenu(rect, new List&lt;string&gt; &#123; &quot;方法一&quot;, &quot;方法二&quot;, &quot;方法三&quot; &#125;, new List&lt;Action&gt;&#123;    (() =&gt; &#123;Debug.LogError(&quot;1&quot;);&#125;),    (() =&gt; &#123;Debug.LogError(&quot;2&quot;);&#125;),    (() =&gt; &#123;Debug.LogError(&quot;3&quot;);&#125;),&#125;);/// &lt;summary&gt;/// 绘制右键菜单/// &lt;/summary&gt;/// &lt;param name=&quot;btnRect&quot;&gt;&lt;/param&gt;/// &lt;param name=&quot;menuName&quot;&gt;&lt;/param&gt;/// &lt;param name=&quot;menuIsOn&quot;&gt;&lt;/param&gt;/// &lt;param name=&quot;menuCallBack&quot;&gt;&lt;/param&gt;/// &lt;returns&gt;&lt;/returns&gt;public GenericMenu CreateNewGenericMenu(Rect btnRect, List&lt;string&gt; menuName, List&lt;Action&gt; menuCallBack,    List&lt;bool&gt; menuIsOn = null)&#123;    var menu = new GenericMenu();    var index = 0;    foreach (var action in menuCallBack)    &#123;        menu.AddItem(new GUIContent(menuName[index]), menuIsOn != null &amp;&amp; menuIsOn[index],            () =&gt; &#123; action?.Invoke(); &#125;);        index++;    &#125;    if (Event.current.button == 1 &amp;&amp; Event.current.type == EventType.MouseDown &amp;&amp;        btnRect.Contains(Event.current.mousePosition))    &#123;        menu.ShowAsContext();    &#125;    return menu;&#125;</code></pre>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;&lt;a href=&quot;https://lianbai.icu/2022/12/28/Unity%E6%9D%82%E6%96%87/Unity%E6%9D%82%E6%96%87%E2%80%94%E2%80%94%E7%BC%96%E8%BE%91%E5%99%A8Foldo</summary>
      
    
    
    
    <category term="Unity杂文" scheme="http://yoursite.com/categories/Unity%E6%9D%82%E6%96%87/"/>
    
    
    <category term="编辑器" scheme="http://yoursite.com/tags/%E7%BC%96%E8%BE%91%E5%99%A8/"/>
    
  </entry>
  
  <entry>
    <title>Unity杂文——UI点击穿透</title>
    <link href="http://yoursite.com/2022/12/17/Unity%E6%9D%82%E6%96%87/Unity%E6%9D%82%E6%96%87%E2%80%94%E2%80%94UI%E7%82%B9%E5%87%BB%E7%A9%BF%E9%80%8F/"/>
    <id>http://yoursite.com/2022/12/17/Unity%E6%9D%82%E6%96%87/Unity%E6%9D%82%E6%96%87%E2%80%94%E2%80%94UI%E7%82%B9%E5%87%BB%E7%A9%BF%E9%80%8F/</id>
    <published>2022-12-17T02:11:15.000Z</published>
    <updated>2023-09-28T03:06:07.803Z</updated>
    
    <content type="html"><![CDATA[<p><a href="https://www.xuanyusong.com/archives/4773">Unity3D研究院之UI完整透下事件（一百二十二）——雨松MOMO</a>  </p><h1 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h1><p>当UI打开一个小的提示tip的时候，常见的需求是点击其他任意地方就关闭tip，这个功能很常见，空白出响应点击，监听到就关闭tip，这里会发现一个问题，我们并不能响应tip面板底下的其他按钮，这样玩家在操作的时候就需要先关掉tip，然后再点一下按钮，这往往不是策划想要的，所以我们需要在点击空白处关闭的时候同时响应底下的按钮，这个时候就需要点击穿透事件。  </p><h1 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h1><pre><code>using System.Collections.Generic;using UnityEngine;using UnityEngine.EventSystems;namespace ZHEngine.UI&#123;    public sealed class UTouchPass : MonoBehaviour, IPointerClickHandler,    IMoveHandler,IPointerDownHandler, IPointerUpHandler,IPointerEnterHandler,ISelectHandler, IDeselectHandler    , ISubmitHandler, IInitializePotentialDragHandler, IBeginDragHandler, IEndDragHandler, IDragHandler, IScrollHandler    &#123;        private GameObject CacheGameObject;        private readonly List&lt;RaycastResult&gt; result = new List&lt;RaycastResult&gt;();        public void OnPointerClick(PointerEventData eventData)        &#123;            PassEvent(eventData, ExecuteEvents.pointerClickHandler);        &#125;        public void OnPointerDown(PointerEventData eventData)        &#123;            PassEvent(eventData, ExecuteEvents.pointerDownHandler);            if (Input.GetButtonDown(&quot;Submit&quot;))               ExecuteEvents.Execute(eventData.pointerCurrentRaycast.gameObject, eventData, ExecuteEvents.submitHandler);        &#125;        public void OnPointerUp(PointerEventData eventData)        &#123;            PassEvent(eventData, ExecuteEvents.pointerUpHandler);        &#125;        public void OnPointerEnter(PointerEventData eventData)        &#123;            PassEvent(eventData, ExecuteEvents.pointerEnterHandler);        &#125;        public void OnSelect(BaseEventData eventData)        &#123;            PassEvent(eventData, ExecuteEvents.selectHandler);        &#125;        public void OnDeselect(BaseEventData eventData)        &#123;            PassEvent(eventData, ExecuteEvents.deselectHandler);        &#125;        public void OnSubmit(BaseEventData eventData)        &#123;            PassEvent(eventData, ExecuteEvents.submitHandler);        &#125;        public void OnMove(AxisEventData eventData)        &#123;            PassEvent(eventData, ExecuteEvents.moveHandler);        &#125;        public void OnInitializePotentialDrag(PointerEventData eventData)        &#123;            CacheGameObject = PassEvent(eventData, ExecuteEvents.initializePotentialDrag);        &#125;        public void OnBeginDrag(PointerEventData eventData)        &#123;             PassEvent(eventData, ExecuteEvents.beginDragHandler);        &#125;        public void OnDrag(PointerEventData eventData)        &#123;            ExecuteEvents.Execute(CacheGameObject, eventData, ExecuteEvents.dragHandler);        &#125;        public void OnEndDrag(PointerEventData eventData)        &#123;            ExecuteEvents.Execute(CacheGameObject, eventData, ExecuteEvents.endDragHandler);            CacheGameObject = null;        &#125;        public void OnScroll(PointerEventData eventData)        &#123;            ExecuteEvents.Execute(CacheGameObject, eventData, ExecuteEvents.scrollHandler);        &#125;        private GameObject PassEvent&lt;T&gt;(BaseEventData data, ExecuteEvents.EventFunction&lt;T&gt; function) where T : IEventSystemHandler        &#123;            if (data is PointerEventData eventData)            &#123;                var pointerGo = eventData.pointerCurrentRaycast.gameObject                    ? eventData.pointerCurrentRaycast.gameObject                    : eventData.pointerDrag;                EventSystem.current.RaycastAll(eventData, result);                foreach (var item in result)                &#123;                    var go = item.gameObject;                    if (go != null &amp;&amp; go != pointerGo)                    &#123;                        var executeGo = ExecuteEvents.GetEventHandler&lt;T&gt;(go);                        if (executeGo)                        &#123;                            if (executeGo.TryGetComponent&lt;UTouchPass&gt;(out var __))                                return null;                            ExecuteEvents.Execute(executeGo, data, function);                            return executeGo;                        &#125;                        else                        &#123;                            if(go.TryGetComponent&lt;UnityEngine.UI.Graphic&gt;(out var com))                            &#123;                                if (com.raycastTarget) return null;                            &#125;                        &#125;                    &#125;                &#125;            &#125;            return null;        &#125;    &#125;&#125;</code></pre>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;&lt;a href=&quot;https://www.xuanyusong.com/archives/4773&quot;&gt;Unity3D研究院之UI完整透下事件（一百二十二）——雨松MOMO&lt;/a&gt;  &lt;/p&gt;
&lt;h1 id=&quot;简介&quot;&gt;&lt;a href=&quot;#简介&quot; class=&quot;headerli</summary>
      
    
    
    
    <category term="Unity杂文" scheme="http://yoursite.com/categories/Unity%E6%9D%82%E6%96%87/"/>
    
    
    <category term="UI" scheme="http://yoursite.com/tags/UI/"/>
    
  </entry>
  
  <entry>
    <title>Unity杂文——编辑器下拉多选菜单</title>
    <link href="http://yoursite.com/2022/09/28/Unity%E6%9D%82%E6%96%87/Unity%E6%9D%82%E6%96%87%E2%80%94%E2%80%94%E7%BC%96%E8%BE%91%E5%99%A8%E4%B8%8B%E6%8B%89%E5%A4%9A%E9%80%89%E8%8F%9C%E5%8D%95/"/>
    <id>http://yoursite.com/2022/09/28/Unity%E6%9D%82%E6%96%87/Unity%E6%9D%82%E6%96%87%E2%80%94%E2%80%94%E7%BC%96%E8%BE%91%E5%99%A8%E4%B8%8B%E6%8B%89%E5%A4%9A%E9%80%89%E8%8F%9C%E5%8D%95/</id>
    <published>2022-09-27T16:10:39.000Z</published>
    <updated>2022-09-27T16:37:18.844Z</updated>
    
    <content type="html"><![CDATA[<h1 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h1><p>游戏编辑器工具开发中会经常遇到下拉菜单的需求，笔者也遇到了一种特殊的下拉菜单，是下拉后可以多选的菜单，于是笔者便记录下这种特殊菜单的开发脚本。</p><h1 id="演示"><a href="#演示" class="headerlink" title="演示"></a>演示</h1><p><img src="https://gitee.com/lian_bai/img-warehouse/raw/master/BlogImg/20220928_003131-2022-9-2800:34:48.gif">  </p><h1 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h1><h2 id="枚举类型"><a href="#枚举类型" class="headerlink" title="枚举类型"></a>枚举类型</h2><pre><code>public enum DropdownMultiType&#123;    [InspectorName(&quot;无&quot;)] None = 0,    Everything = ~0,    [InspectorName(&quot;类型一&quot;)] Type1 = 1 &lt;&lt; 1,    [InspectorName(&quot;类型二&quot;)] Type2 = 1 &lt;&lt;2,    [InspectorName(&quot;类型三&quot;)] Type3 = 1&lt;&lt;3&#125;</code></pre><h2 id="调用脚本"><a href="#调用脚本" class="headerlink" title="调用脚本"></a>调用脚本</h2><pre><code>m_DropdownMultiType = (DropdownMultiType)EditorGUILayout.EnumFlagsField(m_DropdownMultiType, GUILayout.Width(120))；EditorGUILayout.LabelField($&quot;Type1：&#123;m_DropdownMultiType.HasFlag(DropdownMultiType.Type1)&#125;&quot;);EditorGUILayout.LabelField($&quot;Type2：&#123;m_DropdownMultiType.HasFlag(DropdownMultiType.Type2)&#125;&quot;);EditorGUILayout.LabelField($&quot;Type3：&#123;m_DropdownMultiType.HasFlag(DropdownMultiType.Type3)&#125;&quot;);</code></pre>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;简介&quot;&gt;&lt;a href=&quot;#简介&quot; class=&quot;headerlink&quot; title=&quot;简介&quot;&gt;&lt;/a&gt;简介&lt;/h1&gt;&lt;p&gt;游戏编辑器工具开发中会经常遇到下拉菜单的需求，笔者也遇到了一种特殊的下拉菜单，是下拉后可以多选的菜单，于是笔者便记录下这种特殊菜单的开发脚本</summary>
      
    
    
    
    <category term="Unity杂文" scheme="http://yoursite.com/categories/Unity%E6%9D%82%E6%96%87/"/>
    
    
    <category term="编辑器" scheme="http://yoursite.com/tags/%E7%BC%96%E8%BE%91%E5%99%A8/"/>
    
  </entry>
  
  <entry>
    <title>常用Python文件处理脚本</title>
    <link href="http://yoursite.com/2022/09/11/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/%E5%B8%B8%E7%94%A8Python%E6%96%87%E4%BB%B6%E5%A4%84%E7%90%86%E8%84%9A%E6%9C%AC/"/>
    <id>http://yoursite.com/2022/09/11/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/%E5%B8%B8%E7%94%A8Python%E6%96%87%E4%BB%B6%E5%A4%84%E7%90%86%E8%84%9A%E6%9C%AC/</id>
    <published>2022-09-11T14:50:02.000Z</published>
    <updated>2022-09-11T15:21:42.760Z</updated>
    
    <content type="html"><![CDATA[<h1 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h1><p>Unity打包经常会用一些文件的复制粘贴到一些文件到自己指定的SDK目录，一开始我们使用的是bat脚本，但是笔者发现这个脚本没办法在打包机（MAC）上使用，于是笔者便想到了python脚本来实现。  </p><h1 id="引用的库"><a href="#引用的库" class="headerlink" title="引用的库"></a>引用的库</h1><pre><code>import sysimport osimport jsonimport shutil</code></pre><h1 id="利用json配置需要控制的文件或者文件夹"><a href="#利用json配置需要控制的文件或者文件夹" class="headerlink" title="利用json配置需要控制的文件或者文件夹"></a>利用json配置需要控制的文件或者文件夹</h1><pre><code>&#123;    &quot;remdir&quot;:[        &quot;data/libs/&quot;,        &quot;data/src/main/assets/&quot;        ],    &quot;copydir&quot;:[        &quot;../xarchive/android/unityLibrary/libs/&quot;,        &quot;../xarchive/android/unityLibrary/    ]&#125;</code></pre><h1 id="参数读取"><a href="#参数读取" class="headerlink" title="参数读取"></a>参数读取</h1><pre><code>if (len(sys.argv) &gt; 1):    isPAD = sys.argv[1]if(isPAD == &quot;0&quot;)    ...</code></pre><p>命令执行python脚本的时候通过读取<strong>sys.argv</strong>便可以读取到自己传入的参数，这里需要注意的是读取的都是字符串，所以笔者在读数字0的时候需要用到**”0”**。  </p><h1 id="读取Config配置文件（json）"><a href="#读取Config配置文件（json）" class="headerlink" title="读取Config配置文件（json）"></a>读取Config配置文件（json）</h1><pre><code>configFilePath = &quot;config.json&quot;;with open(configFilePath,&#39;r&#39;) as load_f:    load_dict = json.load(load_f)</code></pre><h1 id="删除目录文件或者文件夹"><a href="#删除目录文件或者文件夹" class="headerlink" title="删除目录文件或者文件夹"></a>删除目录文件或者文件夹</h1><pre><code>def path_remove(removepath):    for remdirPath in removepath:        try:            if os.path.exists(remdirPath):                if os.path.isdir(remdirPath):                    shutil.rmtree(remdirPath)                else:                    os.remove(remdirPath)                print(&quot;Remove Success: &quot;+remdirPath)        except OSError as e:            print(&quot;Error: %s : %s&quot; % (remdirPath, e.strerror))</code></pre><h1 id="复制文件或者文件夹到指定目录"><a href="#复制文件或者文件夹到指定目录" class="headerlink" title="复制文件或者文件夹到指定目录"></a>复制文件或者文件夹到指定目录</h1><pre><code>def proc_copy(old_path, new_path):    if os.path.exists(old_path):        if os.path.isdir(old_path):            shutil.copytree(old_path, new_path)        elif os.path.isfile(old_path):            shutil.copyfile(old_path, new_path)        print(&quot;Copy Success: &quot; + old_path)    else:        print(&quot;NOT FOUND: &#123;&#125;&quot;.format(old_path))def path_copy(copydir, pastedir):    for index in range(len(copydir)):        if os.path.exists(pastedir[index]):            if os.path.isdir(pastedir[index]):                shutil.rmtree(pastedir[index])        proc_copy(copydir[index],pastedir[index])</code></pre>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;简介&quot;&gt;&lt;a href=&quot;#简介&quot; class=&quot;headerlink&quot; title=&quot;简介&quot;&gt;&lt;/a&gt;简介&lt;/h1&gt;&lt;p&gt;Unity打包经常会用一些文件的复制粘贴到一些文件到自己指定的SDK目录，一开始我们使用的是bat脚本，但是笔者发现这个脚本没办法在打包机（</summary>
      
    
    
    
    <category term="编程语言" scheme="http://yoursite.com/categories/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/"/>
    
    
    <category term="Python" scheme="http://yoursite.com/tags/Python/"/>
    
  </entry>
  
  <entry>
    <title>Unity杂文——Android设备唯一标识</title>
    <link href="http://yoursite.com/2022/09/02/Unity%E6%9D%82%E6%96%87/Unity%E6%9D%82%E6%96%87%E2%80%94%E2%80%94Android%E8%AE%BE%E5%A4%87%E5%94%AF%E4%B8%80%E6%A0%87%E8%AF%86/"/>
    <id>http://yoursite.com/2022/09/02/Unity%E6%9D%82%E6%96%87/Unity%E6%9D%82%E6%96%87%E2%80%94%E2%80%94Android%E8%AE%BE%E5%A4%87%E5%94%AF%E4%B8%80%E6%A0%87%E8%AF%86/</id>
    <published>2022-09-02T09:18:27.000Z</published>
    <updated>2023-09-28T03:00:54.964Z</updated>
    
    <content type="html"><![CDATA[<p><a href="https://lianbai.icu/2022/09/02/Unity%E6%9D%82%E6%96%87/Unity%E6%9D%82%E6%96%87%E2%80%94%E2%80%94Android%E8%AE%BE%E5%A4%87%E5%94%AF%E4%B8%80%E6%A0%87%E8%AF%86/">原文地址</a>  </p><h1 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h1><p>获取设备唯一标识的方法。</p><h1 id="环境"><a href="#环境" class="headerlink" title="环境"></a>环境</h1><p>引用库的github地址：<a href="https://github.com/gzu-liyujiang/Android_CN_OAID/tree/master"><font color="steelblue" size="5">Android_CN_OAID</font></a><br>在Android最外层的bundel.gradle里添加下面依赖  </p><pre><code>allprojects &#123;    repositories &#123;        &#39;&#39;&#39;        maven &#123; url &#39;https://www.jitpack.io&#39; &#125;        ...    &#125;&#125;</code></pre><p>在app或者library的bundel.gradle添加下面依赖  </p><pre><code>dependencies &#123;implementation &#39;com.github.gzu-liyujiang:Android_CN_OAID:&lt;version&gt;&#39;&#125;</code></pre><p><version>这里换成自己需要的版本</version></p><h1 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h1><h2 id="引用的库的初始化"><a href="#引用的库的初始化" class="headerlink" title="引用的库的初始化"></a>引用的库的初始化</h2><p>在Application或者Active的onCreate里添加下面代码：  </p><pre><code>@Overridepublic void onCreate() &#123;    super.onCreate();    if (privacyPolicyAgreed) &#123;        ...        DeviceIdentifier.register(&lt;Application&gt;);        ...    &#125;&#125;</code></pre><p><Application>这里替换成自己程序的application</Application></p><h2 id="调用"><a href="#调用" class="headerlink" title="调用"></a>调用</h2><p>需要的变量： </p><pre><code>//设备唯一标识private static String deviceId;//设备唯一标识文件名字private static String deviceIdFileName = &quot;all_in_sdk&quot;;//设备唯一标识缓存关键字keyprivate static String deviceIdKey = &quot;DeviceId&quot;;//设备唯一标识正则private static final Pattern ANDROID_ID_PATTERN = Pattern.compile(&quot;^[0-9a-fA-F]&#123;16&#125;$&quot;);</code></pre><p>获取的方法：</p><pre><code>//获取设备的唯一标识public static String GetDeviceID() &#123;    if(m_Activity == null)&#123;        m_Activity = UnityPlayer.currentActivity;    &#125;    Application application = m_Activity.getApplication();    if (application == null)        return &quot;&quot;;    if (deviceId != null)        return deviceId;    deviceId = getSPValue((Context)application, deviceIdKey);    if (!TextUtils.isEmpty(deviceId))        return deviceId;    deviceId = getAndroidIdAsDeviceId((Context)application);    if (!TextUtils.isEmpty(deviceId)) &#123;        saveSPValue((Context)application, deviceIdKey, deviceId);        return deviceId;    &#125;    deviceId = generateSoftDeviceId();    if (!TextUtils.isEmpty(deviceId)) &#123;        saveSPValue((Context)application, deviceIdKey, deviceId);        return deviceId;    &#125;    return deviceId;&#125;private static String getSPValue(Context paramContext, String paramString) &#123;    SharedPreferences sharedPreferences = paramContext.getSharedPreferences(deviceIdFileName, 0);    return sharedPreferences.getString(paramString, null);&#125;private static String getAndroidIdAsDeviceId(Context paramContext) &#123;    String str;    if(DeviceID.supportedOAID(m_Activity))    &#123;        str = DeviceIdentifier.getOAID(m_Activity);        if (isLegalAndroidId(str, true))            return str;    &#125;    str = DeviceIdentifier.getAndroidID(m_Activity);    if (isLegalAndroidId(str, true))        return str;    str = DeviceIdentifier.getIMEI(m_Activity);    if (isLegalAndroidId(str, true))        return str;    str = DeviceIdentifier.getWidevineID();    if (isLegalAndroidId(str, true))        return str;    str = DeviceIdentifier.getPseudoID();    if (isLegalAndroidId(str, true))        return str;    str = DeviceIdentifier.getGUID(m_Activity);    if (isLegalAndroidId(str, true))        return str;    return null;&#125;private static String generateSoftDeviceId() &#123;    String str1 = Build.SERIAL;    String str2 = !TextUtils.isEmpty(str1) ? str1 : &quot;NA&quot;;    try &#123;        long l1 = 1152921504606846976L;        long l2 = randomLong(Long.MAX_VALUE - l1) + l1;        return String.format(&quot;%1$s_%2$s&quot;, new Object[] &#123; Long.toHexString(l2), str2 &#125;);    &#125; catch (Throwable throwable) &#123;        return String.format(&quot;%1$s_%2$s&quot;, new Object[] &#123; &quot;NA&quot; + Long.toHexString(System.currentTimeMillis()), str2 &#125;);    &#125;&#125;private static void saveSPValue(Context paramContext, String paramString1, String paramString2) &#123;    SharedPreferences sharedPreferences = paramContext.getSharedPreferences(deviceIdFileName, 0);    sharedPreferences.edit().putString(paramString1, paramString2).apply();&#125;private static long randomLong(long paramLong) &#123;    if (Build.VERSION.SDK_INT &gt;= 21)        return ThreadLocalRandom.current().nextLong(paramLong);    return (long)((new Random()).nextDouble() * (paramLong - 1L));&#125;private static boolean isLegalAndroidId(String paramString, boolean paramBoolean) &#123;    if (paramBoolean)        return (!TextUtils.isEmpty(paramString) &amp;&amp; ANDROID_ID_PATTERN                .matcher(paramString).find());    return (!TextUtils.isEmpty(paramString) &amp;&amp; ANDROID_ID_PATTERN            .matcher(paramString).find());&#125;</code></pre>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;&lt;a href=&quot;https://lianbai.icu/2022/09/02/Unity%E6%9D%82%E6%96%87/Unity%E6%9D%82%E6%96%87%E2%80%94%E2%80%94Android%E8%AE%BE%E5%A4%87%E5%94%</summary>
      
    
    
    
    <category term="Unity杂文" scheme="http://yoursite.com/categories/Unity%E6%9D%82%E6%96%87/"/>
    
    
    <category term="Android" scheme="http://yoursite.com/tags/Android/"/>
    
  </entry>
  
  <entry>
    <title>Unity杂文——UI父节点随子节点自适应</title>
    <link href="http://yoursite.com/2022/08/24/Unity%E6%9D%82%E6%96%87/Unity%E6%9D%82%E6%96%87%E2%80%94%E2%80%94UI%E7%88%B6%E8%8A%82%E7%82%B9%E9%9A%8F%E5%AD%90%E8%8A%82%E7%82%B9%E8%87%AA%E9%80%82%E5%BA%94/"/>
    <id>http://yoursite.com/2022/08/24/Unity%E6%9D%82%E6%96%87/Unity%E6%9D%82%E6%96%87%E2%80%94%E2%80%94UI%E7%88%B6%E8%8A%82%E7%82%B9%E9%9A%8F%E5%AD%90%E8%8A%82%E7%82%B9%E8%87%AA%E9%80%82%E5%BA%94/</id>
    <published>2022-08-24T11:56:54.000Z</published>
    <updated>2023-09-28T02:59:45.795Z</updated>
    
    <content type="html"><![CDATA[<p><a href="https://lianbai.icu/2022/08/24/Unity%E6%9D%82%E6%96%87/Unity%E6%9D%82%E6%96%87%E2%80%94%E2%80%94UI%E7%88%B6%E8%8A%82%E7%82%B9%E9%9A%8F%E5%AD%90%E8%8A%82%E7%82%B9%E8%87%AA%E9%80%82%E5%BA%94/">原文地址</a>  </p><h1 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h1><p>在UI的开发过程中，经常会遇到Image随子节点的文字变化自动缩放，就是拿Image当背景。笔者遇到这种问题每次都是利用Layout+Content Size Fitter来完成的，笔者想了想每次都要加两个组件，并且Layout只用到了随自己点自适应的功能，于是笔者便想办法把两个功能合成一个脚本来实现需求，于是便有了下面的脚本。  </p><h1 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h1><p>代码如下： </p><pre><code>using System;using UnityEngine;using UnityEngine.EventSystems;using UnityEngine.UI;/// &lt;summary&gt;/// 未完成，暂时别用/// &lt;/summary&gt;[AddComponentMenu(&quot;Layout/Rect Transform Fitter&quot;, 142)][ExecuteAlways][RequireComponent(typeof(RectTransform))]public class RectTransformFit : UIBehaviour, ILayoutGroup&#123;    [SerializeField] protected RectTransform m_RectChildren;    [SerializeField] protected RectOffset m_Padding = new RectOffset();    [SerializeField] protected TextAnchor m_ChildAlignment = TextAnchor.UpperLeft;    [SerializeField] protected bool m_ChildControlWidth = false;    [SerializeField] protected bool m_ChildControlHeight = false;    [SerializeField] protected ContentSizeFitter.FitMode m_HorizontalFit = ContentSizeFitter.FitMode.Unconstrained;    [SerializeField] protected ContentSizeFitter.FitMode m_VerticalFit = ContentSizeFitter.FitMode.Unconstrained;    public RectOffset padding    &#123;        get =&gt; m_Padding;        set =&gt; SetProperty(ref m_Padding, value);    &#125;    public TextAnchor childAlignment &#123; get =&gt; m_ChildAlignment;        set =&gt; SetProperty(ref m_ChildAlignment, value);    &#125;    public bool childControlWidth    &#123;        get =&gt; m_ChildControlWidth;        set =&gt; SetProperty(ref m_ChildControlWidth, value);    &#125;    public bool childControlHeight    &#123;        get =&gt; m_ChildControlHeight;        set =&gt; SetProperty(ref m_ChildControlHeight, value);    &#125;    public ContentSizeFitter.FitMode horizontalFit    &#123;        get =&gt; m_HorizontalFit;        set        &#123;            if (SetPropertyUtility.SetStruct(ref m_HorizontalFit, value)) SetDirty();        &#125;    &#125;    public ContentSizeFitter.FitMode verticalFit    &#123;        get =&gt; m_VerticalFit;        set        &#123;            if (SetPropertyUtility.SetStruct(ref m_VerticalFit, value)) SetDirty();        &#125;    &#125;    [NonSerialized] private RectTransform m_Rect;    protected RectTransform rectTransform    &#123;        get        &#123;            if (m_Rect == null)                m_Rect = GetComponent&lt;RectTransform&gt;();            return m_Rect;        &#125;    &#125;#pragma warning disable 649    private DrivenRectTransformTracker m_Tracker;#pragma warning restore 649    private void OnEnable()    &#123;        m_Rect ??= GetComponent&lt;RectTransform&gt;();        SetDirty();    &#125;    protected override void OnRectTransformDimensionsChange()    &#123;        SetDirty();    &#125;    protected override void OnDisable()    &#123;        m_Tracker.Clear();        base.OnDisable();    &#125;    /// &lt;summary&gt;    /// Calculate and apply the horizontal component of the size to the RectTransform    /// &lt;/summary&gt;    public void SetLayoutHorizontal()    &#123;        m_Tracker.Clear();        if (m_RectChildren == null || !m_ChildControlWidth)        &#123;            SetDirty();            return;        &#125;        HandleSelfFittingAlongAxis(0, m_RectChildren);    &#125;    /// &lt;summary&gt;    /// Calculate and apply the vertical component of the size to the RectTransform    /// &lt;/summary&gt;    public void SetLayoutVertical()    &#123;        if (m_RectChildren == null || !m_ChildControlHeight)        &#123;            SetDirty();            return;        &#125;        HandleSelfFittingAlongAxis(1, m_RectChildren);    &#125;    private void HandleSelfFittingAlongAxis(int axis, RectTransform rectChild)    &#123;        if (rectChild == null) return;        var fitting = (axis == 0 ? horizontalFit : verticalFit);        if (fitting == ContentSizeFitter.FitMode.Unconstrained)        &#123;            // Keep a reference to the tracked transform, but don&#39;t control its properties:            m_Tracker.Add(this, rectChild, DrivenTransformProperties.None);            return;        &#125;        m_Tracker.Add(this, rectChild,            (axis == 0 ? DrivenTransformProperties.SizeDeltaX : DrivenTransformProperties.SizeDeltaY));        // Set size to min or preferred size        rectChild.SetSizeWithCurrentAnchors((RectTransform.Axis)axis,            fitting == ContentSizeFitter.FitMode.MinSize                ? LayoutUtility.GetMinSize(rectChild, axis)                : LayoutUtility.GetPreferredSize(rectChild, axis));        SetDirty();    &#125;    /// &lt;summary&gt;    /// Helper method used to set a given property if it has changed.    /// &lt;/summary&gt;    /// &lt;param name=&quot;currentValue&quot;&gt;A reference to the member value.&lt;/param&gt;    /// &lt;param name=&quot;newValue&quot;&gt;The new value.&lt;/param&gt;    protected void SetProperty&lt;T&gt;(ref T currentValue, T newValue)    &#123;        if ((currentValue == null &amp;&amp; newValue == null) || (currentValue != null &amp;&amp; currentValue.Equals(newValue)))            return;        currentValue = newValue;        SetDirty();    &#125;    protected void SetDirty()    &#123;        if (!IsActive())            return;        RefreshRect();        LayoutRebuilder.MarkLayoutForRebuild(m_RectChildren);        LayoutRebuilder.MarkLayoutForRebuild(m_Rect);    &#125;    public void RefreshRect()    &#123;        if (m_RectChildren == null) return;        Vector2 anchoredPos;        var childSize = m_RectChildren.sizeDelta;        var width = childSize.x + padding.left + padding.right;        var height = childSize.y + padding.top + padding.bottom;        var rectSize = rectTransform.sizeDelta;        if (horizontalFit != ContentSizeFitter.FitMode.Unconstrained &amp;&amp;            verticalFit != ContentSizeFitter.FitMode.Unconstrained)        &#123;            rectTransform.sizeDelta = new Vector2(width, height);        &#125;        else if (horizontalFit != ContentSizeFitter.FitMode.Unconstrained)        &#123;            rectTransform.sizeDelta = new Vector2(width, rectSize.y);        &#125;        else if (verticalFit != ContentSizeFitter.FitMode.Unconstrained)        &#123;            rectTransform.sizeDelta = new Vector2(rectSize.x, height);        &#125;        rectSize = rectTransform.sizeDelta;        var oldPos = rectTransform.anchoredPosition;        var oldPivot = rectTransform.pivot;        switch (m_ChildAlignment)        &#123;            case TextAnchor.UpperLeft:                rectTransform.pivot = new Vector2(0, 1);                anchoredPos = new Vector2(padding.left, -padding.top);                break;            case TextAnchor.UpperCenter:                rectTransform.pivot = new Vector2(0.5f, 1);                anchoredPos = new Vector2(0, -padding.top);                break;            case TextAnchor.UpperRight:                rectTransform.pivot = new Vector2(1, 1);                anchoredPos = new Vector2(-padding.right, -padding.top);                break;            case TextAnchor.MiddleLeft:                rectTransform.pivot = new Vector2(0, 0.5f);                anchoredPos = new Vector2(padding.left, 0);                break;            case TextAnchor.MiddleCenter:                rectTransform.pivot = new Vector2(0.5f, 0);                anchoredPos = new Vector2(0, 0);                break;            case TextAnchor.MiddleRight:                rectTransform.pivot = new Vector2(1, 0);                anchoredPos = new Vector2(-padding.right, 0);                break;            case TextAnchor.LowerLeft:                rectTransform.pivot = new Vector2(0, 0);                anchoredPos = new Vector2(padding.left, padding.bottom);                break;            case TextAnchor.LowerCenter:                rectTransform.pivot = new Vector2(0.5f, 0);                anchoredPos = new Vector2(0, padding.bottom);                break;            case TextAnchor.LowerRight:                rectTransform.pivot = new Vector2(1, 0);                anchoredPos = new Vector2(-padding.right, padding.bottom);                break;            default:                throw new ArgumentOutOfRangeException();        &#125;        var pivot = rectTransform.pivot;        rectTransform.anchoredPosition = new Vector2(oldPos.x + rectSize.x * (pivot.x - oldPivot.x),            oldPos.y + rectSize.y * (pivot.y - oldPivot.y));        m_RectChildren.anchorMax = pivot;        m_RectChildren.anchorMin = pivot;        m_RectChildren.pivot = pivot;        m_RectChildren.anchoredPosition = anchoredPos;    &#125;#if UNITY_EDITOR    protected override void OnValidate()    &#123;        SetDirty();    &#125;#endif&#125;</code></pre><h1 id="需要支持的脚本（源码抄来的SetPropertyUtility）"><a href="#需要支持的脚本（源码抄来的SetPropertyUtility）" class="headerlink" title="需要支持的脚本（源码抄来的SetPropertyUtility）"></a>需要支持的脚本（源码抄来的SetPropertyUtility）</h1><pre><code>using System;using System.Collections.Generic;using UnityEngine;internal static class SetPropertyUtility&#123;    private const float Tolerance = 0.000001f;                 //通过此值判断值是否发生变化    public static bool SetColor(ref Color currentValue, Color newValue)    &#123;        if (Math.Abs(currentValue.r - newValue.r) &lt; Tolerance &amp;&amp;            Math.Abs(currentValue.g - newValue.g) &lt; Tolerance &amp;&amp;            Math.Abs(currentValue.b - newValue.b) &lt; Tolerance &amp;&amp;            Math.Abs(currentValue.a - newValue.a) &lt; Tolerance)            return false;        currentValue = newValue;        return true;    &#125;    public static bool SetStruct&lt;T&gt;(ref T currentValue, T newValue) where T : struct    &#123;        if (EqualityComparer&lt;T&gt;.Default.Equals(currentValue, newValue))            return false;        currentValue = newValue;        return true;    &#125;    public static bool SetClass&lt;T&gt;(ref T currentValue, T newValue) where T : class    &#123;        if ((currentValue == null &amp;&amp; newValue == null) || (currentValue != null &amp;&amp; currentValue.Equals(newValue)))            return false;        currentValue = newValue;        return true;    &#125;&#125;</code></pre><h1 id="编辑器显示Editor代码"><a href="#编辑器显示Editor代码" class="headerlink" title="编辑器显示Editor代码"></a>编辑器显示Editor代码</h1><p>代码如下： </p><pre><code>using System;using UnityEditor;using UnityEditor.UI;using UnityEngine;[CustomEditor(typeof(RectTransformFit))]public class RectTransformFitEditor : SelfControllerEditor&#123;    SerializedProperty m_Padding;    SerializedProperty m_ChildAlignment;    SerializedProperty m_RectChildren;    SerializedProperty m_HorizontalFit;    SerializedProperty m_VerticalFit;    SerializedProperty m_ChildControlWidth;    SerializedProperty m_ChildControlHeight;    protected void OnEnable()    &#123;        m_Padding = serializedObject.FindProperty(&quot;m_Padding&quot;);        m_ChildAlignment = serializedObject.FindProperty(&quot;m_ChildAlignment&quot;);        m_RectChildren = serializedObject.FindProperty(&quot;m_RectChildren&quot;);        m_ChildControlWidth = serializedObject.FindProperty(&quot;m_ChildControlWidth&quot;);        m_ChildControlHeight = serializedObject.FindProperty(&quot;m_ChildControlHeight&quot;);        m_HorizontalFit = serializedObject.FindProperty(&quot;m_HorizontalFit&quot;);        m_VerticalFit = serializedObject.FindProperty(&quot;m_VerticalFit&quot;);    &#125;    public override void OnInspectorGUI()    &#123;        serializedObject.Update();        EditorGUILayout.PropertyField(m_Padding, true);        EditorGUILayout.PropertyField(m_ChildAlignment, true);        EditorGUILayout.PropertyField(m_RectChildren, true);        Rect rect = EditorGUILayout.GetControlRect();        rect = EditorGUI.PrefixLabel(rect, -1, EditorGUIUtility.TrTextContent(&quot;Control Child Size&quot;));        rect.width = Mathf.Max(50, (rect.width - 4) / 3);        EditorGUIUtility.labelWidth = 50;        ToggleLeft(rect, m_ChildControlWidth, EditorGUIUtility.TrTextContent(&quot;Width&quot;));        rect.x += rect.width + 2;        ToggleLeft(rect, m_ChildControlHeight, EditorGUIUtility.TrTextContent(&quot;Height&quot;));        EditorGUIUtility.labelWidth = 0;        EditorGUILayout.PropertyField(m_HorizontalFit, true);        EditorGUILayout.PropertyField(m_VerticalFit, true);        serializedObject.ApplyModifiedProperties();    &#125;    void ToggleLeft(Rect position, SerializedProperty property, GUIContent label)    &#123;        bool toggle = property.boolValue;        EditorGUI.showMixedValue = property.hasMultipleDifferentValues;        EditorGUI.BeginChangeCheck();        int oldIndent = EditorGUI.indentLevel;        EditorGUI.indentLevel = 0;        toggle = EditorGUI.ToggleLeft(position, label, toggle);        EditorGUI.indentLevel = oldIndent;        if (EditorGUI.EndChangeCheck())        &#123;            property.boolValue = property.hasMultipleDifferentValues || !property.boolValue;        &#125;        EditorGUI.showMixedValue = false;    &#125;&#125;</code></pre>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;&lt;a href=&quot;https://lianbai.icu/2022/08/24/Unity%E6%9D%82%E6%96%87/Unity%E6%9D%82%E6%96%87%E2%80%94%E2%80%94UI%E7%88%B6%E8%8A%82%E7%82%B9%E9</summary>
      
    
    
    
    <category term="Unity杂文" scheme="http://yoursite.com/categories/Unity%E6%9D%82%E6%96%87/"/>
    
    
    <category term="UGUI" scheme="http://yoursite.com/tags/UGUI/"/>
    
  </entry>
  
  <entry>
    <title>hexo图床图片无法显示</title>
    <link href="http://yoursite.com/2022/08/20/%E6%90%AD%E5%BB%BA%E5%8D%9A%E5%AE%A2/hexo%E5%9B%BE%E5%BA%8A%E5%9B%BE%E7%89%87%E6%97%A0%E6%B3%95%E6%98%BE%E7%A4%BA/"/>
    <id>http://yoursite.com/2022/08/20/%E6%90%AD%E5%BB%BA%E5%8D%9A%E5%AE%A2/hexo%E5%9B%BE%E5%BA%8A%E5%9B%BE%E7%89%87%E6%97%A0%E6%B3%95%E6%98%BE%E7%A4%BA/</id>
    <published>2022-08-20T10:05:02.000Z</published>
    <updated>2022-08-20T10:15:38.397Z</updated>
    
    <content type="html"><![CDATA[<meta name="referrer" content="no-referrer"><h1 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h1><p>今天更新自己博客的时候发现新博客的图片都无法显示，这个听说只存在于没有域名的博客中，笔者因为没有域名，所以不清除是不是。  </p><blockquote><p>图床：gitee<br>博客：github+hexo<br>原因猜测：gitee开始对图床进行限制，没有域名的博客也访问不到图床的图片  </p></blockquote><p>#解决方法 </p><h2 id="方法一（不推荐）"><a href="#方法一（不推荐）" class="headerlink" title="方法一（不推荐）"></a>方法一（不推荐）</h2><p>最快捷的解决方案就是再自己博客的文章开头加上下面代码   </p><pre><code>&lt;meta name=&quot;referrer&quot; content=&quot;no-referrer&quot; /&gt;</code></pre><p>看下面举例  </p><p><img src="https://gitee.com/lian_bai/img-warehouse/raw/master/BlogImg/1660990253369-2022-8-2018:10:54.png"></p><h2 id="方法二（推荐）"><a href="#方法二（推荐）" class="headerlink" title="方法二（推荐）"></a>方法二（推荐）</h2><p>在自己播放的主题文件加里直接加上就不需要了，具体路径：<br>拿3-hexo主题举例：<br>在**…\themes\3-hexo\layout_partial\header.ejs**文件里加入刚才的代码，如下：  </p><p><img src="https://gitee.com/lian_bai/img-warehouse/raw/master/BlogImg/1660990452909-2022-8-2018:14:13.png">  </p><p>其他主题文件在类似位置：<strong>hexo\themes...\layout_partial\head.ejs</strong><br>其他主题的文件名字应该叫：<strong>head.ejs</strong>。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;meta name=&quot;referrer&quot; content=&quot;no-referrer&quot;&gt;

&lt;h1 id=&quot;问题&quot;&gt;&lt;a href=&quot;#问题&quot; class=&quot;headerlink&quot; title=&quot;问题&quot;&gt;&lt;/a&gt;问题&lt;/h1&gt;&lt;p&gt;今天更新自己博客的时候发现新博客的图片都无法显示</summary>
      
    
    
    
    <category term="搭建博客" scheme="http://yoursite.com/categories/%E6%90%AD%E5%BB%BA%E5%8D%9A%E5%AE%A2/"/>
    
    
    <category term="hexo博客" scheme="http://yoursite.com/tags/hexo%E5%8D%9A%E5%AE%A2/"/>
    
  </entry>
  
</feed>
