unity3d制作文件管理软件

2014-03-16 16:06:06 huang9012 阅读数 0
  • Unity 值得看的500+ 技术内容列表

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

数字模型制作规范

本文提到的所有数字模型制作,全部是用3D MAX建立模型,即使是不同的驱动引擎,对模型的要求基本是相同的。当一个VR模型制作完成时,它所包含的基本内容包括场景尺寸、单位,模型归类塌陷、命名、节点编辑,纹理、坐标、纹理尺寸、纹理格式、材质球等必须是符合制作规范的。一个归类清晰、面数节省、制作规范的模型文件对于程序控制管理是十分必要的。

首先对制作流程作简单介绍:

素材采集-模型制作-贴图制作-场景塌陷、命名、展UV坐标-灯光渲染测试-场景烘培-场景调整导出

 

第一章  模型制作规范

1   在模型分工之前,必须确定模型定位标准。一般这个标准会是一个CAD底图。制作人员必须依照这个带有CAD底图的文件确定自己分工区域的模型位置,并且不得对这个标准文件进行任何修改。

导入到MAX里的CAD底图最好在(0,0,0)位置,以便制作人员的初始模型在零点附近。
   2   在没有特殊要求的情况下,单位为米(Meters),如所示。

3    删除场景中多余的面,在建立模型时,看不见的地方不用建模,对于看不见的面也可以删除,主要是为了提高贴图的利用率,降低整个场景的面数,以提高交互场景的运行速度。如Box底面、贴着墙壁物体的背面等。

4    保持模型面与面之间的距离推荐最小间距为当前场景最大尺度的二千分之一。例如:在制作室内场景时,物体的面与面之间距离不要小于2mm;在制作场景长(或宽)为1km的室外场景时,物体的面与面之间距离不要小于20cm。如果物体的面与面之间贴得太近,会出现两个面交替出现的闪烁现象。模型与模型之间不允许出现共面、漏面和反面,看不见的面要删掉。在建模初期一定要注意检查共面、漏面和反面的情况;

5   可以复制的物体尽量复制。如果一个1000个面的物体,烘焙好之后复制出去100个,那么他所消耗的资源,基本上和一个物体所消耗的资源一样多。

6    建模时最好采用Editable Poly面片建模,这种建模方式在最后烘焙时不会出现三角面现象,如果采用EditableMesh 在最终烘焙时可能会出现三角面的情况。如图所示。

 

 

模型的塌陷

当一栋建筑模型经过建模、贴纹理之后,然后就是将模型塌陷,这一步工作也是为了下一步烘焙做准备。所以在塌陷的时候要注意一些问题:

    (1)    按照“一建筑一物体”的原则塌陷,体量特别大或连体建筑可分塌为2-3个物体,但导出前要按建筑再塌成一个物体,城中村要按照院落塌陷。

   (2)    用Box反塌物体,转成Poly模式,这时需检查贴图有无错乱;

   (3)    塌陷物体,按楼或者地块来塌陷,不要跨区域塌陷;

    (4)    按项目对名称的要求进行严格的标准的命名;

    (5)    所有物体的质心要归于中心,检查物体位置无误后锁定物体;

备注:所有物体不准出现超过20000三角面的情况,否则导出时出错。

8  模型命名  不能使用中文命名,必须使用英文命名,不然在英文系统里会出问题。地块建筑模型不允许出现重名,必须按规范命名。

 

树的种植方法

用十字交叉树或简模树。在种植树木的时候,要考虑到与周围建筑的关系,不能乱种树,要根据现状放置不同的树种、位置;重点建筑地块需种简模树,并在原地与之对应的种上十字片树(替换用);导出时模型树和与之对应的十字片树为单棵的,其它十字片树可塌一起,但面数不能超过1万。

10  模型的级别

也就是模型的精细程度,有时我们在建模的时候要根据建筑所处的具体位置,重要程度对该建筑进行判断是建成何种精度的仿真模型。可以将建筑分为五个等级。

其中,一级为最高等级,五级为最低等级。

单个物体的面数不要太大,毕竟是做虚拟现实,而不是制作单张效果图。单个物体面数要控制到8000个面以下。

11   镜像的物体需要修正

   用镜像复制的方法来创建新模型,需要加修改编辑器修正一下。

   第一步:需要选中镜像后的物体,然后进入Utilities面板中单击Reset XForm,然后单击Reset Selected;

   第二步:进入modfiy面板选取Normal命令,反转一下法线即可。

12   烘焙的物体黑缝解决对办法

   在烘焙的时候,如果图片不够大的时候,往往会在边缘产生黑缝。

处理小技巧 :  

   1)如果做鸟瞰楼体比较复杂可以把楼体合并成一个物体变成多重材质,然后对楼体进行整体完全烘焙;这样可以节省很多资源。2)对于建筑及地形,须检查模型的贴图材料平铺的比例,对于较远的地表(或者草地),可以考虑用一张有真实感的图来平铺,平铺次数少一些。对于远端的地面材料,如果平铺次数大了,真实感比较差。

 

 

第二章       材质贴图规范

材质和贴图类型

我们目前使用的是Unity3D软件作为仿真开发平台,该软件对模型的材质有一些特殊的要求,在我们使用的3dsMax中不是所有材质都被Unity3D软件所支持,只有下面几种材质是被Unity3D软件所支持。

 

Standard(标准材质)

默认的通用材质球。基本上目前所有的仿真系统都支持这种材质类型。

Multi/Sub-Object(多维/子物体材质)

将多个材质组合为一种复合式材质,分别指定给一个物体的不同次物体选择级别。要注意的是,在VR场景制作中,Multi/Sub-Object材质中的子材质一定要是Standard标准材质。否则不被unity3d支持。我们在制作完模型进行烘焙贴图前都必须将所有物体塌陷在一起,塌陷后的新物体就会自动产生一个新的Multi/Sub-Object多维/子物体材质。因此,这种材质类型在我们的仿真制作中经常使用。

2  贴图通道及贴图类型

Unity3D目前只支持Bitmap贴图类型,其它所有贴图类型均不支持。只支持Diffuse Color(漫反射)同self-Illumination(自发光,用来导出lightmap)贴图通道。

Self-Illumination(不透明)贴图通道在烘焙lightmap后,需要将此贴图通道额channel设置为烘焙后的新channel,同时将生成的lightmap指向到self-Illumination

 

 3贴图的文件格式和尺寸

建筑的原始贴图不带通道的为JPG,带通道的为32位TGA,但最大别超过2048;贴图文件尺寸必须是2的N次方(8、16、32、64、128、256、512),最大贴图尺寸不能超过(1024×1024)。

在烘培时将纹理贴图存为TGA 格式。

 

4贴图和材质应用规则

   (1)    贴图不能以中文命名,不能有重名;

   (2)    材质球命名与物体名称一致;

   (3)    材质球的父子层级的命名必须一致;

   (4)    同种贴图必须使一个材质球;

   (5)    除需要用双面材质表现的物体之外,其他物体不能使用双面材质;

   (6)    材质球的ID号和物体的ID号必须一致。

   (7)    若使用CompleteMap烘焙,烘焙完毕后会自动产生一个Shell材质,必须将Shell材质变为Standard标准材质,并且通道要一致,否则不能正确导出贴图。

   (8)    带Alpha通道的贴图,在命名时必须加_al以区分。

