Skip to content

Instantly share code, notes, and snippets.

@adammyhre
Last active July 28, 2025 20:56
Show Gist options
  • Save adammyhre/a0f2d1bf0e3e24d6649a1839636d52d0 to your computer and use it in GitHub Desktop.
Save adammyhre/a0f2d1bf0e3e24d6649a1839636d52d0 to your computer and use it in GitHub Desktop.

Revisions

  1. adammyhre revised this gist Mar 3, 2025. 1 changed file with 12 additions and 1 deletion.
    13 changes: 12 additions & 1 deletion WorldSpaceUIDocument.cs
    Original file line number Diff line number Diff line change
    @@ -4,7 +4,7 @@
    using UnityUtils;

    // https://discussions.unity.com/t/uitoolkit-world-space-support-status/887441/22
    // https://gist.github.com/katas94/7b220a591215efc36110860a0b1125eb/raw/f67f251bbbda65a14fb0bb736b82fe485ee22eb8/WorldSpaceUIDocument.cs
    // https://gist.github.com/katas94/7b220a591215efc36110860a0b1125eb
    public class WorldSpaceUIDocument : MonoBehaviour {
    #region Fields

    @@ -145,4 +145,15 @@ static Mesh GetQuadMesh() {

    return quadMesh;
    }

    void DestroyGeneratedAssets () {
    if (uiDocument) Destroy(uiDocument);
    if (renderTexture) Destroy(renderTexture);
    if (panelSettings) Destroy(panelSettings);
    if (material) Destroy(material);
    }

    void OnDestroy () {
    DestroyGeneratedAssets();
    }
    }
  2. adammyhre created this gist Mar 1, 2025.
    44 changes: 44 additions & 0 deletions DamageNumberSpawner.cs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,44 @@
    using PrimeTween;
    using UnityEngine;
    using UnityEngine.Pool;
    using UnityUtils;

    public class DamageNumberSpawner : MonoBehaviour {
    [SerializeField] WorldSpaceUIDocument uiDocumentPrefab;
    [SerializeField] float positionRandomness = 0.2f;

    IObjectPool<WorldSpaceUIDocument> uiDocumentPool;
    const string k_labelName = "DamageLabel";

    void Awake() => uiDocumentPool = new ObjectPool<WorldSpaceUIDocument>(
    Create,
    OnTake,
    OnReturn,
    OnDestroy
    );

    WorldSpaceUIDocument Create() => Instantiate(uiDocumentPrefab, transform, true);
    void OnTake(WorldSpaceUIDocument uiDocument) => uiDocument.gameObject.SetActive(true);
    void OnReturn(WorldSpaceUIDocument uiDocument) => uiDocument.gameObject.SetActive(false);
    void OnDestroy(WorldSpaceUIDocument uiDocument) {
    if (uiDocument == null) return;
    Destroy(uiDocument.gameObject);
    }

    public void SpawnDamageNumber(int amount, Vector3 worldPosition) {
    Vector3 spawnPosition = worldPosition.RandomOffset(positionRandomness);

    WorldSpaceUIDocument instance = uiDocumentPool.Get();
    instance.transform.SetPositionAndRotation(spawnPosition, Quaternion.identity);
    instance.SetLabelText(k_labelName, amount.ToString());

    Vector3 upPosition = instance.transform.position.Add(y: 1f);

    PrimeTweenConfig.warnEndValueEqualsCurrent = false;
    Sequence.Create(cycles: 1, CycleMode.Yoyo)
    .Group(Tween.PositionY(instance.transform, endValue: upPosition.y, duration: 0.3f))
    .Group(Tween.Scale(instance.transform, endValue: 1.5f, duration: 0.3f))
    .Chain(Tween.PositionY(instance.transform, endValue: -upPosition.y, duration: 0.5f))
    .ChainCallback(() => uiDocumentPool.Release(instance));
    }
    }
    22 changes: 22 additions & 0 deletions Enemy.cs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,22 @@
    using ImprovedTimers;
    using UnityEngine;

    public class Enemy : MonoBehaviour {
    [SerializeField] DamageNumberSpawner damageNumberSpawner;
    [SerializeField] int damageAmount = 10;
    [SerializeField] float spawnInterval = 0.2f;

    CountdownTimer timer;

    void Start() {
    timer = new CountdownTimer(spawnInterval);
    timer.OnTimerStop += () => {
    damageAmount = Random.Range(5, 25);
    damageNumberSpawner.SpawnDamageNumber(damageAmount, transform.position);
    timer.Start();
    };
    timer.Start();
    }

    public void TakeDamage(int damage) => damageNumberSpawner.SpawnDamageNumber(damage, transform.position);
    }
    148 changes: 148 additions & 0 deletions WorldSpaceUIDocument.cs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,148 @@
    using UnityEngine;
    using UnityEngine.Rendering;
    using UnityEngine.UIElements;
    using UnityUtils;

    // https://discussions.unity.com/t/uitoolkit-world-space-support-status/887441/22
    // https://gist.github.com/katas94/7b220a591215efc36110860a0b1125eb/raw/f67f251bbbda65a14fb0bb736b82fe485ee22eb8/WorldSpaceUIDocument.cs
    public class WorldSpaceUIDocument : MonoBehaviour {
    #region Fields

    const string k_transparentShader = "Unlit/Transparent";
    const string k_textureShader = "Unlit/Texture";
    const string k_mainTex = "_MainTex";
    static readonly int MainTex = Shader.PropertyToID(k_mainTex);

    [Header("Panel Configuration")]
    [Tooltip("Width of the panel in pixels.")]
    [SerializeField] int panelWidth = 1280;

    [Tooltip("Height of the panel in pixels.")]
    [SerializeField] int panelHeight = 720;

    [Tooltip("Scale of the panel (like zoom in a browser).")]
    [SerializeField] float panelScale = 1.0f;

    [Tooltip("Pixels per world unit. Determines the real-world size of the panel.")]
    [SerializeField] float pixelsPerUnit = 500.0f;

    [Header("Dependencies")]
    [Tooltip("Visual tree asset for this panel.")]
    [SerializeField] VisualTreeAsset visualTreeAsset;

    [Tooltip("PanelSettings prefab instance.")]
    [SerializeField] PanelSettings panelSettingsAsset;

    [Tooltip("RenderTexture prefab instance.")]
    [SerializeField] RenderTexture renderTextureAsset;

    MeshRenderer meshRenderer;
    UIDocument uiDocument;
    PanelSettings panelSettings;
    RenderTexture renderTexture;
    Material material;

    #endregion

    void Awake() {
    InitializeComponents();
    BuildPanel();
    }

    public void SetLabelText(string label, string text) {
    if (uiDocument.rootVisualElement == null) {
    uiDocument.visualTreeAsset = visualTreeAsset;
    }

    // Consider caching the label element for better performance
    uiDocument.rootVisualElement.Q<Label>(label).text = text;
    }

    void InitializeComponents() {
    InitializeMeshRenderer();

    // Optionally add a box collider to the object
    // BoxCollider boxCollider = gameObject.GetOrAdd<BoxCollider>();
    // boxCollider.size = new Vector3(1, 1, 0);

    MeshFilter meshFilter = gameObject.GetOrAdd<MeshFilter>();
    meshFilter.sharedMesh = GetQuadMesh();
    }

    void InitializeMeshRenderer() {
    meshRenderer = gameObject.GetOrAdd<MeshRenderer>();
    meshRenderer.sharedMaterial = null;
    meshRenderer.shadowCastingMode = ShadowCastingMode.Off;
    meshRenderer.receiveShadows = false;
    meshRenderer.motionVectorGenerationMode = MotionVectorGenerationMode.ForceNoMotion;
    meshRenderer.lightProbeUsage = LightProbeUsage.Off;
    meshRenderer.reflectionProbeUsage = ReflectionProbeUsage.Off;
    }

    void BuildPanel() {
    CreateRenderTexture();
    CreatePanelSettings();
    CreateUIDocument();
    CreateMaterial();

    SetMaterialToRenderer();
    SetPanelSize();
    }

    void CreateRenderTexture() {
    RenderTextureDescriptor descriptor = renderTextureAsset.descriptor;
    descriptor.width = panelWidth;
    descriptor.height = panelHeight;
    renderTexture = new RenderTexture(descriptor) {
    name = $"{name} - RenderTexture"
    };
    }

    void CreatePanelSettings() {
    panelSettings = Instantiate(panelSettingsAsset);
    panelSettings.targetTexture = renderTexture;
    panelSettings.clearColor = true;
    panelSettings.scaleMode = PanelScaleMode.ConstantPixelSize;
    panelSettings.scale = panelScale;
    panelSettings.name = $"{name} - PanelSettings";
    }

    void CreateUIDocument() {
    uiDocument = gameObject.GetOrAdd<UIDocument>();
    uiDocument.panelSettings = panelSettings;
    uiDocument.visualTreeAsset = visualTreeAsset;
    }

    void CreateMaterial() {
    string shaderName = panelSettings.colorClearValue.a < 1.0f ? k_transparentShader : k_textureShader;
    material = new Material(Shader.Find(shaderName));
    material.SetTexture(MainTex, renderTexture);
    }

    void SetMaterialToRenderer() {
    if (meshRenderer != null) {
    meshRenderer.sharedMaterial = material;
    }
    }

    void SetPanelSize() {
    if (renderTexture != null && (renderTexture.width != panelWidth || renderTexture.height != panelHeight)) {
    renderTexture.Release();
    renderTexture.width = panelWidth;
    renderTexture.height = panelHeight;
    renderTexture.Create();

    uiDocument?.rootVisualElement?.MarkDirtyRepaint();
    }

    transform.localScale = new Vector3(panelWidth / pixelsPerUnit, panelHeight / pixelsPerUnit, 1.0f);
    }

    static Mesh GetQuadMesh() {
    GameObject tempQuad = GameObject.CreatePrimitive(PrimitiveType.Quad);
    Mesh quadMesh = tempQuad.GetComponent<MeshFilter>().sharedMesh;
    Destroy(tempQuad);

    return quadMesh;
    }
    }