Hello all! I’ve been working on a building system for a while now. My building code has grown to be almost 800 lines, and its kind of a hassle to keep scrolling around when adding new content and fixing bugs.
I’m wondering if anyone has any suggestions for better organizing my code?
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using TMPro;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.EventSystems;
public class BuildManager : MonoBehaviour
{
public static BuildManager instance;
public enum GameState {BuildMode, DeleteMode, PaintMode}
public GameState state;
[SerializeField] TMP_Text stateText;
[SerializeField] TMP_Text stateInfo;
public bool randomRotationEnabled;
public Vector3Int lastGridPos;
public Vector3 blockPos;
Vector3 size;
public List<Vector3> currentCoordinates;
public List<Vector3> unavailableCoordinates;
List<GameObject> indicators = new List<GameObject>();
public GameObject coordinateIndicator;
GameObject mouseIndicator;
public BuildingBlocks chosenBlock;
[SerializeField] GameObject gridRepresentation;
[SerializeField] Grid grid;
public Material indicatorMaterial;
public bool areaSutableForBuilding;
public bool canPlaceABlock = true;
public bool canDestroy;
public GameObject lastSelectedGameObject;
public GameObject currentSelectedGameObject;
public Material currentSelected_Material;
public GameObject lastBlockPlaced;
int blocksPlaced;
public int playerSpawnersPlaced;
public int spawnersPlaced;
public int chestsPlaced;
Vector3 activationPoint;
Vector3 massP_activation;
Vector3 currentPoint;
Vector3 releasePoint;
Vector3 pos;
float greatestX, greatestY;
float lowestX, lowestY;
GameObject massSelectIndicator;
[SerializeField] Material massSelectMaterial;
private void Awake()
{
instance = this;
}
// Start is called before the first frame update
void Start()
{
state = GameState.BuildMode;
currentCoordinates = new List<Vector3>();
mouseIndicator = new GameObject("indicator");
mouseIndicator.layer = 2;
mouseIndicator.AddComponent<MeshFilter>();
mouseIndicator.AddComponent<MeshRenderer>();
mouseIndicator.AddComponent<MeshCollider>();
mouseIndicator.transform.forward = Vector3.forward;
chosenBlock = BlockManager.instance.blocks_building[0];
spawnersPlaced = 0;
}
// Update is called once per frame
void Update()
{
float rot = mouseIndicator.transform.rotation.eulerAngles.y;
if (rot == 0 || rot == 180 || rot == 360)
{
size = new Vector3Int((int)chosenBlock.block.transform.localScale.x, (int)chosenBlock.block.transform.localScale.y, (int)chosenBlock.block.transform.localScale.z);
}
else if (rot == 90 || rot == 270)
{
size = new Vector3Int((int)chosenBlock.block.transform.localScale.z, (int)chosenBlock.block.transform.localScale.y, (int)chosenBlock.block.transform.localScale.x);
}
gridRepresentation.transform.position = new Vector3(gridRepresentation.transform.position.x, blockPos.y + 0.075f, gridRepresentation.transform.position.z);
if(Input.GetKeyDown(KeyCode.LeftAlt))
{
if(state != GameState.DeleteMode)
{
canDestroy = true;
state = GameState.DeleteMode;
}
else if(state == GameState.DeleteMode)
{
canDestroy = false;
currentSelectedGameObject.GetComponent<Renderer>().material = currentSelected_Material;
Destroy(currentSelectedGameObject.GetComponent<Outline>());
state = GameState.BuildMode;
}
}
if (Input.GetKeyDown(KeyCode.P))
{
if(state == GameState.DeleteMode)
{
currentSelectedGameObject.GetComponent<Renderer>().material = currentSelected_Material;
Destroy(currentSelectedGameObject.GetComponent<Outline>());
}
if(state != GameState.PaintMode)
{
state = GameState.PaintMode;
canDestroy = false;
}
else
{
state = GameState.BuildMode;
canDestroy = false;
}
}
GridSystem();
VisualizeGrid();
CheckPlacement();
Indicator();
if (Input.GetKeyDown(KeyCode.R))
{
mouseIndicator.transform.Rotate(0, 90, 0);
}
switch (state)
{
case GameState.BuildMode:
CreateCorner();
stateText.text = "Build Mode";
stateInfo.text = "Press ALT to Delete \nPress P to Mass Paint";
if (chosenBlock.block.name.Contains("PlayerSpawn"))
{
if(GameObject.Find("PlayerSpawn") != null)
{
canPlaceABlock = false;
}
}
if (Input.GetMouseButtonDown(0))
{
RaycastHit hit;
Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out hit, 100);
if (canPlaceABlock && areaSutableForBuilding && !EventSystem.current.IsPointerOverGameObject())
{
Quaternion rotation = new Quaternion();
int[] rotations = { 0, 90, 180 ,270};
if (randomRotationEnabled)
{
if(chosenBlock.block.transform.localScale.x == chosenBlock.block.transform.localScale.z)
{
rotation = Quaternion.Euler(0, rotations[Random.Range(0 , rotations.Length)], 0);
}
}
else
{
rotation = mouseIndicator.transform.rotation;
}
foreach(var coord in currentCoordinates.ToList())
{
unavailableCoordinates.Add(coord);
}
lastBlockPlaced = Instantiate(chosenBlock.block, blockPos, rotation);
lastBlockPlaced.name = chosenBlock.block.name;
lastBlockPlaced.AddComponent<PhysicalBlock>();
lastBlockPlaced.transform.GetComponent<PhysicalBlock>().block = chosenBlock;
if(lastBlockPlaced.GetComponent<PhysicalBlock>().coordinates.Count == 0)
{
lastBlockPlaced.transform.GetComponent<PhysicalBlock>().coordinates = currentCoordinates.ToList();
}
lastBlockPlaced.transform.GetComponent<AudioSource>().clip = chosenBlock.placeSound;
lastBlockPlaced.transform.GetComponent<AudioSource>().Play();
blocksPlaced++;
if (chosenBlock.block.name.Contains("Spawner"))
{
int spawnerID = spawnersPlaced + 1;
GameManager.instance.enemySpawnerData(lastBlockPlaced, spawnerID);
spawnersPlaced++;
}
else if (chosenBlock.block.name.Contains("Chest"))
{
int chestID = chestsPlaced + 1;
GameManager.instance.chestData(lastBlockPlaced, chestID);
chestsPlaced++;
}
}
}
break;
case GameState.DeleteMode:
stateText.text = "Delete Mode";
stateInfo.text = "Press ALT to Build \nPress P to Mass Paint";
if (canDestroy && !EventSystem.current.IsPointerOverGameObject())
{
Delete();
}
break;
case GameState.PaintMode:
stateText.text = "Paint Mode";
stateInfo.text = "Press ALT to Delete \nPress P to Build";
if (!EventSystem.current.IsPointerOverGameObject())
{
if(chosenBlock.canBeMassedPlaced)
{
MassPlacement();
}
}
break;
}
}
void GridSystem()
{
RaycastHit hit;
if(Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out hit, 100))
{
lastGridPos = grid.WorldToCell(hit.point);
lastGridPos.y = Mathf.RoundToInt(hit.point.y);
//lets make the reference point a corner of the block (lower right)
Vector3Int corner = lastGridPos;
currentCoordinates.Clear();
for(int x = 0; x < size.x; x++)
{
for(int z = 0; z < size.z; z++)
{
for(int y = 0; y < size.y; y++)
{
Vector3 coord = new Vector3(corner.x - x, blockPos.y + y, corner.z + z);
if(!currentCoordinates.Contains(coord))
{
currentCoordinates.Add(coord);
}
}
}
}
List<float> xPositions = new List<float>();
List<float> yPositions = new List<float>();
foreach (var coord in currentCoordinates)
{
xPositions.Add(coord.x);
yPositions.Add(coord.z);
}
float lowestX = Mathf.Min(xPositions.ToArray());
float lowestY = Mathf.Min(yPositions.ToArray());
float greatestX = Mathf.Max(xPositions.ToArray());
float greatestY = Mathf.Max(yPositions.ToArray());
float center_x = lowestX + (greatestX - lowestX) / 2;
float center_y = lowestY + (greatestY - lowestY) / 2;
Vector2 center = new Vector2(center_x, center_y);
blockPos = new Vector3(center.x, Mathf.RoundToInt(hit.point.y), center.y);
}
}
void VisualizeGrid()
{
if(indicators.Count < currentCoordinates.Count)
{
foreach (var coord in currentCoordinates)
{
GameObject indicator = Instantiate(coordinateIndicator, coord, Quaternion.identity);
indicator.transform.position = new Vector3(coord.x, gridRepresentation.transform.position.y, coord.z);
if (!indicators.ToList().Contains(indicator))
{
indicators.Add(indicator);
}
}
}
else if(indicators.Count == currentCoordinates.Count)
{
int i = 0;
foreach(var coord_indicator in indicators)
{
if(coord_indicator.transform.position != new Vector3(currentCoordinates[i].x, gridRepresentation.transform.position.y, currentCoordinates[i].z))
{
coord_indicator.transform.position = new Vector3(currentCoordinates[i].x, gridRepresentation.transform.position.y, currentCoordinates[i].z);
}
i++;
}
}
else if(indicators.Count > currentCoordinates.Count)
{
foreach(var coord_indicator in indicators.ToList())
{
if(coord_indicator != null)
{
if (!currentCoordinates.Contains(new Vector3(coord_indicator.transform.position.x, gridRepresentation.transform.position.y + blockPos.y, coord_indicator.transform.position.z)))
{
Destroy(coord_indicator);
}
}
}
}
foreach(var coord_indicator in indicators.ToList())
{
if (coord_indicator != null)
{
if(state == GameState.BuildMode)
{
coord_indicator.SetActive(true);
}
else
{
coord_indicator.SetActive(false);
}
if (areaSutableForBuilding && canPlaceABlock)
{
coord_indicator.GetComponent<Renderer>().material.color = new Color(0, 1, 0);
}
else
{
coord_indicator.GetComponent<Renderer>().material.color = new Color(1, 0, 0);
}
}
else
{
indicators.Remove(coord_indicator);
}
}
}
void CreateCorner() //the block we will be placing will be a corner!
{
Vector2Int size = new Vector2Int((int)chosenBlock.block.transform.localScale.x, (int)chosenBlock.block.transform.localScale.z);
//L = left, R = right, U = up, D = down
Vector3Int coord_L = grid.WorldToCell(new Vector3(blockPos.x - size.x, 0, blockPos.z));
Vector3Int coord_R = grid.WorldToCell(new Vector3(blockPos.x + size.x, 0, blockPos.z));
Vector3Int coord_U = grid.WorldToCell(new Vector3(blockPos.x, 0, blockPos.z + size.y));
Vector3Int coord_D = grid.WorldToCell(new Vector3(blockPos.x, 0, blockPos.z - size.y));
if (unavailableCoordinates.Contains(coord_L) && unavailableCoordinates.Contains(coord_U))
{
//We have a lower right corner block
print("lower right corner!");
}
if (unavailableCoordinates.Contains(coord_L) && unavailableCoordinates.Contains(coord_D))
{
//We have an upper right corner block
print("upper right corner!");
}
if (unavailableCoordinates.Contains(coord_R) && unavailableCoordinates.Contains(coord_U))
{
//We have a lower left corner block
print("lower left corner!");
}
if (unavailableCoordinates.Contains(coord_R) && unavailableCoordinates.Contains(coord_D))
{
//We have an upper left corner block
print("upper left corner!");
}
}
void MassPlacement()
{
RaycastHit hit;
if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out hit, 100))
{
if (Input.GetMouseButtonDown(0))
{
activationPoint = lastGridPos;
massP_activation = blockPos;
if (massSelectIndicator == null)
{
massSelectIndicator = GameObject.CreatePrimitive(PrimitiveType.Cube);
massSelectIndicator.AddComponent<Outline>();
massSelectIndicator.transform.transform.localScale = chosenBlock.block.transform.localScale;
massSelectIndicator.GetComponent<BoxCollider>().enabled = false;
massSelectMaterial.color = new Color(0, 1, 0, 0.75f);
massSelectIndicator.GetComponent<Renderer>().material = massSelectMaterial;
}
}
if (Input.GetMouseButton(0))
{
currentPoint = blockPos;
if (massSelectIndicator != null)
{
float currentXScale = chosenBlock.block.transform.localScale.x;
float currentYScale = chosenBlock.block.transform.localScale.z;
float xScale = massP_activation.x - currentPoint.x;
float yScale = massP_activation.z - currentPoint.z;
if (Mathf.Abs(xScale) <= chosenBlock.block.transform.localScale.x)
{
xScale = 0;
}
if (Mathf.Abs(yScale) <= chosenBlock.block.transform.localScale.z)
{
yScale = 0;
}
if (mouseIndicator.transform.eulerAngles.y == 90 || mouseIndicator.transform.eulerAngles.y == -90 || mouseIndicator.transform.eulerAngles.y == 270)
{
//print("at a 90 degree turn");
massSelectIndicator.transform.localScale = new Vector3(currentYScale + Mathf.Abs(xScale), chosenBlock.block.transform.localScale.y, currentXScale + Mathf.Abs(yScale));
}
else
{
massSelectIndicator.transform.localScale = new Vector3(currentXScale + Mathf.Abs(xScale), chosenBlock.block.transform.localScale.y, currentYScale + Mathf.Abs(yScale));
}
print(massSelectIndicator.transform.localScale);
float xPos = (massP_activation.x + currentPoint.x) / 2;
float yPos = (massP_activation.z + currentPoint.z) / 2;
massSelectIndicator.transform.position = new Vector3(xPos, massP_activation.y + chosenBlock.block.transform.localScale.y / 2, yPos);
}
}
if (Input.GetMouseButtonUp(0))
{
Destroy(massSelectIndicator);
releasePoint = lastGridPos;
if (activationPoint.x > releasePoint.x)
{
greatestX = activationPoint.x;
lowestX = releasePoint.x;
}
else if (activationPoint.x < releasePoint.x)
{
lowestX = activationPoint.x;
greatestX = releasePoint.x;
}
else if (activationPoint.x == releasePoint.x)
{
lowestX = activationPoint.x;
greatestX = lowestX;
}
if (activationPoint.z > releasePoint.z)
{
greatestY = activationPoint.z;
lowestY = releasePoint.z;
}
else if (activationPoint.z < releasePoint.z)
{
lowestY = activationPoint.z;
greatestY = releasePoint.z;
}
else if (activationPoint.z == releasePoint.z)
{
lowestY = activationPoint.z;
greatestY = lowestY;
}
float xScale = greatestX - lowestX;
float numberofX = xScale / chosenBlock.block.transform.localScale.x;
float yScale = greatestY - lowestY;
float numberofY = yScale / chosenBlock.block.transform.localScale.z;
for (int w = 0; w <= numberofX; w++)
{
for (int l = 0; l <= numberofY; l++)
{
float modifierX = 1;
float modifierY = 1;
if (activationPoint.x > releasePoint.x)
{
modifierX = -1;
}
if (activationPoint.z > releasePoint.z)
{
modifierY = -1;
}
List<Vector3> newBlockCoords = new List<Vector3>();
//Make pos a corner!
pos = new Vector3(activationPoint.x + modifierX * (chosenBlock.block.transform.localScale.x * w), activationPoint.y, activationPoint.z + modifierY * (chosenBlock.block.transform.localScale.z * l));
Vector3Int corner = grid.WorldToCell(pos);
for (int x = 0; x < size.x; x++)
{
for (int z = 0; z < size.z; z++)
{
for (int y = 0; y < size.y; y++)
{
Vector3 coord = new Vector3(corner.x - x, blockPos.y + y, corner.z + z);
if (!newBlockCoords.Contains(coord))
{
newBlockCoords.Add(coord);
}
}
}
}
int intersections = 0;
foreach (var coord in newBlockCoords)
{
if (unavailableCoordinates.Contains(coord))
{
intersections++;
}
}
if (intersections == 0)
{
List<float> xPositions = new List<float>();
List<float> yPositions = new List<float>();
foreach (var coord in newBlockCoords)
{
xPositions.Add(coord.x);
yPositions.Add(coord.z);
}
float lowestX = Mathf.Min(xPositions.ToArray());
float lowestY = Mathf.Min(yPositions.ToArray());
float greatestX = Mathf.Max(xPositions.ToArray());
float greatestY = Mathf.Max(yPositions.ToArray());
float center_x = lowestX + (greatestX - lowestX) / 2;
float center_y = lowestY + (greatestY - lowestY) / 2;
Vector2 center = new Vector2(center_x, center_y);
blockPos = new Vector3(center.x, pos.y, center.y);
Quaternion rotation = new Quaternion();
int[] rotations = { 0, 90, 180, 270, 360 };
if (randomRotationEnabled)
{
if (chosenBlock.block.transform.localScale.x == chosenBlock.block.transform.localScale.z)
{
rotation = Quaternion.Euler(0, rotations[Random.Range(0, rotations.Length)], 0);
}
}
else
{
rotation = mouseIndicator.transform.rotation;
}
foreach (var coord in newBlockCoords.ToList())
{
unavailableCoordinates.Add(coord);
}
lastBlockPlaced = Instantiate(chosenBlock.block, blockPos, rotation);
lastBlockPlaced.name = chosenBlock.block.name;
lastBlockPlaced.AddComponent<PhysicalBlock>();
lastBlockPlaced.transform.GetComponent<PhysicalBlock>().block = chosenBlock;
if (lastBlockPlaced.GetComponent<PhysicalBlock>().coordinates.Count == 0)
{
lastBlockPlaced.transform.GetComponent<PhysicalBlock>().coordinates = newBlockCoords.ToList();
}
lastBlockPlaced.transform.GetComponent<AudioSource>().clip = chosenBlock.placeSound;
lastBlockPlaced.transform.GetComponent<AudioSource>().Play();
blocksPlaced++;
}
}
}
}
}
}
void CheckPlacement()
{
int intersections = 0;
foreach(var coord in currentCoordinates)
{
if (unavailableCoordinates.Contains(coord))
{
intersections++;
}
}
if(intersections == 0)
{
areaSutableForBuilding = true;
}
else
{
areaSutableForBuilding= false;
}
}
void Indicator()
{
mouseIndicator.SetActive(true);
if(mouseIndicator.GetComponent<MeshFilter>().mesh != null && !mouseIndicator.GetComponent<Outline>())
{
mouseIndicator.AddComponent<Outline>();
mouseIndicator.GetComponent<Outline>().enabled = true;
}
mouseIndicator.GetComponent<MeshFilter>().mesh.Clear();
mouseIndicator.GetComponent<MeshFilter>().mesh = chosenBlock.block.GetComponentInChildren<MeshFilter>().sharedMesh;
mouseIndicator.GetComponent<Renderer>().material = indicatorMaterial;
indicatorMaterial.color = new Color(1, 1, 1, 0.75f);
mouseIndicator.transform.localScale = Vector3.one;
if(areaSutableForBuilding && canPlaceABlock)
{
indicatorMaterial.mainTexture = chosenBlock.block.GetComponentInChildren<Renderer>().sharedMaterial.mainTexture;
}
else
{
indicatorMaterial.mainTexture = chosenBlock.block.GetComponentInChildren<Renderer>().sharedMaterial.mainTexture;
}
mouseIndicator.transform.position = blockPos;
}
void Delete()
{
canPlaceABlock = true; //reset the canPlace a block
mouseIndicator.SetActive(false);
RaycastHit hit;
if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out hit, 100))
{
if(currentSelectedGameObject != null && currentSelectedGameObject != hit.transform.gameObject)
{
lastSelectedGameObject = currentSelectedGameObject;
lastSelectedGameObject.GetComponent<Renderer>().material = currentSelected_Material;
Destroy(lastSelectedGameObject.GetComponent<Outline>());
}
currentSelectedGameObject = hit.transform.gameObject;
if(hit.transform.gameObject.layer == 3)
{
if(!hit.transform.gameObject.GetComponent<Renderer>().material.color.Equals(new Color(1, 1, 1, 0.5f)))
{
currentSelected_Material = hit.transform.gameObject.GetComponent<Renderer>().material;
}
hit.transform.GetComponent<Renderer>().material = new Material(indicatorMaterial);
hit.transform.GetComponent<Renderer>().material.color = new Color(1, 1, 1, 0.5f);
hit.transform.GetComponent<Renderer>().material.mainTexture = null;
if (!hit.transform.GetComponent<Outline>())
{
hit.transform.AddComponent<Outline>();
}
if (Input.GetMouseButtonDown(0))
{
if (hit.transform.parent.gameObject.name.Contains("Spawner"))
{
spawnersPlaced--;
}
bool playedSound = false;
if(!playedSound)
{
AudioSource.PlayClipAtPoint(hit.transform.parent.GetComponent<PhysicalBlock>().block.breakSound, Camera.main.transform.position, 0.25f); //We have to create the temporary audio source at the camera pos b/c unity automatically applies logarithmic falloff
playedSound = true;
}
if(playedSound)
{
foreach(var coord in hit.transform.parent.gameObject.GetComponent<PhysicalBlock>().coordinates)
{
unavailableCoordinates.Remove(coord);
}
Destroy(hit.transform.parent.gameObject);
}
}
}
}
}
public void RandomRotation(bool toggle)
{
randomRotationEnabled = toggle;
}
private void OnDrawGizmos()
{
foreach(var coord in unavailableCoordinates)
{
Gizmos.DrawSphere(coord, 0.25f);
}
}
}