using UnityEngine; using System.Linq; using System; using System.Collections.Generic; namespace IsoTools { [ExecuteInEditMode] public class IsoWorld : MonoBehaviour { /// World tile types. public enum TileTypes { Isometric, UpDown } class ObjectInfo { public int Index; public IsoObject IsoObject; public Vector3 MinSector; public Vector3 MaxSector; public bool Visited; public int BeginDepend; public int EndDepend; public ObjectInfo(int index, IsoObject iso_object, Vector3 minSector, Vector3 maxSector) { Index = index; IsoObject = iso_object; MinSector = minSector; MaxSector = maxSector; } public void Init(int first_depend) { Visited = false; BeginDepend = first_depend; EndDepend = first_depend; } } class SectorInfo { public Vector3 Position = Vector3.zero; public List Objects = new List(); public List Depends = new List(); public SectorInfo(Vector3 position) { Position = position; } } bool _dirty = true; List _sectors = new List(); Vector3 _sectorsNum = Vector3.zero; List _objects = new List(); List _depends = new List(); TileTypes _lastTileType = TileTypes.Isometric; float _lastTileSize = 0.0f; float _lastMinDepth = 0.0f; float _lastMaxDepth = 0.0f; float _lastSectorSize = 0.0f; [SerializeField] public TileTypes _tileType = TileTypes.Isometric; /// World tile type. public TileTypes TileType { get { return _tileType; } set { _tileType = value; ChangeSortingProperty(); } } [SerializeField] public float _tileSize = 32.0f; /// Isometric tile size. public float TileSize { get { return _tileSize; } set { _tileSize = Math.Max(value, Mathf.Epsilon); ChangeSortingProperty(); } } [SerializeField] public float _minDepth = 0.0f; /// Min sorting depth value. public float MinDepth { get { return _minDepth; } set { _minDepth = value; ChangeSortingProperty(); } } [SerializeField] public float _maxDepth = 100.0f; /// Max sorting depth value. public float MaxDepth { get { return _maxDepth; } set { _maxDepth = value; ChangeSortingProperty(); } } [SerializeField] public float _sectorSize = 5.0f; /// Sorting sector size. public float SectorSize { get { return _sectorSize; } set { _sectorSize = Mathf.Max(value, 1.0f); ChangeSortingProperty(); } } // ------------------------------------------------------------------------ /// /// Marks world for resorting. /// // ------------------------------------------------------------------------ public void MarkDirty() { _dirty = true; } // ------------------------------------------------------------------------ /// /// Marks world for resorting one object only /// /// Isometric object for resorting. // ------------------------------------------------------------------------ public void MarkDirty(IsoObject obj) { if ( obj && obj.Sorting ) { _dirty = true; } } // ------------------------------------------------------------------------ /// /// Convert isometric coordinates to screen coordinates /// /// Screen coordinates /// Isometric coordinates. // ------------------------------------------------------------------------ public Vector2 IsoToScreen(Vector3 pos) { switch ( TileType ) { case TileTypes.Isometric: return new Vector2( (pos.x - pos.y), (pos.x + pos.y) * 0.5f + pos.z) * TileSize; case TileTypes.UpDown: return new Vector2( pos.x, pos.y + pos.z) * TileSize; default: throw new UnityException("IsoWorld. TileType is wrong!"); } } // ------------------------------------------------------------------------ /// /// Convert screen coordinates to isometric coordinates /// /// Isometric coordinates /// Screen coordinates. // ------------------------------------------------------------------------ public Vector3 ScreenToIso(Vector2 pos) { switch ( TileType ) { case TileTypes.Isometric: return new Vector3( (pos.x * 0.5f + pos.y), (pos.y - pos.x * 0.5f), 0.0f) / TileSize; case TileTypes.UpDown: return new Vector3( pos.x, pos.y, 0.0f) / TileSize; default: throw new UnityException("IsoWorld. TileType is wrong!"); } } // ------------------------------------------------------------------------ /// /// Convert screen coordinates to isometric coordinates with specified isometric height /// /// Isometric coordinates /// Screen coordinates. /// Point isometric height. // ------------------------------------------------------------------------ public Vector3 ScreenToIso(Vector2 pos, float iso_z) { switch ( TileType ) { case TileTypes.Isometric: { var iso_pos = ScreenToIso(new Vector2(pos.x, pos.y - iso_z * TileSize)); iso_pos.z = iso_z; return iso_pos; } case TileTypes.UpDown: { var iso_pos = ScreenToIso(new Vector2(pos.x, pos.y - iso_z * TileSize)); iso_pos.z = iso_z; return iso_pos; } default: throw new UnityException("IsoWorld. TileType is wrong!"); } } void FixAllTransforms() { ApplyToAllIsoObjects(obj => obj.FixTransform()); } void ChangeSortingProperty() { MarkDirty(); FixAllTransforms(); _lastTileType = TileType; _lastTileSize = TileSize; _lastMinDepth = MinDepth; _lastMaxDepth = MaxDepth; _lastSectorSize = SectorSize; } void ApplyToAllIsoObjects(Action act) { var iso_objects = GameObject.FindObjectsOfType(); foreach ( var iso_object in iso_objects ) { act(iso_object); } } int SectorIndex(Vector3 num_pos) { return Mathf.RoundToInt( num_pos.z * _sectorsNum.x * _sectorsNum.y + num_pos.y * _sectorsNum.x + num_pos.x); } Vector3 SectorNumPos(int index) { var z = Mathf.FloorToInt(index / (_sectorsNum.x * _sectorsNum.y)); var y = Mathf.FloorToInt((index - (z * _sectorsNum.x * _sectorsNum.y)) / _sectorsNum.x); var x = index - (z * _sectorsNum.x * _sectorsNum.y) - (y * _sectorsNum.x); return new Vector3(x, y, z); } void SetupSectors() { var iso_objects = GameObject.FindObjectsOfType(); var min = Vector3.zero; var max = Vector3.one; foreach ( var iso_object in iso_objects ) { min = IsoUtils.Vec3Min(min, iso_object.Position); max = IsoUtils.Vec3Max(max, iso_object.Position + iso_object.Size); } _sectorsNum = IsoUtils.Vec3DivCeil(max - min, SectorSize); _sectors.Clear(); _sectors.Capacity = Mathf.CeilToInt(_sectorsNum.x * _sectorsNum.y * _sectorsNum.z); while ( _sectors.Count < _sectors.Capacity ) { var num_pos = SectorNumPos(_sectors.Count); _sectors.Add(new SectorInfo(num_pos * SectorSize)); } _objects.Clear(); foreach ( var iso_object in iso_objects ) { var max_size = IsoUtils.Vec3Max(iso_object.Size, Vector3.one); var min_sector = IsoUtils.Vec3DivFloor(iso_object.Position - min, SectorSize); var max_sector = IsoUtils.Vec3DivCeil(iso_object.Position + max_size - min, SectorSize); var obj_info = new ObjectInfo(_objects.Count, iso_object, min_sector, max_sector); _objects.Add(obj_info); IsoUtils.LookUpCube(min_sector, max_sector, p => { var index = SectorIndex(p); _sectors[index].Objects.Add(obj_info.Index); }); } } void SetupSectorDepends() { foreach ( var sec_a in _sectors ) { for ( var i = 0; i < _sectors.Count; ++i ) { var sec_b = _sectors[i]; if ( sec_a == sec_b || IsDepends(sec_a.Position, sec_b.Position, SectorSize) ) { sec_a.Depends.Add(i); } } } } void SetupObjectDepends() { _depends.Clear(); foreach ( var obj_a in _objects ) { obj_a.Init(_depends.Count); var obj_ao = obj_a.IsoObject; IsoUtils.LookUpCube(obj_a.MinSector, obj_a.MaxSector, p => { var index = SectorIndex(p); foreach ( var sec_i in _sectors[index].Depends ) { var sec = _sectors[sec_i]; foreach ( var obj_bi in sec.Objects ) { var obj_bo = _objects[obj_bi].IsoObject; if ( obj_ao != obj_bo && IsDepends(obj_ao.Position, obj_ao.Size, obj_bo.Position, obj_bo.Size) ) { _depends.Add(obj_bi); ++obj_a.EndDepend; } } } }); } } bool IsDepends(Vector3 a_pos, Vector3 b_pos, float size) { return IsDepends( a_pos, new Vector3(size, size, size), b_pos, new Vector3(size, size, size)); } bool IsDepends(Vector3 a_min, Vector3 a_size, Vector3 b_min, Vector3 b_size) { var a_max = a_min + a_size; var b_max = b_min + b_size; return a_max.x > b_min.x && a_min.x < b_max.x + 1.0f && a_max.y > b_min.y && a_min.y < b_max.y + 1.0f && b_max.z > a_min.z && b_min.z < a_max.z + 1.0f; } void SetupAllObjects() { var depth = MinDepth; foreach ( var info in _objects ) { depth = PlaceObject(info, depth); } } void PlaceObject(IsoObject obj, float depth) { var trans = obj.gameObject.transform; trans.position = new Vector3(trans.position.x, trans.position.y, depth); } float PlaceObject(ObjectInfo info, float depth) { if ( info.Visited ) { return depth; } info.Visited = true; for ( var i = info.BeginDepend; i < info.EndDepend && i < _depends.Count; ++i ) { var obj_index = _depends[i]; var obj = _objects[obj_index]; depth = PlaceObject(obj, depth); } PlaceObject(info.IsoObject, depth); return depth + (MaxDepth - MinDepth) / _objects.Count; } void StepSort() { if ( _dirty ) { SetupSectors(); SetupSectorDepends(); SetupObjectDepends(); SetupAllObjects(); _dirty = false; } } void Start() { ChangeSortingProperty(); StepSort(); } void LateUpdate() { if ( Application.isEditor ) { if ( _lastTileType != _tileType ) TileType = _tileType; if ( !Mathf.Approximately(_lastTileSize, _tileSize ) ) TileSize = _tileSize; if ( !Mathf.Approximately(_lastMinDepth, _minDepth ) ) MinDepth = _minDepth; if ( !Mathf.Approximately(_lastMaxDepth, _maxDepth ) ) MaxDepth = _maxDepth; if ( !Mathf.Approximately(_lastSectorSize, _sectorSize ) ) SectorSize = _sectorSize; } StepSort(); } void OnEnable() { MarkDirty(); } void OnDisable() { ApplyToAllIsoObjects(obj => obj.ResetIsoWorld()); } } } // namespace IsoTools