unity3d场景索引设置_unity 当前场景的索引 - CSDN
  • 一、Unity关卡Unity 使用过程中关卡加载和卸载是大多数三维引擎都要提供的基本功能。 因为关卡切换在游戏中非常常用。 在之前的版本中Unity的关卡切换使用的是:Application.loadedLevel();看看Application类,...

    一、Unity关卡

    Unity 使用过程中关卡加载和卸载是大多数三维引擎都要提供的基本功能。
    因为关卡切换在游戏中非常常用。
    在之前的版本中Unity的关卡切换使用的是:

    Application.loadedLevel();

    看看Application类,此时这个类的功能比较繁杂,比较多。只看与关卡相关的:

     [Obsolete("Use SceneManager.LoadScene")]
            public static void LoadLevel(string name);
    
            [Obsolete("Use SceneManager.LoadScene")]
            public static void LoadLevel(int index);
    
            [Obsolete("Use SceneManager.LoadScene")]
            public static void LoadLevelAdditive(string name);
    
            [Obsolete("Use SceneManager.LoadScene")]
            public static void LoadLevelAdditive(int index);
      //
            // 摘要:
            //     ///
            //     Unloads all GameObject associated with the given scene. Note that assets are
            //     currently not unloaded, in order to free up asset memory call Resources.UnloadAllUnusedAssets.
            //     ///
            //
            // 参数:
            //   index:
            //     Index of the scene in the PlayerSettings to unload.
            //
            //   scenePath:
            //     Name of the scene to Unload.
            //
            // 返回结果:
            //     ///
            //     Return true if the scene is unloaded.
            //     ///
            [Obsolete("Use SceneManager.UnloadScene")]
            public static bool UnloadLevel(string scenePath);
            //
            // 摘要:
            //     ///
            //     Unloads all GameObject associated with the given scene. Note that assets are
            //     currently not unloaded, in order to free up asset memory call Resources.UnloadAllUnusedAssets.
            //     ///
            //
            // 参数:
            //   index:
            //     Index of the scene in the PlayerSettings to unload.
            //
            //   scenePath:
            //     Name of the scene to Unload.
            //
            // 返回结果:
            //     ///
            //     Return true if the scene is unloaded.
            //     ///
            [Obsolete("Use SceneManager.UnloadScene")]
            public static bool UnloadLevel(int index);

    注解:
    这是之前的Application中的关卡的加载和卸载。
    当然现在在新版本(Unity5.3以上)中,有了新的变化,那就是SceneManager类了处理。

    二、Untiy的SceneManager类

    自从Unity5.3版本,Unity 的关卡切换就添加了新的SceneManager的类来处理。
    当然要安装过了Unity文档帮助,并且给下面路径一样,就可以知道在本地打开。
    本地链接:
    file:///C:/Program%20Files/Unity5.3.0/Editor/Data/Documentation/en/Manual/UpgradeGuide53.html
    也可以在Unity中搜索SceneManager来查看。

    #region 程序集 UnityEngine, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
    // H:\Unity\UnityProject\ShiftLevels\Library\UnityAssemblies\UnityEngine.dll
    #endregion
    
    using UnityEngine.Internal;
    
    namespace UnityEngine.SceneManagement
    {
        //
        // 摘要:
        //     ///
        //     Scene management at run-time.
        //     ///
        public class SceneManager
        {
            public SceneManager();
    
    
            public static int sceneCount { get; }
            //
    
            public static int sceneCountInBuildSettings { get; }
    
    
            public static Scene GetActiveScene();
    
            public static Scene[] GetAllScenes();
            // 参数:
            //   index:
            //     Index of the scene to get. Index must be greater than or equal to 0 and less
            //     than SceneManager.sceneCount.
            public static Scene GetSceneAt(int index);
    
            // 返回结果:
            //     ///
            //     The scene if found or an invalid scene if not.
            //     ///
            public static Scene GetSceneByName(string name);
    
            //     Searches all scenes added to the SceneManager for a scene that has the given
            //     asset path.
            //     ///
            //
            // 参数:
            //   scenePath:
            //     Path of the scene. Should be relative to the project folder. Like: "AssetsMyScenesMyScene.unity".
            public static Scene GetSceneByPath(string scenePath);
            [ExcludeFromDocs]
            public static void LoadScene(int sceneBuildIndex);
            [ExcludeFromDocs]
            public static void LoadScene(string sceneName);
    
            // 参数:
            //   sceneName:
            //     Name of the scene to load.
            //
            //   sceneBuildIndex:
            //     Index of the scene in the Build Settings to load.
            //
            //   mode:
            //     Allows you to specify whether or not to load the scene additively. See SceneManagement.LoadSceneMode
            //     for more information about the options.
            public static void LoadScene(int sceneBuildIndex, [DefaultValue("LoadSceneMode.Single")] LoadSceneMode mode);
    
            // 参数:
            //   sceneName:
            //     Name of the scene to load.
            //
            //   sceneBuildIndex:
            //     Index of the scene in the Build Settings to load.
            //
            //   mode:
            //     Allows you to specify whether or not to load the scene additively. See SceneManagement.LoadSceneMode
            //     for more information about the options.
            public static void LoadScene(string sceneName, [DefaultValue("LoadSceneMode.Single")] LoadSceneMode mode);
            [ExcludeFromDocs]
            public static AsyncOperation LoadSceneAsync(int sceneBuildIndex);
            [ExcludeFromDocs]
            public static AsyncOperation LoadSceneAsync(string sceneName);
    
            // 参数:
            //   sceneName:
            //     Name of the scene to load.
            //
            //   sceneBuildIndex:
            //     Index of the scene in the Build Settings to load.
            //
            //   mode:
            //     If LoadSceneMode.Single then all current scenes will be unloaded before loading.
            public static AsyncOperation LoadSceneAsync(int sceneBuildIndex, [DefaultValue("LoadSceneMode.Single")] LoadSceneMode mode);
    
            // 参数:
            //   sceneName:
            //     Name of the scene to load.
            //
            //   sceneBuildIndex:
            //     Index of the scene in the Build Settings to load.
            //
            //   mode:
            //     If LoadSceneMode.Single then all current scenes will be unloaded before loading.
            public static AsyncOperation LoadSceneAsync(string sceneName, [DefaultValue("LoadSceneMode.Single")] LoadSceneMode mode);
            //
    
            // 参数:
            //   sourceScene:
            //     The scene that will be merged into the destination scene.
            //
            //   destinationScene:
            //     Existing scene to merge the source scene into.
            public static void MergeScenes(Scene sourceScene, Scene destinationScene);
            //
            // 摘要:
            //     ///
            //     Move a GameObject from its current scene to a new scene. /// It is required that
            //     the GameObject is at the root of its current scene.
            //     ///
            //
            // 参数:
            //   go:
            //     GameObject to move.
            //
            //   scene:
            //     Scene to move into.
            public static void MoveGameObjectToScene(GameObject go, Scene scene);
            //
    
            // 返回结果:
            //     ///
            //     Returns false if the scene is not loaded yet.
            //     ///
            public static bool SetActiveScene(Scene scene);
    
            //     ///
            public static bool UnloadScene(string sceneName);
            //
            // 摘要:
            //     ///
            //     Unloads all GameObjects associated with the given scene. Note that assets are
            //     currently not unloaded, in order to free up asset memory call Resources.UnloadAllUnusedAssets.
            //     ///
            //
            // 参数:
            //   sceneBuildIndex:
            //     Index of the scene in the Build Settings to unload.
            //
            //   sceneName:
            //     Name of the scene to unload.
            //
            // 返回结果:
            //     ///
            //     Returns true if the scene is unloaded.
            //     ///
            public static bool UnloadScene(int sceneBuildIndex);
        }
    }

    三、SceneManager对于获取场景的一些操作

    (一)
    SceneManager
    class in UnityEngine.SceneManagement
    描述:运行时的场景管理。
    静态变量sceneCount: 当前加载的场景的总数。
    前加载的场景的数量将被返回。
    sceneCountInBuildSettings : 在BuildSettings的号码。

    (二)
    CreateScene:在运行时创建一个空的新场景,使用给定的名称。
    在运行时创建一个空的新场景,使用给定的名称。
    新的场景将开放相加到层次与现有已经打开的场景。新场景的路径将是空的。此函数用于在运行时创建场景。创建一个场景编辑时间(例如,使编辑脚本或工具需要创建场景时),使用editorscenemanager.newscene。

    (三)
    public static SceneManagement.Scene GetActiveScene()
    现场的活动场景。
    描述:获取当前活动场景。
    当前活动的场景将被用来作为目标由脚本实例化新的游戏对象现场。

    using UnityEngine;
    using UnityEngine.SceneManagement;
    
    public class GetActiveSceneExample : MonoBehaviour
    {
        void Start()
        {
            Scene scene = SceneManager.GetActiveScene();
    
            Debug.Log("Active scene is '" + scene.name + "'.");
        }
    }

    (四)
    public static SceneManagement.Scene GetSceneAt(int index);
    index:场景索引。指数必须大于或等于0和小于scenemanager.scenecount。

    返回:
    根据给定的参数返回一个场景引用。
    获取现场在添加场景的场景管理器的列表索引:

    using UnityEditor;
    using UnityEditor.SceneManagement;
    using UnityEngine.SceneManagement;
    using UnityEngine;
    public class Example
    {
        // adds a menu item which gives a brief summary of currently open scenes
        [MenuItem("SceneExample/Scene Summary")]
        public static void ListSceneNames()
        {
            string output = "";
            if (SceneManager.sceneCount > 0)
            {
                for (int n = 0; n < SceneManager.sceneCount; ++n)
                {
                    Scene scene = SceneManager.GetSceneAt(n);
                    output += scene.name;
                    output += scene.isLoaded ? " (Loaded, " : " (Not Loaded, ";
                    output += scene.isDirty ? "Dirty, " : "Clean, ";
                    output += scene.buildIndex >=0 ? " in build)\n" : " NOT in build)\n";
                }
            }
            else
            {
                output = "No open scenes.";
            }
            EditorUtility.DisplayDialog("Scene Summary",output, "Ok");
        }
    }

    (五)

    public static SceneManagement.Scene GetActiveScene();
    获取当前活动场景。
    当前活动的场景将被用来作为目标由脚本实例化新对象。

    using UnityEngine;
    using UnityEngine.SceneManagement;
    
    public class GetActiveSceneExample : MonoBehaviour
    {
        void Start()
        {
            Scene scene = SceneManager.GetActiveScene();
    
            Debug.Log("Active scene is '" + scene.name + "'.");
        }
    }
    
    public static void LoadScene(int sceneBuildIndex, SceneManagement.LoadSceneMode mode = LoadSceneMode.Single);
    public static void LoadScene(string sceneName, SceneManagement.LoadSceneMode mode = LoadSceneMode.Single);

    sceneName: 需发加载的场景名称或路径。

    sceneBuildIndex:在“ Build Settings”加载中的场景的索引。

    Mode:允许您指定是否要加载相加现场。见LoadScene模式有关选项的详细信息。
    LoadSceneMode:在播放器加载一个场景时使用。
    Single:关闭所有当前场景和加载一个新场景。
    Additive:将场景添加到当前加载的场景中。
    你可以使用这个异步版本:LoadSceneAsync。

    using UnityEngine;
    using UnityEngine.SceneManagement;
    
    public class ExampleClass : MonoBehaviour {
        void Start () {
            // Only specifying the sceneName or sceneBuildIndex will load the scene with the Single mode
            SceneManager.LoadScene ("OtherSceneName", LoadSceneMode.Additive);
        }
    }

    四、5.3的实现代码

    上代码:

    /**************************************************************************
    Copyright:@maxdong
    Author: maxdong
    Date: 2017-07-04
    Description:加载关卡,可以分组加载和卸载。使用Unity版本为5.3.0.
    因为里面使用了场景管理的一个类,这个类在5.3.0以上版本才添加的。
    测试操作:使用空格键来切换场景,然后间隔5秒后才开始卸载。
    **************************************************************************/
    using UnityEngine;
    using System.Collections;
    using UnityEngine.SceneManagement;
    
    [System.Serializable]
    public class LevelOrder
    {
    
        [Header("每组关卡名称")]
        public string[] LevelNames;
    }
    
    public class ChangLevelsHasMain : MonoBehaviour
    {
        [Header("所有关卡列表")]
        public LevelOrder[] levelOrder;
        private static int index;
        private int totalLevels = 0;
        private int levelOrderLength;
    
        void Start ()
        {
            for (int i = 0; i < levelOrder.Length; i++)
            {
                totalLevels += levelOrder[i].LevelNames.Length;
            }
    
            if (totalLevels != SceneManager.sceneCountInBuildSettings)
            {
    
            }
            levelOrderLength = levelOrder.Length;
        }
    
        // Update is called once per frame
        void Update ()
        {
            if (Input.GetKeyDown(KeyCode.Space))
            {
                bool isOk = LoadNextLevels();
                if (isOk)
                {
                    InvokeRepeating("UnloadLastLevel", 2.0f, 5);
                }
            }
        }
    
        bool LoadNextLevels()
        {
            bool bResult = true;
            //index = index % levelOrderLength;
            if (index < 0 || index >= levelOrderLength)
            {
                bResult = false;
                return bResult;
            }
    
            int LoadTimes = levelOrder[index].LevelNames.Length;
            for (int i = 0; i < LoadTimes; i++)
            {
                SceneManager.LoadSceneAsync(levelOrder[index].LevelNames[i], LoadSceneMode.Additive);
            }
            return bResult;
        }
    
        void UnloadLastLevel()
        {
            if (index == 0)
            {
                index++;
                CancelInvoke("UnloadLastLevel");
                return;
            }
            // 上一組的關卡
            int TmpLast = (index - 1) >= 0 ? (index - 1) : levelOrderLength - 1;
            int LoadTimes = levelOrder[index].LevelNames.Length;
            for (int i = 0; i < LoadTimes; i++)
            {
                Scene Tmp = SceneManager.GetSceneByName(levelOrder[index].LevelNames[i]);
                if (!Tmp.isLoaded)
                {
                    return;
                }
            }
    
            // 下一關卡全部加載完畢後,卸載之前關卡
            for (int i = 0; i < levelOrder[TmpLast].LevelNames.Length; i++)
            {
                SceneManager.UnloadScene(levelOrder[TmpLast].LevelNames[i]);
            }
            index++;
            CancelInvoke("UnloadLastLevel");
        }
    }

    就这样就可以了。
    代码主要是按组来加载关卡,然后按组来卸载。
    测试中,按下空格键来加载,每组关卡在一定时间后,(这里设置的5秒)自动卸载前一组关卡。这里主地图是不卸载的,会一直存在的。

    怎么设置的呢?首先需要在Build setting中中把所有要处理的关卡放进来。要不就会在加载过程中报错。
    如下图:
    这里写图片描述

    然后把代码挂在主地图的任意对象对象上就可以了.
    这里写图片描述

    展开全文
  • Unity3d加载场景方法

    2017-12-18 14:09:14
    //通过场景索引号异步加载场景,异步加载即后台加载,加载过程显示的依旧是原场景,也可以设置一个加载场景+进度条之类的过渡。 前提:按File - Build Settings -将需转换的场景全部放进去(原场景也要),看见场景名...

     Application.LoadLevelAsync(int index);

    //通过场景索引号异步加载场景,异步加载即后台加载,加载过程显示的依旧是原场景,也可以设置一个加载场景+进度条之类的过渡。

    前提:按File - Build Settings -将需转换的场景全部放进去(原场景也要),看见场景名最右边的数字就是场景索引号。

       Application.LoadLevel():同步加载 会销毁当前场景 适合加载小场景

       Application.LoadLevelAddictive();同步附加式加载 不会销毁当前场景 适合加载小场景

       Application.LoadLevelAddictiveAsync();异步附加式加载 不会销毁当前场景 适合加载大场景

    目前新版本有新出以下2个加载

    using UnityEngine.SceneManagement;

    SceneManager.LoadScene(场景索引号);同步加载

    SceneManager.LoadSceneAsync(场景索引号);  异步加载

    
    
    展开全文
  • 今天想和大家交流的是解析obj模型并将其加载到Unity3D场景中,虽然我们知道Unity3D是可以直接导入OBJ模型的,可是有时候我们并不能保证我们目标客户知道如何使用Unity3D的这套制作流程,可能对方最终提供给我们的...
    版权声明:本文由秦元培创作和发表,采用署名(BY)-非商业性使用(NC)-相同方式共享(SA)国际许可协议进行许可,转载请注明作者及出处,本文作者为秦元培,本文标题为解析OBJ模型并将其加载到Unity3D场景中,本文链接为http://qinyuanpei.com/2015/11/15/deep-learning-of-3d-model-file-format-of-obj/.

      各位朋友,大家好,欢迎大家关注我的博客,我是秦元培,我的博客地址是http://qinyuanpei.com。今天想和大家交流的是解析obj模型并将其加载到Unity3D场景中,虽然我们知道Unity3D是可以直接导入OBJ模型的,可是有时候我们并不能保证我们目标客户知道如何使用Unity3D的这套制作流程,可能对方最终提供给我们的就是一个模型文件而已,所以这个在这里做这个尝试想想还是蛮有趣的呢,既然如此,我们就选择在所有3D模型格式中最为简单的OBJ模型来一起探讨这个问题吧!

    关于OBJ模型

      OBJ格式是一种3D模型文件格式,是由Alias|Wavefront公司为3D建模和动画软件 “Advanced Visualizer”开发的一种标准,适合用于3D软件模型之间的互相转换。和FBX、Max这种内部私有格式不同,OBJ模型文件是一种文本文件,我们可以直接使用记事本等软件打开进行编辑和查看,因此我们这里选择OBJ模型主要是基于它开放和标准这两个特点。需要说明的是,OBJ文件是一种3D模型文件,它主要支持多边形模型(三个点以上的面)。OBJ模型支持法线和贴图坐标,可是因为它本身并不记录动画、材质特性、贴图路径、动力学及粒子等信息,所以我们在游戏开发中基本看不到这种模型格式的,所以我们这里做下简单研究就好。

    OBJ模型解读

      因为OBJ模型文件是一个文本文件,所以我们可以使用记事本等软件打开它来对它的文件结构进行下了解。首先OBJ文件没有头文件,如果你曾经尝试解析过mp3文件的ID3v1/ID3v2标签就应该知道它是根据mp3文件的开头或者末尾的若干字节来判断这些标签信息的,而在OBJ文件中是没有类似这样的头文件的。OBJ文件是由一行行由关键字、空格和文本字符组成的文本文件,通过关键字我们就可以知道这一行的文本表示的是什么数据。例如:

    # Blender v2.76 (sub 0) OBJ File: ''

    #关键字表示一个注释行,通过这个注释信息我们可以知道这个OBJ模型是由Blender2.76版本导出的。再比如:

    mtllib liumengli.mtl

    mtllib关键字则表示当前模型对应的材质库(.mtl)文件名称,每个OBJ模型文件都会有这样一个对应和它同名的.mtl文件,在这个文件中记录了材质相关的信息,稍后我们说到材质的时候会详细说说这个文件的格式,因为它和OBJ文件一样是一个文件文件。再比如:

    usemtl Material__33

    usemtl关键字则表示从当前行到下一个usemtl关键字所在行间的全部网格结构都使用其对应的材质,通过这个材质名称我们可以在.obj文件对应的.mtl文件中找到它的材质定义,这个我们在讲到材质部分的时候会详细说。

      好了,目前我们要做的工作室解析.obj文件然后创建网格进而可以使其显示在Unity3D场景中,在这里我们要重点关注的关键字有:
    * v即Vertex,表示一个顶点的局部坐标系中的坐标,通常有三个分量,因为这里讨论的是三角面。例如:

    v  1.5202 14.9252 -1.1004
    • vn即Vertex Normal,表示法线,注意到这些向量都是单位向量,所以我们可以认为三维软件在导出模型的时候已经做好了相关的标准化工作。
    vn 0.8361 -0.0976 0.5399
    • vt即Vertex Texture,表示纹理坐标,就是我们熟悉的UV坐标啦,显然UV是个2D坐标,有两个分量。
    vt -0.5623 0.4822 1.0000
    • f即face,这是一个真正描述面的关键字,通常它后面有三个索引结构,每个索引结构由顶点索引、法线索引和纹理坐标索引三部分构成。例如:
    f 256/303/637 257/304/638 258/305/639 

    以上这些关键字对我们解析.obj文件来说已经完全足够了,如果大家想对这些细节有更为深入的了解,可以参考这里这里

    OBJ模型的读取

      OBJ模型的读取涉及到网格部分的读取和材质部分的读取两个部分,其中网格部分的读取难点在于当模型存在多个材质的时候,需要将模型分为若干个子物体,然后分别为这些子物体添加材质。可是不幸的是到目前为止,博主并没有找到一种行之有效的方法来对这些网格进行分类,所以这里我们假定模型是一个整体且共享同一种材质和一张贴图。如果大家找到了更好的解决方案,请记得告诉我,再次谢谢大家!

    网格部分

      在网格读取这部分,因为我们已经假设所有的面构成一个物体,因此我们可以先将OBJ文件内的文本按照换行符来进行分割,然后再按照关键字去判断每一行的数据类型并进行相应的处理就可以了。读取OBJ模型的基本流程是:
    * 读取顶点、法线、UV以及三角面
    * 将三角面合并为四边面
    * 根据索引重新计算顶点、法线、UV数组

    读取顶点、法线、UV以及三角面

      首先我们来看第一步的代码实现:

    /// <summary>
    /// 从一个文本化后的.obj文件中加载模型
    /// </summary>
    public ObjMesh LoadFromObj(string objText)
    {
        if(objText.Length <= 0) 
            return null;
        //v这一行前面是两个空格后面是一个空格
        objText=objText.Replace("  ", " ");
    
        //将文本化后的obj文件内容按行分割
        string[] allLines = objText.Split('\n');
        foreach(string line in allLines)
        {
            //将每一行按空格分割
            string[] chars = line.Split(' ');
            //根据第一个字符来判断数据的类型
            switch(chars[0])
            {
                 case "v":
                 //处理顶点
                 this.vertexArrayList.Add(new Vector3(
                    ConvertToFloat(chars[1]), 
                    ConvertToFloat(chars[2]),
                    ConvertToFloat(chars[3]))
                    );
                    break;
                 case "vn":
                 //处理法线
                 this.normalArrayList.Add(new Vector3(
                    ConvertToFloat(chars[1]), 
                    ConvertToFloat(chars[2]), 
                    ConvertToFloat(chars[3]))
                    );
                  break;
                  case "vt":
                  //处理UV
                  this.uvArrayList.Add(new Vector3(
                    ConvertToFloat(chars[1]),
                    ConvertToFloat(chars[2]))
                    );
                    break;
                  case "f":
                  //处理面
                  GetTriangleList(chars);
                    break;
           }
     }

    在这段代码中,我们首先将文本化的.obj文件按照换行符分割成字符串数组allLines,然后再对每一行按照空格分隔成字符串数组chars,这样我们就可以通过该数组的第一个元素chars[0]来判断当前行中的数据类型。这样我们将每一行的文本读取完后,所有的数据都被存储到了其相对应的列表中。其中,vertexArrayList存储顶点信息、normalArrayList存储法线信息、uvArrayList存储UV坐标。至此,我们完成第一部分中的顶点、法线和UV的读取。

      这里可以注意到我们在开始对文本化的.obj文件的内容有1次替换操作,这是因为在3dsMax中导出的.obj文件关键字v这一行中v后面的第一处空格位置是有2个空格,而我们在处理的时候是按照空格来分割每一行的内容的,这样chars[1]就会变成一个空字符串,显然这不符合我们的初衷,所以这里就需要对字符串进行这样一个操作,希望大家在解析的过程中注意,好吧,我承认我想吐槽3dsMax了,我不明白同一家公司的3dsMax和Maya为什么不能互相转换,我不明白3dsMax导出.obj文件的时候要做这样奇葩的设定,我更不明白为什么有开源、免费、轻巧的Blender都不去用非要每次都去安装容量动辄上G的盗版软件和不知道会不会变成下一个GhostXXXX的注册机,我更加不能容忍的是封闭的FBX格式和用起来就如同自虐的FBX SDK。

      好了,吐槽结束,我们接下来来看看三角面是如何读取的。三角面的读取定义在GetTriangleList()方法中,因此三角面的读取实际上首先需要将每一行文本按照空格进行分割,然后再将每一个元素按照/分割,这样就可以依次得到顶点索引、法线索引和UV索引。在某些情况下法线索引可能不存在,所以在处理的过程中需要对其进行处理。

    /// <summary>
    /// 获取面列表.
    /// </summary>
    /// <param name="chars">Chars.</param>
    private void GetTriangleList(string[] chars)
    {
       List<Vector3> indexVectorList = new List<Vector3>();
       List<Vector3> triangleList = new List<Vector3>();
    
       for(int i = 1; i < chars.Length;++i )
       {
           //将每一行按照空格分割后从第一个元素开始
           //按照/继续分割可依次获得顶点索引、法线索引和UV索引
           string[] indexs = chars[i].Split('/');
           Vector3 indexVector = new Vector3(0, 0);
           //顶点索引
           indexVector.x = ConvertToInt(indexs[0]);
           //法线索引
           if(indexs.Length > 1){
              if(indexs[1] != "")
                 indexVector.y = ConvertToInt(indexs[1]);
           }
           //UV索引
           if(indexs.Length > 2){
              if(indexs[2] != "")
                  indexVector.z = ConvertToInt(indexs[2]);
           }
    
           //将索引向量加入列表中
           indexVectorList.Add(indexVector);
       }
    
       //这里需要研究研究
       for(int j = 1; j < indexVectorList.Count - 1; ++j)
       {
           //按照0,1,2这样的方式来组成面
           triangleList.Add(indexVectorList[0]);
           triangleList.Add(indexVectorList[j]);
           triangleList.Add(indexVectorList[j + 1]);
       }
    
       //添加到索引列表
       foreach(Vector3 item in triangleList)
       {
          faceVertexNormalUV.Add(item);
       }
    }

    在这里,我们首先使用一个索引向量列表indexVectorList存储每一行的索引向量。这里的索引向量是指由顶点索引、法线索引和UV索引分别构成Vector3的三个分量,这样做的好处是我们可以节省重新去定义数据机构的时间。好了,我们把所有的索引向量读取完后,按照0、1、2这样的方式组成三角面,这里可能是.obj文件本身定义的一种方式,我们暂且按照这样的方式来处理。最后,全部的三角面会被读取到faceVertexNormalUV列表中,它表示的是每个三角面的顶点、法线和UV的索引向量,是一个List类型的变量。

    将三角面合并为四边面

      现在我们读取到的是三角面,接下来我们需要将它们合并成四边面,合并的原理是判断它们是否在同一个面上。如果两个点的顶点索引相同则表明它们是同一个点,如果两个点的法线索引相同则表明它们在同一个面上。好了,我们来看定义的一个方法Combine():

    /// <summary>
    /// 合并三角面
    /// </summary>
    private void Combine()
    {
       //使用一个字典来存储要合并的索引信息
       Dictionary<int, ArrayList> toCambineList = new Dictionary<int,ArrayList>();
       for(int i = 0; i < faceVertexNormalUV.Count; i++)
       {
           if(faceVertexNormalUV[i] != Vector3.zero)
           {
               //相同索引的列表
               ArrayList SameIndexList = new ArrayList();
               SameIndexList.Add(i);
               for(int j = 0; j < faceVertexNormalUV.Count; j++)
               {
                   if(faceVertexNormalUV[j]!=Vector3.zero)
                   {
                      if(i != j)
                      {
                         //如果顶点索引和法线索引相同,说明它们在一个面上
                         Vector3 iTemp = (Vector3)faceVertexNormalUV[i];
                         Vector3 jTemp = (Vector3)faceVertexNormalUV[j];
                         if(iTemp.x == jTemp.x && iTemp.y == jTemp.y)
                         {
                            //将索引相同索引列表然后将其重置为零向量
                            //PS:这是个危险的地方,如果某个索引信息为Vector3.Zero
                            //就会被忽略过去,可是貌似到目前为止没有发现为Vector3.Zero的情况
                            SameIndexList.Add(j);
                            faceVertexNormalUV[j]=Vector3.zero;
                         }
                       }
                   }
               }
               //用一个索引来作为字典的键名,这样它可以代替对应列表内所有索引
               toCambineList.Add(i, SameIndexList);
           }
        }
     }

    在这里我们使用了一个字典来存储合并后的四边面,这个字典的键名为这一组三角面共同的索引,因为大家都是用同一个索引,因此它可以代替那些被合并的三角面的索引,这样合并以后的四边面列表中元素的个数就是实际的网格中的面数个数,因为如果采用三角面的话,这个面数会比现在的面数还要多,这意味着这样会带来更多的性能上的消耗。这里可能不大好理解,大家可以将博主这里的表达方式换成自己能够理解的方式。佛曰不可说,遇到这种博主自己都说不明白的地方,博主就只能请大家多多担待了。好了,接下来要做的是重新计算顶点、法线和UV数组。可能大家会比较疑惑,这部分内容我们在第一步不是就已经读取出来了嘛,怎么这里又要重新计算了呢?哈哈,且听我慢慢道来!

    根据索引重新计算顶点、法线、UV数组

      虽然我们在第一步就读取到了这些坐标数据,可是当我们合并三角面以后,就会出现大量的无用的点,为什么无用呢,因为它被合并到四边面里了,这样我们原来读取的这些坐标数据就变得不适用了。那怎么办呢?在第三步中我们合并四边面的时候已经用一个字典保存了合并后的索引信息,这就相当于我们已经知道哪些是合并前的索引,哪些是合并后的索引,这个时候我们只要根据索引重新为数组赋值即可:

    //初始化各个数组
    this.VertexArray = new Vector3[toCambineList.Count];
    this.UVArray = new Vector2[toCambineList.Count];
    this.NormalArray = new Vector3[toCambineList.Count];
    this.TriangleArray = new int[faceVertexNormalUV.Count];
    
    //定义遍历字典的计数器
    int count = 0;
    
    //遍历词典
    foreach(KeyValuePair<int,ArrayList> IndexTtem in toCambineList)
    {
        //根据索引给面数组赋值
        foreach(int item in IndexTtem.Value)
        {
            TriangleArray[item] = count;
        }
    
        //当前的顶点、UV、法线索引信息
        Vector3 VectorTemp = (Vector3)faceVertexNormalUV[IndexTtem.Key];
    
        //给顶点数组赋值
        VertexArray[count] = (Vector3)vertexArrayList[(int)VectorTemp.x - 1];
    
        //给UV数组赋值
        if(uvArrayList.Count > 0)
        {
           Vector3 tVec =(Vector3)uvArrayList[(int)VectorTemp.y - 1];
           UVArray[count] = new Vector2(tVec.x, tVec.y);
        }
    
                //给法线数组赋值
                if(normalArrayList.Count > 0)
                {
                    NormalArray[count] = (Vector3)normalArrayList[(int)VectorTemp.z - 1];
                }
    
                count++;
            }

    这样我们就读取到了合并后的坐标信息,通过顶点、法线、UV、面等信息我们现在就可以生成网格了。这部分我们暂且不着急,因为这基本上属于最后整合到Unity3D中步骤了。好了,为了方便大家理解,我已经完整的项目上传到Github,大家可以通过这里了解完整的项目。

    材质部分

      材质这块儿的解析主要集中在.mtl文件中,和.obj文件类似,它同样是一个文本文件、同样采用关键字、空格、文本字符这样的结构来表示数据,因此我们可以借鉴.obj文件的读取。例如:

    newmtl Material

    newmtl关键字表示从当前行到下一个newmtl关键字所在行间都表示该关键字所对应的材质,这里的Material即表示材质的名称,它和.obj文件中的usemtl关键字相对应,因此我们给模型添加材质的过程本质上是从.obj文件中读取网格,然后找到其对应的材质名称,然后在.mtl文件中找到对应的材质定义,并根据定义来生成材质。目前已知的关键字有:

    Ka 0.5880 0.5880 0.5880

    Ka关键字表示环境反射的RGB数值。

    Kd 0.640000 0.640000 0.640000

    Kd关键字表示漫反射的RGB数值。

    Ks 0.500000 0.500000 0.500000

    Ks关键字表示镜面反射的RGB数值。

    map_Ka E:\学习资料\Unity3D技术\Unity3D素材\柳梦璃\Texture\1df2eaa0.dds

    map_Ka关键字表示环境反射的纹理贴图,注意到这里使用的是绝对路径,显然我们在读取模型的时候不会将贴图放在这样一个固定的路径,因此我们这里初步的想法读取贴图的文件名而非贴图的完整路径,考虑到我们在Unity3D中一般使用PNG格式的贴图,因此这里需要对路径进行处理。

    map_Kd E:\学习资料\Unity3D技术\Unity3D素材\柳梦璃\Texture\1df2eaa0.dds

    map_Kd关键字表示漫反射的纹理贴图,和环境反射的纹理贴图是类似地,这里就不再说了。此外还有其它的关键字,初步可以推断出的结论是它和3dsMax中材质编辑器里的定义特别地相似,感兴趣的朋友可以进一步去研究。可是现在就有一个新的问题了,怎样将这些参数和Unity3D里的材质关联起来呢?我们知道Unity3D里的材质是是由着色器和贴图两部分组成的,博主对Shader并不是很熟悉,因此这里确实有些说不清楚了。博主感觉对OBJ文件来说,其实使用Diffuse就完全足够了,所以这里对材质部分的研究我们点到为止,不打算做代码上的实现。如果不考虑这些参数的话,我们要做的就是通过WWW或者Resource将贴图加载进来,然后赋值给我们通过代码创建的Shader即可。而对于.obj文件来说,无论是通过Resource、WWW或者是IO流,只要我们拿到了这个文件中的内容就可以使用本文中的方式加载进来,因为我们假定的是读取只有一种材质的模型。有朋友可能要问,那如果有多种材质怎么办呢?答案是在.mtl问价中获取到所有贴图的名称,然后再到程序指定的路径去读取贴图,分别为其创建不同的材质,可是这些材质要怎么附加到它对应的物体上呢?这个目前博主没有找到解决的方法,所以此事暂且作罢吧!

    在Unity3D中加载obj模型

      下面我们以一个简单的例子来展示今天研究的成果,我们将从.obj文件中读取出一个简单的模型并将其加载到场景中。好了,我们一起来看代码:

    if(!File.Exists("D:\\cube.obj"))
        Debug.Log("请确认obj模型文件是否存在!");
    
    StreamReader reader = new StreamReader("D:\\cube.obj",Encoding.Default);
    string content = reader.ReadToEnd();
    reader.Close();
    
    ObjMesh objInstace = new ObjMesh();
    objInstace = objInstace.LoadFromObj(content);
    
    Mesh mesh = new Mesh();
    mesh.vertices = objInstace.VertexArray;
    mesh.triangles = objInstace.TriangleArray;
    if(objInstace.UVArray.Length > 0)
        mesh.uv = objInstace.UVArray;
    if(objInstace.NormalArray.Length>0)
        mesh.normals = objInstace.NormalArray;
    mesh.RecalculateBounds();
    
    GameObject go = new GameObject();
    MeshFilter meshFilter = go.AddComponent<MeshFilter>();
    meshFilter.mesh = mesh;
    
    MeshRenderer meshRenderer = go.AddComponent<MeshRenderer>();

    这里没有处理材质,所以读取出来就是这个样子的,哈哈!

    最终效果,这是一个悲伤的故事

    材质大家可以尝试用代码去创建一个材质,然后在给一张贴图,这个玩玩就好,哈哈!好了,今天的内容就是这样子了,希望大家喜欢,为了写这篇文章我都怀疑我是不是有拖延症啊!

    展开全文
  • 最近开始研究Unity3D游戏场景优化,每次提及游戏优化这个话题的时候,我的脑海中都会浮现出《仙剑奇侠传六》这个让四路泰坦都光荣陨落的神奇游戏,作为一个使用Unity3D引擎进行游戏开发的仙剑玩家,我曾经天真的以为...

    喜欢我的博客请记住我的名字:秦元培,我的博客地址是:http://qinyuanpei.com
    转载请注明出处,本文作者:秦元培, 本文出处:http://blog.csdn.net/qinyuanpei/article/details/48262583

      各位朋友大家好,我是秦元培,欢迎大家关注我的博客,我的博客地址是http://qinyuanpei.com

      最近开始研究Unity3D游戏场景优化,每次提及游戏优化这个话题的时候,我的脑海中都会浮现出《仙剑奇侠传六》这个让四路泰坦都光荣陨落的神奇游戏,作为一个使用Unity3D引擎进行游戏开发的仙剑玩家,我曾经天真的以为,这款使用Unity3D引擎打造的仙剑二十周年献礼之作,会让我对《仙剑奇侠传》这个系列游戏的未来充满更多期待,然而当游戏真正呈现在我眼前的时候,我感受到了在历代仙剑游戏中从未有过的尴尬和失望,我尴尬的是Unity3D这样一个比较强大的游戏引擎硬生生地被北软玩成了这个鬼样子,我失望的是这部游戏除了剧情和跳跳乐以外并没有什么让人看到希望的东西。

    仙剑奇侠传六

    不到20帧的优化

      我知道我这样说会有一堆仙剑玩家指责我说,仙剑本来就是玩剧情的嘛,所以只要剧情好其它的都可以原谅啦。然而我们每一个人都清楚《仙剑奇侠传》是一个RPG游戏,它不是每隔三年出一次新番的GAL动漫、不是每隔三年更新一次的言情小说、更不是每隔三年播放一次的偶像电影。两年前的今天我可以耐着性子玩通关《仙剑奇侠传五》,但是这一次我真的玩不下去了。当一个游戏因为优化问题而获得《仙剑奇侠传六:泰坦陨落》称号的时候,作为一个玩家我真的不想再为这个游戏洗白什么,虽然我曾经深爱过这个游戏。所以言归正传,作为一个程序员,我们还是来做点程序员该做的事情,那么我们今天说什么呢,我们来说说Unity3D里的批处理

    一、什么是批处理?

      我们知道Unity3D在屏幕上绘制一个图形本质上调用OpneGL或者DirectX这样的API,因此在这个过程中会产生一定程度上的性能消耗。DrawCall是OpenGL中描述绘制次数的一个量,例如一个基本的OpenGL绘制流程是设置颜色->绘图方式->顶点坐标->绘制->结束,在绘制的过程中每帧都会重复这个过程,这就是一次DrawCall,所以当游戏中的绘制过程变得复杂的时候,就会带来DrawCall的急剧增加,进而带来游戏的性能问题,反映到游戏表现上就变成了优化问题。那么在Unity3D中采取了什么样的措施来降低DrawCall呢?这就是我们今天要说的批处理,换句话说Unity3D使用了批处理来达到降低DrawCall的目的,批处理希望通过对物体网格的重组来获得更高的绘制效率,试想以下如果将多个物体合并为一个物体,那么在绘制的时候只需要绘制一次就够了,因此从这个角度上来讲这样做肯定是可以降低DrawCall的,更深刻的一种理解是这里体现了一种资源循环调用的思想,接触过Android开发的朋友们一定知道ListView控件可以对其元素进行“缓存”从而提高效率,因为我们可以发现其实ListView是对列表项进行某种程度上的“复用”从而提高了效率,在Unity3D这里同样遵循了这个原理。在Unity3D中进行批处理的一个前提是相同材质的物体可以被合并,如果这些物体使用不同的材质,那么当我们把这些材质对应的纹理打成“图集”以后可以对其进行合并,并且在合并的时候应该是用Renderer.sharedMaterial 而非 Renderer.material以保证材质是可以共享的。关于DrawCall的相关细节大家从这里来了解,博主并未对图形学领域有过深入的研究,因此就不在这里班门弄斧了啊,哈哈!

    二、Unity3D中批处理的两种方式

      在Unity3D中有静态批处理和动态批处理两种方式,下面我们就来分别说说这两种不同的批处理方式!

    静态批处理

      静态批处理其实大家都是知道的。为什么这样说呢?因为我们在使用Unity3D的过程中无形中培养了这样一个习惯,那就是将场景中相对来说“静态”的物体都勾选Static选项,这在Unity3D中称为Static GameObjects,并且因为这一特性和LightmappingNavigationOff-meshLinksReflectionProbeOccluder and Occludee等内容均有着密切的联系,因此说静态批处理大家都是知道的其实一点都为过,和场景优化相关的内容博主会在后续的博客中涉及,希望大家能及时关注我的博客更新。静态批处理允许游戏引擎尽可能多的去降低绘制任意大小的物体所产生的DrawCall,它会占用更多的内存资源和更少的CPU资源,因为它需要额外的内存资源来存储合并后的几何结构,如果在静态批处理之前,如果有几个对象共享相同的几何结构,那么将为每个对象创建一个几何图形,无论是在编辑器还是在运行时。这看起来是个艰难的选择,你需要在内存性能和渲染性能间做出最为正确的选择。在内部,静态批处理是通过将静态对象转换为世界空间,并为它们构建一个大的顶点+索引缓冲区。然后,在同一批中,一系列的“便宜”画调用,一系列的“便宜”,几乎没有任何状态变化之间的。所以在技术上它并不保存“三维的调用”,但它可以节省它们之间的状态变化(这是昂贵的部分)。使用静态批处理非常简单啦,只要勾选物体的Static选项即可!

    动态批处理

      相对静态批处理而言,动态批处理的要求更为严格一些,它要求批处理的动态对象具有一定的顶点,所以动态批处理只适用于包含小于900个顶点属性的网格。如果你的着色器使用顶点位置,法线和单光,然后你可以批处理300个顶点的动态对象;而如果你的着色器使用顶点位置,法线,uv0,UV1和切线,那么只能处理180个顶点的动态对象。接下来最为重要的一点,如果动态对象使用的是不同的材质,那么即使进行了动态批处理从效率上来讲并不会有太大的提升。如果动态对象采用的是多维子材质,那么批处理是无效的。如果动态对象接收实时光影,同样批处理是无效的。下面展示的是一个将多个物体合并为一个物体的脚本示例:

    [MenuItem("ModelTools/将多个物体合并为一个物体")]
        static void CombineMeshs2()
        {
            //在编辑器下选中的所有物体
            object[] objs=Selection.gameObjects;
            if(objs.Length<=0) return;
    
            //网格信息数组
            MeshFilter[] meshFilters =new MeshFilter[objs.Length];
            //渲染器数组
            MeshRenderer[] meshRenderers = new MeshRenderer[objs.Length];
            //合并实例数组
            CombineInstance[] combines = new CombineInstance[objs.Length];
            //材质数组
            Material[] mats = new Material[objs.Length];
    
            for (int i = 0; i < objs.Length; i++)
            {
                //获取网格信息
                meshFilters[i]=((GameObject)objs[i]).GetComponent<MeshFilter>();
                //获取渲染器
                meshRenderers[i]=((GameObject)objs[i]).GetComponent<MeshRenderer>();
    
                //获取材质
                mats[i] = meshRenderers[i].sharedMaterial;   
                //合并实例           
                combines[i].mesh = meshFilters[i].sharedMesh;
                combines[i].transform = meshFilters[i].transform.localToWorldMatrix;
            }
    
            //创建新物体
            GameObject go = new GameObject();
            go.name = "CombinedMesh_" + ((GameObject)objs[0]).name;
    
            //设置网格信息
            MeshFilter filter = go.transform.GetComponent<MeshFilter>();
            if (filter == null)
                filter = go.AddComponent<MeshFilter>();
           filter.sharedMesh = new Mesh();
           filter.sharedMesh.CombineMeshes(combines,false);
    
           //设置渲染器
           MeshRenderer render = go.transform.GetComponent<MeshRenderer>();
           if (render == null)
               render = go.AddComponent<MeshRenderer>();
            //设置材质
            render.sharedMaterials = mats;
        }

      这段脚本的核心是CombineMeshes()方法,该方法有三个参数,第一个参数是合并实例的数组,第二个参数是是否对子物体的网格进行合并,第三个参数是是否共享材质,如果希望物体共享材质则第三个参数为true,否则为false。在我测试的过程中发现,如果选择了对子物体的网格进行合并,那么每个子物体都不能再使用单独的材质,默认会以第一个材质作为合并后物体的材质,下面演示的是合并前的多个物体和合并后的一个物体的对比:

    合并前

    合并后

    三、批处理效率分析

      那么批处理对游戏效率提升究竟有怎样的作用呢?我们来看下面几组测试对比:

      1、三个不同的物体使用同一种材质,不做静态批处理,不做动态批处理:DrawCall为4、面数为584、顶点数为641

      2、三个不同的物体使用同一种材质,只做静态批处理,不做动态批处理:DrawCall为2、面数为584、顶点数为641

      3、三个不同的物体使用不同的材质,不做静态批处理,不做动态批处理:DrawCall为4、面数为584、顶点数为641

      4、三个不同的物体使用不同的材质,只做静态批处理,不做动态批处理:DrawCall为4、面数为584、顶点数为641

      5、三个不同的物体使用不同的材质,不做静态批处理,只做动态批处理:DrawCall为4、面数为584、顶点数为641

      6、三个不同的物体使用不同的材质,做静态批处理,做动态批处理:DrawCall为4、面数为584、顶点数为641

      7、三个不同的物体使用同一种材质,不做静态批处理,只做动态批处理::DrawCall为4、面数为584、顶点数为641

      大家可以注意到各组测试结果中,只有第二组的DrawCall降低,这说明只有当不同的物体使用同一种材质时通过批处理可以从一定程度上降低DrawCall,即我们在文章开始提到的尽可能地保证材质共享。昨天下午兴冲冲地将游戏场景里的某些物体进行了动态批处理,但是实际测试的过程中发现DrawCall非常地不稳定,但是在场景中的某些地方DrawCall却可以降得非常低,如果静态批处理和动态批处理都不能对场景产生较好的优化,那么Unity3D游戏场景的优化究竟要从哪里抓起呢?我觉得这是我们每一个人都该用心去探索的地方,毕竟游戏做出来首先要保证能让玩家流畅的玩下去吧,一味的强调引擎、强调画面,却时常忽略引擎使用者的主观能动性,希望把一切问题都交给引擎去解决,这样的思路是错误而落后的,仙剑六的问题完全是用不用心的问题,我常常看到有人在公开场合说仙剑以后要换虚幻三,其实按照北软现在这样的状态,给他们一个虚幻四也不过是然并卵。我在知乎上看到了号称15岁就开发次时代游戏的高中生妹子,做出个能称为DEMO的游戏就觉得自己可以搞引擎了,更有甚者随便用DirectX或者OpenGL封装若干函数就敢说自己会做游戏引擎了,呵呵,你确定你的游戏能在别人的电脑或者手机上运行起来吗?优化的重要性可见一斑。

    四、小结

      好了,通过今天这篇文章,我们可以整理出以下观点:
      1、如果不同的物体间共享材质,则可以直接通过静态批处理降低DrawCall
      2、动态批处理并不能降低DrawCall、面数和顶点数(我不会告诉你我昨天傻呵呵地合并了好多场景中的模型,结果面数和顶点数并没有降下来,23333)
      3、不管是静态批处理还是动态批处理都会影响Culiing,这同样是涉及到场景优化的一个概念,好吧,为了让场景的DrawCall降下来我最近可能要研究好多涉及的优化的内容……
      那么今天的内容就是这样子了,希望对大家学习Unity3D有所帮助,欢迎大家和我交流这些问题来相互促进,毕竟这才是我写博客最初的目的嘛,哈哈!

    相关链接

    喜欢我的博客请记住我的名字:秦元培,我的博客地址是:http://qinyuanpei.com
    转载请注明出处,本文作者:秦元培, 本文出处:http://blog.csdn.net/qinyuanpei/article/details/48262583

    展开全文
  • 今天想和大家交流的是解析obj模型并将其加载到Unity3D场景中,虽然我们知道Unity3D是可以直接导入OBJ模型的,可是有时候我们并不能保证我们目标客户知道如何使用Unity3D的这套制作流程,可能对方最终提供给我们的...
  • 所谓无缝场景切换,无非是涉及到场景或物体的预读,复杂点涉及下场景卸载,在复杂点涉及到场景二维数组,再在复杂点涉及数据结构用二叉树实现场景资源有序的读取与卸载。当然,复杂的我以后慢慢再说,比较懒,先写最...
  • 本文介绍了Unity3D界面及游戏对象基本操作。
  • call),然后这个包传递到3D部分在屏幕上呈现出来。这就和你希望你的亲友收到准备好的圣诞礼物需要包装好然后穿过城市准时放在他应该出现的地方一样没什么不同。你的CPU来完成包装和传递他们的活,同时会消耗很多的...
  • 学习Unity脚本推荐:Unity3D官网索引 首先一点,你要确保有一个想要跳转的目标场景。在本案例中,目标场景是ending。 第一步:创建一个碰撞体,放置在需要的触发场景跳转的位置 第二步:删除...
  • 在加载场景使用如下代码,前提是有一个slider作为进度的提示using UnityEngine; using System.Collections; using UnityEngine.SceneManagement; using System; using UnityEngine.UI; public class Loading : ...
  • Unity跳转场景传递数据 学习记录 跳转场景时,保证物体不被摧毁,可以保存场景数据内容 using UnityEngine; using System.Collections; using UnityEngine.SceneManagement; public class DontDestory : SingletonM&...
  • 进入场景S1,在左上角的File里选择Build Settings,然后选择右下角的add open scenes,之后场景被自动添加,3是场景索引。 进入S2,执行相同操作。 再次回到S1,创建一个UI BUTTON按钮。 创建一个脚本 编辑...
  • 今天要和大家分享的是基于Unity3D开发2D游戏,博主一直钟爱于国产武侠RPG,这个我在开始写Unity3D游戏开发系列文章的时候就已经说过了,所以我们今天要做的就是利用Unity3D来实现在2D游戏中人物的走动控制。...
  • Unity5.4版本及以上, using UnityEngine.SceneManagement; void Start () { //获取编号 int index = SceneManager.GetActiveScene().buildIndex; //获取名称 string name = SceneManager.G...
  • 这是一个比较综合的项目,希望对大家学习Unity3D有所帮助,我会在文章最后给出项目代码。作为一个游戏而言,游戏策划十分重要,所以在开始今天的文章之前,我们先来了解下这个项目的策划。我们的玩家是一个飞机,
  • 前段时间,有几个虚拟仿真公司跟我请教关于大地形的加载优化问题,它们使用的引擎都是自己研发的,引擎对于开发者来说,大同小异,它们的基本构造是一样的,关键是在于解决问题的方法,正是基于这个前提写了这个课程...
  • Unity3D 渲染路径

    2017-06-14 18:26:28
    不同的渲染路径有不同的特点和性能特点,主要影响灯光和阴影如果图形卡不能处理选定的渲染路径,Unity将自动使用一个较低保真度的设置。因此,在GPU上不能处理延迟照明(Deferred Lighting),将使用正向渲染...
  • 在游戏中每一个被展示的独立的部分都被放在了一个特别的包中,我们称之为“描绘指令”(draw call),然后这个包传递到3D部分在屏幕上呈现出来。这就和你希望你的亲友收到准备好的圣诞礼物需要包装好然后穿过城市...
  • Unity3D-人物角色选择

    2017-10-18 20:22:51
    Unity3D-人物角色选择今天没什么事,就做了一下人物角色选择界面,和大家分享一下,如果写的不好,请多多包涵。在网上参考以下,觉得这个界面挺适合的,所以直接拿来用了。如图所示:我的思路是这样的,首先要创建一...
1 2 3 4 5 ... 20
收藏数 2,296
精华内容 918
关键字:

unity3d场景索引设置