Unity游戏开发的基础代码框架


开发注意事项

  1. 场景中不放置任何的物体,只放置启动脚本
  2. UI视图和其他预制体不挂载任何的脚本,保持纯净,使用时用代码挂载
  3. 所有的资源都使用AB包的形式进行存储和加载,舍弃Resource文件夹的使用
  4. UI的脚本不要自己创建,使用Tools/ui_gen中的编辑器拓展来生成
  5. Excel的解析工具代码在

单例模式

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// 实现普通的单例模式
// where 限制模板的类型, new()指的是这个类型必须要能被实例化
public abstract class Singleton<T> where T : new() {
    private static T _instance;
    private static object mutex = new object();
    public static T Instance {
        get {
            if (_instance == null) {
                lock (mutex) { // 保证我们的单例,是线程安全的;
                    if (_instance == null) {
                        _instance = new T();
                    }
                }
            }
            return _instance;
        }
    }
}

// Monobeavior: 声音, 网络
// Unity单例

public class UnitySingleton<T> : MonoBehaviour
where T : Component {
    private static T _instance = null;
    public static T Instance {
        get {
            if (_instance == null) {
                _instance = FindObjectOfType(typeof(T)) as T;
                if (_instance == null) {
                    GameObject obj = new GameObject();
                    _instance = (T)obj.AddComponent(typeof(T));
                    obj.hideFlags = HideFlags.DontSave;
                    // obj.hideFlags = HideFlags.HideAndDontSave;
                    obj.name = typeof(T).Name;
                }
            }
            return _instance;
        }
    }

    public virtual void Awake() {
        DontDestroyOnLoad(this.gameObject);
        if (_instance == null) {
            _instance = this as T;
        }
        else {
            GameObject.Destroy(this.gameObject);
        }
    }
}

事件管理

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EventMgr : UnitySingleton<EventMgr>
{
    public delegate void event_handler(string event_name, object udata);

    private Dictionary<string, event_handler> dic = new Dictionary<string, event_handler>();

    public void init() { 

    }

    public void AddListener(string event_name, event_handler h) {
        if (this.dic.ContainsKey(event_name)) {
            this.dic[event_name] += h;
        }
        else {
            this.dic.Add(event_name, h);
        }
    }

    public void RemoveListener(string event_name, event_handler h) {
        if (!this.dic.ContainsKey(event_name)) {
            return;
        }

        this.dic[event_name] -= h;

        if (this.dic[event_name] == null) {
            this.dic.Remove(event_name);
        }
    }

    public void Emit(string event_name, object udata) {
        if (!this.dic.ContainsKey(event_name)) {
            return;
        }

        this.dic[event_name](event_name, udata);
    }

}

资源管理(基于AssetBundle)

资源加载

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using AssetBundles;

public class ResMgr : UnitySingleton<ResMgr> {
    public override void Awake() {
        base.Awake();
        this.gameObject.AddComponent<AssetBundleManager>();
    }

    public T GetAssetCache<T>(string name) where T : UnityEngine.Object
    {
#if UNITY_EDITOR
        if (AssetBundleConfig.IsEditorMode)
        {
            string path = AssetBundleUtility.PackagePathToAssetsPath(name);
            // Debug.Log(path);
            UnityEngine.Object target = UnityEditor.AssetDatabase.LoadAssetAtPath<T>(path);
            return target as T;
        }
#endif
        return AssetBundleManager.Instance.GetAssetCache(name) as T;
    }

    public void LoadAssetBundleAsync(string assetbundleName, Action end_func)
    {
        this.StartCoroutine(this.IE_LoadAssetBundleAsync(assetbundleName, end_func));
    }

    public IEnumerator IE_LoadAssetBundleAsync(string assetbundleName, Action end_func)
    {
        var loader = AssetBundleManager.Instance.LoadAssetBundleAsync(assetbundleName);
        yield return loader;
        loader.Dispose();

        end_func();
    }
}

AB包管理

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

using System;
#if UNITY_EDITOR
using UnityEditor;
#endif

/// <summary>
/// added by wsh @ 2017-12-21
/// 功能:assetbundle管理类,为外部提供统一的资源加载界面、协调Assetbundle各个子系统的运行
/// 注意:
/// 1、抛弃Resources目录的使用,官方建议:https://unity3d.com/cn/learn/tutorials/temas/best-practices/resources-folder?playlist=30089
/// 2、提供Editor和Simulate模式,前者不适用Assetbundle,直接加载资源,快速开发;后者使用Assetbundle,用本地服务器模拟资源更新
/// 3、场景不进行打包,场景资源打包为预设
/// 4、只提供异步接口,所有加载按异步进行
/// 5、采用LZMA压缩方式,性能瓶颈在Assetbundle加载上,ab加载异步,asset加载同步,ab加载后导出全部asset并卸载ab
/// 6、所有公共ab包(被多个ab包依赖)常驻内存,非公共包加载asset以后立刻卸载,被依赖的公共ab包会随着资源预加载自动加载并常驻内存
/// 7、随意卸载公共ab包可能导致内存资源重复,最好在切换场景时再手动清理不需要的公共ab包
/// 8、常驻包(公共ab包)引用计数不为0时手动清理无效,正在等待加载的所有ab包不能强行终止---一旦发起创建就一定要等操作结束,异步过程进行中清理无效
/// 9、切换场景时最好预加载所有可能使用到的资源,所有加载器用完以后记得Dispose回收,清理GC时注意先释放所有Asset缓存
/// 10、逻辑层所有Asset路径带文件类型后缀,且是AssetBundleConfig.ResourcesFolderName下的相对路径,注意:路径区分大小写
/// TODO:
/// 1、区分场景常驻包和全局公共包,切换场景时自动卸载场景公共包
/// 使用说明:
/// 1、由Asset路径获取AssetName、AssetBundleName:ParseAssetPathToNames
/// 2、设置常驻(公共)ab包:SetAssetBundleResident(assebundleName, true)---公共ab包已经自动设置常驻
/// 2、(预)加载资源:var loader = LoadAssetBundleAsync(assetbundleName),协程等待加载完毕后Dispose:loader.Dispose()
/// 3、加载Asset资源:var loader = LoadAssetAsync(assetPath, TextAsset),协程等待加载完毕后Dispose:loader.Dispose()
/// 4、离开场景清理所有Asset缓存:ClearAssetsCache(),UnloadUnusedAssetBundles(), Resources.UnloadUnusedAssets()
/// 5、离开场景清理必要的(公共)ab包:TryUnloadAssetBundle(),注意:这里只是尝试卸载,所有引用计数不为0的包(还正在加载)不会被清理
/// </summary>

namespace AssetBundles
{
    public class AssetBundleManager : UnitySingleton<AssetBundleManager>
    {
        // 最大同时进行的ab创建数量
        const int MAX_ASSETBUNDLE_CREATE_NUM = 5;
        // manifest:提供依赖关系查找以及hash值比对
        Manifest manifest = null;
        // 资源路径相关的映射表
        AssetsPathMapping assetsPathMapping = null;
        // 常驻ab包:需要手动添加公共ab包进来,常驻包不会自动卸载(即使引用计数为0),引用计数为0时可以手动卸载
        HashSet<string> assetbundleResident = new HashSet<string>();
        // ab缓存包:所有目前已经加载的ab包,包括临时ab包与公共ab包
        Dictionary<string, AssetBundle> assetbundlesCaching = new Dictionary<string, AssetBundle>();
        // ab缓存包引用计数:卸载ab包时只有引用计数为0时才会真正执行卸载
        Dictionary<string, int> assetbundleRefCount = new Dictionary<string, int>(); 
        // asset缓存:给非公共ab包的asset提供逻辑层的复用
        Dictionary<string, UnityEngine.Object> assetsCaching = new Dictionary<string, UnityEngine.Object>();
        // 加载数据请求:正在prosessing或者等待prosessing的资源请求
        Dictionary<string, ResourceWebRequester> webRequesting = new Dictionary<string, ResourceWebRequester>();
        // 等待处理的资源请求
        Queue<ResourceWebRequester> webRequesterQueue = new Queue<ResourceWebRequester>();
        // 正在处理的资源请求
        List<ResourceWebRequester> prosessingWebRequester = new List<ResourceWebRequester>();
        // 逻辑层正在等待的ab加载异步句柄
        List<AssetBundleAsyncLoader> prosessingAssetBundleAsyncLoader = new List<AssetBundleAsyncLoader>();
        // 逻辑层正在等待的asset加载异步句柄
        List<AssetAsyncLoader> prosessingAssetAsyncLoader = new List<AssetAsyncLoader>();

        public static string ManifestBundleName
        {
            get;
            set;
        }

        public IEnumerator Initialize()
        {
#if UNITY_EDITOR
            if (AssetBundleConfig.IsEditorMode)
            {
                yield break;
            }
#endif

            manifest = new Manifest();
            assetsPathMapping = new AssetsPathMapping();
            // 说明:同时请求资源可以提高加载速度
            var manifestRequest = RequestAssetBundleAsync(manifest.AssetbundleName);
            var pathMapRequest = RequestAssetBundleAsync(assetsPathMapping.AssetbundleName);

            yield return manifestRequest;
            var assetbundle = manifestRequest.assetbundle;
            manifest.LoadFromAssetbundle(assetbundle);
            assetbundle.Unload(false);
            manifestRequest.Dispose();

            yield return pathMapRequest;
            assetbundle = pathMapRequest.assetbundle;
            var mapContent = assetbundle.LoadAsset<TextAsset>(assetsPathMapping.AssetName);
            if (mapContent != null)
            {
                assetsPathMapping.Initialize(mapContent.text);
            }
            assetbundle.Unload(true);
            pathMapRequest.Dispose();

            // 设置所有公共包为常驻包
            var start = DateTime.Now;
            var allAssetbundleNames = manifest.GetAllAssetBundleNames();
            foreach (var curAssetbundleName in allAssetbundleNames)
            {
                if (string.IsNullOrEmpty(curAssetbundleName))
                {
                    continue;
                }

                int count = 0;
                foreach (var checkAssetbundle in allAssetbundleNames)
                {
                    if (checkAssetbundle == curAssetbundleName || string.IsNullOrEmpty(checkAssetbundle))
                    {
                        continue;
                    }

                    var allDependencies = manifest.GetAllDependencies(checkAssetbundle);
                    if (Array.IndexOf(allDependencies, curAssetbundleName) >= 0)
                    {
                        count++;
                        if (count >= 2)
                        {
                            break;
                        }
                    }
                }

                if (count >= 2)
                {
                    SetAssetBundleResident(curAssetbundleName, true);
                }
            }
            Debug.Log(string.Format("AssetBundleResident Initialize use {0}ms", (DateTime.Now - start).Milliseconds));
            yield break;
        }

