Help/Suggestions with reformatting and reorganizing my code?

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);
        }

   
    }

}

Use #region statements to hide away the code until you need to see it.

2 Likes

That’s what often happens to a generically named class suffixed with “Manager”. There are no mental boundaries to what seems reasonable to add in a BuildManager, including code that runs while making a build. :wink:

An instant code smell is the number of fields in this class. You can tell right away that this class is doing too much (see: single responsibility principle). By the types of these fields you can also see it handles GUI and Input and grid drawing on top of its core functionality.

It has lots of public fields, which means any outside script can just change these fields which means outside scripts would also have to know how the BuildManager works internally. For instance “if I change field X I must also call BuildManager.instance.UpdateThisThing()”.

When you find that any of these fields changes unexpectecly, you cannot debug this since you cannot set a breakpoint on a public field. See: encapsulation, hide internals.

It’s also a singleton which means a lot of other code likely depends on it. But there’s only one minor public method, which means the singleton exists solely to be able to read (or modify!) public fields from other scripts.

There’s a lot of repetition, not as bad as some code I’ve seen but still enough to think the repetition in this class only happens to be low by chance. Repetition makes the code unnecessarily hard to read, hard to debug and also inefficient (see: DRY principle). For example here you ought to get the component once and assign it to a variable:

                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;

There’s more but in essence what you ought to do is to refactor the public fields into properties. The IDE will do that for you although I’m not sure how aware VS is these days in regards to adding a [SerializeField] to the fields - Rider does that perfectly.

Then the main task is to separate individual behaviour into separate, ideally highly decoupled classes. Ideas for decoupling include:

  • BuildInputHandler mapping the input to build functionality, invokes input events

  • BuildController orchestrates the build process (registers input events needed for current state so as to not accidentally respond to input that isn’t valid in the current state), including performing actions, updating the Grid, updating the GUI

  • BuildSomethingComponent performs the actual placement or destructing of “something” (I assume there may be different types of buildables which may require customization so you may have BuildResourceThing or BuildWeaponThing or BuildDefensiveThing)

  • GridDrawComponent (just drawing the grid, depends on GridBlock and GridController state)

  • GridBlockComponent (determines which grid positions are blocked/not blocked)

  • MouseCursorDrawComponent (different cursor based on input/build state, looked up in BuildController)

  • BuildGUIComponent is responsible for updating labels, icons and what not (state is looked up in BuildController)

Definetely most important is to separate the input and drawing from the actual game logic.

Outline of responsibilities and direction of dependencies (it is crucial that these are one-way relationships - class A either depends on B or B on A but never both!):

  • Controller responds to Input events
  • Controller reads from GridBlock state (grid updates only when requested)
  • Controller performs build actions
  • GUI reads from Controller
  • GridDraw reads from Controller
  • Cursor reads from Controller
2 Likes

You have three different modes. The code for each mode can be moved to its own script and your BuildManager script can then enable the relevant script for the selected mode.

1 Like

… or partial class files… those always just seem tidier to me, but they do trigger some computer science people really weirdly, I’m not sure why. :slight_smile:

Partial classes in Unity3D:

https://discussions.unity.com/t/842074/2

1 Like