5通道纹理应用规则

模型需要通过通道处理时需要制作带有通道的纹理。在制作树的通道纹理时,最好将透明部分改成树的主色,这样在渲染时可以使有效边缘部分的颜色正确。通道纹理在程序渲染时占用的资源比同尺寸普通纹理要多。通道纹理命名时应以-AL结尾。 

 

 

第三章       模型烘焙及导出

模型烘焙

1场景灯光

   (1)    渲染方式:采用Max自带的LightTracer光线追踪进行渲染。

   (2)    灯光效果控制:

该项目在烘焙前会给出固定的烘焙灯光,灯光的高度、角度、参数均不可调整,可以在顶视图中将灯光组平移到自己的区块,必须要用灯光合并场景然后烘焙

 

2  烘焙贴图方式

建筑模型的烘焙方式有两种:一种是LightMap烘焙贴方式,这种烘焙贴图渲染出来的贴图只带有阴影信息,不包含基本纹理。具体应用于制作纹理较清晰的模型文件(如地形),原理是将模型的基本纹理贴图和LightMap阴影贴图两者进行叠加。优点是最终模型纹理比较楚,而且可以使用重复纹理贴图,节约纹理资源;烘焙后的模型可以直接导出FBX文件,不用修改贴图通道。缺点是LightingMap贴图不带有高光信息;

另一种是CompleteMap烘焙方式,这种烘焙贴图方式的优点是渲染出来的贴图本身就带有基本纹理和光影信息,但缺点是没有细节纹理,且在近处时纹理比较模糊。

3   烘焙贴图设置

①     CompleteMap烘焙方式

在进行CompleteMap烘焙设置时,应注意以下几点:

a)      贴图通道和物体UV坐标通道必须为1通道,见图所示;

b)     烘焙贴图文件存储格式为TGA格式;

c)      烘焙设置见图所示。

②     LightingMap烘焙方式

在进行LightingMap烘焙设置时,和CompleteMap设置有些地方不同:

a)      贴图通道和物体UV坐标通道必须为3通道,见图所示

b)     烘焙时灯光的阴方式为Adv.RayTraced阴影,见图所示;

c)      烘焙设置见图所示;

d)     用LightingMap烘焙时,背景色要改为白色,可避免有黑边的情况;而用CompleteMap烘培时,背景色要改为与贴图近似的颜色。

e)     在使用lightmap烘焙后,需要将材质改回Standard,然后将新生成的map拷贝到Standardself-Illumination内,并设置正确的贴图通道。

4  贴图UV编辑

必须手动进行UV编辑。

模型导出

1、将烘培材质球改为标准材质球,通道为1,自发光100;

2、将所有物体名、材质球名、贴图名保持一致

3、合并顶点(大小要合适);

4、清除场景,除了主要的有用的物体外,删除一切物件;

5、清材质球,删除多余的材质球(不重要的贴图要缩小);

6、按要求导出fbx(检查看是否要打组导出);

视具体情况导出动作

 

 

第四章  场景模型验收

模型制作流程

模型验收流程

模型验收标准

第五章    模型备份提交标准

文件标准备份模式:

l   UV坐标:存放地型和建筑烘培前编辑的UV坐标;

l   导出fbx:存放最终导出的地型和建筑的fbx文件;

l   烘培贴图:存放地型的最终贴图和建筑的最终烘培贴图,tga格式的,     同时这里面有一份转好的贴图;

l  原始贴图:存放地型和建筑在制作过程中的所有的贴图;

l  Max文件:原始模型,未做任何塌陷的,有UVW贴图坐标的文件。

烘焙前模型,已经塌陷完的,展好UV的,调试好灯光渲染测试过的文件。

烘焙后模型,已经烘培完的,未做任何处理的文件。

导出模型,处理完烘培物体,合并完顶点,删除了一切没用物件的文件。

第六章           项目命名要求

1、建筑模型命名:区域名_jz_编号;如:SH01_01_jz_01、SH01_01_jz_02……

2、建筑贴图命名:模型名_编号;如:SH01_01_jz_01_01……

3、地形模型命名:区域名_dx_编号;如:SH01_01_dx_01……

4、地形贴图命名:模型名_编号;如:SH01_01_dx_01_01……

5、镂空贴图:要加“_al”后缀

6、需要加特效的玻璃要加“_bl”后缀;(需要加特效的要单独一个物体烘焙)

第七章    创建各资料路径

1、航片存放路径

2、各区域CAD导出的MAX文件

3、照片资料

4、公共纹理库资料

5、客户提供的所有资料

第七章           特殊制作要求

1、           围墙、护栏、雨棚、车棚等要按实际制作。

2、           照片与图纸不符时,以实际照片为主。

3、           沿街店铺尽量与实际一致。

4、           预估尺寸时尽量去借助照片中的参照物,如人、汽车、树木等。

5、           除重点建筑外尽量简化模型,贴图做精,能用贴图表现结构的尽量用贴图表现。

6、           绿化的种类、颜色尽量去实际一致。

7、           建筑顶部的结构及顶部的附属设施尽量按实际制作。

8、           建筑贴图时顶部要留出女儿墙的高度,避免出现窗户顶到建筑顶部。

9、           建筑要制作入口。

10、       地形、绿化要丰富。

11、       书报亭、公交站、变电箱、烟囱、信号塔等要简单制作,以丰富场景。

12、       所有贴图UV大小要准确,避免失真。

2016-06-27 10:46:42 u014678046 阅读数 1866
  • Unity 值得看的500+ 技术内容列表

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

下面共享一个我自己的资源管理器,很简单的,看起来也明白,主要是我是新手,不想写的太复杂,自己项目使用的好用,便是最好的。

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;

public class GameObjectMgr
{

    public static Dictionary<string, GameObject> loadedPerfabList = new Dictionary<string,GameObject>();        //存放已经加载的预设资源
    public static Dictionary<string, AssetBundle> loadedAssetList = new Dictionary<string,AssetBundle>();       //存放已经加载的AB包
    public static Dictionary<string, GameObject> createdObjList = new Dictionary<string,GameObject>();      //存放已经创建的对象
    public static Dictionary<string, Dictionary<string, System.Object>> littlePool = new Dictionary<string, Dictionary<string, object>>();
    public static string[] destoryPerfabList = null;        //需要销毁的已经加载的预设
    public static string[] destoryAssetList = null;     //需要销毁的AB包,unload(false);
    public static string[] destoryObjNameList = null;       //需要销毁的对象名字列表;


    public static void AddObjectToDictionary(string objName, System.Type _type, System.Object _obj)
    {
        //Debug.Log("添加=="+objName);
        if (littlePool.ContainsKey(_type.ToString()))
        {
            littlePool[_type.ToString()][objName] = _obj;
        }
        else {
            Dictionary<string, System.Object> adder = new Dictionary<string, System.Object>();
            adder[objName] = _obj;
            littlePool[_type.ToString()] = adder;
        }    
    }

