Перейти к основному содержимому
Живая игра

Эта страница ещё не переведена — показана английская версия.

Bots

Бот входит в игру как обычный игрок. Отдельно живёт только его мозг. Та же сессия, та же проверка при входе, те же правила, тот же ACL. Комната не отличает бота от человека — так задумано. Поэтому боты играют по настоящим правилам вашей игры, а античиту никогда не нужны исключения для ботов.

Когда это нужно

  • Лобби нужно заполнять в часы с низким онлайном: FillRoom добирает матчи до квоты, а когда приходят люди, боты уступают им места (seat).
  • Боты должны играть по настоящим правилам — проверка при входе, ACL, видимость, — чтобы античиту не требовались исключения для ботов.
  • У вас свой внешний мозг (brain) — например, обученная модель или отдельный сервис, — и он подключается через ConnectAsBot, как любой игрок (player).
  • Сущность (entity) отключившегося игрока должна перейти к боту, а после переподключения вернуться обратно, и ни место, ни предсказание не должны этого заметить.
  • Не нужен, если персонаж ничего не решает. Диалоговый NPC без мозга относится к объектам мира.

Эта страница о том, как подключить бота: ввести его в комнату, добрать лобби до квоты, передать место между ботом и человеком. Как написать мозг, который принимает решения, описано на странице Как написать мозг бота.

Кто что делает

АкторЧто делает на этой странице
bot-brainподключается как игрок; получает восприятие; отправляет команды
room-ownerобъявляет профили, заполняет комнаты до квоты, передаёт управление между ботом и человеком

Коротко

Профиль filler: параметры сложности, мозг на системе полезности (utility) и FillRoom до квоты
[BotProfile("filler")]
[Brain(Kind.Utility)]
public static class Filler
{
    public static Difficulty Difficulty = Difficulty.Of(reactionMs: 250, aimJitter: 0.08f);

    [Consider(Targeting.NearestEnemy)] public static Behaviour Target;
    [Steer(Steering.SeekAndStrafe)]    public static Behaviour Move;
    [UseAbilities(When.Ready)]         public static Behaviour Fire;
}

PlayServ.Bots.FillRoom("battle", toQuota: 8, profile: "filler", minHumans: 1);
@BotProfile('filler')
@Brain({ kind: 'utility' })
export class Filler {
  static difficulty = Difficulty.of({ reactionMs: 250, aimJitter: 0.08 });

  @Consider(Targeting.nearestEnemy) target: Behaviour;
  @Steer(Steering.seekAndStrafe) move: Behaviour;
  @UseAbilities(When.ready) fire: Behaviour;
}

PlayServ.bots.fillRoom('battle', { toQuota: 8, profile: 'filler', minHumans: 1 });
@bot_profile("filler")
@brain(kind="utility")
class Filler:
    difficulty = Difficulty.of(reaction_ms=250, aim_jitter=0.08)

    target = consider(Targeting.NEAREST_ENEMY)
    move = steer(Steering.SEEK_AND_STRAFE)
    fire = use_abilities(When.READY)

playserv.bots.fill_room("battle", to_quota=8, profile="filler", min_humans=1)
Скоро — Go

Как объявлять через атрибуты в Go, пока не решено (O-1). Примеры появятся, когда выберут способ записи.

USTRUCT(PSBotProfile = "filler", PSBrain = (Kind = "Utility"))
struct FFiller
{
    GENERATED_BODY()

    UPROPERTY(PSDifficulty = (ReactionMs = 250, AimJitter = "0.08"))
    FPSDifficulty Difficulty;

    UPROPERTY(PSConsider = (Targeting = "NearestEnemy")) FPSBehaviour Target;
    UPROPERTY(PSSteer    = (Steering = "SeekAndStrafe")) FPSBehaviour Move;
    UPROPERTY(PSUseAbilities = (When = "Ready"))         FPSBehaviour Fire;
};
// declarations compile into the same pushed model — playserv push from the UE project or CI

// a host tops up the room it serves
Client->Bots->FillRoom(PSKeys::Rooms::Battle,
    FPSFillRoomParams{ .ToQuota = 8, .Profile = TEXT("filler"), .MinHumans = 1 });
// co_await support ships later: a built-in SDK coroutine wrapper that respects Unreal's GC and threading.
Unity C# is the same C# API here — те же атрибуты компилируются в Unity; для FillRoom нужны права хоста, и они есть у сборки мастер-клиента
[BotProfile("filler")]
[Brain(Kind.Utility)]
public static class Filler
{
    public static Difficulty Difficulty = Difficulty.Of(reactionMs: 250, aimJitter: 0.08f);

    [Consider(Targeting.NearestEnemy)] public static Behaviour Target;
    [Steer(Steering.SeekAndStrafe)]    public static Behaviour Move;
    [UseAbilities(When.Ready)]         public static Behaviour Fire;
}

PlayServ.Bots.FillRoom("battle", toQuota: 8, profile: "filler", minHumans: 1);

Внешний мозг — более тяжёлый ИИ, обученная модель, отдельный сервис — подключается как любой игрок:

ConnectAsBot подключает внешний мозг как игрока: на входе те же дельты, на выходе тот же ввод
var bot = await PlayServ.ConnectAsBot(projectKey, botId: "trainer-07");
var seat = await bot.Matchmaking.Find("battle");
var room = await bot.Rooms.Join(seat);
// perception in ← the same deltas a player receives; commands out ← the same inputs
const bot = await PlayServ.connectAsBot(projectKey, { botId: 'trainer-07' });
const seat = await bot.matchmaking.find('battle');
const room = await bot.rooms.join(seat);
// perception in ← the same deltas a player receives; commands out ← the same inputs
bot = await PlayServ.connect_as_bot(project_key, bot_id="trainer-07")
seat = await bot.matchmaking.find("battle")
room = await bot.rooms.join(seat)
# perception in ← the same deltas a player receives; commands out ← the same inputs
Скоро — Go

Как объявлять через атрибуты в Go, пока не решено (O-1). Примеры появятся, когда выберут способ записи.

// An Unreal-based trainer client is a legitimate brain — it connects as a player.
FPlayServClient::ConnectAsBot(ProjectKey, TEXT("trainer-07"),
    TPSOnResult<FPlayServClient*>::CreateLambda([](const TPSResult<FPlayServClient*>& Result)
    {
        if (!Result.HasValue()) { return; }
        FPlayServClient* Bot = Result.Value();
        Bot->Matchmaking->Of<FBattleQueue>()->Tickets->Create(FPSTicketClaim{ .Mode = TEXT("battle") },
            TPSOnResult<FPSTicket*>::CreateLambda([Bot](const TPSResult<FPSTicket*>& TicketResult)
            {
                if (!TicketResult.HasValue()) { return; }
                TPSSubscription Placement = TicketResult.Value()->Subscribe(
                    [Bot](const FPSSeat& Seat) { Bot->Rooms->Join(Seat); });
            }));
    }));
// perception in ← the same deltas a player receives; commands out ← the same inputs
// co_await support ships later: a built-in SDK coroutine wrapper that respects Unreal's GC and threading.
Unity C# is the same C# API here — работает в Unity без изменений, со сгенерированными типами
var bot = await PlayServ.ConnectAsBot(projectKey, botId: "trainer-07");
var seat = await bot.Matchmaking.Find("battle");
var room = await bot.Rooms.Join(seat);
// perception in ← the same deltas a player receives; commands out ← the same inputs

Модель

Своих понятий бот не вводит. Это учётные данные актора плюс то, что уже объявлено для комнат, данных и перемещения.

Что содержит объявление бота.

Что объявляетсяЧто это
тик мышлениякак часто опрашиваются мозги. Это не тик симуляции: мозги работают снаружи, и сетевой вызов на каждый тик невозможен
где работают мозгив облачной функции, на бэкенде студии или на её игровом сервере. Конкретное место в контракт не входит, и переезд между ними не ломает совместимость
пресет актораправа бота в виде обычного пресета актора
видимость метки ботасообщают ли участникам, что это бот. Сама метка существует всегда, и платформа всегда её видит. Видят ли её игроки, объявляет тип комнаты (room): на одних рынках раскрывать ИИ-соперника обязательно по закону, на других это продуктовое решение
поведение при недоступности мозговодин из трёх вариантов, без значения по умолчанию: do nothing · leave the room · fall back to built-in default behaviour
добор составаего объявляет тип комнаты: сколько ботов, при каком условии и до какого момента. Модуль Matchmaking о ботах ничего не знает: он подбирает акторов и не решает, кем добирать состав

Что верно для любого бота.

ВсегдаЧто это
актор, а не игроку бота есть учётные данные актора, но нет провайдера входа, привязанных аккаунтов и сессий
экономическое владениеотсутствует: ни прав владения, ни покупок, ни записей в таблицах лидеров (leaderboard)
восприятиетакое же, как у игрока: та же зона видимости, тот же предикат, тот же лимит на число объектов. Бот и игрок в одной и той же позиции получают один и тот же набор объектов, поэтому видеть сквозь стены бот может не больше, чем игрок
расширенное восприятиеэто свойство пресета актора, а не бота. Отладочный режим или режим «всевидящего тренера» объявляется пресетом с более широким предикатом
между решениямидействует последняя команда, а что с ней будет дальше, уже объявлено в типе движения для устаревшего ввода. Бот, чей мозг ещё думает, — тот же случай, что и игрок, у которого пропала сеть
вместимость комнатыучитывает бота: он занимает место наравне со всеми

Ошибки

  • Комната не принимает ботов — это конфликт, и повтор не поможет.
  • Лимит ботов исчерпан — это конфликт, а не forbidden: разрешение (permission) добавить бота у вас есть, просто комната заполнена. Повторить стоит, когда место освободится.
  • Команда боту от актора без нужного разрешения получает ответ forbidden, и повторять её бессмысленно.
  • Недоступность мозгов — не ошибка, а одно из трёх объявленных выше поведений.
  • Объявлено при деплое — и отклонено при деплое: бот, указанный владельцем записи в таблице лидеров, и отсутствующий тик мышления — это ошибки валидации при деплое, а не сюрпризы в живой комнате.

Ограничения

Конкретные числа появятся вместе с главой об ограничениях платформы, а поведение на каждом пределе зафиксировано уже сейчас.

  • Боты в комнате: добавление отклоняется как конфликт. Чтобы освободить место, существующих ботов никогда не удаляют.
  • Боты на проект: тот же конфликт.
  • Нижняя граница тика мышления: объявление с частотой выше этой границы отклоняется при деплое, потому что сетевой вызов на каждый тик нереален.
  • Частота команд для одного бота: ограничение частоты со сроком.
  • Срок ответа от мозгов: когда он истекает, включается объявленное поведение на случай недоступности.