Skip to content

Capitolo 3.9: Pattern UI dei Mod Reali

Home | << Precedente: Dialoghi e Modali | Pattern UI dei Mod Reali | Successivo: Widget Avanzati >>


Questo capitolo analizza i pattern UI trovati in sei mod professionali di DayZ: COT (Community Online Tools), VPP Admin Tools, DabsFramework, Colorful UI, Expansion e DayZ Editor. Ogni mod risolve problemi diversi. Studiare i loro approcci ti dà una libreria di pattern collaudati oltre ciò che la documentazione ufficiale copre.

Tutto il codice mostrato è estratto dal codice sorgente reale dei mod. I percorsi dei file fanno riferimento ai repository originali.


Perché Studiare i Mod Reali?

La documentazione di DayZ spiega i widget individuali e i callback degli eventi ma non dice nulla su:

  • Come gestire 12 pannelli admin senza duplicazione del codice
  • Come costruire un sistema di dialoghi con routing dei callback
  • Come applicare un tema a un'intera UI senza toccare i file layout vanilla
  • Come sincronizzare una griglia di mercato con i dati del server via RPC
  • Come strutturare un editor con undo/redo e un sistema di comandi

Questi sono problemi di architettura. Ogni mod grande inventa soluzioni per essi. Alcuni sono eleganti, altri sono storie ammonitorie. Questo capitolo mappa i pattern così puoi scegliere l'approccio giusto per il tuo progetto.


Pattern UI di COT (Community Online Tools)

COT è lo strumento admin DayZ più usato. La sua architettura UI è costruita attorno a un sistema modulo-form-finestra dove ogni strumento (ESP, Player Manager, Teleport, Object Spawner, ecc.) è un modulo autocontenuto con il proprio pannello.

Architettura Modulo-Form-Finestra

COT separa le responsabilità in tre strati:

  1. JMRenderableModuleBase -- Dichiara i metadati del modulo (titolo, icona, percorso layout, permessi). Gestisce il ciclo di vita della CF_Window. Non contiene logica UI.
  2. JMFormBase -- Il pannello UI effettivo. Estende ScriptedWidgetEventHandler. Riceve eventi widget, costruisce elementi UI, comunica con il modulo per le operazioni sui dati.
  3. CF_Window -- Il contenitore della finestra fornito dal framework CF. Gestisce trascinamento, ridimensionamento, chrome di chiusura.

Un modulo si dichiara con degli override:

c
class JMExampleModule: JMRenderableModuleBase
{
    void JMExampleModule()
    {
        GetPermissionsManager().RegisterPermission("Admin.Example.View");
        GetPermissionsManager().RegisterPermission("Admin.Example.Button");
    }

    override bool HasAccess()
    {
        return GetPermissionsManager().HasPermission("Admin.Example.View");
    }

    override string GetLayoutRoot()
    {
        return "JM/COT/GUI/layouts/Example_form.layout";
    }

    override string GetTitle()
    {
        return "Example Module";
    }

    override string GetIconName()
    {
        return "E";
    }

    override bool ImageIsIcon()
    {
        return false;
    }
}

Punto chiave: Ogni strumento è completamente autocontenuto. Aggiungere un nuovo strumento admin significa creare una classe Module, una classe Form, un file layout e inserire una riga nel costruttore. Nessun codice esistente cambia.

UI Programmatica con UIActionManager

COT non costruisce form complesse nei file layout. Usa una classe factory (UIActionManager) che crea widget di azione UI standardizzati a runtime:

c
override void OnInit()
{
    m_Scroller = UIActionManager.CreateScroller(layoutRoot.FindAnyWidget("panel"));
    Widget actions = m_Scroller.GetContentWidget();

    // Layout a griglia: 8 righe, 1 colonna
    m_PanelAlpha = UIActionManager.CreateGridSpacer(actions, 8, 1);

    // Tipi di widget standard
    m_Text = UIActionManager.CreateText(m_PanelAlpha, "Label", "Value");
    m_EditableText = UIActionManager.CreateEditableText(
        m_PanelAlpha, "Name:", this, "OnChange_EditableText"
    );
    m_Slider = UIActionManager.CreateSlider(
        m_PanelAlpha, "Speed:", 0, 100, this, "OnChange_Slider"
    );
    m_Checkbox = UIActionManager.CreateCheckbox(
        m_PanelAlpha, "Enable Feature", this, "OnClick_Checkbox"
    );
    m_Button = UIActionManager.CreateButton(
        m_PanelAlpha, "Execute", this, "OnClick_Button"
    );
}

