Renvix Assets

Vision System

VisionSystem is the static class that updates all Vision Sources. You never need to create or call it for the components to work, but it is useful for queries across all sources and for timing.

Namespace Renvix.VisionFog.

Members

MemberDescription
static bool IsVisibleToTeam(int team, Vector3 worldPoint, ReadOnlySpan<int> ignoredColliderIds = default)True if any source of the team saw the point in its latest update. Colliders with the given instance ids, usually the object's own, do not block. In 2D a Vector2 works too
static IReadOnlyList<VisionSource> SourcesAll enabled sources
static event Action AfterUpdateRaised at the end of every update, after all sources have published their results
static VisionSystemStats LastStatsCounters and timings of the most recent update, see Profiling
static bool UseJobsBurst jobs on worker threads (default). Off is for debugging and comparison
static bool OverlapGatherAndSolveStart each solve right after its obstacles are gathered (default). Off is for comparison

The order of a frame

StepWhat happens
UpdateYour scripts run. Vision Perception updates awareness and raises its events. Fog Hider shows or hides its object
LateUpdateYour scripts run, for example the camera follow
Vision updateAll due sources compute their visible areas from the final positions of the frame. Each source raises Updated, then VisionSystem.AfterUpdate is raised. Cones and fogs are rebuilt
RenderingCones and the fog show the result of this frame

Cones and the fog always match the frame that is drawn. Vision Perception and Fog Hider work in Update, so they use the result of the vision update at the end of the previous frame, one frame behind. At normal frame rates this is not noticeable. When a decision must use the newest result, read it in LateUpdate of the next frame or handle the source's Updated event.

// Called right after this source has a new result.
source.Updated += s => { if (s.CanSee(player.position)) Shoot(); };

CanSee and IsVisibleToTeam can be called at any time. They use the latest result of each source. If a source has never been updated, CanSee updates it first.

In edit mode the system updates about 30 times per second, so cones can be previewed while editing.

Quality

VisionQuality scales all sources and fogs at once. See Updates and Quality for what each level changes.

MemberDescription
static VisionQualityLevel LevelHigh (default), Medium or Low. Changing it applies right away
static event Action ChangedRaised when the level changes
static float ArcStepMultiplierHow much coarser the far edge is at the current level
static float MinUpdateIntervalThe shortest time between updates of one source at the current level
static float FogResolutionMultiplierFog texture resolution factor at the current level

The level is reset to High when Play mode starts.

Plane coordinates

Vision is computed in 2D plane coordinates (float2 from Unity.Mathematics): X, Y in 2D, X, Z in 3D, and ground coordinates in 2D Isometric. Several APIs use them, for example VisionSource.Boundary, VisionSource.Shape and FogOfWar.Area.

MemberDescription
source.ToPlane(Vector3 world)World position to this source's plane coordinates, including the isometric proportions
source.ToWorld(float2 point, float height)Back to a world position at the given height (Y in 3D, Z in 2D)
plane.ToPlane(world, isometricRatio), plane.ToWorld(point, height, isometricRatio)The same as extension methods on VisionPlane
plane.HeightOf(world)The height part of a world position: Y in 3D, Z in 2D
plane.Uses2DPhysics()True for 2D and 2D Isometric

Example: draw the visible area yourself

using Renvix.VisionFog;
using Unity.Mathematics;
using UnityEngine;

[RequireComponent(typeof(LineRenderer))]
public class VisionOutline : MonoBehaviour
{
    [SerializeField] VisionSource source;
    LineRenderer _line;

    void Awake() => _line = GetComponent<LineRenderer>();
    void OnEnable() => source.Updated += Redraw;
    void OnDisable() => source.Updated -= Redraw;

    void Redraw(VisionSource s)
    {
        var boundary = s.Boundary;
        _line.positionCount = boundary.Length;
        for (int i = 0; i < boundary.Length; i++)
            _line.SetPosition(i, s.ToWorld(boundary[i], s.EyeHeight));
    }
}

For a cone, the visible area is the fan from the eye (Shape.Origin) through the boundary points and back to the eye. For a 360° view the boundary is a closed loop. The boundary is valid until the next update of the source, so copy it if you need it longer.