2018-02-26 14:07:20 qq_15559109 阅读数 7806
  • Unity 值得看的500+ 技术内容列表

    Unity3D是由Unity Technologies开发的一个让玩家轻松创建诸如三维视频游戏、建筑可视化、实时三维动画等类型互动内容的多平台的综合型游戏开发工具,是一个全面整合的专业游戏引擎。

在游戏开发中,经常会用到一些配置文件保存一些数据,然后项目运行中读取这些配置文件中的数据在游戏中使用。


如:配置血条:根据角色类型(人物、动物、怪物等)配置不同的血条,包括血条大小,血条名或血条预设,血条颜色等一些简单数据。


如:配置子弹:子弹类型(真子弹、假子弹、追踪子弹等),子弹速度,伤害数值,子弹关联的特效等。


诸如此类的配置很多种,可创建一个可序列化的类存储数据,或者创建 XML 、JSON 文件保存数据,创建 Excel 文件,创建 TXT 文件,皆可完成需求,灵活使用这些方法保存配置数据。


在此介绍一下使用可序列化类保存配置,并且将可序列化类保存成Unity的自定义文件(.asset),然后配置自定义文件(.asset)。


优点:
可以保存数据类型多样(int、string、Vector3、GameObject、Transform、Texture等)如关联预设,关联图片等资源数据,而XML、TXT等只能保存(int、string、Vector3 等基本数据类型)。


缺点:
如果配置数据中保存了(GameObject、Texture)等资源数据,当关联的资源被删除时,配置数据将丢失,需要重新将新的资源再次关联到配置数据上。


下面做个简单的子弹配置数据

方式一:

// 创建一个可序列化的子弹类

Bullet.CSusing UnityEngine;

using System.Collections;

using System;

// 子弹类型枚举

public enum BulletType

{ DirectAttack = 0, // 直接攻击  

Phony, // 假子弹  

Real, // 真子弹 

Track, // 追踪子弹}

/// <summary>/// 可序列化/// </summary>

[Serializable]

public class Bullet : ScriptableObject { // Bullet 类直接继承自 ScriptableObject  

// 子弹类型 public BulletType bulletType = BulletType.DirectAttack;

// 子弹速度
    public int speed = 10;


    // 伤害数值
    public int damage = 5;


    // 子弹关联的特效
    public GameObject effectObj;
}

1
2
using UnityEngine;
using System.Collections;
using UnityEditor;
using System.IO;


public class CreateAsset : Editor {


    // 在菜单栏创建功能项
    [MenuItem("CreateAsset/Asset")]
    static void Create()
    {
        // 实例化类  Bullet
        ScriptableObject bullet = ScriptableObject.CreateInstance<Bullet>();


        // 如果实例化 Bullet 类为空,返回
        if (!bullet)
        {
            Debug.LogWarning("Bullet not found");
            return;
        }
        // 自定义资源保存路径
        string path = Application.dataPath + "/BulletAeeet";
        // 如果项目总不包含该路径,创建一个
        if (!Directory.Exists(path))
        {
            Directory.CreateDirectory(path);
        }


        //将类名 Bullet 转换为字符串
        //拼接保存自定义资源(.asset) 路径
        path = string.Format("Assets/BulletAeeet/{0}.asset", (typeof(Bullet).ToString()));

        // 生成自定义资源到指定路径
        AssetDatabase.CreateAsset(bullet, path);
    }
}

如果想自定义 文件的 Inspector面板,使用编辑器类重写 Bullet.cs 的Inspector面板, 代码如下

using UnityEngine;
using System.Collections;
using UnityEditor;

[CustomEditor(typeof(Bullet))]
public class BulletInspector : Editor {

    // 子弹类型
    public SerializedProperty bulletType;

    // 子弹速度
    public SerializedProperty speed;

    // 伤害数值
    public SerializedProperty damage;

    // 子弹关联的特效
    public SerializedProperty effectObj;

    private void OnEnable()
    {
        bulletType = serializedObject.FindProperty("bulletType");
        speed = serializedObject.FindProperty("speed");
        damage = serializedObject.FindProperty("damage");
        effectObj = serializedObject.FindProperty("effectObj");
    }