    public static System.Object GetObjFromDictionary(string _obgName,System.Type _type) {
        if (littlePool.ContainsKey(_type.ToString()))
        {
            if (littlePool[_type.ToString()].ContainsKey(_obgName))
            {
                if (littlePool[_type.ToString()][_obgName] != null)
                {
                    return littlePool[_type.ToString()][_obgName];
                }
                else {
                    littlePool[_type.ToString()].Remove(_obgName);
                    return null;
                }
            }
            return null;
        }
        else {
            return null;
        }

    }

    //设置是否显示
    public static void SetObjActivePority(string _name, bool _active)
    {
        if (createdObjList == null)
        {
            //Debug.Log("出错拉,那窗口没有: "+_name);
        }
        else
        {
            foreach (string key in createdObjList.Keys)
            {
                if (key == _name)
                {
                    if (createdObjList[key] != null)
                    {
                        createdObjList[key].SetActive(_active);
                    }
                    else
                    {
                        //  Debug.Log("出错拉,窗口丢失: "+_name);
                        createdObjList.Remove(_name);
                    }
                }
            }
        }

    }
    //获得一个已经创建的对象
    public static GameObject GetGameObjectByName(string _name)
    {
        if (createdObjList == null)
        {
            //Debug.Log("出错拉,那窗口没有: "+_name);
            return null;
        }
        else
        {
            foreach (string key in createdObjList.Keys)
            {
                if (key == _name)
                {
                    if (createdObjList[key] != null)
                    {
                        return createdObjList[key];
                    }
                    else
                    {
                        //Debug.Log("出错拉,窗口丢失: "+_name);
                        createdObjList.Remove(_name);
                        return null;
                    }
                }
            }
        }
        return null;
    }
    //获得一个已经加载的预设
    public static GameObject GetPrefabByName(string _name)
    {
        return (GameObject)GetObjFromDictionary(_name, typeof(GameObject));
    }

    //查看预设是否加载
    public static bool IsPerfabLoaded(string _name)
    {
        if (loadedPerfabList == null)
        {
            return false;
        }
        else
        {
            foreach (string key in loadedPerfabList.Keys)
            {
                if (key == _name)
                {
                    if (loadedPerfabList[key] != null)
                        return true;
                    else
                    {
                        loadedPerfabList.Remove(_name);
                        return false;
                    }
                }
            }
        }
        return false;
    }
    //对象是否已经被创建
    public static bool IsObjCreated(string _name)
    {
        if (createdObjList == null)
        {
            return false;
        }
        else
        {
            foreach (string key in createdObjList.Keys)
            {
                if (key == _name)
                {
                    if (createdObjList[key] != null)
                    {
                        return true;
                    }
                    else
                    {
                        createdObjList.Remove(_name);
                        return false;
                    }
                }
            }
        }
        return false;
    }

    //添加已经创建的GameObject键值对
    public static void AddCreatedObjList(string _name, GameObject _obj)
    {
        //Debug.Log ("AddCreatedObjList>>>>>>>>>>>>>>>>>>>>"+_name);
        if (createdObjList.ContainsKey(_name))
        {
            //Debug.Log(string.Format("名字是 {0} 的对象已经添加,不好",_name));
            return;
        }
        createdObjList.Add(_name, _obj);
    }
    //添加已经加载的assetbundle键值对
    public static void AddloadedAssetList(string _name, AssetBundle _obj)
    {
        //Debug.Log ("AddloadedAssetList>>>>>>>>>>>>>>>>>>>>"+_name);
        if (loadedAssetList.ContainsKey(_name))
        {
            //  Debug.Log(string.Format("名字是 {0} 的assetbundle已经添加,不好",_name));
            return;
        }
        loadedAssetList.Add(_name, _obj);
    }

    //添加已经加载的预设键值对
    public static void AddloadedPerfabList(string _name, GameObject _obj)
    {
        //Debug.Log ("AddloadedPerfabList>>>>>>>>>>>>>>>>>>>>"+_name);
        if (loadedPerfabList.ContainsKey(_name))
        {
            //Debug.Log(string.Format("名字是 {0} 的预设已经添加,不用再次添加",_name));
            return;
        }
        loadedPerfabList.Add(_name, _obj);
    }

    //添加需要清理的预设名字
    public static void AdddestoryPerfabList(string _name)
    {
        //Debug.Log ("AdddestoryPerfabList>>>>>>>>>>>>>>>>>>>>"+_name);
        string[] list = null;
        int i = 0;
        if (destoryPerfabList != null)
        {
            list = new string[1 + destoryPerfabList.Length];
            foreach (string str in destoryPerfabList)
            {
                list[i] = destoryPerfabList[i];
                i++;
            }
        }
        else
        {
            i = 0;
            list = new string[1];
        }
        list[i] = _name;
        destoryPerfabList = null;
        destoryPerfabList = new string[i + 1];
        i = 0;
        foreach (string str in destoryPerfabList)
        {
            destoryPerfabList[i] = list[i];
            i++;
        }
        list = null;
        return;
    }

    //添加待销毁对象名字
    public static void AdddestoryObjNameList(string _name)
    {
        //Debug.Log ("AdddestoryObjNameList>>>>>>>>>>>>>>>>>>>>"+_name);
        string[] list = null;
        int i = 0;
        if (destoryObjNameList != null)
        {
            list = new string[1 + destoryObjNameList.Length];
            foreach (string str in destoryObjNameList)
            {
                list[i] = destoryObjNameList[i];
                i++;
            }
        }
        else
        {
            i = 0;
            list = new string[1];
        }
        list[i] = _name;
        destoryObjNameList = null;
        destoryObjNameList = new string[i + 1];
        i = 0;
        foreach (string str in destoryObjNameList)
        {
            destoryObjNameList[i] = list[i];
            i++;
        }
        list = null;
        return;
    }
    //添加待清理assetbundle名字
    public static void AdddestoryABList(string _name)
    {
        //Debug.Log ("AdddestoryABList>>>>>>>>>>>>>>>>>>>>"+_name);
        string[] list = null;
        int i = 0;
        if (destoryAssetList != null)
        {
            list = new string[1 + destoryAssetList.Length];
            foreach (string str in destoryAssetList)
            {
                list[i] = destoryAssetList[i];
                i++;
            }
        }
        else
        {
            i = 0;
            list = new string[1];
        }
        list[i] = _name;
        destoryAssetList = null;
        destoryAssetList = new string[i + 1];
        i = 0;
        foreach (string str in destoryAssetList)
        {
            destoryAssetList[i] = list[i];
            i++;
        }
        list = null;
        return;
    }
    //清理内存
    public static void FreeMemory()
    {
        //Debug.Log ("FreeMemory>>>>>>>>>>>>>>>>>>>>");
        if (destoryObjNameList == null)
        {
            //Debug.Log("不需要释放资源"); 
            return;
        }
        int i = 0;
        if (destoryObjNameList != null)
        {
            i = destoryObjNameList.Length;
            for (int j = i - 1; j >= 0; j--)
            {
                //Debug.Log("j==="+j+"i=="+i);
                if (destoryObjNameList[j] != null)
                    if (createdObjList.ContainsKey(destoryObjNameList[j]))
                    {
                        GameObject obj = createdObjList[destoryObjNameList[j]];
                        createdObjList.Remove(destoryObjNameList[j]);
                        //Debug.Log("销毁对象  "+obj.name);
                        if (obj != null)
                            GameObject.DestroyImmediate(obj, true);
                    }
                destoryObjNameList[j] = null;
            }
        }

        destoryObjNameList = null;
        if (destoryPerfabList != null)
        {
            //Debug.Log("需要销毁预设啊");
            RemoveFromDictionary(destoryPerfabList,typeof(GameObject));
        }

        destoryPerfabList = null;

        if (destoryAssetList == null || destoryAssetList.Length == 0)
        {
            //Debug.Log ("不需要释放AB包,可能会出现这种情况"); 
        }
        else
        {
            //销毁assetbundle内存镜像
            i = destoryAssetList.Length;
            for (int j = i - 1; j >= 0; j--)
            {
                if (loadedAssetList != null)
                    if (destoryAssetList[j] != null)
                        if (loadedAssetList.ContainsKey(destoryAssetList[j]))
                        {
                            //Debug.LogError("销毁assetbundle : " + destoryAssetList[j]);
                            AssetBundle asset = loadedAssetList[destoryAssetList[j]];
                            loadedAssetList.Remove(destoryAssetList[j]);
                            if (asset != null)
                                asset.Unload(true);
                        }
                destoryAssetList[j] = null;
            }
        }

        destoryAssetList = null;

        GC.Collect(0);
        GC.Collect(1);
        Resources.UnloadUnusedAssets();
    }
    ////通知lua创建对象
    //public static GameObject CallLuaCreatePanel(string _message){
    //  //Debug.Log("CallLuaCreatePanel : "+_message);
    //  Global.CallGlobalLuaFunction("LuaInitPrefabs.OnInitPanelOperateMessage",_message);
    //  return createdObjList[_message]as GameObject;
    //}
    //通知lua对创建的Panel进行销毁或者隐藏
    public static void CallLuaDestoryPanel(string _message, bool _isdestory)
    {
        //Debug.Log("CallLuaDestoryPanel : "+_message);
        Global.CallGlobalLuaFunction("LuaInitPrefabs.OnDesPanelOperateMessage", _message, _isdestory);
    }

