Unity6 Game Development · Lesson 33

Resources paths and shared asset lifetime

Load a known TextAsset without confusing an asset key with a disk path.

Unity 6 Editor walkthrough

The Unity script needs the Editor and the setup listed below. The separate C# companion practice runs in SharpRune without UnityEngine.

A Resources key identifies an imported asset, not a general file

Resources.Load resolves assets that Unity has imported and packaged from Resources folders. The key is not an operating-system path and should not include the extension or the Assets/Resources prefix. ResourceTextProbe intentionally loads a small text configuration to expose that lookup boundary without adding an addressable-content system. For a fixed known dependency, a serialized TextAsset reference can be simpler and more inspectable. A string key makes missing assets, conflicting keys and type expectations part of the runtime contract. Choose keyed loading because the application needs that indirection, not because it looks like a universal way to read arbitrary files.

Keep the lookup typed and the missing result visible

OnEnable rejects a blank key and requests Resources.Load<TextAsset>, so a mismatched asset type is not silently treated as useful text. A null result becomes an explicit diagnostic identifying the requested key. A successful result is retained in loaded and its text is logged for this activation. OnDisable clears the local reference only. That is a deliberate borrowed-asset policy: this component has no proof that it is the sole consumer entitled to invalidate the underlying Unity asset. Re-enabling acquires the asset again through the same contract; it does not demonstrate a disk read or guarantee a fresh native instance on every activation.

    private void OnEnable()
    {
        if (string.IsNullOrWhiteSpace(resourceKey))
        { Debug.LogError("Assign the extension-free Resources key.", this); return; }
        loaded = Resources.Load<TextAsset>(resourceKey);
        if (loaded == null)
        { Debug.LogError("TextAsset missing or wrong type at key: " + resourceKey, this); return; }
        Debug.Log("Resource text: " + loaded.text, this);
    }
    private void OnDisable()
    {
        // This component borrows a potentially shared asset; it does not unload it globally.
        loaded = null;
    }
}

Translate one tutorial layout without claiming the full importer rule

The companion accepts exactly the tutorial prefix Assets/Resources and a lowercase .txt suffix, strips them and returns SharpRune/Message. The unrelated Assets/Data path is rejected. It also rejects empty or dot-navigation segments under its own stricter convention. That helper is not a complete validator for every legitimate Unity Resources layout: Unity can have multiple Resources folders, and its documented lookup is case-insensitive. The narrower lab is useful because it establishes this recipe’s mapping with no filesystem or asset database. Its two outputs cannot prove that Message.txt was imported as a TextAsset, included in a Player or loaded into native memory.

Clearing a reference, collecting managed memory and unloading an asset differ

Setting loaded to null ends this component’s direct managed reference. It does not synchronously unload the asset or establish that every other consumer released it. Resources.UnloadAsset can invalidate a loaded asset for existing references, so blindly calling it whenever one consumer disables can break another consumer’s ownership. Resources.UnloadUnusedAssets has a broader engine-owned reachability process and is not a cheap per-frame cleanup operation. A managed garbage collection is yet another mechanism. Addressables would require its own package, acquisition handles and matching release rules; mixing that vocabulary into this Resources example would conceal the very lifetime boundary being taught.

Practice: share the asset, then test a missing key

Create two active consumers of the same Message TextAsset key and verify that both log the intended content. Disable one consumer and confirm that its code does not globally unload the asset out from under the other. In a fresh copied setup, replace the key with a nonexistent value and inspect the explicit error, then restore it. Next compare the Inspector clarity of a serialized TextAsset field for the same fixed content. The exercise is about dependency and ownership choices, not a claimed memory benchmark. To investigate actual native memory later, capture it in the intended Player with a stated measurement tool and loading policy.

  • Hint: the key is SharpRune/Message, with forward slashes and no extension.
  • Hint: a consumer shutting down is not proof that a shared asset has no other users.

Practice: make key conversion an explicit format contract