    public override void OnInspectorGUI()
    {
        serializedObject.Update();

        EditorGUI.indentLevel = 1;

        EditorGUILayout.PropertyField(bulletType, new GUIContent("子弹类型"));
        GUILayout.Space(5);

        EditorGUILayout.PropertyField(speed, new GUIContent("子弹速度"));

      GUILayout.Space(5);        EditorGUILayout.PropertyField(damage, new GUIContent("伤害数值"));

        GUILayout.Space(5);
        EditorGUILayout.PropertyField(effectObj, new GUIContent("特效对象"));
        GUILayout.Space(10);
        // 打印数据
        if (GUILayout.Button("Debug"))
        {
            Debug.Log("bulletType    :" + (BulletType)bulletType.enumValueIndex);
            Debug.Log("speed         :" + speed.intValue);
            Debug.Log("damage        :" + damage.intValue);


            if (effectObj.objectReferenceValue)
            {
                Debug.Log("effectObj    :" + effectObj.objectReferenceValue);
            }
        }


        if (GUI.changed)
        {
            EditorUtility.SetDirty(target);
        }

serializedObject.ApplyModifiedProperties();
    }

}

方式二:


读取asset文件



2017-03-08 18:34:00 cartzhang 阅读数 17091
  • Unity 值得看的500+ 技术内容列表

    Unity3D是由Unity Technologies开发的一个让玩家轻松创建诸如三维视频游戏、建筑可视化、实时三维动画等类型互动内容的多平台的综合型游戏开发工具,是一个全面整合的专业游戏引擎。

本文章由cartzhang编写,转载请注明出处。 所有权利保留。
文章链接:http://blog.csdn.net/cartzhang/article/details/60878354
作者:cartzhang

一、前言


美术想要一个把unity中*.asset的模型导出来,导成3D Max可以打开的模式,fbx或obj.

需要导出的格式:

这里写图片描述
图1

也就是需要一个工具,个人觉得这个问题,肯定之前Unity的前辈就有解决方法了。于是乎网上一通下载和测试。

二、解包工具集合


网络上找来了各种测试,但是没有一个适合我的,很多都是失败,打不开。
参考宣雨松的博客,找了还是没有结果。

这里写图片描述
图3

解包工具有很多种类,
disunity github地址: https://github.com/ata4/disunity

还有就是AssetAssetsExport,还有Unity Studio.
别人的博客里面都有比较多的介绍和说明,这里就详细说了。

最后还网上wiki里,找到了一个合适的我自己的解包。
http://wiki.unity3d.com/index.php?title=ObjExporter

三、初步成果


找到了一个网站:http://wiki.unity3d.com/index.php?title=ObjExporter

可以导出部分对象。
如下图:

这里写图片描述
图0

而原来unity中模型是这个样子的。

这里写图片描述
图4

导出的只有武器和头盔,没有人物主体body.

四、bug修改

其实也不能算bug,也许人家没有这样的需要呢。

 Component[] meshfilter = selection[i].GetComponentsInChildren<MeshFilter>();
            MeshFilter[] mf = new MeshFilter[meshfilter.Length];
            int m = 0;
            for (; m < meshfilter.Length; m++)
            {
                exportedObjects++;
                mf[m] = (MeshFilter)meshfilter[m];
            }


代码中是要查找所有组件中的MeshFilter,发现SkinnedMeshRender组件居然没有这个MeshFilter这个组件,所以总会导出少一个,而这个居然是人的主体。

这里写图片描述
图5

本来说让美术自己添加一个MeshFilter组件,然后根据mesh render中的mesh自己来添加一个对应的mesh.

既然是程序,那就想办法,思路很明显,既然是有meshrender,就从这入手呗。

代码还是不难度。

// 没有meshFilter,添加一个meshFilter.
            SkinnedMeshRenderer[] meshfilterRender = selection[i].GetComponentsInChildren<SkinnedMeshRenderer>();
            for (int j = 0; j < meshfilterRender.Length; j++)
            {   
                if (meshfilterRender[j].GetComponent<MeshFilter>() == null)
                {
                    meshfilterRender[j].gameObject.AddComponent<MeshFilter>();
                    meshfilterRender[j].GetComponent<MeshFilter>().sharedMesh = Instantiate(meshfilterRender[j].sharedMesh);
                }
            }


这样修改过,就会自动在没有MeshFilter,但是有skinnedMeshRender组件的节点下,添加一个MeshFilter,然后就可以正常导出成.obj文件,与.FBX是类似的,都可以被3D max编辑使用。

这里写图片描述
图7

最后的在VS中看的模型,因为没有安装3Dmax.

这里写图片描述
图6

虽然看起来简陋,但是满足他们小需要,就好了。

贴出主要的代码:

/*
Based on ObjExporter.cs, this "wrapper" lets you export to .OBJ directly from the editor menu.

This should be put in your "Editor"-folder. Use by selecting the objects you want to export, and select
the appropriate menu item from "Custom->Export". Exported models are put in a folder called
"ExportedObj" in the root of your Unity-project. Textures should also be copied and placed in the
same folder.
N.B. there may be a bug so if the custom option doesn't come up refer to this thread http://answers.unity3d.com/questions/317951/how-to-use-editorobjexporter-obj-saving-script-fro.html 

Updated for Unity 5.3

2017-03-07
@cartzhang
fixed can not create obj file in folder.
*/

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

struct ObjMaterial
{
    public string name;
    public string textureName;
}

public class EditorObjExporter : ScriptableObject
{
    private static int vertexOffset = 0;
    private static int normalOffset = 0;
    private static int uvOffset = 0;


