123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159 |
- using UnityEngine;
- namespace Pathfinding {
- /// <summary>
- /// Helper for easier fast updates to recast graphs.
- ///
- /// When updating recast graphs, you might just have plonked down a few
- /// GraphUpdateScene objects or issued a few GraphUpdateObjects to the
- /// system. This works fine if you are only issuing a few but the problem
- /// is that they don't have any coordination in between themselves. So if
- /// you have 10 GraphUpdateScene objects in one tile, they will all update
- /// that tile (10 times in total) instead of just updating it once which
- /// is all that is required (meaning it will be 10 times slower than just
- /// updating one tile). This script exists to help with updating only the
- /// tiles that need updating and only updating them once instead of
- /// multiple times.
- ///
- /// It is coupled with the RecastTileUpdate component, which works a bit
- /// like the GraphUpdateScene component, just with fewer options. You can
- /// attach the RecastTileUpdate to any GameObject to have it schedule an
- /// update for the tile(s) that contain the GameObject. E.g if you are
- /// creating a new building somewhere, you can attach the RecastTileUpdate
- /// component to it to make it update the graph when it is instantiated.
- ///
- /// If a single tile contains multiple RecastTileUpdate components and
- /// many try to update the graph at the same time, only one tile update
- /// will be done, which greatly improves performance.
- ///
- /// If you have objects that are instantiated at roughly the same time
- /// but not exactly the same frame, you can use the maxThrottlingDelay
- /// field. It will delay updates up to that number of seconds to allow
- /// more updates to be batched together.
- ///
- /// Note: You should only have one instance of this script in the scene
- /// if you only have a single recast graph. If you have more than one
- /// graph you can have more than one instance of this script but you need
- /// to manually call the SetGraph method to configure it with the correct
- /// graph.
- ///
- /// Note: This does not use navmesh cutting. If you only ever add
- /// obstacles, but never add any new walkable surfaces then you might
- /// want to use navmesh cutting instead. See navmeshcutting (view in online documentation for working links).
- /// </summary>
- [AddComponentMenu("Pathfinding/Navmesh/RecastTileUpdateHandler")]
- [HelpURL("http://arongranberg.com/astar/documentation/stable/class_pathfinding_1_1_recast_tile_update_handler.php")]
- public class RecastTileUpdateHandler : MonoBehaviour {
- /// <summary>Graph that handles the updates</summary>
- RecastGraph graph;
- /// <summary>True for a tile if it needs updating</summary>
- bool[] dirtyTiles;
- /// <summary>True if any elements in dirtyTiles are true</summary>
- bool anyDirtyTiles = false;
- /// <summary>Earliest update request we are handling right now</summary>
- float earliestDirty = float.NegativeInfinity;
- /// <summary>All tile updates will be performed within (roughly) this number of seconds</summary>
- public float maxThrottlingDelay = 0.5f;
- public void SetGraph (RecastGraph graph) {
- this.graph = graph;
- if (graph == null)
- return;
- dirtyTiles = new bool[graph.tileXCount*graph.tileZCount];
- anyDirtyTiles = false;
- }
- /// <summary>Requests an update to all tiles which touch the specified bounds</summary>
- public void ScheduleUpdate (Bounds bounds) {
- if (graph == null) {
- // If no graph has been set, use the first graph available
- if (AstarPath.active != null) {
- SetGraph(AstarPath.active.data.recastGraph);
- }
- if (graph == null) {
- Debug.LogError("Received tile update request (from RecastTileUpdate), but no RecastGraph could be found to handle it");
- return;
- }
- }
- // Make sure that tiles which do not strictly
- // contain this bounds object but which still
- // might need to be updated are actually updated
- int voxelCharacterRadius = Mathf.CeilToInt(graph.characterRadius/graph.cellSize);
- int borderSize = voxelCharacterRadius + 3;
- // Expand borderSize voxels on each side
- bounds.Expand(new Vector3(borderSize, 0, borderSize)*graph.cellSize*2);
- var touching = graph.GetTouchingTiles(bounds);
- if (touching.Width * touching.Height > 0) {
- if (!anyDirtyTiles) {
- earliestDirty = Time.time;
- anyDirtyTiles = true;
- }
- for (int z = touching.ymin; z <= touching.ymax; z++) {
- for (int x = touching.xmin; x <= touching.xmax; x++) {
- dirtyTiles[z*graph.tileXCount + x] = true;
- }
- }
- }
- }
- void OnEnable () {
- RecastTileUpdate.OnNeedUpdates += ScheduleUpdate;
- }
- void OnDisable () {
- RecastTileUpdate.OnNeedUpdates -= ScheduleUpdate;
- }
- void Update () {
- if (anyDirtyTiles && Time.time - earliestDirty >= maxThrottlingDelay && graph != null) {
- UpdateDirtyTiles();
- }
- }
- /// <summary>Update all dirty tiles now</summary>
- public void UpdateDirtyTiles () {
- if (graph == null) {
- new System.InvalidOperationException("No graph is set on this object");
- }
- if (graph.tileXCount * graph.tileZCount != dirtyTiles.Length) {
- Debug.LogError("Graph has changed dimensions. Clearing queued graph updates and resetting.");
- SetGraph(graph);
- return;
- }
- for (int z = 0; z < graph.tileZCount; z++) {
- for (int x = 0; x < graph.tileXCount; x++) {
- if (dirtyTiles[z*graph.tileXCount + x]) {
- dirtyTiles[z*graph.tileXCount + x] = false;
- var bounds = graph.GetTileBounds(x, z);
- // Shrink it a bit to make sure other tiles
- // are not included because of rounding errors
- bounds.extents *= 0.5f;
- var guo = new GraphUpdateObject(bounds);
- guo.nnConstraint.graphMask = 1 << (int)graph.graphIndex;
- AstarPath.active.UpdateGraphs(guo);
- }
- }
- }
- anyDirtyTiles = false;
- }
- }
- }
|