Points, zones et entités de map
Dans l'éditeur de map, place des repères nommés (spawns d'équipe, objectifs, butin), des zones qui préviennent le Lua quand on y entre, et des entités scriptées avec leurs propriétés. Le Lua du gamemode les retrouve par leur nom ou leur catégorie.
Dans l'éditeur de map
Panneau de gauche, onglet Entités : trois boutons s'ajoutent à Point d'apparition, Lumière, Spot et Volume d'eau.
| Bouton | C'est quoi | Dans l'éditeur | En jeu |
|---|---|---|---|
| Point nommé | Un repère (nom, position, orientation, catégorie facultative) lu par le Lua : spawns d'équipe, objectifs, points de butin… | Petit losange rose, flèche vers l'avant (-Z) et étiquette « nom [catégorie] » | Invisible, rien n'est créé : le Lua lit le point avec map.GetPoint / map.GetPoints |
| Zone déclencheur | Une boîte (position, rotation, taille) qui prévient le Lua quand un joueur ou un prop entre ou sort | Boîte orange semi-transparente et étiquette | Invisible ; côté serveur, appelle les hooks TriggerStartTouch / TriggerEndTouch |
| Entité scriptée | Une entité Lua (entities/<classe>.lua) créée au lancement de la map, avec des propriétés clé = valeur | Petit cube violet, flèche et étiquette « nom / classe » (l'éditeur n'exécute pas le Lua, il ne montre pas le modèle de la classe) | Créée comme ents.Create(classe) + Spawn() |
- Sous les boutons, la liste Classes d'entités (gamemodes et addons) montre toutes les classes trouvées : fichiers
entities/<classe>.luade tous les gamemodes installés etlua/entities/<classe>.luades addons activés. Cliquer une classe démarre la pose d'une entité de cette classe (son nom = la classe). - Case Afficher les zones déclencheurs : décochée, les zones sont cachées et on clique à travers (elles restent dans la liste des objets à droite, sélectionnables par la liste). Même cochée, la pose d'un objet et le collage (Ctrl+V) traversent les zones : un bloc posé dans une zone se pose sur le sol, pas sur la boîte.
- Comme les autres objets : clic pour poser (Maj + clic pour en poser plusieurs), sélection, déplacer / tourner (W / E), Échelle (R) pour redimensionner une zone, dupliquer, copier / coller, supprimer, annuler / rétablir, et édition à plusieurs.
Inspecteur
| Objet | Réglages |
|---|---|
| Tous | Nom, Position, Rotation |
| Point nommé | Catégorie (tag) : texte libre, 64 caractères au plus, peut rester vide |
| Zone déclencheur | Taille (0,1 à 2000 m par axe), Catégorie (tag) |
| Entité scriptée | Classe (saisie libre, ou menu « Choisir une classe… » ; un avertissement s'affiche si la classe n'est pas trouvée ici, car elle doit exister là où la map sera jouée) et Propriétés : lignes « clé | valeur », « + Ajouter une propriété » et « × » pour supprimer (32 propriétés au plus, clé de 64 caractères, valeur de 256 ; une clé vide est ignorée ; tout est du texte) |
map.GetPoint("objectif_a"), ents.FindByName("porte_1")). Plusieurs objets peuvent porter le même nom, mais pour un groupe (dix points « loot », les spawns d'une équipe), utilise plutôt une catégorie.Dans le fichier .pcmap
Le format reste en version 1 : une map ancienne s'ouvre telle quelle, et une version plus ancienne de PhysiCore ignore simplement ces objets (les types inconnus sont écartés). Champs communs : id, type, name, pos [x, y, z], rot [x, y, z] (degrés, ordre YXZ).
{ "id": "1a2b3c4d5e6f7a8b", "type": "point", "name": "spawn_rouge", "pos": [12, 0, -4], "rot": [0, 90, 0],
"tag": "spawn_rouge" }
{ "id": "9f8e7d6c5b4a3f2e", "type": "trigger", "name": "zone_capture", "pos": [0, 1.5, 0], "rot": [0, 0, 0],
"size": [6, 3, 6], "tag": "" }
{ "id": "0011223344556677", "type": "entity", "name": "distrib_hall", "pos": [3, 0, 2], "rot": [0, 180, 0],
"class": "distributeur", "keyvalues": { "stock": "10", "objet": "builtin:ball" } }
| Champ | Vérification à la lecture (et à chaque modification en édition à plusieurs) |
|---|---|
name, tag | Texte, 64 caractères au plus |
size | 0,1 à 2000 par axe (défaut [4, 3, 4]) |
class | Texte de 64 caractères au plus, nom de fichier valide (sinon la valeur précédente est gardée). Vide autorisé : l'entité n'est alors pas créée en jeu (message d'erreur) |
keyvalues | Objet { "clé": "valeur" }, 32 paires au plus, clés de 64 caractères (vides ignorées), valeurs converties en texte (256 caractères ; un nombre entier 3 devient "3") |
API Lua
Disponible des deux côtés : les points et zones viennent du fichier de la map, que le serveur envoie à chaque joueur. Seuls les hooks et la détection exacte de zone:GetTouching() sont côté serveur. Sur une map intégrée (pas un .pcmap), ces fonctions renvoient des listes vides ou nil.
Initialize, map.GetPoints() est encore vide. Utilise le hook InitPostEntity, appelé une fois la map chargée et ses entités créées. Côté client, la map est déjà reçue quand les scripts démarrent chez un joueur qui rejoint, mais pas forcément chez l'hôte : lis-la plutôt dans un hook ou au moment d'en avoir besoin.map.GetPoints(tag) → table
Liste (indexée à partir de 1) des points nommés, triés par nom. Avec tag, seulement ceux de cette catégorie. Chaque point est une table { name = "…", pos = Vector, ang = Vector (degrés), tag = "…" } (une copie : la modifier ne change pas la map).
map.GetPoint(nom) → table ou nil
Le premier point (dans l'ordre des noms) qui porte ce nom.
map.GetTriggers(tag) → table
Liste des zones, triées par nom ; filtrées par catégorie si tag est donné.
map.GetTrigger(nom) → zone ou nil
La première zone de ce nom.
map.IsInTrigger(nom, pos) → booléen
true si pos est dans au moins une zone portant ce nom (boîte orientée, bords inclus).
ents.FindByName(nom) → table
Les entités scriptées placées dans la map dont le nom (dans l'éditeur) est nom, puis les zones de ce nom. Vide si le nom est vide. Les props et entités créés par le Lua n'ont pas de nom.
Objet zone
Renvoyé par map.GetTriggers, map.GetTrigger et ents.FindByName, et premier argument des hooks de zone. La même zone donne toujours la même table (tu peux y ranger tes champs : zone.equipe = 2), tant que les scripts ne sont pas rechargés.
| Méthode | Renvoie |
|---|---|
zone:IsValid() | true tant que la zone existe et que les scripts tournent (IsValid(zone) marche aussi) |
zone:GetName(), zone:GetTag() | Nom, catégorie |
zone:GetPos(), zone:GetAngles() | Centre de la boîte, rotation en degrés |
zone:GetSize() | Taille de la boîte (Vector, mètres) |
zone:IsInside(pos) | true si le point est dans la boîte |
zone:GetTouching() | Joueurs et props dedans. Serveur : ce que la zone détecte (corps qui la touchent, mêmes règles que les hooks). Client : calcul par position (pieds du joueur, centre du prop) |
zone:IsTrigger() → true, zone:IsPlayer() → false, zone:GetClass() → "trigger" | Pour distinguer une zone d'une entité |
Propriétés des entités placées
| Méthode | Côté | Description |
|---|---|---|
ent:GetName() | partagé | Nom donné dans l'éditeur à une entité placée dans la map ; "" sinon |
ent:GetKeyValue(clé) | partagé | Valeur (texte) d'une propriété de l'éditeur, ou nil |
ent:GetKeyValues() | partagé | Table { clé = "valeur" } de toutes les propriétés |
ent:SetKeyValue(clé, valeur) | serveur | Change ou ajoute une propriété (convertie en texte, 256 caractères, 32 propriétés au plus), visible par tous, puis appelle ENT:KeyValue(clé, valeur). Seulement sur une entité déjà créée (pas entre ents.Create et Spawn) |
ENT:KeyValue(clé, valeur) (fonction de classe, serveur) est appelée pour chaque propriété de l'éditeur avant ENT:Initialize(), comme dans Garry's Mod, puis à chaque ent:SetKeyValue. valeur est toujours du texte : convertis-la avec tonumber.
Hooks
Côté serveur. Comme les autres hooks : hook.Add("TriggerStartTouch", "id", fonction) ou function GM:TriggerStartTouch(zone, ent).
| Hook | Quand |
|---|---|
TriggerStartTouch(zone, ent) | Un joueur ou un prop (véhicules et entités scriptées compris) entre dans la zone. ent est un joueur ou une entité (ent:IsPlayer()) |
TriggerEndTouch(zone, ent) | Il en sort (aussi quand le prop est supprimé) |
InitPostEntity() | Une fois par lancement des scripts, quand la map est chargée et ses props et entités placées créés (aussi sur une map intégrée) |
PostCleanupMap() | Après game.CleanUpMap(), quand les props et entités de la map ont été recréés |
Création des entités placées
Au lancement de la map (et après game.CleanUpMap()), pour chaque entité scriptée placée, côté serveur :
- la classe doit exister (fichier chargé), sinon un message s'affiche dans la console Lua (« Map : classe d'entité inconnue… ») ; une entité sans classe donne aussi un message ;
- le prop est créé avec
ENT.Model(petite caisse par défaut) à la position et la rotation de l'éditeur, figé siENT.Physics = "static"; le hookPropSpawnedest appelé ; ENT:KeyValue(clé, valeur)pour chaque propriété, dans l'ordre de l'éditeur ;ENT:Initialize().
Les zones ne sont pas recréées par game.CleanUpMap() : elles restent, avec ce qu'elles contiennent.
Exemples
Spawns d'équipe
-- gamemodes/mon_mode/init.lua : chaque équipe apparaît sur ses points.
hook.Add("PlayerSpawn", "SpawnEquipe", function(ply)
local tag = ply:Team() == 1 and "spawn_rouge" or "spawn_bleu"
local points = map.GetPoints(tag)
if #points > 0 then
local p = points[math.random(#points)]
ply:SetPos(p.pos)
end
end)
Zone de capture
hook.Add("TriggerStartTouch", "Capture", function(zone, ent)
if zone:GetName() == "zone_capture" and ent:IsPlayer() then
PrintMessage(ent:Nick() .. " entre dans la zone de capture !")
end
end)
hook.Add("TriggerEndTouch", "Capture", function(zone, ent)
if zone:GetName() == "zone_capture" and ent:IsPlayer() then
ent:ChatPrint("Tu quittes la zone.")
end
end)
-- Un point par seconde passée dans la zone.
timer.Create("CaptureScore", 1, 0, function()
local zone = map.GetTrigger("zone_capture")
if not zone then return end
for _, ent in ipairs(zone:GetTouching()) do
if ent:IsPlayer() then ent:AddFrags(1) end
end
end)
Lave
-- Catégorie « lave » sur plusieurs zones.
hook.Add("TriggerStartTouch", "Lave", function(zone, ent)
if zone:GetTag() == "lave" then
if ent:IsPlayer() then ent:Kill() else ent:Remove() end
end
end)
Entité réglée dans l'éditeur
-- entities/distributeur.lua : propriétés stock = 10, objet = builtin:ball dans l'éditeur.
ENT.Model = "builtin:crate_metal"
ENT.Physics = "static"
if SERVER then
function ENT:KeyValue(key, value)
if key == "stock" then self.stock = tonumber(value) or 0 end
end
function ENT:Initialize()
self.stock = self.stock or 5
self:SetNWInt("stock", self.stock)
end
function ENT:Use(ply)
if self.stock <= 0 then return end
self.stock = self.stock - 1
self:SetNWInt("stock", self.stock)
ents.SpawnProp(self:GetKeyValue("objet") or "builtin:ball", self:GetPos() + Vector(0, 1, 0))
end
end
Préparer la map
hook.Add("InitPostEntity", "PrepareMap", function()
local objectif = map.GetPoint("objectif_a")
if not objectif then print("Cette map n'a pas de point « objectif_a ».") return end
for _, ent in ipairs(ents.FindByName("distrib_hall")) do
if not ent.IsTrigger then ent:SetKeyValue("stock", 20) end
end
end)
Zone sûre affichée au joueur (client)
hook.Add("HUDPaint", "ZoneSafe", function()
local me = LocalPlayer()
for _, zone in ipairs(map.GetTriggers("safe")) do
if zone:IsInside(me:GetPos()) then
draw.SimpleText("Zone sûre", "Big", ScrW() / 2, 40, Color(120, 255, 120), TEXT_ALIGN_CENTER)
return
end
end
end)
Limites
- Les zones détectent les joueurs (sauf en noclip ou morts, quand leur collision est coupée) et les props. Les props figés peuvent ne pas être détectés (Jolt ne signale pas forcément les corps statiques aux zones).
- Les zones et les points sont fixes : le Lua ne peut ni les déplacer ni en créer.
ents.FindByNamene connaît que les entités placées dans l'éditeur. Une copie (dupe) d'une entité placée garde son nom et ses propriétés.- L'éditeur ne montre pas le modèle d'une entité scriptée : un cube violet le remplace.
- La liste des classes de l'éditeur vient des fichiers installés sur l'ordinateur de celui qui édite ; en jeu, seules les classes du gamemode lancé et des addons activés du serveur existent.