        public IEnumerator Cleanup()
        {
#if UNITY_EDITOR
            if (AssetBundleConfig.IsEditorMode)
            {
                yield break;
            }
#endif

            // 等待所有请求完成
            // 要是不等待Unity很多版本都有各种Bug
            yield return new WaitUntil(() =>
            {
                return prosessingWebRequester.Count == 0;
            });
            yield return new WaitUntil(() =>
            {
                return prosessingAssetBundleAsyncLoader.Count == 0;
            });
            yield return new WaitUntil(() =>
            {
                return prosessingAssetAsyncLoader.Count == 0;
            });

            ClearAssetsCache();
            foreach (var assetbunle in assetbundlesCaching.Values)
            {
                if (assetbunle != null)
                {
                    assetbunle.Unload(false);
                }
            }
            assetbundlesCaching.Clear();
            assetbundleRefCount.Clear();
            assetbundleResident.Clear();
            yield break;
        }

        public Manifest curManifest
        {
            get
            {
                return manifest;
            }
        }

        public string DownloadUrl
        {
            get
            {
                // return Setting.SERVER_RESOURCE_ADDR;
                return null;
            }
        }
        
        public void SetAssetBundleResident(string assetbundleName, bool resident)
        {
            Debug.Log("SetAssetBundleResident : " + assetbundleName + ", " + resident.ToString());
            bool exist = assetbundleResident.Contains(assetbundleName);
            if (resident && !exist)
            {
                assetbundleResident.Add(assetbundleName);
            }
            else if(!resident && exist)
            {
                assetbundleResident.Remove(assetbundleName);
            }
        }

        public bool IsAssetBundleResident(string assebundleName)
        {
            return assetbundleResident.Contains(assebundleName);
        }

        public bool IsAssetBundleLoaded(string assetbundleName)
        {
            return assetbundlesCaching.ContainsKey(assetbundleName);
        }

        public AssetBundle GetAssetBundleCache(string assetbundleName)
        {
            AssetBundle target = null;
            assetbundlesCaching.TryGetValue(assetbundleName, out target);
            return target;
        }

        protected void RemoveAssetBundleCache(string assetbundleName)
        {
            assetbundlesCaching.Remove(assetbundleName);
        }

        protected void AddAssetBundleCache(string assetbundleName, AssetBundle assetbundle)
        {
            assetbundlesCaching[assetbundleName] = assetbundle;
        }

        public bool IsAssetLoaded(string assetName)
        {
            return assetsCaching.ContainsKey(assetName);
        }

        public UnityEngine.Object GetAssetCache(string assetName)
        {
            UnityEngine.Object target = null;
            assetsCaching.TryGetValue(assetName, out target);
            return target;
        }

        public void AddAssetCache(string assetName, UnityEngine.Object asset)
        {
            assetsCaching[assetName] = asset;
        }

        public void AddAssetbundleAssetsCache(string assetbundleName)
        {
#if UNITY_EDITOR
            if (AssetBundleConfig.IsEditorMode)
            {
                return;
            }
#endif

            if (!IsAssetBundleLoaded(assetbundleName))
            {
                Debug.LogError("Try to add assets cache from unloaded assetbundle : " + assetbundleName);
                return;
            }
            var curAssetbundle = GetAssetBundleCache(assetbundleName);
            var allAssetNames = assetsPathMapping.GetAllAssetNames(assetbundleName);
            for (int i = 0; i < allAssetNames.Count; i++)
            {
                var assetName = allAssetNames[i];
                if (IsAssetLoaded(assetName))
                {
                    continue;
                }

                var assetPath = AssetBundleUtility.PackagePathToAssetsPath(assetName);
                var asset = curAssetbundle == null ? null : curAssetbundle.LoadAsset(assetPath);
                AddAssetCache(assetName, asset);

#if UNITY_EDITOR
                // 说明:在Editor模拟时,Shader要重新指定
                var go = asset as GameObject;
                if (go != null)
                {
                    var renderers = go.GetComponentsInChildren<Renderer>();
                    for (int j = 0; j < renderers.Length; j++)
                    {
                        var mat = renderers[j].sharedMaterial;
                        if (mat == null)
                        {
                            continue;
                        }

                        var shader = mat.shader;
                        if (shader != null)
                        {
                            var shaderName = shader.name;
                            mat.shader = Shader.Find(shaderName);
                        }
                    }
                }
#endif
            }
        }

        public void ClearAssetsCache()
        {
            assetsCaching.Clear();
        }
        
        public ResourceWebRequester GetAssetBundleAsyncCreater(string assetbundleName)
        {
            ResourceWebRequester creater = null;
            webRequesting.TryGetValue(assetbundleName, out creater);
            return creater;
        }

        protected int GetReferenceCount(string assetbundleName)
        {
            int count = 0;
            assetbundleRefCount.TryGetValue(assetbundleName, out count);
            return count;
        }

        protected int IncreaseReferenceCount(string assetbundleName)
        {
            int count = 0;
            assetbundleRefCount.TryGetValue(assetbundleName, out count);
            count++;
            assetbundleRefCount[assetbundleName] = count;
            return count;
        }

        protected int DecreaseReferenceCount(string assetbundleName)
        {
            int count = 0;
            assetbundleRefCount.TryGetValue(assetbundleName, out count);
            count--;
            assetbundleRefCount[assetbundleName] = count;
            return count;
        }

        protected bool CreateAssetBundleAsync(string assetbundleName)
        {
            if (IsAssetBundleLoaded(assetbundleName) || webRequesting.ContainsKey(assetbundleName))
            {
                return false;
            }

            var creater = ResourceWebRequester.Get();
            var url = AssetBundleUtility.GetAssetBundleFileUrl(assetbundleName);
            creater.Init(assetbundleName, url);
            webRequesting.Add(assetbundleName, creater);
            webRequesterQueue.Enqueue(creater);
            // 创建器持有的引用:创建器对每个ab来说是全局唯一的
            IncreaseReferenceCount(assetbundleName);
            return true;
        }

        // 异步请求Assetbundle资源,AB是否缓存取决于是否设置为常驻包,Assets一律缓存,处理依赖
        public BaseAssetBundleAsyncLoader LoadAssetBundleAsync(string assetbundleName)
        {
#if UNITY_EDITOR
            if (AssetBundleConfig.IsEditorMode)
            {
                return new EditorAssetBundleAsyncLoader(assetbundleName);
            }
#endif

            var loader = AssetBundleAsyncLoader.Get();
            prosessingAssetBundleAsyncLoader.Add(loader);
            if (manifest != null)
            {
                string[] dependancies = manifest.GetAllDependencies(assetbundleName);
                for (int i = 0; i < dependancies.Length; i++)
                {
                    var dependance = dependancies[i];
                    if (!string.IsNullOrEmpty(dependance) && dependance != assetbundleName)
                    {
                        CreateAssetBundleAsync(dependance);
                        // ab缓存对依赖持有的引用
                        IncreaseReferenceCount(dependance);
                    }
                }
                loader.Init(assetbundleName, dependancies);
            }
            else
            {
                loader.Init(assetbundleName, null);
            }
            CreateAssetBundleAsync(assetbundleName);
            // 加载器持有的引用:同一个ab能同时存在多个加载器,等待ab创建器完成
            IncreaseReferenceCount(assetbundleName);
            return loader;
        }

        // 从服务器下载网页内容,需提供完整url
        public ResourceWebRequester DownloadWebResourceAsync(string url)
        {
            var creater = ResourceWebRequester.Get();
            creater.Init(url, url, true);
            webRequesting.Add(url, creater);
            webRequesterQueue.Enqueue(creater);
            return creater;
        }

        // 从资源服务器下载非Assetbundle资源
        public ResourceWebRequester DownloadAssetFileAsync(string filePath)
        {
            if (string.IsNullOrEmpty(DownloadUrl))
            {
                Debug.LogError("You should set download url first!!!");
                return null;
            }

            var creater = ResourceWebRequester.Get();
            var url = DownloadUrl + filePath;
            creater.Init(filePath, url, true);
            webRequesting.Add(filePath, creater);
            webRequesterQueue.Enqueue(creater);
            return creater;
        }

        // 从资源服务器下载Assetbundle资源,不缓存,无依赖
        public ResourceWebRequester DownloadAssetBundleAsync(string filePath)
        {
            // 如果ResourceWebRequester升级到使用UnityWebRequester,那么下载AB和下载普通资源需要两个不同的DownLoadHandler
            // 兼容升级的可能性,这里也做一下区分
            return DownloadAssetFileAsync(filePath);
        }

        // 本地异步请求非Assetbundle资源
        public ResourceWebRequester RequestAssetFileAsync(string filePath, bool streamingAssetsOnly = true)
        {
            var creater = ResourceWebRequester.Get();
            string url = null;
            if (streamingAssetsOnly)
            {
                url = AssetBundleUtility.GetStreamingAssetsFilePath(filePath);
            }
            else
            {
                url = AssetBundleUtility.GetAssetBundleFileUrl(filePath);
            }
            creater.Init(filePath, url, true);
            webRequesting.Add(filePath, creater);
            webRequesterQueue.Enqueue(creater);
            return creater;
        }

        // 本地异步请求Assetbundle资源,不缓存,无依赖
        public ResourceWebRequester RequestAssetBundleAsync(string assetbundleName)
        {
            var creater = ResourceWebRequester.Get();
            var url = AssetBundleUtility.GetAssetBundleFileUrl(assetbundleName);
            creater.Init(assetbundleName, url, true);
            webRequesting.Add(assetbundleName, creater);
            webRequesterQueue.Enqueue(creater);
            return creater;
        }

        public void UnloadAssetBundleDependencies(string assetbundleName)
        {
            if (manifest != null)
            {
                string[] dependancies = manifest.GetAllDependencies(assetbundleName);
                for (int i = 0; i < dependancies.Length; i++)
                {
                    var dependance = dependancies[i];
                    if (!string.IsNullOrEmpty(dependance) && dependance != assetbundleName)
                    {
                        UnloadAssetBundle(dependance);
                    }
                }
            }
        }

