using System.Collections.Generic; using UnityEngine.Tilemaps; using UnityEngine; using System; using System.Collections; using System.Linq; using Newtonsoft.Json; using System.Threading.Tasks; namespace GatherAndDefend.LevelEditor { [Serializable] public class TilemapData : IEnumerable { public const int INVISIBLE_LAYER = 6; [SerializeField] private string _key; [SerializeField] private List _tiles; [SerializeField] private bool _isInvisible; [SerializeField] private bool _isCollidable; [SerializeField] private bool _isTrigger; [SerializeField] private int _renderOrder; [SerializeField] private string _renderLayer; [SerializeField] private Vector2 _position; [SerializeField] private Vector2 _scale; public string Key => _key; /// /// /// /// the current tilemap /// public void LoadToTilemapEditor(Tilemap reference) { var tilesPerSecond = Application.isPlaying ? GlobalConfig.Instance.Current.baseTileSpawnSpeed : float.Epsilon; reference.transform.localPosition = _position; reference.transform.localScale = _scale; var rend = reference.GetComponent(); rend.sortingOrder = _renderOrder; rend.sortingLayerName = _renderLayer; if (_isInvisible) rend.gameObject.layer = INVISIBLE_LAYER; if (_isCollidable) { var collision = rend.gameObject.AddComponent(); collision.isTrigger = _isTrigger; } //all tiles are loaded after their animation is over. use a task to asyncroneously load them and keep control over the flow var tasks = new List(); foreach (TileData data in _tiles) { reference.SetTile(data.Position, data.Tile); } } /// /// /// /// the current tilemap /// the tiles falling on the tilemap /// a reference value that is used in caller method to detect when all tiles have been spawned /// a delegate which speeds up the spawning of the tiles over time /// public async Task LoadToTilemap(Tilemap reference, Func shouldKillTask, PlacementAnimationHandler placementAnimation = null, Action allTilesSpawned = default, Func tileSpawnAcceleration = default) { // if the function we receive is null, we just make it constant 1 if (tileSpawnAcceleration == default) tileSpawnAcceleration = () => 1; if (placementAnimation == null) placementAnimation = (tileData, placer, killer) => { reference.SetTile(tileData.Position, tileData.Tile); return null; }; var tilesPerSecond = Application.isPlaying ? GlobalConfig.Instance.Current.baseTileSpawnSpeed : float.Epsilon; reference.transform.localPosition = _position; reference.transform.localScale = _scale; var rend = reference.GetComponent(); rend.sortingOrder = _renderOrder; rend.sortingLayerName = _renderLayer; if (_isInvisible) rend.gameObject.layer = INVISIBLE_LAYER; if (_isCollidable) { var collision = rend.gameObject.AddComponent(); collision.isTrigger = _isTrigger; } //all tiles are loaded after their animation is over. use a task to asyncroneously load them and keep control over the flow var tasks = new List(); foreach (TileData data in _tiles) { tasks.Add(placementAnimation(data, () => reference.SetTile(data.Position, data.Tile), shouldKillTask)); if (shouldKillTask()) { return; } await Task.Delay((int)(1000f / (tilesPerSecond * tileSpawnAcceleration()))); } allTilesSpawned?.Invoke(); if (shouldKillTask()) { return; } await Task.WhenAll(tasks); } /// /// saves a tilemap into the level object /// /// /// the bounds of the tilemap public void SaveFromTilemap(Tilemap reference) { _key = reference.name; if (_isCollidable = reference.GetComponent()) { _isTrigger = reference.GetComponent().isTrigger; } _isInvisible = reference.gameObject.layer == INVISIBLE_LAYER; _renderLayer = reference.GetComponent().sortingLayerName; _renderOrder = reference.GetComponent().sortingOrder; _position = reference.transform.localPosition; _scale = reference.transform.localScale; _tiles = new List(); BoundsInt bounds = reference.cellBounds; for (int i = bounds.xMin; i <= bounds.xMax; i++) { for (int j = bounds.yMin; j <= bounds.yMax; j++) { Vector3Int position = new Vector3Int(i, j); TileBase tile = reference.GetTile(position); if (!tile) continue; var tileData = new TileData(position, tile); _tiles.Add(tileData); } } } /// /// returns a dictionary representation of the tilemap /// /// public Dictionary ToDictionary() { return new Dictionary() { {nameof(_key), _key }, {nameof(_isInvisible), _isInvisible }, {nameof(_isCollidable), _isCollidable }, {nameof(_isTrigger), _isTrigger }, {nameof(_renderOrder), _renderOrder }, {nameof(_renderLayer), _renderLayer }, {nameof(_position), new float[]{_position.x, _position.y, 0 } }, {nameof(_scale), new float[]{ _scale.x, _scale.y, 0 } }, {nameof(_tiles), _tiles.FindAll(x => !(x.Tile is LevelTile)) .Select(x => new Dictionary() { {nameof(x.Position), new float[] { x.Position.x, x.Position.y, x.Position.z } }, {nameof(x.Tile), x.Tile.name } }).ToArray() } }; } /// /// builds a tilemap from a dictionary representation (from the save file) /// /// /// public static Tilemap FromDictionary(Dictionary dict) { //get all tilemap data var key = dict[nameof(_key)].ToString(); var invisible = dict[nameof(_isInvisible)].ToBool(); var collidable = dict[nameof(_isCollidable)].ToBool(); var trigger = dict[nameof(_isTrigger)].ToBool(); var renderOrder = dict[nameof(_renderOrder)].ToInt(); var renderLayer = dict[nameof(_renderLayer)].ToString(); var position = dict[nameof(_position)].ToVector3(); var scale = dict[nameof(_scale)].ToVector3(); var tiles = dict[nameof(_tiles)]; //get grid var grid = GameObject.FindObjectOfType(); if (!grid) { var levelMgrScript = GameObject.FindObjectOfType(); grid = new GameObject("Grid").AddComponent(); grid.transform.SetParent(levelMgrScript.transform.parent); } //get tilemap by name var tilemap = grid.GetComponentInChildren(key); TilemapRenderer renderer; if (!tilemap) { tilemap = new GameObject(key).AddComponent(); tilemap.tileAnchor = Vector3.zero; tilemap.gameObject.AddComponent(); tilemap.transform.SetParent(grid.transform); } tilemap.transform.localPosition = position; tilemap.transform.localScale = scale; renderer = tilemap.GetComponent(); //populate tilemap according to specs renderer.sortingOrder = renderOrder; renderer.sortingLayerName = renderLayer; if (invisible) tilemap.gameObject.layer = INVISIBLE_LAYER; if (collidable) { tilemap.gameObject.AddComponent().isTrigger = trigger; } //populate tilemaps with non-LevelTile tiles foreach (var tileObj in tiles as IEnumerable) { var tileDict = JsonConvert.DeserializeObject>(tileObj.ToString()); var tileName = tileDict["Tile"].ToString(); var tile = Database.Instance.ScriptableObjects[tileName]; var tilePos = tileDict["Position"].ToVector3(); tilemap.SetTile(Vector3Int.RoundToInt(tilePos), tile as TileBase); } return tilemap; } public IEnumerator GetEnumerator() { return _tiles.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return _tiles.GetEnumerator(); } public TilemapData() { _tiles = new List(); } } }