当前位置:   article > 正文

Unity项目经验分享

unity项目经验

一、代码规范

1、C#代码规范
/**
 * 程序说明
 */

using UnityEngine;
using UnityEngine.UI;

/// <summary>
/// 枚举大写字母开头,驼峰式
/// </summary>
public enum AvatarType
{
	Saber,
	Archer,
	Rider,
}

/// <summary>
/// 类名大写字母开头,驼峰式
/// </summary>
public class Example : MonoBehaviour
{
    #region Abbreviation
    /**
     * GameObject->Go
     * Transform->Trans
     * Position->Pos
     * Button->Btn
     * Dictionary->Dict
     * Number->Num
     * Current->Cur
     */
    #endregion

	/// <summary>
    /// 公有变量和公共属性使用首字母大写驼峰式
    /// </summary>
    public int ExampleNum;
    public int ExampleIndex { get; set; }
	/// <summary>
	/// 常亮使用全大写,单词与单词用下划线分割
	/// </summary>
	private const int EXAMPLE_DK_ID = 20;
    /// <summary>
    /// 私有最好也别省略private
    /// 私有变量可以加前缀 m 表示私有成员 mExampleBtn
    /// 但在Unity进行私有序列化时[SerializeField] 可视面板上的 M 就比较奇怪
    /// </summary>
    private Button mExampleBtn;
    
    /// <summary>
    /// 方法名一律使用首字母大写驼峰式
    /// </summary>
    public void ExampleFunc()
    {
        //局部变量最好用var匿名声明 小写驼峰式
        var go = transform.Find("Example").gameObject
    }
	
