Custom Waypoint

Custom Waypoint is the lightweight runtime marker used for player-placed navigation pins. Where POIMarker is a persistent scene component backed by a PointOfInterest asset, CustomWaypoint is created and destroyed entirely at runtime — the player places one on the world map, it appears on the minimap and compass immediately, and it removes itself automatically when reached or after a configurable lifetime.

Because CustomWaypoint implements the same IPOITarget interface as POIMarker, every navigation surface (minimap, world map, compass) handles it identically without special-case code. From the map's perspective, a custom waypoint is just another icon with a position.

Custom waypoints are almost always created through POIManager.CreateWaypoint() rather than placed manually in the scene.


How Waypoints Are Created

The standard creation flow is:

  1. The player double-clicks a location on the World Map.

  2. WorldMapUI calls POIManager.CreateWaypoint(worldPosition, name).

  3. POIManager instantiates a new GameObject, adds a CustomWaypoint component, calls Initialize() on it, and optionally spawns the configured Marker Prefab as a child.

  4. The new waypoint is registered in POIManager's waypoint list and OnPOIListChanged fires, causing all map surfaces to refresh.

The waypoint then persists — visible on the minimap, compass, and world map — until it is removed via RemoveSelf(), by POIManager.RemoveWaypoint(), or by POIManager.ClearAllWaypoints().


Inspector Sections

Waypoint Data

The identity and icon of this waypoint.

Property
Type
Default
Description

Waypoint Name

string

"Custom Waypoint"

Display name shown on map tooltips and compass labels. Writable at runtime via the WaypointName property.

Icon

Sprite

A single sprite used for both the minimap/world-map icon and the compass icon.

Color

Color

Yellow

Tint applied to the icon on all navigation surfaces. Also used to colour the scene gizmos.

Is Active

bool

true

When false, the waypoint is excluded from all navigation displays.

A live 32 × 32 icon preview (tinted by Color) is drawn beneath the icon slot when a sprite is assigned. The inspector warns if the name is empty or the icon is not assigned, as the waypoint will be invisible without an icon.


Display Settings

Property
Type
Default
Description

Show Distance

bool

true

Whether to append a distance readout to the waypoint label on the compass bar.

Fade Distance

float

200 m

The distance at which the waypoint icon begins to fade. The inspector confirms the active value (e.g. Waypoint will fade at 200 m).


Lifetime Settings

Controls how long the waypoint persists before removing itself.

Property
Type
Default
Description

Has Lifetime

bool

false

When enabled, the waypoint automatically calls RemoveSelf() after the specified duration.

Lifetime

float

60 s

Duration in seconds before auto-removal. Only shown when Has Lifetime is enabled.

Auto Remove on Reach

bool

true

When the player comes within the reach radius (5 m by default), OnWaypointReached fires and the waypoint removes itself.

Reach vs. LifetimeautoRemoveOnReach and hasLifetime are independent. If either condition is met while the player is within range, RemoveSelf() is called. A waypoint with Has Lifetime enabled will also remove itself at the lifetime boundary even if the player never approached it.

In Play mode with Has Lifetime enabled, a progress bar is drawn showing the time remaining as it counts down in real time.


Events

Event
Signature
Fires When

On Waypoint Reached

UnityEvent

The player's distance drops to or below the reach radius (5 m). Fires once per waypoint lifetime — subsequent distance checks are skipped after isReached is set.

On Distance Updated

UnityEvent<float>

Every time UpdateDistance() is called (at POIManager.updateInterval, typically 0.1 s). Passes the current distance in world units.


Status & Testing

In edit mode: Validates that the waypoint name is set and an icon is assigned.

In Play mode, the section shows:

  • IsActive state

  • Current distance to the player

  • World-space position

  • Time remaining (if Has Lifetime is enabled)

Testing buttons (Play mode):

Button
Effect

Remove

Calls RemoveSelf(), cleanly removing the waypoint through POIManager.

[Waypoint Name]

Selects the waypoint's GameObject in the Hierarchy and frames it in the Scene view.


IPOITarget Implementation

CustomWaypoint implements the IPOITarget interface as follows:

Member
Behaviour

WorldPosition

transform.position — no vertical offset (unlike POIMarker).

POIName

Returns waypointName.

MapIcon

Returns icon.

CompassIcon

Returns icon — the same sprite is used for both surfaces.

IconColor

Returns color.

IsActive

isActive && gameObject.activeInHierarchy — both the serialized flag and the GameObject must be active.

CurrentDistance

Cached distance to the player, updated by POIManager each interval.

ShowOnMinimap

Always true — custom waypoints always appear on the minimap.

ShowOnCompass

Always true — custom waypoints always appear on the compass.


Scene Gizmos

Unlike most components, CustomWaypoint draws gizmos via OnDrawGizmos (always visible, not just when selected) so placed waypoints are easy to locate in the Scene view.

Gizmo
Description

