Aller au contenu principal
Le jeu en direct

Cette page n’est pas encore traduite : la version anglaise est affichée.

Map

Le monde statique : limites, terrain, obstacles, et « où peut-on aller ? ». Le modèle physique est volontairement bien plus simple que le modèle visuel : des primitives dotées d’une emprise au sol et d’une hauteur, des couches assorties de règles, et une seule requête de position valide que tous les autres modules réutilisent.

Quand l’utiliser

  • Il vous faut un monde statique (limites, terrain, obstacles) que le serveur peut interroger, et pas seulement afficher.
  • Les apparitions (spawn), les butins et les décors doivent tomber à des endroits autorisés : une seule requête RandomPosition fondée sur des règles, sans contournement possible.
  • Les arènes doivent se régénérer à chaque match : une Seed déclarée reproduit la même carte (map) dans un rapport de bug.
  • Les caisses et les murs se brisent puis reviennent : des objets destructibles, avec des points de vie et des délais de réapparition.
  • Les bots et les projectiles ont besoin de raycasts et de tests de ligne de vue sur l’ensemble des obstacles.
  • Inutile quand le monde est purement visuel et qu’aucun code serveur ne demande où l’on peut aller.

Qui fait quoi

ActeurCe qu’il fait sur cette page
schema-authordéclare les cartes, les primitives d’obstacle, les objets destructibles, les couches et leurs règles
room-ownerassocie une carte à une salle (room) ; demande des positions d’apparition ; lance des raycasts
operatorplace ou retire des obstacles et des couches depuis le panneau

En bref

La disposition arena déclarée : graine et limites, terrain, rochers, caisses qui réapparaissent, une couche de règles
[Map("arena", Seed = 42, Bounds = "160x160")]
public static class Arena
{
    [Terrain(HeightNoise = 0.3f)]                 public static Terrain Height;  // 3D height field
    [Scatter("rock", Count = 40, MinSpacing = 6)] public static ObstacleSet Rocks;
    [Destructible("crate", Count = 12, Hp = 100, RespawnAfter = "30s")] public static ObstacleSet Crates;
    [Layer("ground", NotInside = "water")]        public static Layer Ground;
}
// or: Maps.Named("arena-caves-v3")  — authored in the panel or loaded from an asset
@Map('arena', { seed: 42, bounds: '160x160' })
export class Arena {
  @Terrain({ heightNoise: 0.3 }) height: Terrain;                   // 3D height field
  @Scatter('rock', { count: 40, minSpacing: 6 }) rocks: ObstacleSet;
  @Destructible('crate', { count: 12, hp: 100, respawnAfter: '30s' }) crates: ObstacleSet;
  @Layer('ground', { notInside: 'water' }) ground: Layer;
}
// or: Maps.named('arena-caves-v3')  — authored in the panel or loaded from an asset
@Map("arena", seed=42, bounds="160x160")
class Arena:
    height = terrain(height_noise=0.3)                  # 3D height field
    rocks = scatter("rock", count=40, min_spacing=6)
    crates = destructible("crate", count=12, hp=100, respawn_after="30s")
    ground = layer("ground", not_inside="water")
# or: maps.named("arena-caves-v3")  — authored in the panel or loaded from an asset
Bientôt — Go

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(PSMap = (Name = "arena", Seed = 42, Bounds = "160x160"))
struct FArena
{
    GENERATED_BODY()

    UPROPERTY(PSTerrain = (HeightNoise = "0.3"))                             FPSTerrain Height;  // 3D height field
    UPROPERTY(PSScatter = (Obstacle = "rock", Count = 40, MinSpacing = 6))   FPSObstacleSet Rocks;
    UPROPERTY(PSDestructible = (Obstacle = "crate", Count = 12, Hp = 100,
                                RespawnAfter = "30s"))                       FPSObstacleSet Crates;
    UPROPERTY(PSStratum = (Name = "ground", NotInside = "water"))            FPSStratum Ground;
};
// or: PS::Maps::Named(TEXT("arena-caves-v3")) — authored in the panel or loaded from an asset
// declarations compile into the same pushed model — playserv push from the UE project or CI
Unity C# is the same C# API here — les mêmes attributs compilent dans Unity, sur la base 2021.3 — aucune syntaxe C# 12 ici
[Map("arena", Seed = 42, Bounds = "160x160")]
public static class Arena
{
    [Terrain(HeightNoise = 0.3f)]                 public static Terrain Height;  // 3D height field
    [Scatter("rock", Count = 40, MinSpacing = 6)] public static ObstacleSet Rocks;
    [Destructible("crate", Count = 12, Hp = 100, RespawnAfter = "30s")] public static ObstacleSet Crates;
    [Layer("ground", NotInside = "water")]        public static Layer Ground;
}
// or: Maps.Named("arena-caves-v3")  — authored in the panel or loaded from an asset