    static void RemoveFromDictionary(string[] nameList,System.Type _ty) {
        if (littlePool.ContainsKey(_ty.ToString()))
        {
            foreach(string str in nameList){
                if (littlePool[_ty.ToString()].ContainsKey(str))
                {
                    littlePool[_ty.ToString()].Remove(str);
                }
                //else {
                    //Debug.Log("压根没有="+str);
                //}
            }
        }
        else {
            Debugger.Log("神奇唉~");
        }

    }
}

其实主要的思想就是,在游戏里面动态加载的资源都通过这里面的add方法,将加在进来的资源引用上,然后在游戏里面就可以直接根据资源名称从这里面进行寻找,这里面还有资源包的销毁,亲测可用,代码也比较简单,很好读,我们做的是棋牌大厅一类的,但是都没有上线,所以这里面还是可以再进行优化的,比如将游戏类型放进去,也作为一个目录的一级,在单个游戏退出后,根据需求将这个游戏资源销毁掉,可以根据配置什么的,参考一下喽。

2015-04-21 14:14:53 qinyuanpei 阅读数 35226
  • Unity 值得看的500+ 技术内容列表

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

各位朋友,大家好,我是秦元培。今天博主想和分享的是使用disunity提取Unity3D游戏素材。这个工具呢,博主在Unity3D游戏开发之反编译AssetBundle提取游戏资源这篇文章中其实已经提到过了,不过因为有些朋友对如何使用这个工具依然存在问题,所以博主决定特地写一篇文章来讲解如何使用disunity来提取Unity3D游戏中的素材。

准备工作

  • disunity:负责对Unity3D的数据文件进行解包
  • Unity3D:负责将导出的数据文件显示出来
  • Bleander或者3DsMax:负责Unity3D数据文件的转换处理,二选一即可。个人推荐Blender。
  • Java:负责为disunity提供编译环境

测试文件

  • 《新仙剑OL》下载
  • 《轩辕剑6外传穹之扉》
  • 《雨血前传:蜃楼》下载

提取流程

好了,在确定做好所有的准备工作后,我们就可以正式开始今天的内容了!

编译disunity

虽然我们可以从disunity的项目主页中下载release版本,不过为了保险起见,博主依然建议大家自行编译disunity。编译的方法是在命令行中切换到disunity的目录,然后执行命令:

java -jar disunity.jar

如果大家的Java环境没有任何问题的话,那么接下来我们就应该可以看到:

[Info] DisUnity v0.3.4

以及各种关于这个工具的使用方法和参数选项。那么好了,现在我们就来熟悉下disunity这个工具的常用命令。disunity命令的基本形式是:

disunity [CommandName] [CommandOptions]

disunity命令

  • dump:将一个二进制的对象转化成人类可以阅读的文本信息。
  • dump-struct:将一个二进制的对象转化为结构化的信息。
  • extract:将Unity3D的数据文件转化为常见的文本、声音、图片等信息。
  • extract-raw:将Unity3D的数据文件转化为可序列化的对象,在extract命令不被支持的情况下使用。
  • extract-txt:和dump命令类似输出转换结果到命令行。
  • extract-struct:和dump-struct命令类似输出转换结果到命令行。
  • info:输出Unity3D的数据文件和AssetBundle文件的变量信息。
  • bundle-extract:释放所有的被打包到AssetBundle中的文件。
  • bundle-inject:将从AssetBundle中打包的文件重新打包

暂时先介绍这些,因为其它的命令我们基本用不到,如果需要深入研究这些命令,可以参考disunity项目中的README.md文件。

解析《新仙剑OL 》的AssetBundle文件

这里我们以游戏目录/assetbundles/NPC/Models/下的s049.unity3d_CC9026FB为例来讲解游戏模型的提取。

模型文件提取

首先我们将这个文件的扩展名改为s049.unity3d,因为这是它原始的扩展名,是Unity3D中导出AssetBundle的一种文件格式。好了,我们将这个文件放在一个无中文路径的目录下,这里以C:\Users\Robin\Desktop即桌面为例。注意首先进入disunity的目录,然后执行命令:

disunity extract C:\Users\Robin\Desktop\s049.unity3d

接下来会在桌面生成一个名为s049的文件夹,在这个文件夹中找到Mesh的子文件夹,会得到一个s049.obj的文件,这个文件就是我们提取到的模型文件。

模型贴图提取

好了,下面我们再来看看怎么提取这个模型文件对应的贴图,在游戏目录/assetbundles/NPC/Texture/下有一个名为s049_1.unity3d_1D2446B9的文件,这就是s049这个模型对应的贴图了。同样地,我们将其重命名为s049_1.unity3d然后执行命令:

disunity extract C:\Users\Robin\Desktop\s049_1.unity3d

接下来在桌面上生成一个名为s049_1的文件夹,在这个文件夹中找到Texture2D的子文件夹,会得到一个名为s049_1.dds的贴图文件,这就是我们要提取的模型s049的贴图文件。

将模型和贴图合并

我们打开Blender并将s049.obj文件导入,然后将场景中默认的灯光和摄像机都删除,因为我们只需要一个模型文件,我们发现在Blender中已经可以看到模型了,因为Unity3D中使用的是FBX模型,所以我们这里将模型文件导出为FBX备用。因为Unity3D可以识别dds类型的贴图,所以对贴图我们不用做任何处理。

童年林月如的模型