    //User should probably be able to change this. It is currently left as an excercise for
    //the reader.
    private static string targetFolder = "ExportedObj";


    private static string MeshToString(MeshFilter mf, Dictionary<string, ObjMaterial> materialList)
    {
        Debug.Assert(null != mf);
        Mesh m = mf.sharedMesh;
        Material[] mats = mf.GetComponent<Renderer>().sharedMaterials;

        StringBuilder sb = new StringBuilder();
        if (null == m)
            return sb.ToString();

        sb.Append("g ").Append(mf.name).Append("\n");
        foreach (Vector3 lv in m.vertices)
        {
            Vector3 wv = mf.transform.TransformPoint(lv);

            //This is sort of ugly - inverting x-component since we're in
            //a different coordinate system than "everyone" is "used to".
            sb.Append(string.Format("v {0} {1} {2}\n", -wv.x, wv.y, wv.z));
        }
        sb.Append("\n");

        foreach (Vector3 lv in m.normals)
        {
            Vector3 wv = mf.transform.TransformDirection(lv);

            sb.Append(string.Format("vn {0} {1} {2}\n", -wv.x, wv.y, wv.z));
        }
        sb.Append("\n");

        foreach (Vector3 v in m.uv)
        {
            sb.Append(string.Format("vt {0} {1}\n", v.x, v.y));
        }

        for (int material = 0; material < m.subMeshCount; material++)
        {
            sb.Append("\n");
            sb.Append("usemtl ").Append(mats[material].name).Append("\n");
            sb.Append("usemap ").Append(mats[material].name).Append("\n");

            //See if this material is already in the materiallist.
            try
            {
                ObjMaterial objMaterial = new ObjMaterial();

                objMaterial.name = mats[material].name;

                if (mats[material].mainTexture)
                    objMaterial.textureName = AssetDatabase.GetAssetPath(mats[material].mainTexture);
                else
                    objMaterial.textureName = null;

                materialList.Add(objMaterial.name, objMaterial);
            }
            catch (ArgumentException)
            {
                //Already in the dictionary
            }


            int[] triangles = m.GetTriangles(material);
            for (int i = 0; i < triangles.Length; i += 3)
            {
                //Because we inverted the x-component, we also needed to alter the triangle winding.
                sb.Append(string.Format("f {1}/{1}/{1} {0}/{0}/{0} {2}/{2}/{2}\n",
                    triangles[i] + 1 + vertexOffset, triangles[i + 1] + 1 + normalOffset, triangles[i + 2] + 1 + uvOffset));
            }
        }

        vertexOffset += m.vertices.Length;
        normalOffset += m.normals.Length;
        uvOffset += m.uv.Length;

        return sb.ToString();
    }

    private static void Clear()
    {
        vertexOffset = 0;
        normalOffset = 0;
        uvOffset = 0;
    }

    private static Dictionary<string, ObjMaterial> PrepareFileWrite()
    {
        Clear();

        return new Dictionary<string, ObjMaterial>();
    }

    private static void MaterialsToFile(Dictionary<string, ObjMaterial> materialList, string folder, string filename)
    {
        using (StreamWriter sw = new StreamWriter(folder + Path.DirectorySeparatorChar + filename + ".mtl"))
        {
            foreach (KeyValuePair<string, ObjMaterial> kvp in materialList)
            {
                sw.Write("\n");
                sw.Write("newmtl {0}\n", kvp.Key);
                sw.Write("Ka  0.6 0.6 0.6\n");
                sw.Write("Kd  0.6 0.6 0.6\n");
                sw.Write("Ks  0.9 0.9 0.9\n");
                sw.Write("d  1.0\n");
                sw.Write("Ns  0.0\n");
                sw.Write("illum 2\n");

                if (kvp.Value.textureName != null)
                {
                    string destinationFile = kvp.Value.textureName;


                    int stripIndex = destinationFile.LastIndexOf(Path.DirectorySeparatorChar);

                    if (stripIndex >= 0)
                        destinationFile = destinationFile.Substring(stripIndex + 1).Trim();


                    string relativeFile = destinationFile;

                    destinationFile = folder + Path.DirectorySeparatorChar + destinationFile;

                    Debug.Log("Copying texture from " + kvp.Value.textureName + " to " + destinationFile);

                    try
                    {
                        //Copy the source file
                        File.Copy(kvp.Value.textureName, destinationFile);
                    }
                    catch
                    {

                    }


                    sw.Write("map_Kd {0}", relativeFile);
                }

                sw.Write("\n\n\n");
            }
        }
    }