	/// <summary>
    /// 函数与函数直接留一行空行
    /// </summary>
    public void ExampleFunc2()
    {
        //局部变量最好用var匿名声明 小写驼峰式
        var go = transform.Find("Example").gameObject
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
2、Lua代码规范
-- 系统功能扩展:保持与原功能的风格一致,使用小写,如string的拓展:string.split
-- 逻辑功能命名:保持与C#代码规范一致,采用大写驼峰式命名
--------------------------------示例---------------------------------------
--[[
    @desc: 程序说明
]]

local Example = Class("Example")

-- 方法说明(全部为局部)
local function ExampleFunc(self)
end

-- 方法导出(都定义在底部)
Example.ExampleFunc = ExampleFunc

return Example
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
3、Lua基本框架
└── LuaScripts
    ├── Base                  # 基础公用模块(基本上就是C#端翻成Lua)
    │   ├── Collections       # 常用的集合(Stack, Queue)
    │   ├── Common            # 公共方法(Class, Singleton等)
    │   ├── Debug             # 调试器
    │   ├── Event             # 事件管理
    │   ├── Extension         # Lua端系统方法拓展(string的拓展等)
    │   ├── Pool              # 对象池
    │   ├── Timer             # 定时器
    │   ├── Update            # Update管理
    │   └── Init              # 基础公共模块初始化
    ├── Global                # 全局内容
    │   └── Init              # 全局内容初始化
    ├── Modules               # 模块目录
    │   └── ...               # 各个模块
    ├── Views                 # 视图UI目录
    │   ├── ...               # 各个功能UI
    │   ├── ViewConfig        # UI与Prefab对应配置
    │   └── ViewManager       # 视图UI管理
    ├── Config                # 全局配置文件
    └── GameMain              # Lua程序入口
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

二、资源打包

1、打包策略

冗余情况
A依赖CB依赖C,如果只打成A,B两个Bundle包,就会冗余C。每个资源文件单独打成一个Bundle包,简单方便,同时保证不冗余,但会造成Bundle数目过多。

粒度问题
A依赖B、C、D,按单资源打成单Bundle包的策略会打成A,B,C,D四个Bundle,加载依赖是要加载4个Bundle包,但如果C,D是被A所唯一依赖的,那么更好的策略就是A,C,D打成一个Bundle包,那么加载依赖就只用加载2个Bundle包,提高了加载效率,同时也不会冗余资源。(类似散图和图集的关系,但具体两者差距多少,并没有实际测过)

打包策略
保证不冗余的情况下,最大限度的减少粒度:
a. 指定根目录,记录所有文件和其被依赖列表
b. 文件被依赖数大于1,就单独打成Bundle包,否则向上归并至被依赖项

2、核心代码
/// <summary>
/// 合并依赖,并分组资源
/// </summary>
public static void GroupAssetBundles()
{
    /* 清除同层依赖 把同层之间被依赖的节点下移 (a->b, a->c, b->c) ==> (a->b->c)
     *      a              a
     *     /  \    ==>    /
     *    b -> c         b
     *                  /
     *                 c 
     *  例如:prefab上挂着mat, mat依赖shder。特别注意,此时prefab同时依赖mat,和shader。可以点击右键查看
     *  (prefab->mat, prefab->shader, mat->shader) ==> (prefab->mat->shader)
     */
    var removeList = new List<string>();
    foreach(var item in mAssetItemDict)
    {
        removeList.Clear();
        var path = item.Key;
        var assetItem = item.Value;
        foreach(var depend in assetItem.Depends)
        {
            var dependAssetItem = GetAssetItem(depend);
            foreach(var beDepend in dependAssetItem.BeDepends)
            {
                if (assetItem.Depends.Contains(beDepend))
                    removeList.Add(depend);
            }
        }
        foreach(var depend in removeList)
        {
            assetItem.Depends.Remove(depend);
            var dependAssetItem = GetAssetItem(depend);
            dependAssetItem.BeDepends.Remove(path);
        }
    }

    /* 向上归并依赖
     *      a        e                 
     *       \      /                    
     *        b    f     ==>  (a,b,c,h) -> (d) <- (e,f)
     *      / | \ /                          
     *     c  h  d      
     */
    foreach(var item in mAssetItemDict)
    {
        var path = item.Key;
        var assetItem = item.Value;
        while(assetItem.BeDepends.Count == 1)
        {
            assetItem = GetAssetItem(assetItem.BeDepends[0]);
            if (assetItem.BeDepends.Count != 1)
            {
                item.Value.AssetBundleName = assetItem.AssetBundleName;
            }
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
3、追加图集的打包策略
/// <summary>
/// 遍历Atlas下所有的SpriteAtlas,建立图片资源依赖
/// </summary>
public static void CreateSpriteAtlasMap()
{
    var dir = new DirectoryInfo(BuilderConfig.SpriteAtlasPath);
    if (!dir.Exists) return;
    var files = dir.GetFiles();
    for (var i = 0; i < files.Length; i++)
    {
        var fileInfo = files[i];
        EditorUtility.DisplayProgressBar("CreateSpriteAtlasMap", fileInfo.FullName, 1.0f * (i + 1) / files.Length);
        if (AssetUtils.ValidAsset(fileInfo.FullName))
        {
            var fullPath = fileInfo.FullName.Replace("\\", "/");
            var path = fullPath.Substring(fullPath.IndexOf(BuilderConfig.AssetRootPath));
            var assetItem = GetAssetItem(path);
            var depends = AssetDatabase.GetDependencies(path);
            foreach (var depend in depends)
            {
                if (AssetUtils.ValidAsset(depend) && depend != path)
                {
                    mSpriteAtlasDict.Add(depend, path);
                    assetItem.Depends.Add(depend);
                    var dependAssetItem = GetAssetItem(depend);
                    dependAssetItem.BeDepends.Add(path);
                }
            }
            mSpriteAtlasDict.Add(path, path);
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
4、追加Lua的打包策略

Assets/LuaScripts/.lua -> Assets/GameAssets/LuaScripts/.lua.bytes

/// <summary>
/// 创建Lua二进制文件
/// Assets/LuaScripts/*.lua -> Assets/GameAssets/LuaScripts/*.lua.bytes
/// </summary>
public static void CreateLuaBytes()
{
    if (Directory.Exists(BuilderConfig.LuaScriptsDestPath))
    {
        FileUtil.DeleteFileOrDirectory(BuilderConfig.LuaScriptsDestPath);
    }
    var dir = new DirectoryInfo(BuilderConfig.LuaScriptsSrcPath);
    if (!dir.Exists) return;
    var stack = new Stack<DirectoryInfo>();
    stack.Push(dir);
    while (stack.Count > 0)
    {
        var dirInfo = stack.Pop();
        var files = dirInfo.GetFiles();

        foreach (var file in files)
        {
            if (AssetUtils.ValidAsset(file.FullName))
            {
                var fullPath = file.FullName.Replace("\\", "/");
                var startIndex = fullPath.IndexOf("LuaScripts");
                var destPath = fullPath.Insert(startIndex, "GameAssets/") + ".bytes";
                if (Base.Utils.FileUtil.CheckFileAndCreateDirWhenNeeded(destPath))
                {
                    file.CopyTo(destPath);
                }
            }
        }

        var subDirs = dirInfo.GetDirectories();
        foreach (var subDir in subDirs)
        {
            stack.Push(subDir);
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

三、资源加载

1、注意事项

资源发布到真机前,要构建对应平台的AssetBundle
iOS真机和Mac下的编辑模式,用WWW加载StreamingAssets下的资源路径:"file://" + Application.streamingAssetsPath
iOS真机测试:Player Setting –> Other Setting -> Strip Engine Code去掉

2、Unity资源加载方式

Resources:官方不推荐
AssetBundle:官方介绍
AssetDataBase: 编辑模式下

3、同步加载

编辑器模式下:UnityEditor.AssetDatabase.LoadAssetAtPath

AssetBundle同步加载:
1.根据AssetBundleManifest,同步加载自身及所有依赖的AssetBundle(LoadFromFile)
2.根据加载出来的AssetBundle同步加载对应资源(LoadAsset)

4、同步加载AssetBundle核心代码
/// <summary>
/// 同步加载所有依赖Bundle
/// </summary>
/// <param name="path">Path.</param>
public AssetBundle SyncLoadAllAssetBundle(string path)
{
    var manifest = GetAssetBundleManifest();
    var assetBundleName = PathToBundle(path);
    var dependencies = manifest.GetAllDependencies(assetBundleName);
    foreach (var depend in dependencies)
    {
        SyncLoadAssetBundle(depend);
    }
    return SyncLoadAssetBundle(assetBundleName);
}

/// <summary>
/// 根据AB包名同步加载AssetBundle
/// </summary>
private AssetBundle SyncLoadAssetBundle(string assetBundleName)
{
    AssetBundleUnit unit = null;
    if (!mAssetBundleUnitDict.TryGetValue(assetBundleName, out unit))
    {
        var path = PathUtil.GetLocalAssetBundleFilePath(assetBundleName);
        var assetBundle = AssetBundle.LoadFromFile(path);
        if (assetBundle == null)
        {
            Debugger.LogError("Load AssetBundle Error: " + assetBundleName);
            return null;
        }
        unit = mAssetBundleUnitPool.Spawn();
        unit.AssetBundle = assetBundle;
        unit.RefCount++;
        mAssetBundleUnitDict.Add(assetBundleName, unit);
    }
    else
    {
        unit.RefCount++;
    }
    return unit.AssetBundle;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

四、热更资源

1、热更lua

app整包的时候,备份一份lua全量文件,后面打lua增量包的时候,根据文件差异进行比对,新增和差异的lua文件打成一个lua_update.bundle,放在一个update文件夹中,并压缩成zip,放到服务器端,客户端通过https下载增量包并解压到Application.persistentDataPath目录。游戏加载lua文件的时候,优先从lua_update.bundle中查找。

2、热更资源

做个编辑器工具,指定某个或某些资源文件(预设、音频、动画、材质等),打成多个assetbundle,放在一个update文件夹中,并压缩成一个zip,放到服务器端,客户端通过https下载增量包并解压到Application.persistentDataPath目录。
游戏加载资源文件的时候,优先从update文件夹中查找对应的资源文件。

3、真机热更资源存放路径
persistentDataPath/res/
                      ├──/update/
                      │       ├──/lua/   
                      │       │    └──lua_update.bundle            #lua增量bundle
                      │       ├──/res/
                      │       │    ├──aaa.bundle                   #预设aaa的bundle
                      │       │    ├──bbb.bundle                   #音频bbb的bundle
                      │       │    └──...                          #其他各种格式的资源bundle
                      │       └──/cfg/
                      │            ├──cfg.bundle                   #配置增量bundle
                      │            └──...                          #其他文本或二进制文件增量bundle
                      ├──out_put.log                               #游戏日志
                      └──...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

关于persistentDataPath,可以参见我这篇博客:https://blog.csdn.net/linxinfa/article/details/51679528

五、自动打包

使用命令行运行unity并执行某个静态函数(运用于命令行打包和批量打包)
https://blog.csdn.net/linxinfa/article/details/70914939

Unity与iOS交互(XUPorter的使用)
https://blog.csdn.net/linxinfa/article/details/87103423

Unity打iOS之xcodeapi的使用
https://blog.csdn.net/linxinfa/article/details/87618408

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/115400
推荐阅读
相关标签
  

闽ICP备14008679号