Add test inputs with an empty filename, a dot-navigation segment, the wrong extension and a nested SharpRune folder. Write down which ones this tutorial’s converter accepts before changing it. If the feature should support several content types, return a named key-and-type description rather than removing every extension indiscriminately. Keep that model separate from Unity’s actual asset lookup and state that case policy explicitly. A string conversion test can catch a malformed generated key, but only an imported project and packaged build can establish that the referenced asset exists and that the requested type matches its import result.

  • Hint: do not expand a narrow helper into a claimed Unity importer validator without new evidence.
  • Hint: test generated keys and packaged asset availability as different checks.

Record acquisition and release authority separately

The final handoff should identify the exact imported asset, Resources key, requested type and authority that owns unloading decisions. This example owns only its local retained reference, which explains why its shutdown code is intentionally small. The source and versioned Unity6000.0 API rules were reviewed, but no Editor or Player was available to execute the real load. Desktop execution established the separate string-conversion output only. Preserve that evidence boundary when replacing Resources with direct references, asset bundles or Addressables: each path changes packaging, acquisition and release contracts rather than merely changing one method name.

Runs in Unity 6 Editor

Unity Editor example

Set up in Unity 6

  1. Create Assets/Resources/SharpRune/Message.txt containing Hello from a packaged resource. Let Unity import it as a TextAsset.
  2. Create an active empty object with ResourceTextProbe.cs. Keep resourceKey exactly SharpRune/Message, without Assets, Resources or .txt in the key.
  3. Ensure that no other Resources folder provides a conflicting asset with the same key. Enter Play mode and inspect the logged text.
  4. Disable and re-enable the component to repeat acquisition. Its OnDisable only releases its local reference; do not infer an immediate native-memory reduction.
  5. Test a nonexistent key in a copy and inspect the explicit error. Do not add Resources.UnloadAsset to shared consumer shutdown without reviewing every other consumer and the asset ownership contract.

ResourceTextProbe.cs

Download Unity script ↗
using UnityEngine;
public sealed class ResourceTextProbe : MonoBehaviour
{
    [SerializeField] private string resourceKey = "SharpRune/Message";
    private TextAsset loaded;
    private void OnEnable()
    {
        if (string.IsNullOrWhiteSpace(resourceKey))
        { Debug.LogError("Assign the extension-free Resources key.", this); return; }
        loaded = Resources.Load<TextAsset>(resourceKey);
        if (loaded == null)
        { Debug.LogError("TextAsset missing or wrong type at key: " + resourceKey, this); return; }
        Debug.Log("Resource text: " + loaded.text, this);
    }
    private void OnDisable()
    {
        // This component borrows a potentially shared asset; it does not unload it globally.
        loaded = null;
    }
}

What to observe in Unity

The imported TextAsset is found through its extension-free key and its contents are logged. Missing or mismatched assets produce a clear error; disabling the component does not globally unload the shared asset.

The console practice below isolates the C# logic. It does not run a Unity scene or emulate Unity engine callbacks.

C# companion practice

using System;
using System.Linq;
using System.Collections.Generic;
using System.Globalization;

Console.WriteLine(TutorialKey("Assets/Resources/SharpRune/Message.txt") ?? "rejected");
Console.WriteLine(TutorialKey("Assets/Data/Message.txt") ?? "rejected");
string? TutorialKey(string path)
{
    const string prefix = "Assets/Resources/";
    if (!path.StartsWith(prefix, StringComparison.Ordinal) || !path.EndsWith(".txt", StringComparison.Ordinal)) return null;
    var key = path.Substring(prefix.Length, path.Length - prefix.Length - 4);
    if (string.IsNullOrWhiteSpace(key) || key.Split('/').Any(part => part is "" or "." or "..")) return null;
    return key;
}
Open C# practice

C# practice output

SharpRune/Message
rejected
Try this
Clearing one reference is not a safe global asset-unloading policy.

Further reading