The Unity script needs the Editor and the setup listed below. The separate C# companion practice runs in SharpRune without UnityEngine.
A GameObject groups components with separate responsibilities
A GameObject is a scene object that carries components. Its Transform, renderer, collider and custom MonoBehaviour can have different responsibilities and enabled behavior. ComponentProbe asks for a MeshRenderer requirement and caches a Renderer reference, then reports the object name and concrete component type. This is an explicit local dependency, not a search for every renderer in the scene. Understanding which object owns the component and which component owns a piece of state makes later gameplay code easier to reason about than treating a GameObject as one undifferentiated script.
Inspect the cube before interpreting the log
Use a primitive Cube because it already provides a mesh and renderer, then attach ComponentProbe.cs. Awake obtains the local Renderer, and Start logs a message containing the cube’s name and MeshRenderer type. RequireComponent helps establish the concrete MeshRenderer dependency when adding this script, while GetComponent retrieves the component on this same GameObject. Inspect the hierarchy and Inspector to confirm that the script is on the intended cube. A different object with a similar name is not automatically the same dependency owner.
The portable rule makes dependency ownership explicit
The companion uses a plain DamageRule receiving an IArmor implementation through its constructor. FlatArmor subtracts three from a ten-point hit and clamps at zero, so Apply prints seven. Constructing DamageRule with a null dependency throws ArgumentNullException, which the caller reports as missing armor. This proves a local C# composition rule only. It does not implement GetComponent or pretend a normal class constructor creates a Unity component; instead it demonstrates why a required collaborator should be identified and validated before the workflow relies on it.
Cache for a clear lifetime, not as a substitute for a contract
Caching a local component avoids repeating the same lookup in a hot callback, but the reference still has a lifetime and can become invalid if the associated engine object is destroyed. Inspector-assigned references to other objects need their own validation and ownership policy. Also keep disabling rendering separate from disabling a script: those are different components. Unity callback rules include exceptions, particularly physics messages, so an enabled checkbox should not be treated as a universal ban on every possible method call or message.
Practice: isolate object and component changes
Rename the Cube and verify that the ownership message identifies the new GameObject name without changing the renderer type. Then disable only the Mesh Renderer and observe that the object can remain in the hierarchy while it is no longer rendered. In a separate run, disable the script before its first Start and compare that lifecycle behavior. Do not combine all changes at once; the point is to trace which component controls each observation rather than label every missing visible effect a script failure.
- Use a fresh Play entry when testing a once-only Start callback.
- Inspect the actual component reference instead of choosing a dependency by a coincidental scene name.
Practice: vary the collaborator without rewriting the workflow
For an independent C# change, add an armor implementation that halves a hit according to a clearly stated integer-rounding policy, then pass it into the same DamageRule. Predict results for zero, a small odd hit and ten. Add validation if negative incoming damage is outside the domain. This keeps the gameplay-like calculation separate from engine attachment and shows why a small interface can express variation without giving every rule a global scene lookup or a dependency on a specific MonoBehaviour.
- Test the missing-dependency path separately from the arithmetic rule.
- Do not use null-forgiving syntax as a claim that a runtime reference was validated.
State the dependency and its verification boundary
A useful component explanation identifies the GameObject, the required local component, the acquisition time and the point where it is used. The Unity example is a real attachable class, while the companion tests ordinary dependency injection and arithmetic. Editor integration still needs inspection of the actual scene and lifecycle. As a system grows, prefer explicit serialized references or clearly owned services over repeated broad searches whose result may change with scene composition. The correct approach depends on lifetime and ownership rather than one universally preferred lookup technique.
- The cube’s intended Editor log identifies its name and MeshRenderer.
- The companion prints seven and missing armor through its own plain C# contract.
- Acquire references needed by OnEnable in an earlier valid initialization stage, not by assuming Start already ran.
Unity Editor example
Set up in Unity 6
- Create a Cube in a saved Unity6.0 scene and add ComponentProbe.cs to it.
- Inspect the Transform, Mesh Renderer and script as distinct components on the same GameObject.
- Enter Play mode and inspect the one ownership message.
- Disable only the Mesh Renderer, then only the script, distinguishing rendering from script enabled state.
- For an optional reference experiment, add a serialized Renderer field to a copy and deliberately test an unassigned reference.
ComponentProbe.cs
Download Unity script ↗using UnityEngine;
[RequireComponent(typeof(MeshRenderer))]
public sealed class ComponentProbe : MonoBehaviour
{
private Renderer cachedRenderer;
private void Awake()
{
cachedRenderer = GetComponent<Renderer>();
}
private void Start()
{
Debug.Log(gameObject.name + " owns " + cachedRenderer.GetType().Name, this);
}
}
What to observe in Unity
The cube logs its GameObject name and concrete Renderer component type. Disabling rendering is separate from disabling this script.
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;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
Console.WriteLine(new DamageRule(new FlatArmor(3)).Apply(10));
try { new DamageRule(null!); }
catch (ArgumentNullException) { Console.WriteLine("missing armor"); }
interface IArmor { int Reduce(int hit); }
sealed class FlatArmor : IArmor { private readonly int amount; public FlatArmor(int amount) { this.amount = amount; } public int Reduce(int hit) => Math.Max(0, hit - amount); }
sealed class DamageRule { private readonly IArmor armor; public DamageRule(IArmor armor) { this.armor = armor ?? throw new ArgumentNullException(nameof(armor)); } public int Apply(int hit) => armor.Reduce(hit); }
C# practice output
7 missing armor
A component reference and the GameObject that owns it are different values.