模块化不论是对工程管理仍是开发来讲, 都是百利而无一害的, 从开发层面来看, 它强行让开发人员在开发的时候要考虑到跟其它模块的解耦, 考虑在其它环境下的泛用性和复用性, 直接就能提升开发水平, 而且在开新项目或者移植的时候, 可以拿来就用, 或者单独使用, 或者拼凑使用这些模块, 要什么功能就下哪一个插件, 这就是模块化的好处, 而且输入输出接口可以稳定的话, 其它调用到这些模块的人员也会轻松不少.编辑器
然而如今咱们的工程也是一锅粥, 在前面的开发中也没有想过这些...模块化
如今若是进行模块化, 不但须要进行前期的代码解耦, 中期的代码剥离和提供大量外部注入, 到后期的管理工具都须要作起来, 才能完成一个正常的模块化过程, 如今用的 Unity3D 它官方提供的插件下载 / 更新 / 管理方案就跟我想的差很少, 想要什么就下载便可, 全部下载来的插件都是可以不依赖外部环境运行的, 这就很是方便了, 我还记得之前魔兽世界插件, 不少插件会依赖其它库, 好比 ACE 函数库, 你下了插件没有库结果没有用, 它不是一个自恰的...svn
这两天试了一下, 感受仍是直接经过 SVN 进行版本控制比较方便, 在 Repositories 下建立不一样的工程目录, 每一个工程就是一个模块的代码库, 经过 Unity 打开工程后编辑修改从开发工程中提取出来的代码, 修改为一个可以自恰的工程, 这些模块就能独立运做了.函数
而后是模块管理工具, 个人想法是也建立一个SVN仓库, 在一个工程的 CheckOut 中再进行管理工具的 CehckOut, 而管理工具提供可视化的选项, 能够选择相应的模块进行下载和更新, 这样每一个模块只要加个文档让别人知道须要初始化的东西而后就能使用就好了.工具
--------------------- 开工的分界线 -----------------------ui
因而就开始尝试看看吧, 先找两个比较典型的模块来制做 : this
1. 资源打包加载模块 [ ResourceModule ]spa
2. UI 模块 [ UIModule ]插件
显然资源打包是一个对其它东西没有依赖的模块, 只须要提供接口便可, 比较简单, 而 UI 模块很依赖于资源加载, 咱们就须要给它提供注入加载资源的方法才能运行的模块.命令行
在 SVN 上先建立它们的目录, 大体以下 :
这里额外的一个 BasicProject 就是管理工具的工程, 它做为起点来提供模块下载控制.
资源加载模块没什么好说的, 自己就是独立的, UI 模块只须要使用外部提供的加载过程便可, 看看有改动的地方 :
public sealed class UIManager : MonoSingleton<UIManager> { private static System.Func<string, string, GameObject> ms_loadModule = null; // set load method public static void ImplementLoadModule(System.Func<string, string, GameObject> loadModule) { ms_loadModule = loadModule; } public T LoadUI<T>(string prefabLoadPath) where T : UIBase { if(ms_loadModule == null) { throw new System.NotImplementedException("Resource Load Module Not Implement !!!"); // Tips } T ui = null; var go = ms_loadModule.Invoke(prefabLoadPath, UIPoolName); if(go) { ui = go.GetComponent<T>(); // ... } return ui; } }
这里能够经过静态设置读取方式, 添加错误提示的话, 使用起来基本不会出错了.
固然其它模块确定没这么给你面子, 确定处处调用其它模块的代码, 这就须要后续再看了, 至少基础的底层代码是能够作到的.
而后是控制工具, BasicProject 其实就是一个编辑器工具, 它提供图形界面的话就能够方便控制了, 因而写到一个 EditorWindow 里面, 它的逻辑意外的很简单, 首先在里面用代码写死一些模块名称和相应的 SVN 地址, 来表示模块下载地址, 而后经过查询远程版本和本地版本, 来决定是否须要下载或是更新, 固然是由用户本身选择了, 结构大体以下 :
public class ModuleStatus { public string remotePath; public int remoteVersion; public string localPath; public int localVersion; } public static readonly Dictionary<string, string> Modules = new Dictionary<string, string>() { { "BasicProject", "https://XXXX/svn/BasicProject/Assets/BasicProject" }, { "ResourceModule", "https://XXXX/svn/ResourceModule/Assets/ResourceModule" }, { "UIModule", "https://XXXX/svn/UIModule/Assets/UIModule" }, };
由于它把本身的工程 BasicProject 也加入到插件列表中了, 它就能更新它本身了, 因此这个列表等因而能够正常被更新的, 并不是写死的了. 它的初始化逻辑以下 :
private Dictionary<string, ModuleStatus> m_existsModules = new Dictionary<string, ModuleStatus>(); private HashSet<string> m_noExistsModules = new HashSet<string>(); private volatile bool _inited = false; private volatile int _initCount = 0; private volatile int _initedCount = 0; private void OnEnable() { Load(); } private void Load() { UnitySVN.StopAllThreads(); _inited = false; _initCount = 0; _initedCount = 0; m_existsModules.Clear(); m_noExistsModules.Clear(); m_noExistsModules.UnionWith(Modules.Keys); var folders = Directory.GetDirectories(Application.dataPath, "*.*", SearchOption.AllDirectories); foreach(var folder in folders) { var module = Path.GetFileName(folder); if(Modules.ContainsKey(module)) { _initCount++; UnitySVN.IsVersioned(folder, (_versioned) => { if(_versioned) { var status = new ModuleStatus() { localPath = CommonEditorUtils.LeftSlash(folder), remotePath = Modules[module], }; UnitySVN.GetVersionNumber(@status.remotePath, (_ver) => { status.remoteVersion = _ver; }); UnitySVN.GetVersionNumber(@status.localPath, (_ver) => { status.localVersion = _ver; }); lock(m_existsModules) { m_existsModules[module] = status; } lock(m_noExistsModules) { m_noExistsModules.Remove(module); } } _initedCount++; if(_initedCount >= _initCount) { _inited = true; } }); } } }
SVN 相关的后面再说, 初始化首选去找模块相应的名称的文件夹, 而后查询是否在版本控制之下, 若是没有就加入到 m_noExistsModules 列表中, 显示为可下载. 若是存在就加入到 m_existsModules 中, 这里面包含了相关信息, 包括远程 SVN 地址, 远程版本号, 本地地址, 本地版本号等, 若是版本对不上, 显示为可更新 :
显示 ResourceModule 和 UIModule 都处于可下载状态, 若是点击下载, 会获得下面的图 :
显示的 Version 是本地版本号, 而后咱们在其它工程中对 ResourceModule 进行更新, 使它提高版本号, 而后再打开面板看 :
显示可更新, 点击后工程就更新到相应版本了 :
把模块都下载下来以后, 就能够进行UI加载了, 看看要怎样初始化 :
由于有了UI模块, 能够继承一个 UIBase :
public class MainUI : UIModule.UIBase { }
加载以前只要初始化读取模块给 UIManager 就好了
using UnityEngine; using ResourceModule; using UIModule; public class Test : MonoBehaviour { private void Awake() { UIManager.ImplementLoadModule(PrefabLoader.Instance.Spawn); } // Use this for initialization void Start() { UIManager.Instance.Get<MainUI>("MainUI"); } }
由于旧工程中的加载UI和这里同样, 区别只是须要对 UIManager 设置一个 LoadModule 的函数, 以前就是直接嵌入 PrefabLoader 的, 没有太大区别.
通过这些改动, 就能得到一个解耦的模块代码了, 而且能随用随下, 其实对于复杂工程来讲反而是好事, 提升了开发人员的程序设计能力, 减小耦合...
------------------- SVN -----------------
以前的各类逻辑直接调用的 TortoiseSVN 的扩展命令 /c , 为了泛用性修改成直接调用 svn 的命令行的方式了, 只有一个须要注意的地方, 命令行一个命令的长度为8192长度, 经过一些方法能够加大到32KB, 但是很麻烦也没有根本解决问题, 由于通常使用绝对路径来传输, 像下面这样 :
svn add E:\XXXX\OOOO\Assets\Test.cs.meta E:\XXXX\OOOO\Assets\Test.cs
文件多了确定完蛋, 写一个循环上传的过程就好了.