        protected bool UnloadAssetBundle(string assetbundleName, bool unloadResident = false, bool unloadAllLoadedObjects = false)
        {
            int count = GetReferenceCount(assetbundleName);
            if (count <= 0)
            {
                return false;
            }

            count = DecreaseReferenceCount(assetbundleName);
            if (count > 0)
            {
                return false;
            }

            var assetbundle = GetAssetBundleCache(assetbundleName);
            var isResident = IsAssetBundleResident(assetbundleName);
            if (assetbundle != null)
            {
                if (!isResident || isResident && unloadResident)
                {
                    assetbundle.Unload(unloadAllLoadedObjects);
                    RemoveAssetBundleCache(assetbundleName);
                    UnloadAssetBundleDependencies(assetbundleName);
                    return true;
                }
            }
            return false;
        }

        public bool TryUnloadAssetBundle(string assetbundleName, bool unloadAllLoadedObjects = false)
        {
            int count = GetReferenceCount(assetbundleName);
            if (count > 0)
            {
                return false;
            }

            return UnloadAssetBundle(assetbundleName, true, unloadAllLoadedObjects);
        }

        public void UnloadUnusedAssetBundles(bool unloadResident = false, bool unloadAllLoadedObjects = false)
        {
            int unloadCount = 0;
            bool hasDoUnload = false;
            do
            {
                hasDoUnload = false;
                var iter = assetbundleRefCount.GetEnumerator();
                while (iter.MoveNext())
                {
                    var assetbundleName = iter.Current.Key;
                    var referenceCount = iter.Current.Value;
                    if (referenceCount <= 0)
                    {
                        var result = UnloadAssetBundle(assetbundleName, unloadResident, unloadAllLoadedObjects);
                        if (result)
                        {
                            unloadCount++;
                            hasDoUnload = true;
                        }
                    }
                }
            } while (hasDoUnload);
        }

        public bool MapAssetPath(string assetPath, out string assetbundleName, out string assetName)
        {
            return assetsPathMapping.MapAssetPath(assetPath, out assetbundleName, out assetName);
        }

        public BaseAssetAsyncLoader LoadAssetAsync(string assetPath, System.Type assetType)
        {
#if UNITY_EDITOR
            if (AssetBundleConfig.IsEditorMode)
            {
                string path = AssetBundleUtility.PackagePathToAssetsPath(assetPath); 
                UnityEngine.Object target = AssetDatabase.LoadAssetAtPath(path, assetType);
                return new EditorAssetAsyncLoader(target);
            }
#endif

            string assetbundleName = null;
            string assetName = null;
            bool status = MapAssetPath(assetPath, out assetbundleName, out assetName);
            if (!status)
            {
                Debug.LogError("No assetbundle at asset path :" + assetPath);
                return null;
            }

            var loader = AssetAsyncLoader.Get();
            prosessingAssetAsyncLoader.Add(loader);
            if (IsAssetLoaded(assetName))
            {
                loader.Init(assetName, GetAssetCache(assetName));
                return loader;
            }
            else
            {
                var assetbundleLoader = LoadAssetBundleAsync(assetbundleName);
                loader.Init(assetName, assetbundleLoader);
                return loader;
            }
        }
        
        void Update()
        {
            OnProsessingWebRequester();
            OnProsessingAssetBundleAsyncLoader();
            OnProsessingAssetAsyncLoader();
        }

        void OnProsessingWebRequester()
        {
            for (int i = prosessingWebRequester.Count - 1; i >= 0; i--)
            {
                var creater = prosessingWebRequester[i];
                creater.Update();
                if (creater.IsDone())
                {
                    prosessingWebRequester.RemoveAt(i);
                    webRequesting.Remove(creater.assetbundleName);
                    UnloadAssetBundle(creater.assetbundleName);
                    if (creater.noCache)
                    {
                        return;
                    }
                    // 说明:有错误也缓存下来,只不过资源为空
                    // 1、避免再次错误加载
                    // 2、如果不存下来加载器将无法判断什么时候结束
                    AddAssetBundleCache(creater.assetbundleName, creater.assetbundle);
                    creater.Dispose();
                }
            }
            int slotCount = prosessingWebRequester.Count;
            while (slotCount < MAX_ASSETBUNDLE_CREATE_NUM && webRequesterQueue.Count > 0)
            {
                var creater = webRequesterQueue.Dequeue();
                creater.Start();
                prosessingWebRequester.Add(creater);
                slotCount++;
            }
        }
        
        void OnProsessingAssetBundleAsyncLoader()
        {
            for (int i = prosessingAssetBundleAsyncLoader.Count - 1; i >= 0; i--)
            {
                var loader = prosessingAssetBundleAsyncLoader[i];
                loader.Update();
                if (loader.IsDone())
                {
                    UnloadAssetBundle(loader.assetbundleName);
                    prosessingAssetBundleAsyncLoader.RemoveAt(i);
                }
            }
        }

        void OnProsessingAssetAsyncLoader()
        {
            for (int i = prosessingAssetAsyncLoader.Count - 1; i >= 0; i--)
            {
                var loader = prosessingAssetAsyncLoader[i];
                loader.Update();
                if (loader.IsDone())
                {
                    prosessingAssetAsyncLoader.RemoveAt(i);
                }
            }
        }

#if UNITY_EDITOR
        
        public HashSet<string> GetAssetbundleResident()
        {
            return assetbundleResident;
        }

        
        public ICollection<string> GetAssetbundleCaching()
        {
            return assetbundlesCaching.Keys;
        }

        
        public Dictionary<string, ResourceWebRequester> GetWebRequesting()
        {
            return webRequesting;
        }

        
        public Queue<ResourceWebRequester> GetWebRequestQueue()
        {
            return webRequesterQueue;
        }

        
        public List<ResourceWebRequester> GetProsessingWebRequester()
        {
            return prosessingWebRequester;
        }

        public List<AssetBundleAsyncLoader> GetProsessingAssetBundleAsyncLoader()
        {
            return prosessingAssetBundleAsyncLoader;
        }

        public List<AssetAsyncLoader> GetProsessingAssetAsyncLoader()
        {
            return prosessingAssetAsyncLoader;
        }

        public string GetAssetBundleName(string assetName)
        {
            return assetsPathMapping.GetAssetBundleName(assetName);
        }

        public int GetAssetCachingCount()
        {
            return assetsCaching.Count;
        }

        public Dictionary<string, List<string>> GetAssetCaching()
        {
            var assetbundleDic = new Dictionary<string, List<string>>();
            List<string> assetNameList = null;
            
            var iter = assetsCaching.GetEnumerator();
            while (iter.MoveNext())
            {
                var assetName = iter.Current.Key;
                var assetbundleName = assetsPathMapping.GetAssetBundleName(assetName);
                assetbundleDic.TryGetValue(assetbundleName, out assetNameList);
                if (assetNameList == null)
                {
                    assetNameList = new List<string>();
                }
                assetNameList.Add(assetName);
                assetbundleDic[assetbundleName] = assetNameList;
            }
            return assetbundleDic;
        }

        public int GetAssetbundleRefrenceCount(string assetbundleName)
        {
            return GetReferenceCount(assetbundleName);
        }

        public int GetAssetbundleDependenciesCount(string assetbundleName)
        {
            string[] dependancies = manifest.GetAllDependencies(assetbundleName);
            int count = 0;
            for (int i = 0; i < dependancies.Length; i++)
            {
                var cur = dependancies[i];
                if (!string.IsNullOrEmpty(cur) && cur != assetbundleName)
                {
                    count++;
                }
            }
            return count;
        }

        public List<string> GetAssetBundleRefrences(string assetbundleName)
        {
            List<string> refrences = new List<string>();
            var cachingIter = assetbundlesCaching.GetEnumerator();
            while (cachingIter.MoveNext())
            {
                var curAssetbundleName = cachingIter.Current.Key;
                if (curAssetbundleName == assetbundleName)
                {
                    continue;
                }
                string[] dependancies = manifest.GetAllDependencies(curAssetbundleName);
                for (int i = 0; i < dependancies.Length; i++)
                {
                    var dependance = dependancies[i];
                    if (dependance == assetbundleName)
                    {
                        refrences.Add(curAssetbundleName);
                    }
                }
            }

            var requestingIter = webRequesting.GetEnumerator();
            while (requestingIter.MoveNext())
            {
                var curAssetbundleName = requestingIter.Current.Key;
                if (curAssetbundleName == assetbundleName)
                {
                    continue;
                }

                string[] dependancies = manifest.GetAllDependencies(curAssetbundleName);
                for (int i = 0; i < dependancies.Length; i++)
                {
                    var dependance = dependancies[i];
                    if (dependance == assetbundleName)
                    {
                        refrences.Add(curAssetbundleName);
                    }
                }
            }
            return refrences;
        }

        public List<string> GetWebRequesterRefrences(string assetbundleName)
        {
            List<string> refrences = new List<string>();
            var iter = webRequesting.GetEnumerator();
            while (iter.MoveNext())
            {
                var curAssetbundleName = iter.Current.Key;
                var webRequster = iter.Current.Value;
                if (curAssetbundleName == assetbundleName)
                {
                    refrences.Add(webRequster.Sequence.ToString());
                    continue;
                }
            }
            return refrences;
        }

        public List<string> GetAssetBundleLoaderRefrences(string assetbundleName)
        {
            List<string> refrences = new List<string>();
            var iter = prosessingAssetBundleAsyncLoader.GetEnumerator();
            while (iter.MoveNext())
            {
                var curAssetbundleName = iter.Current.assetbundleName;
                var curLoader = iter.Current;
                if (curAssetbundleName == assetbundleName)
                {
                    refrences.Add(curLoader.Sequence.ToString());
                }
            }
            return refrences;
        }
#endif
    }
}

AssetBundle编辑器扩展

Debug模块

