The Unity script needs the Editor and the setup listed below. The separate C# companion practice runs in SharpRune without UnityEngine.
Separate visibility, eligibility and the gameplay decision
The first question is what the interaction is allowed to observe. A physics ray has an origin and direction, a maximum distance, a layer filter and a trigger rule. The nearest eligible collider is not automatically an interactable object: it might be a wall that correctly blocks the view. Decide whether a query is selecting surfaces, testing obstruction, or finding a particular gameplay role. Those purposes can require different masks and result handling. RayQueryProbe keeps this first exercise to a single observable query, so a miss, a filtered object and a nearer blocker can be investigated independently before another gameplay rule is added.
Read the real ray construction and overload
Probe first refuses an edit-mode invocation or an inactive controller, then rejects an absent camera and an invalid range. ViewportPointToRay uses normalized viewport coordinates, so the centre is one half on both axes. The resulting ray begins on the camera’s near plane; hit.distance is measured from that ray origin, not necessarily from the camera Transform. The selected Raycast overload supplies out hit, range, queryLayers and Ignore explicitly. Read that final argument as part of this component’s contract. Debug.Log uses the hit collider as context, letting the learner select the actual reported object instead of guessing from a printed name.
Ray ray = viewCamera.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0f));
if (Physics.Raycast(ray, out RaycastHit hit, range, queryLayers, QueryTriggerInteraction.Ignore))
Debug.Log("Hit " + hit.collider.name + " at " + hit.distance, hit.collider);
else Debug.Log("No eligible hit in range", this);Trace supplied observations without claiming a geometric query
The companion begins with three already supplied descriptions. The sensor is at distance one, the crate at two and the far object at nine. With a five-unit limit and trigger inclusion disabled, the sensor is rejected by policy and the far entry by range, leaving crate. Turning trigger inclusion on makes sensor the nearest allowed description. The lab never calculates an intersection or discovers whether anything lies behind a wall. Its additional layer bounds guard prevents an invalid supplied layer index from exploiting C# shift-count behavior. Treat these outputs as two selection-rule checks, then verify actual geometry through the separate Editor script.
Diagnose a miss before changing the code
Start with one static collider that is visibly centred in the camera and wait for a settled physics frame. Check that the component references the camera actually used for the experiment, that the collider is enabled and that its GameObject layer is included. A trigger is deliberately invisible to this query even when its layer matches. An origin inside a collider has a documented detection limitation; enlarging the range does not fix that configuration. If another script moves colliders through Transform, inspect when the physics world is synchronized. Do not add SyncTransforms to every query as an unexplained universal repair for an unclear update policy.
Practice: make a blocker an intentional part of selection
Create a short three-object scene: a trigger sensor near the camera, a solid wall farther away and a target cube behind the wall. Predict the result with the wall layer included, then exclude only that wall layer and repeat the context-menu query. Record which change altered eligibility and which changed geometry. For a real use action, extend the result handling so the nearest collider must carry the intended role; a wall should reject interaction rather than disappear from the query. This experiment is independent of changing the portable list because only the Editor can establish that the camera ray intersects those configured collider shapes.
- Hint: keep the trigger rule fixed while changing only the wall layer.
- Hint: use the log context to identify the reported collider, including an unexpectedly nearer one.
Practice: specify selection boundaries with supplied data
In the portable lab, add descriptions exactly at zero and exactly at the maximum distance, then add an invalid negative distance and a layer outside zero through thirty-one. State whether each belongs to the rule before running it. Next introduce two equally distant allowed descriptions and decide whether stable input order is an acceptable tie policy or whether a secondary identity ordering is required. Keep the result’s meaning narrow: it chooses among supplied candidates. Even a complete set of these checks cannot establish a Unity ray origin, collider synchronization, contact offset or scene layer configuration that the program never received.
- Hint: the current range comparisons include both boundaries.
- Hint: make a tie policy explicit if callers require deterministic output independent of source order.
Retain a useful query receipt
A helpful Editor observation records the camera, ray origin, intended layer mask, trigger policy, range and reported collider. That receipt is stronger than merely stating that a method ran, because it preserves the assumptions needed to reproduce the result. For this lesson the documentation baseline is Unity6000.0; no Unity Editor or Player was installed during authoring, so the supplied engine source was syntax-checked and API-reviewed rather than run in a scene. The separate console practice was executed as ordinary C#. Keep those verification scopes visible when applying the query to selection, line-of-sight or the keyed station later in the course.
Unity Editor example
Set up in Unity 6
- Create a 3D scene with a Camera at (0, 0, -4) facing positive Z and a Cube at the origin with its BoxCollider enabled. Keep the camera ray origin outside every tested collider.
- Attach RayQueryProbe.cs to an active empty object, assign the Camera, and set range to 5. Create a named QueryWorld layer, assign the cube to it, and include that layer in queryLayers.
- Enter Play mode and run Probe Centre Ray from the component context menu. The cube is approximately 3.5 units from this camera position along the centre ray.
- Put a trigger box between camera and cube on an included layer; it is deliberately ignored. Put a nontrigger wall on an included layer in front of the cube to make the wall become the nearest hit.
- Use a settled static setup for the baseline. When moving colliders through Transform writes, verify the project’s physics synchronization/timestep policy rather than treating this context menu as synchronization code.
RayQueryProbe.cs
Download Unity script ↗using UnityEngine;
public sealed class RayQueryProbe : MonoBehaviour
{
[SerializeField] private Camera viewCamera;
[SerializeField] private LayerMask queryLayers = ~0;
[SerializeField, Min(0.01f)] private float range = 5f;
[ContextMenu("Probe Centre Ray (Play Mode)")]
public void Probe()
{
if (!Application.isPlaying || !isActiveAndEnabled) return;
if (viewCamera == null || range <= 0f || float.IsNaN(range) || float.IsInfinity(range))
{ Debug.LogError("Assign a camera and a finite positive range.", this); return; }
Ray ray = viewCamera.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0f));
if (Physics.Raycast(ray, out RaycastHit hit, range, queryLayers, QueryTriggerInteraction.Ignore))
Debug.Log("Hit " + hit.collider.name + " at " + hit.distance, hit.collider);
else Debug.Log("No eligible hit in range", this);
}
}
What to observe in Unity
The settled scene reports the nearest eligible nontrigger collider within five units, or an explicit miss. Changing the mask or range changes eligibility without modifying the global physics settings.
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;
var supplied = new[] { new Hit("sensor", 8, true, 1m), new Hit("crate", 8, false, 2m), new Hit("far", 8, false, 9m) };
Console.WriteLine(Choose(supplied, 1 << 8, false, 5m));
Console.WriteLine(Choose(supplied, 1 << 8, true, 5m));
string Choose(IEnumerable<Hit> hits, int mask, bool includeTriggers, decimal range) => hits.Where(x => x.Layer >= 0 && x.Layer < 32 && (mask & (1 << x.Layer)) != 0 && (includeTriggers || !x.Trigger) && x.Distance >= 0 && x.Distance <= range).OrderBy(x => x.Distance).Select(x => x.Name).FirstOrDefault() ?? "none";
record Hit(string Name, int Layer, bool Trigger, decimal Distance);
C# practice output
crate sensor
A query mask filters colliders; it does not make excluded walls block the ray.