using System.Collections; using System.Collections.Generic; using UnityEngine; using System; using System.IO; //Strips at build time #if UNITY_EDITOR using UnityEditor; #endif /*************This class is the base of the system where all other components relate to**************/ [RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))] public class GridSquare : MonoBehaviour { //Enums public enum GridType { SingleCell, Chequered, Simple, Points, Lines } public enum BlockType { BlockBoth, BlockAbove, BlockBelow } /*******Grid private variables********/ //Data variables Vector3[] gridPoints; Vector3[] cells; int numCells; public Dictionary gridCellsStatus; public Dictionary gridCells; static List freeCells = new List(); static List blockedCells = new List(); //Simple mesh creation variables Mesh mesh; Vector3[] meshVertices; int[] meshTriangles; BoxCollider meshCollider; float colliderThickness = 0.01f; Vector2[] uvs; Renderer rend; //Containers GameObject cellContainer; GameObject pointsContainer; GameObject gridObjContainer; //Line variables Mesh linesMesh; Vector3[] linesVertices; int[] linesTriangles; GameObject line; GameObject linesContainer; //Odd Vector3 checkBoxSize; Transform pointsArr; //Cached components GridSelector gridSelector; ObjectPlacer objectPlacer; GridObjectOptions gridObjectOptions; //Save and loading GridCreationData gridCreationData; bool loaded = false; bool created; bool createInEditor = false; bool autoCellBlockPreview = false; /*****************Serialised inspector variables**********************/ //General settings [SerializeField] bool editorExtension = false; [SerializeField] string id; [SerializeField] bool visualOnly = false; [Min(1)] [SerializeField] int gridWidth = 10; [Min(1)] [SerializeField] int gridHeight = 10; [Min(0.001f)] [SerializeField] float cellSize = 1; [SerializeField] GridType gridType; [SerializeField] bool drawSimple; [Min(0)] [SerializeField] int autoSaveInterval; [SerializeField] bool saveGridOnExit; [SerializeField] bool loadSaveOnStart; [SerializeField] bool loadConfigOnStart; [SerializeField] bool checkMatRuntime; [SerializeField] float tileX = 2; [SerializeField] float tileY = 2; [Range(0, 1f)] //Point creation variables [SerializeField] float pointRadius; //LinesMeshVariables [Range(0, 0.5f)] [SerializeField] float linesThickness; //Auto cell blocking variables [SerializeField] bool autoCellBlocking = false; [SerializeField] BlockType blocktype; [SerializeField] LayerMask ignoreLayers; [SerializeField] int groundLayer; [SerializeField] float groundDistance = 0.05f; [SerializeField] float aboveCheckBoxSize = 1f; [SerializeField] float aboveCheckBoxHeight = 1f; [SerializeField] float checkBoxOffset; [SerializeField] bool showAboveBoxColliders = false; [SerializeField] bool showBelowRays = false; [SerializeField] bool checkGroundHits = false; //Prefabs [SerializeField] GameObject gridCellPrefab; [SerializeField] GameObject secondGridCellPrefab; [SerializeField] GameObject blockedAboveCellPrefab; [SerializeField] GameObject blockedBelowCellPrefab; [SerializeField] GameObject pointsPrefab; //Debug [SerializeField] bool drawGridPositions = false; [SerializeField] bool drawCellPositions = false; //Accessors public string Id { get { return id; } set { id = value; } } public Transform PointsArr { get { return pointsArr; } } public bool VisualOnly { get { return visualOnly; } } public GridType GetGridType { get { return gridType; } } public int GridWidth { get { return gridWidth; } } public int GridHeight { get { return gridHeight; } } public float LinesThickness { get { return linesThickness; } } public float PointRadius { get { return pointRadius; } } public bool Created { get { return created; } set { created = value; } } public GameObject GridObjectContainer { get { return gridObjContainer; } } public float CellSize { get { return cellSize; } } public bool Loaded { set { loaded = value; } } public int GroundLayer { get { return groundLayer; } } public Vector3[] Cells { get { return cells; } set { cells = value; } } public GameObject CellContainer { get => cellContainer; set => cellContainer = value; } public List FreeCells { get { return freeCells; } set { freeCells = value; } } public List BlockedCells { get { return blockedCells; } set { blockedCells = value; } } public float GroundDistance { get => groundDistance; set => groundDistance = value; } public BlockType Blocktype { get => blocktype; set => blocktype = value; } public bool EditorExtension { get => editorExtension; set => editorExtension = value; } public bool UseFixedUpdate { get => createInEditor; set => createInEditor = value; } public GridCreationData GridCreationData { get => gridCreationData; set => gridCreationData = value; } public Vector3[] GridPoints { get => gridPoints; set => gridPoints = value; } public bool AutoCellBlockPreview { get => autoCellBlockPreview; set => autoCellBlockPreview = value; } #if UNITY_EDITOR private void Reset() { SetNewId(); CreateGrid(); CreateGridCells(); } public void SetNewId() { id = GUID.Generate().ToString().Substring(0, 8); } #endif private void Awake() { if (GridBuilder2Manager.Instance != null) { //Cache objects objectPlacer = GridBuilder2Manager.Instance.ObjectPlacer; gridObjectOptions = GridBuilder2Manager.Instance.GridObjectOptions; gridSelector = GridBuilder2Manager.Instance.GridSelector; } LoadGridCreationData(); createInEditor = false; if(gridCreationData != null) { created = true; } } public void LoadGridCreationData() { string loadPath = $"GridCreationData/{gameObject.scene.name}/{this.name}-{id}".Replace(" ", string.Empty); gridCreationData = Resources.Load(loadPath); if (gridCreationData != null) { gridCreationData.RebuildData(this); } } public void StartGridCR() { createInEditor = true; StartCoroutine(Start()); } IEnumerator Start() { if (!created) { #if UNITY_EDITOR if(createInEditor) { EditorUtility.DisplayProgressBar("Grid Creation Progress", "Creating data structures...", 0.1f); } #endif RemoveAutoCellBlockPreview(); if(createInEditor || !editorExtension) { InitialiseDataStructures(); } //The grid system needs the physics to calculate so we have to wait for an update frame and then we can build the rest of the grid if(!createInEditor) { yield return new WaitForFixedUpdate(); } yield return null; #if UNITY_EDITOR if (createInEditor) { EditorUtility.DisplayProgressBar("Grid Creation Progress", "Creating grid nodes...", 0.3f); } #endif /************Keep these creation types above the collider creation otherwise the grid will detect itself when building.*************/ //Creates the relevent containers and builds the grid by its grid type switch (gridType) { case GridType.Simple: { mesh = new Mesh(); GetComponent().mesh = mesh; rend = GetComponent(); CreateSimpleGrid(); break; } case GridType.SingleCell: case GridType.Chequered: { cellContainer = new GameObject("CellContainer"); cellContainer.transform.parent = transform; if (FindObjectOfType()) { cellContainer.SetActive(false); } CreateSingleCellGrid(); break; } case GridType.Points: { pointsContainer = new GameObject("PointsContainer"); pointsContainer.transform.parent = transform; if (FindObjectOfType()) { pointsContainer.SetActive(false); } rend = GetComponent(); CreatePointGrid(); pointsArr = pointsContainer.GetComponentInChildren(); break; } case GridType.Lines: { linesContainer = new GameObject("LinesContainer"); linesContainer.transform.parent = transform; if (FindObjectOfType()) { linesContainer.SetActive(false); } linesMesh = new Mesh(); CreateLinesGrid(); break; } } #if UNITY_EDITOR if (createInEditor) { EditorUtility.DisplayProgressBar("Grid Creation Progress", "Saving Grid Creation Data...", 0.6f); } #endif //New parent for the placed objects to go under gridObjContainer = new GameObject("GridObjContainer"); gridObjContainer.transform.parent = transform; //Keep this last if (!visualOnly) { meshCollider = gameObject.AddComponent(); SetColliderSize(); } if (createInEditor) { gridCreationData = SaveLoadGrid.CreateGridCreationData(this); } created = true; } else { gridObjContainer = gameObject.transform.Find("GridObjContainer").gameObject; } #if UNITY_EDITOR if (createInEditor) { EditorUtility.DisplayProgressBar("Grid Creation Progress", "Loading options...", 0.9f); } #endif //This will load the grid at beginning if enabled and if it finds a save file if (loadSaveOnStart) { LoadGrid(); } if (loadConfigOnStart) { LoadPreconfiguration(); } //Sets up repeating the save command after X minutes if (autoSaveInterval > 0) { InvokeRepeating("SaveGrid", autoSaveInterval * 60, autoSaveInterval * 60); } #if UNITY_EDITOR if (createInEditor) { EditorUtility.ClearProgressBar(); } #endif } private void InitialiseDataStructures() { //Initialises the data structures for the grid system CreateGrid(); CreateGridCells(); CreateGridStatus(); CreateGridCellStatus(); } private void RemoveAutoCellBlockPreview() { //Checks to see on play if there is a preview of the autoCellBlock on, and if so, remove it if (gameObject.transform.childCount > 0) { Transform[] children = gameObject.transform.GetComponentsInChildren(); for (int i = 0; i < children.Length; i++) { if (!children[i].GetComponent()) { Destroy(children[i].gameObject); } } } } private void Update() { //Simple way to see the material tiling at runtime if (gridType == GridType.Simple) { if (checkMatRuntime) { UpdateMaterial(); } } } //This will save the grid on application quit if enabled private void OnApplicationQuit() { if (saveGridOnExit) { SaveGrid(); } } //Sends the grid to be saved with all of its placed objects public void SaveGrid() { if(gameObject.activeSelf) { SaveLoadGrid.SaveCurrentGrid(this); } } //Loads the grid with all objects previously saved public void LoadGrid() { //If it is already loaded, do not load again if (loaded) { return; } //If you are loading a preconfig you cannot load a save until after saving again if (loadConfigOnStart) { return; } //Clears storage for initiating objects again ObjectStorage.GOTypeList.Clear(); ObjectStorage.GOInstanceList.Clear(); List destroyGOList = new List(); List changeStatusList = new List(); if(gridCellsStatus != null) foreach (KeyValuePair entry in gridCellsStatus) { if (entry.Value != null && entry.Value != this.gameObject) { destroyGOList.Add(entry.Value); changeStatusList.Add(entry.Key); } } //This is when you try to load after making changes on the current session without saving if (destroyGOList.Count > 0) { Debug.Log("There are unsaved changes, loading old save"); //If using a confirmation UI, put the below for loop in a coroutine to wait until the user has clicked for (int i = 0; i < destroyGOList.Count; i++) { ChangeCellStatus(changeStatusList[i], null); Destroy(destroyGOList[i].gameObject); } } //Will not try to load if you have hidden the grid, the data needs the gridSqaure object to be active if (gameObject.activeSelf) { int counter = 0; int objSizeCounter = 0; int blockCellCounter = 0; //Gets the data GridData gridData = SaveLoadGrid.LoadCurrentGrid(this); if (gridData != null) { //This loops through every placed object that was saved for (int i = 0; i < gridData.objectName.Count; i++) { //Outputs the gridData to all the initial variables the GridObject class needs /**************YOU MUST PUT YOUR PLACEABLE OBJECTS IN "Resources/Prefabs/PlaceableObjects/"*******************/ //These folders can be anywhere, but must contain the above directory ^^^^^^ GameObject resourceObject = (GameObject)Resources.Load($"PlaceableObjects/{gridData.objectName[i]}"); Vector3 position = new Vector3(gridData.xPositions[i], gridData.yPositions[i], gridData.zPositions[i]); Vector3 offset = new Vector3(gridData.xOffsetPositions[i], gridData.yOffsetPositions[i], gridData.zOffsetPositions[i]); Vector3 originalOffset = new Vector3(gridData.xOriginalOffsetPos[i], gridData.yOriginalOffsetPos[i], gridData.zOriginalOffsetPos[i]); float rotation = gridData.rotationY[i]; Building.ObjectSize objSize = new Building.ObjectSize(); bool moveOnPoints = gridData.moveOnPoints[i]; List checkPositions = new List(); int buildTime = gridData.buildTime[i]; int buildTimeRemaining = gridData.buildTimeRemaining[i]; int upgradeLevel = gridData.upgradeLevel[i]; //Checks to see if the object exists in the placeable objects folder if (resourceObject) { GameObject clonedObj; //Gets check positions for (int j = 0; j < gridData.amountOfCheckPositions[i]; j++) { Vector3 checkPosition = new Vector3( gridData.xcheckPositions[counter], gridData.ycheckPositions[counter], gridData.zcheckPositions[counter]); checkPositions.Add(checkPosition); counter++; } //Finds the correct object size booleans int k = 0; System.Reflection.FieldInfo[] clonedFields; clonedFields = objSize.GetType().GetFields(); foreach (System.Reflection.FieldInfo prop in objSize.GetType().GetFields()) { clonedFields[k].SetValue(objSize, gridData.selectChecks[k + objSizeCounter]); k++; } objSizeCounter += objSize.GetNum(); //Builds the GridObject data class GridObject.Data gridObjectData = new GridObject.Data(); gridObjectData.ObjName = gridData.objectName[i]; gridObjectData.PrefabId = gridData.prefabID[i]; gridObjectData.InstanceId = gridData.instanceID[i]; gridObjectData.Position = position; gridObjectData.CheckPositions = checkPositions; gridObjectData.Offset = offset; gridObjectData.OriginalOffset = originalOffset; gridObjectData.Rotation = rotation; gridObjectData.GridSquare = this; gridObjectData.ObjSize = objSize; gridObjectData.MoveOnPoints = moveOnPoints; gridObjectData.BuildTime = buildTime; gridObjectData.BuildTimeRemaining = buildTimeRemaining; gridObjectData.UpgradeLevel = upgradeLevel; //Rebuilds the objects back onto the grid if (objectPlacer) { //Has an upgrade timer remaining if(buildTimeRemaining > 0) { //In the middle of an upgrade with time remaining if(upgradeLevel >= 1) { clonedObj = objectPlacer.PlaceObject(resourceObject, gridObjectData, resourceObject.layer); } //Being built on a timer else { clonedObj = objectPlacer.DelayBuildStart(resourceObject, gridObjectData, resourceObject.layer, gridObjectData.BuildTime); } } else { clonedObj = objectPlacer.PlaceObject(resourceObject, gridObjectData, resourceObject.layer); } //If the object has an upgrade, rebuild the currentUgpradeLevel if (clonedObj.GetComponent()) { clonedObj.GetComponent().CurrentUpgradeLevel = upgradeLevel; if (buildTimeRemaining > 0 && (upgradeLevel >= 1)) { if (gridObjectOptions) { gridObjectOptions.SelectedObject = clonedObj; gridObjectOptions.StartUpgrade(); } } } if (gridSelector.PreviewObjFloorTiles && gridSelector.PlaceTilesWithObject && gridSelector.PreviewObjFloorTilePrefab) { //Rebuilds the floor tiles if enabled GameObject tileParent = new GameObject("FloorTileParent"); tileParent.transform.position = clonedObj.transform.position; tileParent.transform.parent = clonedObj.transform; foreach (Vector3 pos in checkPositions) { GameObject tile = Instantiate(gridSelector.PreviewObjFloorTilePrefab, pos + new Vector3(0, gridSelector.HoverDistance, 0), Quaternion.identity, tileParent.transform); tile.name = "previewFloorTile"; } tileParent.transform.localRotation = Quaternion.Euler(0, -rotation, 0); } //Re hides the cells underneath the loaded object if enabled if (objectPlacer.HideCellsUnderPlacedObj) { objectPlacer.HidePlacedObjCells(this, checkPositions); } } else { clonedObj = null; Debug.Log("Cannot load objects, need an object placer in the scene"); } //Blocks the cells ChangeCellStatus(position, clonedObj); for (int j = 0; j < gridData.amountOfCheckPositions[i]; j++) { Vector3 checkPosition = new Vector3( gridData.xcheckPositions[blockCellCounter], gridData.ycheckPositions[blockCellCounter], gridData.zcheckPositions[blockCellCounter]); ChangeCellStatus(checkPosition, clonedObj); blockCellCounter++; } } //Resource object does not exist else { //This adds one to the internal loop counters skipping one objectsSize if no resource is found counter++; objSizeCounter++; blockCellCounter++; Debug.Log($"{gridData.objectName[i]} not found in Resources/PlaceableObjects/"); } } } loaded = true; } } //Removes this grids save file, careful using this, this is the ONLY copy //Would normally be used for something like users manually deleting old save files public void DeleteGridSave() { SaveLoadGrid.DeleteCurrentGridSaveData(this); } public void LoadPreconfiguration() { //If it is already loaded, do not load again if (loaded) { return; } //Cannot load preconfig and save if(loadSaveOnStart) { return; } //Clears storage for initiating objects again ObjectStorage.GOTypeList.Clear(); ObjectStorage.GOInstanceList.Clear(); List destroyGOList = new List(); List changeStatusList = new List(); if (gridCellsStatus != null) foreach (KeyValuePair entry in gridCellsStatus) { if (entry.Value != null && entry.Value != this.gameObject) { destroyGOList.Add(entry.Value); changeStatusList.Add(entry.Key); } } //This is when you try to load after making changes on the current session without saving if (destroyGOList.Count > 0) { Debug.Log("There are unsaved changes, loading old save"); //If using a confirmation UI, put the below for loop in a coroutine to wait until the user has clicked for (int i = 0; i < destroyGOList.Count; i++) { ChangeCellStatus(changeStatusList[i], null); Destroy(destroyGOList[i].gameObject); } } //Will not try to load if you have hidden the grid, the data needs the gridSqaure object to be active if (gameObject.activeSelf) { int counter = 0; int objSizeCounter = 0; int blockCellCounter = 0; //Gets the data GridData gridData = SaveLoadGrid.LoadPreconfiguration(this); if (gridData != null) { //This loops through every placed object that was saved for (int i = 0; i < gridData.objectName.Count; i++) { //Outputs the gridData to all the initial variables the GridObject class needs /**************YOU MUST PUT YOUR PLACEABLE OBJECTS IN "Resources/PlaceableObjects/"*******************/ //These folders can be anywhere, but must contain the above directory ^^^^^^ GameObject resourceObject = (GameObject)Resources.Load($"PlaceableObjects/{gridData.objectName[i]}"); Vector3 position = new Vector3(gridData.xPositions[i], gridData.yPositions[i], gridData.zPositions[i]); Vector3 offset = new Vector3(gridData.xOffsetPositions[i], gridData.yOffsetPositions[i], gridData.zOffsetPositions[i]); Vector3 originalOffset = new Vector3(gridData.xOriginalOffsetPos[i], gridData.yOriginalOffsetPos[i], gridData.zOriginalOffsetPos[i]); float rotation = gridData.rotationY[i]; Building.ObjectSize objSize = new Building.ObjectSize(); bool moveOnPoints = gridData.moveOnPoints[i]; List checkPositions = new List(); int buildTime = gridData.buildTime[i]; int buildTimeRemaining = gridData.buildTimeRemaining[i]; int upgradeLevel = gridData.upgradeLevel[i]; //Checks to see if the object exists in the placeable objects folder if (resourceObject) { GameObject clonedObj; //Gets check positions for (int j = 0; j < gridData.amountOfCheckPositions[i]; j++) { Vector3 checkPosition = new Vector3( gridData.xcheckPositions[counter], gridData.ycheckPositions[counter], gridData.zcheckPositions[counter]); checkPositions.Add(checkPosition); counter++; } //Finds the correct object size booleans int k = 0; System.Reflection.FieldInfo[] clonedFields; clonedFields = objSize.GetType().GetFields(); foreach (System.Reflection.FieldInfo prop in objSize.GetType().GetFields()) { clonedFields[k].SetValue(objSize, gridData.selectChecks[k + objSizeCounter]); k++; } objSizeCounter += objSize.GetNum(); //Builds the GridObject data class GridObject.Data gridObjectData = new GridObject.Data(); gridObjectData.ObjName = gridData.objectName[i]; gridObjectData.PrefabId = gridData.prefabID[i]; gridObjectData.InstanceId = gridData.instanceID[i]; gridObjectData.Position = position; gridObjectData.CheckPositions = checkPositions; gridObjectData.Offset = offset; gridObjectData.OriginalOffset = originalOffset; gridObjectData.Rotation = rotation; gridObjectData.GridSquare = this; gridObjectData.ObjSize = objSize; gridObjectData.MoveOnPoints = moveOnPoints; gridObjectData.BuildTime = buildTime; gridObjectData.BuildTimeRemaining = buildTimeRemaining; gridObjectData.UpgradeLevel = upgradeLevel; //Rebuilds the objects back onto the grid if (objectPlacer) { if (buildTimeRemaining > 0) { //In the middle of an upgrade with time remaining if (upgradeLevel >= 1) { clonedObj = objectPlacer.PlaceObject(resourceObject, gridObjectData, resourceObject.layer); } //Being built on a timer else { clonedObj = objectPlacer.DelayBuildStart(resourceObject, gridObjectData, resourceObject.layer, gridObjectData.BuildTime); } } else { clonedObj = objectPlacer.PlaceObject(resourceObject, gridObjectData, resourceObject.layer); } //If the object has an upgrade, rebuild the currentUgpradeLevel if (clonedObj.GetComponent()) { clonedObj.GetComponent().CurrentUpgradeLevel = upgradeLevel; if (buildTimeRemaining > 0 && (upgradeLevel >= 1)) { if (gridObjectOptions) { gridObjectOptions.SelectedObject = clonedObj; gridObjectOptions.StartUpgrade(); } } } if (gridSelector.PreviewObjFloorTiles && gridSelector.PlaceTilesWithObject && gridSelector.PreviewObjFloorTilePrefab) { //Rebuilds the floor tiles if enabled GameObject tileParent = new GameObject("FloorTileParent"); tileParent.transform.position = clonedObj.transform.position; tileParent.transform.parent = clonedObj.transform; foreach (Vector3 pos in checkPositions) { GameObject tile = Instantiate(gridSelector.PreviewObjFloorTilePrefab, pos + new Vector3(0, gridSelector.HoverDistance, 0), Quaternion.identity, tileParent.transform); tile.name = "previewFloorTile"; } tileParent.transform.localRotation = Quaternion.Euler(0, -rotation, 0); } //Re hides the cells underneath the loaded object if enabled if (objectPlacer.HideCellsUnderPlacedObj) { objectPlacer.HidePlacedObjCells(this, checkPositions); } } else { clonedObj = null; Debug.Log("Cannot load objects, need an object placer in the scene"); } //Blocks the cells ChangeCellStatus(position, clonedObj); for (int j = 0; j < gridData.amountOfCheckPositions[i]; j++) { Vector3 checkPosition = new Vector3( gridData.xcheckPositions[blockCellCounter], gridData.ycheckPositions[blockCellCounter], gridData.zcheckPositions[blockCellCounter]); ChangeCellStatus(checkPosition, clonedObj); blockCellCounter++; } } //Resource object does not exist else { //This adds one to the internal loop counters skipping one objectsSize if no resource is found counter++; objSizeCounter++; blockCellCounter++; Debug.Log($"{gridData.objectName[i]} not found in Resources/PlaceableObjects/"); } } } loaded = true; } } //If the grid type is set to points, this calculates if a point should exist and where it should be //This function includes the autoCellBlock calculations private void CreatePointObjects(GameObject prefab, int i) { //Finds the vector3 position of each point on a cell float halfCell = cellSize * 0.5f; Vector3 point1 = gridPoints[i] + new Vector3(-halfCell, 0, halfCell); point1 = TrimToThreeDP(point1); Vector3 point2 = gridPoints[i] + new Vector3(halfCell, 0, halfCell); point2 = TrimToThreeDP(point2); Vector3 point3 = gridPoints[i] + new Vector3(halfCell, 0, -halfCell); point3 = TrimToThreeDP(point3); Vector3 point4 = gridPoints[i] + new Vector3(-halfCell, 0, -halfCell); point4 = TrimToThreeDP(point4); RaycastHit hit; //Depending on the blocktype selected, the following functions if statements //calculate if a point should exist or not switch(blocktype) { case (BlockType.BlockBoth): { if (Physics.Raycast(gridPoints[i], Vector3.down, out hit, GroundDistance)) { //This is the check for below, if it does not hit something, continue to check above if (hit.distance < GroundDistance) { if (checkGroundHits) { Debug.Log(hit.distance); } //This is the check for above, if it is clear, create a point if (CheckCellStatus(point1) || CheckCellStatus(point2) || CheckCellStatus(point3) || CheckCellStatus(point4)) { createPhysicalGridCellObject(prefab, i, false); } } } break; } //This is the check for above, if clear, create a point case (BlockType.BlockAbove): { if (CheckCellStatus(point1) || CheckCellStatus(point2) || CheckCellStatus(point3) || CheckCellStatus(point4)) { createPhysicalGridCellObject(prefab, i, false); } break; } //This is the check for below, if clear, create a point case (BlockType.BlockBelow): { if (Physics.Raycast(gridPoints[i], Vector3.down, out hit, GroundDistance)) { if (hit.distance < GroundDistance) { if (checkGroundHits) { Debug.Log(hit.distance); } createPhysicalGridCellObject(prefab, i, false); } } break; } } } //This function trims any numbers longer than 3 decimal points, fixing any rounding errors private Vector3 TrimToThreeDP(Vector3 num) { num.x = float.Parse(num.x.ToString("F3")); num.y = float.Parse(num.y.ToString("F3")); num.z = float.Parse(num.z.ToString("F3")); return num; } //Filters the cells to whether a cell block should be placed or not public void CreateAutoCellBlock(GameObject prefab, int i, bool preview) { RaycastHit hit; //Create four points from the centre of each cell float halfCell = cellSize * 0.5f; Vector3 point1 = new Vector3(-halfCell, 0, halfCell); Vector3 point2 = new Vector3(halfCell, 0, halfCell); Vector3 point3 = new Vector3(halfCell, 0, -halfCell); Vector3 point4 = new Vector3(-halfCell, 0, -halfCell); //Checks the points bool point1Clear = CheckPoint(point1); bool point2Clear = CheckPoint(point2); bool point3Clear = CheckPoint(point3); bool point4Clear = CheckPoint(point4); ClearAbove(i); //This function filters for any open space below the grid, thus not creating a cell there bool CheckPoint(Vector3 cellPoint) { if (Physics.Raycast(cells[i] + cellPoint, Vector3.down, out hit, GroundDistance, 1 << groundLayer)) { //Need to stop the underlaying grid having effect if (hit.distance < GroundDistance) { if (checkGroundHits) { Debug.Log(hit.distance); } return true; } else { return false; } } else { return false; } } switch (blocktype) { //Only creates cells if not blocked above or below case (BlockType.BlockBoth): { //Cells are both clear above and below if (point1Clear && point2Clear && point3Clear && point4Clear && ClearAbove(i)) { if (gridType != GridType.Points) { createPhysicalGridCellObject(prefab, i, false); if (autoCellBlockPreview) { freeCells.Add(prefab.GetComponent()); } } else { if (autoCellBlockPreview) { createPhysicalGridCellObject(prefab, i, false); freeCells.Add(prefab.GetComponent()); } } } //Cells are blocked either above or below else { if (blockedAboveCellPrefab != null && point1Clear && point2Clear && point3Clear && point4Clear) { createPhysicalGridCellObject(blockedAboveCellPrefab, i, true); } if (blockedBelowCellPrefab != null && ClearAbove(i)) { createPhysicalGridCellObject(blockedBelowCellPrefab, i, true); } if (!autoCellBlockPreview) { //Marks the cells with something in them //ChangeGridCellStatus(cells[i], null); ChangeCellStatus(cells[i], gameObject); } else { //Displays the blocked cells for the preview if (preview) { createPhysicalGridCellObject(prefab, i, true); } else { //ChangeGridCellStatus(cells[i], null); ChangeCellStatus(cells[i], gameObject); } if (autoCellBlockPreview) { blockedCells.Add(prefab.GetComponent()); } } } break; } //Only creates cells if not blocked above case (BlockType.BlockAbove): { if (ClearAbove(i)) { if (gridType != GridType.Points) { createPhysicalGridCellObject(prefab, i, false); if (autoCellBlockPreview) { freeCells.Add(prefab.GetComponent()); } } else { if (autoCellBlockPreview) { createPhysicalGridCellObject(prefab, i, false); freeCells.Add(prefab.GetComponent()); } } } else { //If a cell is blocked, place a prefab instead of leaving it empty if (blockedAboveCellPrefab != null) { createPhysicalGridCellObject(blockedAboveCellPrefab, i, true); } //Marks the cells with something in them if (Application.isPlaying) { //Marks the cells with something in them //ChangeGridCellStatus(cells[i], null); ChangeCellStatus(cells[i], gameObject); } else { //Displays the blocked cells for the preview if (preview) { createPhysicalGridCellObject(prefab, i, true); } else { //ChangeGridCellStatus(cells[i], null); ChangeCellStatus(cells[i], gameObject); } if(autoCellBlockPreview) { blockedCells.Add(prefab.GetComponent()); } } } break; } //Only creates cells if there is no empty space below case (BlockType.BlockBelow): { if (point1Clear && point2Clear && point3Clear && point4Clear) { if (gridType != GridType.Points) { createPhysicalGridCellObject(prefab, i, false); if (autoCellBlockPreview) { freeCells.Add(prefab.GetComponent()); } } else { if (autoCellBlockPreview) { createPhysicalGridCellObject(prefab, i, false); freeCells.Add(prefab.GetComponent()); } } } else { //If a cell is blocked, place a prefab instead of leaving it empty if (blockedBelowCellPrefab != null) { createPhysicalGridCellObject(blockedBelowCellPrefab, i, true); } //Marks the cells with something in them if (Application.isPlaying) { //Marks the cells with something in them //ChangeGridCellStatus(cells[i], null); ChangeCellStatus(cells[i], gameObject); } else { //Displays the blocked cells for the preview if (preview) { createPhysicalGridCellObject(prefab, i, true); } else { //ChangeGridCellStatus(cells[i], null); ChangeCellStatus(cells[i], gameObject); } if (autoCellBlockPreview) { blockedCells.Add(prefab.GetComponent()); } } } break; } } } private bool ClearAbove(int i) { //This section filters for anything blocking above the grid bool aboveClear = true; Collider[] colliders = Physics.OverlapBox(cells[i] + new Vector3(0, checkBoxOffset + (aboveCheckBoxHeight * 0.5f), 0), GetCheckBoxSize()); foreach (var item in colliders) { //If the hit is not a gridsquare component if (!item.GetComponent()) { int layerInt = item.gameObject.layer; if (ignoreLayers != (ignoreLayers | (1 << layerInt))) { aboveClear = false; } } } return aboveClear; } //This function creates the physical cell object using the given prefab private void createPhysicalGridCellObject(GameObject prefab, int i, bool blocked) { //Checks to see if there is a pointsPrefab to use if(gridType == GridType.Points && !pointsPrefab) { return; } //Checks to see if the grid type matches one that uses grid cells if (gridType != GridType.Points && gridType != GridType.Lines) { //Checks to see if a prefab is assigned in the inspector if application is in play mode if (Application.isPlaying && gridCellPrefab == null) { return; } } GameObject gridCellInstance; switch(gridType) { case (GridType.SingleCell): case (GridType.Chequered): case (GridType.Simple): { CreateSingleGridCellInstance(prefab, i, blocked); break; } case (GridType.Points): { if (Application.isPlaying || createInEditor && !autoCellBlockPreview) { gridCellInstance = Instantiate(prefab, gridPoints[i], Quaternion.identity); gridCellInstance.transform.parent = pointsContainer.transform; //Scales the points Vector3 gridCellTransform = gridCellInstance.transform.localScale; gridCellTransform.x = pointRadius; gridCellTransform.z = pointRadius; gridCellInstance.transform.localScale = gridCellTransform; //Names the points gridCellInstance.name = "GridPoint: " + gridPoints[i]; } else { CreateSingleGridCellInstance(prefab, i, blocked); } break; } case (GridType.Lines): { if (Application.isPlaying || createInEditor && !autoCellBlockPreview) { gridCellInstance = Instantiate(prefab, cells[i], Quaternion.identity); gridCellInstance.transform.parent = linesContainer.transform; //gridCells.Add(cells[i], gridCellInstance); //Names the lines gridCellInstance.name = "GridLine"; } else { CreateSingleGridCellInstance(prefab, i, blocked); } break; } } } private GameObject CreateSingleGridCellInstance(GameObject prefab, int i, bool blocked) { GameObject gridCellInstance; if (prefab != null) { gridCellInstance = Instantiate(prefab, cells[i], Quaternion.identity); } else { gridCellInstance = new GameObject(); } gridCellInstance.transform.parent = cellContainer.transform; //Scales the grid cells based on cell size Vector3 scale = gridCellInstance.transform.localScale; scale.x *= cellSize; scale.z *= cellSize; gridCellInstance.transform.localScale = scale; //Names the cells if (!blocked) { gridCellInstance.name = "GridCell: " + cells[i]; } else { gridCellInstance.name = "BlockedCell: " + cells[i]; } if (!autoCellBlockPreview) { ChangeGridCellStatus(cells[i], gridCellInstance); } else { //This adds the cells to relevent Lists to use them later for the Grid autocellblock preview in the editor if (!blocked) { freeCells.Add(gridCellInstance.GetComponent()); } else { blockedCells.Add(gridCellInstance.GetComponent()); } } return gridCellInstance; } //Determines the size of the checkbox used in the physics calculations public Vector3 GetCheckBoxSize() { checkBoxSize = new Vector3((cellSize * aboveCheckBoxSize) * 0.5f, aboveCheckBoxHeight * 0.5f, (cellSize * aboveCheckBoxSize) * 0.5f); return checkBoxSize; } //This is the core function creating all of the grid points in 3D space public void CreateGrid() { //Creates the initial grid of points if (gridWidth > 0 && gridHeight > 0) { //Double loop to go over grid width and grid height gridPoints = new Vector3[(gridWidth + 1) * (gridHeight + 1)]; for (int i = 0, x = 0; x <= gridWidth; x++) { for (int z = 0; z <= gridHeight; z++) { //Add the points into the data structure gridPoints[i] = new Vector3( transform.position.x + x * cellSize, transform.position.y, transform.position.z + z * cellSize); i++; } } } } //This uses the core function above to create the cells for the grid public void CreateGridCells() { //Calculates the total number of cells numCells = gridHeight * gridWidth; cells = new Vector3[numCells]; if (gridPoints == null) return; //Calculates the center of the grid cells from the grid points for (int i = 1, j = 0; i < gridPoints.Length - gridHeight; i++) { if (i % (gridHeight + 1) != 0) { cells[j] = gridPoints[i - 1] + new Vector3(cellSize, 0, cellSize) * 0.5f; float trimmedX = float.Parse(cells[j].x.ToString("F3")); float trimmedY = float.Parse(cells[j].y.ToString("F3")); float trimmedZ = float.Parse(cells[j].z.ToString("F3")); //Add the vectors into the data structure cells[j] = new Vector3(trimmedX, trimmedY, trimmedZ); j++; } } } //Initialises each cell with a value of null to begin with, or in other words, its empty. public void CreateGridStatus() { gridCellsStatus = new Dictionary(); for (int i = 0; i < cells.Length; i++) { gridCellsStatus.Add(cells[i], null); }; } public void CreateGridCellStatus() { gridCells = new Dictionary(); for (int i = 0; i < cells.Length; i++) { gridCells.Add(cells[i], null); }; } //This function checks to see if a cell is empty. public bool CheckCellStatus(Vector3 cellPos) { bool isEmpty; GameObject value; isEmpty = gridCellsStatus.TryGetValue(cellPos, out value); //If a given cell position is empty and exists it will return true otherwise it will return false if (value == null && isEmpty) { isEmpty = true; } else { isEmpty = false; } return isEmpty; } //This function checks to see if a cell is on the visibly created cells only. //This is used by the ObjectPlacer Overwrite field. public bool CheckIfOnGrid(Vector3 cellPos) { bool isEmpty; GameObject value; gridCellsStatus.TryGetValue(cellPos, out value); //If a given cell was created if (value != this.gameObject) { isEmpty = false; } else { isEmpty = true; } return isEmpty; } //This simply changes the value of the given cell position to something other than null, so the cell is not empty //You can pass null here to empty it public void ChangeCellStatus(Vector3 cellPos, GameObject obj) { gridCellsStatus[cellPos] = obj; } //This adds the given cell prefab to the cells List at its position public void ChangeGridCellStatus(Vector3 cellPrefabPos, GameObject obj) { gridCells[cellPrefabPos] = obj; } //Returns the cell GameObject occupied in the gridCells dictionary at the given position //If nothing is found or position does not exist, it will return null public GameObject GetCellObject(Vector3 pos) { GameObject obj; gridCells.TryGetValue(pos, out obj); return obj; } //Returns the placed GameObject occupied in the gridCellsStatus dictionary at the given position //If nothing is found or position does not exist, it will return null public GameObject GetGridObject(Vector3 pos) { GameObject obj; gridCellsStatus.TryGetValue(pos, out obj); return obj; } /***********The following functions create initialise each grid type*******************/ //Initialises the single cell and chequered grid types private void CreateSingleCellGrid() { //Chequered type with autocellblocking if(gridType == GridType.Chequered) { if(autoCellBlocking) { int c = 0; //Loops through all the cells for (int i = 0; i < gridWidth; i++) { for (int j = 0; j < gridHeight; j++) { //This creates the checkered effect if ((i + j) % 2 == 0) { CreateAutoCellBlock(gridCellPrefab, c, false); } else { if(secondGridCellPrefab) { CreateAutoCellBlock(secondGridCellPrefab, c, false); } else { ChangeGridCellStatus(cells[c], gameObject); Debug.Log("Assign a second prefab in inspector for checkered pattern"); } } c++; } } } //Chequered type without autocellblocking else { int c = 0; for (int i = 0; i < gridWidth; i++) { for (int j = 0; j < gridHeight; j++) { if ((i + j) % 2 == 0) { createPhysicalGridCellObject(gridCellPrefab, c, false); } else { if(secondGridCellPrefab) { createPhysicalGridCellObject(secondGridCellPrefab, c, false); } else { Debug.Log("Assign a second prefab in inspector for checkered pattern"); } } c++; } } } } if(gridType == GridType.SingleCell) { //Single cell type with autocellblocking if(autoCellBlocking) { for (int i = 0; i < cells.Length; i++) { CreateAutoCellBlock(gridCellPrefab, i, false); } } //Single cell type without autocellblocking else { for (int i = 0; i < cells.Length; i++) { createPhysicalGridCellObject(gridCellPrefab, i, false); } } } } //Creates the simple type, creating a new uv'd mesh plane private void CreateSimpleGrid() { //Creates 4 vertices from the gridPoints array meshVertices = new Vector3[] { gridPoints[0] - transform.position, gridPoints[gridHeight] - transform.position, gridPoints[gridWidth * (gridHeight + 1)] - transform.position, gridPoints[gridPoints.Length - 1] - transform.position }; //Creates the 2 triangles meshTriangles = new int[] { 0, 1, 2, 1, 3, 2 }; CreateMesh(); } //Builds and assigns the vertices and triangles to the mesh //Also updates the normals private void CreateMesh() { mesh.Clear(); mesh.vertices = meshVertices; mesh.triangles = meshTriangles; mesh.name = "SimpleGridMesh"; CreateUVs(); mesh.uv = uvs; UpdateMaterial(); mesh.RecalculateNormals(); } //Creates the UV's for the simple grid type private void CreateUVs() { uvs = new Vector2[meshVertices.Length]; uvs[0] = new Vector2(0, 0); uvs[1] = new Vector2(0, 1); uvs[2] = new Vector2(1, 0); uvs[3] = new Vector2(1, 1); } //Updates the simple grid types material with tiling equivalent to the grid height and width private void UpdateMaterial() { if(rend) rend.sharedMaterial.mainTextureScale = new Vector2((float)gridWidth / tileX, (float)gridHeight / tileY); } //Calculates the Point grid type private void CreatePointGrid() { //With autocellblock if(autoCellBlocking) { for (int i = 0; i < cells.Length; i++) { //Does the normal cell blocking without physically creating anything CreateAutoCellBlock(pointsPrefab, i, false); } for (int i = 0; i < gridPoints.Length; i++) { //This is the function that goes on to create the points using the already 'available' cells specified in the last for loop CreatePointObjects(pointsPrefab, i); } } //Without autocellblock else { for (int i = 0; i < gridPoints.Length; i++) { createPhysicalGridCellObject(pointsPrefab, i, false); } } } //Creates the lines type grid private void CreateLinesGrid() { //Offset so the center of the lines is central on the cell, not on grid point Vector3 offset = new Vector3((-cellSize * 0.5f), 0, (-cellSize * 0.5f)); //Calculates each vertex position of the lines linesVertices = new Vector3[] { new Vector3(linesThickness, 0, linesThickness) + offset, new Vector3(-linesThickness, 0, -linesThickness) + offset, new Vector3(-linesThickness, 0, (cellSize + linesThickness)) + offset, new Vector3(linesThickness, 0, (cellSize - linesThickness)) + offset, new Vector3((cellSize + LinesThickness), 0, (cellSize + LinesThickness)) + offset, new Vector3((cellSize - LinesThickness), 0, (cellSize - LinesThickness)) + offset, new Vector3((cellSize + linesThickness), 0, -linesThickness) + offset, new Vector3((cellSize - linesThickness), 0, linesThickness) + offset }; //Triangle vertex order linesTriangles = new int[] { 0, 1, 2, 0, 2, 3, 2, 4, 3, 3, 4, 5, 4, 6, 5, 5, 6, 7, 7, 6, 1, 0, 7, 1 }; //Appends everything to the mesh linesMesh.Clear(); linesMesh.vertices = linesVertices; linesMesh.triangles = linesTriangles; linesMesh.name = "LinesMesh"; line = new GameObject("Line"); line.AddComponent().mesh = linesMesh; MeshRenderer ownMR = GetComponent(); MeshRenderer lineMR = line.AddComponent(); lineMR.sharedMaterial = ownMR.sharedMaterial; lineMR.shadowCastingMode = ownMR.shadowCastingMode; lineMR.receiveShadows = ownMR.receiveShadows; lineMR.allowOcclusionWhenDynamic = ownMR.allowOcclusionWhenDynamic; //Creates the lines only on available cells for (int i = 0; i < cells.Length; i++) { if (autoCellBlocking) { CreateAutoCellBlock(line, i, false); } //Creates the lines on all cells else { createPhysicalGridCellObject(line, i, false); } } //Removes the original line created for instantiating if(createInEditor) { DestroyImmediate(line); } else { Destroy(line); } } //Changes the size and position of the collider based on cellSize and the grid width and height private void SetColliderSize() { meshCollider.size = new Vector3(gridWidth * cellSize, colliderThickness, gridHeight * cellSize); meshCollider.center = new Vector3((float)gridWidth * cellSize / 2, (colliderThickness / 2), (float)gridHeight * cellSize / 2); } //These functions initialise all the data for creating the grids points, so they can then be previewed in the viewport private void OnValidate() { if(!Application.isPlaying) { CreateGrid(); CreateGridCells(); } } //Creates the viewport preview of some of the basic settings private void OnDrawGizmos() { Gizmos.color = Color.red; //If you move the grid, reinitialise its points if (transform.hasChanged) { CreateGrid(); CreateGridCells(); transform.hasChanged = false; } //Wont run at build time #if UNITY_EDITOR //These two functions are used for debugging, they will impact performance if used on large grids if (drawGridPositions) { //Draw the points the grid is made up from DrawPointPositions(); } if(drawCellPositions) { //Draw the cell coordinates in world space DrawCellPositions(); } //Previews the autocellblocking setup features if (autoCellBlocking) { if (showAboveBoxColliders) { //Draw the autocellblock physics check boxes DrawAutoCellBlockingBoxes(); } if(showBelowRays) { //Draw the rays to check for the ground object DrawAutoCellBlockingRays(); } } //Draws the grid gizmo if(!drawSimple) { DrawLines(); } else { DrawSimpleLines(); } #endif } //Draws only the outside lines of the grid, useful if your grid is extremely large to speed up scene view private void DrawSimpleLines() { Gizmos.DrawLine(gridPoints[0], gridPoints[gridHeight]); Gizmos.DrawLine(gridPoints[gridHeight], gridPoints[gridPoints.Length - 1]); Gizmos.DrawLine(gridPoints[gridPoints.Length - 1], gridPoints[gridPoints.Length - 1 - gridHeight]); Gizmos.DrawLine(gridPoints[0], gridPoints[gridPoints.Length - 1 - gridHeight]); } //Draws a line from each grid point extending as far as the ground distance, this is how far the physics check goes private void DrawAutoCellBlockingRays() { for (int i = 0; i < gridPoints.Length; i++) { Gizmos.DrawLine(gridPoints[i], gridPoints[i] + new Vector3(0, -GroundDistance, 0)); } } //Draws a box from the center of each cell private void DrawAutoCellBlockingBoxes() { if (cells != null) { for (int i = 0; i < cells.Length; i++) { Gizmos.DrawWireCube(cells[i] + new Vector3(0, checkBoxOffset + (aboveCheckBoxHeight * 0.5f), 0), new Vector3(cellSize * aboveCheckBoxSize, aboveCheckBoxHeight, cellSize * aboveCheckBoxSize)); } } } //Wont run at build time #if UNITY_EDITOR //Draws the cell position as a Vector3 private void DrawCellPositions() { for (int i = 0; i < numCells; i++) { Handles.Label(cells[i], cells[i].ToString("F3")); } } //Draws the point positions as a Vector3 private void DrawPointPositions() { for (int i = 0; i < gridPoints.Length; i++) { Handles.Label(gridPoints[i], new Vector2(gridPoints[i].x, gridPoints[i].z).ToString("F3")); } } #endif //Draws the Lines of the grid private void DrawLines() { if (gridHeight > 0 && gridWidth > 0) { //Z lines for (int x = 0; x < gridWidth + 1; x++) { for (int z = 0; z < gridHeight; z++) { Gizmos.DrawLine(gridPoints[x * (gridHeight + 1) + z], gridPoints[x * (gridHeight + 1) + z + 1]); } } //X lines for (int z = 0; z < gridHeight + 1; z++) { for (int x = 0; x < gridWidth; x++) { Gizmos.DrawLine(gridPoints[z * gridWidth + x], gridPoints[z * gridWidth + (gridHeight + 1) + x]); } } } } //Useful function to call to get a list of all of the currently placed objects on this grid public List GetCellsDictionary() { List allPlacedObjects = new List(); foreach (KeyValuePair entry in gridCellsStatus) { //Debug.Log(entry); allPlacedObjects.Add(entry.Value); } return allPlacedObjects; } //Useful function to call to get a list of all of the currently placed objects on this grid public List GetGridCellsDictionary() { List allPlacedObjects = new List(); foreach (KeyValuePair entry in gridCells) { //Debug.Log(entry); allPlacedObjects.Add(entry.Value); } return allPlacedObjects; } }