Macro forms
query!(ctx, expr) -> Vec<NodeID>query!(ctx, expr, in_subtree(parent_id)) -> Vec<NodeID>query_first!(ctx, expr) -> Option<NodeID>query_first!(ctx, expr, in_subtree(parent_id)) -> Option<NodeID>Docs
Query is a runtime filter that returns NodeID values for nodes matching boolean expressions and name, tag, type, or base type predicates. Start with direct lookup (name/tag/type), then compose filters with gates and subtree scope. Without in_subtree, the full tree is queried.
Query itself does not cause borrow pain. Main rule stays same: collect values, then run one runtime mutation closure at a time.
query!(ctx, expr) -> Vec<NodeID>query!(ctx, expr, in_subtree(parent_id)) -> Vec<NodeID>query_first!(ctx, expr) -> Option<NodeID>query_first!(ctx, expr, in_subtree(parent_id)) -> Option<NodeID>let boss = query_first!(ctx, all(name["Boss"]));let enemies = query!(ctx, all(tags["enemy"]));let cameras = query!(ctx, all(is[Camera3D]));Query predicates compose into high-signal runtime selectors. This is practical ECS-style targeting with node types as structure and tags as runtime grouping data.
let arena_targets = query!( ctx, all( any(tags["enemy"], tags["elite"]), base_type[Node3D], not(tags["dead"]) ), in_subtree(arena_root)); for id in arena_targets { let _ = with_base_node_mut!(ctx, Node3D, id, |node| { node.position.y += 0.1; });}Tags can be authored in scenes or changed at runtime with tag_set!, tag_add!, and tag_remove!.
Typical pattern: query IDs first, compute helper values, then mutate each target in short closures.
// 1) find idslet ids = query!(ctx, all(tags["enemy"], base_type[Node3D])); // 2) optional: collect runtime values outside mutation closureslet dt = delta_time!(ctx); // 3) mutate in isolated closuresfor id in ids { let _ = with_base_node_mut!(ctx, Node3D, id, |node| { node.position.y += 0.1 * dt; });}name[...]Match one or more scene node names.
tags[...]Match nodes carrying runtime tags.
is[...] / is_type[...]Match exact concrete node types.
base[...] / base_type[...]Match nodes that inherit from a base type.
all(...)Every nested condition must match.
any(...)At least one nested condition must match.
not(...)Inner condition must not match.
in_subtree(parent_id)Limit query to descendants under one parent.
is[] and is_type[] match concrete node types. base[] and base_type[] use inheritance checks and match descendants of a base type.
When a query returns mixed Node3D descendants, use with_base_node! or with_base_node_mut! to access shared base fields safely.