World Objects
Anomaly.Client.Api.Features.WorldObjects is a local-only manipulation API for arbitrary GameObjects in the scene — typically non-networked map decorations, lights, props, particles, decals, ambient objects. Setting a transform here only affects the calling client; for multi-client synchronised motion you’ll need a server companion broadcasting custom messages.
The API exists because mods routinely want to reposition or rotate scene-placed decoration (move that bench out of the way, rotate banners on round events, recolour ambient lights), and writing transforms by hand creates the same five footguns every time. WorldObjects caches the original local pose on first override so ResetTransform always restores it, and exposes a small set of find helpers so the lookup-then-write pattern doesn’t pollute every consumer.
API surface
Section titled “API surface”using Anomaly.Client.Api.Features;using UnityEngine;
// World-space settersWorldObjects.SetPosition(myObj, new Vector3(0, 1, 0));WorldObjects.SetRotation(myObj, Quaternion.Euler(0, 45, 0));WorldObjects.SetScale(myObj, 1.5f); // uniformWorldObjects.SetScale(myObj, new Vector3(1, 2, 1)); // per-axisWorldObjects.SetTransform(myObj, pos, rot, scale); // atomic three-component set
// Local-space setters (relative to parent transform)WorldObjects.SetLocalPosition(myObj, Vector3.zero);WorldObjects.SetLocalRotation(myObj, Quaternion.identity);
// Relative motionWorldObjects.Translate(myObj, Vector3.up * 0.1f);WorldObjects.Rotate(myObj, Vector3.up, 30f); // axis + angleWorldObjects.LookAt(myObj, target.transform.position);
// Reset to the original (level-designer-placed) poseWorldObjects.ResetTransform(myObj);bool isModded = WorldObjects.IsOverridden(myObj);public static bool SetPosition(GameObject obj, Vector3 worldPosition);public static bool SetRotation(GameObject obj, Quaternion worldRotation);public static bool SetScale(GameObject obj, Vector3 scale);public static bool SetScale(GameObject obj, float uniformScale);public static bool SetTransform(GameObject obj, Vector3 worldPosition, Quaternion worldRotation, Vector3 scale);public static bool SetLocalPosition(GameObject obj, Vector3 localPosition);public static bool SetLocalRotation(GameObject obj, Quaternion localRotation);
public static bool Translate(GameObject obj, Vector3 delta);public static bool Rotate(GameObject obj, Quaternion delta);public static bool Rotate(GameObject obj, Vector3 axis, float angleDegrees);public static bool LookAt(GameObject obj, Vector3 target, Vector3? worldUp = null);
public static bool ResetTransform(GameObject obj);public static bool IsOverridden(GameObject obj);All setters return false if the object is null or destroyed. Scale is always local — Unity has no world scale.
Find helpers
Section titled “Find helpers”// Walk the scene hierarchy by `/`-separated path. Handles inactive objects.var bench = WorldObjects.FindByPath("Lobby/Decorations/Bench (4)");
// Wraps GameObject.Find — name-only, active objects.var hint = WorldObjects.FindByName("InteractionHint");
// Tag and component searches.var spawnPoints = WorldObjects.FindAllByTag("PlayerSpawn");var spotlights = WorldObjects.FindAllByComponent<Light>();public static GameObject FindByPath(string scenePath);public static GameObject FindByName(string name);public static GameObject[] FindAllByTag(string tag);public static T[] FindAllByComponent<T>() where T : Component;Caching semantics
Section titled “Caching semantics”The cache stores each object’s original local position, rotation, and scale on first override. Subsequent overrides don’t refresh the cache — ResetTransform always returns to the original pose, not the last-set pose. A second ResetTransform on the same object is a no-op.
Local-space caching means resets stay consistent if the parent moves (e.g. an entire room rotates around the cached object). The world-space pose is recomputed on demand from the parent chain.
The framework calls WorldObjects.Reset() on ClientEvents.Disconnected, which restores every overridden object and clears the cache.
Caveats
Section titled “Caveats”These are the failure modes that bite mod authors. The first three are physical limits of how Unity / SCP:SL render the scene; the last two are about not stepping on engine systems.
Worked example: rotate a bench on round end
Section titled “Worked example: rotate a bench on round end”using Anomaly.Client.Api.Events.Handlers;using Anomaly.Client.Api.Features;using UnityEngine;
RoundEvents.Ended += _ =>{ var bench = WorldObjects.FindByPath("Lobby/Decorations/Bench (4)"); if (bench != null) WorldObjects.Rotate(bench, Vector3.up, 45f);};
ClientEvents.Disconnected += _ =>{ // No need to call ResetTransform manually — framework calls Reset() on disconnect.};Worked example: snap a light toward the local player
Section titled “Worked example: snap a light toward the local player”using Anomaly.Client.Api.Features;using Anomaly.Client.Api.Scheduling;
Scheduler.Every(System.TimeSpan.FromSeconds(0.5), () =>{ var lp = LocalPlayer.Instance; if (lp?.Camera == null) return;
foreach (var light in WorldObjects.FindAllByComponent<UnityEngine.Light>()) { WorldObjects.LookAt(light.gameObject, lp.Camera.transform.position); }}, ownerId: "mymod.tracking_lights");Virtual Inputs for injecting button / mouse / axis data into the engine’s input pipeline.