随记

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

public class PackEditor : EditorWindow
{
    [MenuItem("Window/发布 _F4")]
    static void PackagerWindow()
    {
        luaFilesPath = Application.dataPath + "/GameLua";
        luaZipPath = Application.streamingAssetsPath + "/lua.zip";
        PackEditor packagerEditor = (PackEditor)EditorWindow.GetWindow(typeof(PackEditor), false, "发布", true);
        packagerEditor.minSize = new Vector2(300, 220);
        packagerEditor.maxSize = new Vector2(300, 220);
        packagerEditor.Show();
    }

    public void OnGUI()
    {
        buildTarget = (BuildTarget)EditorGUI.EnumPopup(new Rect(80, 10, 150, 20), buildTarget);
        if (GUI.Button(new Rect(80, 50, 150, 20), "设置保存位置"))
        {
            fabuPath = EditorUtility.SaveFilePanel("文件保存路径", "c", Application.productName, JudgePlatform());
        }
        if (GUI.Button(new Rect(80, 90, 150, 20), "开始打包"))
        {
            MyBuild();
        }
        GUI.TextArea(new Rect(10, 120, 280, 90), "当前保存位置:" + fabuPath);
    }

    string JudgePlatform()
    {
        if (buildTarget == BuildTarget.Android)
            return "apk";
        else if (buildTarget == BuildTarget.StandaloneWindows64)
            return "exe";
        else if (buildTarget == BuildTarget.WebGL)
            return "htm";
        return "apk";
    }

    static string luaFilesPath = string.Empty;
    static string luaZipPath = string.Empty;
    static BuildTarget buildTarget = BuildTarget.Android;
    static string fabuPath = string.Empty;
    static void MyBuild()
    {
        if (!string.IsNullOrEmpty(fabuPath))
        {
            EditorBuildSettingsScene[] sceneArray = EditorBuildSettings.scenes;
            if (sceneArray != null && sceneArray.Length > 0)
            {
                List<string> outScenes = new List<string>();
                for (int i = 0; i < sceneArray.Length; i++)
                {
                    if (sceneArray[i].enabled)
                        outScenes.Add(sceneArray[i].path);
                }
                if (outScenes.Count > 0)
                {
                    GenerateZip(luaFilesPath);
                    AssetDatabase.Refresh();
                    BuildPipeline.BuildPlayer(outScenes.ToArray(), fabuPath, buildTarget, BuildOptions.None);
                    AssetDatabase.Refresh();
                    System.Diagnostics.Process.Start(Path.GetDirectoryName(fabuPath));
                }
            }
        }
    }

    static void GenerateZip(string _orignalFloder)
    {
        allFiles = new List<string>();
        GetAllFiles(_orignalFloder);
        if (File.Exists(luaZipPath))
            File.Delete(luaZipPath);
        AssetDatabase.Refresh();
        CSharpUtils.CompressFiles(luaZipPath, allFiles, _orignalFloder.LastIndexOf('/') + 1, "");
    }

    static List<string> allFiles = null;
    public static void GetAllFiles(string path)
    {
        string[] dir = Directory.GetDirectories(path);
        DirectoryInfo fdir = new DirectoryInfo(path);
        FileInfo[] file = fdir.GetFiles();
        if (file.Length != 0 || dir.Length != 0)
        {
            foreach (FileInfo f in file)
            {
                if (!f.FullName.Contains(".meta"))
                    allFiles.Add(f.FullName);
            }
            foreach (string d in dir)
            {
                GetAllFiles(d);//递归   
            }
        }
    }
}
PackEditor
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

public class LayerTagSpawn : AssetPostprocessor
{
    static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
    {
        foreach (string s in importedAssets)
        {
            if (s.Contains("LayerTagSpawn"))
            {
                AddLayer("dragLayer");
                AddLayer("terrain");
                return;
            }
        }
    }

    static void AddTag(string tag)
    {
        if (!isHasTag(tag))
        {
            SerializedObject tagManager = new SerializedObject(AssetDatabase.LoadAllAssetsAtPath("ProjectSettings/TagManager.asset")[0]);
            SerializedProperty it = tagManager.GetIterator();
            while (it.NextVisible(true))
            {
                if (it.name == "tags")
                {
                    for (int i = 0; i < it.arraySize; i++)
                    {
                        SerializedProperty dataPoint = it.GetArrayElementAtIndex(i);
                        if (string.IsNullOrEmpty(dataPoint.stringValue))
                        {
                            dataPoint.stringValue = tag;
                            tagManager.ApplyModifiedProperties();
                            return;
                        }
                    }
                }
            }
        }
    }

    static void AddLayer(string layer)
    {
        if (!isHasLayer(layer))
        {
            SerializedObject tagManager = new SerializedObject(AssetDatabase.LoadAllAssetsAtPath("ProjectSettings/TagManager.asset")[0]);
            SerializedProperty it = tagManager.GetIterator();
            while (it.NextVisible(true))
            {
                if (it.name.StartsWith("User Layer"))
                {
                    if (it.type == "string")
                    {
                        if (string.IsNullOrEmpty(it.stringValue))
                        {
                            it.stringValue = layer;
                            tagManager.ApplyModifiedProperties();
                            return;
                        }
                    }
                }
            }
        }
    }

    static bool isHasTag(string tag)
    {
        for (int i = 0; i < UnityEditorInternal.InternalEditorUtility.tags.Length; i++)
        {
            if (UnityEditorInternal.InternalEditorUtility.tags[i].Contains(tag))
                return true;
        }
        return false;
    }

    static bool isHasLayer(string layer)
    {
        for (int i = 0; i < UnityEditorInternal.InternalEditorUtility.layers.Length; i++)
        {
            if (UnityEditorInternal.InternalEditorUtility.layers[i].Contains(layer))
                return true;
        }
        return false;
    }
}
LayerTagSpawn
using System.IO;
using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;

public class DecoratorEditors : EditorWindow
{

    [MenuItem("Window/打包场景 _F3")]
    static void PackagerWindow()
    {
        DecoratorEditors packagerEditor = (DecoratorEditors)EditorWindow.GetWindow(typeof(DecoratorEditors), false, "打包", true);
        packagerEditor.minSize = new Vector2(300, 220);
        packagerEditor.maxSize = new Vector2(300, 220);
        packagerEditor.Show();
    }

    public void OnGUI()
    {
        buildTarget = (BuildTarget)EditorGUI.EnumPopup(new Rect(80, 50, 150, 20), buildTarget);
        if (GUI.Button(new Rect(80, 80, 150, 20), "生成场景AB"))
        {
            MyBuild();
        }
    }

    static BuildTarget buildTarget = BuildTarget.Android;
    static void MyBuild()
    {
        string dir = Application.streamingAssetsPath + "/";
        if (!Directory.Exists(dir))
        {
            Directory.CreateDirectory(dir);
        }
        string activeSceneName = SceneManager.GetActiveScene().name;
        string saveSceneABPath = dir + activeSceneName + "-" + buildTarget +".unity3d";
        string[] levels = { SceneManager.GetActiveScene().path };
        //生成场景的AB包
        BuildPipeline.BuildPlayer(levels, saveSceneABPath, buildTarget, BuildOptions.BuildAdditionalStreamedScenes);
        AssetDatabase.Refresh();
        System.Diagnostics.Process.Start(dir);
    }
}
DecoratorEditors
using System;
using System.IO;
using UnityEngine;
using XLua;

public class LuaEnvGlobal : MonoBehaviour
{
    public static LuaEnvGlobal instance;
    LuaEnv luaEnv;
    void Awake()
    {
        instance = this;
        luaEnv = new LuaEnv();
        luaEnv.AddLoader((ref string filename) =>
        {
            string luaPath = AppConfig.SearchGameLuaPath + "/" + filename + ".lua";
            if (!File.Exists(luaPath))
                return null;
            string script = File.ReadAllText(luaPath);
            return System.Text.Encoding.UTF8.GetBytes(script);
        });
        DontDestroyOnLoad(gameObject);
    }

    void Start()
    {
    }

    private Action luaUpdate;
    private Action luaOnDestroy;
    public void StartUp()
    {
        print("启动Lua");
        luaEnv.DoString("require 'GameLua/Others/Main'");
        luaEnv.Global.Get("GlobalUpdate", out luaUpdate);
        luaEnv.Global.Get("GlobalOnDestory", out luaOnDestroy);
    }

    public void DoString(string luaStr)
    {
        luaEnv.DoString(luaStr);
    }

    public T GlobalGet<T>(string key)
    {
        T result;
        luaEnv.Global.Get(key, out result);
        return result;
    }


    void Update()
    {
        if (luaUpdate != null)
            luaUpdate();
        if (luaEnv != null)
            luaEnv.Tick();
    }

    void OnDestroy()
    {
        if (luaOnDestroy != null)
            luaOnDestroy();
        Clear();
        if (luaEnv != null)
            luaEnv.Dispose();
    }

    void Clear()
    {
        luaUpdate = null;
        luaOnDestroy = null;
    }
}
LuaEnvGlobal
/*
 * Tencent is pleased to support the open source community by making xLua available.
 * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved.
 * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
 * http://opensource.org/licenses/MIT
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*/

using System.Collections.Generic;
using System;
using XLua;
using System.Reflection;
using System.Linq;
using UnityEngine;

