Skip to content

Instantly share code, notes, and snippets.

@adammyhre
Created October 5, 2025 12:54
Show Gist options
  • Save adammyhre/a232f6da67222d98ae8c72d0143a50e0 to your computer and use it in GitHub Desktop.
Save adammyhre/a232f6da67222d98ae8c72d0143a50e0 to your computer and use it in GitHub Desktop.
Decal Pooling in Unity
using System.Collections;
using UnityEngine;
using UnityEngine.Pool;
using UnityEngine.Rendering.Universal;
public class BulletHoleSpawner : MonoBehaviour {
#region Fields
IObjectPool<DecalProjector> decalPool;
[Tooltip("Material to use for the bullet hole decal.")]
public Material decalMaterial;
[Tooltip("Layers that can receive bullet hole decals.")]
public LayerMask decalLayers = -1;
[Tooltip("Size of each decal in world units (x = width, y = height, z = depth).")]
public Vector3 decalSize = new Vector3(0.5f, 0.5f, 0.5f);
[Tooltip("Duration in seconds for the decal to fade out before being returned to the pool.")]
public float fadeDuration = 5f;
Camera cam;
#endregion
void Start() {
cam = Camera.main;
decalPool = new ObjectPool<DecalProjector>(
createFunc: () => {
GameObject go = new GameObject("DecalProjector");
DecalProjector dp = go.AddComponent<DecalProjector>();
go.transform.parent = this.transform;
dp.material = decalMaterial;
dp.fadeFactor = 1f;
dp.fadeScale = 0.95f;
dp.startAngleFade = 0f;
dp.endAngleFade = 30f;
return dp;
},
actionOnGet: dp => dp.gameObject.SetActive(true),
actionOnRelease: dp => dp.gameObject.SetActive(false),
actionOnDestroy: dp => Destroy(dp.gameObject),
collectionCheck: false,
defaultCapacity: 10,
maxSize: 20
);
}
void SpawnDecal(RaycastHit hit) {
DecalProjector projector = decalPool.Get();
projector.transform.position = hit.point + hit.normal * 0.01f;
Quaternion normalRotation = Quaternion.LookRotation(-hit.normal, Vector3.up);
Quaternion randomRotation = Quaternion.Euler(0, 0, Random.Range(0f, 360f));
projector.transform.rotation = normalRotation * randomRotation;
projector.size = decalSize;
StartCoroutine(FadeAndRelease(projector, fadeDuration));
}
void Update() {
if (Input.GetMouseButtonDown(0)) {
Ray ray = cam.ScreenPointToRay(Input.mousePosition);
if (Physics.SphereCast(ray, decalSize.x * 0.3f, out RaycastHit hitInfo, Mathf.Infinity, decalLayers)) {
SpawnDecal(hitInfo);
}
}
}
IEnumerator FadeAndRelease(DecalProjector projector, float duration) {
float time = 0f;
float initialFade = projector.fadeFactor;
while (time < duration) {
if (projector == null) yield break;
time += Time.deltaTime;
float t = time / duration;
projector.fadeFactor = Mathf.Lerp(initialFade, 0f, t);
yield return null;
}
if (projector != null) {
projector.fadeFactor = initialFade;
decalPool.Release(projector);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment