Monde et physique

Angles et vecteurs, traces (rayons), repères et physique des entités (forme de collision, frottement, rebond, collisions au cas par cas), attacher un prop à un autre, réagir aux chocs, chercher des entités, créer des contraintes (soudures, cordes, treuils, vérins, moteurs…), explosions, date et heure, changement de map.

UnitésComme partout dans PhysiCore : positions en mètres, Y vers le haut, angles en degrés. L'avant d'un objet est l'axe -Z.

Angles et vecteurs

Un angle est un Vector(pitch, yaw, roll) en degrés ; Angle(p, y, r) le crée. C'est le format renvoyé par ent:GetAngles() et ply:EyeAngles(). Convention du jeu : avant = -Z, Y en haut, pitch positif = vers le haut, yaw positif = vers la gauche (rotation autour de Y).

AngleRésultat
ang.p / ang.pitch, ang.y / ang.yaw, ang.r / ang.rollLes composantes (= x, y, z)
ang:Forward(), ang:Right(), ang:Up()Vecteurs unitaires (avant, droite, haut)
ang:RotateAroundAxis(axe, degrés)Nouvel angle tourné autour de l'axe (un Vector)
vec:Angle()Angle qui regarde dans la direction du vecteur (roll 0)
vec:Rotate(ang)Nouveau vecteur tourné par l'angle

Les vecteurs ont aussi les noms de Garry's Mod : :Length(), :LengthSqr(), :Length2D() (sur le sol, x et z), :Distance(v), :DistToSqr(v), :Dot(v), :Cross(v), :GetNormalized(), :IsZero(), :IsEqualTol(v, tolérance), :WithinAABox(min, max). Les méthodes Godot (length(), normalized(), distance_to()…) restent disponibles. Un vecteur ne se modifie jamais « en place » : chaque méthode renvoie un nouveau vecteur.

local ang = ply:EyeAngles()
local devant = ply:EyePos() + ang:Forward() * 2 -- 2 m devant les yeux
local cap = (cible - ply:GetPos()):Angle().y     -- yaw pour regarder la cible

Aides mathématiques

FonctionRôle
Lerp(t, a, b), LerpVector(t, a, b)Mélange de a (t = 0) à b (t = 1)
LerpAngle(t, a, b)Pareil pour des angles, par le plus court chemin
math.Clamp(v, min, max), math.Round(v, décimales)Borne une valeur, arrondit
math.Approach(actuel, cible, pas)Avance vers la cible d'au plus pas
math.Remap(v, a, b, c, d)Passe v de l'intervalle a–b à c–d
math.Distance(x1, y1, x2, y2)Distance entre deux points 2D
math.NormalizeAngle(a)Ramène un angle entre -180 et 180
VectorRand(min, max), AngleRand()Vecteur (composantes entre -1 et 1 par défaut) et angle au hasard

Traces

Une trace lance un rayon (ou une boîte) entre deux points et dit ce qu'il touche. Des deux côtés.

util.TraceLine(options) → table

Options : start, endpos (des Vector), filter, mask, ignoreworld. Longueur 20 000 m au plus. Résultat :

ChampValeur
Hittrue si le rayon a touché quelque chose
HitPos, HitNormalPoint touché, orientation de la surface
FractionPart du trajet parcourue (0 à 1)
EntityProp ou joueur touché, sinon nil
HitWorld, HitNonWorldTouché la map / un prop ou un joueur
HitSkyToujours false
StartPos, NormalDépart, direction du rayon
StartSolidDéjà bloqué au départ
-- Le prop sous le viseur, en ignorant le joueur.
local tr = util.TraceLine({ start = ply:EyePos(), endpos = ply:EyePos() + ply:GetAimVector() * 10, filter = ply })
if tr.Hit and IsValid(tr.Entity) then print("Je vise", tr.Entity:GetModel()) end

util.TraceHull(options) → table

