unity自动化流程之代码修改导航网格参数

Posted 程序员茶馆

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了unity自动化流程之代码修改导航网格参数相关的知识,希望对你有一定的参考价值。

一.应用背景

由于公司项目较大,为了妥善管理庞大繁杂的资源,我们采用了工作场景和运行时场景相分离的策略,将美术工作场景独立出来。美术人员只需要在自己的工作场景下操作,待场景搭建完善后,采用一套自动化的Build工作管线将美术工作场景资源导出为运行时所需的场景资源,以此来实现自动化的工作流程。该自动化Build管线也较为复杂,除了具体资源的Build逻辑外,还包括大量资源的依赖关系重建。在Build管线的开发过程中需要在我们的代码中大量调用并设定许多系统参数,而某些参数unity并未直接提供API来设定,因而在开发过程中需要频繁使用反射机制调用unity私有函数或者访问私有字段/属性,同时还会使用SerializedObjectSerializedProperty来修改无法直接通过API或反射来访问的字段/属性。本文将把导航网格设定相关的参数封装起来,以便在代码中直接调用并设定,而不是通过unity原有的参数设定面板来设定。

二.设计思想

1.unity原有的导航网格参数设定面板如下:

该面板主要由四部分组成,其中Agents和Areas栏中的参数独立于具体场景,即所有本工程的场景共用这些参数设定,其参数保存在ProjectSettings/NavMeshAreas.asset(如果打开是乱码,需要将工程的Serialization Mode设为Force Text,设置方法Edit->Project Settings...->Editor->Asset Serialization->Mode),如下图:

Bake栏的参数与具体场景相关,即每个场景的设定是独立的,其参数设定保存在具体的场景文件中(*.unity,如果打开为乱码,请将工程的Serialization Mode设为Force Text),如下图:

至于Object栏并未涉及到参数设定,因此这里不讨论它。

2.对Agents和Areas栏相关参数的设定,当然我们可以直接以文本形式打开并编辑NavMeshAreas.asset中的相应参数,但其前提是工程的Serialization Mode为Force Text。当条件不允许时(比如项目过大时),直接编辑NavMeshAreas.asset行不通,此时就需要使用SerializedObject和SerializedProperty的组合来来实现我们的需求。此时的关键是要获取NavMeshAreas.asset对应的Unity对象,可通过以下API获取:  Unsupported.GetSerializedAssetInterfaceSingleton("NavMeshProjectSettings");

3.同理对Bake栏的参数的设定,同样需要获取存储参数的对象,可通过以下API获取:NavMeshBuilder.navMeshSettingsObject;

4.获取承载参数设定的对象后,我们需要将这些对象序列化,以便提取我们需要的参数,可构建其对应的SerializedObject对象;

5.在获取SerializedObject对象后,我们通过SerializedObject.FindProperty(string propertyPath)传入参数路径便可以获取到所需的参数;

6.对于生成的导航网格需要重新关联到指定场景时,最便捷的办法也是采用SerializedObject和SerializedProperty的组合。

三.核心源码

1.获取NavMeshAreas.asset对象并提取Agent和Area的源码:

public class NavMeshProjectSettingsParamSetter

    private SerializedObject m_navMeshProjectSettingsSo;
    internal NavMeshProjectSettingsParamSetter()
    
        var serializedAssetInterfaceSingleton = Unsupported.GetSerializedAssetInterfaceSingleton("NavMeshProjectSettings");
        m_navMeshProjectSettingsSo = new SerializedObject(serializedAssetInterfaceSingleton);
    
    public NavigationAgent GetAgentByIndex(int index = 0)
    
        return new NavigationAgent(m_navMeshProjectSettingsSo, index);
    
    public int GetAgentsCount()
    
        var agents = m_navMeshProjectSettingsSo.FindProperty("m_Settings");
        return agents.arraySize;
    
    public string[] GetAgentNames()
    
        var agentnamesProp = m_navMeshProjectSettingsSo.FindProperty("m_SettingNames");
        var names = new string[agentnamesProp.arraySize];
        for (var index = 0; index < names.Length; index++)
        
            names[index] = agentnamesProp.GetArrayElementAtIndex(index).stringValue;
        
        return names;
    
    public NavigationArea GetNavigationArea(int index = 0)
    
        return new NavigationArea(m_navMeshProjectSettingsSo, index);
    

2.Agent参数设定源码:

public class NavigationAgent

    private SerializedObject m_navMeshProjectSettingsObject;
    private int agentIndex;
    private SerializedProperty m_agentName;
    private SerializedProperty m_agentRadius;
    private SerializedProperty m_agentHeight;
    private SerializedProperty m_agentStepHeight;
    private SerializedProperty m_agentMaxSlope;
    public NavigationAgent(SerializedObject navMeshProjectSettingsObject, int agentIndex)
    
        this.m_navMeshProjectSettingsObject = navMeshProjectSettingsObject;
        this.agentIndex = agentIndex;
        var _agents = m_navMeshProjectSettingsObject.FindProperty("m_Settings");
        var _settingNames = m_navMeshProjectSettingsObject.FindProperty("m_SettingNames");
        var agent = _agents.GetArrayElementAtIndex(agentIndex);
        m_agentName = _settingNames.GetArrayElementAtIndex(agentIndex);
        m_agentRadius = agent.FindPropertyRelative("agentRadius");
        m_agentHeight = agent.FindPropertyRelative("agentHeight");
        m_agentStepHeight = agent.FindPropertyRelative("agentClimb");
        m_agentMaxSlope = agent.FindPropertyRelative("agentSlope");
    
    public int AgentIndex
    
        get  return agentIndex; 
    
    public string AgentName
    
        get  return m_agentName.stringValue; 
        set
        
            m_agentName.stringValue = value;
            m_navMeshProjectSettingsObject.ApplyModifiedProperties();
        
    
    public float AgentRadius
    
        get  return m_agentRadius.floatValue; 
        set
        
            m_agentRadius.floatValue = value;
            m_navMeshProjectSettingsObject.ApplyModifiedProperties();
        
    
    public float AgentHeight
    
        get  return m_agentHeight.floatValue; 
        set
        
            m_agentHeight.floatValue = value;
            m_navMeshProjectSettingsObject.ApplyModifiedProperties();
        
    
    public float AgentStepHeight
    
        get  return m_agentStepHeight.floatValue; 
        set
        
            m_agentStepHeight.floatValue = value;
            m_navMeshProjectSettingsObject.ApplyModifiedProperties();
        
    
    public float AgentMaxSlope
    
        get  return m_agentMaxSlope.floatValue; 
        set
        
            m_agentMaxSlope.floatValue = value;
            m_navMeshProjectSettingsObject.ApplyModifiedProperties();
        
    

3.Area参数设定源码

public class NavigationArea

    private SerializedObject m_navMeshProjectSettingsObject;
    private int areaIndex;
    private SerializedProperty m_name;
    private SerializedProperty m_cost;
    internal NavigationArea(SerializedObject navMeshProjectSettingsObject, int areaIndex)
    
        this.m_navMeshProjectSettingsObject = navMeshProjectSettingsObject;
        this.areaIndex = areaIndex;
        var areas = m_navMeshProjectSettingsObject.FindProperty("areas");
        var size = areas.arraySize;
        if (areaIndex < 0 || areaIndex > areas.arraySize - 1)
        
            throw new System.ArgumentOutOfRangeException("越界");
        
        var area = areas.GetArrayElementAtIndex(areaIndex);
        m_name = area.FindPropertyRelative("name");
        m_cost = area.FindPropertyRelative("cost");
    
    public int AreaIndex
    
        get  return areaIndex; 
    
    public string Name
    
        get  return m_name.stringValue; 
        set
        
            m_name.stringValue = value;
            m_navMeshProjectSettingsObject.ApplyModifiedProperties();
        
    
    public float Cost
    
        get  return m_cost.floatValue; 
        set
        
            m_cost.floatValue = value;
            m_navMeshProjectSettingsObject.ApplyModifiedProperties();
        
    

4.Bake参数设定源码