//配置的详细介绍请看Doc下《XLua的配置.doc》
public static class ExampleConfig
{
    /***************如果你全lua编程,可以参考这份自动化配置***************/
    //--------------begin 纯lua编程配置参考----------------------------
    static List<string> exclude = new List<string> {
        "HideInInspector", "ExecuteInEditMode",
        "AddComponentMenu", "ContextMenu",
        "RequireComponent", "DisallowMultipleComponent",
        "SerializeField", "AssemblyIsEditorAssembly",
        "Attribute", "Types",
        "UnitySurrogateSelector", "TrackedReference",
        "TypeInferenceRules", "FFTWindow",
        "RPC", "Network", "MasterServer",
        "BitStream", "HostData",
        "ConnectionTesterStatus", "GUI", "EventType",
        "EventModifiers", "FontStyle", "TextAlignment",
        "TextEditor", "TextEditorDblClickSnapping",
        "TextGenerator", "TextClipping", "Gizmos",
        "ADBannerView", "ADInterstitialAd",
        "Android", "Tizen", "jvalue",
        "iPhone", "iOS", "Windows", "CalendarIdentifier",
        "CalendarUnit", "CalendarUnit",
        "ClusterInput", "FullScreenMovieControlMode",
        "FullScreenMovieScalingMode", "Handheld",
        "LocalNotification", "NotificationServices",
        "RemoteNotificationType", "RemoteNotification",
        "SamsungTV", "TextureCompressionQuality",
        "TouchScreenKeyboardType", "TouchScreenKeyboard",
        "MovieTexture", "UnityEngineInternal",
        "Terrain", "Tree", "SplatPrototype",
        "DetailPrototype", "DetailRenderMode",
        "MeshSubsetCombineUtility", "AOT", "Social", "Enumerator",
        "SendMouseEvents", "Cursor", "Flash", "ActionScript",
        "OnRequestRebuild", "Ping",
        "ShaderVariantCollection", "SimpleJson.Reflection",
        "CoroutineTween", "GraphicRebuildTracker",
        "Advertisements", "UnityEditor", "WSA",
        "EventProvider", "Apple",
        "ClusterInput", "Motion",
        "UnityEngine.UI.ReflectionMethodsCache", "NativeLeakDetection",
        "NativeLeakDetectionMode", "WWWAudioExtensions", "UnityEngine.Experimental",
        "UnityEngine.Purchasing","AnimatorControllerParameter","UnityEngine.Tilemaps",
        "UnityEngine.Analytics","UnityEngine.Caching",
        "UnityEngine.EventSystems.HoloLensInput","UnityEngine.NScreenBridge",
        "UnityEngine.TestTools.LogAssert","UnityEngine.Microphone",
        "UnityEngine.CanvasRenderer","UnityEngine.Audio.AudioSpatializerMicrosoft",
        "UnityEngine.ProceduralMaterial","UnityEngine.ProceduralPropertyDescription",
        "UnityEngine.ProceduralTexture","UnityEngine.SpatialTracking",
        "FFmpeg.AutoGen","UnityEngine.PostProcessing","EntriesDrawer","SerializedProperty",
        "AQUAS_Screenshotter","TiltInputAxisStylePropertyDrawer","ReplacementListDrawer","WaypointListDrawer","ReplacementListDrawer","TransformNameComparer",
        "BatchMaterialConverter","BatchModelImporter","BatchTextureImporter","EditorHotkeysTracker","EditorPrefsX","LB_LightingSettingsHepler","HighlightingSystem.Demo",
        "FrameWork.Meta","FrameWork.vComment"
    };

    static bool isExcluded(Type type)
    {
        var fullName = type.FullName;
        for (int i = 0; i < exclude.Count; i++)
        {
            if (fullName.Contains(exclude[i]))
            {
                return true;
            }
        }
        return false;
    }

    [LuaCallCSharp]
    public static IEnumerable<Type> LuaCallCSharp
    {
        get
        {
            var unityTypes = (from assembly in AppDomain.CurrentDomain.GetAssemblies()
                              where !(assembly.ManifestModule is System.Reflection.Emit.ModuleBuilder)
                              from type in assembly.GetExportedTypes()
                              where type.Namespace != null && type.Namespace.StartsWith("UnityEngine") && !isExcluded(type) && type.BaseType != typeof(MulticastDelegate) && !type.IsInterface && !type.IsEnum
                              select type);
            string[] customAssemblys = new string[] {
                "Assembly-CSharp",
            };
            var customTypes = (from assembly in customAssemblys.Select(s => Assembly.Load(s))
                               from type in assembly.GetExportedTypes()
                               where (type.Namespace == null || !type.Namespace.StartsWith("XLua")) && !isExcluded(type)
                                       && type.BaseType != typeof(MulticastDelegate) && !type.IsInterface && !type.IsEnum
                               select type);
            return unityTypes.Concat(customTypes);
        }
    }

    //自动把LuaCallCSharp涉及到的delegate加到CSharpCallLua列表,后续可以直接用lua函数做callback
    [CSharpCallLua]
    public static List<Type> CSharpCallLua
    {
        get
        {
            var lua_call_csharp = LuaCallCSharp;
            var delegate_types = new List<Type>();
            var flag = BindingFlags.Public | BindingFlags.Instance
                | BindingFlags.Static | BindingFlags.IgnoreCase | BindingFlags.DeclaredOnly;
            foreach (var field in (from type in lua_call_csharp select type).SelectMany(type => type.GetFields(flag)))
            {
                if (typeof(Delegate).IsAssignableFrom(field.FieldType))
                {
                    if (!isExcluded(field.FieldType))
                        delegate_types.Add(field.FieldType);
                }
            }

            foreach (var method in (from type in lua_call_csharp select type).SelectMany(type => type.GetMethods(flag)))
            {
                if (typeof(Delegate).IsAssignableFrom(method.ReturnType))
                {
                    if (!isExcluded(method.ReturnType))
                        delegate_types.Add(method.ReturnType);
                }
                foreach (var param in method.GetParameters())
                {
                    var paramType = param.ParameterType.IsByRef ? param.ParameterType.GetElementType() : param.ParameterType;
                    if (typeof(Delegate).IsAssignableFrom(paramType))
                    {
                        if (!isExcluded(paramType))
                            delegate_types.Add(paramType);
                    }
                }
            }
            return delegate_types.Distinct().ToList();
        }
    }
    //--------------end 纯lua编程配置参考----------------------------

    /***************热补丁可以参考这份自动化配置***************/
    [Hotfix]
    static IEnumerable<Type> HotfixInject
    {
        get
        {
            List<Type> ll = (from type in Assembly.Load("Assembly-CSharp").GetExportedTypes()
                             where (type.Namespace == null || !type.Namespace.StartsWith("XLua")) && !isExcluded(type)
                             select type).ToList();
            return (from type in Assembly.Load("Assembly-CSharp").GetExportedTypes()
                    where (type.Namespace == null || !type.Namespace.StartsWith("XLua")) && !isExcluded(type)
                    select type);
        }
    }
    //--------------begin 热补丁自动化配置-------------------------
    static bool hasGenericParameter(Type type)
    {
        if (type.IsGenericTypeDefinition) return true;
        if (type.IsGenericParameter) return true;
        if (type.IsByRef || type.IsArray)
        {
            return hasGenericParameter(type.GetElementType());
        }
        if (type.IsGenericType)
        {
            foreach (var typeArg in type.GetGenericArguments())
            {
                if (hasGenericParameter(typeArg))
                {
                    return true;
                }
            }
        }
        return false;
    }

    //// 配置某Assembly下所有涉及到的delegate到CSharpCallLua下,Hotfix下拿不准那些delegate需要适配到lua function可以这么配置
    //[CSharpCallLua]
    //static IEnumerable<Type> AllDelegate
    //{
    //    get
    //    {
    //        BindingFlags flag = BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public;
    //        List<Type> allTypes = new List<Type>();
    //        var allAssemblys = new Assembly[]
    //        {
    //            Assembly.Load("Assembly-CSharp")
    //        };
    //        foreach (var t in (from assembly in allAssemblys from type in assembly.GetTypes() select type))
    //        {
    //            var p = t;
    //            while (p != null)
    //            {
    //                allTypes.Add(p);
    //                p = p.BaseType;
    //            }
    //        }
    //        allTypes = allTypes.Distinct().ToList();
    //        var allMethods = from type in allTypes
    //                         from method in type.GetMethods(flag)
    //                         select method;
    //        var returnTypes = from method in allMethods
    //                          select method.ReturnType;
    //        var paramTypes = allMethods.SelectMany(m => m.GetParameters()).Select(pinfo => pinfo.ParameterType.IsByRef ? pinfo.ParameterType.GetElementType() : pinfo.ParameterType);
    //        var fieldTypes = from type in allTypes
    //                         from field in type.GetFields(flag)
    //                         select field.FieldType;
    //        return (returnTypes.Concat(paramTypes).Concat(fieldTypes)).Where(t => t.BaseType == typeof(MulticastDelegate) && !hasGenericParameter(t)).Distinct();
    //    }
    //}
    //--------------end 热补丁自动化配置-------------------------

    //黑名单
    [BlackList]
    public static List<List<string>> BlackList = new List<List<string>>()  {
                new List<string>(){"System.Xml.XmlNodeList", "ItemOf"},
                new List<string>(){"UnityEngine.WWW", "movie"},
    #if UNITY_WEBGL
                new List<string>(){"UnityEngine.WWW", "threadPriority"},
    #endif
                new List<string>(){"UnityEngine.Texture2D", "alphaIsTransparency"},
                new List<string>(){"UnityEngine.Security", "GetChainOfTrustValue"},
                new List<string>(){"UnityEngine.CanvasRenderer", "onRequestRebuild"},
                new List<string>(){"UnityEngine.Light", "areaSize"},
                new List<string>(){"UnityEngine.Light", "lightmapBakeType"},
                new List<string>(){"UnityEngine.WWW", "MovieTexture"},
                new List<string>(){"UnityEngine.WWW", "GetMovieTexture"},
                new List<string>(){"UnityEngine.AnimatorOverrideController", "PerformOverrideClipListCleanup"},
    #if !UNITY_WEBPLAYER
                new List<string>(){"UnityEngine.Application", "ExternalEval"},
    #endif
                new List<string>(){"UnityEngine.GameObject", "networkView"}, //4.6.2 not support
                new List<string>(){"UnityEngine.Component", "networkView"},  //4.6.2 not support
                new List<string>(){"System.IO.FileInfo", "GetAccessControl", "System.Security.AccessControl.AccessControlSections"},
                new List<string>(){"System.IO.FileInfo", "SetAccessControl", "System.Security.AccessControl.FileSecurity"},
                new List<string>(){"System.IO.DirectoryInfo", "GetAccessControl", "System.Security.AccessControl.AccessControlSections"},
                new List<string>(){"System.IO.DirectoryInfo", "SetAccessControl", "System.Security.AccessControl.DirectorySecurity"},
                new List<string>(){"System.IO.DirectoryInfo", "CreateSubdirectory", "System.String", "System.Security.AccessControl.DirectorySecurity"},
                new List<string>(){"System.IO.DirectoryInfo", "Create", "System.Security.AccessControl.DirectorySecurity"},
                new List<string>(){"UnityEngine.MonoBehaviour", "runInEditMode"},
                new List<string>(){"UnityEngine.UI.Text", "OnRebuildRequested"},
                new List<string>(){ "UnityEngine.Input", "IsJoystickPreconfigured","System.String"},
                new List<string>(){ "UnityEngine.AudioSettings", "GetSpatializerPluginNames"},
                new List<string>(){ "UnityEngine.AudioSettings", "SetSpatializerPluginName","System.String"},
                new List<string>(){ "UnityEngine.UI.Graphic", "OnRebuildRequested"},
                new List<string>(){ "MediaPlayerCtrl", "Interrupt1"},
                new List<string>(){ "MediaPlayerCtrl", "MediaPlayerCtrl"},
                new List<string>(){ "MediaPlayerCtrl", "Call_SetUnityActivity"},
            };
}


