> For the complete documentation index, see [llms.txt](https://docs.allout.game/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.allout.game/all-out-docs/ru/osnovnye-koncepcii-dvizhka/interactables.md).

# Интерактивные объекты

<figure><img src="https://2709175413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FzA8RGUKJ88fD0oXVALlz%2Fuploads%2Fgit-blob-29d531efc3ba62fe4342df6008264afb7d552819%2Finteractable.png?alt=media" alt="An interact prompt near a world object"><figcaption></figcaption></figure>

### Компонент Interactable

<figure><img src="https://2709175413-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FzA8RGUKJ88fD0oXVALlz%2Fuploads%2Fgit-blob-ca76aa70b048f6a2a55fc2c45d1993889341e38e%2Fimage.png?alt=media" alt="Interactable component fields in the inspector"><figcaption></figcaption></figure>

Добавление компонента Interactable к сущности автоматически покажет UI, когда игроки окажутся в пределах `радиуса` указанного вами.

Радиус взаимодействия масштабируется вместе с мировым масштабом сущности, поэтому держите корневой объект interactable в масштабе 1 и масштабируйте визуального дочернего объекта, если хотите, чтобы дальность взаимодействия оставалась фиксированной.

Чтобы отреагировать на нажатие игроком на interactable, вам нужно написать код CSL.

У сущности может быть более одного `Interactable`. Используйте `приоритета` когда одновременно может быть доступно несколько подсказок.

{% hint style="warning" %}
Выбор подсказки выполняется для каждого игрока отдельно, но содержимое подсказки — нет. `can_use(player)` определяет, может ли игрок видеть и использовать interactable; `set_text`, `set_hold_text`, `set_subtitle`и `subtitle_color` изменяйте один общий компонент, видимый каждому игроку. Не обновляйте эти значения из состояния квеста, инвентаря или прогресса, зависящего от игрока — последний записанный игроком A текст также будет показан игроку B.

Для разных действий для каждого игрока, таких как "Принять квест" и "Сдать", используйте нейтральную общую метку, например "Поговорить", или добавьте отдельные interactable с фиксированной меткой и ограничьте каждый из них с помощью `can_use(player)`. Тогда каждый игрок будет выбирать из interactable, доступных для его собственного состояния.
{% endhint %}

Самый распространённый шаблон такой:

* Создайте класс, наследующийся от `Interactable`
* Вызывайте `this.set_listener(this)` в `ao_start`
* Реализуйте `can_use` и `on_interact` в вашем классе

{% hint style="info" %}
Если вы добавляете новый класс interactable в коде, вам всё равно нужно добавить этот компонент к сущности в редакторе (или создать сущность и добавить компонент во время выполнения).
{% endhint %}

#### Пример с мусором

```go
// Получай монеты за подбор мусора :D
Trash :: class : Interactable {
    @ao_serialize coins: s64;
    used: bool;

    ao_start :: method() {
        this.set_listener(this);
        this.set_text("Подобрать мусор");
        this.set_hold_text("Подбор...");
        required_hold_time = 0.25;
        radius = 1.25;
    }

    can_use :: method(player: Player) -> bool {
        if used return false;
        return true;
    }

    on_interact :: method(player: Player) {
        used = true;
        Economy.deposit_currency(player, "Coins", coins);
        entity.destroy();
    }
}
```

#### Пример с увеличением

Interactable, который один раз «увеличивает» сущность:

```go
Grow_Button :: class : Interactable {
    @ao_serialize scale_multiplier: float;
    used: bool;

    ao_start :: method() {
        this.set_listener(this);
        this.set_text("Увеличить");
        radius = 1.25;
        required_hold_time = 0;
    }

    can_use :: method(player: Player) -> bool {
        return !used;
    }

    on_interact :: method(player: Player) {
        used = true;
        new_scale := entity.local_scale * scale_multiplier;
        entity.set_local_scale(new_scale);
        this.set_text("Уже использовано");
    }
}
```

#### Пример с запросом покупки

Interactable, который запрашивает покупку товара:

```go
Buy_Admin_Pass :: class : Interactable {
    @ao_serialize product_id: string;

    ao_start :: method() {
        this.set_listener(this);
        this.set_text("Купить Admin Pass");
        radius = 1.25;
    }

    can_use :: method(player: Player) -> bool {
        // Пример проверки: скрывать, если уже куплено (game pass)
        return !Purchasing.owns_product(player, product_id);
    }

    on_interact :: method(player: Player) {
        Purchasing.prompt_purchase(player, product_id);
    }
}
```

### Справочник API

```go
Interactable :: class : Component {
    // Общий текст подсказки (взаимодействие по нажатию); не для каждого игрока отдельно.
    get_text :: method() -> string;
    set_text :: method(text: string);

    // Общий текст подсказки при удержании (для взаимодействия с удержанием).
    get_hold_text :: method() -> string;
    set_hold_text :: method(text: string);

    // Общий подзаголовок подсказки.
    get_subtitle :: method() -> string;
    set_subtitle :: method(text: string);

    // Подключить объект-слушатель, который реализует:
    // - can_use(player: Player) -> bool
    // - on_interact(player: Player)
    // - on_holding(player: Player) [optional]
    set_listener :: method(new_listener: Component);

    // Общие поля (задаются в редакторе или в коде)
    offset: v2;
    prompt_offset: v2;
    radius: float;
    required_hold_time: float;
    priority: s64;
    subtitle_color: v4;
}
```

### Необязательные хуки игрока (глобальные правила)

Вы также можете реализовать их в вашем `Player` классе, чтобы принудительно соблюдать общие для игры правила (например, нельзя использовать interactable до возрождения):

```go
Player :: class : Player_Base {
    ao_can_use_interactable :: method(interactable: Interactable) -> bool {
        return true;
    }

    ao_on_interactable_used :: method(interactable: Interactable) {
    }

    ao_on_holding_interactable :: method(interactable: Interactable) {
    }
}
```