    private static void MeshToFile(MeshFilter mf, string folder, string filename)
    {
        Dictionary<string, ObjMaterial> materialList = PrepareFileWrite();

        using (StreamWriter sw = new StreamWriter(folder + Path.DirectorySeparatorChar + filename + ".obj"))
        {
            sw.Write("mtllib ./" + filename + ".mtl\n");

            sw.Write(MeshToString(mf, materialList));
        }

        MaterialsToFile(materialList, folder, filename);
    }

    private static void MeshesToFile(MeshFilter[] mf, string folder, string filename)
    {
        Dictionary<string, ObjMaterial> materialList = PrepareFileWrite();

        using (StreamWriter sw = new StreamWriter(folder + Path.DirectorySeparatorChar + filename + ".obj"))
        {
            sw.Write("mtllib ./" + filename + ".mtl\n");

            for (int i = 0; i < mf.Length; i++)
            {
                sw.Write(MeshToString(mf[i], materialList));
            }
        }

        MaterialsToFile(materialList, folder, filename);
    }

    private static bool CreateTargetFolder()
    {
        try
        {
            System.IO.Directory.CreateDirectory(targetFolder);
        }
        catch
        {
            EditorUtility.DisplayDialog("Error!", "Failed to create target folder!", "");
            return false;
        }

        return true;
    }

    [MenuItem("Custom/Export/Export whole selection to single OBJ")]
    static void ExportWholeSelectionToSingle()
    {
        if (!CreateTargetFolder())
            return;


        Transform[] selection = Selection.GetTransforms(SelectionMode.Editable | SelectionMode.ExcludePrefab);

        if (selection.Length == 0)
        {
            EditorUtility.DisplayDialog("No source object selected!", "Please select one or more target objects", "");
            return;
        }

        int exportedObjects = 0;

        ArrayList mfList = new ArrayList();

        for (int i = 0; i < selection.Length; i++)
        {
            Component[] meshfilter = selection[i].GetComponentsInChildren(typeof(MeshFilter));

            for (int m = 0; m < meshfilter.Length; m++)
            {
                exportedObjects++;
                mfList.Add(meshfilter[m]);
            }
        }

        if (exportedObjects > 0)
        {
            MeshFilter[] mf = new MeshFilter[mfList.Count];

            for (int i = 0; i < mfList.Count; i++)
            {
                mf[i] = (MeshFilter)mfList[i];
            }

            string filename = EditorSceneManager.GetActiveScene().name + "_" + exportedObjects;

            int stripIndex = filename.LastIndexOf(Path.DirectorySeparatorChar);

            if (stripIndex >= 0)
                filename = filename.Substring(stripIndex + 1).Trim();

            MeshesToFile(mf, targetFolder, filename);


            EditorUtility.DisplayDialog("Objects exported", "Exported " + exportedObjects + " objects to " + filename, "");
        }
        else
            EditorUtility.DisplayDialog("Objects not exported", "Make sure at least some of your selected objects have mesh filters!", "");
    }