Wire sphere (0.5 m)

Drawn at the waypoint's position in the icon color.

Vertical line

10 units tall, acting as a scene beacon.

Reach radius sphere

Semi-transparent wire sphere at the reach distance (5 m by default). Represents the proximity that triggers OnWaypointReached.

The OnSceneGUI handler (selected only) draws the same beacon shapes via Handles, adds a semi-transparent disc at the Fade Distance radius, and renders the waypoint name as a coloured bold label at the top of the vertical line.


Public API

Lifecycle

Method
Description

Initialize(string name, Sprite icon, Color color, float reachDist = 5f)

Sets the waypoint's name, icon, colour, and reach radius. Called automatically by POIManager.CreateWaypoint() immediately after the component is added. Also resets the creation time, so a lifetime clock starts from this call.

RemoveSelf()

Cleanly removes this waypoint. Calls POIManager.RemoveWaypoint(this) if the manager is available (which fires OnWaypointRemoved and OnPOIListChanged), or falls back to Destroy(gameObject).

Runtime Updates

Method
Description

UpdateDistance(Vector3 playerPosition)

Recalculates CurrentDistance, fires OnDistanceUpdated, and checks whether the reach threshold has been crossed. Called by POIManager each update interval.

Post-Creation Configuration

Method
Description

SetIconAndColor(Sprite newIcon, Color newColor)

Updates the icon and colour after the waypoint has been created (e.g. after the player confirms a selection in a WaypointPickerUI). Fires POIManager.OnPOIListChanged so all navigation surfaces refresh their icons immediately.

SetLifetime(float seconds)

Enables the lifetime system and sets the duration. Resets the creation time to now, so the countdown begins from the moment this method is called rather than from when the component started.

Properties

Property
Type
Description

WaypointName

string (get/set)

Read or write the display name at runtime. Setting the name does not automatically notify POIManager — call POIManager.OnPOIListChanged?.Invoke() manually if the name change needs to propagate to UI immediately.

TimeRemaining

float (read-only)

Seconds until auto-removal. Returns float.MaxValue when Has Lifetime is disabled. Never returns a negative value (Mathf.Max(0, …) is applied).


Workflow: Full Player-Placed Waypoint Flow

  1. Player interaction — Player opens the World Map and double-clicks a location.

  2. WorldMapUI — Calls POIManager.CreateWaypoint(worldPosition, "Custom Waypoint").

  3. POIManager — Creates a new GameObject, adds CustomWaypoint, calls Initialize() with the default icon and colour from its Default Waypoint Icon and Waypoint Color settings, and optionally spawns the Marker Prefab in the world.

  4. Icon refreshOnPOIListChanged fires; minimap, compass, and world map rebuild their icon pools. The new pin appears on all surfaces.

  5. (Optional) WaypointPickerUI — If a picker panel is configured, the player selects a custom icon and name before confirming. The panel calls SetIconAndColor() on the created waypoint, triggering another icon refresh.

  6. POIManager.SetActiveWaypoint(waypoint) — Marks this as the navigation target; minimap and world map draw a dotted path to it.

  7. Player navigatesUpdateDistance() fires every 0.1 s. When the player reaches within 5 m, OnWaypointReached fires and (if autoRemoveOnReach is true) RemoveSelf() is called.

  8. CleanupPOIManager.RemoveWaypoint() destroys the GameObject and fires OnPOIListChanged. The icon disappears from all navigation surfaces.


Other Tips

  • Always create via POIManager — Calling new GameObject().AddComponent<CustomWaypoint>() directly bypasses the waypoint cap check, the marker prefab spawning, and the OnWaypointCreated event. Always use POIManager.CreateWaypoint().

  • Same icon for map and compass — Unlike POIMarker, CustomWaypoint uses a single sprite for both surfaces. If you need different icons per surface on a waypoint, use a POIMarker with useCustomSettings = true and customCategory = Waypoint instead.

  • WorldPosition has no offset — The icon is placed at exactly transform.position. There is no worldMarkerOffset equivalent. The world marker prefab (if any) is parented to the waypoint's GameObject with local position zero.

  • SetLifetime() restarts the clock — Calling SetLifetime() on an existing waypoint does not add time; it resets the countdown to start from the moment of the call. This is intentional for scripted "refresh" scenarios (e.g. a timed event extending a waypoint's life).

  • TimeRemaining is not zero when lifetime expires — The Update loop calls RemoveSelf() the frame the condition is met; TimeRemaining is clamped to zero but the GameObject may persist until the end of that frame. Do not rely on TimeRemaining == 0 as a reliable destroy signal; use OnWaypointRemoved on POIManager instead.

  • Reach detection requires POIManager to call UpdateDistance() — The reach check runs inside UpdateDistance(), not in the waypoint's own Update loop. If the waypoint is not registered with POIManager (i.e. created manually), OnWaypointReached will never fire.

Last updated