/// <summary>
/// xlua自定义导出
/// </summary>
public static class XLuaCustomExport
{
    /// <summary>
    /// dotween的扩展方法在lua中调用
    /// </summary>
    [LuaCallCSharp]
    [ReflectionUse]
    public static List<Type> dotween_lua_call_cs_list = new List<Type>()
    {
        typeof(DG.Tweening.AutoPlay),
        typeof(DG.Tweening.AxisConstraint),
        typeof(DG.Tweening.Ease),
        typeof(DG.Tweening.LogBehaviour),
        typeof(DG.Tweening.LoopType),
        typeof(DG.Tweening.PathMode),
        typeof(DG.Tweening.PathType),
        typeof(DG.Tweening.RotateMode),
        typeof(DG.Tweening.ScrambleMode),
        typeof(DG.Tweening.TweenType),
        typeof(DG.Tweening.UpdateType),
        typeof(DG.Tweening.Ease),

        typeof(DG.Tweening.DOTween),
        typeof(DG.Tweening.DOVirtual),
        typeof(DG.Tweening.EaseFactory),
        typeof(DG.Tweening.Tweener),
        typeof(DG.Tweening.Tween),
        typeof(DG.Tweening.Sequence),
        typeof(DG.Tweening.TweenParams),
        typeof(DG.Tweening.Core.ABSSequentiable),

        typeof(DG.Tweening.Core.TweenerCore<Vector3, Vector3, DG.Tweening.Plugins.Options.VectorOptions>),

        typeof(DG.Tweening.TweenCallback),
        typeof(DG.Tweening.TweenExtensions),
        typeof(DG.Tweening.TweenSettingsExtensions),
        typeof(DG.Tweening.ShortcutExtensions),
        typeof(DG.Tweening.ShortcutExtensions43),
        typeof(DG.Tweening.ShortcutExtensions46),
        typeof(DG.Tweening.ShortcutExtensions50),
       
        //dotween pro 的功能
        //typeof(DG.Tweening.DOTweenPath),
        //typeof(DG.Tweening.DOTweenVisualManager),
    };
}
ExampleConfig
using System;
using System.Collections;
using System.IO;
using UnityEngine;

public class ExtractZipHelper : MonoBehaviour
{
    public static ExtractZipHelper instance;

    void Awake()
    {
        instance = this;
        zipExtractFloder = Application.persistentDataPath + "/";
    }

    public void StartExtract(string zipPath)
    {
        print("执行解压压缩包任务:" + zipPath);
        StartCoroutine(Extract(zipPath));
    }

    public Action onSuccess;
    public Action<string> onError;
    string zipExtractFloder = string.Empty;
    IEnumerator Extract(string zipfile)
    {
        using (WWW www = new WWW(zipfile))
        {
            yield return www;
            if (www.error != null)
            {
                print("加载压缩包出错:" + www.error);
                if (onError != null)
                    onError(www.error);
            }
            else
            {
                print("加载压缩包成功:" + zipfile);
                string zipExtractPath = zipExtractFloder + Path.GetFileName(zipfile);
                using (FileStream fs = File.Create(zipExtractPath))
                {
                    fs.Write(www.bytes, 0, www.bytes.Length);
                }
                ExtractToPersi(zipExtractPath);
            }
        }
    }

    void ExtractToPersi(string filePath)
    {
        print("开始解压压缩包" + filePath);
        byte[] bytes = File.ReadAllBytes(filePath);
        try
        {
            CSharpUtils.UncompressMemory(zipExtractFloder, bytes);
            if (onSuccess != null)
                onSuccess();
        }
        catch (Exception ex)
        {
            print("解压出错:" + ex.Message);
            if (onError != null)
                onError(ex.Message);
        }
    }
}
ExtractZipHelper
只有在编辑器模式下用的是非PC平台的AB包才会出现shader丢失的问题,解决方案如下,但是最好的解决方案是打个PC端的AB包给程序开发时用,然后在对应发布其他端的AB包
1、内置的shader通过Shader.Find去获取,然后代码重新设置下
2、自定义shader通过ab加载去获取,然后代码重新设置下
3、在Lighting面板中skybox对应的材质shader,要在Project Settings下Graphics的面板中Always Included Shaders里面包含进去,这个仅仅是为了修复编辑器模式下天空盒材质shader丢失的问题而产生的解决方案,但是如果发布到android版本是不能设置这个的,不然安卓平台会产生天空盒shader丢失的问题,为了同时满足两者的需求,我们通过代码设置天空盒材质的shader,已在ABFixShader.cs中添加进去了
4、如果打包的是PC平台的场景ab包,切记把ProjectSettings -> Other Settings里的PC平台的Auto Graphics API for ***全部勾选上,不然材质会丢失
打包shader注意点
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
using System.Threading;
using System.Linq;

public class Loom : MonoBehaviour
{
    public static int maxThreads = 8;
    static int numThreads;

    private static Loom _current;
    public static Loom Current
    {
        get
        {
            Initialize();
            return _current;
        }
    }
    //####去除Awake
    //  void Awake()  
    //  {  
    //      _current = this;  
    //      initialized = true;  
    //  }  

    static bool initialized;

    //####作为初始化方法自己调用,可在初始化场景调用一次即可
    public static void Initialize()
    {
        if (!initialized)
        {

            if (!Application.isPlaying)
                return;
            initialized = true;
            GameObject g = new GameObject("Loom");
            //####永不销毁
            DontDestroyOnLoad(g);
            _current = g.AddComponent<Loom>();
        }
    }

    private List<Action> _actions = new List<Action>();
    public struct DelayedQueueItem
    {
        public float time;
        public Action action;
    }
    private List<DelayedQueueItem> _delayed = new List<DelayedQueueItem>();

    List<DelayedQueueItem> _currentDelayed = new List<DelayedQueueItem>();

    public static void QueueOnMainThread(Action action)
    {
        QueueOnMainThread(action, 0f);
    }
    public static void QueueOnMainThread(Action action, float time)
    {
        if (time != 0)
        {
            if (Current != null)
            {
                lock (Current._delayed)
                {
                    Current._delayed.Add(new DelayedQueueItem { time = Time.time + time, action = action });
                }
            }
        }
        else
        {
            if (Current != null)
            {
                lock (Current._actions)
                {
                    Current._actions.Add(action);
                }
            }
        }
    }

    public static Thread RunAsync(Action a)
    {
        Initialize();
        while (numThreads >= maxThreads)
        {
            Thread.Sleep(1);
        }
        Interlocked.Increment(ref numThreads);
        ThreadPool.QueueUserWorkItem(RunAction, a);
        return null;
    }

    private static void RunAction(object action)
    {
        try
        {
            ((Action)action)();
        }
        catch
        {
        }
        finally
        {
            Interlocked.Decrement(ref numThreads);
        }
    }

    void OnDisable()
    {
        if (_current == this)
        {
            _current = null;
        }
    }

    // Use this for initialization  
    void Start()
    {
    }

    List<Action> _currentActions = new List<Action>();

    // Update is called once per frame  
    void Update()
    {
        lock (_actions)
        {
            _currentActions.Clear();
            _currentActions.AddRange(_actions);
            _actions.Clear();
        }
        foreach (var a in _currentActions)
        {
            a();
        }
        lock (_delayed)
        {
            _currentDelayed.Clear();
            _currentDelayed.AddRange(_delayed.Where(d => d.time <= Time.time));
            foreach (var item in _currentDelayed)
                _delayed.Remove(item);
        }
        foreach (var delayed in _currentDelayed)
        {
            delayed.action();
        }
    }
}
Loom
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;

public class DecoratorEditors : EditorWindow
{

    [MenuItem("Window/打包 _F2")]
    static void PackagerWindow()
    {
        DecoratorEditors packagerEditor = (DecoratorEditors)EditorWindow.GetWindow(typeof(DecoratorEditors), false, "打包", true);
        packagerEditor.minSize = new Vector2(300, 220);
        packagerEditor.maxSize = new Vector2(300, 220);
        packagerEditor.Show();
    }

    public void OnGUI()
    {
        buildTarget = (BuildTarget)EditorGUI.EnumPopup(new Rect(80, 50, 150, 20), buildTarget);
        if (GUI.Button(new Rect(80, 80, 150, 20), "生成场景AB"))
        {
            MyBuild();
        }
    }

