using System.Collections; using System.Collections.Generic; using UnityEngine; using KairoEngine.Chunks; using KairoEngine.Core; using KairoEngine.UI; using Utils = KairoEngine.Utility.Utilities; using Sirenix.OdinInspector; namespace KairoEngine.VoxelBuildingSystem { [HideMonoScript] public class VoxelBuildingSystem : MonoBehaviour { #region Variables public static VoxelBuildingSystem instance; public Vector3Int chunkSize = new Vector3Int(16, 16, 16); public Vector3Int chunkCount = new Vector3Int(3, 3, 3); public Vector3 voxelSize = new Vector3(1f, 1f, 1f); public LayerMask layerMask; public List buildingTemplates = new List(); public bool debugBlocks = false; public GameObject blockVisualPrefab; public Transform blockContainer; [ReadOnly] public int currentBuildingTemplate; [ReadOnly] public int currentRotationIndex; [ReadOnly] public VoxelBuildingTool tool = VoxelBuildingTool.Build; [ReadOnly] public List rotationList = new List(); public ChunkSystem chunkSystem; [HideInInspector] public Dictionary buildings = new Dictionary(); [HideInInspector] public List buildingList = new List(); private Vector3 pointerPosition; #endregion #region Lifecycle public void CreateVoxelSystem() { SetupRotationList(); chunkSystem = new ChunkSystem(chunkSize, false); for (int x = 0; x < chunkCount.x; x++) { for (int z = 0; z < chunkCount.z; z++) { for (int y = 0; y < chunkCount.y; y++) { Vector3Int chunkPos = new Vector3Int(x * chunkSize.x, y * chunkSize.y, z * chunkSize.z); chunkSystem.CreateChunk(chunkPos, (Chunk c, Vector3Int pos) => new VoxelBuildData(pos, -1)); } } } } void Start() => CreateVoxelSystem(); void Update() { pointerPosition = Utils.GetMouseWorldPosition(layerMask.value); if(!MouseInputUIBlocker.BlockedByUI) { switch (tool) { case VoxelBuildingTool.Build: if(Input.GetKeyDown(KeyCode.R)) RotateBuilding(); if(Input.GetMouseButtonDown(0)) PlaceBuilding(pointerPosition); break; case VoxelBuildingTool.Remove: if(Input.GetMouseButtonDown(0)) RemoveBuilding(pointerPosition); break; default: break; } } if(Input.GetKeyDown(KeyCode.Alpha1)) CycleBuildingTemplate(); } #endregion #region Actions public void RotateBuilding() { if(currentRotationIndex < rotationList.Count - 1) currentRotationIndex += 1; else currentRotationIndex = 0; } public void PlaceBuilding(Vector3 pos) { Vector3Int buildingPos = GetMouseBuildingPosition(); Vector3Int voxelPos = GetMouseVoxelPosition(); Vector3Int buildingRotation = rotationList[currentRotationIndex]; if(!CanPlaceBuilding(buildingPos, currentBuildingTemplate, currentRotationIndex, true)) return; Quaternion rotation = Quaternion.Euler(buildingRotation.x, buildingRotation.y, buildingRotation.z); Transform newBuilding = Instantiate(buildingTemplates[currentBuildingTemplate].prefab, buildingPos, rotation, this.transform); BuildingBehaviour buildingBehaviour = newBuilding.GetComponent(); if(buildingBehaviour == null) { Debug.LogError($"Building prefab has no BuildingBehaviour component! Cannot place building", newBuilding); Destroy(newBuilding); return; } buildingBehaviour.origin = buildingPos; buildings.Add(buildingPos, buildingBehaviour); buildingList.Add(buildingBehaviour); List voxelPositions = GetBuildingVoxelList(buildingPos, currentBuildingTemplate, currentRotationIndex); for (int i = 0; i < voxelPositions.Count; i++) { VoxelBuildData block = chunkSystem.GetBlock(voxelPositions[i]); block.buildingIndex = buildingList.Count - 1; block.buildingTemplateIndex = currentBuildingTemplate; chunkSystem.SetBlock(voxelPositions[i], block); if(debugBlocks) Instantiate(blockVisualPrefab, voxelPositions[i], new Quaternion(), blockContainer); } } public void RemoveBuilding(Vector3 pos) {} public void CycleBuildingTemplate() { if(currentBuildingTemplate < buildingTemplates.Count - 1) currentBuildingTemplate += 1; else currentBuildingTemplate = 0; GenericEvents.Trigger("OnBuildingTemplateChanged"); //Debug.Log($"Changing current building template to {currentBuildingTemplate}"); } #endregion #region Queries public bool CanPlaceBuilding(Vector3Int voxelPos, int buildingTemplateIndex, int buildingRotationIndex = 0, bool debug = false) { return EvaluateRuleset(voxelPos, buildingRotationIndex, buildingTemplateIndex); } public Vector3Int FindVoxelPosition(Vector3 pos) { return new Vector3Int(Mathf.FloorToInt(pos.x/voxelSize.x), Mathf.FloorToInt(pos.y/voxelSize.y), Mathf.FloorToInt(pos.z/voxelSize.z)); } public Vector3 GetMouseWorldSnappedPosition() => (Vector3)FindVoxelPosition(pointerPosition); public Vector3Int GetMouseVoxelPosition() => FindVoxelPosition(pointerPosition); public Vector3Int GetMouseBuildingPosition() { Vector3Int voxelPos = FindVoxelPosition(pointerPosition); Vector3Int rotationOffset = GetRotationOffset(currentRotationIndex, buildingTemplates[currentBuildingTemplate].size); return voxelPos - rotationOffset; } public List GetBuildingVoxelList(Vector3Int offset, int buildingTemplateId, int buildingRotationIndex = 0) { BuildingTemplate template = buildingTemplates[buildingTemplateId]; List positionList = new List(); Vector3Int rotationOffset = GetRotationOffset(buildingRotationIndex, template.size); switch (buildingRotationIndex) { default: case 0: case 2: for(int x = 0; x < template.size.x; x++) { for (int y = 0; y < template.size.y; y++) { for (int z = 0; z < template.size.z; z++) { Vector3Int pos = new Vector3Int(offset.x + rotationOffset.x + x, offset.y + rotationOffset.y + y, offset.z + rotationOffset.z + z); positionList.Add(pos); } } } break; case 1: case 3: for(int x = 0; x < template.size.z; x++) { for (int y = 0; y < template.size.y; y++) { for (int z = 0; z < template.size.x; z++) { Vector3Int pos = new Vector3Int(offset.x + rotationOffset.x + x, offset.y + rotationOffset.y + y, offset.z + rotationOffset.z + z); positionList.Add(pos); } } } break; } //Debug.Log($"{positionList.Count} voxel positions found for building \"{buildingTemplates[buildingTemplateId].title}\""); return positionList; } #endregion #region Rotation private void SetupRotationList() { rotationList = new List { new Vector3Int(0, 0, 0), new Vector3Int(0, 90, 0), new Vector3Int(0, 180, 0), new Vector3Int(0, 270, 0) }; } public Vector3Int GetRotationOffset(int rotationIndex, Vector3Int size) { switch (rotationIndex) { default: case 0: return new Vector3Int(0, 0, 0); case 1: return new Vector3Int(0, 0, -size.x); case 2: return new Vector3Int(-size.x, 0, -size.z); case 3: return new Vector3Int(-size.z, 0, 0); } } #endregion #region Connectors /// Get an offset value for calculating a connectors position, accounting for the buildings rotation and size. /// For now it only works in the Y axis with rotation indexes ranging from 0 to 3. /// A reference for the Voxel Building Connector /// The size of the building that the connector belongs to /// The rotation index for the building /// A position offset that can be added to a connector position to get its coordinates // TODO: Add rotations to the X and Z axis public Vector3Int GetConnectorPositionOffset(VoxelConnectorData connector, Vector3Int size, int rotationIndex) { Vector3Int pos = connector.position; switch (rotationIndex) { default: case 0: return connector.position; case 1: return new Vector3Int(pos.z, pos.y, (pos.x * -1) + size.x - 1); case 2: return new Vector3Int((pos.x * -1) + size.x - 1, pos.y, (pos.z * -1) + size.z - 1); case 3: return new Vector3Int((pos.z * -1) + size.z - 1, pos.y, pos.x); } } /// Returns an Offset from a connector's voxel to its target voxel accounting for the building rotation and the direction of the connector. /// For now it only works in the Y axis with rotation indexes ranging from 0 to 3. /// The rotation index for calculating the offset /// A reference for the Voxel Building Connector /// A position offset // TODO: Add rotations to the X and Z axis public Vector3Int GetPositionOffsetFromDirection(int rotationIndex, VoxelConnectorData connector) { switch (rotationIndex) { default: case 0: switch (connector.direction) { default: case TargetDirection.Back: return new Vector3Int(0, 0, -1); case TargetDirection.Left: return new Vector3Int(-1, 0, 0); case TargetDirection.Front: return new Vector3Int(0, 0, 1); case TargetDirection.Right: return new Vector3Int(1, 0, 0); case TargetDirection.Up: return new Vector3Int(0, 1, 0); case TargetDirection.Down: return new Vector3Int(0, -1, 0); } case 1: switch (connector.direction) { default: case TargetDirection.Back: return new Vector3Int(-1, 0, 0); case TargetDirection.Left: return new Vector3Int(0, 0, 1); case TargetDirection.Front: return new Vector3Int(1, 0, 0); case TargetDirection.Right: return new Vector3Int(0, 0, -1); case TargetDirection.Up: return new Vector3Int(0, 1, 0); case TargetDirection.Down: return new Vector3Int(0, -1, 0); } case 2: switch (connector.direction) { default: case TargetDirection.Back: return new Vector3Int(0, 0, 1); case TargetDirection.Left: return new Vector3Int(1, 0, 0); case TargetDirection.Front: return new Vector3Int(0, 0, -1); case TargetDirection.Right: return new Vector3Int(-1, 0, 0); case TargetDirection.Up: return new Vector3Int(0, 1, 0); case TargetDirection.Down: return new Vector3Int(0, -1, 0); } case 3: switch (connector.direction) { default: case TargetDirection.Back: return new Vector3Int(1, 0, 0); case TargetDirection.Left: return new Vector3Int(0, 0, -1); case TargetDirection.Front: return new Vector3Int(-1, 0, 0); case TargetDirection.Right: return new Vector3Int(0, 0, 1); case TargetDirection.Up: return new Vector3Int(0, 1, 0); case TargetDirection.Down: return new Vector3Int(0, -1, 0); } } } #endregion #region Rules public bool EvaluateRuleset(Vector3Int position, int rotationIndex, int templateIndex) { List results = new List(); BuildingTemplate template = buildingTemplates[currentBuildingTemplate]; for (int i = 0; i < template.rulesets.Count; i++) { results.Add(template.rulesets[i].Evaluate(this, templateIndex, position, rotationIndex)); } if(template.rulesetCondition == ConditionType.AND) { foreach (var value in results) { if(value == false) return false; } return true; } else { foreach (var value in results) { if(value == true) return true; } return false; } } #endregion } public enum VoxelBuildingTool { None, Build, Remove } }