h8man / NavMeshPlus

Unity NavMesh 2D Pathfinding
MIT License
1.81k stars 209 forks source link

CollectTilemapSourcesCache2d (CollectSourcesCache2d for tilemaps) #201

Closed snarlynarwhal closed 4 months ago

snarlynarwhal commented 5 months ago

Hi, from my understanding, CollectSourcesCache2d does not support tilemaps, at least not on a per tile basis. So I wrote this extension and wanted to share it and see if you were interested in me making a PR and to see what changes you might want me to make before submitting a PR (aside from naming conventions which I made note of).

using NavMeshPlus.Components;
using NavMeshPlus.Extensions;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.Tilemaps;

namespace NavMeshPlus.Extensions
{
    [ExecuteAlways]
    [AddComponentMenu("Navigation/Navigation CacheTilemapSources2d", 30)]
    public class CollectTilemapSourcesCache2d : NavMeshExtension
    {
        public List<NavMeshBuildSource> Cache => cache;

        [SerializeField] private Tilemap tilemap;
        [SerializeField] private NavMeshModifier modifier;
        [SerializeField] private NavMeshModifierTilemap modifierTilemap;

        private Dictionary<TileBase, NavMeshModifierTilemap.TileModifier> modifierMap;
        private Dictionary<Vector3Int, int> lookup;
        private List<NavMeshBuildSource> cache;

        protected override void Awake()
        {
            base.Awake();
            modifier = tilemap.GetComponent<NavMeshModifier>();
            modifierTilemap = tilemap.GetComponent<NavMeshModifierTilemap>();
            modifierMap = modifierTilemap.GetModifierMap();
        }

        private void OnTilemapTileChanged(Tilemap map, Tilemap.SyncTile[] syncs)
        {
            if (map == tilemap)
            {
                foreach (Tilemap.SyncTile sync in syncs)
                {
                    Vector3Int position = sync.position;
                    if (sync.tile != null && modifierMap.TryGetValue(sync.tile, out NavMeshModifierTilemap.TileModifier tileModifier))
                    {
                        int i = lookup[position];
                        NavMeshBuildSource source = cache[i];
                        source.area = tileModifier.area;
                        cache[i] = source;
                    }
                    else if (modifier.overrideArea)
                    {
                        int i = lookup[position];
                        NavMeshBuildSource source = cache[i];
                        source.area = modifier.area;
                        cache[i] = source;
                    }
                }
            }
        }

        public override void PostCollectSources(NavMeshSurface surface, List<NavMeshBuildSource> sources, NavMeshBuilderState navNeshState)
        {
            cache = sources;
            if (lookup == null)
            {
                lookup = new Dictionary<Vector3Int, int>();
                for (int i = 0; i < cache.Count; i++)
                {
                    NavMeshBuildSource source = cache[i];
                    Vector3Int position = tilemap.WorldToCell(source.transform.GetPosition());
                    lookup[position] = i;
                }
            }
            Tilemap.tilemapTileChanged -= OnTilemapTileChanged;
            Tilemap.tilemapTileChanged += OnTilemapTileChanged;
        }

        protected override void OnDestroy()
        {
            base.OnDestroy();
            Tilemap.tilemapTileChanged -= OnTilemapTileChanged;
        }
    }
}
h8man commented 5 months ago

Looks good. Let me check how can I make cache to work in composition style. Like a stack of caches that can sink to one collection.

h8man commented 5 months ago

Make pull request and I will refactor later

snarlynarwhal commented 4 months ago

Done!

https://github.com/h8man/NavMeshPlus/pull/202

h8man commented 4 months ago

congratulations, now you are contributor. After refactoring I'll add component into documentation. I want to have 1 cache that covers all builder sources.