    static string sceneABPath = string.Empty;
    static string shaderABSavePath = string.Empty;
    static BuildTarget buildTarget = BuildTarget.Android;
    static void MyBuild()
    {
        string saveABPath = Application.dataPath.Replace("Assets", "AB");
        if (!Directory.Exists(saveABPath))
            Directory.CreateDirectory(saveABPath);

        string activeSceneName = SceneManager.GetActiveScene().name;
        string saveSceneABPath = saveABPath + "/" + activeSceneName + ".unity3d";
        string[] levels = { SceneManager.GetActiveScene().path };

        //生成场景的AB包
        BuildPipeline.BuildPlayer(levels, saveSceneABPath, buildTarget, BuildOptions.BuildAdditionalStreamedScenes);

        ////生成场景对应的Shader的AB包
        //string shaderABName = "shader.unity3d";
        //Shader[] shaders = Resources.FindObjectsOfTypeAll<Shader>();
        //AssetBundleBuild[] abArrary = new AssetBundleBuild[shaders.Length];
        //for (int i = 0; i < shaders.Length; i++)
        //{
        //    AssetBundleBuild ab = new AssetBundleBuild();
        //    string assetPath = AssetDatabase.GetAssetPath(shaders[i]);
        //    ab.assetBundleName = shaderABName;
        //    ab.assetNames = new string[] { assetPath };
        //    ab.assetBundleVariant = "";
        //    string shaderName = shaders[i].name;
        //    if (shaderName.Contains("/"))
        //        shaderName = shaderName.Split('/')[1];
        //    ab.addressableNames = new string[] { shaderName };
        //    abArrary[i] = ab;
        //}
        //BuildPipeline.BuildAssetBundles(saveABPath, abArrary, BuildAssetBundleOptions.UncompressedAssetBundle, buildTarget);

        //避免让美术设置手动ab,所以不采取下面这种方式
        //BuildPipeline.BuildAssetBundles(shaderABSavePath, BuildAssetBundleOptions.UncompressedAssetBundle, buildTarget);

        AssetDatabase.Refresh();
        System.Diagnostics.Process.Start(saveABPath);
    }
}
生成场景对应的Shader的AB包
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

public class ABFixShader : MonoBehaviour
{
#if UNITY_EDITOR    
    string shaderABPath = string.Empty;
    void Awake()
    {
        string activeSceneName = SceneManager.GetActiveScene().name;
        shaderABPath = Application.streamingAssetsPath + "/" + activeSceneName +"-shader.unity3d";
        StartCoroutine(Download());
    }

    IEnumerator Download()
    {
        using (WWW www = new WWW(shaderABPath))
        {
            yield return www;
            if (www.error != null)
            {
                Debug.Log("shader下载失败:" + www.error);
            }
            else
            {
                Debug.Log("开始修复shader丢失问题");
                FixShader(www.assetBundle);
            }
        }
    }

    Dictionary<string, Shader> shaderDic = null;
    void FixShader(AssetBundle shaderBundle)
    {
        List<GameObject> rendererList = new List<GameObject>();
        GetAllChildren(transform, rendererList);
        if (rendererList != null && rendererList.Count > 0)
        {
            shaderDic = new Dictionary<string, Shader>();
            rendererList.ForEach((go) =>
            {
                ResetShader(go, shaderBundle);
            });
        }
        //代码修复天空盒材质shader丢失的问题
        ChangeMaterialShader(RenderSettings.skybox, shaderBundle);
    }

    void GetAllChildren(Transform root, List<GameObject> rendererList)
    {
        foreach (Transform item in root)
        {
            if (item.GetComponent<Renderer>() != null)
            {
                rendererList.Add(item.gameObject);
            }
            else if (item.GetComponent<Skybox>() != null)
            {
                rendererList.Add(item.gameObject);
            }
            GetAllChildren(item, rendererList);
        }
    }

    void ResetShader(GameObject target, AssetBundle shaderBundle)
    {
        Material[] materials = null;
        Renderer renderer = target.GetComponent<Renderer>();
        if (renderer != null)
        {
            materials = renderer.materials;
            ParticleSystemRenderer psr = GetComponent<ParticleSystemRenderer>();
            if (psr != null)
            {
                materials[materials.Length] = psr.trailMaterial;
            }
        }
        else
        {
            Skybox skybox = target.GetComponent<Skybox>();
            if (skybox != null)
            {
                materials = new Material[] { skybox.material };
            }
        }
        if (materials != null && materials.Length > 0)
        {
            for (int i = 0; i < materials.Length; i++)
            {
                ChangeMaterialShader(materials[i], shaderBundle);
            }
        }
    }

    void ChangeMaterialShader(Material material, AssetBundle shaderBundle)
    {
        if (material != null && shaderBundle != null)
        {
            string shaderNode = string.Empty;
            string shaderName = string.Empty;
            shaderNode = material.shader.name;
            shaderName = shaderNode;
            if (shaderNode.Contains("/"))
                shaderName = shaderNode.Split('/')[1];
            Shader resultShader = null;
            if (shaderDic.ContainsKey(shaderName))
                resultShader = shaderDic[shaderName];
            else
            {
                resultShader = shaderBundle.LoadAsset<Shader>(shaderName);
                if (resultShader == null)
                {
                    if (shaderDic.ContainsKey(shaderNode))
                        resultShader = shaderDic[shaderNode];
                    else
                    {
                        resultShader = Shader.Find(shaderNode);
                        if (resultShader != null)
                            shaderDic[shaderNode] = resultShader;
                    }
                }
                else
                {
                    shaderDic[shaderName] = resultShader;
                }
            }
            material.shader = resultShader;
        }
    }
#endif
}
ABFixShader
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEngine;
using UnityEngine.UI;

public class DownloadManager : MonoBehaviour
{
    public static DownloadManager instance;

    void Awake()
    {
        instance = this;
    }

    #region Properties
    /// <summary>
    /// 正在执行和正在等待的任务数量总和
    /// </summary>
    public int WaitingAndRunningCount { get { return mWaiting.Count + mRunning.Count; } }
    /// <summary>
    /// 最多可同时执行多少个任务
    /// </summary>
    public int MaxTaskCountInSameTime { get; private set; }
    /// <summary>
    /// 当前正在执行的任务数量
    /// </summary>
    public int RunningCount { get { return mRunning.Count; } }
    /// <summary>
    /// 已经完成的任务数量
    /// </summary>
    public int FinishedCount { get { return mFinished.Count; } }
    /// <summary>
    /// 任务是否成功完成,并且不存在失败的任务
    /// </summary>
    public bool IsCompleted { get { return WaitingAndRunningCount == 0; } }
    /// <summary>
    /// 是否正在下载
    /// </summary>
    public bool IsDownloading { get; private set; }

    /// <summary>
    /// 任务执行完时的回调
    /// </summary>
    public event Action WorkDone;

    #endregion

    #region Fields
    List<DownloadSocketFileHelper> mWaiting = new List<DownloadSocketFileHelper>();
    List<DownloadSocketFileHelper> mRunning = new List<DownloadSocketFileHelper>();
    List<DownloadSocketFileHelper> mFinished = new List<DownloadSocketFileHelper>();

    #endregion

    void Update()
    {
        if (!IsDownloading)
        {
            return;
        }
        if (mRunning.Count > 0)
        {
            for (int i = mRunning.Count - 1; i >= 0; i--)
            {
                if (mRunning[i].IsSuccess)
                {
                    mFinished.Add(mRunning[i]);
                    mRunning.RemoveAt(i);
                }
                else if (mRunning[i].IsFailed)
                {
                    mWaiting.Add(mRunning[i]);
                    mRunning.RemoveAt(i);
                }
            }
        }

        if (mWaiting.Count > 0)
        {
            if (mRunning.Count < MaxTaskCountInSameTime)
            {
                DownloadSocketFileHelper urlData = FindEnabledTask();
                if (urlData != null)
                {
                    urlData.Start();
                    mRunning.Add(urlData);
                }
            }
        }

        if (mRunning.Count == 0)
        {
            IsDownloading = false;
            if (WorkDone != null)
            {
                WorkDone();
            }
        }
    }

    private DownloadSocketFileHelper FindEnabledTask()
    {
        DownloadSocketFileHelper urlData = null;
        foreach (var item in mWaiting)
        {
            if (item.TryTimes < AppConfig.MAX_TRY_TIMES)
            {
                urlData = item;
                mWaiting.Remove(item);
                break;
            }
        }
        return urlData;
    }

    public void Init()
    {
        mWaiting.Clear();
        mRunning.Clear();
        mFinished.Clear();

        IsDownloading = false;
        MaxTaskCountInSameTime = AppConfig.MAX_DOWNLOAD_TASKS;
        WorkDone = null;
    }

    public void StartDownload()
    {
        if (IsDownloading == false)
        {
            IsDownloading = true;
        }
    }

    public void Retry()
    {
        if (IsDownloading == false)
        {
            //重试就要把所有失败的任务重试次数重置为0
            foreach (var item in mWaiting)
            {
                item.TryTimes = 0;
            }
            IsDownloading = true;
        }
    }

    public void PushTask(string filePath, Action<string> onSuccess, Action<string> onFailed)
    {
        mWaiting.Add(new DownloadSocketFileHelper(filePath, onSuccess, onFailed));
    }
}


public class DownloadSocketFileHelper
{
    int serverPort = 12345;
    int buffSize = 1024 * 1024;
    string serverIp = string.Empty;

    public int TryTimes = 0;
    public string filePath = string.Empty;
    string wholeFilePath = string.Empty;

    public int status = 0;//0为默认状态1为成功2为失败
    public DownloadSocketFileHelper(string _filePath, Action<string> _OnSuccess, Action<string> _OnError)
    {
        serverIp = AppConfig.serverIP;
        filePath = _filePath;
        OnSuccess = _OnSuccess;
        OnError = _OnError;
        TryTimes = 0;
        wholeFilePath = AppConfig.HotAssetsPath + filePath;
    }