show_fps

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class show_fps : MonoBehaviour {
    // 固定的一个时间间隔
    private float time_delta = 0.5f;
    // Time.realtimeSinceStartup: 指的是我们当前从启动开始到现在运行的时间,单位(s)
    private float prev_time = 0.0f; // 上一次统计FPS的时间;
    private float fps = 0.0f; // 计算出来的FPS的值;
    private int i_frames = 0; // 累计我们刷新的帧数;

    // GUI显示;
    private GUIStyle style;

    void Awake() {
        // 假设CPU 100% 工作的状态下FPS 300,
        // 当你设置了这个以后,他就维持在60FPS左右,不会继续冲高;
        // -1, 游戏引擎就会不段的刷新我们的画面,有多高,刷多高; 60FPS左右;
        Application.targetFrameRate = 60;
    }

	// Use this for initialization
	void Start () {
        this.prev_time = Time.realtimeSinceStartup;
        this.style = new GUIStyle();
        this.style.fontSize = 15;
        this.style.normal.textColor = new Color(255, 255, 255);
	}

    void OnGUI() {
        GUI.Label(new Rect(0, Screen.height - 20, 200, 200), "FPS:" + this.fps.ToString("f2"), this.style);
    }
	// Update is called once per frame
    // 每次游戏刷新的时候就会调用;
	void Update () {
        this.i_frames ++;

        if (Time.realtimeSinceStartup >= this.prev_time + this.time_delta) {
            this.fps = ((float)this.i_frames) / (Time.realtimeSinceStartup - this.prev_time);
            this.prev_time = Time.realtimeSinceStartup;
            this.i_frames = 0; // 重新累积我们的FPS
        }
	}
}

DebugManager(如果是编辑器模式,显示调试信息到界面)


#define USE_DEBUG 

using UnityEngine;
using System;
using System.Collections.Generic;



#if RELEASE_BUILD

//发布的时候重载默认Debug
public static class Debug
{
    public static void Log(object message) { }
    public static void Log(object message, object context) { }
    public static void LogError(object message) { }
    public static void LogError(object message, object context) { }
    public static void LogException(Exception exception) { }
    public static void LogException(Exception exception, object context) { }
    public static void LogWarning(object message) { }
    public static void LogWarning(object message, object context) { }
    public static void DrawLine(Vector3 start, Vector3 end) { }
    public static void DrawLine(Vector3 start, Vector3 end, Color color) { }
}
#endif


//重载默认Debug
public static class Debugger
{
    public static void Log(object message)
    {
        #if RELEASE_BUILD
        #else
            if (Application.platform == RuntimePlatform.Android || Application.platform == RuntimePlatform.IPhonePlayer)
                DebugManager.Log(message.ToString());
            else
                UnityEngine.Debug.Log(message.ToString());

         #endif
    }

    public static void LogError(object message)
    {
        #if RELEASE_BUILD
        #else
        if (Application.platform == RuntimePlatform.Android || Application.platform == RuntimePlatform.IPhonePlayer)
            DebugManager.Log(message.ToString());
        else
            UnityEngine.Debug.LogError(message.ToString());
        #endif
    }


    public static void LogWarning(object message)
    {
        #if RELEASE_BUILD
        #else
        if (Application.platform == RuntimePlatform.Android || Application.platform == RuntimePlatform.IPhonePlayer)
            DebugManager.Log(message.ToString());
        else
            UnityEngine.Debug.LogWarning(message.ToString());
        #endif
    }
}



//[yaz]调试管理器
public class DebugManager : MonoBehaviour 
{
    // public bool DebugEffect = false;
    static public bool DebugInfo = false;
    static public Int32 DebugCount = 0;

    //保存错误信息列表
    static public List<string> errorInfoList = new List<string>();

	// Use this for initialization
	void Start ()
    {
	}
	
	// Update is called once per frame
	void Update () 
    {
       
    }

    public static void Switch()
    {
        DebugInfo = !DebugInfo;
        if (DebugInfo)
        {
            ++DebugCount;
            errorInfoList.Clear();
            errorInfoList.Add(DebugCount.ToString());
        }
    }

    public static void Log(string str)
    {
        errorInfoList.Add(str);
    }

    //错误信息窗口
    public Rect errorInfoWindowRect = new Rect(80, 20, 800, 2000);

    void OnGUI()
    {
        GUILayout.Space(40);
        if (GUILayout.Button("ShowError"))
        {
            DebugInfo = !DebugInfo;
        }
        else if (GUILayout.Button("Clear"))
        {
            errorInfoList.Clear();
        }

        //调试错误信息
        if (DebugInfo)
        {
            errorInfoWindowRect = GUILayout.Window(1, errorInfoWindowRect, DebugErrorWindow, "Debug Error Window");            
        }
    }

    //错误信息显示窗口
    private Vector2 errorInfoPos = new Vector2(0, 0);
    void DebugErrorWindow(int windowID)
    {      
        errorInfoPos = GUILayout.BeginScrollView(errorInfoPos, false, true, GUILayout.Width(800), GUILayout.Height(600));

        GUILayout.Space(30);
        GUILayout.BeginVertical();

        foreach (string str in errorInfoList)
        {
            GUILayout.Label(str, GUILayout.Width(800));
        }
    
        //GUI.color = Color.white;
        GUILayout.EndVertical();
        GUILayout.EndScrollView();
    }


}

声音管理模块

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SoundMgr : UnitySingleton<SoundMgr>
{
    const int MAX_SOUNDS = 8;
    private AudioSource music;
    private AudioSource[] sounds = new AudioSource[MAX_SOUNDS];

    
    private int music_muted;
    private int sound_muted;

    private float music_volume;
    private float sound_volume;

    private int now_soundid = 0;

	public override void Awake () {
        base.Awake();

        this.music_muted = PlayerPrefs.GetInt("music_muted", 0);
        this.sound_muted = PlayerPrefs.GetInt("sound_muted", 0);
        this.music_volume = PlayerPrefs.GetFloat("music_volume", 1.0f);
        
        this.sound_volume = PlayerPrefs.GetFloat("sound_volume", 1.0f);


        this.music = this.gameObject.AddComponent<AudioSource>();
        this.music.mute = (this.music_muted == 1);
        this.music.volume = (this.music_volume);

        for (int i = 0; i < MAX_SOUNDS; i++) {
            this.sounds[i] = this.gameObject.AddComponent<AudioSource>();
            this.sounds[i].mute = (this.sound_muted == 1);
            this.sounds[i].volume = (this.sound_volume);
        }
	}

    public void play_music(string url, bool loop = true)
    {
        AudioClip clip =  ResMgr.Instance.GetAssetCache<AudioClip>(url) as AudioClip;
        // AudioClip clip = (AudioClip)UnityEditor.AssetDatabase.LoadAssetAtPath("Assets/AssetsPackage/Sounds/bgm_scene1.ogg", typeof(AudioClip));
        this.music.clip = clip;
        this.music.loop = loop;
        this.music.Play();
    }

    public int play_sound(string url, bool loop = false)
    {
        int soundid = this.now_soundid;
        AudioClip clip = (AudioClip)ResMgr.Instance.GetAssetCache<AudioClip>(url);

        this.sounds[this.now_soundid].clip = clip;
        this.sounds[this.now_soundid].loop = loop;
        this.sounds[this.now_soundid].Play();

        this.now_soundid ++;
        this.now_soundid = (this.now_soundid >= this.sounds.Length) ? 0 : this.now_soundid;
        return soundid;
    }

    public int play_one_shot(string url, bool loop = false)
    {
        int soundid = this.now_soundid;
        AudioClip clip = (AudioClip)ResMgr.Instance.GetAssetCache<AudioClip>(url);

        this.sounds[this.now_soundid].clip = clip;
        this.sounds[this.now_soundid].loop = loop;
        this.sounds[this.now_soundid].PlayOneShot(clip);

        this.now_soundid++;
        this.now_soundid = (this.now_soundid >= this.sounds.Length) ? 0 : this.now_soundid;
        return soundid;
    }

    public void stop_sound(int soundid)
    {
        if (soundid < 0 || soundid >= this.sounds.Length) {
            return;
        }

        this.sounds[soundid].Stop();
        this.sounds[this.now_soundid].clip = null;
    }

    public void stop_music()
    {
        this.music.Stop();
        this.music.clip = null;
    }

    public void stop_all_sounds()
    {
        for (int i = 0; i < this.sounds.Length; i++) {
            this.sounds[i].Stop();
            this.sounds[i].clip = null;
        }
    }

    public void set_music_mute(bool mute)
    {
        if (mute == (this.music_muted == 1)) {
            return;
        }

        this.music_muted = mute ? 1 : 0;
        this.music.mute = mute;

        PlayerPrefs.SetInt("music_muted", this.music_muted);
    }

    public void set_sound_mute(bool mute)
    {
        if (mute == (this.sound_muted == 1)) {
            return;
        }

        this.sound_muted = (mute) ? 1 : 0;
        for (int i = 0; i < MAX_SOUNDS; i++) {
            this.sounds[i].mute = mute;
        }

        PlayerPrefs.SetInt("sound_muted", this.sound_muted);
    }

    public void set_music_volume(float value)
    {
        if (value < 0.0f || value > 1.0f) {
            return;
        }

        this.music_volume = value;
        this.music.volume = this.music_volume;
        PlayerPrefs.SetFloat("music_volume", this.music_volume);
    }

    public void set_sound_volume(float value)
    {
        if (value < 0.0f || value > 1.0f) {
            return;
        }

        this.sound_volume = value;
        for (int i = 0; i < MAX_SOUNDS; i++) {
            this.sounds[i].volume = value;
        }

        PlayerPrefs.SetFloat("sound_volume", this.sound_volume);
    }

    public bool musicMuted {
        get {
            return (this.music_muted == 1);
        }
    }

    public bool soundMuted {
        get {
            return (this.sound_muted == 1);
        }
    }

    public float musicVolume {
        get {
            return this.music_volume;
        }
    }

    public float soundVolume {
        get {
            return this.sound_volume;
        }
    }
}

UI框架模块

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Events;
using System;

// 这是一个UI脚本的编写模板
public class UICtrl : MonoBehaviour {
    public Dictionary<string, GameObject> view = new Dictionary<string, GameObject>();

    private void load_all_object(GameObject root, string path) {
        foreach (Transform tf in root.transform) {
            if (this.view.ContainsKey(path + tf.gameObject.name)) {
                // Debugger.LogWarning("Warning object is exist:" + path + tf.gameObject.name + "!");
                continue;
            }
            this.view.Add(path + tf.gameObject.name, tf.gameObject);
            load_all_object(tf.gameObject, path + tf.gameObject.name + "/");
        }

    }

