Configured spawns, geometry and scene queries
Configured spawns and queries run in the existing OpalScript interpreter in both editor Play and the standalone player. They share the world execution, allocation, prefab preflight and retained-memory budgets.
Typed configured spawns
Entity Scene.SpawnConfigured(Asset<blueprint> prefab, Vector2 position,
string initializer);
Entity Scene.SpawnConfigured(Asset<blueprint> prefab, Vector2 position,
string initializer, T payload);position also accepts Point<World>. The initializer argument must be a literal method name on the calling component. The old form requires synchronous void Configure(Entity spawned). The fourth-argument form requires synchronous void Configure(Entity spawned, T payload). The checker validates the exact callback shape and payload type, including ordinary int to float widening. There is no delegate value or explicit SpawnConfigured<T> syntax.
Prefer an explicit immutable record for several values:
record ShotConfig { Vector2 Aim; int Damage; }
class Launcher : Component {
[Property] Asset<blueprint> ShotPrefab;
public void Fire() {
Entity target = Scene.FindNearest<Health>(Transform.WorldPosition, 250);
if (target == null) return;
var config = new ShotConfig(target.WorldPosition, 12);
Scene.SpawnConfigured(ShotPrefab, Transform.WorldPoint, "Configure", config);
}
void Configure(Entity spawned, ShotConfig config) {
var shot = spawned.GetComponent<ShotMover>();
shot.Aim = config.Aim;
shot.Damage = config.Damage;
}
}The project must also declare ShotMover, with public Vector2 Aim and int Damage fields, and attach it to the referenced prefab. Those fields can be ordinary runtime fields; they do not need [Property] to survive into the spawned Start(). Preparation resets the new cohort's session state first, configuration then runs, and native initialization and authored Start hooks follow. The initializer sees the admitted cohort and can look up its components.
The payload expression is evaluated once before entering spawn or attach callbacks. Immutable data retains its original graph; vectors copy by value. Reentrant callbacks changing the caller's fields or launching another spawn cannot replace the first invocation's payload. Temporary payload roots are charged against retained memory and released on success, cancellation or failure.
Supported T values are the existing immutable data types: primitives, Vector2, typed units and coordinates, enums, records, options, maps and asset handles. A single Entity is also supported as an ephemeral capability. Mutable List<T>, component/interface references, tasks, subscriptions and input/UI handles are rejected. Existing bounded, acyclic record rules are unchanged: records cannot contain Entity fields.
For a homing projectile, pass one target directly:
// On the launcher:
Scene.SpawnConfigured(ShotPrefab, Transform.WorldPoint, "ConfigureTarget", target);
void ConfigureTarget(Entity spawned, Entity target) {
spawned.GetComponent<HomingShot>().Target = target;
}
// In the separate HomingShot component:
public Entity Target;
// Start and Update must check Target != null before reading it.This reference keeps the target's world and object identity tokens. Destruction, scene replacement or reuse of the same textual id cannot revive it. Keep homing targets in ordinary runtime fields. Do not encode an entity id into a record and resolve it later as a substitute for retaining the original entity identity. For multiple spawn settings plus an aim target, use a captured world aim point in an immutable record, or use a single target payload with damage/speed authored on the projectile prefab. A compound record containing live entity capabilities is not part of this API.
Spawning requires a preloaded prefab and an active Play owner. Owner task cancellation, component disable/detach/reload, Stop or scene replacement abort activation. An initializer exception or cancellation removes the newly spawned cohort, unregisters its graphs and retires its component resources. Effects already committed to other objects by the initializer remain committed. Configuration is synchronous; its closure cannot be invoked later to reactivate retired work.
Geometry
| API | Result and behavior |
|---|---|
Vector2.Distance(Vector2 a, Vector2 b) | float, Euclidean distance |
Vector2.DistanceSquared(Vector2 a, Vector2 b) | float, squared distance without a square root |
Vector2.Normalize(Vector2 value) | Vector2, unit direction; zero returns zero |
Vector2.MoveTowards(Vector2 current, Vector2 target, float maxDistance) | Vector2, moves by at most the nonnegative distance without overshooting |
entity.WorldPosition | Read-only Vector2, current visual origin in world coordinates |
entity.WorldPoint | Read-only Point<World>, the same origin with a coordinate-space type |
The four static vector helpers are deterministic and allowed in pure functions. They accept Vector2; typed coordinate values keep their existing explicit conversion rules. Nonfinite results fault, including squared-distance overflow. MoveTowards rejects negative distances. Geometry reads account for parent transforms and pivots. Entity.Position remains the existing writable local position; for an unparented projectile, it is also its world position.
Component and spatial queries
| API | Return type |
|---|---|
HasComponent<T>() | bool, on this component's entity |
entity.HasComponent<T>() | bool, false for a null/expired entity |
Scene.FindWith<T>() | List<Entity>, existing whole-scene component query |
Scene.FindInRadius<T>(Vector2 center, float radius) | List<Entity> |
Scene.FindNearest<T>(Vector2 center, float radius) | Entity, or null |
Spatial centers also accept Point<World> and radii also accept Pixels. T must name a native component, project script component, or nominal interface. Presence means an enabled component on a live entity. Interface matching checks the declared contract without calling the interface's methods.
Radius is finite and nonnegative, includes the boundary, and measures distance between world-space visual origins. It does not test collider overlap, line of sight or navigation reachability. A zero radius matches coincident origins. Entities with zero Health HP remain queryable while the entity/component is live; check Health or gameplay state explicitly when looking for attackable targets.
Lists preserve scene order and contain at most 1,024 entities; exceeding this limit faults visibly rather than truncating. Nearest queries retain the first entity in scene order on ties and do not allocate a result list. They can inspect more than 1,024 candidates while staying within the execution budget. The querying entity is included if it matches T and the radius; there is no implicit self exclusion.
Concrete-component queries reuse the world-owned component index. Membership changes invalidate it, and each spatial query reads current transforms so moving objects never leave stale spatial results. Interface queries scan live component attachments under the same work budget. These helpers use bounded linear scans over candidates; they do not add a separate spatial index or an unbounded search.
Snapshots keep their original membership after world changes; individual entity references expire safely. Destroying or disabling a component does not invalidate the containing entity itself, so recheck HasComponent<T>() when needed.
Script Studio supplies completion, hover help, generic query signature help and payload parameter help from the named initializer. Semantic rename follows record and query type uses and updates the initializer's string literal with its method.