Search code examples
unity-game-engineobjectprefab

3D House Prefab doesn't get transparent red or green whether it overlaps or not


I have a building placement project which doesn't work with 3D house objects. The objects all have rigidbody and box collider components while only the house prefab objects have child objects. My scripts are like this:

BuildingManager script:

public class BuildingManager : MonoBehaviour
{
      private GameObject pendingObject;
      [SerializeField] private Material[] materials;
      public bool canPlace;

void Update()
{
     if (pendingObject != null)
     {
         UpDateMaterials();
          
      pendingObject.transform.position = pos;

         if (Input.GetMouseButtonDown(0) && canPlace)
         {
             PlaceObject();
         }
     }
}

public void PlaceObject()
{
     pendingObject.GetComponent<MeshRenderer>().material = materials[2];
     pendingObject = null;
}

public void UpDateMaterials()
{
     if (canPlace)
     {
         pendingObject.GetComponent<MeshRenderer>().material = materials[0];
     
     }
     if (!canPlace)
     {
         pendingObject.GetComponent<MeshRenderer>().material = materials[1];
     
     }
}
}

CheckPlacement script:

public class CheckPlacement : MonoBehaviour
{
    BuildingManager buildingManager;

    void Start()
    {
        buildingManager = GameObject.Find("BuildingManager").GetComponent<BuildingManager>();
    }

   private void OnTriggerEnter(Collider other)
    {
        if (other.gameObject.CompareTag("Building"))
        {
            Debug.Log("Can't Place Building");
            buildingManager.canPlace = false;
        }
    }

    private void OnTriggerExit(Collider other)
    {
        if (other.gameObject.CompareTag("Building"))
        {
            Debug.Log("Can Place Building");
             buildingManager.canPlace = true;
       }
    }
}

The scripts work fine with Unity's default 3D Objects, like 3D capsule, sphere, box objects. I tried overlapping for example, capsule object with house object that is already on the ground(plane). The capsule object turns transparent red as it collides with house object and it turns transparent green as I drag it to the ground.

Any suggestions are welcome...


Solution

  • Since the house object has multiple child objects, you need to update all of the mesh renderers. To do this, use GetComponentsInChildren to get all of the mesh renderers, and loop through those when changing materials. If the object doesn't change, you can cache the mesh renderers to avoid calling GetComponents multiple times.

    private MeshRenderer[] meshRenderers;
    
    private void Awake()
    {
        meshRenderers = pendingObject.GetComponentsInChildren<MeshRenderer>();
    }
    ...
    
    private void UpdateMaterials()
    {
        if (canPlace)
        {
            UpdateMeshRenderersMaterial(materials[0]);
        }
        else
        {
            UpdateMeshRenderersMaterial(materials[1]);
        }
    }
    private void PlaceObject()
    {
        UpdateMeshRenderersMaterial(materials[2]);
    }
    private void UpdateMeshRenderersMaterial(Material material)
    {
        foreach (var meshRenderer in meshRenderers)
        {
            meshRenderer.material = material;
        }
    }
    

    Instead of updating the materials every frame (even when placement status has not changed!), i would make canPlace private and only allow other components to change it through a function. In this way, you can respond to changes in the objects placement status to avoid unnecessarily changing the materials. I added a force update parameter that defaults to false, which when true will force a material update.

    private bool canPlace;
    
    public void UpdatePlacementStatus(bool canPlace, bool forceUpdate = false)
    {
        if (forceUpdate || this.canPlace != canPlace)
        {
            this.canPlace = canPlace;
    
            UpdateMaterials();
        }
    }
    

    From other component:

    Debug.Log("Can't Place Building");
    buildingManager.UpdatePlacementStatus(false);