    public virtual void Awake() {
        this.load_all_object(this.gameObject, "");
    }


    public void AddButtonListener(string view_name, UnityAction onclick) {
        Button bt = this.view[view_name].GetComponent<Button>();
        if (bt == null)
        {
            Debugger.LogWarning("UI_manager add_button_listener: not Button Component!");
            return;
        }

        bt.onClick.AddListener(onclick);
    }

    public void AddSliderListener(string view_name, UnityAction<float> on_value_changle) {
        Slider s = this.view[view_name].GetComponent<Slider>();
        if (s == null)
        {
            Debugger.LogWarning("UI_manager add_slider_listener: not Slider Component!");
            return;
        }

        s.onValueChanged.AddListener(on_value_changle);
    }
}
// UI管理类
public class UIMgr : UnitySingleton<UIMgr> {
    public GameObject canvas;
    private Transform uiBloodRoot = null;
    public override void Awake() {
        base.Awake();
        this.canvas = GameObject.Find("Canvas");
        if (this.canvas == null) {
            Debugger.LogError("UI manager load  Canvas failed!!!!!");
        }

        this.uiBloodRoot = this.canvas.transform.Find("UIBloodRoot");
    }

    public UICtrl ShowUIView(string name) {
        string path = "GUI/UI_Prefabs/" + name + ".prefab";
        GameObject ui_prefab = (GameObject)ResMgr.Instance.GetAssetCache<GameObject>(path);
        GameObject ui_view = GameObject.Instantiate(ui_prefab);
        ui_view.name = ui_prefab.name;
        ui_view.transform.SetParent(this.canvas.transform, false);

        int lastIndex = name.LastIndexOf("/");
        if (lastIndex > 0) {
            name = name.Substring(lastIndex + 1);
        }
        
        Type type = Type.GetType(name + "_UICtrl");
        UICtrl ctrl = (UICtrl)ui_view.AddComponent(type);

        return ctrl;
    }

    public GameObject ShowSubView(string name, GameObject parent = null) {
        string path = "GUI/Prefabs/" + name + ".prefab";
        GameObject ui_prefab = (GameObject)ResMgr.Instance.GetAssetCache<GameObject>(path);
        GameObject ui_view = GameObject.Instantiate(ui_prefab);
        ui_view.name = ui_prefab.name;
        if (parent == null) {
            parent = this.canvas;
        }
        ui_view.transform.SetParent(parent.transform, false);
        return ui_view;
    }

    public void RemoveUIView(string name) {
        int lastIndex = name.LastIndexOf("/");
        if (lastIndex > 0) {
            name = name.Substring(lastIndex + 1);
        }

        Transform view = this.canvas.transform.Find(name);
        if (view) {
            GameObject.DestroyImmediate(view.gameObject);
        }
    }

    public void RemoveAllViews() {
        List<Transform> children = new List<Transform>();
        foreach (Transform t in this.canvas.transform) {
            if (t.gameObject.name == "UIBloodRoot") {
                continue;
            }
            children.Add(t);
        }

        for (int i = 0; i < children.Count; i++) {
            GameObject.DestroyImmediate(children[0].gameObject);
        }
    }
}

网络模块

网络连接管理,拆包封包

using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

enum State{
    Disconnect = 0,
    Connecting = 1,
    Connected = 2,
}

public class NetMgr : UnitySingleton<NetMgr>
{
    // tcp
    [SerializeField]
    private string server_ip = "127.0.0.1";
    [SerializeField]
    private int port = 6080;

    private Socket client_socket = null;
    private bool is_connect = false;
    private Thread recv_thread = null;

    private const int RECV_LEN = 8192;
    private byte[] recv_buf = new byte[RECV_LEN];
    private int recved;
    private byte[] long_pkg = null;
    private int long_pkg_size = 0;
    // end

    private State state = State.Disconnect;
    // 接收消息的队列
    private Queue<cmd_msg> net_events = new Queue<cmd_msg>();
    public delegate void net_message_handler(cmd_msg msg);
    // 消息事件管理
    private Dictionary<int, net_message_handler> event_listeners = new Dictionary<int, net_message_handler>(); 

	void Start () {
        this.state = State.Disconnect;
        this.connect_to_server();
	}

    void OnDestroy() {
        this.close();
    }

    void OnApplicaitonQuit() {
        this.close();
    }
	
	void Update () {
        
        // 如果失去连接,重新连接
        if (this.state == State.Disconnect) {
            this.connect_to_server();
            return;
        }
        // 从队列中推出事件,调用对应的委托事件
        lock (this.net_events) {
            while (this.net_events.Count > 0) { 
                cmd_msg msg = this.net_events.Dequeue();
                if (this.event_listeners.ContainsKey(msg.stype)) {
                    this.event_listeners[msg.stype](msg);
                }
            }
        }
	}

    void on_conntect_timeout() { 
    }

    void on_connect_error(string err) {
        this.state = State.Disconnect;
    }

    void connect_to_server() {
        if (this.state != State.Disconnect) {
            return;
        }

        this.state = State.Connecting;

        try {
            this.client_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPAddress ipAddress = IPAddress.Parse(this.server_ip);
            IPEndPoint ipEndpoint = new IPEndPoint(ipAddress, this.port);

            this.client_socket.BeginConnect(ipEndpoint, new AsyncCallback(this.on_connected), this.client_socket);
        }
        catch (System.Exception e) {
            Debug.Log(e.ToString());
            this.on_connect_error(e.ToString());
        }
    }

    void on_recv_cmd(byte[] data, int start, int data_len) {
        cmd_msg msg;
        proto_man.unpack_cmd_msg(data, start, data_len, out msg);
        if (msg != null) { 
            lock (this.net_events) { // recv thread
                this.net_events.Enqueue(msg);
            }
        }
    }

    void on_recv_tcp_data() { 
        byte[] pkg_data = (this.long_pkg != null) ? this.long_pkg : this.recv_buf;
        while (this.recved > 0) {
			int pkg_size = 0;
			int head_size = 0;

			if (!tcp_packer.read_header(pkg_data, this.recved, out pkg_size, out head_size)) {
				break;
			}

			if (this.recved < pkg_size) {
				break;
			}

            int raw_data_start = head_size;
            int raw_data_len = pkg_size - head_size;

            on_recv_cmd(pkg_data, raw_data_start, raw_data_len);
			
			if (this.recved > pkg_size) {
                this.recv_buf = new byte[RECV_LEN];
				Array.Copy(pkg_data, pkg_size, this.recv_buf, 0, this.recved - pkg_size);
                pkg_data = this.recv_buf;
			}

			this.recved -= pkg_size;

			if (this.recved == 0 && this.long_pkg != null) {
				this.long_pkg = null;
                this.long_pkg_size = 0;
			}
		}
    }

    void thread_recv_worker() {
        if (this.is_connect == false) {
            return;
        }

        while (true) {
            if (!this.client_socket.Connected) {
                break;
            }

            try
            {
                int recv_len = 0;
                if (this.recved < RECV_LEN) {
                    recv_len = this.client_socket.Receive(this.recv_buf, this.recved, RECV_LEN - this.recved, SocketFlags.None);
                }
                else {
                    if (this.long_pkg == null) {
                        int pkg_size;
                        int head_size;
                        tcp_packer.read_header(this.recv_buf, this.recved, out pkg_size,  out head_size);
                        this.long_pkg_size = pkg_size;
                        this.long_pkg = new byte[pkg_size];
                        Array.Copy(this.recv_buf, 0, this.long_pkg, 0, this.recved);
                    }
                    recv_len = this.client_socket.Receive(this.long_pkg, this.recved, this.long_pkg_size - this.recved, SocketFlags.None);
                }

                if (recv_len > 0) {
                    this.recved += recv_len;
                    this.on_recv_tcp_data();
                }
            }
            catch (System.Exception e) {
                Debug.Log(e.ToString());
                if (this.client_socket != null && this.client_socket.Connected) {
                    if(this.client_socket.Connected) {
                        this.client_socket.Disconnect(true);
                    }
                    // this.client_socket.Shutdown(SocketShutdown.Both);
                    this.client_socket.Close();
                }
                this.client_socket = null; 
                this.is_connect = false;
                this.state = State.Disconnect;
                break;
            }
        }

        Debugger.Log("exit recv thread");
    }

    void on_connected(IAsyncResult iar) {
        try {
            Socket client = (Socket)iar.AsyncState;
            client.EndConnect(iar);

            this.state = State.Connected;

            this.is_connect = true;
            this.recv_thread = new Thread(new ThreadStart(this.thread_recv_worker));
            this.recv_thread.Start();

            Debug.Log("connect to server success" + this.server_ip + ":" + this.port + "!");
        }
        catch (System.Exception e) {
            Debug.Log(e.ToString());
            this.on_connect_error(e.ToString());
            this.is_connect = false;
        }
    }

    void close() {
        if (!this.is_connect) {
            return;
        }

        this.is_connect = false;
        this.state = State.Disconnect;

        // abort recv thread
        if (this.recv_thread != null) {
            this.recv_thread.Interrupt();
            this.recv_thread.Abort();
            this.recv_thread = null;
        }
        // end

        if (this.client_socket != null && this.client_socket.Connected) {
            this.client_socket.Close();
            this.client_socket = null;
        }
    }
    
    private void on_send_data(IAsyncResult iar)
    {
        try
        {
            Socket client = (Socket)iar.AsyncState;
            client.EndSend(iar);
        }
        catch (System.Exception e)
        {
            Debug.Log(e.ToString());
        }
    }

    public void send_protobuf_cmd(int stype, int ctype, ProtoBuf.IExtensible body) {
        byte[] cmd_data = proto_man.pack_protobuf_cmd(stype, ctype, body);
        if (cmd_data == null) {
            return;
        }

        byte[]tcp_pkg = tcp_packer.pack(cmd_data);

        // end 
        this.client_socket.BeginSend(tcp_pkg, 0, tcp_pkg.Length, SocketFlags.None, new AsyncCallback(this.on_send_data), this.client_socket);
        // this.client_socket.Send(tcp_pkg);
        // end 
    }

