Menus, clavier et souris

Côté client, le Lua peut ouvrir de vraies fenêtres (boutons, listes, champs de texte, onglets…) avec vgui, comme le Derma de Garry's Mod, et réagir aux touches du joueur. De quoi faire un choix d'équipe, une boutique, un vote ou une capacité spéciale.

Première fenêtre

Les fenêtres se créent dans un fichier client (cl_init.lua d'un gamemode, lua/autorun/client/ d'un addon). Ce menu s'ouvre et se ferme avec F3 :

local menu

local function ouvrir_menu()
	menu = vgui.Create("DFrame")
	menu:SetTitle("Choisis ton équipe")
	menu:SetSize(320, 160)
	menu:Center()
	menu:MakePopup() -- affiche le curseur

	for i, nom in ipairs({ "Rouges", "Bleus" }) do
		local bouton = vgui.Create("DButton", menu)
		bouton:SetText(nom)
		bouton:SetTall(40)
		bouton:DockMargin(0, 0, 0, 6)
		bouton:Dock(TOP)
		bouton.DoClick = function()
			net.Start("choix_equipe")
			net.WriteInt(i)
			net.SendToServer()
			menu:Close()
		end
	end
end

hook.Add("PlayerButtonDown", "menu_equipe", function(ply, touche)
	if touche ~= KEY_F3 then return end
	if IsValid(menu) then menu:Close() else ouvrir_menu() end
end)

Côté serveur, net.Receive("choix_equipe", …) reçoit le choix et appelle ply:SetTeam(…) (voir Messages net). Le serveur doit toujours vérifier ce que le client demande.

Panneaux

vgui.Create(type, parent) crée un panneau et le renvoie. Sans parent, il est posé sur l'écran ; avec un parent, ses coordonnées sont relatives au parent. Les tailles et positions sont en pixels, l'origine en haut à gauche.

TypeC'est quoiMéthodes et rappels propres
DFrameFenêtre avec titre, bouton ✕, déplaçable à la sourisSetTitle, ShowCloseButton(bool), SetDraggable(bool), SetDeleteOnClose(bool), Close() ; rappel OnClose
DPanelRectangle de fondSetBackgroundColor(Color), SetPaintBackground(bool)
EditablePanelConteneur invisible—
DLabelTexteSetText, SetFont, SetTextColor, SetWrap(bool), SetContentAlignment(1-9), SizeToContents()
DButtonBoutonSetText, SetEnabled ; rappels DoClick, DoRightClick
DTextEntryChamp de texte (une ligne)GetValue, SetValue, SetPlaceholderText, SetNumeric(bool), SetEditable, SetMaxLength, RequestFocus() ; rappels OnEnter(self, texte), OnChange(self)
DCheckBox, DCheckBoxLabelCase à cocher (avec texte pour la seconde)SetChecked, GetChecked, Toggle, SetText ; rappel OnChange(self, coché)
DNumSliderCurseur avec texte et nombreSetText, SetMin, SetMax, SetMinMax, SetDecimals, SetValue, GetValue ; rappel OnValueChanged(self, valeur)
DComboBoxListe déroulanteAddChoice(texte, donnée, choisir), ChooseOptionID(n), ChooseOption(texte), GetSelected() → texte, donnée, GetSelectedID, GetOptionText(n), GetOptionData(n), SetValue(texte) (texte affiché avant un choix), Clear ; rappel OnSelect(self, n, texte, donnée)
DListViewTableau à colonnes, triableAddColumn(nom) → colonne (:SetFixedWidth(px)), AddLine(...) → ligne, RemoveLine(id), GetLine(id), GetLines(), GetSelectedLine() → id, ligne, GetSelected(), ClearSelection, SortByColumn(n, décroissant), SetMultiSelect, SetHideHeaders, Clear ; rappels OnRowSelected(self, id, ligne), DoDoubleClick, OnRowRightClick
DScrollPanelZone qui défile verticalementLes enfants dockés en TOP s'empilent ; ScrollToChild(panneau)
DPropertySheetOngletsAddSheet(nom, panneau), SwitchToName(nom)
DIconLayoutGrille qui passe à la ligneSetSpaceX, SetSpaceY ; les enfants gardent leur SetSize
DProgressBarre de progressionSetFraction(0 à 1), GetFraction
SpawnIconVignette d'un prop (boutique)SetModel(id) (ex. "builtin:crate"), GetModelName ; rappel DoClick
DImageImageSetImage(chemin, secours), SetImageColor, SetKeepAspect, SizeToContents
DImageButtonBouton imageComme DImage, plus SetStretchToFit, SetDepressImage ; rappels DoClick, DoRightClick
DModelPanelAperçu 3D d'un modèleSetModel, SetFOV, SetCamPos, SetLookAt, SetColor, SetAutoRotate, SetAnimated…
DMenuMenu contextuel (avec DermaMenu())AddOption, AddSpacer, AddSubMenu, Open ; rappel OptionSelected
DColorMixerSélecteur de couleurSetColor, GetColor, SetAlphaBar, SetPalette, SetWangs ; rappel ValueChanged
RichTextTexte en couleurs qui défile (journal, discussion)AppendText, InsertColorChange, liens cliquables ; rappel OnTextClicked
DBinderBouton pour choisir une toucheSetValue(code), GetValue ; rappel OnChange
DTreeArborescence de nœuds dépliablesAddNode(texte, icône) → nœud, Root, GetSelectedItem, Clear ; rappels OnNodeSelected, DoClick, DoRightClick, OnNodeExpanded
DHTML, HTMLPage de HTML simple (sans navigateur)SetHTML, GetHTML, OpenURL ; rappel OnLinkClicked
DCollapsibleCategory, DCategoryListCatégorie repliable, liste de catégoriesSetLabel, SetContents, Add, SetExpanded, Toggle ; rappel OnToggle
DNumberWangChamp numérique bornéSetMinMax, SetDecimals, SetValue, SetInterval ; rappel OnValueChanged
DColumnSheetOnglets en colonne à gaucheAddSheet(nom, panneau), SetActiveButton ; rappel OnActiveButtonChanged
DHorizontalDivider, DVerticalDividerDeux panneaux séparés par une barre déplaçableSetLeft / SetRight, SetTop / SetBottom, SetLeftWidth, SetTopHeight…
DGrid, DTileLayoutGrille à colonnes fixes ; grille qui passe à la ligneSetCols, SetColWide, SetRowHeight, AddItem…
DAdjustableModelPanelAperçu 3D qu'on fait tourner à la sourisComme DModelPanel ; rappel OnCameraChanged
DFileBrowserExplorateur du dossier de donnéesSetBaseFolder, SetFileTypes, SetCurrentFolder ; rappels OnSelect, OnDoubleClick…

