Im not sure how good this is, maybe mormon can give his opinion, but hopefully this is a proper way to do things.
Click for code
using UnityEngine;
public abstract class PlaceableBehaviour : MonoBehaviour
{
public bool isPlaced {get; private set;}
public abstract void OnPreview();
public abstract bool OnPlace();
protected abstract bool CanPlace();
protected abstract void SetPosition(RaycastHit hitInfo);
public void Place()
{
if(OnPlace())
{
isPlaced = true;
}
}
}
using System;
using UnityEngine;
public class Placeable : PlaceableBehaviour
{
public Color canPlaceColor;
public Color cantPlaceColor;
Renderer myRenderer;
Material originalMaterial;
Material temporaryMaterial;
Collider myCollider;
void Awake()
{
myRenderer = gameObject.GetComponent<Renderer>();
originalMaterial = myRenderer.sharedMaterial;
SetupTemporaryMaterial();
myRenderer.material = temporaryMaterial;
myCollider = gameObject.GetComponent<Collider>();
myCollider.enabled = false;
SetPosition(GetPlacementPosition());
}
public override void OnPreview()
{
if(isPlaced) return;
if(CanPlace())
{
temporaryMaterial.color = canPlaceColor;
}else{
temporaryMaterial.color = cantPlaceColor;
}
SetPosition(GetPlacementPosition());
}
protected override bool CanPlace()
{
if(GetPlacementPosition().collider != null) return true;
return false;
}
public override bool OnPlace()
{
RaycastHit hitInfo = GetPlacementPosition();
if(hitInfo.collider != null)
{
SetPosition(hitInfo);
myRenderer.material = originalMaterial;
myCollider.enabled = true;
return true;
}
return false;
}
protected override void SetPosition(RaycastHit hitInfo)
{
if(hitInfo.collider != null)
{
transform.position = hitInfo.point; //can do custom placement logic such as adding offsets...
}
}
RaycastHit GetPlacementPosition()
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hitInfo;
Physics.Raycast(ray, out hitInfo);
return hitInfo;
}
void SetupTemporaryMaterial()
{
//Creates standard shader with transparency
temporaryMaterial = new Material(Shader.Find("Standard"));
temporaryMaterial.SetFloat("_Mode", 3);
temporaryMaterial.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One);
temporaryMaterial.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
temporaryMaterial.SetInt("_ZWrite", 0);
temporaryMaterial.DisableKeyword("_ALPHATEST_ON");
temporaryMaterial.DisableKeyword("_ALPHABLEND_ON");
temporaryMaterial.EnableKeyword("_ALPHAPREMULTIPLY_ON");
temporaryMaterial.renderQueue = 3000;
}
}
using System;
using UnityEngine;
public class Placer : MonoBehaviour
{
public PlaceableBehaviour placeable;
public KeyCode place = KeyCode.Mouse0;
public KeyCode select = KeyCode.Alpha1;
public KeyCode cancel = KeyCode.E;
PlaceableBehaviour currentSpawned;
bool hasSpawned;
void Update()
{
if(!hasSpawned)
{
if(Input.GetKeyDown(select)) Spawn();
}else{
if(Input.GetKeyDown(place)) Place();
if(Input.GetKeyDown(cancel)) Cancel();
ShowPreview();
}
}
void Spawn()
{
currentSpawned = Instantiate(placeable);
hasSpawned = true;
}
void Place()
{
currentSpawned.Place();
hasSpawned = false;
}
void Cancel()
{
Destroy(currentSpawned.gameObject);
hasSpawned = false;
}
void ShowPreview()
{
currentSpawned.OnPreview();
}
}
So, we have a PlaceableBehaviour which will be the base of all your placeable objects, and then we have an example Placeable object and the Placer.
The Placeable is an example of what you can do. You can probably set this component up better so it can handle all sorts of things.
You create a Placeable object prefab and place this Placeable component on it and set the colors up. I can set canPlaceColor to a transparent green and cantPlaceColor to a transparent red in the inspector.
I think I set things up correctly so that we dont create instance materials for each object you place so that batching still works.
You then put on your player or something, the Placer component. Press 1 to select/spawn the item, move mouse around to choose a spot, press left mouse to place or E to cancel.
You would probably want to set it up in a way so you can spawn different types of placeables by using an array of PlaceableBehaviours.
EDIT - I am getting a weird material bug where at certain positions, such as on a wall, the temporary material is doing weird things such as being transparent at certain angles. Only happens when I use the transparent option on the standard shader.
If I update it by making the temporaryMaterial public and setting it to opaque and back to transparent, it then shows properly. Weird.
Edit - I found a way around the bug by instead of copying the originalMaterial, I just create a new transparent material from scratch (got help in doing that from this site Sassybot)