    string errorMsg = string.Empty;
    Socket clientSocket;
    public void Start()
    {
        try
        {
            status = 0;
            errorMsg = string.Empty;
            IsSuccess = false;
            IsFailed = false;
            DownloadManager.instance.StartCoroutine(CheckStatus());
            clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Parse(serverIp), serverPort);
            clientSocket.Connect(ipEndPoint);
            Thread newThread = new Thread(ReceiveFile);
            newThread.Start();
            SendMsgToServer(Encoding.UTF8.GetBytes(filePath));
            Debug.Log("开始下载 = " + filePath);
        }
        catch (Exception ex)
        {
            errorMsg = ex.Message + " = " + filePath;
            status = 2;
        }
    }

    Action<string> OnSuccess;
    Action<string> OnError;

    void ReceiveFile()
    {
        try
        {
            string dir = Path.GetDirectoryName(wholeFilePath);
            if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
            using (FileStream writer = new FileStream(wholeFilePath, FileMode.OpenOrCreate, FileAccess.Write))
            {
                int len = 0;
                byte[] buff = new byte[buffSize];
                while ((len = clientSocket.Receive(buff)) != 0)
                {
                    string msg = Encoding.UTF8.GetString(buff, len - 3, 3);
                    if (msg == "END")
                    {
                        writer.Write(buff, 0, len - 3);
                        clientSocket.Close();
                        status = 1;
                        break;
                    }
                    writer.Write(buff, 0, len);
                }
            }

        }
        catch (Exception ex)
        {
            clientSocket.Close();
            errorMsg = ex.Message + " = " + filePath;
            status = 2;
        }
    }

    public bool IsSuccess = false;
    public bool IsFailed = false;
    IEnumerator CheckStatus()
    {
        yield return new WaitUntil(() => { return status != 0; });
        if (status == 1)
        {
            if (OnSuccess != null)
                OnSuccess(filePath);
            IsSuccess = true;
        }
        else if (status == 2)
        {
            TryTimes++;
            if (TryTimes == AppConfig.MAX_TRY_TIMES)
            {
                errorMsg = StaticText.DownloadOverTimes;
            }
            if (OnError != null)
                OnError(errorMsg);
            IsFailed = true;
        }
    }

    public void SendMsgToServer(byte[] bytes)
    {
        clientSocket.Send(bytes);
    }
}
Client:DownloadManager
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.UI;

class MyFileClient : MonoBehaviour
{
    public Slider slider;
    public static MyFileClient instance;
    DownloadManager downloadManager;
    void Awake()
    {
        GetServerIP();
        downloadManager = GetComponent<DownloadManager>();
        instance = this;
    }

    void GetServerIP()
    {
        AppConfig.serverIP = File.ReadAllText(AppConfig.HotAssetsPath + AppConfig.serverIpFileName);
    }

    string LocalMD5FilePath = string.Empty;
    string hotAssetsPath = string.Empty;
    void Start()
    {
        LocalMD5FilePath = AppConfig.LocalMD5FilePath;
        hotAssetsPath = AppConfig.HotAssetsPath;

        downloadManager.Init();
        downloadManager.WorkDone += onDownloadFileListDone;
        downloadManager.PushTask(AppConfig.LIST_FILENAME, onDownLoadFileListSuccess, onDownLoadFileListFailed);
        downloadManager.StartDownload();
    }

    void StartGame()
    {

    }

    List<FileData> mServerFileList = null;
    void onDownLoadFileListSuccess(string filePath)
    {
        Debug.Log("文件列表下载成功");
        string[] downloadFileStringList = File.ReadAllLines(hotAssetsPath + filePath);
        mServerFileList = new List<FileData>();
        foreach (var item in downloadFileStringList)
        {
            if (string.IsNullOrEmpty(item.Trim()))
            {
                continue;
            }
            string[] tokens = item.Split('|');
            if (tokens.Length >= 3)
            {
                FileData data = new FileData();
                data.needUpdate = true;
                data.name = tokens[0].Trim();
                data.md5 = tokens[1].Trim();
                data.size = 0;
                int.TryParse(tokens[2], out data.size);
                if (data.size > 0)
                {
                    mServerFileList.Add(data);
                }
            }
        }
    }

    void onDownLoadFileListFailed(string msg)
    {
        Debug.Log(msg + "下载失败");
        ErrorHandle(msg);
    }

    void ErrorHandle(string msg)
    {
        MFMessageBox.Instance.PopYesNo(msg, () =>
        {
            Application.Quit();
        }, () =>
        {
            downloadManager.Retry();
        }, StaticText.QuitGame, StaticText.STR_RETRY);
    }

    void onDownloadFileListDone()
    {
        Debug.Log("下载任务进程结束");
        if (downloadManager.IsCompleted)
        {
            print("文件列表下载成功");
            CheckFileMD5();
        }
        else
        {
            print("文件列表下载失败");
        }
    }

    void CheckFileMD5()
    {
        //得到本地的文件列表
        string[] oldLines = null;
        Debug.Log("比较MD5值 = " + LocalMD5FilePath);
        if (File.Exists(LocalMD5FilePath))
        {
            oldLines = File.ReadAllLines(LocalMD5FilePath);
        }
        if (oldLines != null && oldLines.Length > 0)
        {
            //去除不需要更新的文件
            foreach (var item in oldLines)
            {
                if (string.IsNullOrEmpty(item.Trim())) continue;
                string[] tokens = item.Split('|');
                if (tokens.Length < 2) continue;

                string name = tokens[0].Trim();
                string md5 = tokens[1].Trim();
                if (!File.Exists(hotAssetsPath + name))
                {
                    continue;
                }
                for (int i = 0; i < mServerFileList.Count; i++)
                {
                    if (mServerFileList[i].name == name && mServerFileList[i].md5 == md5)
                    {
                        mServerFileList[i].needUpdate = false;
                        break;
                    }
                }
            }
        }
        StartDownloadAssets();
    }

    public void StartDownloadAssets()
    {
        downloadManager.Init();
        downloadManager.WorkDone += onDownloadAllAssetDown;
        foreach (var item in mServerFileList)
        {
            if (item.needUpdate)
            {
                print("需要下载文件 = " + item.name);
                downloadManager.PushTask(item.name, onDownloadAssetSuccess, onDownloadAssetFailed);
            }
        }
        downloadManager.StartDownload();
    }

    void onDownloadAssetSuccess(string filePath)
    {
        Debug.Log(filePath + "下载成功");
        for (int i = 0; i < mServerFileList.Count; i++)
        {
            if (mServerFileList[i].name == filePath)
            {
                mServerFileList[i].needUpdate = false;
                break;
            }
        }
    }
    void onDownloadAssetFailed(string filePath)
    {
        Debug.Log(filePath + "下载失败");
        ErrorHandle(StaticText.DownloadAssetsUpdateError);
    }

    void onDownloadAllAssetDown()
    {
        Debug.Log("下载任务进程结束");
        if (downloadManager.IsCompleted)
        {
            print("所有ab资源下载成功");
            StartGame();
        }
        else
        {
            print("存在ab资源下载失败");
        }
        UpdateFileList();
    }

    void Update()
    {
        slider.value = (float)downloadManager.FinishedCount / downloadManager.WaitingAndRunningCount;
    }

    void OnApplicationQuit()
    {
        UpdateFileList();
    }

    void UpdateFileList()
    {
        if (mServerFileList != null && mServerFileList.Count > 0)
        {
            string reslut = string.Empty;
            FileData fileData = null;
            for (int i = 0; i < mServerFileList.Count; i++)
            {
                fileData = mServerFileList[i];
                if (fileData.needUpdate == false)
                {
                    reslut += fileData.name + "|" + fileData.md5 + "|" + fileData.size + "
";
                }
            }
            reslut = reslut.TrimEnd('
');
            File.WriteAllText(AppConfig.LocalMD5FilePath, reslut);
            print("保存已经下载的文件的MD5值");
        }
    }
}
Client:MyFileClient
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEngine;

public class MyFileServer : MonoBehaviour
{
    void Start()
    {
        StartUp();
    }

    int serverPort = 12345;
    int listenCount = 10000;
    int buffSize = 1024 * 1024;


    Socket serverSocket;
    void StartUp()
    {
        try
        {
            serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPEndPoint ipEndPoint = new IPEndPoint(GetMyIP(), serverPort);
            serverSocket.Bind(ipEndPoint);
            serverSocket.Listen(listenCount);
            Thread receiveThread = new Thread(ReceiveClientMsg);
            receiveThread.Start();
            Debug.Log("启动成功");
        }
        catch (SocketException ex)
        {
            Debug.LogError(ex.Message);
        }
    }

    void ReceiveClientMsg()
    {
        while (true)
        {
            Socket servicesSocket = serverSocket.Accept();
            if (servicesSocket != null)
            {
                Thread newThread = new Thread(new ParameterizedThreadStart(HandleClientMsg));
                newThread.Start(servicesSocket);
            }
        }
    }

    void HandleClientMsg(object _serivesSocket)
    {
        Socket serivesSocket = (Socket)_serivesSocket;
        byte[] buff = new byte[buffSize];
        int len;
        while ((len = serivesSocket.Receive(buff)) != 0)
        {
            string msg = Encoding.UTF8.GetString(buff, 0, len);
            print("客户端返回消息 = " + msg);
            SentFileToClient(serivesSocket, "V:/Upload/" + msg);
        }
    }

    void SendMsgToClient(Socket serivesSocket, byte[] bytes)
    {
        serivesSocket.Send(bytes, 0, bytes.Length, SocketFlags.None);
    }

    void SentFileToClient(Socket serivesSocket, string filePath)
    {
        using (FileStream read = new FileStream(filePath, FileMode.Open, FileAccess.Read))
        {
            byte[] buff = new byte[buffSize];
            int len = 0;
            while ((len = read.Read(buff, 0, buff.Length)) != 0)
            {
                Array.Resize(ref buff, len);
                SendMsgToClient(serivesSocket, buff);
            }
        }
        SendMsgToClient(serivesSocket, Encoding.UTF8.GetBytes("END"));
        serivesSocket.Close();
    }

    void OnApplicationQuit()
    {
        print("关闭socket");
        serverSocket.Close();
    }

