第9章 资源组件和交互物品开发
前面章节我们已经完成了整个虚拟仿真训练系统中人物系统模块和场景系统模块的开发。但是一个仿真训练系统还需要许多能够交互的组件,形成不同的任务,比如器械功能组件、车辆和飞行器等交通工具。那么,如何完成器械组件和车辆等的开发呢?本章就为大家介绍相关的开发方法,以及一些组件的使用方法。
9.1 资源组件的导入和整理管理
学习功能组件的开发,需要先导入所需的资源组件,并且做好文件管理。具体步骤如下所述。
(1)打开本书附带的系统工程CarProject中的client工程,如图9-1所示。
(2)打开工程以后,需要导入的资源文件包括特效资源、动态资源模型、建筑预制体、器械预制体。在管理方面,把特效资源制作放置在3DEffect文件夹下,并且制作成预制体;把3D动态资源模型和器械预制体归类到3DPrefabs下进行管理,这个文件夹下管理所有动态资源的预制体,如图9-2所示。
(3)本章还需要讲解车辆动力系统的开发,以及相关资源的打包和读取,包括系统组件和物资组件的开发。在工程当中,还需要导入相关的资源插件,以及功能组件代码,如图9-3所示。
部分插件和代码介绍如下所述。
·RealisticCarControllerV2:车辆动力系统第二代功能插件;
·RealisticCarControllerV3:车辆动力系统第三代功能插件;
·Code:包含工程中的主要代码,包括场景、人物、动力组件和系统打包资源管理等;
·Editor:工程中编辑器的代码,包括资源打包工具和其余组件相关的编辑器。此文件夹用来存放工具类代码。
9.2 资源物品的数据导入和整理管理
本章需要导入相应的数据data资源。data资源定义了相关车辆的动力参数、器械,以及环境组件(比如地雷)的类型、任务障碍配置等。系统依据这些资源的配置数据,形成了各项任务Task的物资和障碍模拟,为仿真训练任务提供不同的训练要点和难度分级。具体步骤如下所述。
(1)找到本书附带工程中的Protobuf-net文件夹,这是Protobuf功能库类的代码文件夹。然后把Probobuf-net的母目录中的文件全部导入工程中,如图9-4所示。
(2)导入资源文件以后,等待Unity加载和编译Protobuf库文件目录。完成以后,再找到DataProtoBuf文件夹并且导入Unity工程中的Plugins文件夹,它是资源文件目录中的Probobuf解析协议文件目录。等待Unity解析完成,我们的CarProject工程就完成了数据协议和解析代码的导入,如图9-5所示。
(3)导入解析代码以后,需要把生成的二进制data资源导入资源文件夹中。找到对应的data文件夹,然后放置到工程的外置资源文件夹下面。其中有一个data数据文件夹CarProject/rhino_resource/www.sintolvr.com/data,里面的每一个data文件都代表一段配置文件数据,如图9-6所示。
图9-5 Protobuf资源解析处理协议
图9-6 data数据资源文件
(4)导入完成以后,就可以在工程里面使用DataManager读取相应的数据资源了。那么这些资源分别代表什么意义?本章需要使用的数据资源有哪些呢?下面详细介绍。
·bigfireEffect.data:火焰特效资源,分别列出了不同类型(大、中、小)的火焰集群的组成方案。
·car.data:车辆资源的数据,列出了能使用的不同的车辆数据资源,包括车辆的动力参数。
·carequipment.data:车辆装备所使用的资源数据。
·dynamicItem.data:动态资源所使用的资源数据,包括枪械、地雷、道路、震石等,它是资源的数据集群。
·effectobj.data:特效所使用的资源数据。
·task.data:任务数据,包括任务行驶的起始和结束点、任务动态物品组成方案等。任务数据中关联了其他数据的索引,比如bigfir,
car,dynamicitem等数据的索引。
9.3 AssetBundle的资源组成和功能API
工程中大量使用了prefab预制体资源的.unity3d动态资源,它们通过打包的AssetBundle形成。
那么这类动态资源是如何形成并且产生对应依赖关系的呢?如何使用它们组成系统所要的资源文件呢?本节中将介绍AssetBundle资源,以及如何使用ResourcesManager管理它们的依赖对象。
AssetBundle是Unity定义的一种存储资源文件的格式,它的作用是存储任意Unity定义可以识别的文件格式,包含Scene、Mesh、Material、Texture、Audio和noxss等格式。
AssetBundle还包含开发者自定义的二进制文件,包含数据data资源文件。它的方法是这样的:把自定义文件的扩展名改为.bytes,Unity就会把它识别为TextAsset,接着打包到AssetBundle中。Unity可以识别的所有资源文件称为Assets,AssetBundle属于Assets资源的一个集合。
Unity引擎提供了创建AssetBundle的API,通过编译管线BuildPipeline来创建AssetBundle文件,有以下3种方法:
BuildPipeline.BuildAssetBundle(mainAsset : Object, assets : Object[], pathName : string,options : BuildAssetBundleOptions = BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.CompleteAssets, targetPlatform : BuildTarget = BuildTarget.WebPlayer) : bool
这个API会把资源编辑器中任意选中的Asset资源文件打包成为AssetBundle。
BuildPipeline.BuildStreamedSceneAssetBundle(level : string[], locationPath : string, target : BuildTarget) : String
这个API会把场景或者文件以流的形式加载,然后打包为AssetBundle资源,最后输出到外部进行存储。
BuildPipeline.BuildAssetBundleExplicitAssetNames(assets : Object[], assetNames :string[],pathName : string, options : BuildAssetBundleOptions =BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.CompleteAssets, targetPlatform : BuildTarget = BuildTarget.WebPlayer) : bool
该API功能与上面相同,但创建的时候可以为每个Object指定一个自定义的名字。
9.4 开发自己的AssetBundle打包工具插件
在系统的资源管理上,它需要处理AssetBundle资源的依赖对象和引用标记,这是使用Unity开发中大型系统必不可少的一个技术。本节将介绍开发Unity的AssetBundle打包工具,部分代码示例如下:
//ExportAssetWindow.cs 编辑器工具,资源打包工具 using System; using System.Linq; using UnityEngine; using UnityEditor; using System.Collections.Generic; using System.IO; using System.Text.RegularExpressions; using Object = UnityEngine.Object; using FintolClient; //资源导出编辑器,继承EditorWindow //编辑器的开发模式 //负责把选中的资源打包导出为AssetBundle的.unit3d资源 public class ExportAssetWindow : EditorWindow { //-----------------------此处部分代码省略------------------- //MenuItem的标记,让这个功能函数能够在菜单栏中显示出来 [MenuItem("FintolTools/ExportAssetWindow")] static void Init() { //初始化导出面板 ExportAssetWindow win = (ExportAssetWindow)GetWindow(typeof(ExportAssetWindow)); win.autoRepaintOnSceneChange = true; //显示tanchuk win.ShowPopup(); } //菜单功能,去除音效监听器 [MenuItem("Rhino/RemoveListener")] static void RemoveAudioListener() { //获得选中的所有物体GameObject[] objs = Selection.gameObjects; if (objs != null) { //遍历所有选中的物体,然后删除它的AudioListener组件 foreach (GameObject obj in objs) { AudioListener[] listeners = obj.GetComponentsInChildren (true); foreach (AudioListener al in listeners) GameObject.DestroyImmediate(al); } } } //导出预制体Asset private void ExportPrefabsAsset() { /* string prefabFullPath = Path.GetFullPath(PrefabsFolderPath); Log.logout("Prefab Full Path: " + prefabFullPath); DirectoryInfo folderInfo = new DirectoryInfo(prefabFullPath); List prefabObjectsList = new List(); GatherPrefabs(folderInfo, prefabObjectsList); */ //过滤选中的物体中的Assets Object[] prefabObjectsList = Selection.GetFiltered(typeof(Object), SelectionMode.DeepAssets); //导出这些物体 Export(prefabObjectsList); } static void GatherPrefabs(DirectoryInfo folderInfo, List result) { //Regex rgx = new Regex("^.*\\.prefab$",RegexOptions.IgnoreCase); string dataPath = Application.dataPath; Regex rgx = new Regex("^.*\\.prefab$", RegexOptions.IgnoreCase); foreach (FileInfo fInfo in folderInfo.GetFiles()) { string fileExt = fInfo.Extension; if (rgx.IsMatch(fileExt)) {string assetPath = fInfo.FullName.Substring(dataPath. Length); assetPath = "Assets" + assetPath; Object obj = AssetDatabase.LoadAssetAtPath(assetPath, typeof(GameObject)); if (obj != null) { result.Add(obj); } } } foreach (DirectoryInfo childFolder in folderInfo.GetDirectories()) { GatherPrefabs(childFolder, result); } } //打包工具的UI显示部分 void OnGUI() { //-------------------此处部分代码省略------------------ } /// // //比较资源是否相同 //----------------------此处部分代码省略------------------- //显示所有预制体资源 private void ShowAssets(Object[] selection, ref BundleDependency Statistics bundles, BundleDependencyStatistics.State state) { //-------------------此处部分代码省略------------------- } //导出所有资源 private List GetFormattedExports(Object[] objects) { //判定资源类型 List formattedExports = new List();foreach (Object obj in objects) { string type = null; //预制体资源类型 if (AssetType.IsMainLibraryPrefeb(obj)) type = "Prefeb"; //场景资源类型 if (AssetType.IsLibraryScene(obj)) type = "Scene"; //纹理贴图资源类型 if (AssetType.IsTexture(obj)) type = "Texture"; //光照数据资源类型 if (AssetType.IsLightingData(obj)) type = "LightData"; if (type != null) { formattedExports.Add(String.Format("[{0}]{1}", type, obj.name)); } } formattedExports.Sort(); return formattedExports; } //开始导出所有对象 private void Export(Object[] objs) { exporting = true; //首先选中需要导出的平台(PC、Android、iOS 或者其他) AssetExportor.SwitchPlatform(AssetExportor.AdvicedPlatform); //根据选中的数据导出设置方式 var mode = new AssetExportor.Mode { compressed = exportCompressed, shared = exportShared, seperateSkin = exportSeperateSkin, removeAnimation = removeAnimation, removeMesh = removeMesh, removeTexture = removeTexture, }; /*foreach (Object obj in objs) { if (obj is GameObject) { GameObject go = (GameObject)obj;AudioListener[] listeners = go.GetComponentsInChildren (true); foreach (AudioListener al in listeners) GameObject.DestroyImmediate(al); } }*/ //获取AssetExportor导出工具 var exporter = new AssetExportor(mode); //导出选中的资源 exporter.Export(objs); exporting = false; } }
这是一段编辑器的代码。使用这段代码,并放到Unity中的Editor文件夹下,编辑器会编译这一段代码,然后为编辑器工具栏生成相应的插件。这一段代码中,除了编辑器的使用,AssetExportor类的使用是它输出的核心,是后台打包输出的逻辑。大家也可以自行打开工程进行查看,打包工具编辑器的形式如图9-7所示。