Search Unity

how to create a dynamic area cast in navmesh

Discussion in 'Navigation' started by Mortalanimal, Dec 11, 2019.

  1. Mortalanimal

    Mortalanimal

    Joined:
    Jun 7, 2014
    Posts:
    568
    Hi,

    How do I Increase the area cost of my navmesh in a radius under my navmeshagent at runtime. Ideally I would also like the ability to remove this area cost back to default also.

    Thanks in advance
     
  2. Abbrew

    Abbrew

    Joined:
    Jan 1, 2018
    Posts:
    417
    I made a Monobehaviour that dynamically assigns area types to sections of the navmesh. You do this by calling NavMesh.UpdateAsync, which accepts a list of NavMeshBuildSource. NavMeshBuildSource are volumes that, when intersecting with a NavMesh, modify that area type within the intersection plane. Each Update loop, create a NavMeshBuildSource that's a ModifierBox type centered on the NavMeshAgent. Call NavMesh.UpdateAsync using this ModifierBox, and the NavMesh will weigh the area around the NavMeshAgent accordingly. You can either write your own implementation, or you can use this code:

    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using UnityEngine.AI;
    4. using Unity.Entities;
    5. using Unity.Jobs;
    6. using Unity.Collections;
    7. using Unity.Mathematics;
    8. using System.Collections.Generic;
    9. using Unity.Burst;
    10. using Unity.Physics;
    11. using Unity.Physics.Systems;
    12. using UnityEngine.Experimental.AI;
    13. public class NavMeshBuilderGrid : MonoBehaviour
    14. {
    15.     [Header("User")]
    16.     [SerializeField]
    17.     private int agentType;
    18.     [SerializeField]
    19.     private float agentRadius;
    20.     [SerializeField]
    21.     private float agentStepHeight;
    22.     [SerializeField]
    23.     private float agentSlope;
    24.     [SerializeField]
    25.     private float agentHeight;
    26.  
    27.     [Header("Map")]
    28.     [SerializeField]
    29.     private LayerMask includedLayers;
    30.     [SerializeField]
    31.     private Bounds gridBounds;
    32.     [SerializeField]
    33.     private int tileSize;
    34.     [SerializeField]
    35.     private float voxelRadius;
    36.     [SerializeField]
    37.     private float chunkLength;
    38.  
    39.     [Header("Debug")]
    40.     [SerializeField]
    41.     private bool showBounds = true;
    42.  
    43.     private bool ready;
    44.  
    45.     private float heightOfEnvironment;
    46.     private float middleOfEnvironment;
    47.     private PhysicsConverter chunkConverter;
    48.  
    49.     private int xChunkGridLength;
    50.     private int yChunkGridLength;
    51.  
    52.     private EntityManager em;
    53.  
    54.     private NavMeshChunk[,] grid;
    55.     private NavMeshLinkInstance[] links;
    56.     private Dictionary<int, List<NavMeshBuildSource>> chunkToCells;
    57.     private NavMeshBuildSettings buildSettings;
    58.  
    59.     private static NavMeshBuilderGrid instance;
    60.  
    61.     public static List<AsyncOperation> AllocateModifiers(
    62.         NativeMultiHashMap<int, float2x2> areaTypeModifierVolumes
    63.     )
    64.     {
    65.         // clear the reusable data structure
    66.         var chunkToCellLength = instance.chunkToCells.Count;
    67.         for (var i = 0; i < chunkToCellLength; i++)
    68.         {
    69.             instance.chunkToCells[i].Clear();
    70.         }
    71.  
    72.         var physicsData = Physics.GetPhysics().GetData();
    73.         var topOfEnvironment = physicsData.GetBottomLeftCorner().y + physicsData.GetDimensions().y;
    74.         var bottomOfEnvironment = physicsData.GetBottomLeftCorner().y;
    75.         var middleOfEnvironment = (topOfEnvironment + bottomOfEnvironment) / 2;
    76.         var heightOfEnvironment = topOfEnvironment - bottomOfEnvironment;
    77.        
    78.         var cellDefinitions = new List<CellDefinition>();
    79.         for (var i = 0; i < NavMeshAreas.GetNumAreaTypes(); i++)
    80.         {
    81.             var areaType = i;
    82.             if (areaTypeModifierVolumes.TryGetFirstValue(
    83.                 areaType,
    84.                 out var volumeDim,
    85.                 out var it
    86.             ))
    87.             {
    88.                 var ll = volumeDim.c0;
    89.                 var ur = volumeDim.c1;
    90.  
    91.                 instance.AddToChunk(
    92.                     areaType,
    93.                     ll,
    94.                     ur
    95.                 );
    96.  
    97.                 while (areaTypeModifierVolumes.TryGetNextValue(out volumeDim, ref it))
    98.                 {
    99.                     ll = volumeDim.c0;
    100.                     ur = volumeDim.c1;
    101.  
    102.                     instance.AddToChunk(
    103.                         areaType,
    104.                         ll,
    105.                         ur
    106.                     );
    107.                 }
    108.             }
    109.         }
    110.  
    111.         var operations = new List<AsyncOperation>();
    112.         var chunkGridDimX = instance.grid.GetLength(0);
    113.         var chunkGridDimY = instance.grid.GetLength(1);
    114.         for (var i = 0; i < chunkGridDimX; i++)
    115.         {
    116.             for (var j = 0; j < chunkGridDimY; j++)
    117.             {
    118.                 var currentSources = instance.chunkToCells[(i * chunkGridDimY) + j];
    119.  
    120.                 // only update if cells are in the chunk
    121.                 if(currentSources.Count == 0)
    122.                 {
    123.                     continue;
    124.                 }
    125.  
    126.                 var currentChunk = instance.grid[i,j];
    127.                 var currentAllSources = new List<NavMeshBuildSource>();
    128.                 currentAllSources.AddRange(currentSources);
    129.                 currentAllSources.AddRange(currentChunk.staticSources);
    130.                 operations.Add(
    131.                     NavMeshBuilder.UpdateNavMeshDataAsync(
    132.                         currentChunk.data,
    133.                         instance.buildSettings,
    134.                         currentAllSources,
    135.                         currentChunk.data.sourceBounds
    136.                     )
    137.                 );
    138.             }
    139.         }
    140.         return operations;
    141.     }
    142.    
    143.     private void AddToChunk(int areaType, float2 ll, float2 ur)
    144.     {
    145.         var cellLocation = (ll + ur) / 2;
    146.         var cellLocation4x4 = Matrix4x4.Translate(cellLocation.To3D(middleOfEnvironment));
    147.         var cellRadii = (ur - ll) / 2;
    148.         var cellDiameter = cellRadii * 2;
    149.         var cell = new NavMeshBuildSource
    150.         {
    151.             size = new Vector3(cellDiameter.x, heightOfEnvironment, cellDiameter.y),
    152.             area = areaType,
    153.             transform = cellLocation4x4,
    154.             shape = NavMeshBuildSourceShape.ModifierBox
    155.         };
    156.  
    157.         var llChunk = chunkConverter.ToGridCoord(ll);
    158.         var urChunk = chunkConverter.ToGridCoord(ur);
    159.         var top = urChunk.y;
    160.         var bottom = llChunk.y;
    161.         var right = urChunk.x;
    162.         var left = llChunk.x;
    163.         var gridHeight = grid.GetLength(1);
    164.         for (var i = left; i <= right; i++)
    165.         {
    166.             for (var j = bottom; j <= top; j++)
    167.             {
    168.                 chunkToCells[(i * gridHeight) + j].Add(cell);
    169.             }
    170.         }
    171.     }
    172.  
    173.     private struct CellDefinition
    174.     {
    175.         public float2 location;
    176.         public float2 radii;
    177.         public int areaType;
    178.     }
    179.  
    180.     private void Awake()
    181.     {
    182.         instance = this;
    183.         em = World.Active.EntityManager;
    184.     }
    185.  
    186.     private void OnEnable()
    187.     {
    188.         var topOfEnvironment = gridBounds.center.y + gridBounds.extents.y;
    189.         var bottomOfEnvironment = gridBounds.center.y - gridBounds.extents.y;
    190.         middleOfEnvironment = (topOfEnvironment + bottomOfEnvironment) / 2;
    191.         heightOfEnvironment = topOfEnvironment - bottomOfEnvironment;
    192.  
    193.         xChunkGridLength = (int)Mathf.Ceil(gridBounds.size.x / chunkLength);
    194.         yChunkGridLength = (int)Mathf.Ceil(gridBounds.size.z / chunkLength);
    195.  
    196.         buildSettings = NavMesh.CreateSettings();
    197.         buildSettings.agentTypeID = agentType;
    198.         buildSettings.overrideVoxelSize = true;
    199.         buildSettings.voxelSize = voxelRadius;
    200.         buildSettings.overrideTileSize = true;
    201.         buildSettings.tileSize = tileSize;
    202.         buildSettings.agentRadius = agentRadius;
    203.         buildSettings.agentClimb = agentStepHeight;
    204.         buildSettings.agentSlope = agentSlope;
    205.         buildSettings.agentHeight = agentHeight;
    206.         GenerateNavMeshGrid(buildSettings);
    207.         GenerateLinks(buildSettings);
    208.     }
    209.  
    210.     private void GenerateNavMeshGrid(NavMeshBuildSettings settings)
    211.     {
    212.  
    213.         var nodeRadius = chunkLength / 2;
    214.         chunkConverter = new PhysicsConverter(
    215.             gridBounds.center - gridBounds.extents,
    216.             nodeRadius
    217.         );
    218.  
    219.         grid = new NavMeshChunk[xChunkGridLength, yChunkGridLength];
    220.         var gridArea = xChunkGridLength * yChunkGridLength;
    221.         for(var i = 0; i < xChunkGridLength; i++)
    222.         {
    223.             for (var j = 0; j < yChunkGridLength; j++)
    224.             {
    225.                 var staticSources = new List<NavMeshBuildSource>();
    226.  
    227.                 var currentCenter =
    228.                     chunkConverter.ToWorldCoord(
    229.                         new int2(i, j)
    230.                     ).To3D(middleOfEnvironment);
    231.                 var currentBounds = new Bounds
    232.                 {
    233.                     center = Vector3.zero,
    234.                     size = new Vector3(chunkLength, heightOfEnvironment, chunkLength)
    235.                 };
    236.                
    237.                 NavMeshBuilder.CollectSources(
    238.                     currentBounds,
    239.                     includedLayers,
    240.                     NavMeshCollectGeometry.RenderMeshes,
    241.                     0,
    242.                     new List<NavMeshBuildMarkup>(),
    243.                     staticSources
    244.                 );
    245.  
    246.                 var data = NavMeshBuilder.BuildNavMeshData(
    247.                    settings,
    248.                    staticSources,
    249.                    currentBounds,
    250.                    currentCenter,
    251.                    Quaternion.identity
    252.                 );
    253.                 var reference = NavMesh.AddNavMeshData(data);
    254.                 grid[i, j] = new NavMeshChunk
    255.                 {
    256.                     data = data,
    257.                     reference = reference,
    258.                     staticSources = staticSources
    259.                 };
    260.             }
    261.         }
    262.  
    263.         var cellDiameter = NavMeshQueryScheduler.GetAgentModifierCellRadius() * 2;
    264.         var xCellGridDim = (int)(gridBounds.size.x / cellDiameter);
    265.         var yCellGridDim = (int)(gridBounds.size.z / cellDiameter);
    266.         var maxCells = xCellGridDim * yCellGridDim;
    267.         var reasonableMaxCellsPerChunk = (maxCells /(gridArea * 50));
    268.  
    269.         chunkToCells = new Dictionary<int, List<NavMeshBuildSource>>(gridArea);
    270.         for(var i = 0; i < gridArea; i++)
    271.         {
    272.             chunkToCells.Add(i, new List<NavMeshBuildSource>(reasonableMaxCellsPerChunk));
    273.         }
    274.     }
    275.  
    276.     private struct NavMeshChunk
    277.     {
    278.         public NavMeshData data;
    279.         public NavMeshDataInstance reference;
    280.         public List<NavMeshBuildSource> staticSources;
    281.     }
    282.  
    283.     private void GenerateLinks(
    284.         NavMeshBuildSettings settings
    285.     )
    286.     {
    287.         var physics = Physics.GetPhysics().GetData();
    288.         var world = World.Active.GetOrCreateSystem<BuildPhysicsWorld>().PhysicsWorld.CollisionWorld;
    289.  
    290.         var horizontalDistanceBetweenLinks =
    291.             (settings.agentRadius * 2) + (PhysicsMeasurements.METER * .1f);
    292.         var horizontalConverter = new PhysicsConverterAsymmetrical(
    293.             gridBounds.center - gridBounds.extents,
    294.             horizontalDistanceBetweenLinks / 2,
    295.             chunkLength / 2
    296.         );
    297.         var horizontalLinkWidthRadius = settings.agentRadius - (PhysicsMeasurements.METER * .1f);
    298.         var horizontalLinkLengthRadius = PhysicsMeasurements.METER * 2f;
    299.         var linksPerHorizontal =
    300.             (int)((gridBounds.center.x + gridBounds.extents.x) / horizontalDistanceBetweenLinks);
    301.         var numHorizontalLinks = yChunkGridLength * linksPerHorizontal;
    302.         var horizontalLinkStarts = new NativeArray<float3>(numHorizontalLinks, Allocator.TempJob);
    303.         var horizontalLinkEnds = new NativeArray<float3>(numHorizontalLinks, Allocator.TempJob);
    304.         var horizontalValidLinks = new NativeArray<bool>(numHorizontalLinks, Allocator.TempJob);
    305.         var horizontalHandle = new GenerateLinksHorizontalJob
    306.         {
    307.             linkLengthRadius = horizontalLinkLengthRadius,
    308.             converter = horizontalConverter,
    309.             linksPerHorizontal = linksPerHorizontal,
    310.             linkStarts = horizontalLinkStarts,
    311.             linkEnds = horizontalLinkEnds,
    312.             linkIsValid = horizontalValidLinks,
    313.             walkableLayers = includedLayers,
    314.             world = world,
    315.             topOfEnvironment = physics.GetTopOfEnvironment(),
    316.             bottomOfEnvironment = physics.GetBottomOfEnvironment()
    317.         }.Schedule(numHorizontalLinks, 128, default);
    318.  
    319.         JobHandle.ScheduleBatchedJobs();
    320.  
    321.         var verticalDistanceBetweenLinks =
    322.             (settings.agentRadius * 2) + (PhysicsMeasurements.METER * .1f);
    323.         var verticalConverter = new PhysicsConverterAsymmetrical(
    324.             gridBounds.center - gridBounds.extents,
    325.             chunkLength / 2,
    326.             verticalDistanceBetweenLinks / 2
    327.         );
    328.         var verticalLinkWidthRadius = settings.agentRadius - (PhysicsMeasurements.METER * .1f);
    329.         var verticalLinkLengthRadius = PhysicsMeasurements.METER * 2f;
    330.         var linksPerVertical =
    331.             (int)((gridBounds.center.y + gridBounds.extents.y) / verticalDistanceBetweenLinks);
    332.         var numVerticalLinks = xChunkGridLength * linksPerVertical;
    333.         var verticalLinkStarts = new NativeArray<float3>(numVerticalLinks, Allocator.TempJob);
    334.         var verticalLinkEnds = new NativeArray<float3>(numVerticalLinks, Allocator.TempJob);
    335.         var verticalValidLinks = new NativeArray<bool>(numVerticalLinks, Allocator.TempJob);
    336.         var verticalHandle = new GenerateLinksVerticalJob
    337.         {
    338.             linkLengthRadius = verticalLinkLengthRadius,
    339.             converter = verticalConverter,
    340.             linksPerVertical = linksPerVertical,
    341.             linkStarts = verticalLinkStarts,
    342.             linkEnds = verticalLinkEnds,
    343.             linkIsValid = verticalValidLinks,
    344.             walkableLayers = includedLayers,
    345.             world = world,
    346.             topOfEnvironment = physics.GetTopOfEnvironment(),
    347.             bottomOfEnvironment = physics.GetBottomOfEnvironment()
    348.         }.Schedule(numVerticalLinks, 128, default);
    349.  
    350.         var inputDeps = JobHandle.CombineDependencies(horizontalHandle, verticalHandle);
    351.         NavMeshWorld.GetDefaultWorld().AddDependency(inputDeps);
    352.         inputDeps.Complete();
    353.  
    354.         links = new NavMeshLinkInstance[numHorizontalLinks + numVerticalLinks];
    355.         for (var i = 0; i < numHorizontalLinks; i++)
    356.         {
    357.             if (horizontalValidLinks[i])
    358.             {
    359.                 links[i] = NavMesh.AddLink(new NavMeshLinkData
    360.                 {
    361.                     width = horizontalLinkWidthRadius * 2,
    362.                     startPosition = horizontalLinkStarts[i],
    363.                     endPosition = horizontalLinkEnds[i],
    364.                     agentTypeID = agentType,
    365.                     area = 0,
    366.                     bidirectional = true,
    367.                 });
    368.             }
    369.         }
    370.  
    371.         for (var i = 0; i < numVerticalLinks; i++)
    372.         {
    373.             if (verticalValidLinks[i])
    374.             {
    375.                 links[numHorizontalLinks + i] = NavMesh.AddLink(new NavMeshLinkData
    376.                 {
    377.                     width = verticalLinkWidthRadius * 2,
    378.                     startPosition = verticalLinkStarts[i],
    379.                     endPosition = verticalLinkEnds[i],
    380.                     agentTypeID = agentType,
    381.                     area = 0,
    382.                     bidirectional = true,
    383.                 });
    384.             }
    385.         }
    386.  
    387.         verticalLinkStarts.Dispose();
    388.         verticalLinkEnds.Dispose();
    389.         verticalValidLinks.Dispose();
    390.         horizontalLinkStarts.Dispose();
    391.         horizontalLinkEnds.Dispose();
    392.         horizontalValidLinks.Dispose();
    393.     }
    394.  
    395.     [BurstCompile]
    396.     private struct GenerateLinksHorizontalJob : IJobParallelFor
    397.     {
    398.         public float linkLengthRadius;
    399.         public int linksPerHorizontal;
    400.  
    401.         [WriteOnly]
    402.         public NativeArray<float3> linkStarts;
    403.         [WriteOnly]
    404.         public NativeArray<float3> linkEnds;
    405.         [WriteOnly]
    406.         public NativeArray<bool> linkIsValid;
    407.  
    408.         [ReadOnly]
    409.         public CollisionWorld world;
    410.         public LayerMask walkableLayers;
    411.         public PhysicsConverterAsymmetrical converter;
    412.         public float topOfEnvironment;
    413.         public float bottomOfEnvironment;
    414.  
    415.         public void Execute(int index)
    416.         {
    417.             var nodeX = index % linksPerHorizontal;
    418.             var nodeY = index / linksPerHorizontal;
    419.             var linkCenter = converter.ToWorldCoordNoOffset(new int2(nodeX, nodeY));
    420.  
    421.             var linkTop = linkCenter + new float2(0, linkLengthRadius);
    422.             var linkTopLOSRequest = new LineOfSightRequest
    423.             {
    424.                 start = linkTop.To3D(topOfEnvironment),
    425.                 end = linkTop.To3D(bottomOfEnvironment),
    426.             };
    427.             var linkTopLOSResult = LineOfSightHelper.FindOne(
    428.                 linkTopLOSRequest,
    429.                 walkableLayers,
    430.                 world
    431.             );
    432.  
    433.             var linkBottom = linkCenter - new float2(0, linkLengthRadius);
    434.             var linkBottomLOSRequest = new LineOfSightRequest
    435.             {
    436.                 start = linkBottom.To3D(topOfEnvironment),
    437.                 end = linkBottom.To3D(bottomOfEnvironment),
    438.             };
    439.             var linkBottomLOSResult = LineOfSightHelper.FindOne(
    440.                 linkBottomLOSRequest,
    441.                 walkableLayers,
    442.                 world
    443.             );
    444.  
    445.             if (linkTopLOSResult.exists == 0 && linkBottomLOSResult.exists == 0)
    446.             {
    447.                 linkIsValid[index] = true;
    448.                 linkStarts[index] = linkTopLOSResult.point;
    449.                 linkEnds[index] = linkBottomLOSResult.point;
    450.             }
    451.         }
    452.     }
    453.  
    454.  
    455.     [BurstCompile]
    456.     private struct GenerateLinksVerticalJob : IJobParallelFor
    457.     {
    458.         public float linkLengthRadius;
    459.         public int linksPerVertical;
    460.  
    461.         [WriteOnly]
    462.         public NativeArray<float3> linkStarts;
    463.         [WriteOnly]
    464.         public NativeArray<float3> linkEnds;
    465.         [WriteOnly]
    466.         public NativeArray<bool> linkIsValid;
    467.  
    468.         [ReadOnly]
    469.         public CollisionWorld world;
    470.         public LayerMask walkableLayers;
    471.         public PhysicsConverterAsymmetrical converter;
    472.         public float topOfEnvironment;
    473.         public float bottomOfEnvironment;
    474.  
    475.         public void Execute(int index)
    476.         {
    477.             var nodeX = index / linksPerVertical;
    478.             var nodeY = index % linksPerVertical;
    479.             var linkCenter = converter.ToWorldCoordNoOffset(new int2(nodeX, nodeY));
    480.  
    481.             var linkTop = linkCenter + new float2(linkLengthRadius, 0);
    482.             var linkTopLOSRequest = new LineOfSightRequest
    483.             {
    484.                 start = linkTop.To3D(topOfEnvironment),
    485.                 end = linkTop.To3D(bottomOfEnvironment),
    486.             };
    487.             var linkTopLOSResult = LineOfSightHelper.FindOne(
    488.                 linkTopLOSRequest,
    489.                 walkableLayers,
    490.                 world
    491.             );
    492.  
    493.             var linkBottom = linkCenter - new float2(linkLengthRadius, 0);
    494.             var linkBottomLOSRequest = new LineOfSightRequest
    495.             {
    496.                 start = linkBottom.To3D(topOfEnvironment),
    497.                 end = linkBottom.To3D(bottomOfEnvironment),
    498.             };
    499.             var linkBottomLOSResult = LineOfSightHelper.FindOne(
    500.                 linkBottomLOSRequest,
    501.                 walkableLayers,
    502.                 world
    503.             );
    504.  
    505.             if (linkTopLOSResult.exists == 0 && linkBottomLOSResult.exists == 0)
    506.             {
    507.                 linkIsValid[index] = true;
    508.                 linkStarts[index] = linkTopLOSResult.point;
    509.                 linkEnds[index] = linkBottomLOSResult.point;
    510.             }
    511.         }
    512.     }
    513.  
    514.     public static PhysicsConverter GetModifierGridConverter()
    515.     {
    516.         return
    517.             new PhysicsConverter(
    518.                 instance.gridBounds.center - instance.gridBounds.extents,
    519.                 NavMeshQueryScheduler.GetAgentModifierCellRadius()
    520.             );
    521.     }
    522.  
    523.     public static int GetUserAgentType()
    524.     {
    525.         return instance.agentType;
    526.     }
    527.  
    528.     private void OnDrawGizmos()
    529.     {
    530.         if(showBounds)
    531.         {
    532.             Gizmos.DrawWireCube(gridBounds.center, gridBounds.size);
    533.         }
    534.     }
    535.  
    536.     private void OnDestroy()
    537.     {
    538.         var gridDimX = grid.GetLength(0);
    539.         var gridDimY = grid.GetLength(1);
    540.         for (var i = 0; i < gridDimX; i++)
    541.         {
    542.             for (var j = 0; j < gridDimY; j++)
    543.             {
    544.                 NavMesh.RemoveNavMeshData(grid[i,j].reference);
    545.             }
    546.         }
    547.         var numLinks = links.Length;
    548.         for(var i = 0; i < numLinks; i++)
    549.         {
    550.             NavMesh.RemoveLink(links[i]);
    551.         }
    552.     }
    553. }
    This code contains some junk that divides the NavMesh into "chunks", and if a NavMeshBuildSource is confined within a subset of the chunks, only those chunks are updated. I'm not sure if it actually improves performance, so feel free to remove that portion of the code. Anyways, AllocateModifiers is the method you want to be calling. It accepts NativeMultiHashMap<int, float2x2> areaTypeModifierVolumes, which maps an int representing the area type to an unordered list of float2x2, which represents the upper-right and lower-left of a cell. On the caller side, do yield return asyncOperation; to block the main thread until the NavMesh is updated. Alternatively, start a coroutine and do
    Code (CSharp):
    1. while(!asyncOperation.isComplete){
    2.      yield return new WaitForEndOfFrame();
    3. }
    so that the main thread is not blocked and the update completes at its own pace.
     
    vhman and Mortalanimal like this.
  3. Mortalanimal

    Mortalanimal

    Joined:
    Jun 7, 2014
    Posts:
    568

    Thank you so much for this, I was beginning to think it was impossible.

    I will have to study your code, its a bit advanced for me but I am happy never the less!

    Thanks again
     
  4. Abbrew

    Abbrew

    Joined:
    Jan 1, 2018
    Posts:
    417
    Please ping me if you need help with your own implementation, the code I provided is admittedly spaghettish and lacks context
     
  5. Mortalanimal

    Mortalanimal

    Joined:
    Jun 7, 2014
    Posts:
    568
    Thank you so much

    Problem is, I Have only started programming around 3 months ago, so I asked a friend to help me study this script who has been programming for a few years, and he couldn't understand it either haha. I mean, I have written Scripts 5000 lines long (not saying this is a good thing lol), but this was so advanced to me that I could only read 10% of it haha.

    My questions:

    1) will this be able to work in run-time?, because thats what I need. I need to constantly change the cost of the navmesh withing a small radius during runtime.

    2) the way I would use it, once my unit has a velocity of 0 for 3 seconds, he will increase the mavMesh Cost under him, (this is for an RTS game so this should apply to all my units).

    3) if my unity starts moving I would need that area cost to go back to default.

    4) I am not Using ACS / Any Burst Compilers / JOB system, because I have not learned it. And I was wondering if its possible to do it without.

    5) I still want to continue using the In-Game navmesh / navmeshAgent, because my scripts are written around them (would this be a problem while using your method).

    6) Basically, Unless you fully spoon feed me this, I dont think I can Understand it lol. A video on this would be great coz no one has covered this at all, and if you dont make videos, I will do it for you if you manage to teach me this and I will give you full credit.

    7) how taxiing is this process, I have been told that this is very taxing, which doesnt make sense to me because you can make a dynamic navmesh obstacle with carve and its not that taxing, in my eyes this feels like it should be just as simple. (ofc I could be wrong)
     
  6. Abbrew

    Abbrew

    Joined:
    Jan 1, 2018
    Posts:
    417
    I don't think my script fits your use case. My script was made for a game that batches together NavMesh queries that are created over time, then takes a few dozen milliseconds to asynchronously update the NavMesh, then executes those queries. In your use case, the updates must occur every frame, which means using the asynchronous API won't work. I would say that you could batch pathfinding requests the way my game does, but because the NavMesh could change halfway through a path, that won't work either. However, I'll try my best to answer your questions and formulate a solution

    1) You could use the synchronous API to update the NavMesh, but updating a huge map typical of RTS games would cause huge lag spikes in the framerate.

    2) You could keep track of all units that are affecting the NavMesh, and once other units want to trace a path, update the NavMesh asynchronously, and when done, compute paths for all pending units

    3) Same thing as above, batch together units that have moved's last locations, and when other units want to trace a path, update the NavMesh taking into account the deleted modified costs.

    4) It can be done, and for now it is recommended that you use the MonoBehaviour way of doing things. However, Unity recently announced that they are planning on providing a ECS/Jobs API for NavMesh. This would speed up performance significantly. If you have time I suggest learning Unity's DOTS so that once that is released you'd be ready to take advantage of it

    5) You can still update the NavMesh asynchronously, and once done, use NavMeshAgent and NavMesh to find and traverse a path. There will be delays though

    6) Try following some Youtube tutorials, and build a simple NavMesh system to toy around with. Once you've gotten the hang of it slowly add features until you reach the point you want.

    7) Updating the NavMesh takes, for a large map with lots of modifiers, around 200ms for my game. This sounds bad, but because it's being done asynchronously, the latency is spread evenly across multiple frames. This means that while there will be a ~200ms delay between the batch of NavMesh queries being created, and the NavMesh queries being executed, there will be no frame rate hiccups.

    In the end, you should profile first. One thing about gamedev that I hate but is necessary is that to ensure that you are doing things the most performant way, you'd have to build prototypes that, if they fail, must be discarded. Try building the simplest one where you synchronously update the NavMesh every time a NavMesh query is made. To reduce latency, increase the tile size and voxel size, effectively making your NavMesh less "detailed", but more performant. If this implementation causes lag spikes, then try more complicated alternatives.

    Some other ideas are:

    1. Use NavMeshObstacles. Instead of changing an area's areatype, this will make the area unwalkable. Attach a NavMeshObstacle to each agent. You might not want this as it will cause cluttering

    2. Learn the Job and Burst systems. NavMeshQuery is an API that allows you to calculate paths asynchronously inside of a Job, and is fully Burst-able. This way you can batch together NavMesh queries, update the entire map asynchronously once, and then execute those queries efficiently. One weakness is that once you've calculated a path and somewhere on the map an agent remains still long enough to create an area cost modifier, that path would be considered "stale", since it was calculated without that new area cost modifier in mind.

    Good luck, and you're doing very well for someone who has been programming for only 3 months. When I was at that stage I was still trying to compare Strings with ==. Good luck!
     
  7. Mortalanimal

    Mortalanimal

    Joined:
    Jun 7, 2014
    Posts:
    568
    @Abbrew,

    Thank you for taking your time to write a detailed reply!

    I thought about this, but it makes it look even worse than my current temporary solution (I will make a video on this and show you once I tweak it, this might explain to you where I am coming from)


    I tried, but I Could not understand it, at least I found it hard, And i thought this is a result of my ignorance, so I though I would try to learn it when I have more experience in general. Worse part is when I get stuck for over 2+ days on something and lose motivation, which is exactly what happened when I tried to learn this.

    Haha, I always compare things with == or != , ppl say its costly but how else am I suppose to compare. But I dont compare strings, I hardly ever use them outside of invokes.

    Thanks again bro, I will make a youtube video and and show my prototyping. The thing is, I personally believe this is the most important part of an RTS game, thats why I have spent over a months on this script.
     
  8. Abbrew

    Abbrew

    Joined:
    Jan 1, 2018
    Posts:
    417
    Yeah learning Jobs was hard for me as well - it took me 6 months to learn enough to start conceptualizing and writing code efficiently. However, it's well worth it because of the massive performance benefits. Your style of video game (RTS) is perfectly suited for Jobs, and maybe even ECS. Try to learn them in a way that complements your learning style. For example, if you like reverse-engineering, poke around the forums looking for code snippets or Github links, and learn from those examples.
     
  9. vhman

    vhman

    Joined:
    Aug 13, 2018
    Posts:
    359
    @Abbrew

    Do you have this part on GitHub or your other Components?
     
  10. Abbrew

    Abbrew

    Joined:
    Jan 1, 2018
    Posts:
    417
    I do, but the repository is likely to change and even become private in the coming months. You're free to clone the repository and use code in the meantime as long as you give credit. Note that there are likely errors in play mode due to my bad git habits. Here it is:

    https://github.com/KontosTwo/DidymosECS

    NavMesh-specific code is in:

    https://github.com/KontosTwo/DidymosECS/tree/master/Assets/MyAssets/Src/Environment/NavMesh
     
    vhman likes this.
  11. BinaryEclipse

    BinaryEclipse

    Joined:
    Sep 1, 2016
    Posts:
    43
    should this work out of the box? I get errors on many things that don't seem to exist. I doubt a few months would be enough for all of these to not exist?
    I got the burst, physics, entities, dots packages. all the "using" lines at the top are recognized.
     
  12. Abbrew

    Abbrew

    Joined:
    Jan 1, 2018
    Posts:
    417
    This script assumes you have NavMeshComponents in your project. However, looking back the script is just over-engineered and horribly bad performance-wise. I recommend you try instead to dynamically weigh the destinations instead of the paths. To better explain: split one large pathfind (a start/destination pair) into multiple smaller pathfinds. For each smaller pathfind, find the destination with the lowest cost (the heuristic for doing so could be how hidden the destination is from enemies, etc), and trace a path with normal, static weights. Keep doing this until you reach the initial destination. As a result you'll simulate dynamically weighted paths. You'll have to balance the size of the smaller pathfinds though - too small and while it will be very close to dynamically weighted paths the agent will get lost in dead ends. Too large and the path will be naive.
     
  13. BinaryEclipse

    BinaryEclipse

    Joined:
    Sep 1, 2016
    Posts:
    43
    No, I have that as well. I seem to be missing "PhysicsConverter", "LineOfSightRequest", "World.Active", "PhysicsMeasurements", "NavMeshQueryScheduler", "Physics.GetPhysics", "NavMeshAreas"
     
  14. Abbrew

    Abbrew

    Joined:
    Jan 1, 2018
    Posts:
    417
    Those are also scripts I wrote. Again, I recommend you implement what I described in my previous comment. My initial implementation of dynamic path weights is EXTREMELY inefficient. The game literally hangs for an entire second every time the navmesh is recalculated. I can post my newer implementation if you'd like
     
  15. BinaryEclipse

    BinaryEclipse

    Joined:
    Sep 1, 2016
    Posts:
    43
    Ah I see. Well, I haven't used navmesh all that much in the past besides simple navmesh. There doesn't seem to be a whole lot of documentation regarding modifying a navmesh at runtime. I'm making a city builder and I need to modify the navmesh after the player places a road so that agents can prefer walking along the road but can still walk on terrain when necessary. I'm using the navmesh components system and I've worked off of the plank drop example. I set the LocalNavMeshBuilder to only run on command and made it's bounds only encompass the new road area because I have a very large terrain and I am concerned for performance. The issue is that when placing the second road, when the navmesh updates, it loses the info from the first road. I've tried different things to make that work, but I seem to be out of my element. I'd appreciate any advice you can offer or resources you could point me to. Otherwise, my next step is to split my terrain into smaller chunks of meshes, each with their own LocalNavMeshBuilder. What you were describing sounds like it would be ideal though
     
  16. Abbrew

    Abbrew

    Joined:
    Jan 1, 2018
    Posts:
    417
    I don't think the entire navmesh is recalculated every time you update a small section of it. Wild guess but maybe the reason the navmesh is first divided into a grid is so that only grid cells that contain updates are recalculated.

    When you talk about "losing info" do you mean that the agents stop moving after you update a section of the navmesh that agent was moving through?
     
  17. BinaryEclipse

    BinaryEclipse

    Joined:
    Sep 1, 2016
    Posts:
    43
    Allow me to explain. So the navmesh, originally is all of area type 0. I place road A and that area becomes type 2. After placing road B, that section becomes type 2, but the section under road A is type 0
     
  18. Tion-Gaming

    Tion-Gaming

    Joined:
    Jan 30, 2015
    Posts:
    28
    Hey, you wouldnt happen to have a copy of that code laying around you can toss onto something like github gists? Ive become really interested in navmesh stuff recently and would love to see how you tackled some of those problems. Or if anyone in this post ended up cloning the repo i would love a link to check it out.
     
  19. Abbrew

    Abbrew

    Joined:
    Jan 1, 2018
    Posts:
    417
    The original code is at https://github.com/Unity-Technologies/NavMeshComponents. I think I made a mistake linking my old project's copy of that, but maybe I made local modifications to those files