    IPAddress GetMyIP()
    {
        IPHostEntry IpEntry = Dns.GetHostEntry(Dns.GetHostName());
        IPAddress ip = null;
        for (int i = 0; i < IpEntry.AddressList.Length; i++)
        {
            if (IpEntry.AddressList[i].AddressFamily == AddressFamily.InterNetwork)//检查是不是IPv4,IPv6是InterNetworkv6
            {
                ip = IpEntry.AddressList[i];
                break;
            }
        }
        return ip;
    }
}
Server:MyFileServer
--默认值可以不传
local ConfigHelpers = {}

--设置物体高亮  target:设置对象   isLigth:是否高亮   seeThrough:是否穿透(默认为true,穿透) startColor:高亮开始颜色(默认初始化)  endColor:高亮结束颜色(默认初始化)  flashingFrequency:闪烁频率(默认为2)  flashingDelay:闪烁延迟(默认为0) nextConfigIdList:完成过后要执行的id集合
function ConfigHelpers.SetHeightLight( target , isLight , seeThrough , startColor , endColor , flashingFrequency , flashingDelay  , nextConfigIdList )
    local target = ConfigHelpers.FindGameObject(target)
    if target then
        flashingFrequency = flashingFrequency or 0.5
        flashingDelay = flashingDelay or 0
        seeThrough = (seeThrough == nil and {true} or {seeThrough})[1]
        startColor = startColor or Color.green
        endColor = endColor or Color.red
        GameUtils.SetHeightLight( target , isLight, seeThrough, startColor , endColor , flashingFrequency , flashingDelay ) 
        local shla = target:GetComponent("SyncHighLightAction")
        if shla == nil then
            shla = target:AddComponent(typeof(SyncHighLightAction))
        end  
        shla:UpdateStatus(isLight, seeThrough , startColor , endColor , flashingFrequency , flashingDelay)
        if nextConfigIdList then
            ConfigHelpers.ExecIdList(nextConfigIdList)
        end
    end
end

--播放动画  target:设置对象  clipName:动画名称  nextConfigIdList:完成过后要执行的id集合  speed:速度(默认为1)   normalizedTime:动画从哪播放(默认为0,从头开始播放)
function ConfigHelpers.PlayAnimation( target , clipName , nextConfigIdList , speed , normalizedTime )
    if target then
        target = ConfigHelpers.FindGameObject(target)
        if target then
            local anima = target:GetComponent('Animation')
            if anima then
                local saa = target:GetComponent("SyncAnimationAction")
                if saa == nil then
                    saa = target:AddComponent(typeof(SyncAnimationAction))
                end
                speed = speed or 1
                normalizedTime = normalizedTime or 0
                local clip = anima:GetClip(clipName)
                if clip then
                    anima.clip = clip
                    local tClip = anima:get_Item(clipName)
                    tClip.speed = speed
                    tClip.normalizedTime = normalizedTime
                    anima:Play()
                    saa:SendMsgToServer()
                    if nextConfigIdList then
                        local allTime = anima.clip.length / math.abs(speed)
                        GameUtils.StartCoroutineDelaySec(function ( ... )
                            ConfigHelpers.ExecIdList(nextConfigIdList)
                        end,allTime)
                    end
                else
                    Print_t('<color=red>error:</color>Cannot find animation object = '..clipName)
                end
            end
        end
    end
end

--播放动画  target:设置对象  clipName:动画名称  nextConfigIdList:完成过后要执行的id集合  speed:速度(默认为1)   normalizedTime:动画从哪播放(默认为0,从头开始播放)
function ConfigHelpers.PlayAnimator( target , clipName , layer , speed , nextConfigIdList )
    if target then
        target = ConfigHelpers.FindGameObject(target)
        if target then
            local anima = target:GetComponent('Animator')
            if anima then
                local saa = target:GetComponent("SyncAnimatorAction")
                if saa == nil then
                    target:AddComponent(typeof(SyncAnimatorAction))
                end
                speed = speed or 1
                if clipName then
                    anima:Play(clipName,layer)
                    anima.speed = speed
                    if nextConfigIdList then
                        local allTime = anima:GetCurrentAnimatorStateInfo(layer).length / math.abs(speed)
                        GameUtils.StartCoroutineDelaySec(function ( ... )
                            ConfigHelpers.ExecIdList(nextConfigIdList)
                        end,allTime)
                    end
                else
                    Print_t('<color=red>error:</color>Cannot find animation object = '..clipName)
                end
            end
        end
    end
end

--延迟几秒执行     sec:延迟秒数    nextConfigIdList:完成过后要执行的id集合
function ConfigHelpers.DelaySec( sec , nextConfigIdList )
    GameUtils.StartCoroutineDelaySec(function ( ... )
        if nextConfigIdList then
            ConfigHelpers.ExecIdList(nextConfigIdList)
        end 
    end,sec)
end

--播放声音  audioName:音频名称   delaySec:延迟几秒播放(默认为0)  nextConfigIdList:完成过后要执行的id集合
function ConfigHelpers.PlayAudio( audioName , delaySec , nextConfigIdList )
    local audioLibrary = AudioLibrary.instance
    local audioClip = audioLibrary:GetAudioClip(audioName)
    if audioClip then
        delaySec = delaySec or 0
        AudioSourceGlobal.clip = audioClip
        AudioSourceGlobal:PlayDelayed(delaySec)
        audioLibrary:SendSyncAudio(audioName,delaySec)
        if nextConfigIdList then
            GameUtils.StartCoroutineDelaySec(function ( ... )
                ConfigHelpers.ExecIdList(nextConfigIdList)
            end,delaySec + audioClip.length)
        end
    else
        Print_t('<color=red>error:</color>Cannot find audio object = '..audioName)
    end
end

--关闭音频播放   nextConfigIdList:完成过后要执行的id集合
function ConfigHelpers.CloseAudio( nextConfigIdList )
    AudioSourceGlobal.clip = nil
    AudioSourceGlobal:Stop()
    if nextConfigIdList then
        ConfigHelpers.ExecIdList(nextConfigIdList)
    end 
end

--设置组件状态  target:设置对象  enabled:是否关闭   nextConfigIdList:完成过后要执行的id集合
function ConfigHelpers.SetComponentEnabled( target , componentName , enabled , nextConfigIdList )
    if target then
        target = ConfigHelpers.FindGameObject(target)
        if target then
            local component = target:GetComponent(componentName)
            if component then
                component.enabled = enabled
                if nextConfigIdList then
                    ConfigHelpers.ExecIdList(nextConfigIdList)
                end 
            else
                Print_t('<color=red>error:</color>Cannot find component object = '..clipName)
            end
        end
    end
end

--开始计时    nextConfigIdList:完成过后要执行的id集合
local isEndTime = false
local tempTime = 0
function ConfigHelpers.StartTime( nextConfigIdList )
    tempTime = 0
    isEndTime = false
    GameUtils.StartCoroutineWaitUntil(function (  )
        tempTime = tempTime + Time.deltaTime
        return isEndTime
    end)
    if nextConfigIdList then
        ConfigHelpers.ExecIdList(nextConfigIdList)
    end 
end

--计时结束      nextConfigIdList:完成过后要执行的id集合
function ConfigHelpers.EndTime( target , nextConfigIdList )
    isEndTime = true
    if target then
        target = ConfigHelpers.FindGameObject(target)
        if target then
            local textObj = target:GetComponent('Text')
            if textObj then
                textObj.text = ConfigHelpers.FormatSecond(tempTime)
                local sta = target:GetComponent("SyncTextAction")
                if sta == nil then
                    target:AddComponent(typeof(SyncTextAction))
                end
                if nextConfigIdList then
                    ConfigHelpers.ExecIdList(nextConfigIdList)
                end 
            end
        end
    end
end

--初始化总分    nextConfigIdList:完成过后要执行的id集合
local allScore = 100
function ConfigHelpers.InitScore( score , nextConfigIdList )
    allScore = score
    if nextConfigIdList then
        ConfigHelpers.ExecIdList(nextConfigIdList)
    end 
end

--更新分数     nextConfigIdList:完成过后要执行的id集合
function ConfigHelpers.UpdateScore( value , nextConfigIdList )
    allScore = allScore + value
    if nextConfigIdList then
        ConfigHelpers.ExecIdList(nextConfigIdList)
    end 
end

--获取分数并显示      nextConfigIdList:完成过后要执行的id集合
function ConfigHelpers.GetScore( nextConfigIdList )
    if nextConfigIdList then
        ConfigHelpers.ExecIdList(nextConfigIdList)
    end
    return allScore
end

--计时器  target:设置对象    direction:(1为正计时,-1为倒计时) startCount:起始点  endCount:目标点  nextConfigIdList:完成过后要执行的id集合
function ConfigHelpers.Timer( target , direction , startCount , endCount , nextConfigIdList )
    if target then
        target = ConfigHelpers.FindGameObject(target)
        if target then
            local textObj = target:GetComponent('Text')
            if textObj then
                GameUtils.StartCoroutineWaitUntil(function (  )
                    if direction > 0 then
                        startCount = startCount + Time.deltaTime
                        if startCount >= endCount then
                            if nextConfigIdList then
                                ConfigHelpers.ExecIdList(nextConfigIdList)
                            end
                            Print_t('正计时结束')
                        end                        
                        textObj.text = tostring(math.floor(startCount))
                    else
                        startCount = startCount - Time.deltaTime
                        if startCount <= endCount then
                            if nextConfigIdList then
                                ConfigHelpers.ExecIdList(nextConfigIdList)
                            end
                            Print_t('倒计时结束')
                        end                        
                        textObj.text = tostring(math.ceil(startCount))
                    end
                    local sta = target:GetComponent("SyncTextAction")
                    if sta == nil then
                        target:AddComponent(typeof(SyncTextAction))
                    end
                    local result = (direction > 0 and {startCount >= endCount} or {startCount <= endCount})[1]
                    return result
                end)
            end
        end
    end
end

--淡入淡出  finishNextConfigIdList:淡出后要执行的id集合    stayNextConfigIdList:黑屏时要执行的集合   fadeInTime:淡入花费时间   stayTime:黑屏花费时间  fadeOutTime:弹出花费时间
function ConfigHelpers.FadeInOut( finishNextConfigIdList , stayNextConfigIdList , fadeInTime , stayTime , fadeOutTime )
    fadeInTime = fadeInTime or 1.5
    stayTime = stayTime or 0.5
    fadeOutTime = fadeOutTime or 1.5
    GameUtils.FadeInOut(BlackBgGlobal,fadeInTime,stayTime,fadeOutTime,function ( ... )
        if finishNextConfigIdList then
            ConfigHelpers.ExecIdList(finishNextConfigIdList)
        end
    end,function ( ... )
        if stayNextConfigIdList then
            ConfigHelpers.ExecIdList(stayNextConfigIdList)
        end
    end)
end

--设置对象激活状态   target:设置对象   isActive:是否激活    nextConfigIdList:完成过后要执行的id集合
function ConfigHelpers.SetGameObjectActive( target , isActive , nextConfigIdList )
    if target then
        target = ConfigHelpers.FindGameObject(target)
        if target then            
            local saa = target:GetComponent("SyncActiveAction")
            if saa == nil then
                saa = target:AddComponent(typeof(SyncActiveAction))
            end            
            target.gameObject:SetActive(isActive)
            saa:UpdateStatus()
            if nextConfigIdList then
                ConfigHelpers.ExecIdList(nextConfigIdList)
            end 
        end
    end
end

--设置物体FillAmount
function ConfigHelpers.SetFillAmount( path , value , nextConfigIdList )
   if path then
        target = ConfigHelpers.FindGameObject(path)
        if target then 
            local v = target:GetComponent('Image')
            if v then
                v.fillAmount = value
                GameUtils.SyncFillAmount(path,value)
                if nextConfigIdList then
                    ConfigHelpers.ExecIdList(nextConfigIdList)
                end 
            end
        end
    end
end

--实例化物体   target:要实例化的目标对象    name:为实例化的对象重命名
function  ConfigHelpers.CloneGo( path , name , nextConfigIdList )
    if path then
        local target = ConfigHelpers.FindGameObject(path)
        local result = nil
        if target then
            GameUtils.SyncCloneGo(path,name)
            result = GameObject.Instantiate(target)
            result.transform.parent = target.transform.parent
            result.transform.localEulerAngles = target.transform.localEulerAngles
            result.transform.localPosition = target.transform.localPosition
            result.transform.localScale = target.transform.localScale
            result.name = name
            if nextConfigIdList then
                ConfigHelpers.ExecIdList(nextConfigIdList)
            end 
            return result
        end
    end
    return result
end

--销毁物体   target:要销毁的对象
function  ConfigHelpers.DestoryGo( path , isImmediate , nextConfigIdList )
    if path then
        local target = ConfigHelpers.FindGameObject(path)
        if target then
            isImmediate = (isImmediate == nil and {false} or {isImmediate})[1]
            if isImmediate then            
                CS.UnityEngine.Object.DestroyImmediate(target)
            else
                CS.UnityEngine.Object.Destroy(target)
            end
            GameUtils.SyncDestoryGo(path,isImmediate)
            if nextConfigIdList then
                ConfigHelpers.ExecIdList(nextConfigIdList)
            end 
        end
    end
end

--设置对象旋转   target:设置对象   isLocal:是否本地坐标旋转  time:旋转所需时间    nextConfigIdList:完成过后要执行的id集合
function ConfigHelpers.Rotation( target , isLocal , time , rx , ry , rz , nextConfigIdList )
     if target then
        target = ConfigHelpers.FindGameObject(target)
        if target then
            local tween
            if isLocal then
                tween = target.transform:DOLocalRotate(Vector3(rx, ry, rz), time):SetEase(Ease.Linear)
            else
                tween = target.transform:DORotate(Vector3(rx, ry, rz), time):SetEase(Ease.Linear)
            end
            local sta = target:GetComponent("SyncTransformAction")
            if sta == nil then
                target:AddComponent(typeof(SyncTransformAction))
            end
            tween:OnComplete(function ( ... )
                if nextConfigIdList then
                    ConfigHelpers.ExecIdList(nextConfigIdList)
                end
            end)
        end
    end
end

--设置对象缩放   target:设置对象   time:缩放所需时间    nextConfigIdList:完成过后要执行的id集合
function ConfigHelpers.Scale( target , time , sx , sy , sz , nextConfigIdList )
     if target then
        target = ConfigHelpers.FindGameObject(target)
        if target then
            local tween
            tween = target.transform:DOScale(Vector3(sx, sy, sz), time):SetEase(Ease.Linear)
            local sta = target:GetComponent("SyncTransformAction")
            if sta == nil then
                target:AddComponent(typeof(SyncTransformAction))
            end
            tween:OnComplete(function ( ... )
                if nextConfigIdList then
                    ConfigHelpers.ExecIdList(nextConfigIdList)
                end
            end)
        end
    end
end

--设置主角位置  target:设置对象   px:x值   py:y值   pz:z值   lookTarget:面对的对象   nextConfigIdList:完成过后要执行的id集合
function ConfigHelpers.SetPlayerTransform( px , py , pz , lookTarget , nextConfigIdList )
    local target = nil
    if lookTarget ~= nil then
        target = ConfigHelpers.FindGameObject(lookTarget)
    end
    GameUtils.SetPlayerTransform(px,py,pz,target and target.transform or nil) 
    if nextConfigIdList then
        ConfigHelpers.ExecIdList(nextConfigIdList)
    end   
end

--设置文本内容  target:设置对象   content:内容    nextConfigIdList:完成过后要执行的id集合
function ConfigHelpers.SetText( target , content , nextConfigIdList )
    if target then
        target = ConfigHelpers.FindGameObject(target)
        if target then
            local text = target:GetComponent('Text')
            if text then
                local sta = target:GetComponent("SyncTextAction")
                if sta == nil then
                    target:AddComponent(typeof(SyncTextAction))
                end
                text.text = tostring(content)
                if nextConfigIdList then
                    ConfigHelpers.ExecIdList(nextConfigIdList)
                end   
            end
        end
    end
end

--移动位置   target:设置对象   isLocal:是否是本地坐标   time:移动所需时间  nextConfigIdList:完成过后要执行的id集合
function ConfigHelpers.MoveToTarget( target , isLocal , px , py , pz , rx , ry , rz , time , nextConfigIdList )
    if target then
        target = ConfigHelpers.FindGameObject(target)
        if target then
            rx = rx or 0
            ry = ry or 0
            rz = rz or 0
            local sta = target:GetComponent("SyncTransformAction")
            if sta == nil then
                target:AddComponent(typeof(SyncTransformAction))
            end
            GameUtils.MoveToTarget(target,isLocal,px,py,pz,rx,ry,rz ,time,function ( ... )
                if nextConfigIdList then
                    ConfigHelpers.ExecIdList(nextConfigIdList)
                end
            end)
        end
    end
end

local dataLibrary = {}
--设置数据   dataName:数据名称   dataValue:数据的值   nextConfigIdList:完成过后要执行的id集合
function ConfigHelpers.SetData( dataName , dataValue , nextConfigIdList )
    dataLibrary[dataName] = dataValue
    if nextConfigIdList then
        ConfigHelpers.ExecIdList(nextConfigIdList)
    end
end

--获取数据   dataName:数据名称   nextConfigIdList:完成过后要执行的id集合
function ConfigHelpers.GetData( dataName , nextConfigIdList )
    if nextConfigIdList then
        ConfigHelpers.ExecIdList(nextConfigIdList)
    end
    return dataLibrary[dataName]
end

-- local needExecNextConfigIdList
-- function ConfigHelpers.SceneLoaded( scene , mode )
--     LoadConfig(scene.name,needExecNextConfigIdList)
--     SceneManager.sceneLoaded('-', ConfigHelpers.SceneLoaded)
-- end

--切换场景    sceneName:要切换的场景    nextConfigIdList:完成过后要执行的id集合
function ChangseSceneClearData()
    -- needExecNextConfigIdList = nextConfigIdList
    -- SceneManager.sceneLoaded('+', ConfigHelpers.SceneLoaded)
    -- SceneManager.LoadSceneAsync(sceneName)
    ConfigHelpers.CloseAudio()
    ConfigHelpers.ClearLongPressMove()
    followMeDic = {}
    lookAtDic = {}
    MF.Route.ClearUpdate()
    ConfigHelpers.SetRaySelectListenerStatus(true)
    ConfigHelpers.StopAllCoroutine()
end

--切换场景    sceneName:要切换的场景    nextConfigIdList:完成过后要执行的id集合
function ConfigHelpers.ChangeScene( sceneName , nextConfigIdList )
    -- needExecNextConfigIdList = nextConfigIdList
    -- SceneManager.sceneLoaded('+', ConfigHelpers.SceneLoaded)
    -- SceneManager.LoadSceneAsync(sceneName)
    GameUtils.LoadScene(sceneName,nextConfigIdList,nil,nil)
end

--停止已经开始的所有流程
function ConfigHelpers.StopAllCoroutine( nextConfigIdList )
    GameUtils.StopAllCoroutine()
    if nextConfigIdList then
        ConfigHelpers.ExecIdList(nextConfigIdList)
    end
end

--设置摄像机远近
function ConfigHelpers.SetCameraNearFar( near , far , nextConfigIdList )
    GameUtils.SetNearFar(near,far)
    if nextConfigIdList then
        ConfigHelpers.ExecIdList(nextConfigIdList)
    end
end

--切换操作模式(手柄或者凝视)
function ConfigHelpers.ChangeEventMode( isHandle , nextConfigIdList )
    GameUtils.ChangeEventMode(isHandle)
    if nextConfigIdList then
        ConfigHelpers.ExecIdList(nextConfigIdList)
    end
end

--为按钮注册事件  target:设置对象   nextConfigIdList:完成过后要执行的id集合
function ConfigHelpers.RegisterClick( target , nextConfigIdList )
    if target then
        target = ConfigHelpers.FindGameObject(target)
        if target and nextConfigIdList then
            GameUtils.RegisterClick(target,function ( ... )
                ConfigHelpers.ExecIdList(nextConfigIdList)
            end)
        end
    end
end

function ConfigHelpers.RemoveClick( target , nextConfigIdList )
    if target then
        target = ConfigHelpers.FindGameObject(target)
        if target then
            GameUtils.RemoveClick(target)
        end
        if nextConfigIdList then
            ConfigHelpers.ExecIdList(nextConfigIdList)
        end
    end
end

function ConfigHelpers.OnMouseEnter( target , nextConfigIdList )
    if target then
        target = ConfigHelpers.FindGameObject(target)
        if target and nextConfigIdList then
            GameUtils.OnMouseEnter(target,function ( ... )
                ConfigHelpers.ExecIdList(nextConfigIdList)
            end)
        end
    end
end

function ConfigHelpers.RemoveMouseEnter( target , nextConfigIdList )
    if target then
        target = ConfigHelpers.FindGameObject(target)
        if target then
            GameUtils.RemoveMouseEnter(target)
        end
        if nextConfigIdList then
            ConfigHelpers.ExecIdList(nextConfigIdList)
        end
    end
end

function ConfigHelpers.OnMouseExit( target , nextConfigIdList )
   if target then
        target = ConfigHelpers.FindGameObject(target)
        if target and nextConfigIdList then
            GameUtils.OnMouseExit(target,function ( ... )
                ConfigHelpers.ExecIdList(nextConfigIdList)
            end)
        end
    end
end

function ConfigHelpers.RemoveMouseExit( target , nextConfigIdList )
    if target then
        target = ConfigHelpers.FindGameObject(target)
        if target then
            GameUtils.RemoveMouseExit(target)
        end
        if nextConfigIdList then
            ConfigHelpers.ExecIdList(nextConfigIdList)
        end
    end
end

function ConfigHelpers.SetRaySelectListenerStatus( active , nextConfigIdList )
    GameUtils.SetRaySelectListenerStatus(active)
    if nextConfigIdList then
        ConfigHelpers.ExecIdList(nextConfigIdList)
    end
end

--设置父物体    target:设置对象   parent:父物体   nextConfigIdList:完成过后要执行的id集合
function ConfigHelpers.SetParent( target , parent , px , py , pz , rx , ry , rz , sx , sy , sz , nextConfigIdList )
    if target and parent then
        target = ConfigHelpers.FindGameObject(target)
        if target then
            parent = ConfigHelpers.FindGameObject(parent)
            if parent then
                local sta = target:GetComponent("SyncTransformAction")
                if sta == nil then
                    target:AddComponent(typeof(SyncTransformAction))
                end
                local t = target.transform
                t:SetParent(parent.transform)
                t.localPosition = Vector3( px , py , pz )
                t.localEulerAngles = Vector3( rx , ry , rz )
                t.localScale = Vector3( sx , sy , sz )
                sta:UpdateTargetPath()
                if nextConfigIdList then
                    ConfigHelpers.ExecIdList(nextConfigIdList)
                end
            end
        end
    end
end

--跟随摄像机   target:目标对象  isFollow:是否跟随  distance:面向的距离  nextConfigIdList:执行的id集合
local followMeDic = {}
function ConfigHelpers.FollowMe( target , isFollow , ver , hor , distance , nextConfigIdList)
    if target then
        target = ConfigHelpers.FindGameObject(target)
        if target then
            if isFollow then
                local sta = target:GetComponent("SyncTransformAction")
                if sta == nil then
                    target:AddComponent(typeof(SyncTransformAction))
                end
                local player = GameObject.Find('FrameWork/Customs/ViveFocus/WaveVR/head').transform
                distance = distance or 4  
                ver = ver or 0
                hor = hor or 0
                local spanEulerAngles = target.transform.eulerAngles - player.eulerAngles
                followMeDic[target] = true
                GameUtils.StartCoroutineWaitUntil(function ( ... )
                    local v = player.position
                    local result = v + (player.forward * distance)  + (player.right * hor)  + (player.up * ver)
                    target.transform.position = result
                    target.transform.eulerAngles = spanEulerAngles + player.eulerAngles
                    return not followMeDic[target]
                end)
            else
                followMeDic[target] = nil
            end
            if nextConfigIdList then
                ConfigHelpers.ExecIdList(nextConfigIdList)
            end
        end
    end
end

--跟随摄像机   target:目标对象  isStop:是否停止面向自己(默认为false)
local lookAtDic = {}
function ConfigHelpers.LookMe( target , isStop , nextConfigIdList)
    if target then
        target = ConfigHelpers.FindGameObject(target)
        if target then
            local sta = target:GetComponent("SyncTransformAction")
            if sta == nil then
                target:AddComponent(typeof(SyncTransformAction))
            end
            local player = GameObject.Find('FrameWork/Customs/ViveFocus/WaveVR/head').transform
            if not isStop then
                lookAtDic[target] = true
                local isUI = GameUtils.IsUI(target)
                GameUtils.StartCoroutineWaitUntil(function ( ... )
                    target.transform:LookAt(player.position)
                    if isUI then
                        target.transform:Rotate(Vector3.up, 180)
                    end
                    return not lookAtDic[target]
                end)
            else
                lookAtDic[target] = nil
            end
            if nextConfigIdList then
                ConfigHelpers.ExecIdList(nextConfigIdList)
            end
        end
    end
end

--播放视频   target:目标对象   videoName:视频名称(视频放到StreamingAssets文件夹下才有效)  isLoop:是否重复播放(默认重复)  nextConfigIdList:如果isLoop是true的话,那么会立马执行id集合,否则会等视频播放完才执行id集合
function ConfigHelpers.PlayVedio( target , videoName , isLoop , nextConfigIdList )
    if target then
        target = ConfigHelpers.FindGameObject(target)
        if target then
            target:SetActive(false)
            isLoop = (isLoop == nil and {false} or {isLoop})[1]
            local mp = target:GetComponent('MediaPlayerCtrl')
            if mp == nil then
                mp = target:AddComponent(typeof(MediaPlayerCtrl))
            end
            mp.m_TargetMaterial = { target }
            -- mp.m_objResize = { target }
            mp:Load(videoName)
            mp.m_bLoop = isLoop
            mp:Play()
            target:SetActive(true)
            if isLoop then
                if nextConfigIdList then
                    ConfigHelpers.ExecIdList(nextConfigIdList)
                end
            else
                mp.OnEnd = function ( ... )
                    if nextConfigIdList then
                        ConfigHelpers.ExecIdList(nextConfigIdList)
                    end
                end
            end            
        end
    end
end

--获取手柄在圆形区域的触摸点
function ConfigHelpers.GetTouchPadPosition( nextConfigIdList )
    if nextConfigIdList then
        ConfigHelpers.ExecIdList(nextConfigIdList)
    end
    return GameUtils.GetTouchPadPosition()
end

--判断手柄上某键是否按下或者鼠标左键是否按下
function ConfigHelpers.GetKeyDown( keycode , nextConfigIdList )
    if nextConfigIdList then
        ConfigHelpers.ExecIdList(nextConfigIdList)
    end
    return GameUtils.GetKeyDown(keycode)
end

--判断手柄上某键是否抬起或者鼠标左键是否抬起
function ConfigHelpers.GetKeyUp( keycode , nextConfigIdList )
    if nextConfigIdList then
        ConfigHelpers.ExecIdList(nextConfigIdList)
    end
    return GameUtils.GetKeyUp(keycode)
end

--判断手柄上某键是否长按
function ConfigHelpers.GetKey( keycode , nextConfigIdList )
    if nextConfigIdList then
        ConfigHelpers.ExecIdList(nextConfigIdList)
    end
    return GameUtils.GetKey(keycode)
end

--增加物体长按拖动
function ConfigHelpers.AddLongPressMove( target , nextConfigIdList )
    if target then
        target = ConfigHelpers.FindGameObject(target)
        if target then
            local sta = target:GetComponent("SyncTransformAction")
            if sta == nil then
                target:AddComponent(typeof(SyncTransformAction))
            end
            GameUtils.AddLongPressMove(target)
        end
    end
    if nextConfigIdList then
        ConfigHelpers.ExecIdList(nextConfigIdList)
    end
end

--移除物体长按拖动
function ConfigHelpers.RemoveLongPressMove( target , nextConfigIdList )
    if target then
        target = ConfigHelpers.FindGameObject(target)
        if target then
            GameUtils.RemoveLongPressMove(target)
        end
    end
    if nextConfigIdList then
        ConfigHelpers.ExecIdList(nextConfigIdList)
    end
end

--清除所有能拖拽物体的状态,使之不能被拖拽
function ConfigHelpers.ClearLongPressMove( nextConfigIdList )
    GameUtils.ClearLongPressMove()
    if nextConfigIdList then
        ConfigHelpers.ExecIdList(nextConfigIdList)
    end
end

function ConfigHelpers.ExecId( id )
    if id == nil then
        Print_t('<color=red>This ID is illegal</color>')
        return
    end
    if ConfigGlobal[id] == nil or ConfigGlobal[id].action == nil then
        Print_t('<color=red>This ID does not exist = '..id..'</color>')
        return
    end
    Print_t(id..' = '..ConfigGlobal[id].action)
    if ConfigGlobal[id].action then
        -- assert(load(ConfigGlobal[id].action))()
        xpcall(load(ConfigGlobal[id].action),function ( ... )
            Print_t(debug.traceback(),'<color=red>error</color>')
        end)
    end
end

function ConfigHelpers.ExecIdList( idListOrFunc )
    if idListOrFunc then
        if type(idListOrFunc) == 'table' then
            for i,id in ipairs(idListOrFunc) do
                ConfigHelpers.ExecId(id)
            end
        else
            idListOrFunc()
        end
    end
end

function ConfigHelpers.FindGameObject( path )
    if path and path ~= '' then
        local target = GameObject.Find(path)
        if target then
            return target
        else
            local startIndex = string.find(path,'/')
            if startIndex then
                local rootPath = string.sub(path,0,startIndex-1)
                local root = GameObject.Find('/'..rootPath)
                if root then
                    local childPath = string.sub(path,startIndex+1,path.length)
                    local result = root.transform:Find(childPath)
                    if result then
                        return result.gameObject
                    end
                end
            end
        end
    end
    Print_t('<color=red>error:</color>No object was found = '..(path or ''))
    return nil
end

--总秒数格式化   传总秒数   返回的时间格式为 59:34   第二个参数表示是否显示小时
function ConfigHelpers.FormatSecond( senconds , isShowHour )
    local min,sec = math.modf(senconds / 60)
    local hour,sec = math.modf(senconds / 3600)

    sec = math.floor(senconds - min*60)
    if sec < 10 then
       sec = '0'..sec
    end
    if hour < 10 then
       hour = '0'..hour
    end
    local t = min ..':'..sec
    return isShowHour and hour..t or t
end

return ConfigHelpers
ConfigHelpers
原文地址:https://www.cnblogs.com/MrZivChu/p/hr.html