    [MenuItem("Custom/Export/Export each selected to single OBJ")]
    static void ExportEachSelectionToSingle()
    {
        if (!CreateTargetFolder())
            return;

        Transform[] selection = Selection.GetTransforms(SelectionMode.Editable | SelectionMode.ExcludePrefab);

        if (selection.Length == 0)
        {
            EditorUtility.DisplayDialog("No source object selected!", "Please select one or more target objects", "");
            return;
        }

        int exportedObjects = 0;


        for (int i = 0; i < selection.Length; i++)
        {
            // 没有meshFilter,添加一个meshFilter.
            SkinnedMeshRenderer[] meshfilterRender = selection[i].GetComponentsInChildren<SkinnedMeshRenderer>();
            for (int j = 0; j < meshfilterRender.Length; j++)
            {   
                if (meshfilterRender[j].GetComponent<MeshFilter>() == null)
                {
                    meshfilterRender[j].gameObject.AddComponent<MeshFilter>();
                    meshfilterRender[j].GetComponent<MeshFilter>().sharedMesh = Instantiate(meshfilterRender[j].sharedMesh);
                }
            }

            Component[] meshfilter = selection[i].GetComponentsInChildren<MeshFilter>();
            MeshFilter[] mf = new MeshFilter[meshfilter.Length];
            int m = 0;
            for (; m < meshfilter.Length; m++)
            {
                exportedObjects++;
                mf[m] = (MeshFilter)meshfilter[m];
            }

            MeshesToFile(mf, targetFolder, selection[i].name + "_" + i);
        }

        if (exportedObjects > 0)
        {
            EditorUtility.DisplayDialog("Objects exported", "Exported " + exportedObjects + " objects", "");
        }
        else
            EditorUtility.DisplayDialog("Objects not exported", "Make sure at least some of your selected objects have mesh filters!", "");
    }

}


可以直接复制到直接的项目中使用。

五、怎么使用呢?


首先把代码拷贝到项目中,直接下载工程也行。

步骤一

这里写图片描述
图8

步骤二

这里写图片描述
图10

步骤三

这里写图片描述
图11

就可以使用你的模型编辑工具来查看了。

五、源码和示例工程


源码地址:

https://github.com/cartzhang/unity_lab/blob/master/ExportFbx/UnityAssetExportFBX/Assets/Editor/OBJExport/EditorObjExporter.cs

示例工程地址:
https://github.com/cartzhang/unity_lab/tree/master/ExportFbx/UnityAssetExportFBX

博客图片地址:
https://github.com/cartzhang/unity_lab/tree/master/ExportFbx/Img

Github readme:
https://github.com/cartzhang/unity_lab/blob/master/ExportFbx/Unity%20asset%E6%96%87%E4%BB%B6%20%E5%AF%BC%E5%87%BAOBJ.md

六、参考

【1】http://www.xuanyusong.com/archives/3618

【2】https://forums.inxile-entertainment.com/viewtopic.php?t=13724

【3】http://www.cnblogs.com/Niger123/p/4261763.html

【4】http://prog3.com/sbdm/download/download/akof1314/9097153

【5】http://wiki.unity3d.com/index.php?title=ObjExporter

【6】https://github.com/KellanHiggins/UnityFBXExporter/tree/master/Assets/Packages/UnityFBXExporter

七,最后但不是不重要


Asset导出成FBX的格式:https://github.com/cartzhang/UnityFBXExporter

与上面介绍的不是一个方法,但是思路都一样。这个源码可以把纹理和材质都匹配上去,当然我也做了稍微的修改,修复了之前的小bug。

非常感谢,欢迎留言!!

2015-04-28 11:56:15 u013895270 阅读数 597
  • Unity 值得看的500+ 技术内容列表

    Unity3D是由Unity Technologies开发的一个让玩家轻松创建诸如三维视频游戏、建筑可视化、实时三维动画等类型互动内容的多平台的综合型游戏开发工具,是一个全面整合的专业游戏引擎。

转自云风的Blog: http://blog.codingnow.com/2014/08/unity3d_asset_bundle.html

Unity3D asset bundle 格式简析

Unity3D 的 asset bundle 的格式并没有公开。但为了做更好的差异更新,我们还是希望了解其打包格式。这样可以制作专门的差异比较合并工具,会比直接做二进制差异比较效果好的多。因为可以把 asset bundle 内的数据拆分为独立单元,只对变更的单元做差异比较即可。

网上能查到的资料并不是官方给出的,最为流行的是一个叫做 disunity 的开源工具。它是用 java 编写的,只有源代码,而没有给出格式说明(而后者比代码重要的多)。通过阅读 disunity 的代码,我整理出如下记录:

asset bundle 分为压缩模式和非压缩模式。压缩模式仅仅是用开源的 lzma 库 对整个非压缩包做了一次整体压缩。压缩数据的头有 13 个字节,前 5 个字节是 lzma 解压缩的 API 需要穿入的 props ,接下来的 4 字节是解压缩后的数据库长度。最后 4 字节不用理会它。

把压缩数据解开后,就和非压缩模式没有差别,下面只讨论非压缩格式:

assert bundle 的文件头是从这样一个数据结构序列化出来的。

struct AssetBundleFileHead {
struct LevelInfo {
unsigned int PackSize;
unsigned int UncompressedSize;
};