Comme TraceLine, mais avec une boîte : mins et maxs (autour du point) en plus. StartSolid vaut true si la boîte est déjà bloquée au départ (pratique pour vérifier qu'un joueur tient à un endroit).

Autres aides

Filtres et masques

filter : une entité ou un joueur (ignoré), une table de ceux-ci, ou une fonction function(ent) return true end (true = on touche cet objet, false = on passe à travers ; le monde n'est jamais filtré ; 16 objets traversés au plus).

MasqueCe qui est touché
MASK_SOLID (défaut), MASK_SHOTMonde, joueurs, props, ragdolls
MASK_PLAYERSOLIDMonde, joueurs, props
MASK_SOLID_BRUSHONLY, MASK_VISIBLELe monde seul
MASK_ALLTout, y compris les props « débris » (voir groupes de collision)

ignoreworld = true retire le monde.

Repères des entités

Ces méthodes existent des deux côtés, sur les props, les entités scriptées et les véhicules.

MéthodeRenvoie
ent:GetForward(), ent:GetRight(), ent:GetUp()Directions de l'entité (vecteurs unitaires)
ent:LocalToWorld(v), ent:WorldToLocal(v)Position convertie entre le repère de l'entité et le monde
ent:LocalToWorldAngles(ang), ent:WorldToLocalAngles(ang)Pareil pour un angle
ent:OBBMins(), ent:OBBMaxs(), ent:OBBCenter()Boîte du modèle dans son repère
ent:BoundingRadius()Rayon de la sphère qui l'englobe
ent:GetVelocity()Vitesse en m/s (serveur : exacte ; client : estimée d'après le mouvement)
ent:GetAngularVelocity()Vitesse de rotation en degrés/s (serveur)
ent:GetCollisionGroup(), ent:IsSolid(), ent:GetNoDraw(), ent:GetMaterial(), ent:GetModelScale(), ent:GetParent()Réglages décrits plus bas
-- Une caisse 1 m devant et 0,5 m au-dessus d'un prop, tournée comme lui.
local pos = prop:LocalToWorld(Vector(0, 0.5, -1))
local caisse = ents.SpawnProp("builtin:crate", pos)
caisse:SetAngles(prop:GetAngles())

Physique

Vitesse et forces serveur

MéthodeEffet
ent:SetVelocity(v)Remplace la vitesse (m/s, 500 au plus)
ent:SetAngularVelocity(v)Vitesse de rotation (degrés/s)
ent:ApplyForce(impulsion)Pousse au centre (N·s)
ent:ApplyForceOffset(impulsion, posMonde)Pousse à un point du monde (fait tourner)
ent:ApplyTorque(impulsion) (alias ApplyTorqueCenter)Impulsion de rotation
ent:SetGravityEnabled(bool)Active ou coupe la gravité
ent:SetOwner(ply ou nil)Change le propriétaire (GetOwner, nettoyage, limites)

Objet physique

ent:GetPhysicsObject() renvoie un objet à la manière de Garry's Mod, pour reprendre du code existant. Ses méthodes sont des raccourcis vers celles de l'entité ; les modifications n'existent que côté serveur.

Lecture (partagé)Modification (serveur)
:IsValid(), :GetEntity(), :GetMass(), :GetVelocity(), :GetAngleVelocity(), :GetPos(), :GetAngles(), :IsMotionEnabled(), :GetMaterial(), :GetDamping(), :IsAsleep():SetMass(kg), :SetVelocity(v), :AddVelocity(v), :SetAngleVelocity(v), :AddAngleVelocity(v), :ApplyForceCenter(impulsion), :ApplyForceOffset(impulsion, pos), :ApplyTorqueCenter(couple), :EnableMotion(bool) (libère / fige), :EnableGravity(bool), :EnableCollisions(bool), :Wake(), :Sleep(), :SetPos(v), :SetAngles(ang), :SetMaterial(nom), :SetDamping(linéaire, angulaire), :EnableDrag(bool), :SetBuoyancyRatio(r)

Matériau, freinage, flottaison et repos :

MéthodeEffet
:SetMaterial(nom) / :GetMaterial()Matériau physique d'après un nom de surface de Garry's Mod : un nom qui contient ice glisse (plastique, frottement 0,03), bouncy rebondit (caoutchouc, rebond 0,9), rubber donne du caoutchouc, metal / chain / grate du métal, wood du bois, concrete / brick / rock / stone / gravel / dirt du béton ; le reste, du plastique. Remet à zéro SetFriction / SetElasticity. GetMaterial renvoie le matériau de PhysiCore ("plastic", "metal", "wood", "concrete", "rubber"…). Ce n'est pas l'apparence (ent:SetMaterial).
:SetDamping(linéaire, angulaire) / :GetDamping()Freinage dans l'air du déplacement et de la rotation (0 à 100, 0 par défaut). GetDamping renvoie les deux valeurs.
:EnableDrag(bool)true = freinage de 0,1 / 0,1 ; false = aucun.
:SetBuoyancyRatio(r)Flottaison dans l'eau, de 0 (coule) à 1 (flotte très haut) ; vers 0,5 l'objet reste entre deux eaux. Sans appel, elle dépend de la densité du modèle.
:IsAsleep(), :Sleep(), :Wake()Le prop est-il au repos (immobile ou figé) ? Le met au repos / le réveille (sans effet sur un prop figé).

Les lectures (GetDamping, IsAsleep, ent:GetFriction…) sont exactes côté serveur, qui simule les props.

-- /saut : envoie le prop visé en l'air en le faisant tourner.
concommand.Add("saut", function(ply)
	local ent = ply:GetEyeTrace().Entity
	if not IsValid(ent) or ent:IsPlayer() then return end
	local phys = ent:GetPhysicsObject()
	phys:EnableMotion(true)
	phys:AddVelocity(Vector(0, 8, 0))
	phys:AddAngleVelocity(Vector(0, 360, 0))
end)

Collisions et apparence serveur

MéthodeEffet
ent:SetCollisionGroup(groupe)COLLISION_GROUP_NONE : normal. COLLISION_GROUP_DEBRIS, DEBRIS_TRIGGER, WORLD : ne touche que la map. COLLISION_GROUP_INTERACTIVE_DEBRIS, WEAPON, PASSABLE_DOOR : touche la map et les props, les joueurs passent au travers. Les traces par défaut (MASK_SOLID) ne voient plus ces props ; MASK_ALL oui.
ent:SetNotSolid(bool)Plus aucune collision (le prop tombe à travers le sol s'il n'est pas figé)
ent:SetNoDraw(bool)Invisible (garde ses collisions). Pour la transparence : ent:SetColor(Color(r, g, b, alpha)) avec alpha < 255.
ent:SetMaterial(id)Matériau de l'outil Matériau ("metal", "chrome", "gold"… la liste s'affiche dans l'erreur si l'id est inconnu) ; "" revient au matériau d'origine
ent:SetModelScale(échelle)Taille de 0,05 à 20, modèle et collisions ensemble, instantané
-- Un fantôme : transparent, doré, les joueurs passent au travers.
local f = ents.SpawnProp("builtin:barrel", Vector(0, 1, 0))
f:SetMaterial("gold")
f:SetColor(Color(255, 255, 255, 120))
f:SetCollisionGroup(COLLISION_GROUP_INTERACTIVE_DEBRIS)
f:SetModelScale(1.5)

Forme et solidité serveur

Par défaut, une entité collisionne avec les formes de son modèle. Ces méthodes la remplacent par une forme simple ou la rendent traversable. Le changement s'applique chez tous les joueurs (ils butent aussi sur la nouvelle forme). Unités : mètres, dans le repère de l'entité.

MéthodeEffet
ent:SetSolid(SOLID_…)SOLID_NONE : plus aucune collision (comme SetNotSolid(true)). SOLID_BBOX ou SOLID_OBB : une seule boîte, celle de SetCollisionBounds si elle est réglée, sinon la boîte du modèle. SOLID_VPHYSICS : les formes du modèle (normal).
ent:GetSolid() (partagé)SOLID_NONE, SOLID_BBOX (forme en boîte) ou SOLID_VPHYSICS.
ent:PhysicsInit(SOLID_…)Revient aux formes du modèle (SOLID_NONE : sans collision). Renvoie true.
ent:PhysicsInitBox(mins, maxs)Une boîte de coins mins / maxs (±100 m au plus). Renvoie true.
ent:PhysicsInitSphere(rayon)Une sphère centrée sur l'entité (0,01 à 100 m) : elle roule. Renvoie true.
ent:PhysicsDestroy()Plus de physique : l'entité est figée et sans collision.
ent:SetFriction(f) / ent:GetFriction()Frottement de 0 (glisse) à 10 ; 1 par défaut pour la plupart des modèles.
ent:SetElasticity(e) / ent:GetElasticity()Rebond de 0 (aucun) à 1.

Constantes : SOLID_NONE (0), SOLID_BSP (1), SOLID_BBOX (2), SOLID_OBB (3), SOLID_OBB_YAW (4), SOLID_CUSTOM (5), SOLID_VPHYSICS (6) ; FSOLID_NOT_SOLID (4) et FSOLID_TRIGGER (8) existent pour reprendre du code de Garry's Mod.

-- Une caisse qui roule comme une boule et rebondit.
local c = ents.SpawnProp("builtin:crate", Vector(0, 3, 0))
c:PhysicsInitSphere(0.5)
c:SetElasticity(0.8)
c:SetFriction(0.2)

Collisions au cas par cas : ShouldCollide

ent:SetCustomCollisionCheck(true) serveur fait demander au hook ShouldCollide(ent1, ent2) si l'entité doit heurter un autre corps : ent1 est l'entité réglée, ent2 un prop, un joueur, un PNJ, ou un élément du décor de la map (qui n'est pas une entité : vérifie avec istable avant d'appeler ses méthodes). La question est posée quand le corps s'approche à moins de 50 cm environ de l'entité, sur chaque machine (chaque joueur simule son propre déplacement) : ajoute le hook dans un fichier partagé. Renvoyer false : les deux se traversent, jusqu'à ce que la règle soit remise en question. Autre chose : ils se heurtent, et la question sera reposée la prochaine fois que le corps reviendra.

Après avoir changé ta règle, appelle ent:CollisionRulesChanged() (serveur) : les réponses déjà données sont oubliées et le hook est redemandé. ent:GetCustomCollisionCheck() lit le réglage ; SetCustomCollisionCheck(false) l'enlève et rend les collisions normales.

-- Partagé : les joueurs de l'équipe 1 traversent les portes de leur équipe.
hook.Add("ShouldCollide", "portes_equipe", function(ent, autre)
	if ent:GetClass() == "porte_equipe" and istable(autre) and autre.IsPlayer and autre:IsPlayer() and autre:Team() == 1 then
		return false
	end
end)

-- Serveur, à la création de la porte :
-- porte:SetCustomCollisionCheck(true)

Attacher : SetParent

ent:SetParent(ent ou ply ou nil) serveur : le prop suit l'entité ou le joueur en gardant sa position relative actuelle (il est figé tant qu'il est attaché). nil le détache et il retombe. Si le parent disparaît, il est détaché tout seul. ent:GetParent() renvoie le parent.

-- Un ballon qui suit le joueur au-dessus de sa tête.
local ballon = ents.SpawnProp("builtin:ball", ply:GetPos() + Vector(0, 2.5, 0))
ballon:SetParent(ply)

Réagir aux chocs : PhysicsCollide

ENT:PhysicsCollide(data) (entités scriptées, serveur) est appelé quand l'entité heurte un prop ou un joueur (au plus toutes les 0,05 s). Seulement si la classe définit PhysicsCollide : sinon les chocs ne sont pas suivis.

Champ de dataValeur
HitEntityLe prop ou le joueur heurté
HitPos, HitNormalPoint de contact (approximatif), normale
OurOldVelocity, TheirOldVelocityVitesses avant le choc
Speed, HitSpeedVitesse, vitesse relative
DeltaTime0
-- entities/vase.lua : un vase qui se brise quand il tombe fort.
ENT.Model = "builtin:cylinder"
if SERVER then
	function ENT:PhysicsCollide(data)
		if data.Speed > 6 then
			self:EmitSound("physicore/clic.wav")
			self:Remove()
		end
	end
end

Chercher des entités

FonctionRenvoie
ents.FindInSphere(pos, rayon)Props et joueurs à moins de rayon mètres
ents.FindInBox(min, max)Props et joueurs dans la boîte (1024 au plus)
ents.FindInCone(origine, direction, distance, cosAngle)Props et joueurs dans un cône (cosAngle 0,7 ≈ cône de 45°)
ents.FindByModel(id)Props de ce modèle
ents.FindByName(nom)Entités et zones placées dans une map (voir Points, zones et entités de map)
ents.GetByIndex(ent:EntIndex())L'entité de cet identifiant
player.GetBySteamID(id), player.GetBySteamID64(id)Le joueur (id en texte, comme ply:SteamID()), ou nil
-- Qui est devant moi, à moins de 20 m ?
for _, ent in ipairs(ents.FindInCone(ply:EyePos(), ply:GetAimVector(), 20, 0.9)) do
	if ent:IsPlayer() and ent ~= ply then ply:ChatPrint(ent:Nick() .. " est devant toi") end
end

Contraintes

La bibliothèque constraint serveur crée les mêmes liens que le toolgun : ils sont dessinés chez tous (cordes) et sauvés dans les dupes. Les arguments suivent Garry's Mod ; les « bones » sont ignorés (mets 0). Les positions locales sont dans le repère de chaque entité. ent2 = nil accroche au monde. Chaque fonction renvoie l'identifiant de la contrainte (un nombre) ou nil.

FonctionLien
constraint.Weld(ent1, ent2, 0, 0, 0, nocollide)Soudure
constraint.NoCollide(ent1, ent2)Les deux ne se touchent plus
constraint.Rope(ent1, ent2, 0, 0, lpos1, lpos2, longueur, ajout, 0, largeur)Corde (longueur ≤ 0 : distance actuelle)
constraint.Elastic(ent1, ent2, 0, 0, lpos1, lpos2, force, amortissement)Élastique
constraint.Axis(ent1, ent2, 0, 0, lpos1, lpos2, 0, 0, 0, nocollide, axeLocal)Axe (roue, charnière)
constraint.Ballsocket(ent1, ent2, 0, 0, lpos)Rotule
constraint.Slider(ent1, ent2, 0, 0, lpos1, lpos2)Glissière
constraint.Keepupright(ent, nil, 0, force)Garde l'entité droite
constraint.AdvBallsocket(ent1, ent2, 0, 0, lpos1, lpos2, 0, 0, xmin, ymin, zmin, xmax, ymax, zmax, 0, 0, 0, rotationSeule, nocollide)Rotule avec des limites d'angle par axe (degrés, -180 à 180 ; -180 / 180 = libre). rotationSeule = true : l'écart entre les deux points n'est plus tenu, seule la rotation est limitée
constraint.Pulley(ent1, ent2, 0, 0, lpos1, lpos2, poulie1, poulie2)Poulie : une corde de ent1 à poulie1, puis de poulie2 à ent2 (poulies : positions dans le monde), de longueur totale fixe : quand un côté descend, l'autre monte. Deux entités obligatoires

Contraintes pilotées au pavé numérique

Ces contraintes bougent quand le joueur ply (le propriétaire, celui dont on écoute les touches) appuie sur des touches du pavé numérique (KEY_PAD_0 à KEY_PAD_9…), comme celles du toolgun. Longueurs en mètres (0,05 à 100), vitesses en m/s. Les arguments de Garry's Mod sans effet (os, largeur, matériau…) gardent leur place : mets 0.

FonctionLien
constraint.Winch(ply, ent1, ent2, 0, 0, lpos1, lpos2, 0, toucheRentrer, toucheSortir, vitesse)Treuil : une corde qui raccourcit tant que toucheRentrer est tenue et s'allonge avec toucheSortir (vitesse 0,05 à 20 m/s, 1,5 par défaut ; longueur de 0,1 à 100 m). Longueur de départ : la distance actuelle
constraint.Hydraulic(ply, ent1, ent2, 0, 0, lpos1, lpos2, longueur1, longueur2, 0, touche, 0, vitesse, 0, bascule)Vérin : pousse et tire pour tenir longueur1 ; touche tenue, il passe à longueur2 à vitesse m/s, et revient quand on la lâche. bascule = true : chaque appui passe d'une longueur à l'autre
constraint.Muscle(ply, ent1, ent2, 0, 0, lpos1, lpos2, longueur1, longueur2, 0, touche, 0, période, 0, démarré)Muscle : va et vient entre longueur1 et longueur2 (un aller-retour en période secondes, 0,05 à 60). La touche le met en marche ou l'arrête ; démarré = true : il bouge tout de suite
constraint.Motor(ent1, ent2, 0, 0, lpos1, lpos2, 0, couple, 0, nocollide, bascule, ply, 0, toucheAvant, toucheArrière, sens, axe)Moteur : un axe (comme constraint.Axis, autour de axe, un Vector dans le repère de ent1, Y par défaut) qui tourne tant qu'une touche est tenue. couple de 1 à 100 000 (800 par défaut). bascule = true : un appui le lance, un autre l'arrête. sens = -1 inverse la rotation. Attention : ici ply est le 12e argument, comme dans Garry's Mod

Pour constraint.FindConstraints et RemoveConstraints, leurs types sont "winch", "hydraulic", "muscle", "pulley" et "advballsocket" ; un moteur est un "axis". Elles sont dessinées chez tous et gardées dans les dupes, comme les autres.

-- Un vérin entre une base figée et une plateforme : 1,5 m au repos, 3 m tant que le joueur tient 8 du pavé numérique.
local base = ents.SpawnProp("builtin:crate", ply:GetPos() + Vector(0, 0.5, -4))
base:SetFrozen(true)
local plateforme = ents.SpawnProp("builtin:crate", base:GetPos() + Vector(0, 1.5, 0))
constraint.Hydraulic(ply, base, plateforme, 0, 0, Vector(0, 0, 0), Vector(0, 0, 0), 1.5, 3, 0, KEY_PAD_8, 0, 1)

Gestion : constraint.Remove(id), constraint.RemoveAll(ent) (renvoie le nombre retiré), constraint.RemoveConstraints(ent, "weld"), constraint.FindConstraints(ent, type) → liste de {Index, Type, Ent1, Ent2} (type facultatif), constraint.GetAllConstrainedEntities(ent) → toutes les entités reliées (ent compris).

local a = ents.SpawnProp("builtin:crate", Vector(0, 1, 0))
local b = ents.SpawnProp("builtin:crate", Vector(0, 2, 0))
constraint.Weld(a, b)
constraint.Rope(a, nil, 0, 0, Vector(0, 0.45, 0), Vector(0, 6, 0), 0, 0.5) -- pend à 6 m du sol

Explosions

util.BlastDamage(pos, rayon, dégâts, attaquant, force) serveur : dégâts aux joueurs avec ligne de vue, qui baissent avec la distance ; pousse les props ; effet visible et hook WeaponExplosion. Rayon de 0,1 à 100 m ; attaquant et force sont facultatifs. Respecte le réglage « dégâts entre joueurs ».

-- Un baril qui explose quand on le supprime.
hook.Add("EntityRemoved", "baril_explosif", function(ent)
	if SERVER and ent:GetModel() == "builtin:barrel" then
		util.BlastDamage(ent:GetPos(), 6, 80)
	end
end)

Changer de map

game.ChangeMap(id, gamemode) serveur change de map (et de gamemode, facultatif) à la fin de l'image ; les scripts actuels s'arrêtent (hook ShutDown). Renvoie false si la map n'existe pas. game.GetMapList() donne les ids : maps intégrées (builtin:…) et .pcmap des addons activés (addon:…/maps/…pcmap).

concommand.Add("prochaine", function(ply)
	if not ply:IsAdmin() then return end
	local maps = game.GetMapList()
	game.ChangeMap(maps[math.random(#maps)])
end)

Date et heure : os

Une version sûre de os, des deux côtés. Rien d'autre (pas de fichiers, pas de commandes).

FonctionRenvoie
os.time()Secondes Unix réelles
os.time({year, month, day, hour, min, sec})Secondes Unix de cette date (heure locale)
os.clock(), os.difftime(a, b)Temps processeur, différence en secondes
os.date(format, temps)Texte daté : %Y %y %m %d %H %I %M %S %p %A %a %B %b %w %j %c %x %X %% (jours et mois en français). "*t" → table {year, month, day, hour, min, sec, wday (1 = dimanche), yday, isdst}. Préfixe ! = heure UTC. temps est facultatif.
print(os.date("%A %d %B à %H:%M")) -- « mardi 13 octobre à 07:30 »