using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
[AddComponentMenu("Camera/Camera Follow")]
public class CameraFollow : MonoBehaviour
{
public Transform target;
public Vector3 offset = new Vector3(0f, 8f, -8f);
public float smoothTime = 0.12f;
public bool keepFixedRotation = true;
public Vector3 fixedEulerRotation = new Vector3(30f, 0f, 0f);
Vector3 velocity;
[Header("Occluder Settings")]
public LayerMask occluderLayers = ~0;
[Range(0f, 1f)]
public float occluderAlpha = 0.25f;
public bool debugOccluders = false;
public Material fallbackTransparentMaterial;
public bool forceFallback = false;
private Dictionary<Renderer, Material[]> originalMaterials = new Dictionary<Renderer, Material[]>();
private Dictionary<Renderer, ShadowCastingMode> originalShadowCasting = new Dictionary<Renderer, ShadowCastingMode>();
private Dictionary<Renderer, bool> originalReceiveShadows = new Dictionary<Renderer, bool>();
private List<Renderer> currentlyOccluding = new List<Renderer>();
private Material runtimeFallback;
void LateUpdate()
{
if (target == null) return;
Vector3 desiredPos = target.position + offset;
transform.position = Vector3.SmoothDamp(transform.position, desiredPos, ref velocity, smoothTime);
if (keepFixedRotation)
{
transform.rotation = Quaternion.Euler(fixedEulerRotation);
}
UpdateOccluders();
}
private void UpdateOccluders()
{
currentlyOccluding.Clear();
Vector3 camPos = transform.position;
Vector3 targetPos = target.position;
Vector3 dir = (targetPos - camPos);
float dist = dir.magnitude;
if (dist <= 0.001f) return;
dir /= dist;
RaycastHit[] hits = Physics.RaycastAll(camPos, dir, dist, occluderLayers);
foreach (var hit in hits)
{
if (hit.collider == null) continue;
if (hit.collider.transform.IsChildOf(target)) continue;
Renderer r = hit.collider.GetComponent<Renderer>();
if (r == null) r = hit.collider.GetComponentInParent<Renderer>();
if (r == null) r = hit.collider.GetComponentInChildren<Renderer>();
if (r == null) continue;
if (debugOccluders) Debug.Log($"Occluder hit: {r.gameObject.name} at distance {hit.distance}");
if (r.transform.IsChildOf(transform)) continue;
if (!currentlyOccluding.Contains(r)) currentlyOccluding.Add(r);
}
var keys = new List<Renderer>(originalMaterials.Keys);
foreach (var r in keys)
{
if (!currentlyOccluding.Contains(r))
{
RestoreRendererMaterials(r);
}
}
foreach (var r in currentlyOccluding)
{
if (!originalMaterials.ContainsKey(r)) MakeRendererTransparent(r, occluderAlpha);
}
}
private void MakeRendererTransparent(Renderer r, float alpha)
{
if (r == null) return;
try
{
Material[] shared = r.sharedMaterials;
if (shared == null || shared.Length == 0) return;
originalMaterials[r] = shared;
originalShadowCasting[r] = r.shadowCastingMode;
originalReceiveShadows[r] = r.receiveShadows;
Material[] mats = r.materials;
for (int i = 0; i < mats.Length; i++)
{
var m = mats[i];
if (m == null)
{
mats[i] = null;
continue;
}
var src = shared.Length > i ? shared[i] : null;
bool setAlpha = false;
string[] colorProps = new string[] { "_BaseColor", "_Color", "_TintColor", "_MainColor" };
foreach (var prop in colorProps)
{
if (m.HasProperty(prop))
{
try
{
Color c = m.GetColor(prop);
c.a = alpha;
m.SetColor(prop, c);
setAlpha = true;
break;
}
catch { }
}
}
if (m.HasProperty("_Surface"))
{
try
{
m.SetFloat("_Surface", 1f);
if (m.HasProperty("_ZWrite")) m.SetFloat("_ZWrite", 0f);
m.renderQueue = 3000;
setAlpha = true;
}
catch { }
}
if (!setAlpha && m.HasProperty("_Mode"))
{
m.SetFloat("_Mode", 3);
m.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
m.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
m.SetInt("_ZWrite", 0);
m.DisableKeyword("_ALPHATEST_ON");
m.EnableKeyword("_ALPHABLEND_ON");
m.DisableKeyword("_ALPHAPREMULTIPLY_ON");
m.renderQueue = 3000;
setAlpha = true;
}
if (!setAlpha && m.HasProperty("_Color"))
{
try
{
Color c = m.GetColor("_Color");
c.a = alpha;
m.SetColor("_Color", c);
}
catch { }
}
bool alphaApplied = false;
string[] checkProps = new string[] { "_BaseColor", "_Color", "_TintColor", "_MainColor", "_Surface", "_Mode" };
foreach (var p in checkProps)
{
if (m.HasProperty(p))
{
alphaApplied = true;
break;
}
}
if (!alphaApplied || forceFallback)
{
Material fallback = GetOrCreateFallbackTransparent(src != null ? src : m, alpha);
mats[i] = fallback;
}
else
{
ApplyTransparentSettings(m, alpha);
mats[i] = m;
}
}
r.materials = mats;
try
{
var mpb = new MaterialPropertyBlock();
Material mat0 = mats.Length > 0 ? mats[0] : null;
if (mat0 != null)
{
if (mat0.HasProperty("_BaseColor"))
{
Color c = mat0.GetColor("_BaseColor"); c.a = alpha; mpb.SetColor("_BaseColor", c);
}
else if (mat0.HasProperty("_Color"))
{
Color c = mat0.GetColor("_Color"); c.a = alpha; mpb.SetColor("_Color", c);
}
if (mat0.HasProperty("_Surface")) mpb.SetFloat("_Surface", 1f);
if (mat0.HasProperty("_Mode")) mpb.SetFloat("_Mode", 3f);
}
r.SetPropertyBlock(mpb);
}
catch { }
try
{
r.shadowCastingMode = ShadowCastingMode.Off;
r.receiveShadows = false;
}
catch { }
}
catch (System.Exception)
{
}
}
private void RestoreRendererMaterials(Renderer r)
{
if (r == null) return;
if (!originalMaterials.ContainsKey(r)) return;
try
{
r.sharedMaterials = originalMaterials[r];
}
catch (System.Exception)
{
}
try
{
if (originalShadowCasting.ContainsKey(r)) r.shadowCastingMode = originalShadowCasting[r];
if (originalReceiveShadows.ContainsKey(r)) r.receiveShadows = originalReceiveShadows[r];
}
catch { }
try
{
r.SetPropertyBlock(null);
}
catch { }
originalMaterials.Remove(r);
if (originalShadowCasting.ContainsKey(r)) originalShadowCasting.Remove(r);
if (originalReceiveShadows.ContainsKey(r)) originalReceiveShadows.Remove(r);
}
private void OnDisable()
{
var keys = new List<Renderer>(originalMaterials.Keys);
foreach (var r in keys)
RestoreRendererMaterials(r);
if (runtimeFallback != null)
{
DestroyImmediate(runtimeFallback);
runtimeFallback = null;
}
}
private Material GetOrCreateFallbackTransparent(Material src, float alpha)
{
if (fallbackTransparentMaterial != null)
{
Material m = new Material(fallbackTransparentMaterial);
try
{
if (src.HasProperty("_MainTex") && m.HasProperty("_MainTex"))
{
Texture t = src.GetTexture("_MainTex");
Texture mod = TryCreateAlphaModifiedTexture(t, alpha);
m.SetTexture("_MainTex", mod != null ? mod : t);
}
if (src.HasProperty("_Color") && m.HasProperty("_Color"))
{
Color c = src.GetColor("_Color");
c.a = alpha;
m.SetColor("_Color", c);
}
if (src.HasProperty("_BaseMap") && m.HasProperty("_BaseMap"))
{
Texture t = src.GetTexture("_BaseMap");
Texture mod = TryCreateAlphaModifiedTexture(t, alpha);
m.SetTexture("_BaseMap", mod != null ? mod : t);
}
if (src.HasProperty("_BaseColor") && m.HasProperty("_BaseColor"))
{
Color bc = src.GetColor("_BaseColor");
bc.a = alpha;
m.SetColor("_BaseColor", bc);
}
}
catch { }
return m;
}
if (runtimeFallback == null)
{
Shader shader = Shader.Find("Universal Render Pipeline/Unlit");
if (shader == null) shader = Shader.Find("Universal Render Pipeline/Lit");
if (shader == null) shader = Shader.Find("Standard");
runtimeFallback = new Material(shader != null ? shader : Shader.Find("Standard"));
if (runtimeFallback.HasProperty("_Surface")) runtimeFallback.SetFloat("_Surface", 1f);
if (runtimeFallback.HasProperty("_Mode")) runtimeFallback.SetFloat("_Mode", 3f);
runtimeFallback.renderQueue = 3000;
}
Material inst = new Material(runtimeFallback);
try
{
if (src.HasProperty("_MainTex") && inst.HasProperty("_MainTex"))
{
Texture t = src.GetTexture("_MainTex");
Texture mod = TryCreateAlphaModifiedTexture(t, alpha);
inst.SetTexture("_MainTex", mod != null ? mod : t);
}
if (src.HasProperty("_Color") && inst.HasProperty("_Color"))
{
Color c = src.GetColor("_Color");
c.a = alpha;
inst.SetColor("_Color", c);
}
if (src.HasProperty("_BaseMap") && inst.HasProperty("_BaseMap"))
{
Texture t = src.GetTexture("_BaseMap");
Texture mod = TryCreateAlphaModifiedTexture(t, alpha);
inst.SetTexture("_BaseMap", mod != null ? mod : t);
}
if (src.HasProperty("_BaseColor") && inst.HasProperty("_BaseColor"))
{
Color bc = src.GetColor("_BaseColor");
bc.a = alpha;
inst.SetColor("_BaseColor", bc);
}
}
catch { }
return inst;
}
private void ApplyTransparentSettings(Material m, float alpha)
{
if (m == null) return;
try
{
m.SetOverrideTag("RenderType", "Transparent");
if (m.HasProperty("_Mode"))
{
m.SetFloat("_Mode", 3f);
m.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
m.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
if (m.HasProperty("_ZWrite")) m.SetFloat("_ZWrite", 0f);
m.DisableKeyword("_ALPHATEST_ON");
m.EnableKeyword("_ALPHABLEND_ON");
m.DisableKeyword("_ALPHAPREMULTIPLY_ON");
m.renderQueue = 3000;
}
if (m.HasProperty("_Surface"))
{
m.SetFloat("_Surface", 1f);
if (m.HasProperty("_ZWrite")) m.SetFloat("_ZWrite", 0f);
m.renderQueue = 3000;
}
string[] colorProps = new string[] { "_BaseColor", "_Color", "_TintColor", "_MainColor" };
foreach (var prop in colorProps)
{
if (m.HasProperty(prop))
{
try
{
Color c = m.GetColor(prop);
c.a = alpha;
m.SetColor(prop, c);
}
catch { }
}
}
}
catch { }
}
private Texture TryCreateAlphaModifiedTexture(Texture tex, float alpha)
{
if (tex == null) return null;
Texture2D src = tex as Texture2D;
if (src == null) return null;
try
{
if (!src.isReadable) return null;
Color32[] px = src.GetPixels32();
for (int i = 0; i < px.Length; i++)
{
px[i].a = (byte)(px[i].a * Mathf.Clamp01(alpha));
}
Texture2D t = new Texture2D(src.width, src.height, src.format, src.mipmapCount > 1);
t.SetPixels32(px);
t.Apply();
t.wrapMode = src.wrapMode;
t.filterMode = src.filterMode;
t.name = src.name + "_alpha";
return t;
}
catch { return null; }
}
}