> For the complete documentation index, see [llms.txt](https://malbersanimations.gitbook.io/animal-controller/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://malbersanimations.gitbook.io/animal-controller/malbers-questforge/points-of-interest/poi-marker.md).

# POI Marker

### Overview

**POI Marker** is the scene component that makes any GameObject a trackable Point of Interest. Attach it to an NPC, chest, dungeon entrance, or quest target and it will automatically appear as an icon on the minimap, compass bar, and world map.

The component acts as the bridge between the data layer (the `PointOfInterest` ScriptableObject) and the scene. It self-registers with `POIManager` on Start, drives its own 3D world-space billboard, and reacts autonomously to changes in quest state, objective progression, and dialogue completion — all without requiring external scripting.

POI Markers support two configuration modes: **POI Data mode**, where all settings are read from a shared `PointOfInterest` asset, and **Custom Settings mode**, where icon, name, colour, and category are set inline on the component itself.

<figure><img src="https://963537199-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Lzhr1XSMzMqNXjRnNlb%2Fuploads%2FKZfgqtXVObAq2iH395Mu%2Fimage.png?alt=media&amp;token=460bf50a-2dae-49a9-ab6b-aa1923a62600" alt=""><figcaption></figcaption></figure>

***

### Inspector Sections

#### POI Configuration

The core identity and lifecycle settings for this marker.

| Property                | Type            | Default | Description                                                                                                                                                                                                                            |
| ----------------------- | --------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Use Custom Settings** | bool            | false   | When enabled, replaces the POI Data asset slot with inline custom fields. Use for one-off markers that don't need a shared asset.                                                                                                      |
| **POI Data**            | PointOfInterest | —       | The ScriptableObject asset supplying this marker's name, icons, colours, and display rules. Shown only when Use Custom Settings is off. A live preview box beneath the slot confirms the asset's name, category, and visibility flags. |
| **Is Active**           | bool            | true    | Whether this POI is currently tracked and visible. Can be toggled at runtime via the `IsActive` property.                                                                                                                              |
| **Auto Register**       | bool            | true    | Calls `POIManager.RegisterPOI()` automatically in Start. Disable only if you need to control registration timing manually.                                                                                                             |
| **Persistent**          | bool            | false   | Calls `DontDestroyOnLoad` in Awake so this POI survives scene transitions.                                                                                                                                                             |

***

#### Custom Settings *(only when Use Custom Settings is enabled)*

Inline overrides that replace the POI Data asset entirely.

| Property         | Type        | Description                                            |
| ---------------- | ----------- | ------------------------------------------------------ |
| **POI Name**     | string      | Display name shown on maps and tooltips.               |
| **Map Icon**     | Sprite      | Icon used on the minimap and world map.                |
| **Compass Icon** | Sprite      | Icon used on the compass bar.                          |
| **Icon Color**   | Color       | Tint applied to both icons.                            |
| **Category**     | POICategory | Category used for map filtering. Defaults to `Custom`. |

***

#### Location Data *(only for Location-category POIs)*

When the assigned `PointOfInterest` asset has its category set to `Location`, a **Location Data** section appears showing a live comparison between the position stored in the asset and the marker's current scene transform.

| Inspector Element                 | Description                                                                                                                                                                                   |
| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Stored Position (Asset)**       | Read-only display of the `WorldPosition` field saved in the POI asset.                                                                                                                        |
| **Current Position (Transform)**  | Read-only display of this marker's `transform.position`.                                                                                                                                      |
| **Sync World Position to Asset**  | Copies `transform.position` to the asset's `WorldPosition` field, marks the asset dirty, and calls `AssetDatabase.SaveAssets()`. This is the canonical way to set the asset's world position. |
| **Move Marker to Asset Position** | Moves this marker's transform to the position stored in the asset. Only works if the stored position is non-zero.                                                                             |
| **Location Info Preview**         | Read-only summary of Location ID, type, trigger radius, and parent location pulled directly from the asset.                                                                                   |

The inspector warns when the stored position is still at (0, 0, 0), and also warns if the positions have drifted more than 1 metre apart since the last sync.

***

#### Location Discovery *(only for Location-category POIs)*

Controls the physics trigger that fires when the player enters this location's area.

| Property                          | Type   | Default    | Description                                                                                                                                             |
| --------------------------------- | ------ | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Enable Location Trigger**       | bool   | false      | Activates a `SphereCollider` trigger on this GameObject at runtime. When the player enters, the `LocationManager` is notified and discovery logic runs. |
| **Use Custom Trigger Radius**     | bool   | false      | Overrides the `LocationRadius` from the POI asset with a per-marker radius.                                                                             |
| **Custom Trigger Radius**         | float  | 50 m       | The override radius, shown only when Use Custom Trigger Radius is enabled. The active radius from the asset is shown as a read-only field otherwise.    |
| **Auto-Complete Quest Objective** | bool   | true       | Automatically completes any `GoToLocationObjective` in an active quest whose location ID matches this POI when the player enters.                       |
| **Trigger Tag**                   | string | `"Player"` | Only colliders with this tag trigger discovery.                                                                                                         |

**Collider Management** — The editor actively validates the collider state:

* If no collider is present, the inspector explains one will be created at runtime and offers an **Add SphereCollider Now** button to add it immediately in edit mode.
* If a collider exists but `Is Trigger` is off, a warning and an **Enable Is Trigger** button appear.
* If a `SphereCollider` exists but its radius doesn't match the target radius, a **Sync Collider Radius** button appears.

**Location Events**

| Event                      | Fires When                                                                                     |
| -------------------------- | ---------------------------------------------------------------------------------------------- |
| **On Location Entered**    | The player's collider enters the trigger zone.                                                 |
| **On Location Exited**     | The player's collider leaves the trigger zone.                                                 |
| **On Location Discovered** | The player enters and this is the **first time** (the location was not previously discovered). |

In Play mode, the Location Discovery section also shows whether the player is currently inside the location, the current discovery status, and a **Force Discover (Debug)** button that calls `TriggerLocationDiscovery()` manually.

***

#### Quest Integration

Links this POI to a specific quest objective so its visibility is driven automatically by quest progression. The editor uses a two-step workflow:

**Step 1 — Select Quest**: Drag a `Quest` ScriptableObject asset into the **Linked Quest** field. Changing the quest clears any previously selected objective.

**Step 2 — Select Objective**: A dropdown appears listing every objective in the quest, formatted as `index: ObjectiveType — description`. Select the objective this POI represents. The **Linked Objective ID** field is then filled automatically (read-only) and a confirmation box displays the quest name, objective description, type, and whether it is optional.

**Additional Settings** (shown once a quest and objective are linked):

| Property                  | Type  | Default | Description                                                                                                                                                                    |
| ------------------------- | ----- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Hide When Complete**    | bool  | true    | Hides this POI when the linked objective is completed. Exception: if this is a discovered Location-category POI, it stays visible even after the objective completes.          |
| **Only Show When Active** | bool  | true    | Hides the POI until the linked objective is the **currently active** step in the quest's sequential progression. Future and past objectives are hidden.                        |
| **Auto-Link Location**    | bool  | true    | On Start, if no objective ID is set, scans active quests for a `GoToLocationObjective` whose `targetPosition` is within 5 metres of this marker and links to it automatically. |
| **Show Gizmo**            | bool  | true    | Draws the objective link indicator in the Scene view (see Gizmos below).                                                                                                       |
| **Gizmo Color**           | Color | Cyan    | Colour of the objective link gizmo. Only shown when Show Gizmo is enabled.                                                                                                     |

***

#### Dialogue Trigger Integration

When a `DialogueTrigger` on the same (or another) GameObject marks a conversation as completed, this POI can hide itself automatically — useful for quest givers who should no longer show a marker after their opening dialogue has played.

| Property                    | Type            | Default | Description                                                                                                                                   |
| --------------------------- | --------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| **Auto-Detect**             | bool            | true    | At runtime, searches this GameObject with `GetComponent<DialogueTrigger>()` if no manual reference is assigned.                               |
| **Linked Dialogue Trigger** | DialogueTrigger | —       | Manual reference to any `DialogueTrigger` in the scene. Takes precedence over auto-detection.                                                 |
| **Hide When Completed**     | bool            | true    | Hides this POI when the linked trigger's `HasTriggered` becomes true, or if it was already completed in the save system when the scene loads. |

When a manual reference is set, an info box shows the linked trigger's GameObject name, dialogue asset name, `triggerOnce` flag, and `saveTriggerState` setting.

In Play mode the section shows live status: whether the dialogue has been completed and whether the trigger can currently fire.

> **Save-system awareness** — On Start, the POI Marker queries `DialogueSaveSystem` directly using the same ID generation logic as `DialogueTrigger` to check completion state. This avoids problems caused by same-frame execution order, where the `DialogueTrigger`'s own `Start()` may not yet have run.

***

#### World Marker (3D)

An optional 3D prefab spawned in world space above this POI's position.

| Property            | Type       | Default   | Description                                                                                                                                           |
| ------------------- | ---------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Marker Prefab**   | GameObject | —         | Prefab instantiated as a child of this GameObject at Start (if the POI is active). If left empty, the POI appears only on 2D navigation surfaces.     |
| **Position Offset** | Vector3    | (0, 2, 0) | Local-space offset applied to the spawned instance. Also defines `WorldPosition`, which is what the map and elevation systems use for icon placement. |
| **Face Camera**     | bool       | true      | Every Update, the world marker rotates to face `Camera.main` (billboard behaviour).                                                                   |

The world marker is created lazily: if the POI starts inactive, the prefab is not instantiated until the POI is activated. If the POI is a Location with `HideUntilDiscovered`, the prefab is not instantiated until the location is first discovered.

***

#### Events

**State Events**

| Event                  | Signature    | Fires When                   |
| ---------------------- | ------------ | ---------------------------- |
| **On POI Activated**   | `UnityEvent` | `IsActive` changes to true.  |
| **On POI Deactivated** | `UnityEvent` | `IsActive` changes to false. |

**Distance Events**

| Event                     | Signature           | Fires When                                                                                          |
| ------------------------- | ------------------- | --------------------------------------------------------------------------------------------------- |
| **On Distance Changed**   | `UnityEvent<float>` | The cached distance changes by more than 0.1 world units since last check. Passes the new distance. |
| **On Player Enter Range** | `MEvent`            | The player crosses into the visible range window (`MinDistance`–`MaxDistance`).                     |
| **On Player Exit Range**  | `MEvent`            | The player exits the visible range window.                                                          |

***

#### Debug Options

| Property       | Description                                                                                                                          |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| **Debug Mode** | Enables detailed `Debug.Log` output for all visibility decisions, registration events, objective evaluations, and location triggers. |

***

#### Status & Debug Tools

**In edit mode**, the section validates the configuration:

* Error if no POI Data is assigned and Custom Settings is off
* Warning if Custom Name or Custom Map Icon is missing when using inline settings
* Warning if a quest is selected but no objective has been chosen
* Confirmation that auto-register is enabled, or a warning that manual registration is required

**In Play mode**, the section shows:

* `IsActive`, current distance, name, and category
* Live `ShowOnMinimap`, `ShowOnCompass`, `ShowWorldMarker` values (including discovery-gate results)
* Quest link status: quest name, objective description, whether the quest is active/completed, whether the objective is in progress or done

**Testing buttons** (Play mode only):

| Button                    | Effect                                                                           |
| ------------------------- | -------------------------------------------------------------------------------- |
| **Activate / Deactivate** | Toggles `IsActive` immediately. Label updates to reflect the current state.      |
| **Register**              | Calls `RegisterPOI()` manually.                                                  |
| **Unregister**            | Calls `UnregisterPOI()` manually, which also destroys the world marker instance. |

***

### Scene Gizmos

Visible when the GameObject is selected in the Scene view.

| Gizmo                       | Description                                                                                                                                                                                        |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Inner wire sphere**       | Drawn at `MinDistance` radius, tinted with the POI's icon colour at 30% opacity. Represents the near-clip distance.                                                                                |
| **Outer wire sphere**       | Drawn at `MaxDistance` radius at 10% opacity. Represents the visibility range limit.                                                                                                               |
| **Wire cube**               | Drawn at `transform.position + worldMarkerOffset`. Shows exactly where the world marker prefab will spawn.                                                                                         |
| **Objective link sphere**   | Cyan (configurable) 2 m wire sphere and a bold label above the object showing `"Objective: [id]"`. Only shown when Show Gizmo is enabled and an objective ID is set.                               |
| **Location trigger sphere** | Green-cyan wire sphere at `TriggerRadius`, with a light filled sphere for area context. A bold label above shows the location name and radius. Only shown when Enable Location Trigger is enabled. |

The `OnSceneGUI` handles also draw two stacked horizontal discs and a tall vertical line (10 units high) at the marker's position as a scene-view beacon, coloured with the POI's icon colour.

***

### Visibility Resolution

POI Marker uses a priority chain to determine whether to show each navigation surface. All three (`ShowOnMinimap`, `ShowOnCompass`, `ShowWorldMarker`) apply the same gate order:

1. **POI Data flag** — `poiData.ShowOnMinimap` (or equivalent) must be true.
2. **Discovery gate** — If this is a `Location` POI and both `IsDiscoverable` and `HideUntilDiscovered` are true, the marker is hidden until `LocationManager.IsDiscovered(locationId)` returns true.

#### Objective Visibility Logic

When a quest and objective are linked and `onlyShowWhenObjectiveActive` is true, `EvaluateObjectiveVisibility()` determines `IsActive` following this precedence:

| Condition                                                  | Result                                                                                   |
| ---------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| Inspector `isActive` was false at Start                    | Always hidden — designer intent overrides everything.                                    |
| Dialogue trigger has been completed                        | Hidden.                                                                                  |
| Quest not yet started or already completed                 | Hidden.                                                                                  |
| Objective completed + `hideWhenObjectiveComplete` true     | Hidden — **unless** this is a discovered Location POI, in which case it remains visible. |
| Objective is the current active step in the quest sequence | Shown.                                                                                   |
| Objective is a future step not yet reached                 | Hidden.                                                                                  |

This evaluation re-runs whenever a relevant `QuestEventBus` event fires (`QuestStarted`, `QuestCompleted`, `QuestFailed`, `QuestProgressUpdated`).

***

### Public API

#### Registration

| Method            | Description                                                                                                  |
| ----------------- | ------------------------------------------------------------------------------------------------------------ |
| `RegisterPOI()`   | Manually registers this marker with `POIManager`. Called automatically in Start when `autoRegister` is true. |
| `UnregisterPOI()` | Removes this marker from `POIManager` and destroys the world marker instance.                                |

#### State

| Member             | Type           | Description                                                                                                                |
| ------------------ | -------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `IsActive`         | bool (get/set) | Setting to false hides the world marker and fires `OnPOIDeactivated`. Setting to true shows it and fires `OnPOIActivated`. |
| `UpdateDistance()` | void           | Recalculates `CurrentDistance` and fires range events. Called by `POIManager` each update interval for registered markers. |

#### Quest & Objective

| Method                          | Returns            | Description                                                                                                                                                                                     |
| ------------------------------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `EvaluateObjectiveVisibility()` | void               | Re-evaluates and updates `IsActive` based on the current quest and objective state. Called automatically when quest events fire; call manually after quest state changes outside the event bus. |
| `SetLinkedObjectiveId(string)`  | void               | Sets the linked objective ID at runtime.                                                                                                                                                        |
| `GetLinkedObjectiveData()`      | QuestObjectiveData | Returns the runtime data for the linked objective from `QuestManager`, or null if not found.                                                                                                    |
| `IsObjectiveTracked()`          | bool               | Returns true if the linked objective's `isTracked` flag is set in QuestManager.                                                                                                                 |

#### Location

| Method                       | Returns | Description                                                                                                                                          |
| ---------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `TriggerLocationDiscovery()` | void    | Manually marks this location as discovered via `LocationManager.DiscoverLocation()`. Use for scripted discovery events (cutscenes, loading screens). |
| `IsLocationDiscovered()`     | bool    | Returns `LocationManager.IsDiscovered(poiData.LocationId)`, or false if this is not a Location POI.                                                  |
| `IsDiscovered()`             | bool    | Same as `IsLocationDiscovered()`. Returns true for non-Location POIs (they are always considered "discovered").                                      |

#### Waypoint Conversion

| Method                                                 | Description                                                                                                                                       |
| ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `SetAsWaypoint(string name, Sprite icon, Color color)` | Switches to Custom Settings mode and configures this marker as a `Waypoint`-category POI. Used by `CustomWaypoint` and waypoint creation systems. |

***

### Key Properties

| Property                | Returns         | Description                                                                                                           |
| ----------------------- | --------------- | --------------------------------------------------------------------------------------------------------------------- |
| `POIName`               | string          | Custom name if using custom settings, otherwise `poiData.POIName`.                                                    |
| `MapIcon`               | Sprite          | Custom icon or asset icon.                                                                                            |
| `CompassIcon`           | Sprite          | Custom icon or asset icon.                                                                                            |
| `IconColor`             | Color           | Custom colour or asset colour.                                                                                        |
| `Category`              | POICategory     | Custom category or asset category.                                                                                    |
| `WorldPosition`         | Vector3         | `transform.position + worldMarkerOffset`. This is what the minimap, world map, compass, and elevation system all use. |
| `CurrentDistance`       | float           | Cached distance to the player, updated at `POIManager.updateInterval`.                                                |
| `ShowOnMinimap`         | bool            | Final resolved visibility after applying data flags and discovery gates.                                              |
| `ShowOnCompass`         | bool            | Final resolved visibility.                                                                                            |
| `ShowWorldMarker`       | bool            | Final resolved visibility.                                                                                            |
| `IsLocationPOI`         | bool            | True when `poiData != null && poiData.IsLocation`.                                                                    |
| `TriggerRadius`         | float           | Active location trigger radius: custom value if overriding, otherwise `poiData.LocationRadius`.                       |
| `PlayerInsideLocation`  | bool            | True while the player's collider is inside the location trigger zone.                                                 |
| `LinkedDialogueTrigger` | DialogueTrigger | The resolved dialogue trigger reference (auto-detected or manually set).                                              |

***

### Workflow: Setting Up a New POI Marker

1. Add the **POI Marker** component to the scene GameObject you want to track.
2. Drag a `PointOfInterest` asset into the **POI Data** slot, or enable **Use Custom Settings** and fill in inline values.
3. If this is a **Location POI**, click **Sync World Position to Asset** to write the scene position into the asset.
4. To link with quest progression, select the **Linked Quest** asset, then pick the objective from the dropdown.
5. If the location should trigger discovery when the player walks into it, enable **Enable Location Trigger**. Use the **Add SphereCollider Now** editor button if you prefer to see the collider at design time.
6. Assign a **Marker Prefab** if you want a 3D billboard above the object in the game world.
7. Enter Play mode — the icon should appear on the minimap and compass immediately.

***

### Other Tips

* **`WorldPosition` includes the offset** — The map icon is placed at `transform.position + worldMarkerOffset`, not at the raw transform. If icons appear 2 metres above where you expect on the map, the offset is the reason. Set it to `Vector3.zero` for flat-plane markers.
* **Objective visibility vs. IsActive** — Setting `IsActive = false` in the inspector permanently suppresses the marker (the `inspectorIsActive` flag captures this at Start). The objective system will never re-enable it. If you want quest logic to control a marker that starts hidden, set `isActive = true` in the inspector and let the objective visibility logic hide it on first evaluation.
* **Discovery gate is per-surface** — `HideUntilDiscovered` hides the minimap and compass icons but the Location Discovery trigger is still active. The player can physically enter the area and discover the location even while the icon is invisible.
* **Auto-Link Location** runs once at Start — If no objective ID is set and a matching `GoToLocationObjective` exists within 5 metres, it links automatically. This saves setup time for densely placed markers but can produce false matches if two objectives are close together. Prefer the Quest Integration dropdown for explicit control.
* **Same-frame execution with DialogueTrigger** — The save-system check in `InitializeDialogueTrigger()` works around Unity's non-deterministic Start() order. No special script execution order settings are required.
* **Persistent markers across scenes** — Mark `isPersistent = true` only for markers that genuinely need to survive the full game session (e.g. a hub town). Overusing persistence adds overhead and can cause duplicate entries if the same scene is reloaded.