    public void send_json_cmd(int stype, int ctype, string json_body)
    {
        byte[] cmd_data = proto_man.pack_json_cmd(stype, ctype, json_body);
        if (cmd_data == null) {
            return;
        }

        byte[] tcp_pkg = tcp_packer.pack(cmd_data);
        // end 
        this.client_socket.BeginSend(tcp_pkg, 0, tcp_pkg.Length, SocketFlags.None, new AsyncCallback(this.on_send_data), this.client_socket);
        // end 
    }

    public void add_service_listener(int stype, net_message_handler handler) {
        if (this.event_listeners.ContainsKey(stype)) {
            this.event_listeners[stype] += handler;
        }
        else {
            this.event_listeners.Add(stype, handler);
        }
    }

    public void remove_service_listener(int stype, net_message_handler handler) {
        if (!this.event_listeners.ContainsKey(stype)) {
            return;
        }
        this.event_listeners[stype] -= handler;
        if (this.event_listeners[stype] == null) {
            this.event_listeners.Remove(stype);
        }
    }
}

tcp封包函数

using System;

public class tcp_packer {
    private const int HEADER_SIZE = 2;
    public static byte[] pack(byte[] cmd_data) {
        int len = cmd_data.Length;
        if (len > 65535 - 2) {
            return null;
        }

        int cmd_len = len + HEADER_SIZE;
        byte[] cmd = new byte[cmd_len];
        data_viewer.write_ushort_le(cmd, 0, (ushort)cmd_len);
        data_viewer.write_bytes(cmd, HEADER_SIZE, cmd_data);

        return cmd;
    }

    public static bool read_header(byte[] data, int data_len, out int pkg_size, out int head_size) {
        pkg_size = 0;
        head_size = 0;

        if (data_len < 2) {
            return false;
        }

        head_size = 2;
        pkg_size = (data[0] | (data[1] << 8));

        return true;
    }
}

协议的封包拆包

using System;
using System.IO;
using System.Text;
using ProtoBuf;

public class cmd_msg {
    public int stype;
    public int ctype;
    public byte[] body; // protobuf, utf8 string json byte;
}

public class proto_man {
    private const int HEADER_SIZE = 8; // 2 stype, 2 ctype, 4utag, msg--> body;

    private static byte[] protobuf_serializer(ProtoBuf.IExtensible data)
    {
        using (MemoryStream m = new MemoryStream())
        {
            byte[] buffer = null;
            Serializer.Serialize(m, data);
            m.Position = 0;
            int length = (int)m.Length;
            buffer = new byte[length];
            m.Read(buffer, 0, length);
            return buffer;
        }
    }


    public static byte[] pack_protobuf_cmd(int stype, int ctype, ProtoBuf.IExtensible msg) {
        int cmd_len = HEADER_SIZE;
        byte[] cmd_body = null;
        if (msg != null) {
            cmd_body = protobuf_serializer(msg);
            cmd_len += cmd_body.Length;
        }
        
        byte[] cmd = new byte[cmd_len];
        // stype, ctype, utag(4保留), cmd_body
        data_viewer.write_ushort_le(cmd, 0, (ushort)stype);
        data_viewer.write_ushort_le(cmd, 2, (ushort)ctype);
        if (cmd_body != null) {
            data_viewer.write_bytes(cmd, HEADER_SIZE, cmd_body);
        }
        return cmd;
    }

    public static byte[] pack_json_cmd(int stype, int ctype, string json_msg) {
        int cmd_len = HEADER_SIZE;
        byte[] cmd_body = null;

        if (json_msg.Length > 0) { // utf8
            cmd_body = Encoding.UTF8.GetBytes(json_msg);
            cmd_len += cmd_body.Length;
        }

        byte[] cmd = new byte[cmd_len];
        // stype, ctype, utag(4保留), cmd_body
        data_viewer.write_ushort_le(cmd, 0, (ushort)stype);
        data_viewer.write_ushort_le(cmd, 2, (ushort)ctype);
        if (cmd_body != null)
        {
            data_viewer.write_bytes(cmd, HEADER_SIZE, cmd_body);
        }
        return cmd;
    }

    public static bool unpack_cmd_msg(byte[] data, int start, int cmd_len, out cmd_msg msg) {
        msg = new cmd_msg();
        msg.stype = data_viewer.read_ushort_le(data, start);
        msg.ctype = data_viewer.read_ushort_le(data, start + 2);

        int body_len = cmd_len - HEADER_SIZE;
        msg.body = new byte[body_len];
        Array.Copy(data, start + HEADER_SIZE, msg.body, 0, body_len);

        return true;
    }

    public static T protobuf_deserialize<T>(byte[] _data) {
        using (MemoryStream m = new MemoryStream(_data))
        {
            return Serializer.Deserialize<T>(m);
        }
    }

}

读写数据

using System;

public class data_viewer {
    public static void write_ushort_le(byte[] buf, int offset, ushort value) { 
        // value ---> byte[];
        byte[] byte_value = BitConverter.GetBytes(value);
        // 小尾,还是大尾?BitConvert 系统是小尾还是大尾;
        if (!BitConverter.IsLittleEndian) {
            Array.Reverse(byte_value);
        }

        Array.Copy(byte_value, 0, buf, offset, byte_value.Length);
    }

    public static void write_uint_le(byte[] buf, int offset, uint value) {
        // value ---> byte[];
        byte[] byte_value = BitConverter.GetBytes(value);
        // 小尾,还是大尾?BitConvert 系统是小尾还是大尾;
        if (!BitConverter.IsLittleEndian)
        {
            Array.Reverse(byte_value);
        }

        Array.Copy(byte_value, 0, buf, offset, byte_value.Length);
    }

    public static void write_bytes(byte[] dst, int offset, byte[] value) {
        Array.Copy(value, 0, dst, offset, value.Length);
    }

    public static ushort read_ushort_le(byte[] data, int offset) {
        int ret = (data[offset] | (data[offset + 1] << 8));

        return (ushort)ret;
    }
}

异步请求资源(热更新模块,本地、远程通杀)

using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// added by wsh @ 2017.12.22
/// 功能:资源异步请求,本地、远程通杀
/// 注意:
/// 1、Unity5.3官方建议用UnityWebRequest取代WWW:https://unity3d.com/cn/learn/tutorials/topics/best-practices/assetbundle-fundamentals?playlist=30089
/// 2、这里还是采用WWW,因为UnityWebRequest的Bug无数:
///     1)Unity5.3.5:http://blog.csdn.net/st75033562/article/details/52411197
///     2)Unity5.5:https://bitbucket.org/Unity-Technologies/assetbundledemo/pull-requests/25/feature-unitywebrequest/diff#comment-None
///     3)还有各个版本发行说明中关于UnityWebRequest的修复,如Unity5.4.1(5.4全系列版本基本都有修复这个API的Bug):https://unity3d.com/cn/unity/whats-new/unity-5.4.1
///     4)此外对于LZMA压缩,采用UnityWebRequest好处在于节省内存,性能上并不比WWW优越:https://docs.unity3d.com/530/Documentation/Manual/AssetBundleCompression.html
/// 3、LoadFromFile(Async)在Unity5.4以上支持streamingAsset目录加载资源,5.3.7和5.4.3以后支持LAMZ压缩,但是没法加载非Assetbundle资源
/// 4、另外,虽然LoadFromFile(Async)是加载ab最快的API,但是会延缓Asset加载的时间(读磁盘),如果ab尽量预加载,不考虑内存敏感问题,这个API意义就不大
/// </summary>

namespace AssetBundles
{
    public class ResourceWebRequester : ResourceAsyncOperation
    {
        static Queue<ResourceWebRequester> pool = new Queue<ResourceWebRequester>();
        static int sequence = 0;
        // protected WWW www = null;
        protected bool isOver = false;

        UnityEngine.Networking.UnityWebRequest www_req = null;
        public static ResourceWebRequester Get()
        {
            if (pool.Count > 0)
            {
                return pool.Dequeue();
            }
            else
            {
                return new ResourceWebRequester(++sequence);
            }
        }

        public static void Recycle(ResourceWebRequester creater)
        {
            pool.Enqueue(creater);
        }

        public ResourceWebRequester(int sequence)
        {
            Sequence = sequence;
        }

        public void Init(string name, string url, bool noCache = false)
        {
            assetbundleName = name;
            this.url = url;
            this.noCache = noCache;
            // www = null;
            www_req = null;
            isOver = false;
        }

        public int Sequence
        {
            get;
            protected set;
        }

        public bool noCache
        {
            get;
            protected set;
        }

        public string assetbundleName
        {
            get;
            protected set;
        }

        public string url
        {
            get;
            protected set;
        }

        public AssetBundle assetbundle
        {
            get
            {
                // return www.assetBundle;
                return AssetBundle.LoadFromMemory(www_req.downloadHandler.data);
            }
        }

        public byte[] bytes
        {
            get
            {
                // return www.bytes;
                return www_req.downloadHandler.data;
            }
        }

        public string text
        {
            get
            {
                // return www.text;
                return www_req.downloadHandler.text;
            }
        }

        public string error
        {
            get
            {
                // 注意:不能直接判空
                // 详见:https://docs.unity3d.com/530/Documentation/ScriptReference/WWW-error.html
                // return string.IsNullOrEmpty(www.error) ? null : www.error;
                return string.IsNullOrEmpty(www_req.error) ? null : www_req.error;
            }
        }

        public override bool IsDone()
        {
            return isOver;
        }

        public void Start()
        {
            // www = new WWW(url);
            www_req = UnityEngine.Networking.UnityWebRequest.Get(url);
            // if (www == null) {
            if (www_req == null) {
                Debug.LogError("New www failed!!!");
                isOver = true;
            }
            else
            {
                Debug.Log("Downloading : " + url);
                www_req.SendWebRequest();
            }
        }

        public override float Progress()
        {
            if (isDone)
            {
                return 1.0f;
            }

            // return www != null ? www.progress : 0f;

            return www_req != null ? www_req.downloadProgress : 0f;
        }

        public override void Update()
        {
            if (isDone)
            {
                return;
            }

            // isOver = www != null && (www.isDone || !string.IsNullOrEmpty(www.error));
            isOver = www_req != null && (www_req.isDone || !string.IsNullOrEmpty(www_req.error));
            if (!isOver)
            {
                return;
            }

            // if (www != null && !string.IsNullOrEmpty(www.error))
            if (www_req != null && !string.IsNullOrEmpty(www_req.error))
            {
                // Debug.LogError(www.error);
                Debug.LogError(www_req.error);
            }
        }

