Stateful Scriptable Objects
Here's an example of a Scriptable Object with state. Part of TilePlus Toolkit. Note this is free on the asset store but is copyrighted.
// ***********************************************************************
// Assembly : TilePlus
// Created : 03-25-2023
//
// Last Modified On : 04-03-2023
// ***********************************************************************
//
// Copyright (c) All rights reserved.
//
//
// ***********************************************************************
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
// ReSharper disable once RedundantUsingDirective
using UnityEditor;
using UnityEngine;
using UnityEngine.Tilemaps;
using static TilePlus.TpLib;
// ReSharper disable MemberCanBePrivate.Global
namespace TilePlus
{
///
/// TpZoneManager is used to manage square areas of tilemaps called Zones.
///
public class TpZoneManager : ScriptableObject
{
#region subscriptions
///
/// Notify me when a Zone Reg is added.
///
/// Be aware that when the ZoneManager instance is deleted this subscription expires
public event Action? OnZoneRegAdded;
///
/// Notify me when a Zone Reg is deleted.
///
/// Be aware that when the ZoneManager instance is deleted this subscription expires
public event Action? OnZoneRegDeleted;
///
/// Notify me that a list of TPT tiles will be deleted.
///
/// Be aware that when the ZoneManager instance is deleted this subscription expires
public event Action>? OnTptTilesWillBeDeleted;
#endregion
#region privateFields
///
/// Mapping between RectInts (locator) and Zone Registrations.
///
private Dictionary chunkMap = new ();
///
/// The default ChunkLocator from the size param to Initialize.
/// This defines the size of each chunk - it's size.x and size.y params
/// (both should be equal). This is available via a property.
///
private RectInt defaultLocator;
///
/// The starting position, the position of chunk zero.
///
private Vector2Int worldOrigin;
///
/// indicates that chunking is enabled after a call to Initialize.
///
private bool chunkingConfigured; //backup field for property
//holds the return value for RestoreFromRegistrationJson
private readonly List currentLoadresults = new(8);
//temporary list but use the same one repeatedly to reduce garbage
private List getZoneRegForChunkInternal = new(32);
//maps for this instance. clients need to use only these maps.
private Dictionary monitoredTilemaps = new();
//the name of this instance.
private string instanceName = string.Empty;
#endregion
#region properties
///
/// Get the all loading results
///
public IEnumerable GetAllZoneRegistrations => chunkMap.Values.OrderBy(zr => zr.dex);
///
/// Is chunking configured?
///
public bool ChunkingConfigured => chunkingConfigured;
///
/// Size of a chunk as set during initialization.
///
public int ChunkSize => defaultLocator.size.x;
///
/// The number of chunks in the chunkmap.
///
public int NumChunksInUse => chunkMap.Count;
///
/// Get a collection of the MonitoredTilemaps dictionary values: Tilemap instances. These are
/// the only ones that clients of this instance should be using.
///
public Dictionary.ValueCollection MonitoredTilemaps => monitoredTilemaps.Values;
///
/// Access to the Monitored Tilemaps for this instance. DO NOT ALTER THIS DICTIONARY. DON'T SAVE A REFERENCE.
///
public Dictionary MonitoredTilemapDict => monitoredTilemaps;
///
/// Get the default locator. This is the value used when
/// you don't specify a dimensions value when GetZoneReg
/// or GetLocator with dimensions = null.
///
// ReSharper disable once ConvertToAutoProperty
public RectInt DefaultLocator => defaultLocator;
///
/// Get the ChunkMapAnchorPosition. This is the base or startingPosition from where all Chunks begin.
/// You can find the center of a chunk by adding multiples of ChunkMapAnchorSize to ChunkMapAnchorPosition.
///
public Vector2Int WorldOrigin => worldOrigin;
///
/// The name of this instance. Read only
///
public string InstanceName => instanceName;
///
/// A ref to the ZoneLayout, if used with chunking system. Null otherwise.
///
public TpZoneLayout? ZoneLayoutComponent { get; set; }
#endregion
#region access
///
/// Reset all registrations, reset registrationIndex
///
/// Reset event descriptions (default=true)
public void ResetInstance(bool resetEvents = true )
{
chunkMap.Clear();
chunkingConfigured = false;
currentLoadresults.Clear();
getZoneRegForChunkInternal.Clear();
monitoredTilemaps.Clear();
if (!resetEvents)
return;
OnZoneRegDeleted = null;
OnZoneRegAdded = null;
OnTptTilesWillBeDeleted = null;
}
///
/// Add a TileFab chunk to the database. In general this should ONLY be
/// called from TileFabLib. CHUNKS ONLY.
///
/// The TileFab to load
/// mark the ZoneReg as immortal
/// placement offset
/// rotation
/// remapping dictionary
/// Asset GUIDs
/// Asset names
/// List of prefabs spawned when the TileFab was loaded. Note: not serialized
/// in the ZoneReg class instance created herein.
/// Tuple of ZoneReg and RectInt (locator) or null for error. AssetReg is null if error.
internal (ZoneReg? reg, RectInt locator) AddZone(TpTileFab? tileFab,
bool createAsImmortal,
Vector3Int offset,
TpTileBundle.TilemapRotation rotation,
Dictionary[]? posToGuidMaps,
string[]? bundleAssetGuids,
string[]? bundleAssetNames,
List? spawnedPrefabs)
{
if (!chunkingConfigured)
{
TpLogError("Cannot add Zones to TpZoneManager before it is configured. Use 'Initialize' first!!!");
return(null,defaultLocator);
}
if (!tileFab || posToGuidMaps == null || bundleAssetGuids == null || bundleAssetNames == null)
{
TpLogError("null TileFab, posToGuidMaps, bundleAssetGuids or bundleAssetName was passed to TpZoneLayout.AddZone.");
return(null,defaultLocator);
}
if (!tileFab.m_FromGridSelection)
{
TpLogError("Cannot use TpZoneManager.AddZone with a non-chunk TileFab!!! And you can't just click the 'From Grid Selection checkbox on the asset: that won't work correctly. PLease recreate the TileFabs using GridSelections!");
return(null,defaultLocator);
}
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
if(tileFab.m_TileAssets.Count == 0 || tileFab.m_TileAssets[0] == null || !tileFab.m_TileAssets[0].m_Asset)
{
TpLogError($"Invalid TileFab [{tileFab.name}]: does not have any bundles.");
return(null,defaultLocator);
}
//need a boundsInt for the chunk in order to create a 'locator' RectInt.
//we know that the TileFab has to have at least one Chunk.
//all chunk boundsInts are identical.
var chunkBoundsInt = tileFab.IsChunkified ? new BoundsInt(0,0,0,ChunkSize,ChunkSize,1) :
tileFab.LargestBounds; //this method does exactly that when a Fab is a chunk.
//update map from BoundsInt to reg (for camera-region culling)
//now compute the locator for this chunk: Basically it's a RectInt encompassing the entire Chunk as placed.
//NOTE THAT the position of a RectInt is NOT the center. It's the lower-left corner. This is fine
//as long as we're consistent.
var locator = GetLocatorForGridPosition(offset);
if (chunkMap.ContainsKey(locator))
{
TpLogError($"The locator [{locator}] already exists! Can't place at offset {offset}");
return(null,defaultLocator);
}
var reg = new ZoneReg(TileFabLib.RegistrationIndex,
locator,
tileFab.AssetGuidString,
tileFab.name,
offset,
rotation,
posToGuidMaps,
bundleAssetGuids,
bundleAssetNames,
chunkBoundsInt,
spawnedPrefabs);
if (createAsImmortal)
reg.imm = true;
var hash = new AssetGuidPositionHash(tileFab.TileFabGuid, offset);
return !AddRegistration(reg, hash)
? (null,defaultLocator)
: (reg,locator);
}
///
/// Add a registration. Only use if you're not using AddZone and creating your own ZoneRegs
///
/// The ZoneReg
/// An AssetGuidPositionHash instance
/// false for failure: means that there was an entry already existing for this locator.
public bool AddRegistration(ZoneReg reg, AssetGuidPositionHash hash)
{
var locator = reg.m_MyLocator;
if (!chunkMap.TryAdd(locator, reg))
{
#if UNITY_EDITOR
TpLogError($"Fatal: duplicate key {locator} in ChunkMap for reg {reg}. ");
#endif
return false;
}
TileFabLib.S_LoadedGuids?.Add(hash);
TileFabLib.IncrementRegistrationIndex();
OnZoneRegAdded?.Invoke(reg,this);
return true;
}
///
/// Is there a registration for this ZoneReg?
///
/// A ZoneReg
///
public bool HasZone(ZoneReg reg)
{
if (chunkingConfigured)
return chunkMap.ContainsKey(reg.m_MyLocator);
TpLogError("Cannot use TpZoneManager before it is configured. Use 'Initialize' first!!!");
return false;
}
///
/// Unload a list of Zones
///
/// List of ZoneRegs to delete
/// destroy tiles (default)
/// destroy prefabs (default)
/// false if failed
public bool UnloadZones(List regs, bool destroyTiles = true, bool destroyPrefabs = true)
{
var error = false;
foreach (var reg in regs)
error |= UnloadZone(reg, destroyTiles, destroyPrefabs);
return error;
}
///
/// Unload a list of Zones, Async
/// Does one reg per frame.
///
/// List of ZoneRegs to delete
/// destroy tiles (default)
/// destroy prefabs (default)
/// false if failed
public async Awaitable UnloadZonesAsync(List regs, bool destroyTiles = true, bool destroyPrefabs = true)
{
var success = true;
foreach (var reg in regs)
{
success &= UnloadZone(reg, destroyTiles, destroyPrefabs);
if (success)
await Awaitable.NextFrameAsync();
}
return success;
}
///
/// Unload ALL zones, including all parented prefabs.
///
///
public bool UnloadAllZones()
{
return UnloadZones(chunkMap.Values.ToList());
}
///
/// Unload a chunk
///
/// corresponding ZoneReg for the chunk you want to delete.
/// destroy tiles if true (default)
/// destroy prefabs if true (default)
/// true if successful.
/// runtime use ONLY
public bool UnloadZone(ZoneReg? reg, bool destroyTiles = true, bool destroyPrefabs = true)
{
if (reg == null)
{
TpLogError("Null ZoneReg passed to UnloadZone.");
return false;
}
//reserved zones are handled simply since there aren't any tiles/prefabs to delete.
if (reg.m_Reserved)
{
if (!DeleteZoneRegistration(reg))
TpLogWarning($"Could not delete this zonereg: {reg}");
OnZoneRegDeleted?.Invoke(reg,this);
return true;
}
if (!chunkingConfigured)
{
TpLogError("Cannot delete Zones from TpZoneManager before it is configured. Use 'Initialize' first!!!");
return false;
}
if (!chunkMap.ContainsKey(reg.m_MyLocator))
{
TpLogError($"Unknown ZoneReg [{reg}], can't delete zone!");
return false;
}
if (destroyTiles)
{
//area is 'largestbounds' from the asset
var eraseBounds = reg.lb;
//offset it
eraseBounds.position += reg.offs;
var sz = eraseBounds.size;
sz.z = 1;
eraseBounds.size = sz;
var ri = TpTileUtils.RectIntFromBoundsInt(eraseBounds, Vector3Int.zero);
//Debug.Log($"bounds {eraseBounds} rectint {ri}");
//todo: could cache depending on chunk size.
var nulls = new TileBase[sz.x * sz.y]; //these should all be null.
//get a list of TPBs (which is cleared as required for
using (TpLib.S_TilePlusBaseList_Pool.Get(out var pTiles))
{
if (pTiles != null)
{
foreach (var map in monitoredTilemaps.Values)
{
var pos = map.transform.position;
var gridPos = map.WorldToCell(pos);
var ri2 = new RectInt((Vector2Int)gridPos + ri.position, ri.size);
TpLib.GetAllTilesInRegionForMap(map, pTiles, ri2);
OnTptTilesWillBeDeleted?.Invoke(map, pTiles); //event.
map.SetTilesBlock(eraseBounds, nulls);
}
}
}
}
if (destroyPrefabs && reg.m_Prefabs != null)
{
//destroy any prefabs
if (reg.m_Prefabs.Count != 0)
{
foreach (var gameObj in reg.m_Prefabs)
{
if (gameObj.TryGetComponent(out var link))
{
link.DespawnMe();
continue;
}
#if UNITY_EDITOR
UnityEngine.Object.DestroyImmediate(gameObj, false);
#else
UnityEngine.Object.Destroy(gameObj);
#endif
}
}
}
if (!DeleteZoneRegistration(reg))
TpLogWarning($"Could not delete this zonereg: {reg}");
OnZoneRegDeleted?.Invoke(reg,this);
return true;
}
#endregion
#region chunking
///
/// Required if you want to use chunking. No chunking data is accumulated and
/// chunking will not work if this isn't used. Note that you should use this again for
/// every new scene. WIPES OUT ANY EXISTING TILEFAB REGISTRATION DATA WHEN USED!!
///
/// Size of a chunk. Must be even, rounded up if not.
/// Min=4. 4x4, 6x6, 8x8 ... 16x16 chunks etc
/// The origin position, the base position, such as
/// Vector3Int.zero, where the chunk numbers should be centered. If null then Vector3Int.zero is used.
/// sets certain data structures' initial size. Base this on the total
/// number of chunks of 'size' that would be in your camera's FOV at one time (for example). There's no problem
/// if the max num chunks is exceeded, this just allocates memory early on given your best estimate of what's
/// required as set in this method.
public void Initialize(int size, Vector3Int? origin = null, int initialMaxNumChunks = 64)
{
origin ??= Vector3Int.zero; //default for origin position
if (size < 4) //this would be a 4x4 TileFab which is ridiculously (?) small.
size = 4;
//test for an even number.
if (size % 2 != 0) //remainder should be zero if this is a multiple of 2.
size++;
//allocate memory for arrays.
chunkMap = new Dictionary(initialMaxNumChunks);
getZoneRegForChunkInternal = new List(initialMaxNumChunks / 4);
defaultLocator = new RectInt(Vector2Int.zero, new Vector2Int(size, size));
worldOrigin = new Vector2Int(origin.Value.x, origin.Value.y);
chunkingConfigured = true;
}
///
/// Get the ZoneRegs for a Zone locator RectInt
///
/// the RectInt chunk locator
/// ZoneReg List, list is Empty for error
/// return empty list if chunking not enabled or chunklocator is null.
/// Note that the same list is cleared and re-used every time that this is called.
public List GetZoneRegsForRegion(RectInt? locator)
{
getZoneRegForChunkInternal.Clear();
if (!chunkingConfigured || !locator.HasValue || locator.Value.size == Vector2Int.zero)
return getZoneRegForChunkInternal;
var loc = locator.Value;
// ReSharper disable once ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator
foreach (var item in chunkMap.Keys)
{
if (item.Overlaps(loc))
getZoneRegForChunkInternal.Add(chunkMap[item]);
}
return getZoneRegForChunkInternal;
}
///
/// Obtain two datasets: one is a List of ZoneRegs that are outside of an area and another
/// is a HashSet of ZoneRegs that are inside the area.
///
/// the RectInt Locator describing the area.
/// ref HashSet for inside
/// ref List for outside
/// false if any error occurs
public bool FindRegionalZoneRegs(RectInt? locator, ref HashSet inside, ref List outside)
{
if (!chunkingConfigured || !locator.HasValue)
return false;
var loc = locator.Value;
inside.Clear();
outside.Clear();
// return other.xMin < this.xMax && other.xMax > this.xMin && other.yMin < this.yMax && other.yMax > this.yMin;
// ReSharper disable once ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator
foreach ((var zone, var reg) in chunkMap)
{
if (zone.Overlaps(loc)) //if ANY part of the locator/zone overlaps the 'locator' RectInt
inside.Add(zone); //inside the locator's region
else
outside.Add(reg); //outside the locator's region
}
return true;
}
///
/// Get the ZoneRegs for a chunk located at a Grid position.
///
/// The position to use when searching for Zone registrations
/// [Nullable] if not null, this is the search area. If null, ChunkMapAnchorPosition as set by Initialize
/// Align to grid. Default=true
/// a list of ZoneRegs. Empty list is valid and means error or nothing found.
public List GetZoneRegsForGridPosition(Vector3Int gridPosition, Vector2Int? dimensions = null, bool align = true)
{
return GetZoneRegsForRegion(GetLocatorForGridPosition(gridPosition,dimensions,align));
}
///
/// Get the Zone registration for a chunk located at a World position.
///
/// The position to use when searching for Zone registrations
/// Tilemap to use for translating world to grid positions. If null, an empty list is returned.
/// [Nullable] if not null, this is the search area. If null, ChunkMapAnchorPosition as set by Initialize
/// Align to grid. Default=true
/// a list of ZoneRegs. Empty list is valid and means error or nothing found.
public List GetZoneRegsForWorldPosition(Vector3 worldPosition, Tilemap? map, Vector2Int? dimensions = null, bool align = true)
{
if (map)
return GetZoneRegsForRegion(GetLocatorForWorldPosition(worldPosition, map, dimensions, align));
getZoneRegForChunkInternal.Clear();
return getZoneRegForChunkInternal;
}
///
/// Create an Zone registration locator from a grid position
///
/// position on a Tilemap
/// [Nullable] optional size of locator. If null, ChunkMapAnchorPosition as set by Initialize
/// Align to grid. Default=true
/// RectInt Zone registration locator
public RectInt GetLocatorForGridPosition(Vector3Int gridPosition, Vector2Int ?dimensions = null, bool align = true)
{
if (align)
gridPosition = AlignToGrid(gridPosition);
return new RectInt((Vector2Int) gridPosition + worldOrigin, dimensions ?? defaultLocator.size);
}
///
/// Create an Zone registration locator from a world position
///
/// world position
/// Tilemap to use for translating world to grid positions. If null, new RectInt() is returned.
/// [Nullable] optional size of locator. If null, ChunkMapAnchorPosition as set by Initialize
/// Align to grid. Default=true
/// RectInt Zone registration locator
public RectInt GetLocatorForWorldPosition(Vector3 position, Tilemap? map, Vector2Int? dimensions = null, bool align = true)
{
return !map ? new RectInt()
: GetLocatorForGridPosition(map.WorldToCell(position), dimensions,align);
}
///
/// Is there a Zone registration associated with a RectInt locator?
///
/// the RectInt to check
/// true if there's already a locator there.
public bool HasZoneRegForLocator(RectInt locator)
{
return chunkMap.ContainsKey(locator);
}
///
/// Get a Zone Reg for a locator RectInt
///
/// a locator
/// a registration
/// true if reg was found. Note: if false the reg is default
public bool GetZoneRegForLocator(RectInt locator, out ZoneReg? reg)
{
return chunkMap.TryGetValue(locator, out reg);
}
///
/// Convert a super-grid position to a locator
///
/// s sGrid postion
/// dimensions of locator or null for default
/// a Locator.
public RectInt GetLocatorForSgridPosition(Vector2Int sGridPosition, Vector2Int? dimensions = null)
{
var chunkSize = defaultLocator.size.x;
var gridPos = new Vector3Int(sGridPosition.x * chunkSize, sGridPosition.y * chunkSize);
return GetLocatorForGridPosition(gridPos, dimensions);
}
///
/// Get a Tilemap grid position from a sGrid position.
///
/// a sGrid position
/// a Tilemap grid position
public Vector3Int GetGridPositionForSgridPosition(Vector2Int sGridPosition)
{
var chunkSize = defaultLocator.size.x;
return new Vector3Int(sGridPosition.x * chunkSize, sGridPosition.y * chunkSize);
}
#endregion
#region utils
///
/// Get a JSON version of the Zone registrations
///
///
///
public string GetZoneRegJson(bool prettyPrint = true)
{
var registrations= chunkMap.Values.OrderBy(zr => zr.dex).ToArray();
var loadWrapper = new LoadWrapper(registrations);
return JsonUtility.ToJson(loadWrapper, prettyPrint);
}
///
/// Restore all Tilefabs or Bundles based on a json-archived dataset.
///
/// data string to decode
/// optional mapping from tilemap name to Tilemap instance. Speeds tilemap lookups greatly
/// a Func of [(enum)FilterDataSource, object] returning a bool. See also LoadTileFab.
/// if true (default) only applies filters to TilePLus tiles, which is usually sufficient and saves much time.
/// A list of the TilefabLoadResults from however many Bundles are in the TileFab. Null is returned for errors
/// The list of TilefabLoadResults is cleared the next time that this method is used.
public List? RestoreFromZoneRegJson(string jsonString,
Dictionary? targetMap = null,
Func? filter = null,
bool filterOnlyTilePlusTiles = true
)
{
var data = JsonUtility.FromJson(jsonString);
if(data == null)
return null;
var loadResultsArray = data.m_Res.ToList();
loadResultsArray.Sort(Comparison); //ensure sorted in ascending index order.
var numLoadsToMake = loadResultsArray.Count;
var numPrevLoads = currentLoadresults.Count;
currentLoadresults.Clear(); //absolutely required to avoid exception from next line if count < capacity (no this was thought of in advance)
if(numLoadsToMake > numPrevLoads) //don't play with Capacity unless enlarging.
currentLoadresults.Capacity = loadResultsArray.Count;
var loadFlags = filterOnlyTilePlusTiles
? FabOrBundleLoadFlags.NormalWithFilter
: FabOrBundleLoadFlags.Normal;
foreach (var r in loadResultsArray)
{
if(!TileFabLib.GetTileFabFromGuid(r.g, out var fab) || !fab)
continue;
var result = TileFabLib.LoadTileFab(null,
fab,
r.offs,
r.rot,
loadFlags,
filter,
targetMap,
this);
if(result == null)
continue;
currentLoadresults.Add(result);
TileFabLib.UpdateGuidLookup(r, result.ZoneReg!);
}
return currentLoadresults;
}
private int Comparison(ZoneReg x, ZoneReg y)
{
if (x.dex < y.dex)
return -1;
return x.dex == y.dex ? 0 : 1;
}
///
/// Sets the name and managed Tilemaps for this instance.
/// Note you can only do this once.
///
/// instance name
/// Dictionary of tilemap names to tilemap instances.
/// false if this has been called already.
/// This is only called by TileFabLib when creating a ZoneManager instance.
internal bool SetNameAndMap(string iName, Dictionary stringToTilemap)
{
if (!string.IsNullOrEmpty(instanceName) || string.IsNullOrEmpty(iName))
return false;
instanceName = iName;
monitoredTilemaps = stringToTilemap;
return true;
}
///
/// Set or change the monitored Tilemaps dict.
///
/// Dict of stringTilemapName -> TilemapInstance
internal void SetMaps(Dictionary stringToTilemap)
{
monitoredTilemaps = stringToTilemap;
}
///
/// Remove an Zone registration given an instance of one
///
/// ZoneReg instance
/// true if found
public bool DeleteZoneRegistration(ZoneReg reg)
{
var locator = reg.m_MyLocator;
if (!chunkMap.Remove(locator))
{
TpLogError($"Could not delete ZoneReg {reg}");
return false;
}
if (reg.m_Reserved)
return true;
//need to delete all entries from the s_LoadedGuidLookup that were originally added.
//this is done by getting the new GUIDs from the registration, then doing a
//reverse lookup. That gets us the OLD guid which is the key for the LoadedGuidLookup dictionary.
//of course also need to remove the corresponding item in the reverse-lookup dictionary
// ReSharper disable once LoopCanBePartlyConvertedToQuery
foreach (var bundleGuidMap in reg.ptgm) //these are the GUIDs assigned when loaded via LoadTileFab
TileFabLib.RemoveGuidLookup(bundleGuidMap);
TileFabLib.S_LoadedGuids!.Remove(new AssetGuidPositionHash(new Guid(reg.g), reg.offs));
return true;
}
///
/// Get the last N Zone registrations.
///
/// # of results desired. For a tilefab that should be 1
/// Enumerable of registrations, which could be empty.
public IEnumerable GetLastRegistrations(int numResults = 1)
{
return chunkMap.Values.OrderBy(zr => zr.dex).TakeLast(numResults);
}
///
/// Get the very last Zone Reg. Handy when you know there is only one.
///
/// the last zone reg used or a new one (index will be 0) if there aren't any regs in the ChunkMap for this ZM.
public ZoneReg? GetLastRegistration()
{
return chunkMap.Count == 0 ? new ZoneReg() : chunkMap.Values.OrderBy(zr => zr.dex).Last();
}
///
/// Get all zone registrations with optional filtering and ordering
///
/// order by ZoneReg index if true
/// Func of ZoneReg returning bool. If ret val true then zm returned else it is skipped.
/// IEnumerable of ZoneReg instances. DON'T HOLD REFERENCES TO THESE!!! will make a memory leak.
public IEnumerable GetAllZoneRegistrationsFiltered(bool orderByIndex=false, Func? filter = null)
{
if (filter != null)
return orderByIndex
? chunkMap.Values.OrderBy(zr => zr.dex).Where(filter)
: chunkMap.Values.Where(filter);
if(orderByIndex)
return chunkMap.Values.OrderBy(zr => zr.dex);
return chunkMap.Values;
}
///
/// Is this grid position aligned to the super-grid?
///
/// position to test
/// true if aligned
public bool IsAlignedToGrid(Vector3Int position)
{
var relativePosition = position - (Vector3Int)worldOrigin;
var size = defaultLocator.size.x;
return relativePosition.x % size == 0 && relativePosition.y % size == 0;
}
///
/// Align a position to the super-grid. Note: positions are aligned to the lower-left corner of a rectint.
///
/// position to adjust
/// Adjusted position. Won't change if already aligned.
public Vector3Int AlignToGrid(Vector3Int position)
{
if (IsAlignedToGrid(position))
return position;
var relPos = position - (Vector3Int)worldOrigin;
var size = defaultLocator.size.x;
var diffX = relPos.x % size;
var diffY = relPos.y % size;
return new Vector3Int(relPos.x - diffX, relPos.y - diffY, position.z);
}
#endregion
}
}