Scatter et Destructible sont des générateurs de placement, pas des tirages au runtime. Un générateur est résolu au moment où la version de la carte est publiée : les quarante rochers deviennent quarante primitives déclarées, et la version publiée contient les primitives, pas la règle. La même Seed donne donc les mêmes quarante rochers dans le match, dans le replay et dans le rapport de bug — et les limites de géométrie sont vérifiées une seule fois, sur cet ensemble résolu, avant que la version n’atteigne un environnement.

La requête que tout le reste utilise :

RandomPosition : une apparition équitable, au sol, loin des joueurs, sans répétition
var spawn = map.RandomPosition(r =>
{
    r.Layer("ground");
    r.AwayFrom(players, minDistance: 12);
    r.NoRepeat(lastN: 3);
});
const spawn = map.randomPosition((r) => {
  r.layer('ground');
  r.awayFrom(players, { minDistance: 12 });
  r.noRepeat({ lastN: 3 });
});
spawn = map.random_position(rules=lambda r: (
    r.layer("ground"),
    r.away_from(players, min_distance=12),
    r.no_repeat(last_n=3),
))
Bientôt — Go

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.

// Dedicated-server host: place a spawn through the same rule-based query
Map->Positions->GetRandom({ .Stratum = PSKeys::Strata::Ground,
                            .AwayFrom = Players,
                            .MinDistance = 12.f,
                            .NoRepeatLastN = 3 },
    TPSOnResult<FVector>::CreateLambda([](const TPSResult<FVector>& Result)
    {
        if (!Result.HasValue()) { return; }
        PlaceSpawn(Result.Value());
    }));
// 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 — un build client maître exécute la même requête ; un client ordinaire apparaît à la position obtenue
var spawn = map.RandomPosition(r =>
{
    r.Layer("ground");
    r.AwayFrom(players, minDistance: 12);
    r.NoRepeat(lastN: 3);
});

Le modèle

static layer — the published map versionone map instancea query — RandomPosition, a raycastan answer, true for a named momentdynamic obstacles brought by entities shared, immutable geometrythis instance only
Une requête lit la couche statique publiée et les obstacles dynamiques d’une instance ; la réponse vaut pour un instant précis

Deux couches, déclarées par des personnes différentes.

CoucheCe qu’elle contient, et qui la déclare
statiqueterrain avec relief, primitives d’obstacle, limites et lieux — du contenu créé par des auteurs
dynamiquedes obstacles apportés par des entités au runtime : portes, objets destructibles, plateformes. Un objet destructible est donc une entité avec un cycle de vie, des états et un propriétaire, et il ne relève de la carte que pour l’obstacle qu’il apporte : un rocher statique est déclaré dans la carte, une porte est une entité qui apporte un obstacle. Ces obstacles n’ont pas de cycle de vie propre ici : il appartient à l’entité

Ce que déclare une carte.

