683 lines
22 KiB
C#
683 lines
22 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using UnityEngine;
|
|
using UnityEngine.AddressableAssets;
|
|
using UnityEngine.ResourceManagement.AsyncOperations;
|
|
|
|
public class GeoTileAddressablesLoader : MonoBehaviour
|
|
{
|
|
[Header("References")]
|
|
[SerializeField] private Transform player;
|
|
[SerializeField] private Transform tilesParent;
|
|
|
|
[Header("Paths")]
|
|
[SerializeField] private string tileBundleFolderName = "TileBundles";
|
|
[SerializeField] private string manifestFileName = "TileManifest.json";
|
|
[SerializeField] private string buildingManifestFileName = "TileBuildingsManifest.json";
|
|
[SerializeField] private string buildTargetFolderOverride = "";
|
|
|
|
[Header("Streaming")]
|
|
[SerializeField] private float loadRadiusMeters = 1500f;
|
|
[SerializeField] private float unloadRadiusMeters = 2000f;
|
|
[SerializeField] private float updateInterval = 0.5f;
|
|
[SerializeField] private int maxConcurrentLoads = 2;
|
|
[SerializeField] private bool verboseLogging = false;
|
|
[SerializeField] private bool useEmbeddedBuildingsInTilePrefabs = true;
|
|
|
|
[Header("Buildings (2km blocks)")]
|
|
[SerializeField] private bool loadBuildingsFor2kmBlocks = true;
|
|
[SerializeField] private int buildingBlockSizeInTiles = 2;
|
|
|
|
private TileManifest manifest;
|
|
private TileManifest buildingManifest;
|
|
private readonly Dictionary<string, TileEntry> tiles = new Dictionary<string, TileEntry>();
|
|
private readonly Dictionary<string, GameObject> loaded = new Dictionary<string, GameObject>();
|
|
private readonly HashSet<string> loading = new HashSet<string>();
|
|
private readonly HashSet<string> queued = new HashSet<string>();
|
|
private readonly Queue<string> loadQueue = new Queue<string>();
|
|
private readonly Dictionary<string, TileEntry> buildingTiles = new Dictionary<string, TileEntry>();
|
|
private readonly Dictionary<string, GameObject> buildingLoaded = new Dictionary<string, GameObject>();
|
|
private readonly HashSet<string> buildingLoading = new HashSet<string>();
|
|
private readonly HashSet<string> buildingQueued = new HashSet<string>();
|
|
private readonly Queue<string> buildingLoadQueue = new Queue<string>();
|
|
private readonly Dictionary<string, string> tileBlockKey = new Dictionary<string, string>();
|
|
private readonly Dictionary<string, string> blockBuildingKey = new Dictionary<string, string>();
|
|
|
|
private bool initialized;
|
|
private float nextUpdateTime;
|
|
|
|
private void OnValidate()
|
|
{
|
|
if (unloadRadiusMeters < loadRadiusMeters)
|
|
unloadRadiusMeters = loadRadiusMeters;
|
|
if (maxConcurrentLoads < 1)
|
|
maxConcurrentLoads = 1;
|
|
if (updateInterval < 0.05f)
|
|
updateInterval = 0.05f;
|
|
if (buildingBlockSizeInTiles < 1)
|
|
buildingBlockSizeInTiles = 1;
|
|
}
|
|
|
|
private void Awake()
|
|
{
|
|
if (tilesParent == null)
|
|
{
|
|
var parent = new GameObject("Geo_Tile_Addressables");
|
|
tilesParent = parent.transform;
|
|
}
|
|
}
|
|
|
|
private void Start()
|
|
{
|
|
if (player == null && Camera.main != null)
|
|
player = Camera.main.transform;
|
|
|
|
StartCoroutine(Initialize());
|
|
}
|
|
|
|
private IEnumerator Initialize()
|
|
{
|
|
var buildTargetFolder = string.IsNullOrWhiteSpace(buildTargetFolderOverride)
|
|
? GetBuildTargetFolderName()
|
|
: buildTargetFolderOverride;
|
|
|
|
var basePath = Path.Combine(Application.persistentDataPath, tileBundleFolderName, buildTargetFolder);
|
|
var manifestPath = Path.Combine(basePath, manifestFileName);
|
|
|
|
Debug.Log(
|
|
$"[GeoTileAddressablesLoader] Startup: platform={Application.platform}, " +
|
|
$"persistentDataPath={Application.persistentDataPath}, basePath={basePath}, manifestPath={manifestPath}");
|
|
|
|
Log($"Initializing loader. BasePath={basePath}");
|
|
Log($"ManifestPath={manifestPath}");
|
|
|
|
if (!File.Exists(manifestPath))
|
|
{
|
|
Debug.LogError($"[GeoTileAddressablesLoader] Manifest not found: {manifestPath}");
|
|
yield break;
|
|
}
|
|
|
|
manifest = JsonUtility.FromJson<TileManifest>(File.ReadAllText(manifestPath));
|
|
if (manifest == null || manifest.tiles == null || manifest.tiles.Length == 0)
|
|
{
|
|
Debug.LogError("[GeoTileAddressablesLoader] Manifest is empty or invalid.");
|
|
yield break;
|
|
}
|
|
|
|
tiles.Clear();
|
|
foreach (var tile in manifest.tiles)
|
|
{
|
|
var key = !string.IsNullOrWhiteSpace(tile.tileKey) ? tile.tileKey : tile.tileId;
|
|
if (!string.IsNullOrWhiteSpace(key))
|
|
tiles[key] = tile;
|
|
}
|
|
|
|
LoadBuildingManifest(basePath);
|
|
BuildBuildingBlocks();
|
|
|
|
Debug.Log(
|
|
$"[GeoTileAddressablesLoader] Manifest loaded: path={manifestPath}, buildTarget={manifest.buildTarget}, " +
|
|
$"catalogFile={manifest.catalogFile}, catalogHashFile={manifest.catalogHashFile}, tileCount={tiles.Count}");
|
|
|
|
Log($"Manifest loaded. Tiles={tiles.Count} CatalogFile={manifest.catalogFile}");
|
|
|
|
var catalogPath = Path.Combine(basePath, manifest.catalogFile);
|
|
if (!File.Exists(catalogPath))
|
|
{
|
|
Debug.LogError($"[GeoTileAddressablesLoader] Catalog not found: {catalogPath}");
|
|
yield break;
|
|
}
|
|
|
|
var catalogUri = ToFileUri(catalogPath);
|
|
Debug.Log($"[GeoTileAddressablesLoader] Loading catalog: path={catalogPath}, uri={catalogUri}");
|
|
|
|
Log($"Loading catalog: {catalogPath}");
|
|
var handle = Addressables.LoadContentCatalogAsync(catalogUri, true);
|
|
yield return handle;
|
|
|
|
if (handle.Status != AsyncOperationStatus.Succeeded)
|
|
{
|
|
Debug.LogError($"[GeoTileAddressablesLoader] Catalog load failed: {handle.OperationException}");
|
|
yield break;
|
|
}
|
|
|
|
Debug.Log($"[GeoTileAddressablesLoader] Catalog loaded successfully: {manifest.catalogFile}");
|
|
Log("Catalog loaded successfully.");
|
|
|
|
// Sanity-check key resolution against the loaded catalog so logcat immediately
|
|
// shows whether the runtime catalog actually contains our tile keys.
|
|
yield return StartCoroutine(LogCatalogKeyProbe());
|
|
|
|
initialized = true;
|
|
nextUpdateTime = Time.time;
|
|
}
|
|
|
|
private IEnumerator LogCatalogKeyProbe()
|
|
{
|
|
int probeCount = 0;
|
|
foreach (var kvp in tiles)
|
|
{
|
|
var key = kvp.Key;
|
|
var locationsHandle = Addressables.LoadResourceLocationsAsync(key);
|
|
yield return locationsHandle;
|
|
|
|
int locationCount = 0;
|
|
if (locationsHandle.Status == AsyncOperationStatus.Succeeded && locationsHandle.Result != null)
|
|
locationCount = locationsHandle.Result.Count;
|
|
|
|
Debug.Log($"[GeoTileAddressablesLoader] KeyProbe tile key={key} locations={locationCount}");
|
|
Addressables.Release(locationsHandle);
|
|
|
|
probeCount++;
|
|
if (probeCount >= 5)
|
|
break;
|
|
}
|
|
|
|
if (loadBuildingsFor2kmBlocks && buildingTiles.Count > 0)
|
|
{
|
|
int buildingProbeCount = 0;
|
|
foreach (var kvp in buildingTiles)
|
|
{
|
|
var key = kvp.Key;
|
|
var locationsHandle = Addressables.LoadResourceLocationsAsync(key);
|
|
yield return locationsHandle;
|
|
|
|
int locationCount = 0;
|
|
if (locationsHandle.Status == AsyncOperationStatus.Succeeded && locationsHandle.Result != null)
|
|
locationCount = locationsHandle.Result.Count;
|
|
|
|
Debug.Log($"[GeoTileAddressablesLoader] KeyProbe building key={key} locations={locationCount}");
|
|
Addressables.Release(locationsHandle);
|
|
|
|
buildingProbeCount++;
|
|
if (buildingProbeCount >= 5)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void LoadBuildingManifest(string basePath)
|
|
{
|
|
buildingManifest = null;
|
|
buildingTiles.Clear();
|
|
|
|
if (!loadBuildingsFor2kmBlocks || string.IsNullOrWhiteSpace(buildingManifestFileName))
|
|
return;
|
|
|
|
var path = Path.Combine(basePath, buildingManifestFileName);
|
|
Debug.Log($"[GeoTileAddressablesLoader] Building manifest path: {path}");
|
|
if (!File.Exists(path))
|
|
{
|
|
Log($"Building manifest not found: {path}");
|
|
return;
|
|
}
|
|
|
|
buildingManifest = JsonUtility.FromJson<TileManifest>(File.ReadAllText(path));
|
|
if (buildingManifest == null || buildingManifest.tiles == null || buildingManifest.tiles.Length == 0)
|
|
{
|
|
Log("Building manifest is empty or invalid.");
|
|
return;
|
|
}
|
|
|
|
foreach (var tile in buildingManifest.tiles)
|
|
{
|
|
var key = !string.IsNullOrWhiteSpace(tile.tileKey) ? tile.tileKey : tile.tileId;
|
|
if (!string.IsNullOrWhiteSpace(key))
|
|
buildingTiles[key] = tile;
|
|
}
|
|
|
|
Debug.Log(
|
|
$"[GeoTileAddressablesLoader] Building manifest loaded: path={path}, buildTarget={buildingManifest.buildTarget}, " +
|
|
$"catalogFile={buildingManifest.catalogFile}, tileCount={buildingTiles.Count}");
|
|
Log($"Building manifest loaded. BuildingTiles={buildingTiles.Count}");
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (!initialized || player == null)
|
|
return;
|
|
|
|
if (Time.time < nextUpdateTime)
|
|
return;
|
|
|
|
nextUpdateTime = Time.time + updateInterval;
|
|
|
|
UpdateTileSet();
|
|
ProcessQueue();
|
|
}
|
|
|
|
private void UpdateTileSet()
|
|
{
|
|
var playerPos = player.position;
|
|
var tileSize = manifest.tileSizeMeters;
|
|
var wantedTiles = new HashSet<string>();
|
|
HashSet<string> wantedBlocks = (loadBuildingsFor2kmBlocks && buildingTiles.Count > 0)
|
|
? new HashSet<string>()
|
|
: null;
|
|
|
|
foreach (var kvp in tiles)
|
|
{
|
|
var tileKey = kvp.Key;
|
|
var tile = kvp.Value;
|
|
var tileCenter = new Vector3(
|
|
tile.offsetX + tileSize * 0.5f,
|
|
tile.baseY,
|
|
tile.offsetZ + tileSize * 0.5f);
|
|
var distance = Vector2.Distance(
|
|
new Vector2(playerPos.x, playerPos.z),
|
|
new Vector2(tileCenter.x, tileCenter.z));
|
|
|
|
bool withinLoad = distance <= loadRadiusMeters;
|
|
bool withinKeep = distance <= unloadRadiusMeters;
|
|
|
|
if (withinLoad || (withinKeep && loaded.ContainsKey(tileKey)))
|
|
wantedTiles.Add(tileKey);
|
|
|
|
if (wantedBlocks != null && tileBlockKey.TryGetValue(tileKey, out var blockKey))
|
|
{
|
|
if (withinLoad)
|
|
wantedBlocks.Add(blockKey);
|
|
else if (withinKeep && blockBuildingKey.TryGetValue(blockKey, out var buildingKey) && buildingLoaded.ContainsKey(buildingKey))
|
|
wantedBlocks.Add(blockKey);
|
|
}
|
|
}
|
|
|
|
foreach (var tileKey in wantedTiles)
|
|
EnqueueTileLoad(tileKey);
|
|
|
|
if (loaded.Count == 0)
|
|
{
|
|
if (wantedBlocks == null)
|
|
return;
|
|
}
|
|
|
|
var toUnload = new List<string>();
|
|
foreach (var kvp in loaded)
|
|
{
|
|
if (!wantedTiles.Contains(kvp.Key))
|
|
toUnload.Add(kvp.Key);
|
|
}
|
|
|
|
for (int i = 0; i < toUnload.Count; i++)
|
|
UnloadTile(toUnload[i]);
|
|
|
|
if (wantedBlocks == null)
|
|
return;
|
|
|
|
var wantedBuildingKeys = new HashSet<string>();
|
|
foreach (var blockKey in wantedBlocks)
|
|
{
|
|
if (blockBuildingKey.TryGetValue(blockKey, out var buildingKey))
|
|
wantedBuildingKeys.Add(buildingKey);
|
|
}
|
|
|
|
foreach (var buildingKey in wantedBuildingKeys)
|
|
EnqueueBuildingLoad(buildingKey);
|
|
|
|
if (buildingLoaded.Count == 0)
|
|
return;
|
|
|
|
var toUnloadBuildings = new List<string>();
|
|
foreach (var kvp in buildingLoaded)
|
|
{
|
|
if (!wantedBuildingKeys.Contains(kvp.Key))
|
|
toUnloadBuildings.Add(kvp.Key);
|
|
}
|
|
|
|
for (int i = 0; i < toUnloadBuildings.Count; i++)
|
|
UnloadBuilding(toUnloadBuildings[i]);
|
|
}
|
|
|
|
private void EnqueueTileLoad(string tileKey)
|
|
{
|
|
if (loaded.ContainsKey(tileKey) || loading.Contains(tileKey) || queued.Contains(tileKey))
|
|
return;
|
|
|
|
loadQueue.Enqueue(tileKey);
|
|
queued.Add(tileKey);
|
|
}
|
|
|
|
private void EnqueueBuildingLoad(string buildingKey)
|
|
{
|
|
if (buildingLoaded.ContainsKey(buildingKey) || buildingLoading.Contains(buildingKey) || buildingQueued.Contains(buildingKey))
|
|
return;
|
|
|
|
buildingLoadQueue.Enqueue(buildingKey);
|
|
buildingQueued.Add(buildingKey);
|
|
}
|
|
|
|
private void ProcessQueue()
|
|
{
|
|
bool preferTiles = true;
|
|
while (loadQueue.Count > 0 || buildingLoadQueue.Count > 0)
|
|
{
|
|
int inFlight = loading.Count + buildingLoading.Count;
|
|
if (inFlight >= maxConcurrentLoads)
|
|
return;
|
|
|
|
if ((preferTiles && loadQueue.Count > 0) || buildingLoadQueue.Count == 0)
|
|
{
|
|
var tileKey = loadQueue.Dequeue();
|
|
queued.Remove(tileKey);
|
|
StartLoad(tileKey);
|
|
}
|
|
else
|
|
{
|
|
var buildingKey = buildingLoadQueue.Dequeue();
|
|
buildingQueued.Remove(buildingKey);
|
|
StartBuildingLoad(buildingKey);
|
|
}
|
|
|
|
preferTiles = !preferTiles;
|
|
}
|
|
}
|
|
|
|
private void StartLoad(string tileKey)
|
|
{
|
|
if (!tiles.TryGetValue(tileKey, out var tile))
|
|
return;
|
|
|
|
loading.Add(tileKey);
|
|
Log($"Loading tile {tileKey}...");
|
|
|
|
var handle = Addressables.InstantiateAsync(tileKey, tilesParent);
|
|
handle.Completed += op =>
|
|
{
|
|
loading.Remove(tileKey);
|
|
|
|
if (op.Status != AsyncOperationStatus.Succeeded)
|
|
{
|
|
Debug.LogError($"[GeoTileAddressablesLoader] Load failed for {tileKey}: {op.OperationException}");
|
|
return;
|
|
}
|
|
|
|
var instance = op.Result;
|
|
instance.name = tileKey;
|
|
instance.transform.position = new Vector3(tile.offsetX, tile.baseY, tile.offsetZ);
|
|
|
|
if (!useEmbeddedBuildingsInTilePrefabs)
|
|
RemoveEmbeddedBuildings(instance, tileKey);
|
|
|
|
LogTerrainState(instance, tileKey);
|
|
|
|
loaded[tileKey] = instance;
|
|
Log($"Loaded tile {tileKey}. LoadedCount={loaded.Count}");
|
|
};
|
|
}
|
|
|
|
private void StartBuildingLoad(string buildingKey)
|
|
{
|
|
if (!buildingTiles.TryGetValue(buildingKey, out var tile))
|
|
return;
|
|
|
|
buildingLoading.Add(buildingKey);
|
|
Log($"Loading buildings {buildingKey}...");
|
|
|
|
var handle = Addressables.InstantiateAsync(buildingKey, tilesParent);
|
|
handle.Completed += op =>
|
|
{
|
|
buildingLoading.Remove(buildingKey);
|
|
|
|
if (op.Status != AsyncOperationStatus.Succeeded)
|
|
{
|
|
Debug.LogError($"[GeoTileAddressablesLoader] Building load failed for {buildingKey}: {op.OperationException}");
|
|
return;
|
|
}
|
|
|
|
var instance = op.Result;
|
|
instance.name = buildingKey;
|
|
instance.transform.position = new Vector3(tile.offsetX, tile.baseY, tile.offsetZ);
|
|
buildingLoaded[buildingKey] = instance;
|
|
Log($"Loaded buildings {buildingKey}. LoadedCount={buildingLoaded.Count}");
|
|
};
|
|
}
|
|
|
|
private void UnloadTile(string tileKey)
|
|
{
|
|
if (!loaded.TryGetValue(tileKey, out var instance))
|
|
return;
|
|
|
|
Addressables.ReleaseInstance(instance);
|
|
loaded.Remove(tileKey);
|
|
Log($"Unloaded tile {tileKey}. LoadedCount={loaded.Count}");
|
|
}
|
|
|
|
private void UnloadBuilding(string buildingKey)
|
|
{
|
|
if (!buildingLoaded.TryGetValue(buildingKey, out var instance))
|
|
return;
|
|
|
|
Addressables.ReleaseInstance(instance);
|
|
buildingLoaded.Remove(buildingKey);
|
|
Log($"Unloaded buildings {buildingKey}. LoadedCount={buildingLoaded.Count}");
|
|
}
|
|
|
|
private static void LogTerrainState(GameObject instance, string tileKey)
|
|
{
|
|
if (instance == null)
|
|
return;
|
|
|
|
var terrain = instance.GetComponent<Terrain>();
|
|
if (terrain == null)
|
|
{
|
|
Debug.LogWarning($"[GeoTileAddressablesLoader] Tile {tileKey}: no Terrain component on instance.");
|
|
return;
|
|
}
|
|
|
|
var td = terrain.terrainData;
|
|
if (td == null)
|
|
{
|
|
Debug.LogWarning($"[GeoTileAddressablesLoader] Tile {tileKey}: TerrainData is NULL.");
|
|
return;
|
|
}
|
|
|
|
var layers = td.terrainLayers;
|
|
int layerCount = layers != null ? layers.Length : 0;
|
|
bool hasDiffuse = false;
|
|
string diffuseName = "<none>";
|
|
if (layerCount > 0 && layers[0] != null && layers[0].diffuseTexture != null)
|
|
{
|
|
hasDiffuse = true;
|
|
diffuseName = layers[0].diffuseTexture.name;
|
|
}
|
|
|
|
string materialName = terrain.materialTemplate != null ? terrain.materialTemplate.name : "<none>";
|
|
Debug.Log(
|
|
$"[GeoTileAddressablesLoader] Tile {tileKey}: terrain ok, " +
|
|
$"drawHeightmap={terrain.drawHeightmap}, layers={layerCount}, " +
|
|
$"hasDiffuse={hasDiffuse}, diffuse={diffuseName}, material={materialName}");
|
|
}
|
|
|
|
private void RemoveEmbeddedBuildings(GameObject tileInstance, string tileKey)
|
|
{
|
|
if (tileInstance == null)
|
|
return;
|
|
|
|
int removed = 0;
|
|
var removedIds = new HashSet<int>();
|
|
|
|
// Current tile prefab convention: a direct child named "Buildings"
|
|
var direct = tileInstance.transform.Find("Buildings");
|
|
if (direct != null)
|
|
{
|
|
Destroy(direct.gameObject);
|
|
removed++;
|
|
removedIds.Add(direct.gameObject.GetInstanceID());
|
|
}
|
|
|
|
// Safety pass if any nested "Buildings" transforms exist
|
|
var all = tileInstance.GetComponentsInChildren<Transform>(true);
|
|
for (int i = 0; i < all.Length; i++)
|
|
{
|
|
var t = all[i];
|
|
if (t == null || t == tileInstance.transform)
|
|
continue;
|
|
|
|
if (!string.Equals(t.name, "Buildings", StringComparison.Ordinal))
|
|
continue;
|
|
|
|
if (removedIds.Contains(t.gameObject.GetInstanceID()))
|
|
continue;
|
|
|
|
Destroy(t.gameObject);
|
|
removed++;
|
|
removedIds.Add(t.gameObject.GetInstanceID());
|
|
}
|
|
|
|
if (removed > 0)
|
|
Log($"Removed {removed} embedded building root(s) from tile {tileKey}.");
|
|
}
|
|
|
|
private void BuildBuildingBlocks()
|
|
{
|
|
tileBlockKey.Clear();
|
|
blockBuildingKey.Clear();
|
|
|
|
if (!loadBuildingsFor2kmBlocks)
|
|
return;
|
|
|
|
int blockSize = Math.Max(1, buildingBlockSizeInTiles);
|
|
foreach (var kvp in tiles)
|
|
{
|
|
if (!TryGetTileXY(kvp.Value, out int x, out int y))
|
|
continue;
|
|
|
|
int blockX = x - (x % blockSize);
|
|
int blockY = y - (y % blockSize);
|
|
string blockKey = $"{blockX}_{blockY}";
|
|
tileBlockKey[kvp.Key] = blockKey;
|
|
}
|
|
|
|
if (buildingTiles.Count == 0)
|
|
return;
|
|
|
|
foreach (var kvp in buildingTiles)
|
|
{
|
|
if (!TryGetTileXY(kvp.Value, out int x, out int y))
|
|
continue;
|
|
|
|
int blockX = x - (x % blockSize);
|
|
int blockY = y - (y % blockSize);
|
|
string blockKey = $"{blockX}_{blockY}";
|
|
blockBuildingKey[blockKey] = kvp.Key;
|
|
}
|
|
}
|
|
|
|
private static bool TryGetTileXY(TileEntry tile, out int x, out int y)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(tile.tileKey) && TryParseTileKey(tile.tileKey, out x, out y))
|
|
return true;
|
|
|
|
return TryParseTileId(tile.tileId, out x, out y);
|
|
}
|
|
|
|
private static bool TryParseTileKey(string tileKey, out int x, out int y)
|
|
{
|
|
x = 0;
|
|
y = 0;
|
|
if (string.IsNullOrWhiteSpace(tileKey))
|
|
return false;
|
|
|
|
var parts = tileKey.Split('_');
|
|
if (parts.Length != 2)
|
|
return false;
|
|
|
|
return int.TryParse(parts[0], out x) && int.TryParse(parts[1], out y);
|
|
}
|
|
|
|
private static bool TryParseTileId(string tileId, out int x, out int y)
|
|
{
|
|
x = 0;
|
|
y = 0;
|
|
if (string.IsNullOrWhiteSpace(tileId))
|
|
return false;
|
|
|
|
var parts = tileId.Split('_');
|
|
var coords = new List<int>();
|
|
for (int i = 0; i < parts.Length; i++)
|
|
{
|
|
var part = parts[i];
|
|
if (part.Length < 3)
|
|
continue;
|
|
|
|
bool allDigits = true;
|
|
for (int j = 0; j < part.Length; j++)
|
|
{
|
|
if (!char.IsDigit(part[j]))
|
|
{
|
|
allDigits = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!allDigits)
|
|
continue;
|
|
|
|
if (int.TryParse(part, out int value))
|
|
coords.Add(value);
|
|
}
|
|
|
|
if (coords.Count < 2)
|
|
return false;
|
|
|
|
x = coords[coords.Count - 2];
|
|
y = coords[coords.Count - 1];
|
|
return true;
|
|
}
|
|
|
|
private static string GetBuildTargetFolderName()
|
|
{
|
|
switch (Application.platform)
|
|
{
|
|
case RuntimePlatform.Android:
|
|
return "Android";
|
|
case RuntimePlatform.WindowsPlayer:
|
|
case RuntimePlatform.WindowsEditor:
|
|
return "StandaloneWindows64";
|
|
case RuntimePlatform.LinuxPlayer:
|
|
case RuntimePlatform.LinuxEditor:
|
|
return "StandaloneLinux64";
|
|
case RuntimePlatform.OSXPlayer:
|
|
case RuntimePlatform.OSXEditor:
|
|
return "StandaloneOSX";
|
|
default:
|
|
return "Android";
|
|
}
|
|
}
|
|
|
|
private static string ToFileUri(string path)
|
|
{
|
|
var normalized = path.Replace("\\", "/");
|
|
return normalized.StartsWith("file://", StringComparison.OrdinalIgnoreCase)
|
|
? normalized
|
|
: "file://" + normalized;
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
foreach (var instance in loaded.Values)
|
|
Addressables.ReleaseInstance(instance);
|
|
|
|
loaded.Clear();
|
|
loading.Clear();
|
|
queued.Clear();
|
|
loadQueue.Clear();
|
|
|
|
foreach (var instance in buildingLoaded.Values)
|
|
Addressables.ReleaseInstance(instance);
|
|
|
|
buildingLoaded.Clear();
|
|
buildingLoading.Clear();
|
|
buildingQueued.Clear();
|
|
buildingLoadQueue.Clear();
|
|
}
|
|
|
|
private void Log(string message)
|
|
{
|
|
if (verboseLogging)
|
|
Debug.Log($"[GeoTileAddressablesLoader] {message}");
|
|
}
|
|
}
|