 string          FileID;
 unsigned int     Version;
 string          MainVersion;
 string          BuildVersion;
 size_t          MinimumStreamedBytes;
 size_t          HeaderSize;
 size_t          NumberOfLevelsToDownloadBeforeStreaming;
 size_t          LevelCount;
 LevelInfo     LevelList[];
 size_t          CompleteFileSize;
 size_t          FileInfoHeaderSize;
 bool          Compressed;

};
string 是直接以 \0 结尾的字符串,顺序序列化;size_t 是大端的 4 字节数字;bool 是单个字节;vector 就是顺着排列的结构。

根据 Unity 版本的不同,assert bundle 的格式也不完全相同。Version 指明了 bundle 的格式版本,从 Unity 3.5 开始到 4.x 版都使用 Version = 3 ,下面只讨论这个版本。HeaderSize 应该恰好等于以上这个文件头的数据长度。

一个 assert bundle 是由多个 asset 文件打包而成,接下来顺序打包了这些 asset 。序列化成这样的结构:

struct AssetFileHeader {
struct AssetFileInfo {
string name;
size_t offset;
size_t length;
};
size_t FileCount;
AssetFileInfo File[];
};
这样我们就可以分解出被打包在一起的多个 Asset 了(大多数情况下只有一个)。offset 表示的是除去 HeaderSize 后的偏移量。我们可以用 HeaderSize 加上那个部分的 offset 得到这个部分相对于整个 bundle 的文件偏移。

对于每个 asset ,又有它自己的数据头。数据头除了基本的数据头结构 AssetHeader 外,还有额外的三个部分。disunity 把它们称为 TypeTree ObjectPath 和 AssetRef 。注意:这里 Format 随不同 Unity3D 的版本有所不同,我们只关心目前的版本的格式,这里 Format 为 9 (其它版本的格式,在大小端等问题上有所不同)。

struct AssetHeader {
size_t TypeTreeSize;
size_t FileSize;
unsigned int Format;
size_t dataOffset;
size_t Unknown;
Unity 对 Asset 数据做了简单粗暴的序列化操作。整个序列化过程是针对每种对象的数据结构进行的。TypeTree 是对数据结构本身的描述,通过这个描述,就可以反序列化出每个对象。

AssetHeader 后面紧跟着的就是 TypeTree 。但是,这个 TypeTree 对于 asset bundle 来说是可选的,因为数据结构的信息可以事先放置在引擎中(引擎多半只支持固有的数据类型)。在发布到移动设备上时,TypeTree 是不打包到 asset bundle 中的。

每个 asset 对象,都有一个 class id ,可以在 TypeTree 中查到如何反序列化。class id 的和具体类型的对应关系,在 Unity3d 的官方文档 可以查到。但若我们只是想将差异比较在对象一级进行(而不是具体比较对象中具体的属性),那么就不需要解开具体对象的细节信息,这部分也不用关心。所以这里也不展开(有兴趣可以读一下 disunity 的代码,格式并不复杂)。

在 AssetHeader 中的 TypeTreeSize 指的就是 TypeTree 部分的大小。接下来是每个 AssetObject 的描述数据。

struct ObjectHeader {
struct ObjectInfo {
int pathID;
int offset;
int length;
byte classID[8];
};
int ObjectCount;
ObjectInfo Object[];
};
这里,所有的 int 都是以小端编码的 4 字节整数(不同于外部文件格式采用的大端编码)。在 Unity3D 中,每个对象都有唯一的字符串 path ,但是在 asset bundle 里并没有直接保存字符串,而是一个 hash 过的整数,也可以看成是对这个对象的索引号。真正的对象放在数据头的后面,偏移量为 offset 的地方。

这里的 offset 是相对当前 asset 块的。如果想取得正确的相对整个文件的位置,应该是文件的 HeaderSize + asset 的 offset + asset 的 dataOffset + 这里的 object offset 。

接在 ObjectHeader 后的是 AssetRef 表,记录了 Asset 的引用关系。用于指明这个 bundle 内 asset 对外部 asset 的引用情况。AssetRefTable 结构如下:

struct AssetTable {
struct AssetRef {
byte GUID[8];
int type;
string filePath;
string assetPath;
};
int Count;
byte Unknown;
vector Refs;

2016-12-14 23:05:16 u010832643 阅读数 2241
  • Unity 值得看的500+ 技术内容列表