public class NavMeshSettingsBakeParamSetter

    private SerializedObject m_navigationSettings;
    private SerializedProperty m_agentRadius;
    private SerializedProperty m_agentHeight;
    private SerializedProperty m_maxSlope;
    private SerializedProperty m_stepHeight;
    private SerializedProperty m_dropHeight;
    private SerializedProperty m_jumpDistance;
    private SerializedProperty m_manualVoxelSize;
    private SerializedProperty m_voxelSize;
    private SerializedProperty m_minRegionArea;
    private SerializedProperty m_heightMesh;
    internal NavMeshSettingsBakeParamSetter(Scene scene)
    
        SceneManager.SetActiveScene(scene);
        m_navigationSettings = new SerializedObject(NavMeshBuilder.navMeshSettingsObject);
        m_agentRadius = m_navigationSettings.FindProperty("m_BuildSettings.agentRadius");
        m_agentHeight = m_navigationSettings.FindProperty("m_BuildSettings.agentHeight");
        m_maxSlope = m_navigationSettings.FindProperty("m_BuildSettings.agentSlope");
        m_stepHeight = m_navigationSettings.FindProperty("m_BuildSettings.agentClimb");
        m_dropHeight = m_navigationSettings.FindProperty("m_BuildSettings.ledgeDropHeight");
        m_jumpDistance = m_navigationSettings.FindProperty("m_BuildSettings.maxJumpAcrossDistance");
        m_manualVoxelSize = m_navigationSettings.FindProperty("m_BuildSettings.manualCellSize");
        m_voxelSize = m_navigationSettings.FindProperty("m_BuildSettings.cellSize");
        m_minRegionArea = m_navigationSettings.FindProperty("m_BuildSettings.minRegionArea");
        m_heightMesh = m_navigationSettings.FindProperty("m_BuildSettings.accuratePlacement");
    
    public float AgentRadius
    
        get  return m_agentRadius.floatValue; 
        set
        
            m_agentRadius.floatValue = value;
            m_navigationSettings.ApplyModifiedProperties();
        
    
    public float AgentHeight
    
        get  return m_agentHeight.floatValue; 
        set
        
            m_agentHeight.floatValue = value;
            m_navigationSettings.ApplyModifiedProperties();
        
    
    public float MaxSlope
    
        get  return m_maxSlope.floatValue; 
        set
        
            m_maxSlope.floatValue = value;
            m_navigationSettings.ApplyModifiedProperties();
        
    
    public float StepHeight
    
        get  return m_stepHeight.floatValue; 
        set
        
            m_stepHeight.floatValue = value;
            m_navigationSettings.ApplyModifiedProperties();
        
    
    public float DropHeight
    
        get  return m_dropHeight.floatValue; 
        set
        
            m_dropHeight.floatValue = value;
            m_navigationSettings.ApplyModifiedProperties();
        
    
    public float JumpDistance
    
        get  return m_jumpDistance.floatValue; 
        set
        
            m_jumpDistance.floatValue = value;
            m_navigationSettings.ApplyModifiedProperties();
        
    
    public bool ManualVoxelSize
    
        get  return m_manualVoxelSize.boolValue; 
        set
        
            m_manualVoxelSize.boolValue = value;
            m_navigationSettings.ApplyModifiedProperties();
        
    
    public float VoxelSize
    
        get  return m_voxelSize.floatValue; 
        set
        
            m_voxelSize.floatValue = value;
            m_navigationSettings.ApplyModifiedProperties();
        
    
    public float MinRegionArea
    
        get  return m_minRegionArea.floatValue; 
        set
        
            m_minRegionArea.floatValue = value;
            m_navigationSettings.ApplyModifiedProperties();
        
    
    public bool HeightMesh
    
        get  return m_heightMesh.boolValue; 
        set
        
            m_heightMesh.boolValue = value;
            m_navigationSettings.ApplyModifiedProperties();
        
    

5.最后将上功能述封装起来,对外提供统一接口,同时这里也提供了获取与指定场景相关联导航网格的API,以及将指定导航网格关联到指定场景上去的API,这些都是无法直接通过调用Unity的API实现的。

public class NavigationHelper

    public static NavMeshProjectSettingsParamSetter GetNavMeshProjectSettings()
    
        return new NavMeshProjectSettingsParamSetter();
    
    public static NavMeshSettingsBakeParamSetter GetNavMeshSettings(Scene scene)
    
        return new NavMeshSettingsBakeParamSetter(scene);
    
    public static Object GetNavMeshData(Scene scene)
    
        SceneManager.SetActiveScene(scene);
        var naviMeshSettings = new SerializedObject(NavMeshBuilder.navMeshSettingsObject);
        var prop = naviMeshSettings.FindProperty("m_NavMeshData");
        return prop != null ? prop.objectReferenceValue : null;
    
    public static void SetNavMeshData(Scene scene, Object obj)
    
        SceneManager.SetActiveScene(scene);
        var naviMeshSettings = new SerializedObject(NavMeshBuilder.navMeshSettingsObject);
        var prop = naviMeshSettings.FindProperty("m_NavMeshData");
        if (prop != null)
        
            prop.objectReferenceValue = obj;
            naviMeshSettings.ApplyModifiedProperties();
        
        EditorSceneManager.SaveScene(scene, scene.path);
    

四.测试结果

针对上述源码,这里进行相应的测试,测试源码如下:

using UnityEngine;
using UnityEditor;
using UnityEngine.SceneManagement;
using UnityEditor.SceneManagement;
public class NavigationHelperTest : MonoBehaviour

    [MenuItem("Test/AgentTest")]
    static void AgentTest()
    
        var navMeshProjectSettings = NavigationHelper.GetNavMeshProjectSettings();
        var agent0 = navMeshProjectSettings.GetAgentByIndex(0);
        Debug.Log($"Index=agent0.AgentIndex,Name=agent0.AgentName," +
                  $"Radius=agent0.AgentRadius,Height= agent0.AgentHeight," +
                  $"StepHeight=agent0.AgentStepHeight,MaxSlope= agent0.AgentMaxSlope");
        agent0.AgentName = "AgentTest";
        agent0.AgentRadius = 1;
        agent0.AgentHeight = 1;
        agent0.AgentStepHeight = 1;
        agent0.AgentMaxSlope = 60;
        Debug.Log($"Index=agent0.AgentIndex,Name=agent0.AgentName," +
                  $"Radius=agent0.AgentRadius,Height= agent0.AgentHeight," +
                  $"StepHeight=agent0.AgentStepHeight,MaxSlope= agent0.AgentMaxSlope");
    

    [MenuItem("Test/AreaTest")]
    static void AreaTest()
    
        var navMeshProjectSettings = NavigationHelper.GetNavMeshProjectSettings();
        var area = navMeshProjectSettings.GetNavigationArea(3);
        Debug.Log($"Index=area.AreaIndex,name=area.Name,cost=area.Cost");
        area.Name = "AreaTest";
        area.Cost = 5;
        Debug.Log($"Index=area.AreaIndex,name=area.Name,cost=area.Cost");
    
    [MenuItem("Test/BakeTest")]
    static void BakeTest()
    
        var bakeSetter = NavigationHelper.GetNavMeshSettings(SceneManager.GetActiveScene());
        Debug.Log($"AgentRadius=bakeSetter.AgentRadius,AgentHeight=bakeSetter.AgentHeight" +
                  $",MaxSlope=bakeSetter.MaxSlope,StepHeight=bakeSetter.StepHeight" +
                  $",DropHeight=bakeSetter.DropHeight,JumpDistance=bakeSetter.JumpDistance" +
                  $",ManualVoxelSize=bakeSetter.ManualVoxelSize,VoxelSize=bakeSetter.VoxelSize" +
                  $",MinRegionArea=bakeSetter.MinRegionArea,HeightMesh=bakeSetter.HeightMesh");
        bakeSetter.AgentRadius = 1;
        bakeSetter.AgentHeight = 1;
        bakeSetter.MaxSlope = 1;
        bakeSetter.StepHeight = 1;
        bakeSetter.DropHeight = 1;
        bakeSetter.JumpDistance = 1;
        bakeSetter.ManualVoxelSize = true;
        bakeSetter.VoxelSize = 1;
        bakeSetter.MinRegionArea = 1;
        bakeSetter.HeightMesh = true;
        Debug.Log($"AgentRadius=bakeSetter.AgentRadius,AgentHeight=bakeSetter.AgentHeight" +
                  $",MaxSlope=bakeSetter.MaxSlope,StepHeight=bakeSetter.StepHeight" +
                  $",DropHeight=bakeSetter.DropHeight,JumpDistance=bakeSetter.JumpDistance" +
                  $",ManualVoxelSize=bakeSetter.ManualVoxelSize,VoxelSize=bakeSetter.VoxelSize" +
                  $",MinRegionArea=bakeSetter.MinRegionArea,HeightMesh=bakeSetter.HeightMesh");
    

    [MenuItem("Test/Redirect")]
    static void Redirect()
    
        var src = "Assets/Test1.unity";
        var target = "Assets/Test2.unity";
        var srcScene = EditorSceneManager.OpenScene(src, OpenSceneMode.Single);
        var navidata = NavigationHelper.GetNavMeshData(srcScene);
        var targetScene = EditorSceneManager.OpenScene(target, OpenSceneMode.Single);
        NavigationHelper.SetNavMeshData(targetScene, navidata);
    

1.Agent参数设定测试效果:

2.Area参数设定效果:

3.针对Test1场景的Bake参数设定效果:

4.将Test1场景的导航网格数据重新关联到Test2场景,效果如下:

五.工程源码

详见本人Github

以上是关于unity自动化流程之代码修改导航网格参数的主要内容,如果未能解决你的问题,请参考以下文章

Unity之导航网格寻路相关参数

Unity3d之-使用BMFont制作美术字体

Unity3D_(网格导航)简单物体自动寻路

unity资源导入神器之StartAssetEditing/StopAssetEditing

Unity3D AI:导航系统-导航网格生成

unity中NavMeshAgent有关知识