L'approccio factory significa: dimensionamento e spaziatura coerenti in tutti i pannelli, nessuna duplicazione dei file layout, nuovi tipi di azione possono essere aggiunti una volta e usati ovunque.

Overlay ESP (Disegno su CanvasWidget)

Il sistema ESP di COT disegna etichette, barre di salute e linee direttamente sul mondo 3D usando CanvasWidget. I widget ESP vengono creati da layout prefab e posizionati ogni frame proiettando posizioni 3D in coordinate schermo.

Dialoghi di Conferma

COT fornisce un sistema di conferma basato su callback integrato in JMFormBase. Le conferme sono create con callback nominati:

c
CreateConfirmation_Two(
    JMConfirmationType.INFO,
    "Are you sure?",
    "This will kick the player.",
    "#STR_COT_GENERIC_YES", "OnConfirmKick",
    "#STR_COT_GENERIC_NO", ""
);

Questo permette il concatenamento delle conferme (una conferma ne apre un'altra) senza codificare rigidamente il flusso.


Pattern UI di VPP Admin Tools

VPP adotta un approccio diverso da COT: usa UIScriptedMenu con un HUD a barra degli strumenti, sotto-finestre trascinabili e un sistema di dialog box globale.

Registrazione dei Pulsanti della Toolbar

VPPAdminHud mantiene una lista di definizioni di pulsanti. Ogni pulsante mappa una stringa di permesso a un nome visualizzato, un'icona e un tooltip. I mod esterni possono fare override di DefineButtons() per aggiungere i propri pulsanti alla toolbar, rendendo VPP estensibile senza modificare il suo sorgente.

Sistema di Sotto-Finestre

Ogni pannello strumento estende AdminHudSubMenu, che fornisce comportamento di finestra trascinabile, toggle mostra/nascondi e gestione della priorità della finestra:

c
class AdminHudSubMenu: ScriptedWidgetEventHandler
{
    protected Widget M_SUB_WIDGET;
    protected Widget m_TitlePanel;

    void ShowSubMenu()
    {
        m_IsVisible = true;
        M_SUB_WIDGET.Show(true);
        VPPAdminHud rootHud = VPPAdminHud.Cast(
            GetVPPUIManager().GetMenuByType(VPPAdminHud)
        );
        rootHud.SetWindowPriorty(this);
        OnMenuShow();
    }

    // Supporto trascinamento via barra del titolo
    override bool OnDrag(Widget w, int x, int y)
    {
        if (w == m_TitlePanel)
        {
            M_SUB_WIDGET.GetPos(m_posX, m_posY);
            m_posX = x - m_posX;
            m_posY = y - m_posY;
            return false;
        }
        return true;
    }
}

Punto chiave: VPP costruisce un mini window manager dentro DayZ. Ogni sotto-menu è una finestra trascinabile, ridimensionabile con gestione del focus.

VPPDialogBox -- Dialogo Basato su Callback

Il sistema di dialoghi di VPP usa un approccio guidato da enum. Il dialogo mostra/nasconde pulsanti basandosi su un tipo enum, e instrada il risultato attraverso CallFunction:

c
enum DIAGTYPE
{
    DIAG_YESNO,
    DIAG_YESNOCANCEL,
    DIAG_OK,
    DIAG_OK_CANCEL_INPUT
}

class VPPDialogBox extends ScriptedWidgetEventHandler
{
    private Class   m_CallBackClass;
    private string  m_CbFunc = "OnDiagResult";

    private void OnOutCome(int result)
    {
        GetGame().GameScript.CallFunction(m_CallBackClass, m_CbFunc, null, result);
        delete this;
    }
}

Pattern UI di DabsFramework

DabsFramework introduce un'architettura MVC (Model-View-Controller) completa per la UI di DayZ. È usato da DayZ Editor e Expansion come fondamento UI.

ViewController e Data Binding

L'idea centrale: invece di trovare manualmente i widget e impostare il loro testo, dichiari proprietà su una classe controller e le leghi ai widget per nome nell'editor di layout.

c
class TestController: ViewController
{
    // Il nome della variabile corrisponde a Binding_Name nel layout
    string TextBox1 = "Initial Text";
    int TextBox2;
    bool WindowButton1;

    void SetWindowButton1(bool state)
    {
        WindowButton1 = state;
        NotifyPropertyChanged("WindowButton1");
    }

    override void PropertyChanged(string propertyName)
    {
        switch (propertyName)
        {
            case "WindowButton1":
                Print("Button state: " + WindowButton1);
                break;
        }
    }
}

Il binding bidirezionale significa che i cambiamenti nel widget (l'utente che digita) si propagano automaticamente alla proprietà del controller.

ObservableCollection -- Data Binding per Liste

Per liste dinamiche, DabsFramework fornisce ObservableCollection<T>. Le operazioni di inserimento/rimozione aggiornano automaticamente il widget legato:

c
class MyController: ViewController
{
    ref ObservableCollection<string> ItemList;

    void MyController()
    {
        ItemList = new ObservableCollection<string>(this);
        ItemList.Insert("Item A");
        ItemList.Insert("Item B");
    }
}

Ogni Insert() lancia un evento CollectionChanged, che il ViewBinding intercetta per creare/distruggere widget figli. Nessuna gestione manuale dei widget necessaria.

ScriptView -- Layout da Codice

ScriptView è l'alternativa tutto-script a OnWidgetScriptInit. La sottoclassi, fai override di GetLayoutFile() e la istanzi. Il costruttore carica il layout, trova il controller e collega tutto.

RelayCommand -- Binding Pulsante-Azione

I pulsanti possono essere legati a oggetti RelayCommand tramite la proprietà reference Relay_Command nel ViewBinding. Questo disaccoppia i click dei pulsanti dagli handler.

Punto chiave: DabsFramework elimina il boilerplate. Dichiari i dati, li leghi per nome, e il framework gestisce la sincronizzazione. Il costo è la curva di apprendimento e la dipendenza dal framework.


Pattern di Colorful UI

Colorful UI sostituisce i menu vanilla di DayZ con versioni tematizzate senza modificare i file script vanilla. Il suo approccio si basa interamente su override modded class e un sistema centralizzato di colori/branding.

Sistema a Tema a 3 Strati

I colori sono organizzati in tre livelli:

Strato 1 -- UIColor (palette base): Valori colore grezzi con nomi semantici.

Strato 2 -- colorScheme (mappatura semantica): Mappa concetti UI ai colori della palette. Gli owner del server cambiano questo strato per tematizzare il proprio server.

Strato 3 -- Branding/Settings (identità server): Percorsi logo, URL, toggle funzionalità.

Modifica Non Distruttiva della UI Vanilla

Colorful UI sostituisce i menu vanilla usando modded class. Ogni sottoclasse UIScriptedMenu vanilla viene moddata per caricare un file layout personalizzato e applicare i colori del tema:

c
modded class MainMenu extends UIScriptedMenu
{
    override Widget Init()
    {
        layoutRoot = GetGame().GetWorkspace().CreateWidgets(
            "Colorful-UI/GUI/layouts/menus/cui.mainMenu.layout"
        );

        // Applica i colori del tema
        if (m_TopShader) m_TopShader.SetColor(colorScheme.TopShader());
        if (m_BottomShader) m_BottomShader.SetColor(colorScheme.BottomShader());

        Branding.ApplyLogo(m_Logo);
        return layoutRoot;
    }
}

Punto chiave: Colorful UI dimostra che puoi ritematizzare l'intero client DayZ senza codice lato server, usando solo override modded class, file layout personalizzati e un sistema di colori centralizzato.


Pattern UI di Expansion

DayZ Expansion è il più grande ecosistema di mod della comunità. La sua UI spazia da toast di notifica a interfacce complete di trading con sincronizzazione server.

Sistema di Notifiche (Tipi Multipli)

Expansion definisce sei tipi visivi di notifica, ciascuno con il proprio layout. Le notifiche vengono create da qualsiasi punto (client o server) usando un'API statica:

c
NotificationSystem.Create_Expansion(
    "Trade Complete",          // titolo
    "You purchased M4A1",     // testo
    "market_icon",             // nome icona
    ARGB(255, 50, 200, 50),   // colore
    7,                         // tempo di visualizzazione (secondi)
    sendTo,                    // PlayerIdentity (null = tutti)
    ExpansionNotificationType.MARKET  // tipo
);

L'ExpansionMarketMenu è una delle UI più complesse in qualsiasi mod di DayZ. Gestisce: albero delle categorie con sezioni comprimibili, griglia oggetti con filtro di ricerca, visualizzazione prezzi acquisto/vendita, controlli quantità, widget anteprima oggetto, anteprima inventario giocatore, selettori dropdown per skin, checkbox configurazione accessori e dialoghi di conferma per acquisti/vendite.

Punto chiave: Per UI interattive complesse, Expansion combina l'MVC di DabsFramework con riferimenti diretti ai widget. Il controller gestisce il data binding per liste e testo, mentre i riferimenti diretti ai widget gestiscono widget specializzati come ItemPreviewWidget e PlayerPreviewWidget che necessitano di controllo imperativo.


Pattern UI di DayZ Editor

DayZ Editor è uno strumento completo di posizionamento oggetti costruito come mod di DayZ. Usa DabsFramework estensivamente e implementa pattern tipicamente trovati in applicazioni desktop: toolbar, menu, property inspector, sistema di comandi con undo/redo.

Pattern dei Comandi con Scorciatoie da Tastiera

Il sistema di comandi dell'Editor disaccoppia le azioni dagli elementi UI. Ogni azione è una sottoclasse EditorCommand:

c
class EditorUndoCommand: EditorCommand
{
    protected override bool Execute(Class sender, CommandArgs args)
    {
        super.Execute(sender, args);
        m_Editor.Undo();
        return true;
    }

    override ShortcutKeys GetShortcut()
    {
        return { KeyCode.KC_LCONTROL, KeyCode.KC_Z };
    }

    override bool CanExecute()
    {
        return GetEditor().CanUndo();
    }
}

I comandi si integrano con il RelayCommand di DabsFramework così i pulsanti della toolbar si disattivano automaticamente quando CanExecute() restituisce false.

HUD con Pannelli Data-Bound

Il controller HUD dell'editor usa ObservableCollection per tutti i pannelli lista. Aggiungere un oggetto alla scena lo aggiunge automaticamente alla lista nella sidebar. Cancellare lo rimuove. Nessuna creazione/distruzione manuale dei widget.


Pattern Architetturali UI Comuni

Questi pattern appaiono in più mod. Rappresentano il consenso della comunità su come risolvere problemi UI ricorrenti in DayZ.

Panel Manager (Mostra/Nascondi per Nome o Tipo)

Sia VPP che COT mantengono un registro di pannelli UI accessibili per typename. Questo previene pannelli duplicati e fornisce un singolo punto di controllo per la visibilità.

Riciclo dei Widget per le Liste

Quando si visualizzano liste grandi, i mod evitano di creare/distruggere widget ad ogni aggiornamento. Mantengono invece un pool:

c
// Pattern semplificato usato tra i mod
void UpdatePlayerList(array<PlayerInfo> players)
{
    // Nascondi i widget in eccesso
    for (int i = players.Count(); i < m_PlayerWidgets.Count(); i++)
        m_PlayerWidgets[i].Show(false);

    // Crea nuovi widget solo se necessario
    while (m_PlayerWidgets.Count() < players.Count())
    {
        Widget w = GetGame().GetWorkspace().CreateWidgets(PLAYER_ENTRY_LAYOUT, m_ListParent);
        m_PlayerWidgets.Insert(w);
    }

    // Aggiorna i widget visibili con i dati
    for (int j = 0; j < players.Count(); j++)
    {
        m_PlayerWidgets[j].Show(true);
        SetPlayerData(m_PlayerWidgets[j], players[j]);
    }
}

Creazione Widget Lazy

Diversi mod differiscono la creazione dei widget fino alla prima visualizzazione. Questo evita di caricare tutti i pannelli admin all'avvio quando la maggior parte non verrà mai aperta.

OnWidgetScriptInit come Punto di Ingresso Universale

Ogni mod studiato usa OnWidgetScriptInit come meccanismo di binding layout-script:

c
void OnWidgetScriptInit(Widget w)
{
    m_root = w;
    m_root.SetHandler(this);

    // Trova widget figli
    m_Button = ButtonWidget.Cast(m_root.FindAnyWidget("button_name"));
    m_Text = TextWidget.Cast(m_root.FindAnyWidget("text_name"));
}

Questo viene impostato tramite la proprietà scriptclass nel file layout. Il motore chiama OnWidgetScriptInit automaticamente quando CreateWidgets() elabora un widget con una script class.


Anti-Pattern da Evitare

Questi errori appaiono nel codice reale dei mod e causano problemi di prestazioni o crash.

Creare Widget Ogni Frame

La creazione di widget alloca memoria e attiva il ricalcolo del layout. A 60 FPS questo crea 60 widget al secondo. Crea sempre una volta e aggiorna sul posto.

Non Pulire gli Event Handler

Ogni Insert su uno ScriptInvoker o coda di aggiornamento necessita di un Remove corrispondente nel distruttore. Gli handler orfani causano chiamate a oggetti eliminati e crash per accesso null.

Codificare Posizioni in Pixel

Usa sempre posizionamento proporzionale (0.0-1.0) o lascia che i widget contenitore gestiscano il layout. Le posizioni in pixel funzionano solo alla risoluzione per cui sono state progettate.

Annidamento Profondo dei Widget Senza Motivo

Ogni livello di annidamento aggiunge overhead al calcolo del layout. Se un widget intermedio non serve a nulla (nessuno sfondo, nessun vincolo di dimensione, nessuna gestione eventi), rimuovilo. Appiattisci le gerarchie dove possibile.

Ignorare la Gestione del Focus

Senza SetFocus(), gli eventi tastiera possono ancora andare ai widget dietro il dialogo.

Dimenticare la Pulizia dei Widget alla Distruzione

Se crei widget con CreateWidgets(), li possiedi. Chiama Unlink() sulla root nel tuo distruttore. ScriptView e UIScriptedMenu gestiscono questo automaticamente, ma le sottoclassi di ScriptedWidgetEventHandler devono farlo manualmente.


Riepilogo: Quale Pattern Usare e Quando

NecessitàPattern ConsigliatoMod Sorgente
Pannello strumento sempliceScriptedWidgetEventHandler + OnWidgetScriptInitVPP
UI complessa data-boundScriptView + ViewController + ObservableCollectionDabsFramework
Sistema pannelli adminModule + Form + Window (pattern registrazione moduli)COT
Sotto-finestre trascinabiliAdminHudSubMenu (gestione trascinamento barra titolo)VPP
Dialogo di confermaVPPDialogBox o JMConfirmation (basato su callback)VPP / COT
Popup con inputPattern PopUpCreatePreset (delete this alla chiusura)VPP
Menu a schermo interoExpansionScriptViewMenu (blocco controlli, blur, timer)Expansion
Sistema tema/colori3 strati (palette, schema, branding) con modded classColorful UI
Override UI vanillamodded class + file .layout sostitutiviColorful UI
Sistema di notificheEnum tipo + layout per-tipo + API di creazione staticaExpansion
Sistema comandi toolbarEditorCommand + EditorCommandManager + scorciatoieDayZ Editor
Barra menu con elementiEditorMenu + ObservableCollection<EditorMenuItem>DayZ Editor
Overlay ESP/HUDCanvasWidget a schermo intero + posizionamento widget proiettatoCOT
Varianti per risoluzioneDirectory layout separate (stretto/medio/largo)Colorful UI
Prestazioni liste grandiPool di riciclo widget (nascondi/mostra, crea su richiesta)Comune
ConfigurazioneVariabili statiche (mod client) o JSON via config managerColorful UI

Diagramma Decisionale

  1. È un pannello semplice una tantum? Usa ScriptedWidgetEventHandler con OnWidgetScriptInit. Costruisci il layout nell'editor, trova i widget per nome.

  2. Ha liste dinamiche o dati che cambiano frequentemente? Usa il ViewController di DabsFramework con ObservableCollection. Il data binding elimina gli aggiornamenti manuali dei widget.

  3. Fa parte di uno strumento admin multi-pannello? Usa il pattern modulo-form di COT. Ogni strumento è autocontenuto con il proprio modulo, form e layout. La registrazione è una singola riga.

  4. Deve sostituire la UI vanilla? Usa il pattern di Colorful UI: modded class, file layout personalizzato, schema colori centralizzato.

  5. Ha bisogno di sincronizzazione dati server-client? Combina qualsiasi pattern sopra con RPC. Il menu mercato di Expansion mostra come gestire stati di caricamento, cicli richiesta/risposta e timer di aggiornamento all'interno di una ScriptView.

  6. Ha bisogno di undo/redo o interazione complessa? Usa il pattern dei comandi di DayZ Editor. I comandi disaccoppiano le azioni dai pulsanti, supportano scorciatoie e si integrano con il RelayCommand di DabsFramework per abilitazione/disabilitazione automatica.


Prossimo capitolo: Widget Avanzati -- Formattazione RichTextWidget, disegno CanvasWidget, marker MapWidget, ItemPreviewWidget, PlayerPreviewWidget, VideoWidget e RenderTargetWidget.

Released under CC BY-SA 4.0 | Code examples under MIT License