        public override void Dispose()
        {
            /*if (www != null)
            {
                www.Dispose();
                www = null;
            }*/

            if (www_req != null) {
                www_req.Dispose();
                www_req = null;
            }
            Recycle(this);
        }
    }
}

定时器模块

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

class TimerNode
{
    public TimerMgr.TimerHandler callback;
    public float duration; // 定时器触发的时间间隔;
    public float delay; // 第一次触发要隔多少时间;
    public int repeat; // 你要触发的次数;
    public float passedTime; // 这个Timer过去的时间;
    public object param; // 用户要传的参数
    public bool isRemoved; // 是否已经删除了
    public int timerId; // 标识这个timer的唯一Id号;
}

public class TimerMgr : UnitySingleton<TimerMgr>
{
    public delegate void TimerHandler(object param);

    private int autoIncId = 1;

    private Dictionary<int, TimerNode> timers = null;
    private List<TimerNode> removeTimers = null;
    private List<TimerNode> newAddTimers = null;

    // 初始化的入口
    public void Init()
    {
        this.timers = new Dictionary<int, TimerNode>();
        this.autoIncId = 1;

        this.removeTimers = new List<TimerNode>();
        this.newAddTimers = new List<TimerNode>();
    }

    private void Update()
    {
        float dt = Time.deltaTime;

        // 把新加进来的放入到我们的表里面来
        for (int i = 0; i < this.newAddTimers.Count; i++) {
            this.timers.Add(this.newAddTimers[i].timerId, this.newAddTimers[i]);
        }
        this.newAddTimers.Clear();
        // end

        foreach (TimerNode timer in this.timers.Values) {
            if (timer.isRemoved) {
                this.removeTimers.Add(timer);
                continue;
            }

            timer.passedTime += dt;
            if (timer.passedTime >= (timer.delay + timer.duration)) {
                // 做一次触发
                timer.callback(timer.param);
                timer.repeat--;
                timer.passedTime -= (timer.delay + timer.duration);
                timer.delay = 0; // 很重要;

                if (timer.repeat == 0)
                { // 触发次数结束,我们是不是要删除这个Timer; 
                    timer.isRemoved = true;
                    this.removeTimers.Add(timer);
                }
                // end 
            }
        }

        // 结束以后,清理掉要删除的Timer;
        for (int i = 0; i < this.removeTimers.Count; i++) {
            this.timers.Remove(this.removeTimers[i].timerId);
        }
        this.removeTimers.Clear();
        // end
    }
	
    public int ScheduleOnce(TimerHandler func, float delay)
    {
        return this.Schedule(func, 1, 0, delay);
    }

    public int ScheduleOnce(TimerHandler func, object param, float delay)
    {
        return this.Schedule(func, param, 1, delay);
    }

    // [repeat < 0 or repeat == 0 表示的是无限触发]
    public int Schedule(TimerHandler func, int repeat, float duration, float delay = 0.0f)
    {
        return Schedule(func, null, repeat, duration, delay);
    }

    // [repeat < 0 or repeat == 0 表示的是无限触发]
    public int Schedule(TimerHandler func, object param, int repeat, float duration, float delay = 0.0f)
    {
        TimerNode timer = new TimerNode();
        timer.callback = func;
        timer.param = param;
        timer.repeat = repeat;
        timer.duration = duration;
        timer.delay = delay;
        timer.passedTime = timer.duration;
        timer.isRemoved = false;

        timer.timerId = this.autoIncId;
        this.autoIncId++;

        // this.timers.Add(timer.timerId, timer);
        this.newAddTimers.Add(timer);
        return timer.timerId;
    }


    public void Unschedule(int timerId)
    {
        if (!this.timers.ContainsKey(timerId)) {
            return;
        }

        TimerNode timer = this.timers[timerId];
        timer.isRemoved = true;
    }
}

Excel表格处理模块

把Excel处理成CSV文件

Protobuf协议生成

Utils工具模块

检查版本

using UnityEngine;

/// <summary>
/// added by wsh @ 2018.01.04
/// 功能:构建相关配置和通用函数
/// </summary>

public class BuildUtils
{
    public const string PackageNameFileName = "package_name.bytes";
    public const string AppVersionFileName = "app_version.bytes";
    public const string ResVersionFileName = "res_version.bytes";
    public const string NoticeVersionFileName = "notice_version.bytes";
    public const string AssetBundlesSizeFileName = "assetbundls_size.bytes";
    public const string UpdateNoticeFileName = "update_notice.bytes";

    public static bool CheckIsNewVersion(string sourceVersion, string targetVersion)
    {
        string[] sVerList = sourceVersion.Split('.');
        string[] tVerList = targetVersion.Split('.');

        if (sVerList.Length >= 3 && tVerList.Length >= 3)
        {
            try
            {
                int sV0 = int.Parse(sVerList[0]);
                int sV1 = int.Parse(sVerList[1]);
                int sV2 = int.Parse(sVerList[2]);
                int tV0 = int.Parse(tVerList[0]);
                int tV1 = int.Parse(tVerList[1]);
                int tV2 = int.Parse(tVerList[2]);

                if (tV0 > sV0)
                {
                    return true;
                }
                else if (tV0 < sV0)
                {
                    return false;
                }

                if (tV1 > sV1)
                {
                    return true;
                }
                else if (tV1 < sV1)
                {
                    return false;
                }

                if (tV2 > sV2)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
            catch (System.Exception ex)
            {
                Debug.LogError(string.Format("parse version error. clientversion: {0} serverversion: {1}\n {2}\n{3}", sourceVersion, targetVersion, ex.Message, ex.StackTrace));
                return false;
            }
        }

        return false;
    }
}

数据转换

using System;
using System.Collections.Generic;
using System.Collections;
using System.IO;

public class DataUtils
{
    public static void CopyBytes(byte[] copyTo, int offsetTo, byte[] copyFrom, int offsetFrom, int count)
    {
        Array.Copy(copyFrom, offsetFrom, copyTo, offsetTo, count);
    }
    
    public static byte[] StringToBytes(string str)
    {
        return System.Text.Encoding.Default.GetBytes(str);
    }

    public static byte[] StringToUTFBytes(string str)
    {
        return System.Text.Encoding.UTF8.GetBytes(str);
    }

    public static string BytesToString(byte[] bytes)
    {
        return System.Text.Encoding.Default.GetString(bytes).Trim();
    }

    public static Hashtable HttpGetInfo(string info)
    {
        if (string.IsNullOrEmpty(info))
        {
            return null;
        }

        Hashtable table = new Hashtable();
        string[] paramList = info.Split('&');
        for (int i = 0; i < paramList.Length; i++)
        {
            string[] keyAndValue = paramList[i].Split('=');
            if (keyAndValue.Length >= 2)
            {
                if (!table.ContainsKey(keyAndValue[0]))
                {
                    table.Add(keyAndValue[0], keyAndValue[1]);
                }
            }
        }

        return table;
    }
}

文件和路径操作

using UnityEngine;
using System.IO;
using System.Linq;
using System;
using System.Collections.Generic;

/// <summary>
/// added by wsh @ 2017.12.25
/// 功能:通用静态方法
/// </summary>

public class GameUtility
{
    public const string AssetsFolderName = "Assets";

    public static string FormatToUnityPath(string path)
    {
        return path.Replace("\\", "/");
    }

    public static string FormatToSysFilePath(string path)
    {
        return path.Replace("/", "\\");
    }

    public static string FullPathToAssetPath(string full_path)
    {
        full_path = FormatToUnityPath(full_path);
        if (!full_path.StartsWith(Application.dataPath))
        {
            return null;
        }
        string ret_path = full_path.Replace(Application.dataPath, "");
        return AssetsFolderName + ret_path;
    }

    public static string GetFileExtension(string path)
    {
        return Path.GetExtension(path).ToLower();
    }

    public static string[] GetSpecifyFilesInFolder(string path, string[] extensions = null, bool exclude = false)
    {
        if (string.IsNullOrEmpty(path))
        {
            return null;
        }

        if (extensions == null)
        {
            return Directory.GetFiles(path, "*.*", SearchOption.AllDirectories);
        }
        else if (exclude)
        {
            return Directory.GetFiles(path, "*.*", SearchOption.AllDirectories)
                .Where(f => !extensions.Contains(GetFileExtension(f))).ToArray();
        }
        else
        {
            return Directory.GetFiles(path, "*.*", SearchOption.AllDirectories)
                .Where(f => extensions.Contains(GetFileExtension(f))).ToArray();
        }
    }

    public static string[] GetSpecifyFilesInFolder(string path, string pattern)
    {
        if (string.IsNullOrEmpty(path))
        {
            return null;
        }

        return Directory.GetFiles(path, pattern, SearchOption.AllDirectories);
    }

    public static string[] GetAllFilesInFolder(string path)
    {
        return GetSpecifyFilesInFolder(path);
    }

    public static string[] GetAllDirsInFolder(string path)
    {
        return Directory.GetDirectories(path, "*", SearchOption.AllDirectories);
    }

    public static void CheckFileAndCreateDirWhenNeeded(string filePath)
    {
        if (string.IsNullOrEmpty(filePath))
        {
            return;
        }

        FileInfo file_info = new FileInfo(filePath);
        DirectoryInfo dir_info = file_info.Directory;
        if (!dir_info.Exists)
        {
            Directory.CreateDirectory(dir_info.FullName);
        }
    }

    public static void CheckDirAndCreateWhenNeeded(string folderPath)
    {
        if (string.IsNullOrEmpty(folderPath))
        {
            return;
        }

        if (!Directory.Exists(folderPath))
        {
            Directory.CreateDirectory(folderPath);
        }
    }

    public static bool SafeWriteAllBytes(string outFile, byte[] outBytes)
    {
        try
        {
            if (string.IsNullOrEmpty(outFile))
            {
                return false;
            }

            CheckFileAndCreateDirWhenNeeded(outFile);
            if (File.Exists(outFile))
            {
                File.SetAttributes(outFile, FileAttributes.Normal);
            }
            File.WriteAllBytes(outFile, outBytes);
            return true;
        }
        catch (System.Exception ex)
        {
            Debug.LogError(string.Format("SafeWriteAllBytes failed! path = {0} with err = {1}", outFile, ex.Message));
            return false;
        }
    }