打开Unity3D将童年林月如的模型和贴图一起导入,将童年林月如的模型拖入到游戏场景中,因为模型的尺寸没有经过调整,所以模型刚开始可能会比较小,我们可以在Unity3D进行局部的调整。接下来我们会发现模型没有贴图,只要选择这个模型然后在属性窗口为它附上s049_1.dds的贴图文件即可。下面是童年林月如的模型导入Unity3D以后的效果:

童年林月如导入Unity3D后的效果

解析《新仙剑OL》的assets文件

和AssetBundle不同,assets文件是整个Unity3D项目中项目资源的打包集合,比如说Asset文件下的资源都会被打包到这里,所以说解析assets文件可能会有更大的收获吧!因为所有的Unity3D游戏都会有这样的文件,而AssetBundle文件只有在使用了这项技术的游戏项目中才有。比如说在Unity3D中有一个重要的Resource文件夹,这个文件夹打包后被被打包成resources.assets文件。这里我们以xianjian_Data/resources.assets文件为例。首先执行命名:

disunity extract C:\Users\Robin\Desktop\resources.assets

接下来会在桌面生成一个resources的文件夹,打开这个文件夹我们会发现三个子文件夹,分别是Shader、TextAsset和Texture2D。解析的结果似乎有点失望,不过在TextAsset文件夹下我们会找到一个叫做ResourceFiles.txt的文件,这是一个纯文本文件,我们可以直接打开,打开后我们发现它的内容是一个Xml文件,并且在这个Xml文件中定义了游戏中使用的各种资源的路径,不过这些资源都是以AssetBundle的形式来定义的。这说明什么呢?这说明《新仙剑OL》的场景和界面资源是通过动态加载的方式加载到游戏当中的,而这些资源则是通过这个Xml文件来配置和管理的,这符合我们平时在Unity3D游戏开发中的观点和方法。通过这个文件,我们找到了assetbundles/config/movieconfig.unity3d这个文件,这是一个负责维护游戏中场景过场动画的文件。下面我们就来尝试解析这个文件,不过游戏制作方对config文件夹下的内容进行了加密,因为在这个文件夹下面是两个AssetBundle文件,博主尝试用extract和bundle-extract两个命令进行解析,可是得到的只是些文本文件,对我们继续研究没有什么帮助。那么好了,现在我们能够进行解析的只有xinjian_Data/sharedassets0.assets文件了:

disunity extract C:\Users\Robin\Desktop\sharedassets0.assets

这个解出来的话是些没有什么用的贴图文件,看来如果要提取音乐或者图片的话,还需要进行更加深入的研究才行啊。

解析《雨血前传.蜃楼》的assets文件

因为解析《新仙剑OL》的assets文件没有得到什么有用的东西,所以我们接下来来尝试解析《雨血前传.蜃楼》的assets文件。这款游戏是博主比较喜欢的一款游戏,基于Unity3DY引擎,而且这款游戏是作为Unity3D官方范例来推广的,因此研究这款游戏对我们提高Unity3D的资源打包机制会比较有帮助。好了,我们直接上手:

disunity extract C:\Users\Robin\Desktop\resources.assets

哈哈,这款游戏果然没有让我们失望,我们得到了什么呢?

蜃楼中各种Boss的头像

蜃楼中游戏连招视频1

蜃楼中游戏连招视频2

总结

  • 不同的游戏采用的资源配置方案都不同,不过一般可以从resources.assets这个文件入手作为突破点。
  • 如果能拿到游戏中数据配置方案,对于我们提取游戏中的素材会有较大的帮助,因为这样方向性会更强些。
  • 通过AssetBundle动态加载到场景中最好还是采用一个配置表来进行配置,这样便于我们管理和维护整个游戏项目。
  • 如果没有服务器段的干预,理论上只要修改了本地的AssetBundle文件就可以实现对游戏内容和数据的更改,换句话说,可以做外挂和修改器。

声明:我不是在教你破解游戏,我只是在研究AssetBundle打包 !

2017-06-10 22:44:17 liuyuehui110 阅读数 10360
  • Unity 值得看的500+ 技术内容列表

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

本章主要是 Unity3D 的基础快速入门篇,通过本章的学习,能让初学者们快速的掌握和
应用 Unity3D 软件。
本章导读
本章无论对于 Unity3D 初学者、或是以前从事建模工作的设计师、又或者是从事过 3D
编程的人群,在转向 Unity3D 的学习的时候,学习本章内容都极有必要的。通过本章节内容
的学习,你可以迅速的掌握 Unity3D 的软件结构,各个板块具体的功能和作用以及场景制作
流程等等。本章节可以说是全书的根基,只要你能把本章节的内容掌握熟悉,那在今后的日
子里面开发任何的游戏可以说是事半功倍。这就像运动员们在小的时候打好了坚实的基础,
再向今后的专业突破的时候,就感觉游刃有余,几乎不费吹灰之力就拿下任何一个项目。
对于有一定 Unity3D 基础的人群而言,笔者也强力建议在有充足时间的情况下,耐心的
阅读下本章节的内容。因为笔者时常在网上发现一些 Unity3D 早期的开发者,往往对一些 U
nity3D 很基础性的功能莫不着头脑。这不能怨这些开发者的水品不够,而是有许多 Unity3D
的功能在长期未被使用的情况下,被他们遗忘了,突然有一天需要使用的时候,就抓了狂,
发了疯。
另外笔者要在这里提醒一下建模设计师,可能你们在原先的行业里面已经将 3DS MA
X、Maya 等建模软件摸索得相当熟练了,但游戏场景和影视场景还是存在很大的差异。首
先最大的差异就在于灯效的实时性,游戏讲究的是动态灯光,追求与游戏者视觉互动;而影
视灯光则是一种静态的逐帧灯光,所有效果都被渲染成一张张静态的连续图片,它们主要倾
向与影迷们的观赏互动;前者比较主动,而后者相对来说比较被动。前者在使用灯效处理的
时候受硬件的局限性大,面对的客户群体需要有所区分;后者在观赏灯效处理的时候,几乎
不受任何计算机硬件的影响,面对的客户群体也比较广泛。

1.1 界面入门


如图 1-1 所示为 Unity3D 最经典 2 by 3 结构界面,上面呈现了 Unity3D 最为常用的几
个面板,下面为各个面板的详细说明。
  Scene【场景面板】:该面板为 Unity3D 的编辑面板;你可以将你所有的模型、灯光、
以及其他材质对象拖放到该场景中。构建游戏中所能呈现景象。
  Game【游戏面板】:与场景面板不同,该面板是用来渲染场景面板中景象的。该面
板不能用作编辑,但却可以呈现完整的动画效果。
  Hierarchy【层次清单栏】:该面板栏主要功能是显示放在场景面板中所有的物体对
象。
  Project【项目文件栏】:该面板栏主要功能是显示该项目文件中的所有资源列表。
除了模型、材质、字体等,还包括该项目的各个场景文件。
  Inspector【监视面板】:该面板栏会呈现出任何对象的所固有的属性,包括三维坐
标、旋转量、缩放大小、脚本的变量和对象等等。
  【场景调整工具】:可改变你在编辑过程中的场景视角、物体世界坐标和本地坐标