DéclareCe que c’est
key et versionune carte est du contenu d’auteur : déclarée dans le code, désignée par sa key et versionnée — la version fait partie de ce qu’une salle référence. Modifier la géométrie d’une version publiée est interdit ; toute modification donne une nouvelle version
terrainun champ de hauteurs — une grille régulière au pas déclaré, et ce pas est une limite de précision déclarée : une requête de hauteur répond à ce pas près, pas exactement. Le terrain peut être absent : une arène dans le vide est parfaitement valide
obstaclesun ensemble fermé de primitives — boîte, sphère, capsule, enveloppe convexe avec une limite de sommets déclarée. Un maillage de triangles arbitraire n’est pas accepté : c’est à cette condition qu’une vérification côté serveur est seulement possible
type de franchissabilité par obstacleinfranchissable · franchissable pour une classe déclarée · bloque seulement la ligne de vue. Une même primitive sert de mur comme de buisson, et la différence est déclarée au lieu d’être modélisée deux fois
limitesle volume hors duquel une position n’est pas admise
strates du mondedes strates spatiales déclarées à l’intérieur d’une carte — sol, souterrain, air. Ce sont des déclarations de géométrie et d’adressage
lieuxdes emplacements ou des zones nommés — un point d’apparition, une zone de capture, un couloir. Un lieu répond à la question où, jamais à ce qui s’y passe : il ne porte aucune logique de jeu
générateur de placementfacultatif : une règle qui produit des primitives — un nombre, un espacement minimal, une zone, une graine. Il est résolu à la publication d’une version, de façon déterministe d’après la graine, et la carte contient ensuite des primitives, pas une règle

Une strate du monde et une instance de carte ne sont jamais synonymes.

Ce que c’est
strate du mondeune déclaration à l’intérieur de la carte — sol, souterrain, air
instance de carteune copie au runtime, indépendante, de la carte publiée. Les instances partagent la géométrie publiée, immuable, mais chacune a ses propres obstacles dynamiques et sa propre liste d’entités. Une salle occupe une instance et peut y sélectionner des strates

Ce qui vaut pour toute requête.

ToujoursCe que c’est
un seul référentiel géométriquetoute la géométrie s’exprime dans le référentiel de coordonnées déclaré par la plateforme, et la précision de chaque champ géométrique est déclarée sur le champ lui-même
le modèle du mondeest une simplification : la géométrie du serveur n’est pas le modèle graphique, et n’a pas à l’être
une réponse nomme son instance et son instantla réponse à une requête s’appuie sur la couche statique de la carte et sur les obstacles dynamiques de l’instance visée, et elle déclare l’instant auquel elle est vraie : les obstacles dynamiques changent, la réponse est donc un snapshot
les valeurssont managed, pas seed : la géométrie ne se règle pas au jour le jour, si bien qu’une modification depuis la console d’administration est refusée au lieu d’être conservée en silence
le mouvement et le contactne sont pas résolus ici : la carte dit ce qu’est l’espace ; savoir si une position est admissible et quelle réaction s’ensuit relève des collisions, et l’appliquer relève du déplacement

Erreurs

  • Une carte, une version ou une instance introuvable répond not found, tout comme une version retirée : inutile de réessayer.
  • Publier une géométrie modifiée sous une version existante est un conflit : créez une nouvelle version.
  • Les échecs de publication surviennent à la déclaration ou au déploiement, jamais au runtime — une carte qui dépasse la limite de primitives, une enveloppe convexe qui dépasse sa limite de sommets ou un maillage arbitraire utilisé comme obstacle donnent tous lieu à un refus de validation, avant toute mise en production.
  • Une requête de hauteur hors des limites n’est pas une erreur : c’est la réponse déclarée « hors des limites », et elle se distingue de « à l’intérieur d’un obstacle », car dans un cas le client fait demi-tour et dans l’autre il contourne l’obstacle.
  • Un rythme de requêtes dépassé répond dans la catégorie limite de débit, avec une échéance.

Limites

Les chiffres viendront avec le chapitre sur les limites de la plateforme ; ce qui se passe à chaque plafond, lui, est déjà fixé.

  • Primitives d’obstacle par carte, sommets d’enveloppe convexe, résolution du champ de hauteurs, taille des limites, lieux par carte : chaque dépassement est refusé à la publication, pas au moment de la requête.
  • Instances par carte : la création d’une instance de plus est refusée comme conflit ; les instances existantes ne sont jamais libérées pour faire de la place.
  • Versions conservées : la plus ancienne version en cours de dépréciation est retirée ; une version qui sert une salle active ne l’est jamais.
  • Le rythme des requêtes spatiales : une limite de débit, avec une échéance.