    Unity3D是由Unity Technologies开发的一个让玩家轻松创建诸如三维视频游戏、建筑可视化、实时三维动画等类型互动内容的多平台的综合型游戏开发工具,是一个全面整合的专业游戏引擎。

引言:
在unity3d的实际项目开发中,由于资源的增删改频繁,手动设置asset bundle name是一项繁琐且容易出错的工作,所以衍生出了开发自动化工具的需求。
工作原理:
以文件夹为目标,设置asset bundle name,在目标文件夹内的文件都会打进同一个asset bundle。
实现原理:
通过遍历Asset路径下的所有文件夹,收集所有的子文件夹列表,然后顺序设置asset bundle name。命名规格是将路径中的"\"替换为"_"。其中对文件夹的操作用到System.IO下面的接口。
另外添加了黑名单配置列表,用于过滤无需设置asset bundle name的文件夹。(例如导入的插件等)
特别的,为了避免资源冗余,仅对最低层的文件夹设置asset bundle name。
源代码:
using UnityEngine;
using System.Collections;
using UnityEditor;
using System.Collections.Generic;
using System.IO;

public class AutoGenerateAssetBundleName
{
    /// <summary>
    /// 黑名单
    /// </summary>
    static public string[] skip_dirs_all = { "testBlackList" };

    [MenuItem("Asset Bundle/Auto generate asset bundle name", false, 1)]
    public static void GenerateAssetBundleName()
    {
        string dataPath = Application.dataPath;

        string root_path = "";
        string sub_dir = "";
        int position = dataPath.LastIndexOf(@"/");
        if (position != -1)
        {
            root_path = dataPath.Substring(0, position + 1);
            sub_dir = dataPath.Substring(position + 1);
        }

        ClearAssetBundleName(root_path, sub_dir);
        Debug.Log("Begin GenerateAssetBundleName...");

        List<string> OutPathList = new List<string>();
        List<string> IncludeOutPathList = new List<string>();
        OutPathList = GetFilterDirectories(@root_path, @sub_dir);
        
        foreach(string AssetBundleName in OutPathList)
        {
            AssetImporter importer = AssetImporter.GetAtPath(AssetBundleName);
            if (importer != null)
            {
                importer.assetBundleName = AssetBundleName.Replace('/', '_');
                importer.assetBundleVariant = "";
            }
        }
        AssetDatabase.Refresh();

        Debug.Log("End GenerateAssetBundleName... Total asset bundle count: " + OutPathList.Count);
    }

