Cette page n’est pas encore traduite : la version anglaise est affichée.
Bots
Un bot rejoint la partie comme un joueur ordinaire. Seul son cerveau vit ailleurs. Même session, même validation à l’entrée, mêmes règles, même ACL. La salle ne voit pas la différence, et c’est voulu : les bots éprouvent vos vraies règles de jeu, et l’anti-triche n’a jamais besoin d’exception pour eux.
Quand l’utiliser
- Vos lobbies doivent se remplir aux heures creuses :
FillRoomcomplète les matchs jusqu’à un quota, et les bots cèdent leur place (seat) à mesure que des humains arrivent. - Les bots doivent jouer selon les vraies règles (validation à l’entrée, ACL, visibilité), pour que l’anti-triche n’ait jamais besoin d’exception pour eux.
- Vous apportez un cerveau (brain) externe, par exemple une politique apprise ou un service, qui se connecte via
ConnectAsBotcomme n’importe quel joueur. - L’entité d’un joueur déconnecté doit passer à un bot puis lui revenir à la reconnexion, sans que la place ni la prédiction s’en aperçoivent.
- Inutile quand le personnage ne décide jamais rien. Un PNJ de dialogue sans cerveau relève des objets du monde.
Cette page explique comment connecter un bot : le faire entrer dans une salle, remplir un lobby jusqu’au quota, faire passer une place d’un bot à un humain et inversement. Écrire le cerveau qui décide, c’est l’objet de Écrire le cerveau d’un bot.
Qui fait quoi
| Acteur | Ce qu’il fait sur cette page |
|---|---|
bot-brain | se connecte comme un joueur ; reçoit la perception ; envoie des commandes |
room-owner | déclare les profils, remplit les salles (room) jusqu’au quota, fait passer la main entre bot et humain |
En bref
filler : des valeurs de difficulté, un cerveau de type utility, et FillRoom jusqu’à un quota[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)La déclaration par attributs en Go n’est pas encore tranchée (O-1). Les exemples arriveront quand la forme d’écriture sera choisie.
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.[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);Un cerveau externe (une IA plus lourde, une politique apprise, un service) se connecte comme n’importe quel joueur :
ConnectAsBot fait entrer un cerveau externe comme un joueur : mêmes deltas en entrée, mêmes entrées en sortievar 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 inputsconst 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 inputsbot = 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 inputsLa déclaration par attributs en Go n’est pas encore tranchée (O-1). Les exemples arriveront quand la forme d’écriture sera choisie.
// 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.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 inputsLe modèle
Un bot n’introduit aucune notion qui lui soit propre. C’est un identifiant d’acteur qui endosse ce que déclarent déjà les salles, les données et le déplacement.
Ce que porte la déclaration d’un bot.
| Déclare | Ce que c’est |
|---|---|
tick de réflexion | la fréquence à laquelle on interroge les cerveaux, et ce n’est pas le tick de simulation : les cerveaux tournent à l’extérieur, et un appel réseau à chaque tick est irréalisable |
emplacement des cerveaux | l’endroit où ils s’exécutent — une fonction cloud, le backend du studio, son serveur de jeu. Lequel ne fait pas partie du contrat, et passer de l’un à l’autre n’est pas un changement cassant |
preset d'acteur | les droits du bot, sous forme de preset d’acteur ordinaire |
visibilité du marqueur de bot | si les participants en sont informés. Le marqueur lui-même existe toujours et la plateforme l’observe toujours ; que les joueurs le voient relève de la déclaration du type de salle, car sur certains marchés signaler un adversaire IA est une obligation, et sur d’autres un choix produit |
comportement quand les cerveaux sont indisponibles | l’un de trois, sans valeur par défaut : do nothing · leave the room · fall back to built-in default behaviour |
remplissage de la composition | déclaré par le type de salle — combien, à quelle condition, jusqu’à quel moment. Le matchmaking ne sait rien des bots : il apparie des acteurs, et ne décide pas avec qui compléter |
Ce qui vaut pour chaque bot.
| Toujours | Ce que c’est |
|---|---|
un acteur, pas un joueur | il détient un identifiant d’acteur, mais n’a ni fournisseur de connexion, ni comptes liés, ni sessions |
propriété économique | aucune : ni droits d’usage (entitlement), ni achats, ni entrées de classement |
perception | c’est celle d’un joueur : même zone de visibilité, même prédicat, même limite du nombre d’objets. Un bot et un joueur à la même position reçoivent le même ensemble d’objets : un bot ne peut donc pas plus tricher au wallhack qu’un joueur |
perception élargie | c’est un preset d’acteur, pas une propriété du bot : un mode debug ou « coach omniscient » se déclare comme un preset au prédicat plus large |
entre deux réflexions | la dernière commande s’applique, et son sort est celui que le type de mouvement déclare déjà pour une entrée périmée — un bot dont le cerveau réfléchit, c’est le même cas qu’un joueur dont le réseau a lâché |
capacité de la salle | elle compte le bot : il occupe une place comme n’importe qui |
Erreurs
- La salle n’accepte pas de bots : c’est un conflit, et réessayer n’y changera rien.
- La limite de bots est atteinte : c’est un conflit, pas forbidden — la permission d’introduire un bot est bien là ; c’est la salle qui est pleine. Réessayez une fois la salle libérée.
- Une commande pour un bot venant d’un acteur sans la permission : la réponse est forbidden, et réessayer ne sert à rien.
- L’indisponibilité des cerveaux n’est pas une erreur — c’est l’un des trois comportements déclarés ci-dessus.
- Déclaré au déploiement, refusé au déploiement : un bot désigné comme propriétaire d’une entrée de classement et un tick de réflexion manquant sont tous deux des échecs de validation au déploiement, et non des surprises dans une salle en cours.
Limites
Les chiffres arriveront avec le chapitre sur les limites de la plateforme ; ce qui se passe à chaque plafond est déjà fixé.
- Bots dans une salle : l’introduction est refusée comme un conflit ; on ne retire jamais des bots existants pour faire de la place.
- Bots par projet : le même conflit.
- Tick de réflexion, borne inférieure : une déclaration plus rapide que le plancher est refusée au déploiement, car un appel réseau par tick n’est pas réaliste.
- Fréquence des commandes pour un bot : une limitation de débit, avec une échéance.
- Délai de réponse des cerveaux : une fois expiré, le comportement d’indisponibilité déclaré s’applique.