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.

Revisions

  1. adammyhre created this gist Oct 5, 2025.
    93 changes: 93 additions & 0 deletions BulletHoleSpawner.cs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,93 @@
    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);
    }
    }
    }