的更换、物体法线中心的位子,以及物体在场景中的坐标位置,缩放大小等等。
  【播放、暂停、逐帧按钮】:用于运行游戏,暂停游戏和逐帧调试程序。
  【层级显示按钮】:勾选或取消该下拉框中对应层的名字,就能决定该层中所有物
体是否在场景面板中被显示。
  【版面布局按钮】:调整该下拉框中的选项,即可改变编辑面板的布局。
  【菜单栏】:和其他软件一样,包含了软件几乎所有要用到的工具下拉菜单。
除了 Unity3D 初始化的这些面板而外,你还可以通过“Add Tab”按钮和菜单栏中的“W
indow”下拉菜单中,增添其他面板和删减现有面板。特别是“Window”下拉菜单中的“Li
ghtmapping”和“Occllusion Culling”面板对游戏的后期优化尤为管用。除此而外还有用
于制作动画文件的 Animation【动画面板】,用于观测性能指数的 profiler【分析器面板】,
用于购买产品和发布产品的 Asset Store【资源商店】,用于控制项目版本的 Asset Server
【资源服务器】,用于观测和调试错误的 Console【控制台面板】。
在【菜单栏】中包含有八个菜单选项:分别是 File【文件】、Edit【编辑】、Assets【资
源】、GameObject【游戏对象】、Component【组件】、Terrain【地形】、Window【窗口】、He
lp【帮助】。这些是 Unity3D 中最标准的菜单选项卡,其各自又有自己的子菜单,表 1-1 中
列出了各个菜单栏以及它们所包含的下拉菜单及其译名,仅供读者参考。

主菜单  包含的子菜单

File【文件】

New Scene【新建场景】
Open Scene【打开场景】
Save Scene 【保存场景】
Save Scene as…【场景另存为…】
New Project… 【新建工程文件】
Open Project… 【打开工程文件】
Save Project 【保存工程文件】
Build Settings… 【创建设置】(这里可以设置你的游戏将要以
何种方式发布,发布的场景文件又包含那些)
Build & Run 【创建并运行】(这里以“Build Settings”里设
置好的方式,发布并运行游戏)
Exit 【退出】

Edit【编辑】

Undo 【撤销上一步操作】
Redo 【恢复被撤销的操作】
Cut 【剪切】
Copy 【拷贝】
Paste 【粘贴】
Duplicate 【复制】
Delete 【删除】
Frame Selected 【在编辑场景中最大化显示被选中的物体】
Select All 【全选编辑面板中的所有物体】
Preferences… 【首选参数设置】
Play 【播放】(如果游戏已经开始播放,点此按钮代表停止播
放)
Pause 【暂停】
Step 【逐帧播放游戏】
Load selection 【载入所选】(与“Save selection”【存储所选】
联合使用,你可以把它理解为一个临时的快捷键,帮你快速的
找到特定的以被存储的物体对象。)
Save selection 【存储所选】(与“Load selection”【载入所选】
联合使用,你可以把它理解为一个临时的快捷键,帮你快速的
找到特定的以被存储的物体对象。)
Project Settings 【工程文件设置】(包含了该工程项目的“Inp
ut”【热键】、“Tags”【标签管理】、“Audio”【音频设置】、“Ti
me”【时间设置】、“Player”【播放器设置】、“Physics”【默认仿
真物理设置】、“Quality”【播放质量参数设置】、“NetWork”【网
络工作参数设置】、“Editor”【编辑器设置】)“Script Execution
Order”【脚本编译顺序设置】
Render settings 【渲染设置】(默认渲染参数设置,包括环境光,
周围的雾化程度,环境颜色等等一系列参数的设定)
Network emulation 【网络仿真】(由于你制作的游戏将会在不
同的网络环境中工作,所以需要这个参数来模拟不同的网络工
作环境)
Graphics emulation 【图形卡仿真】(由于你制作的游戏将会在
不同的图形卡环境中工作,所以将需要这个参数来模拟不同硬
件条件下的游戏显示质量)
Snap settings 【捕捉设置】(和 3Ds Max 的“栅格和捕捉设置”
类似。)

Assets 【资源】

Create 【创建】(包含有“Folder”【文件夹】、“JavaScript”【Ja
vaScript 编程脚本】、“C# Script”【C#编程脚本】、“Boo Script”
【Boo 编程脚本】、“Shader”【着色语言】、“Prefab”【预置物体】、
“Material”【材质】、“Animation”【动画】、“Cubemap”【立方
体贴图】、“Lens Flare”【镜头光晕】、“Custom Font”【自定义
字体】、“Render Texture”【渲染纹理】、“Physic Material”【物
理材质】、“GUI Skin”【用户图形界面皮肤】)
Show in Explor 【显示项目资源所在的文件夹】
Open【打开选中的资源】
Delete【删除选定资源】
Import New Asset... 【导入新的资源】
Import Package...【导入资源包】
Export Package... 【导出资源包】
Select Dependencies 【选择相关联的文件】
Export compressed audio file... 【导出压缩的音频文件】
Refresh 【刷新】
Reimport 【重新导入选中的资源】
Reimport All 【重新导入所有的资源文件】
Sync MonoDevelop Project 【与 Mono 项目文件同步】

GameObject【游戏项目】

Create Empty【创建空的游戏对象】
Create Other 【创建其他组件】(包含了“Particle System”【粒
子系统】、“Camera”【摄像机】、“GUI Text”【图形用户界面文
本】、“GUI Texture”【图形用户界面图片】、“3D Text”【3D 文
字】、“Directional Light”【平行光】、“Point Light”【点光源】、
“Spotlight”【聚光灯】、“Cube”【立方体】、“Sphere”【球】、“C
apsule”【胶囊】、“Cylinder”【圆筒】、“Plane”【平面】、“cloth”
【布料】、“Audio Reverb Zone”【声音回响区域】、“Ragdoll..”
【布娃娃系统】、“Tree”【植被树系统】、“Wind Zone”【风的区
域】)
Center On Children 【归位到子物体中心点】
Make Parent 【创建父集】(必须选择两个以上的物体才能使用
该命令,最先被选中的物体为父级对象,其余的对象都为该对
象的子集)
Clear Parent 【取消父集】(取消被选中物体与它上一个父级之
间的父子级关系)
Apply Changes To Prefab 【改变影响预制物体】(如果你在场
景中编辑的物体是从资源面板拖拽出的预制物体,默认的情况
下,你在场景面板中对物体做出的改变不会影响原先的预制物
体,除非你点击该按钮)
Move To View 【移动物体到“Scene”视窗的中心点】
Align With View 【移动物体到“Scene”视窗的中心点,并且
与显示口正对齐,物体中心位于显示口的中心点】
Align View to Selected 【移动“Scene”视窗与物体对齐,并
且显示口的中心点位于物体的中心】

Component【组件】

Mesh 【网格】(“Mesh Filter”【网格填充】、“Text Mesh”【文
字网格】、“Mesh Renderer”【网格渲染】、“Combine Children”
【合并子物体】)
Particles 【粒子系统】(能打造出非常棒的流体效果,是制作烟
雾、激光、火焰等效果的首选。“Ellipsoid Particle Emitter”【
椭球粒子发射器 】,“Mesh Particle Emitter”【面片粒子发射器】,
“Particle Animator” 【粒子动画】, “World Particle Collider” 【世
界粒子碰撞机】,“Particle Renderer”【粒子渲染器】,“Trail Re
nderer”【蔓延渲染】)
Physics 【物理系统】(可使物体带有对应的物理属性)
Audio 【音频】(可创建声音源和声音的听者)
Rendering 【渲染】
Miscellaneous 【杂项】
Scripts 【脚本】(Unity 内置的一些功能很强大的脚本)
Image Effects【图形渲染效果】(仅限专业版)
Character【角色控制器】
Camera-Control 【摄像机控制】

Terrain【地形】 

 Create Terrain 【创建地形】
Import Heightmap - Raw... 【导入高度图】
Export Heightmap - Raw... 【导出高度图】
Set Resolution... 【设置分辨率】
Create Lightmap... 【创建光影图】
Mass Place Trees... 【批量种植树】
Flatten Heightmap... 【展平高度图】
Refresh Tree and Detail Prototypes 【刷新树及细节模型】

Window【窗口】

Next Window 【下个窗口】
Previous Window 【前一个窗口】
Layouts 【布局】
Scene 【场景窗口】
Game 【游戏窗口】
Inspector 【监视窗口】(这里主要指各个对象的属性)
Hierarchy 【层次窗口】
Project 【项目文件窗口】
Animation 【动画窗口】(用于创建时间动画的面板)
Profiler 【性能探测窗口】
Asset Store 【资源商店】
Asset Server 【源服务器】
Lightmapping 【灯影视图窗口】
Occlusion Culling 【遮挡剔除窗口】
Console 【控制台】

Help【帮助】

About Unity... 【关于 Unity】
Enter serial number... 【输入序列号】
Unity Manual 【Unity 手册】
Reference Manual 【参考手册】
Scripting Manual 【脚本手册】
What’s New 【最新功能】
Unity Forum 【Unity 论坛】
Unity Answers 【Unity 在线答疑】
Unity Feedback 【Unity 使用信息反馈】
Welcome Screen 【欢迎窗口】
Check for Updates 【查看升级】
Release Notes 【发行说明】
Report a bug【软件缺陷反馈】

2017-10-12 23:45:25 q764424567 阅读数 5601
  • Unity 值得看的500+ 技术内容列表

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

一、前言

最近跑酷游戏比较流行,开发教程也很多,但是大部分都是不太详细,这篇文章就带着大家一步一步开发出来一个跑酷类的游戏,教程比较基础,适合大部分Unity开发的初学者。
还有就是,此专栏已经开通收费,里面整合的都是小游戏的开发教程,想要学习Unity开发游戏的,都可以订阅一下。
如果文章出现什么问题,就及时联系我

二、效果图&下载链接

在这里插入图片描述
Github地址:https://github.com/764424567/Game_Parkour

三、教程

在教程开始之前,我们分析一下跑酷类游戏制作思路。
首先是道路和障碍物,我们可以先设置三段道路,然后障碍物随机生成
道路中间有抵达点,角色到达抵达点判断是否将后面的道路移动到前面接起来。
首先到达第一段的抵达点,肯定是不切换
到达第二段的抵达点,将1号路段移动到最前面
到达第三段的抵达点,将2号路段移动到最前面
循环往复,无穷尽也
然后是主角的移动脚本,躲避障碍物,移动位置固定三个点,可以跳,可以铲地
主角碰到障碍物就挂,游戏结束

1、新建项目

博主的Unity版本是Unity5.6.1f1,推荐大家使用我这个版本,或者其他的5.6.x版本,不然可能会出现各种各样奇奇怪怪的问题。
在这里插入图片描述
文件目录的话就按照我这个目录来,比较清晰明了。

2、导入资源

资源已经上传到Github,需要的可以下载
https://github.com/764424567/Unity-plugin/tree/master/ParkourDemo
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3、处理动画资源

在这里插入图片描述
可以看到所有的动画文件都有。
接着我们就可以新建一个Animator Controller文件来管理动画文件。
在这里插入图片描述
命名随意。
在这里插入图片描述
接着我们将动画剪辑拖到Animator处理面板中:
在这里插入图片描述
默认状态是run,然后有jump 、slide、idle
在这里插入图片描述
接着就是“Take Transition”将run和jump 以及 run 、slide、idle连下线。

设置两个bool值,来控制动画的切换:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
接下来我们就可以在场景中看一下动画效果了:
在这里插入图片描述

4、处理路段模型

在这里插入图片描述
首先我们找到导入的资源SimpleRoadwork,里面有一个Demo场景,点进去可以看一下各类模型:
在这里插入图片描述
在Prefabs文件夹中,可以找到我们需要的各类模型,包括路面、路标、障碍物:
在这里插入图片描述
接下来,我们就设计一下路面:
在这里插入图片描述
在这里插入图片描述
接着摆放路标:
在这里插入图片描述
接着摆放障碍物:
在这里插入图片描述
因为障碍物我们要后期自动生成,现在就可以先隐藏起来。
在这里插入图片描述
然后设置到达点(到达点的目的是当角色到达这个位置的时候,自动切换路线):
在这里插入图片描述
隐藏它的Mesh Renderer ,将BoxCollider IsTrigger设置成true:
在这里插入图片描述
路段就完成了:

在这里插入图片描述
整个目录如下:
在这里插入图片描述
不会摆放也没有关系,我已经设置好了,用我的也行。

5、主角模型处理

在这里插入图片描述

明显是有点大,我们给它同比例缩小一下:
在这里插入图片描述
在这里插入图片描述
接着设置一下摄像机的视角:

在这里插入图片描述
在这里插入图片描述

6、生成障碍物

新建脚本Control_Scenes.cs
我们首先来生成障碍物:

using UnityEngine;

public class Control_Scenes : MonoBehaviour
{
    public GameObject[] m_ObstacleArray;
    public Transform[] m_ObstaclePosArray;

    void Start()
    {
        Spawn_Obstacle(1);
    }
	
	//生成障碍物
	public void Spawn_Obstacle(int index)
    {
        //销毁原来的对象
        GameObject[] obsPast = GameObject.FindGameObjectsWithTag("Obstacle" + index);
        for (int i = 0; i < obsPast.Length; i++)
        {
            Destroy(obsPast[i]);
        }
        //生成障碍物
        foreach (Transform item in m_ObstaclePosArray[index])
        {
            GameObject prefab = m_ObstacleArray[Random.Range(0, m_ObstacleArray.Length)];
            Vector3 eulerAngle = new Vector3(0, Random.Range(0, 360), 0);
            GameObject obj = Instantiate(prefab, item.position, Quaternion.Euler(eulerAngle));
            obj.tag = "Obstacle" + index;
        }
    }
}

将脚本挂在Control_Scenes对象上:
将Prefab文件夹里面的模型拖入到ObstacleArray对象数组卡槽中
将隐藏以后的障碍物拖入到ObstaclePosArray对象数组卡槽中
在这里插入图片描述
添加Tag:
在这里插入图片描述
运行看一下:
在这里插入图片描述

7、路段切换

我们接着打开Control_Scenes.cs
添加函数:

void Start()
{
        //Spawn_Obstacle(1);
        Change_Road(1);
}
public void Change_Road(int index)
{
        if (m_ISFirst && index == 0)
        {
            m_ISFirst = false;
            return;
        }
        else
        {
            int lastIndex = index - 1;
            if (lastIndex < 0)
                lastIndex = 2;
            m_RoadArray[lastIndex].transform.position = m_RoadArray[lastIndex].transform.position - new Vector3(150, 0, 0);
            Spawn_Obstacle(lastIndex);
        }
}

PS:这里解释一下代码,怎么切换的呢
举个例子,角色跑到了第二段,那么第一段要移动到第三段后面隔一个路段长度的距离,接下来画个图:
在这里插入图片描述
那么为啥x轴减去150。这是因为我发现这三条路段的距离都差了50,坐标轴是负轴,所以就减去了150。
在这里插入图片描述

我们可以测试一下效果:
在这里插入图片描述
但是仅仅这样是不够的,我们还需要在角色到达抵达点的时候,切换路线,当然第一段路不用切换,因为再切就没了。。
这个在我们写完角色移动以后再补充。

8、角色移动

新建脚本:Control_Player.cs
说明一下:因为我们设定的三条道,所以角色只能在三条道里面切换。那么只需要改变角色的z值就可以了。如果角色在最左边,那么只能往右移动,同理在最右边,只能往左移动,在中间两边都可以移动。

接着我们就可以看一下z轴的值:
中间:
在这里插入图片描述
在这里插入图片描述

左边:
在这里插入图片描述
在这里插入图片描述
右边:
在这里插入图片描述
在这里插入图片描述
代码:

using UnityEngine;

public class Control_Player : MonoBehaviour
{
    //前进速度
    public float m_ForwardSpeeed = 7.0f;
    //动画组件
    private Animator m_Anim;
    //动画现在状态
    private AnimatorStateInfo m_CurrentBaseState;

    //动画状态参照
    static int m_jumpState = Animator.StringToHash("Base Layer.jump");
    static int m_slideState = Animator.StringToHash("Base Layer.slide");

    // Use this for initialization
    void Start()
    {
        m_Anim = GetComponent<Animator>();
    }

    // Update is called once per frame
    void Update()
    {
        transform.position += Vector3.left * m_ForwardSpeeed * Time.deltaTime;
        m_CurrentBaseState = m_Anim.GetCurrentAnimatorStateInfo(0);
        if (Input.GetKeyDown(KeyCode.W))
        {
            m_Anim.SetBool("jump", true);
        }
        else if (Input.GetKeyDown(KeyCode.S))
        {
            m_Anim.SetBool("slide", true);
        }
        else if (Input.GetKeyDown(KeyCode.A))
        {
            Change_PlayerZ(true);
        }
        else if (Input.GetKeyDown(KeyCode.D))
        {
            Change_PlayerZ(false);
        }
        if (m_CurrentBaseState.fullPathHash == m_jumpState)
        {
            m_Anim.SetBool("jump", false);
        }
        else if (m_CurrentBaseState.fullPathHash == m_slideState)
        {
            m_Anim.SetBool("slide", false);
        }
    }

    public void Change_PlayerZ(bool IsAD)
    {
        if (IsAD)
        {
            if (transform.position.z == -14.7f)
                return;
            else if (transform.position.z == -9.69f)
            {
                transform.position = new Vector3(transform.position.x, transform.position.y, -14.7f);
            }
            else
            {
                transform.position = new Vector3(transform.position.x, transform.position.y, -9.69f);
            }
        }
        else
        {
            if (transform.position.z == -6.2f)
                return;
            else if (transform.position.z == -9.69f)
            {
                transform.position = new Vector3(transform.position.x, transform.position.y, -6.2f);
            }
            else
            {
                transform.position = new Vector3(transform.position.x, transform.position.y, -9.69f);
            }

        }
    }
}

键盘WSAD控制上跳,下滑,左右移动等操作。现在就可以去试试啦。
在这里插入图片描述

但是,有一点哈,角色怎么越跑越远离开了我们呢,因为,还没有写摄像机跟随脚本,接下来继续吧。

9、摄像机跟随

新建脚本Control_Camera.cs

using UnityEngine;

public class Control_Camera : MonoBehaviour
{
	//间隔距离
	public float m_DistanceAway = 5f;
	//间隔高度
	public float m_DistanceHeight = 10f;
	//平滑值
	public float smooth = 2f;               
	//目标点
	private Vector3 m_TargetPosition;      
	//参照点
	Transform m_Follow;        

	void Start()
	{
		m_Follow = GameObject.Find("Player").transform;
	}

	void LateUpdate()
	{
		m_TargetPosition = m_Follow.position + Vector3.up * m_DistanceHeight - m_Follow.forward * m_DistanceAway;
		transform.position = Vector3.Lerp(transform.position, m_TargetPosition, Time.deltaTime * smooth);
	}
}

OK 摄像机跟着Player跑起来了

10、碰撞检测

我们需要不停的躲避障碍物,一旦碰撞到障碍物就dead了
我们首先修改障碍物的碰撞器属性:
在这里插入图片描述
Mesh Collider : Is Trigger=true
在这里插入图片描述
我们给Player加上刚体和碰撞体:
在这里插入图片描述
注意要勾上Is Trigger

打开Control_Player.cs脚本:

//游戏结束
bool m_IsEnd = false;
void OnGUI()
{
        if (m_IsEnd)
        {
            GUIStyle style = new GUIStyle();
            style.alignment = TextAnchor.MiddleCenter;
            style.fontSize = 40;
            style.normal.textColor = Color.red;
            GUI.Label(new Rect(Screen.width / 2 - 100, Screen.height / 2 - 50, 200, 100), "你输了~", style);
        }
}
void OnTriggerEnter(Collider other)
    {
        if (other.gameObject.name == "Vehicle_DumpTruck" || other.gameObject.name == "Vehicle_MixerTruck")
        {
            m_IsEnd = true;
            m_ForwardSpeeed = 0;
            m_Anim.SetBool("idle", true);
        }
    }

11、切换道路

在第七章的时候就已经写好了道路切换,但是一直没有讲到碰撞检测,那么现在我们就结合碰撞检测,检测到抵达点然后切换道路吧

我们首先找到三个抵达点:MonitorPos
然后改名叫做MonitorPos0 MonitorPos1 MonitorPos2
在这里插入图片描述
修改一下BoxCollider的Is Trigger属性:
在这里插入图片描述
修改Control_Player.cs的代码:

 //场景控制对象
Control_Scenes m_ControlScenes;
void Start()
{
        m_Anim = GetComponent<Animator>();
        m_ControlScenes = GameObject.Find("Control_Scenes").GetComponent<Control_Scenes>();
}
void OnTriggerEnter(Collider other)
{
        if (other.gameObject.name == "Vehicle_DumpTruck" || other.gameObject.name == "Vehicle_MixerTruck")
        {
            m_IsEnd = true;
            m_ForwardSpeeed = 0;
            m_Anim.SetBool("idle", true);
        }
        if (other.gameObject.name == "MonitorPos0")
        {
            m_ControlScenes.Change_Road(0);
        }
        else if (other.gameObject.name == "MonitorPos1")
        {
            m_ControlScenes.Change_Road(1);
        }
        else if (other.gameObject.name == "MonitorPos2")
        {
            m_ControlScenes.Change_Road(2);
        }
}