using UnityEditor; using UnityEngine; namespace OctoCore.LevelBuilder { [ExecuteAlways] public class OctoLevelBuilderTile : MonoBehaviour { [System.Serializable] private class Configuration { public OctoLevelBuilderStyleData _overrideStyle; public bool Ceiling; public bool Wall; public bool Floor; public bool Stairs; } [System.Serializable] private class References { [OctoReadOnly] public OctoLevelBuilderMap Map; [OctoReadOnly] public GameObject CeilingInstance; [OctoReadOnly] public GameObject WallInstance; [OctoReadOnly] public GameObject FloorInstance; } [System.Serializable] private class WallConnections { public OctoLevelBuilderTile North; public OctoLevelBuilderTile Est; public OctoLevelBuilderTile South; public OctoLevelBuilderTile West; } [System.Serializable] private class WallConfiguration { public bool AutoConnect = true; [Range(0, 3)] public int Orientation = 0; public WallType Type = WallType.Wall; public WallShape Shape = WallShape.Straight; [OctoReadOnly] public WallConnections Connections = new WallConnections(); } [System.Serializable] private class Infos { public Vector3Int Coordinates; public Vector3 Position; } [SerializeField] private Configuration _configuration = new Configuration(); [SerializeField] private WallConfiguration _wall = new WallConfiguration(); [SerializeField] private References _references = new References(); [SerializeField, OctoReadOnly] private Infos _infos = new Infos(); private bool _isMapUpToDate; public bool IsUpToDate => _isMapUpToDate; public bool HasWall => _configuration.Wall; public bool HasFloor => _configuration.Floor; public bool HasCeiling => _configuration.Ceiling; public bool HasStairs => _configuration.Stairs; public float TileSize => Map ? Map.TileSize : OctoLevelBuilderMap.DEFAULT_TILE_SIZE; public float FloorHeight => Map ? Map.FloorHeight : OctoLevelBuilderMap.DEFAULT_FLOOR_HEIGHT; public OctoLevelBuilderMap Map => _references.Map; public OctoLevelBuilderStyleData Style => _configuration._overrideStyle ? _configuration._overrideStyle : _references.Map ? _references.Map.DefaultStyle : null; #if UNITY_EDITOR void OnValidate() { if (!Application.isPlaying) { UnityEditor.EditorApplication.delayCall += () => { if (this != null) RequireMapRefresh(); }; } } #endif void OnEnable() { #if UNITY_EDITOR if (!Application.isPlaying) { RegisterToMap(); } #endif } void OnDisable() { #if UNITY_EDITOR if (!Application.isPlaying) { UnregisterFromMap(); //DestroyInstance(_wallInstance); //DestroyInstance(_floorInstance); //DestroyInstance(_ceilingInstance); _references.Map.RequireTileRefresh(_wall.Connections.North); _references.Map.RequireTileRefresh(_wall.Connections.Est); _references.Map.RequireTileRefresh(_wall.Connections.South); _references.Map.RequireTileRefresh(_wall.Connections.West); } #endif } #if UNITY_EDITOR private void OnDrawGizmos() { DrawTileGizmo(new Color(0, 1, 0, 0.2f)); } private void OnDrawGizmosSelected() { DrawTileGizmo(new Color(0, 1, 0, 0.5f)); } private void DrawTileGizmo(Color color) { float tileSize = Map ? Map.TileSize : 2; Gizmos.matrix = transform.localToWorldMatrix; Gizmos.color = color; Gizmos.DrawCube(Vector3.up * tileSize * .025f, new Vector3(tileSize * .2f, tileSize * .05f, tileSize * .2f)); /*if (!HasWall()) { Gizmos.color = color; Gizmos.DrawCube(transform.position + Vector3.up * 0.5f, new Vector3(1.8f, 1f, 1.8f)); return; } float rotation = Placement.Angle * -90f; if (Wall.Shape == WallShape.Straight) rotation += 90f; Matrix4x4 oldMatrix = Gizmos.matrix; Gizmos.matrix = Matrix4x4.TRS(transform.position + Vector3.up * 1.5f, Quaternion.Euler(0, rotation, 0), Vector3.one); Gizmos.color = color; float size = 2f; float thickness = 0.3f; float height = 3f; switch (Wall.Shape) { case WallShape.Straight: Gizmos.DrawCube(Vector3.zero, new Vector3(size, height, thickness)); break; case WallShape.Corner: Vector3 arm1Pos = new Vector3(size / 4f, 0, 0); Vector3 arm2Pos = new Vector3(0, 0, size / 4f); Gizmos.DrawCube(arm1Pos, new Vector3(size / 2f, height, thickness)); Gizmos.DrawCube(arm2Pos, new Vector3(thickness, height, size / 2f)); break; case WallShape.T: Vector3 basePos = new Vector3(0, 0, 0); Vector3 crossPos = new Vector3(0, 0, size / 4f); Gizmos.DrawCube(basePos, new Vector3(size, height, thickness)); Gizmos.DrawCube(crossPos, new Vector3(thickness, height, size / 2f)); break; case WallShape.Cross: Gizmos.DrawCube(Vector3.zero, new Vector3(size, height, thickness)); Gizmos.DrawCube(Vector3.zero, new Vector3(thickness, height, size)); break; case WallShape.DeadEnd: Vector3 deadEndPos = new Vector3(0, 0, size / 4f); Gizmos.DrawCube(deadEndPos, new Vector3(thickness, height, size / 2f)); break; case WallShape.Door: case WallShape.Window: Gizmos.DrawCube(Vector3.zero, new Vector3(size, height, thickness)); break; default: Gizmos.DrawCube(Vector3.zero, new Vector3(size * 0.5f, height, size * 0.5f)); break; } Gizmos.matrix = oldMatrix;*/ } #endif void Update() { if (_infos.Position != transform.localPosition) { _infos.Position = transform.localPosition; SetName(); RequireMapRefresh(); } } public void Init(bool wall) { Debug.Log(wall); SetName(); _configuration.Ceiling = true; _configuration.Wall = wall; _configuration.Floor = true; } public void Refresh() { SetConnections(); ClearChildren(); RefreshWall(); RefreshFloor(); RefreshCeiling(); _isMapUpToDate = true; //Debug.Log($"[OctoTile] {name}: Updated"); } public Vector3Int GetCoordinates(bool update = false) { if (update) SetCoordinates(); return _infos.Coordinates; } private void SetCoordinates() { Vector3 pos = transform.position; _infos.Coordinates = new Vector3Int( Mathf.RoundToInt(pos.x / TileSize), Mathf.RoundToInt(pos.y / FloorHeight), Mathf.RoundToInt(pos.z / TileSize) ); } public void SetConnections() { GetMap(); if (!_references.Map) return; // Require update for previous connections OctoLevelBuilderTile previousNorth = _wall.Connections.North; OctoLevelBuilderTile previousEst = _wall.Connections.Est; OctoLevelBuilderTile previousSouth = _wall.Connections.South; OctoLevelBuilderTile previousWest = _wall.Connections.West; // Get new connections OctoLevelBuilderTile newNorth = GetNextTile(Vector3Int.forward); OctoLevelBuilderTile newEst = GetNextTile(Vector3Int.right ); OctoLevelBuilderTile newSouth = GetNextTile(Vector3Int.back ); OctoLevelBuilderTile newWest = GetNextTile(Vector3Int.left ); _wall.Connections.North = newNorth && newNorth.HasWall ? newNorth : null; _wall.Connections.Est = newEst && newEst .HasWall ? newEst : null; _wall.Connections.South = newSouth && newSouth.HasWall ? newSouth : null; _wall.Connections.West = newWest && newWest .HasWall ? newWest : null; bool wallChanged = HasWall != (_references.WallInstance ? true : false); // Require update for new connections if (_wall.Connections.North != previousNorth || wallChanged) { Debug.Log(_wall.Connections.North); Debug.Log(previousNorth); _references.Map.RequireTileRefresh(_wall.Connections.North); _references.Map.RequireTileRefresh(previousNorth); } if (_wall.Connections.Est != previousEst || wallChanged) { Debug.Log(_wall.Connections.Est); Debug.Log(previousEst); _references.Map.RequireTileRefresh(_wall.Connections.Est); _references.Map.RequireTileRefresh(previousEst); } if (_wall.Connections.South != previousSouth || wallChanged) { Debug.Log(_wall.Connections.South); Debug.Log(previousSouth); _references.Map.RequireTileRefresh(_wall.Connections.South); _references.Map.RequireTileRefresh(previousSouth); } if (_wall.Connections.West != previousWest || wallChanged) { Debug.Log(_wall.Connections.West); Debug.Log(previousWest); _references.Map.RequireTileRefresh(_wall.Connections.West); _references.Map.RequireTileRefresh(previousWest); } } private void RefreshShape() { if (!HasWall || !_wall.AutoConnect) return; int connectionHash = (_wall.Connections.North ? 1000 : 0) + (_wall.Connections.Est ? 100 : 0) + (_wall.Connections.South ? 10 : 0) + (_wall.Connections.West ? 1 : 0); int connectionCount = (_wall.Connections.North ? 1 : 0) + (_wall.Connections.Est ? 1 : 0) + (_wall.Connections.South ? 1 : 0) + (_wall.Connections.West ? 1 : 0); switch (connectionCount) { default: _wall.Shape = WallShape.DeadEnd; switch(connectionHash) { case 0010: _wall.Orientation = 0; break; case 0001: _wall.Orientation = 1; break; case 1000: _wall.Orientation = 2; break; case 0100: _wall.Orientation = 3; break; } break; case 2: if ((_wall.Connections.North && _wall.Connections.South) || (_wall.Connections.Est && _wall.Connections.West)) { _wall.Shape = WallShape.Straight; switch (connectionHash) { case 0101: _wall.Orientation = 0; break; case 1010: _wall.Orientation = 1; break; } } else { _wall.Shape = WallShape.Corner; switch (connectionHash) { case 1001: _wall.Orientation = 0; break; case 1100: _wall.Orientation = 1; break; case 0110: _wall.Orientation = 2; break; case 0011: _wall.Orientation = 3; break; } } break; case 3: _wall.Shape = WallShape.T; switch (connectionHash) { case 1101: _wall.Orientation = 0; break; case 1110: _wall.Orientation = 1; break; case 0111: _wall.Orientation = 2; break; case 1011: _wall.Orientation = 3; break; } break; case 4: _wall.Shape = WallShape.Cross; _wall.Orientation = 0; break; } } private OctoLevelBuilderTile GetNextTile(Vector3Int direction) { Vector3Int nextCoordinates = _infos.Coordinates + direction; if (!_references.Map) { Debug.LogWarning("[OctoTile] No Map found"); return null; } if (_references.Map.GetTile(nextCoordinates, out OctoLevelBuilderTile nexTile)) return nexTile; return null; } private void ClearChildren() { for (int i = transform.childCount - 1; i >= 0; i--) { GameObject instance = transform.GetChild(i).gameObject; if (instance != _references.CeilingInstance && instance != _references.WallInstance && instance != _references.FloorInstance) { DestroyInstance(instance); } } } private void RefreshWall() { if (!HasWall) { DestroyInstance(_references.WallInstance); _references.WallInstance = null; return; } RefreshShape(); if (!Style) return; GameObject prefab = GetWallPrefab(Style.Wall, _wall.Shape, _wall.Type); if (prefab == null) { DestroyInstance(_references.WallInstance); _references.WallInstance = null; return; } if (IsInstanceCorrespondingToPrefab(_references.WallInstance, prefab)) { DestroyInstance(_references.WallInstance); _references.WallInstance = InstantiatePrefab(prefab, "Wall"); } if (_references.WallInstance != null) { float angle = _wall.Orientation * 90f; _references.WallInstance.transform.localRotation = Quaternion.Euler(0, angle, 0); } } private GameObject GetWallPrefab(OctoLevelBuilderWallData wallData, WallShape shape, WallType type) { if (type == WallType.Door) return wallData.WallDoor; if (type == WallType.Window) return wallData.WallWindow; return wallData.GetPrefab(_wall.Orientation, shape); } private void RefreshFloor() { if (!HasFloor) { DestroyInstance(_references.FloorInstance); _references.FloorInstance = null; return; } if (!Style) return; GameObject prefab = Style.Floor.Prefab; if (prefab == null) { DestroyInstance(_references.FloorInstance); _references.FloorInstance = null; return; } if (IsInstanceCorrespondingToPrefab(_references.FloorInstance, prefab)) { DestroyInstance(_references.FloorInstance); _references.FloorInstance = InstantiatePrefab(prefab, "Floor"); } } private void RefreshCeiling() { if (!HasCeiling) { DestroyInstance(_references.CeilingInstance); _references.CeilingInstance = null; return; } if (!Style) return; GameObject prefab = Style.Ceiling.Prefab; if (prefab == null) { DestroyInstance(_references.CeilingInstance); _references.CeilingInstance = null; return; } if (IsInstanceCorrespondingToPrefab(_references.CeilingInstance, prefab)) { DestroyInstance(_references.CeilingInstance); _references.CeilingInstance = InstantiatePrefab(prefab, "Ceiling"); } } private bool IsInstanceCorrespondingToPrefab(GameObject instance, GameObject prefab) { if (instance == null) return true; #if UNITY_EDITOR GameObject source = (GameObject)UnityEditor.PrefabUtility.GetCorrespondingObjectFromSource(instance); return source != prefab; #else return false; #endif } private void DestroyInstance(GameObject instance) { if (instance != null) { #if UNITY_EDITOR if (!Application.isPlaying) DestroyImmediate(instance); else #endif Destroy(instance); instance = null; } } private GameObject InstantiatePrefab(GameObject prefab, string childName) { #if UNITY_EDITOR GameObject instance = (GameObject)UnityEditor.PrefabUtility.InstantiatePrefab(prefab, transform); SceneVisibilityManager.instance.DisablePicking(instance, true); #else GameObject instance = Instantiate(prefab, transform); #endif instance.name = childName; instance.transform.localPosition = Vector3.zero; return instance; } private void GetMap() { if (!_references.Map) _references.Map = GetComponentInParent(); } private bool GetMap(out OctoLevelBuilderMap map) { GetMap(); map = _references.Map; return map; } private void RequireMapRefresh() { _isMapUpToDate = false; if (GetMap(out OctoLevelBuilderMap map)) map.RequireMapRefresh(); } private void RegisterToMap() { if (GetMap(out OctoLevelBuilderMap map)) map.RegisterTile(this); } private void UnregisterFromMap() { if (GetMap(out OctoLevelBuilderMap map)) map.UnregisterTile(this); } private void SetName() { int x = _infos.Coordinates.x; int y = _infos.Coordinates.z; //string tmpName = "Tile " + (x < 0 ? '-' : '+') + Mathf.Abs(x).ToString().PadLeft(3, '0') + " " + (y < 0 ? '-' : '+') + Mathf.Abs(y).ToString().PadLeft(3, '0'); string tmpName = "Tile_" + transform.GetSiblingIndex().ToString().PadLeft(3, '0'); if (name != tmpName) name = tmpName; } } }