    static List<string> GetFilterDirectories(string root_path, string sub_dir, bool bNeedSkip = true)
    {
        //需要过滤的文件夹
        List<string> skip_dirs = new List<string>(skip_dirs_all);
        string sub_folder_name = sub_dir;
        int position = sub_dir.LastIndexOf(@"/");
        if (position != -1)
            sub_folder_name = sub_dir.Substring(position + 1);

        if (bNeedSkip)
        {
            bool bSkipit = false;
            foreach(string skip_dir in skip_dirs)
            {
                if (string.Compare(skip_dir, sub_folder_name, true) == 0)
                {
                    bSkipit = true;
                    break;
                }
            }
            if (bSkipit)
            {
                return new List<string>();
            }
        }

        string[] dirs = Directory.GetDirectories(root_path + sub_dir, "*", SearchOption.TopDirectoryOnly);
        // 提取文件夹名
        string[] folder_names = new string[dirs.Length];
        for (int i = 0; i < dirs.Length; ++i)
        {
            int pos = dirs[i].LastIndexOf(@"\");
            if (pos != -1)
                folder_names[i] = dirs[i].Substring(pos + 1);
        }

        List<string> filter_dirs = new List<string>();
        if (dirs.Length == 0)
        {
            //没有子目录
            //如果sub_folder_name不在skip_dirs内,则设置assetbundlename
            bool bFindit = false;
            foreach(string skip_dir in skip_dirs)
            {
                if (string.Compare(skip_dir, sub_folder_name, true) == 0)
                {
                    bFindit = true;
                    break;
                }
            }
            if (!bFindit)
            {
                Debug.Log("collect valid asset bundle name: " + sub_dir);
                filter_dirs.Add(sub_dir);
            }
            return filter_dirs;

        }
        foreach (string folder_name in folder_names)
        {
            List<string> sub_filter_dirs = GetFilterDirectories(root_path, sub_dir + "/" + folder_name);
            filter_dirs.AddRange(sub_filter_dirs);
        }

        return filter_dirs;
    }

    static void GetAllDirectories(string root_path, string sub_dir, ref List<string> outPathList)
    {
        string[] dirs = Directory.GetDirectories(root_path + sub_dir, "*", SearchOption.TopDirectoryOnly);
        outPathList.Add(sub_dir);
        if (dirs.Length > 0)
        {
            // 提取文件夹名
            string[] folder_names = new string[dirs.Length];
            for(int i = 0; i < dirs.Length; ++i)
            {
                int pos = dirs[i].LastIndexOf(@"\");
                if (pos != -1)
                    folder_names[i] = dirs[i].Substring(pos + 1);
            }

            foreach(string folder_name in folder_names)
            {
                GetAllDirectories(root_path, sub_dir + "/" + folder_name, ref outPathList);
            }
        }
    }

    static void ClearAssetBundleName(string root_path, string sub_dir)
    {
        List<string> OutPathList = new List<string>();
        GetAllDirectories(@root_path, @sub_dir, ref OutPathList);
        foreach(string folder in OutPathList)
        {
            AssetImporter importer = AssetImporter.GetAtPath(folder);
            if (importer != null)
            {
                importer.assetBundleName = "";
            }
        }
        AssetDatabase.RemoveUnusedAssetBundleNames();
        AssetDatabase.Refresh();
    }
}
运行结果:
运行的log输出

文件夹的asset bundle name:

2018-05-06 22:19:40 qq_16763249 阅读数 6445
  • Unity 值得看的500+ 技术内容列表

    Unity3D是由Unity Technologies开发的一个让玩家轻松创建诸如三维视频游戏、建筑可视化、实时三维动画等类型互动内容的多平台的综合型游戏开发工具,是一个全面整合的专业游戏引擎。

简介

在游戏开发中,我们经常要对一些数据预先配置,比如不同角色的属性,武器的各项数值等等,一般可通过XML配置. 此外还可以用Unity自带的.Asset资源配置文件配置数据,下面介绍一下如何灵活快速的创建.Asset文件并使用.

转载请注明出处

相比XML配置文件, Asset文件更加直观而且支持直接配置如 GameObject、Sprite、AudioClip等更加高级的对象,基本上除了接口、事件 都可以通过Asset文件配置 , 所以说灵活性上比XML要更强一点.

插件演示

  • 首先创建一个继承于ScriptableObject 类的对象
[System.Serializable]
public class DemoConfig : ScriptableObject {

    public string userName;

    public int userID;

    public GameObject userObject;

    public Sprite sprite;

    public AudioClip audioClip;

    public void print() {
        Debug.Log(string.Format("name: {0} id: {1}" , userName , userID));
    }
}
  • 使用UConfig创建对应的Asset文件

创建Asset配置文件

创建成功

  • 配置各项属性
    这里写图片描述

创建Asset文件

创建Asset文件的核心代码只有一句, 那就是通过ScriptableObject.CreateInstance()方法, 这个方法允许传入一个类名并创建对应的资源配置文件,以下代码如下.

static void CreateAsset()
        {
            //class_Name表示类名 , config_Name表示要创建的配置文件名称 , file_floder表示创建路径
            if (class_Name.Length == 0 || config_Name.Length == 0 || file_floder.Length == 0)
            {
                Debug.LogError(string.Format("[UConfig]: 创建失败 , 信息不完整!"));
                return;
            }
            ScriptableObject config = ScriptableObject.CreateInstance(class_Name);

            if (config == null)
            {
                Debug.LogError(string.Format("[UConfig]: 创建失败 , 类名无法识别! --> {0}", class_Name));
                return;
            }
            // 自定义资源保存路径
            string path = file_floder;
            //如果项目总不包含该路径,创建一个
            if (!Directory.Exists(path))
            {
                Directory.CreateDirectory(path);
            }
            config_Name = config_Name.Replace(".asset", "");
            path = string.Format("{0}/{1}.asset", file_floder, config_Name);
            string defFilePath = "Assets/" + config_Name + ".asset";
            // 生成自定义资源到指定路径
            AssetDatabase.CreateAsset(config, defFilePath);
            File.Move(Application.dataPath + string.Format("/{0}.asset" , config_Name), path);
            AssetDatabase.Refresh();
            Debug.Log(string.Format("<color=yellow>[UConfig]: 创建成功 ! --> {0}</color>", path));
            configWindow.Close();
        }

读取配置好的Asset文件

通过以上方式创建的配置文件可以作为一个与Prefab资源对待, 并且可以随其他prefab资源一样被打成AssetBundle包动态加载 , 理所当然的可以实现所谓的热更配置表.

加载的代码就用 Resources.Load(“fileName”); 就可以了,也可以预先挂着到某一个代码的属性中,然后直接通过as强转.

其他

有需要的伙伴可以访问我的码云下载源码,有更详细的代码,供大家参考


点击以下图标前往源码 :

Fork me on Gitee

(顺便Stars一下吧 ^_^)