    public static bool SafeWriteAllLines(string outFile, string[] outLines)
    {
        try
        {
            if (string.IsNullOrEmpty(outFile))
            {
                return false;
            }

            CheckFileAndCreateDirWhenNeeded(outFile);
            if (File.Exists(outFile))
            {
                File.SetAttributes(outFile, FileAttributes.Normal);
            }
            File.WriteAllLines(outFile, outLines);
            return true;
        }
        catch (System.Exception ex)
        {
            Debug.LogError(string.Format("SafeWriteAllLines failed! path = {0} with err = {1}", outFile, ex.Message));
            return false;
        }
    }

    public static bool SafeWriteAllText(string outFile, string text)
    {
        try
        {
            if (string.IsNullOrEmpty(outFile))
            {
                return false;
            }

            CheckFileAndCreateDirWhenNeeded(outFile);
            if (File.Exists(outFile))
            {
                File.SetAttributes(outFile, FileAttributes.Normal);
            }
            File.WriteAllText(outFile, text);
            return true;
        }
        catch (System.Exception ex)
        {
            Debug.LogError(string.Format("SafeWriteAllText failed! path = {0} with err = {1}", outFile, ex.Message));
            return false;
        }
    }

    public static byte[] SafeReadAllBytes(string inFile)
    {
        try
        {
            if (string.IsNullOrEmpty(inFile))
            {
                return null;
            }

            if (!File.Exists(inFile))
            {
                return null;
            }

            File.SetAttributes(inFile, FileAttributes.Normal);
            return File.ReadAllBytes(inFile);
        }
        catch (System.Exception ex)
        {
            Debug.LogError(string.Format("SafeReadAllBytes failed! path = {0} with err = {1}", inFile, ex.Message));
            return null;
        }
    }

    public static string[] SafeReadAllLines(string inFile)
    {
        try
        {
            if (string.IsNullOrEmpty(inFile))
            {
                return null;
            }

            if (!File.Exists(inFile))
            {
                return null;
            }

            File.SetAttributes(inFile, FileAttributes.Normal);
            return File.ReadAllLines(inFile);
        }
        catch (System.Exception ex)
        {
            Debug.LogError(string.Format("SafeReadAllLines failed! path = {0} with err = {1}", inFile, ex.Message));
            return null;
        }
    }

    public static string SafeReadAllText(string inFile)
    {
        try
        {
            if (string.IsNullOrEmpty(inFile))
            {
                return null;
            }

            if (!File.Exists(inFile))
            {
                return null;
            }

            File.SetAttributes(inFile, FileAttributes.Normal);
            return File.ReadAllText(inFile);
        }
        catch (System.Exception ex)
        {
            Debug.LogError(string.Format("SafeReadAllText failed! path = {0} with err = {1}", inFile, ex.Message));
            return null;
        }
    }

    public static void DeleteDirectory(string dirPath)
    {
        string[] files = Directory.GetFiles(dirPath);
        string[] dirs = Directory.GetDirectories(dirPath);

        foreach (string file in files)
        {
            File.SetAttributes(file, FileAttributes.Normal);
            File.Delete(file);
        }

        foreach (string dir in dirs)
        {
            DeleteDirectory(dir);
        }

        Directory.Delete(dirPath, false);
    }

    public static bool SafeClearDir(string folderPath)
    {
        try
        {
            if (string.IsNullOrEmpty(folderPath))
            {
                return true;
            }

            if (Directory.Exists(folderPath))
            {
                DeleteDirectory(folderPath);
            }
            Directory.CreateDirectory(folderPath);
            return true;
        }
        catch (System.Exception ex)
        {
            Debug.LogError(string.Format("SafeClearDir failed! path = {0} with err = {1}", folderPath, ex.Message));
            return false;
        }
    }

    public static bool SafeDeleteDir(string folderPath)
    {
        try
        {
            if (string.IsNullOrEmpty(folderPath))
            {
                return true;
            }

            if (Directory.Exists(folderPath))
            {
                DeleteDirectory(folderPath);
            }
            return true;
        }
        catch (System.Exception ex)
        {
            Debug.LogError(string.Format("SafeDeleteDir failed! path = {0} with err: {1}", folderPath, ex.Message));
            return false;
        }
    }

    public static bool SafeDeleteFile(string filePath)
    {
        try
        {
            if (string.IsNullOrEmpty(filePath))
            {
                return true;
            }

            if (!File.Exists(filePath))
            {
                return true;
            }
            File.SetAttributes(filePath, FileAttributes.Normal);
            File.Delete(filePath);
            return true;
        }
        catch (System.Exception ex)
        {
            Debug.LogError(string.Format("SafeDeleteFile failed! path = {0} with err: {1}", filePath, ex.Message));
            return false;
        }
    }

    public static bool SafeRenameFile(string sourceFileName, string destFileName)
    {
        try
        {
            if (string.IsNullOrEmpty(sourceFileName))
            {
                return false;
            }

            if (!File.Exists(sourceFileName))
            {
                return true;
            }
            File.SetAttributes(sourceFileName, FileAttributes.Normal);
            File.Move(sourceFileName, destFileName);
            return true;
        }
        catch (System.Exception ex)
        {
            Debug.LogError(string.Format("SafeRenameFile failed! path = {0} with err: {1}", sourceFileName, ex.Message));
            return false;
        }
    }

    public static bool SafeCopyFile(string fromFile, string toFile)
    {
        try
        {
            if (string.IsNullOrEmpty(fromFile))
            {
                return false;
            }

            if (!File.Exists(fromFile))
            {
                return false;
            }
            CheckFileAndCreateDirWhenNeeded(toFile);
            if (File.Exists(toFile))
            {
                File.SetAttributes(toFile, FileAttributes.Normal);
            }
            File.Copy(fromFile, toFile, true);
            return true;
        }
        catch (System.Exception ex)
        {
            Debug.LogError(string.Format("SafeCopyFile failed! formFile = {0}, toFile = {1}, with err = {2}",
                fromFile, toFile, ex.Message));
            return false;
        }
    }
}

#if UNITY_EDITOR
public static class GameUtilityExporter
{
    public static List<Type> LuaCallCSharp = new List<Type>(){
            typeof(GameUtility),
        };
}
#endif

其他通用

using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Security.Cryptography;


using UnityEngine;

public class utils {

    public static string rand_str(int len) {
        byte[] b = new byte[4];
        new System.Security.Cryptography.RNGCryptoServiceProvider().GetBytes(b);
        System.Random r = new System.Random(System.BitConverter.ToInt32(b, 0));

        string str = null;
        str += "0123456789";
        str += "abcdefghijklmnopqrstuvwxyz";
        str += "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

        string s = null;

        for (int i = 0; i < len; i++)
        {
            s += str.Substring(r.Next(0, str.Length - 1), 1);
        }
        return s;
    }

    public static string md5(string str) {
        string cl = str;
        StringBuilder md5_builder = new StringBuilder();
        MD5 md5 = MD5.Create();//实例化一个md5对像
        // 加密后是一个字节类型的数组,这里要注意编码UTF8/Unicode等的选择 
        byte[] s = md5.ComputeHash(Encoding.UTF8.GetBytes(cl));
        // 通过使用循环,将字节类型的数组转换为字符串,此字符串是常规字符格式化所得
        for (int i = 0; i < s.Length; i++)
        {
            // 将得到的字符串使用十六进制类型格式。格式后的字符是小写的字母,如果使用大写(X)则格式后的字符是大写字符
            md5_builder.Append(s[i].ToString("X2"));
            //pwd = pwd + s[i].ToString("X");

        }
        return md5_builder.ToString();
    }

    public static void DestroyAllChildren(Transform parent) {
        int count = parent.childCount;
        for (int i = 0; i < count; i++) {
            Transform child = parent.GetChild(0);
            GameObject.DestroyImmediate(child.gameObject);
        }
    }
}

启动脚本

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System;
using AssetBundles;
using GameChannel;

public class GameLanch : UnitySingleton<GameLanch> {
    public bool isUseSocket = false;

    public override void Awake() {
        // 初始化游戏框架
        this.gameObject.AddComponent<TimerMgr>().Init();
        this.gameObject.AddComponent<ResMgr>();
        this.gameObject.AddComponent<SoundMgr>();
        this.gameObject.AddComponent<EventMgr>();
        EventMgr.Instance.init();
        this.gameObject.AddComponent<UIMgr>();

        if (this.isUseSocket) {
            this.gameObject.AddComponent<NetMgr>();
        }

#if RELEASE_BUILD
#else
        this.gameObject.AddComponent<show_fps>();
        if (Application.platform == RuntimePlatform.Android ||
            Application.platform == RuntimePlatform.IPhonePlayer)
        {
            this.gameObject.AddComponent<DebugManager>();
        }
#endif
        // end 

        this.gameObject.AddComponent<GameApp>().Init();
    }

    IEnumerator InitPackageName()
    {
#if UNITY_EDITOR
        if (AssetBundleConfig.IsEditorMode)
        {
            yield break;
        }
#endif
        var packageNameRequest = AssetBundleManager.Instance.RequestAssetFileAsync(BuildUtils.PackageNameFileName);
        yield return packageNameRequest; // 中断当前协程,直到请求结束;
        var packageName = packageNameRequest.text;
        packageNameRequest.Dispose(); // 释放请求;

        AssetBundleManager.ManifestBundleName = packageName; // 包名字;
        ChannelManager.Instance.Init(packageName);
        Debug.Log(string.Format("packageName = {0}", packageName));
        yield break;
    }

    IEnumerator CheckAndDownload() {
        yield break;
    }

    IEnumerator GameStart() {
        var start = DateTime.Now;
        yield return InitPackageName();
        Debug.Log(string.Format("InitPackageName use {0}ms", (DateTime.Now - start).Milliseconds));

        // 启动资源管理模块
        start = DateTime.Now;
        yield return AssetBundleManager.Instance.Initialize();
        Debug.Log(string.Format("AssetBundleManager Initialize use {0}ms", (DateTime.Now - start).Milliseconds));

        // 启动检测更新
        yield return CheckAndDownload();
        // end 

        GameApp.Instance.EnterGame();

        yield break;
    }

    void Start() {
        this.StartCoroutine(this.GameStart());
    }
}



文章作者: 微笑紫瞳星
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 微笑紫瞳星 !
  目录