Une ligne de DListView a ligne:GetValue(colonne) (aussi GetColumnText), ligne:SetValue(colonne, valeur), ligne:GetID() et ligne:IsLineSelected(). Tu peux y ranger tes propres champs (ligne.prix = 50).

Méthodes de tous les panneaux

MéthodeEffet
SetPos(x, y), GetPos()Position dans le parent
SetSize(l, h), GetSize(), SetWide, SetTall, GetWide, GetTallTaille
Center(), CenterHorizontal(), CenterVertical()Centre dans le parent (ou l'écran)
Dock(type), DockMargin(g, h, d, b), DockPadding(g, h, d, b)Placement automatique (voir plus bas)
SetVisible(bool), Show(), Hide(), IsVisible()Afficher / cacher
MakePopup()Met la fenêtre devant et libère le curseur de la souris
Remove(), IsValid()Supprime le panneau et ses enfants (rappel OnRemove)
Add(type)Comme vgui.Create(type, self)
SetText / GetText, SetValue / GetValueTexte ou valeur, selon le type
SetFont(police), SetTextColor(Color)Polices : "Default", "Small", "Bold", "Big", "Title", ou une taille en nombre
SetEnabled(bool), SetDisabled(bool), IsEnabled()Grise le panneau
SetTooltip(texte), SetAlpha(0-255), MoveToFront(), MoveToBack()Bulle d'aide, transparence, ordre
GetParent(), GetChildren(), Clear()Parent, enfants, supprimer les enfants
SizeToContents(), SizeToChildren(l, h), InvalidateLayout()Ajuster la taille, refaire le placement
IsHovered(), RequestFocus(), HasFocus()Survol, focus clavier
SetMouseInputEnabled(bool), SetKeyboardInputEnabled(bool)Le panneau reçoit-il la souris / le clavier

Rappels de tous les panneaux : Paint(self, l, h), Think(self) (chaque image), PerformLayout(self, l, h), OnCursorEntered, OnCursorExited, OnMousePressed(self, bouton), OnMouseReleased, OnKeyCodePressed(self, touche) (quand le panneau a le focus), OnRemove.

Placer avec Dock

Plutôt que de calculer les positions, accroche les enfants aux bords du parent : TOP, BOTTOM, LEFT, RIGHT prennent leur hauteur (ou largeur) sur le bord, dans l'ordre de création, puis FILL prend tout ce qui reste. NODOCK revient à SetPos. DockMargin écarte un enfant de ses voisins, DockPadding réserve une marge à l'intérieur du parent (une DFrame réserve déjà sa barre de titre).

local fenetre = vgui.Create("DFrame")
fenetre:SetTitle("Boutique")
fenetre:SetSize(420, 320)
fenetre:Center()
fenetre:MakePopup()

local liste = vgui.Create("DListView", fenetre)
liste:Dock(FILL)
liste:AddColumn("Objet")
liste:AddColumn("Prix"):SetFixedWidth(70)
liste:AddLine("Caisse", 50).modele = "builtin:crate"
liste:AddLine("Baril", 20).modele = "builtin:barrel"

local acheter = vgui.Create("DButton", fenetre)
acheter:SetText("Acheter")
acheter:SetTall(32)
acheter:DockMargin(0, 6, 0, 0)
acheter:Dock(BOTTOM)
acheter.DoClick = function()
	local id, ligne = liste:GetSelectedLine()
	if not id then return end
	net.Start("boutique_achat")
	net.WriteString(ligne.modele)
	net.SendToServer()
end

Dessiner soi-même : Paint

Définis Paint pour dessiner un panneau avec draw.* et surface.* (voir Dessiner le HUD), en coordonnées locales. Le fond par défaut disparaît. Le panneau est redessiné à chaque image.

local argent = vgui.Create("DPanel", fenetre)
argent:SetTall(26)
argent:Dock(TOP)
argent.Paint = function(self, l, h)
	draw.RoundedBox(4, 0, 0, l, h, Color(40, 110, 200))
	draw.SimpleText("Argent : " .. LocalPlayer():GetNWInt("argent") .. " $", "Default", 8, h / 2, Color(255, 255, 255), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
end

Tes propres types : vgui.Register

vgui.Register(nom, PANEL, base) crée un type réutilisable à partir d'une table de fonctions et d'un type de base. PANEL:Init() est appelée à la création (celle de la base d'abord).

local PANEL = {}

function PANEL:Init()
	self:SetTitle("Vote")
	self:SetSize(300, 140)
	self:Center()
	self:MakePopup()
end

function PANEL:AjouterChoix(texte, action)
	local bouton = self:Add("DButton")
	bouton:SetText(texte)
	bouton:Dock(TOP)
	bouton.DoClick = function() action() self:Close() end
end

vgui.Register("MonVote", PANEL, "DFrame")

local vote = vgui.Create("MonVote")
vote:AjouterChoix("Encore une manche", function() print("oui") end)

Images, modèles, menus et texte riche

Ces panneaux ont toutes les méthodes communes (SetPos, SetSize, Dock, SetVisible, Remove, SetTooltip, Think, Paint…). Les images viennent d'un dossier materials/ du gamemode ou d'un addon activé ("mon_addon/logo.png") ou des images intégrées "physicore/logo.png", "physicore/icon_physgun.png" et "physicore/icon_toolgun.png" ; formats png, jpg, jpeg, webp et svg. Une image introuvable écrit une erreur dans la console. Comme pour les sons, les autres joueurs ne voient que les images du jeu et du Workshop : celles d'un addon local restent chez l'hôte. Pour dessiner des images dans le HUD, voir Material.

DImage : image

MéthodeDescription
img:SetImage(chemin, secours)Affiche l'image. secours (facultatif) : autre chemin essayé si le premier est introuvable.
img:GetImage()Chemin donné à SetImage ("" sinon).
img:SetMaterial(chemin)Comme SetImage (accepte seulement un chemin texte).
img:SetImageColor(Color) / img:GetImageColor()Teinte multipliée à l'image (alpha compris).
img:SetKeepAspect(bool)true : garde les proportions (centrée, bandes vides). Par défaut, l'image est étirée à la taille du panneau.
img:SizeToContents()Donne au panneau la taille en pixels de l'image.

Taille par défaut : 64 × 64.

local frame = vgui.Create("DFrame")
frame:SetSize(300, 260) frame:Center() frame:MakePopup()
local logo = vgui.Create("DImage", frame)
logo:Dock(FILL)
logo:SetImage("mon_addon/logo.png")
logo:SetKeepAspect(true)

DImageButton : bouton image

Bouton sans fond qui affiche une image (étirée par défaut). L'image s'éclaircit au survol et s'enfonce de 2 px au clic.

Méthode / rappelDescription
SetImage(chemin, secours), GetImage(), SetImageColor(Color), SetKeepAspect(bool), SizeToContents()Comme DImage.
btn:SetStretchToFit(bool)false : image à sa taille réelle, centrée. true par défaut.
btn:SetDepressImage(bool)false : l'image ne s'enfonce plus au clic. true par défaut.
btn.DoClick(self), btn.DoRightClick(self)Clic gauche, clic droit.
local close = vgui.Create("DImageButton", frame)
close:SetSize(24, 24)
close:SetPos(frame:GetWide() - 30, 30)
close:SetImage("mon_addon/fermer.png")
close.DoClick = function() frame:Close() end

SetImage et SetIcon(chemin) marchent aussi sur un DButton et sur une option de DMenu : une icône à gauche du texte (16 px au plus dans un menu).

DModelPanel : aperçu 3D d'un modèle

Affiche un prop (id du menu de spawn : "builtin:crate", "addon:mon_pack/models/chaise.glb", véhicules…) dans une petite scène à part (fond transparent, lumière fixe). Par défaut, le modèle tourne sur lui-même (10° par seconde) et la caméra le cadre automatiquement. Unités en mètres, Y en haut, comme Vector. Le rendu ne tourne que quand le panneau est visible ; un id inconnu affiche le modèle d'erreur.

Méthode / rappelDescription
mdl:SetModel(id) / mdl:GetModelName()Modèle affiché ("" ou nil : rien).
mdl:SetFOV(degrés) / GetFOV()Champ de vision (1 à 170, 70 par défaut).
mdl:SetCamPos(Vector) / GetCamPos()Position de la caméra (sinon cadrage automatique).
mdl:SetLookAt(Vector) / GetLookAt()Point regardé (sinon le centre du modèle).
mdl:SetColor(Color) / GetColor()Teinte du modèle ; alpha < 255 = transparent ; blanc = couleurs d'origine.
mdl:SetAutoRotate(bool, vitesse)Rotation automatique (vitesse en degrés par seconde, 10 par défaut). false remet l'angle à 0. Propre à PhysiCore.
mdl:SetModelAngle(degrés)Tourne le modèle (axe vertical) à cet angle. Propre à PhysiCore.
mdl:SetAnimated(bool) / GetAnimated()Joue en boucle la première animation du modèle (modèles importés qui en ont).
mdl:SetAmbientLight(Color)Couleur de la lumière ambiante.
mdl.DoClick(self), mdl.DoRightClick(self)Clics sur l'aperçu.
mdl.LayoutEntitySi tu définis ce rappel, la rotation automatique s'arrête ; il n'est pas appelé (il n'y a pas d'objet entité dans l'aperçu). Il sert à reprendre le code GMod function mdl:LayoutEntity() end.
16 aperçus au plusAu-delà de 16 DModelPanel en même temps, vgui.Create renvoie nil et écrit une erreur. Pour une grande boutique, utilise des SpawnIcon et un seul DModelPanel pour l'objet choisi.
local frame = vgui.Create("DFrame")
frame:SetSize(320, 360) frame:Center() frame:MakePopup()
frame:SetTitle("Caisse")

local mdl = vgui.Create("DModelPanel", frame)
mdl:Dock(FILL)
mdl:SetModel("builtin:crate")
mdl:SetFOV(40)
mdl:SetColor(Color(255, 200, 150))
mdl.DoClick = function() mdl:SetAutoRotate(false) end

DMenu et DermaMenu : menu contextuel

DermaMenu(garderOuverts, parent) crée un menu (caché) et ferme d'abord les autres menus, sauf si le premier argument vaut true ou est un autre DMenu. Avec un parent, le menu disparaît avec ce panneau. menu:Open() l'affiche au-dessus de toutes les fenêtres et libère le curseur tant qu'il est ouvert. Il se ferme (et est supprimé) quand on choisit une option, qu'on clique ailleurs ou qu'on appuie sur Échap. CloseDermaMenus() ferme tous les menus ouverts.

Méthode / rappelDescription
menu:AddOption(texte, fonction)Ajoute une option et la renvoie. La fonction (facultative) est son DoClick et reçoit l'option.
option:SetIcon(chemin)Petite icône devant le texte. Les options ont aussi SetText / GetText, SetTooltip, SetEnabled…
menu:AddSpacer()Ligne de séparation.
menu:AddSubMenu(texte, fonction)Option qui ouvre un sous-menu (au survol, ou au clic s'il n'y a pas de fonction) ; renvoie sousMenu, option. Avec une fonction, cliquer l'option l'appelle et ferme le menu. 8 niveaux au plus.
menu:Open(x, y)Ouvre aux coordonnées de l'écran ; sans argument, à la position de la souris. Le menu reste dans l'écran.
menu:Hide() / menu:Remove()Cache / supprime (avec ses sous-menus).
menu:SetDeleteSelf(bool) / GetDeleteSelf()false : le menu est seulement caché quand il se ferme (réutilisable avec Open). true par défaut.
menu:SetMinimumWidth(px)Largeur minimale (120 par défaut).
menu:ChildCount() / menu:GetChild(n)Nombre d'options / n-ième option (à partir de 1).
menu.OptionSelected(self, option, texte)Appelé après le DoClick d'une option.

200 éléments au plus par menu.

local liste = vgui.Create("DListView", frame)
liste:Dock(FILL)
liste:AddColumn("Joueur")
for _, ply in ipairs(player.GetAll()) do liste:AddLine(ply:Nick()) end

liste.OnRowRightClick = function(self, id, ligne)
	local menu = DermaMenu()
	menu:AddOption("Copier le nom", function()
		print("Nom :", ligne:GetValue(1))
	end):SetIcon("mon_addon/copier.png")
	menu:AddSpacer()
	local equipe = menu:AddSubMenu("Changer d'équipe")
	equipe:AddOption("Rouges", function() net.Start("equipe") net.WriteString("rouge") net.SendToServer() end)
	equipe:AddOption("Bleus", function() net.Start("equipe") net.WriteString("bleu") net.SendToServer() end)
	menu:Open()
end

DColorMixer : sélecteur de couleur

Le sélecteur de Godot (carré teinte / saturation, curseurs, code hexadécimal, palette). Sa taille minimale est d'environ 300 × 400 px : plus petit, il garde sa taille minimale.

Méthode / rappelDescription
mixer:SetColor(Color) / mixer:GetColor()Couleur choisie (SetValue / GetValue font pareil). SetColor n'appelle pas ValueChanged.
mixer:SetAlphaBar(bool)Réglage de la transparence (affiché par défaut).
mixer:SetPalette(bool)Couleurs enregistrées sous le sélecteur.
mixer:SetWangs(bool)Curseurs et cases numériques R, G, B.
mixer.ValueChanged(self, couleur)Quand le joueur change la couleur.
local mixer = vgui.Create("DColorMixer", frame)
mixer:Dock(FILL)
mixer:SetAlphaBar(false)
mixer:SetColor(Color(255, 120, 0))
mixer.ValueChanged = function(self, couleur)
	net.Start("couleur_vehicule") net.WriteColor(couleur) net.SendToServer()
end

RichText : texte riche défilant

Du texte en couleurs, ajouté morceau par morceau (journal, discussion). Il défile tout seul vers le bas quand on ajoute du texte (si on était déjà en bas), et il est sélectionnable. Pas de BBCode : le texte est affiché tel quel. 8192 caractères au plus par AppendText ; au-delà de 2000 paragraphes, les plus anciens sont retirés.

Méthode / rappelDescription
rt:AppendText(texte)Ajoute du texte ("\n" = nouvelle ligne) avec la couleur courante.
rt:InsertColorChange(r, g, b, a)Couleur du texte ajouté ensuite (0 à 255 ; accepte aussi une Color). Blanc par défaut.
rt:InsertClickableTextStart(signal) / rt:InsertClickableTextEnd()Le texte ajouté entre les deux devient un lien : un clic appelle OnTextClicked avec signal.
rt.OnTextClicked(self, signal)Clic sur un lien.
rt:SetText(texte) / rt:GetText()Efface tout puis ajoute ce texte / texte affiché (sans couleurs).
rt:GotoTextEnd() / rt:GotoTextStart()Défile tout en bas / en haut.
rt:SetVerticalScrollbarEnabled(bool)Cache la barre de défilement (la molette marche toujours).
rt:SetFontInternal(police) / rt:SetFont(police)Police (nom de surface.CreateFont ou police intégrée).
local journal = vgui.Create("RichText", frame)
journal:Dock(FILL)
journal:SetFontInternal("Default")

local function log(couleur, texte)
	journal:InsertColorChange(couleur) -- une Color, ou quatre nombres de 0 à 255
	journal:AppendText(texte .. "\n")
end
log(Color(255, 80, 80), "Erreur !")

journal:InsertColorChange(120, 200, 255, 255)
journal:InsertClickableTextStart("regles")
journal:AppendText("[Lire les règles]\n")
journal:InsertClickableTextEnd()
journal.OnTextClicked = function(self, signal)
	if signal == "regles" then Derma_Message("Pas de spawnkill !", "Règles") end
end

DBinder : choisir une touche

Un bouton qui affiche une touche. Au clic, il affiche « Appuie sur une touche… » et la prochaine touche du clavier ou bouton de la souris (gauche, droit, molette, 4, 5) devient sa valeur ; Échap annule. Le clic droit remet « Aucune » (0), sauf si le script définit DoRightClick. Les valeurs sont les codes KEY_* / MOUSE_* (les mêmes que input.IsKeyDown et PlayerButtonDown) ; 0 = aucune touche.

Méthode / rappelDescription
binder:SetValue(code) / binder:GetValue()Touche choisie (un code inconnu devient 0).
binder:SetSelectedNumber(code) / GetSelectedNumber()Mêmes choses (noms GMod).
binder.OnChange(self, code)Quand le joueur choisit une touche, ou quand SetValue change la valeur.
local touche = vgui.Create("DBinder", frame)
touche:SetSize(140, 26)
touche:SetPos(20, 40)
touche:SetValue(KEY_G)
touche.OnChange = function(self, code)
	print("Nouvelle touche :", input.GetKeyName(code))
end

hook.Add("PlayerButtonDown", "mon_addon_touche", function(ply, code)
	if code == touche:GetValue() then print("Action !") end
end)
Garder le choixEnregistre la touche dans une ConVar client (cvar:SetInt(code) dans OnChange) pour la retrouver à la prochaine partie.

Autres panneaux Derma

Ces panneaux reprennent ceux de Garry's Mod. Ils sont écrits en Lua au-dessus des panneaux du jeu (DTree et DHTML s'appuient sur les natifs __DTree et __DHTML) : ils ont toutes les méthodes communes, et on peut s'en servir de base avec vgui.Register.

DTree : arborescence

Une liste de nœuds qu'on déplie (dossiers, catégories). Les nœuds sont repliés à leur création. 5000 nœuds au plus par arbre.

Méthode / rappel de l'arbreDescription
tree:AddNode(texte, icône)Ajoute un nœud de premier niveau et le renvoie (nil au-delà de la limite). L'icône (facultative) est un chemin d'image, comme Material.
tree:Root()Le nœud racine invisible : AddNode, GetChildNodes, GetChildNode(i), Clear.
tree:GetSelectedItem() / tree:SetSelectedItem(nœud)Nœud choisi.
tree:Clear()Retire tous les nœuds.
tree.OnNodeSelected(self, nœud), tree.DoClick(self, nœud)Quand un nœud est choisi.
tree.DoRightClick(self, nœud)Clic droit sur un nœud.
tree.OnNodeExpanded(self, nœud, ouvert)Un nœud est déplié ou replié.
Méthode / rappel d'un nœudDescription
node:AddNode(texte, icône)Ajoute un sous-nœud et le renvoie.
SetText / GetText, SetIcon(chemin), SetTooltip(texte)Texte, icône, bulle d'aide.
SetExpanded(bool) / GetExpanded(), ExpandRecurse(bool), ExpandTo(bool)Déplier ce nœud, ce nœud et tous ses descendants, ou tous ses parents.
GetChildNodes(), GetChildNode(i) (à partir de 0), GetChildNodeCount(), HasChildren(), GetParentNode()Parcourir l'arbre.
SetSelected(), Remove(), Clear(), IsValid()Choisir, supprimer (avec ses enfants), vider, encore là ?
node.DoClick(self)Clic sur ce nœud ; renvoyer true empêche OnNodeSelected / DoClick de l'arbre.
node.DoDoubleClick(self), node.DoRightClick(self) (true = stop), node.OnNodeExpanded(self, ouvert)Double-clic, clic droit, dépliage.
local arbre = vgui.Create("DTree", frame)
arbre:Dock(FILL)
local armes = arbre:AddNode("Armes", "mon_addon/icone_armes.png")
armes:AddNode("Pistolet").classe = "weapon_pistol"
armes:AddNode("Fusil").classe = "weapon_ar2"
armes:SetExpanded(true)

arbre.OnNodeSelected = function(self, node)
	if node.classe then print("Choisi :", node.classe) end
end

DHTML et HTML : page HTML simple

Il n'y a pas de navigateur dans le jeu : DHTML (et HTML, le même) affiche une page claire de HTML simple, convertie en texte mis en forme. Balises reconnues : h1 à h6, p, div, br, b / strong, i / em, u, s, a href (liens http(s) seulement), font color, span style="color: …", ul / ol / li, pre / code, hr, center, et les entités &nbsp;, &lt;, &gt;, &quot;, &amp;… Les scripts, styles et autres balises sont ignorés.

Méthode / rappelDescription
html:SetHTML(html) / html:GetHTML()Contenu de la page (200 000 caractères au plus).
html:OpenURL(url) / GetURL()Affiche un lien vers cette adresse, qui s'ouvre dans le navigateur du joueur ; appelle OnDocumentReady(url) s'il est défini.
html.OnLinkClicked(self, url)Clic sur un lien. Renvoyer true pour le gérer soi-même ; sinon gui.OpenURL(url) (avec confirmation).
QueueJavascript, RunJavascript, AddFunction, SetAllowLua, Call, SetScrollbars, Refresh, StopLoadingAcceptés, sans effet (pas de JavaScript). IsLoading() renvoie false.
local regles = vgui.Create("DHTML", frame)
regles:Dock(FILL)
regles:SetHTML([[
<h2>Règles du serveur</h2>
<ul><li>Pas de <b>spawnkill</b></li><li><font color="#c03030">Pas de triche</font></li></ul>
<p><a href="https://example.com/forum">Forum</a></p>
]])

DCollapsibleCategory et DCategoryList : catégories repliables

DCollapsibleCategory est un en-tête (bouton ▾ / ▸) au-dessus d'un contenu qu'on replie ; sa hauteur suit celle du contenu. DCategoryList est une zone qui défile (DScrollPanel) où l'on empile des catégories.

Méthode / rappelDescription
cat:SetLabel(texte) / GetLabel()Texte de l'en-tête.
cat:SetContents(panneau)Le panneau affiché sous l'en-tête (docké en haut ; l'ancien est supprimé).
cat:Add(texte)Ajoute un DButton à la liste de la catégorie et le renvoie.
cat:SetExpanded(bool) / GetExpanded(), DoExpansion(bool), Toggle()Déplier / replier.
cat:SetHeaderHeight(px)Hauteur de l'en-tête (22 par défaut).
cat.OnToggle(self, ouvert)Quand le joueur clique l'en-tête (ou Toggle).
liste:Add(nom) / liste:AddItem(panneau)DCategoryList : ajoute une catégorie (renvoyée) / un panneau quelconque.
local liste = vgui.Create("DCategoryList", frame)
liste:Dock(FILL)
local vehicules = liste:Add("Véhicules")
vehicules:Add("Voiture").DoClick = function() RunConsoleCommand("say", "voiture") end
vehicules:Add("Bateau")
liste:Add("Outils"):SetExpanded(false)

DNumberWang : champ numérique

Un DTextEntry qui n'accepte qu'un nombre, gardé entre un minimum et un maximum (0 à 100 par défaut) et arrondi au nombre de décimales. Les flèches ↑ / ↓ ajoutent ou retirent l'intervalle.

Méthode / rappelDescription
SetMin, SetMax, SetMinMax(min, max), GetMin, GetMaxBornes.
SetDecimals(n) / GetDecimals()Décimales (0 par défaut, 8 au plus).
SetValue(n) / GetValue()Valeur (un nombre, bornée et arrondie).
SetInterval(n) / GetInterval()Pas des flèches (1 par défaut).
SetFraction(0 à 1) / GetFraction()Valeur en fraction de l'intervalle min–max.
wang.OnValueChanged(self, valeur)Quand la valeur change (frappe, flèches ou SetValue).

DColumnSheet : onglets en colonne

Une colonne de boutons à gauche (sheet.Navigation, un DScrollPanel de 120 px) et la zone de contenu à droite (sheet.Content). Le premier onglet ajouté est affiché.

Méthode / rappelDescription
sheet:AddSheet(nom, panneau)Ajoute un onglet ; renvoie { Button = bouton, Panel = panneau }.
sheet:SetActiveButton(bouton) / GetActiveButton()Onglet affiché.
sheet.OnActiveButtonChanged(self, bouton)Quand l'onglet affiché change.
local sheet = vgui.Create("DColumnSheet", frame)
sheet:Dock(FILL)
local general = vgui.Create("DPanel")
local touches = vgui.Create("DPanel")
sheet:AddSheet("Général", general)
local onglet = sheet:AddSheet("Touches", touches)
sheet:SetActiveButton(onglet.Button)

DHorizontalDivider et DVerticalDivider : diviseurs

Deux panneaux séparés par une barre que le joueur fait glisser à la souris. Chaque côté garde une taille minimale (30 px par défaut).

DHorizontalDividerDVerticalDividerDescription
SetLeft(p) / SetRight(p), GetLeft / GetRightSetTop(p) / SetBottom(p), GetTop / GetBottomLes deux panneaux (le second prend la place restante).
SetLeftWidth(px) / GetLeftWidth()SetTopHeight(px) / GetTopHeight()Taille du premier côté (150 par défaut).
SetLeftMin(px), SetRightMin(px)SetTopMin(px), SetBottomMin(px)Tailles minimales.
SetDividerWidth(px)SetDividerHeight(px)Épaisseur de la barre (6 par défaut).
local div = vgui.Create("DHorizontalDivider", frame)
div:Dock(FILL)
div:SetLeft(vgui.Create("DTree"))
div:SetRight(vgui.Create("DListView"))
div:SetLeftWidth(180)
div:SetLeftMin(100)

DGrid et DTileLayout : grilles

DGrid range ses éléments en colonnes de largeur fixe, ligne par ligne ; sa hauteur suit le nombre de lignes. DTileLayout est un DIconLayout (grille qui passe à la ligne) qui accepte aussi SetBaseSize et SetMinHeight.

Méthode (DGrid)Description
SetCols(n), SetColWide(px), SetRowHeight(px) (et Get…)Nombre de colonnes (4), largeur d'une colonne (32), hauteur d'une ligne (32).
AddItem(panneau), RemoveItem(panneau, garder), GetItems()Ajouter, retirer (supprimé sauf si garder vaut true), lister.
SortByMember(clé, décroissant)Trie les éléments selon leur champ clé (panneau.prix…).
local grille = vgui.Create("DGrid", frame)
grille:Dock(TOP)
grille:SetCols(5)
grille:SetColWide(70)
grille:SetRowHeight(70)
for _, id in ipairs({ "builtin:crate", "builtin:barrel" }) do
	local icone = vgui.Create("SpawnIcon")
	icone:SetSize(64, 64)
	icone:SetModel(id)
	grille:AddItem(icone)
end

DAdjustableModelPanel : aperçu 3D orientable

Un DModelPanel sans rotation automatique : le joueur maintient le clic gauche ou droit et glisse pour tourner la caméra autour du modèle (à la même distance). Rappel OnCameraChanged(self) à chaque mouvement. SetFirstPerson et CaptureMouse sont acceptés, sans effet.

DFileBrowser : explorateur de fichiers

Un arbre des dossiers (4 niveaux) à gauche et la liste des fichiers à droite, dans le dossier de données client du gamemode en cours (lua_data_client/gamemode_<nom>/), même quand il est créé par un addon.

Méthode / rappelDescription
SetBaseFolder(dossier) / GetBaseFolder()Dossier racine affiché (à appeler en premier : il remplit l'arbre).
SetFileTypes("*.txt *.json") / GetFileTypes()Motifs des fichiers listés, séparés par des espaces ("*" par défaut).
SetCurrentFolder(dossier) / GetCurrentFolder()Dossier dont les fichiers sont listés.
SetPath(chemin) / GetPath()Second argument passé à file.Find ("DATA" par défaut ; sans effet, c'est toujours ton dossier de données).
fb.OnSelect(self, chemin, ligne), fb.OnDoubleClick(self, chemin, ligne), fb.OnRightClick(self, chemin, ligne)Fichier choisi, double-cliqué, clic droit ; chemin est relatif au dossier de données.
fb.OnFolderChanged(self, dossier)Le dossier affiché change.
local fb = vgui.Create("DFileBrowser", frame)
fb:Dock(FILL)
fb:SetBaseFolder("sauvegardes")
fb:SetFileTypes("*.json")
fb.OnDoubleClick = function(self, chemin)
	print("Fichier choisi :", chemin) -- relatif au dossier de données du gamemode
end

Peaux Derma

Une peau est une table de fonctions SKIN:Paint<Type>(panneau, l, h) qui dessinent les panneaux à la place du thème du jeu. Quand une peau est active, chaque panneau créé ensuite par vgui.Create est dessiné par la fonction de son type, sauf s'il a déjà son propre Paint. Fonctions reconnues : PaintFrame, PaintPanel, PaintButton, PaintTextEntry, PaintCheckBox, PaintComboBox, PaintListView, PaintProgress, PaintMenu, PaintMenuOption, PaintPropertySheet, PaintScrollPanel, PaintLabel.

FonctionDescription
derma.DefineSkin(nom, description, SKIN)Enregistre une peau et la renvoie.
derma.GetSkinTable(), derma.GetNamedSkin(nom)Toutes les peaux (par nom) ; une peau.
derma.GetDefaultSkin()La peau active : celle dont le hook client ForceDermaSkin renvoie le nom, sinon "Default" (le thème du jeu).
derma.SkinHook("Paint", "Button", panneau, l, h)Appelle SKIN:PaintButton(panneau, l, h) de la peau du panneau.
derma.Color(nom, panneau, défaut)Le champ nom de la peau du panneau, sinon défaut.
panel:SetSkin(nom) / panel:GetSkin()Change la peau d'un panneau dessiné par une peau / sa peau (la peau active si aucune).
local SKIN = {}
SKIN.fond = Color(30, 30, 40)

function SKIN:PaintFrame(panel, l, h)
	draw.RoundedBox(6, 0, 0, l, h, self.fond)
end

function SKIN:PaintButton(panel, l, h)
	local couleur = panel:IsHovered() and Color(90, 140, 230) or Color(60, 60, 80)
	draw.RoundedBox(4, 0, 0, l, h, couleur)
end

derma.DefineSkin("MaPeau", "Peau sombre de mon serveur", SKIN)
hook.Add("ForceDermaSkin", "MaPeau", function() return "MaPeau" end)
Au moment de la créationLa peau est choisie quand le panneau est créé : ajoute le hook ForceDermaSkin avant d'ouvrir tes fenêtres. Les menus du jeu lui-même ne changent pas.

DForm : formulaire de réglages

vgui.Create("DForm", parent) (aussi "ControlPanel", le même) empile des contrôles de haut en bas et grandit avec eux. C'est le panneau que reçoivent les options d'outils (spawnmenu.AddToolMenuOption). Les contrôles liés à une ConVar prennent sa valeur et la changent avec RunConsoleCommand.

MéthodeAjoute
form:SetName(texte) (aussi SetLabel)Un titre en gras en haut.
form:Help(texte), form:ControlHelp(texte) → DLabelUn texte d'aide (sur plusieurs lignes) ; le même en gris.
form:CheckBox(texte, convar) → DCheckBoxLabelUne case à cocher ("1" / "0").
form:NumSlider(texte, convar, min, max, décimales) → DNumSliderUn curseur numérique (2 décimales par défaut).
form:TextEntry(texte, convar) → DTextEntry, DLabelUn champ texte avec son étiquette à gauche.
form:ComboBox(texte, convar) → DComboBox, DLabelUne liste de choix (remplis-la avec AddChoice(texte, donnée) ; la ConVar reçoit la donnée, ou le texte).
form:Button(texte, commande, ...) → DButtonUn bouton qui lance RunConsoleCommand(commande, ...) (ou mets ton propre DoClick).
form:AddItem(gauche, droite)Tes propres panneaux : un seul en pleine largeur, ou deux sur une ligne (le premier à gauche, 110 px).
form:AddControl(type, données)Style ancien de GMod : "Header" et "Label" (Description / Text), "CheckBox", "Slider" (Label, Command, Min, Max, Type = "Integer"), "TextBox", "Button".
form:Clear()Retire tous les contrôles ajoutés.
CreateClientConVar("monaddon_couleur", "rouge", true, false)
CreateClientConVar("monaddon_taille", 1, true, false, "Taille", 0.5, 3)

local form = vgui.Create("DForm", fenetre)
form:Dock(TOP)
form:SetName("Mes réglages")
form:NumSlider("Taille", "monaddon_taille", 0.5, 3, 1)
local choix = form:ComboBox("Couleur", "monaddon_couleur")
choix:AddChoice("Rouge", "rouge")
choix:AddChoice("Bleu", "bleu")
form:ControlHelp("Prend effet à la prochaine apparition.")

Menu de spawn et menu contextuel

Les addons ajoutent leurs propres onglets au menu de spawn avec spawnmenu.AddCreationTab (un panneau vgui créé une fois et gardé), des options dans la liste des outils avec spawnmenu.AddToolMenuOption (un DForm), et des actions au menu contextuel (touche N maintenue : la souris est libre, on clique sur un objet) avec properties.Add. Hooks client : ContextMenuOpen, OnContextMenuOpen, OnContextMenuClose, PopulateToolMenu… Tout est décrit dans Intégration au bac à sable.

Traductions : language

Côté client, language.Add(clé, texte) enregistre une phrase et language.GetPhrase(clé) la renvoie (avec ou sans # devant ; la clé elle-même si elle est inconnue). Un texte qui commence par # est remplacé par sa phrase dans SetText, SetTitle, SetTooltip, SetPlaceholderText, AddChoice, AddColumn, AddLine, AddOption, AddSheet, AddSubMenu et AppendText (au moment de l'appel : ajoute les phrases avant de créer les panneaux).

language.Add("monaddon.acheter", "Acheter")
local bouton = vgui.Create("DButton", fenetre)
bouton:SetText("#monaddon.acheter")  -- affiche « Acheter »

Boîtes de dialogue toutes faites

FonctionEffet
Derma_Message(texte, titre, bouton)Message avec un bouton OK
Derma_Query(texte, titre, "Oui", fonction, "Non", fonction…)Question avec jusqu'à 4 boutons
Derma_StringRequest(titre, texte, défaut, siOK, siAnnulé)Demande un texte ; siOK(texte)
Derma_Query("Changer de carte ?", "Vote", "Oui", function()
	net.Start("vote") net.WriteBool(true) net.SendToServer()
end, "Non", function()
	net.Start("vote") net.WriteBool(false) net.SendToServer()
end)

Curseur et touche Échap

Touches et souris

Hooks PlayerButtonDown et PlayerButtonUp

Appelés quand le joueur appuie sur une touche ou un bouton de la souris, puis la relâche, avec (ply, touche). Ils existent des deux côtés : le client les reçoit pour son joueur (ply = LocalPlayer()), et le serveur les reçoit pour chaque joueur. Pas d'appel pendant que le joueur écrit (chat, champ de texte) ni quand un menu du jeu est ouvert. Les clics de souris ne comptent que quand la souris tient la caméra.

-- Serveur : capacité spéciale sur G, une fois toutes les 10 secondes.
local recharge = {}
hook.Add("PlayerButtonDown", "dash", function(ply, touche)
	if touche ~= KEY_G or not ply:Alive() then return end
	if (recharge[ply] or 0) > CurTime() then return end
	recharge[ply] = CurTime() + 10
	ply:SetPos(ply:GetPos() + Vector(0, 6, 0))
	ply:ChatPrint("Super saut !")
end)

Côté serveur, ply:IsKeyDown(touche) dit si une touche est tenue (pour une action « tant que la touche est enfoncée »). Le jeu limite chaque joueur à 40 appuis par seconde.

Bibliothèque input (client)

FonctionRenvoie
input.IsKeyDown(touche)Touche tenue (faux pendant la saisie de texte)
input.IsMouseDown(bouton)Bouton de souris tenu
input.IsButtonDown(code)Touche ou bouton tenu
input.IsShiftDown(), input.IsControlDown()Maj / Ctrl tenus
input.GetKeyName(touche)Nom affichable ("F3", "G", "Clic gauche"…)
input.IsTyping()Le joueur écrit-il (chat, champ de texte) ?
input.GetCursorPos()x, y du curseur

Codes des touches

Les constantes existent des deux côtés : KEY_A à KEY_Z, KEY_0 à KEY_9, KEY_PAD_0 à KEY_PAD_9, KEY_F1 à KEY_F12, KEY_SPACE, KEY_ENTER, KEY_TAB, KEY_BACKSPACE, KEY_ESCAPE, KEY_LSHIFT, KEY_LCONTROL, KEY_LALT, KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, KEY_INSERT, KEY_DELETE, KEY_HOME, KEY_END, KEY_PAGEUP, KEY_PAGEDOWN, KEY_CAPSLOCK, KEY_PAD_ENTER, KEY_PAD_PLUS, KEY_PAD_MINUS, KEY_PAD_MULTIPLY, KEY_PAD_DIVIDE, et pour la souris MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE, MOUSE_4, MOUSE_5.

Touches et clavier AZERTYUne lettre désigne la touche qui porte cette lettre : KEY_A est la touche A sur un clavier AZERTY comme QWERTY. Affiche toujours input.GetKeyName(touche) au joueur. Les touches du jeu (déplacements, Q, C, E…) continuent de marcher : écouter une touche ne la bloque pas, choisis plutôt des touches libres (F3, F4, G…).