Tutorial 28 - Práce s dialogy

Autor : Ruprt

Download = Tento tutorial + ukázka

 

 

Co je to vlastně dialog ?
Dialog je všem uživatelům PC dobře znám i přesto, že kolikrát vlastně ani nevědí, že se o dialogové okno právě jedná. Dialogem jsou veškerá okna aplikací sloužící k tomu, aby uživateli podaly nějakou zprávu nebo informaci, nebo aby od uživatele daná aplikace získala nějaká data nebo rozhodnutí. Typickým dialogovým oknem je například okno požadující po uživateli zadání jména a hesla při spouštění operačního systému Windows, nebo okénko zobrazené po provedení změny v souboru např. u aplikace MS Word dotazující se uživatele, zda-li mají být provedené změny uloženy. U OFP je typickým dialogovým oknem celý Editor misí nebo okénko spouštěné pomocí příkazu HintC. Když se to vezme kolem a kolem OFP celé je vlastně založeno na dialogových oknech -> brífingovým zápisníkem počínaje (zkuste si jen tak pro zajímavost jednou napsat do pole PŘI AKTIVACI jakéhokoli spínače vysílačky toto : check = CreateDialog "RscDisplayMainMap", a pak ji spusťte) a zobrazením cinemaborderu při zabití hráče konče (check = CreateDialog "RscDisplayMissionEnd").


K čemu v OFP potřebujeme dialogy ?
Dialog je velmi silný nástroj pomáhající tvůrci misí vytvářet mise mnohem zajímavější, větvené, poskytující hráči možnosti volby, jak se bude hra dále vyvíjet v závislosti na jeho rozhodnutí. Takto je jednoduše možné dát hráči na výběr tím, že v určité fázi hry se mu objeví dialogové okno, které mu nabídne např. takovéto možnosti : 1) bude pokračovat dál, sám a s větším rizikem, 2) Přivolá na pomoc další vojáky, 3) Úkol ležící před ním vynechá a bude pokračovat dál aniž by daný úkol splnil (a mise by dál nemohla pokračovat), nebo 4) Misi prostě ukončí, přičemž každá z těchto voleb bude nějak bodově ohodnocena (toto se jeví aplikovatelné především u kampaní) a podle dosaženého počtu bodů se bude kampaň dále vyvíjet.
V neposlední řadě může být dialogové okno velmi silným pomocníkem tvůrce při samotném vývoji a testování mise. Kdokoli si již alespoň jednou zkusil misi vytvořit, bude se mnou souhlasit, že by práce byla mnohem jednodušší, kdyby při při testovacím běhu mise věděl, na jakých souřadnicích se např. určitá jednotka nachází, jak daleko je nejbližší nepřítel, jakou zbraň právě používá raketometčík, který má odpálit BVPéčko (obvykle má zrovna v ruce jenom klacek) a kde se (ksakru) zrovna nachází podpůrná skupina. Tak tohle všechno lze jednoduše zjistit pomocí jednoho předem definovaného dialogového debug okna, které si spustíte např. nabídkou v akčním menu nebo prostě vysílačkou.

 

Popis logiky definice tříd dialogů - description.ext
Základem jakéhokoli dialogového okna je definice jeho tříd (class), které OFP vyhledává v souborech v pořadí :
1) description.ext - v adresáři mise
2) description.ext - v adresáři kampaně
3) resource.bin - v vdresáři \Res\Bin nebo\Bin hry OFP

Tyto třídy jsou vlastně stavební kameny jednotlivých prvků (objektů) dialogu jako jsou např. tlačítka, rámečky, texty, obrázky, comboboxy apod. (pozn. skládají se z vlastností, metod a funkcí - ale to není důležité vědět. Pokud však máte nějaké zkušenosti s některými programovacími jazyky jako jsou např. VisulaBasic, C++, Visual FoxPro, budete mít práci s pochopením tříd velmi usnadněnou).

 

Příklad vytvoření dialogu
Pro základní pochopení tvorby dialogu zde popíšu postup vytvoření a spuštění velmi oblíbeného okna začátečníků ve všech programovacích jazycích "Hello, World!", které doplním o combobox a funkci získání a zobrazení souřadnic jednotlivých vojáků ve skupině hráče. Misi provedeme ve dvojjazyčném zpracování.
Misi si umístíme na ostrov Desert Island (nejrychleji se načítá). Kamkoli (na pevninu :o)) si umístíme skupinu vojáků Západu a hráče uděláme velitelem. Poté si misi uložíme jako HelloWorld.
V adresáři HelloWorld, který nám hra sama vytvořila, najdeme pouze soubor mission.sqm. V tomto adresáři vytvoříme soubor description.ext (upozornění: pokaždé, když upravíte soubor desription.ext nebo stringtable.csv, je nutné misi znovu načíst aby se změny projevily) do kterého vepíšeme následující kód:

 

#define FontMAINCZ "SteelfishB64CE" // definice typu písma
#define FontNOTES "AudreysHandI48" // definice typu písma
#define FontS "tahomaB24" // definice typu písma
#define FontM "tahomaB36" // definice typu písma
// Control types
#define CT_STATIC 0 // statický typ (text, pozadí, obrázek apod.)
#define CT_BUTTON 1 // Tlačítka
#define CT_COMBO 4 // ComboBoxy
// Static styles
#define ST_LEFT 0 // zarovnání textu doleva
#define ST_RIGHT 1 // zarovnání textu doprava
#define ST_CENTER 2 // zarovnání textu na střed
#define ST_UP 3 // zarovnání textu nahoru
#define ST_DOWN 4 // zarovnání textu dolu
#define ST_VCENTER 5 // zarovnání textu vertikálně - otočí text o 90° po směru hodinových ručiček
#define ST_SINGLE 0 // do pole lze zadat pouze 1 řádek textu - nelze použít \n pro přechod na další řádek
#define ST_MULTI 16 // do pole lze zapsat více řádek textu - lze použít \n pro přechod na další řádek
#define ST_TITLE 32 // titulkový rámeček
#define ST_PICTURE 48 // obrázek
#define ST_3D_BORDER 80 // rámeček je vyplněn a má 3D okraj
#define ST_NO_BORDER 96 // rámeček bez okraje
#define ST_HUD_BACKGROUND 128 // typ podkladu - je průsvitný
#define ST_WITH_RECT 160 // rámeček bude mít viditelný okraj
#define ST_SHADOW 256 // písmo bude mít stín
#define ST_NO_RECT 512 // rámeček nebude mít viditelný okraj

 

(toto není tak moc důležité neboť se tím pouze přiřadily lépe zapamatovatelné názvy typům a stylům objektů - důležitá jsou čísla a ta jsou již předem nadefinována. Jinými slovy kód by fungoval i bez výše uvedeného, ale musely by se používat pouze čísla a ta se hůře pamatují)

Nyní si nadefinujeme základní třídy objektů dialogu (base control classes). Opět to není nutné, ale práce se tím podstatně zjednodušuje, neboť bez základních tříd by se musely všechny objekty definovat zvlášť, což zabírá moc času a mnohem víc řádek kódu. Takto vždy každý objekt odvodíme od již definované základní třídy. Takže :

class RscBackground
{
type=CT_STATIC
idc=-1
style=ST_3D_BORDER
x=0.150000;
y=0.150000;
w=0.700000;
h=0.700000;
text="";
colorBackground[]={1,1,1,1};
colorText[]={0,0,0,0};
font="tahomaB24";
sizeEx=0
};

class RscTitle
{
type=CT_STATIC
idc=-1
style=ST_TITLE + ST_CENTER;
x=0.150000;
y=0.164000;
w=0.700000;
h=0.060000;
text="";
colorBackground[]={1,1,0,1};
colorText[]={1,1,1,1};
font="tahomaB36";
sizeEx="1.0714 * 0.03";
};

class RscGroupBox
{
type=CT_STATIC
idc=-1
style=ST_NO_BORDER
text="";
colorBackground[]={0,0,0,0};
colorText[]={0,0,0,0};
font="tahomaB24";
sizeEx=0.020000;
};

class RscText
{
type=CT_STATIC
idc=-1
style=ST_LEFT + ST_MULTI + ST_NO_RECT;
lineSpacing=1.000000;
h=0.040000;
colorBackground[]={0,0,0,0};
colorText[]={0.080000,0.080000,0.120000,0.750000};
font="tahomaB24";
sizeEx=0.020000;
};

class RscPicture
{
type=CT_STATIC
idc=-1
style=ST_PICTURE
colorBackground[]={0,0,0,0};
colorText[]={1,1,1,1};
font="tahomaB24";
sizeEx=0
};

class RscButton
{
type=CT_BUTTON
style=ST_CENTER
w=0.110000;
h=0.050000;
colorText[]={0.080000,0.080000,0.120000,1};
font="tahomaB24";
sizeEx=0.020000;
default=0
soundPush[]={"ui\ui_ok",0.200000,1};
soundClick[]={"",0.200000,1};
soundEscape[]={"ui\ui_cc",0.200000,1};
};

class RscCombo
{
type=CT_COMBO
style=ST_LEFT
h=0.040000;
wholeHeight=0.250000;
colorSelect[]={0.350000,0.380000,0.360000,1};
colorText[]={0.080000,0.080000,0.120000,0.750000};
colorBackground[]={0.350000,0.380000,0.360000,0.750000};
font="tahomaB24";
sizeEx=0.020000;
};

Vysvětlivky :
idc - index třídy objektu (vlastně takový číselný název), zde většinou -1, neboť se jedná o base class
style - chování objektu (zarovnání apod.)
colorBackground - barva pozadí RGBA (1 = 255 = FF (HEX) a 0 = 0 = 00(HEX)) - A je průsvitnost
colorText - barva písma RGBA (1 = 255 = FF (HEX) a 0 = 0 = 00(HEX)) - A je průsvitnost
colorSelect - barva podkladu myší vybrané položky ComboBoxu - RGBA
font - typ písma (lze použít i ony definice např. FontMAINCZ, ale tady je lepší používat název písma)
lineSpacing - velikost mezery mezi řádky při stylu ST_MULTI
sizeEx - velikost písma
size - velikost písma
default - standardní/hlavní tlačítko (když se stiskne ENTER stiskne se tlačítko s default=1)
soundPush/Escape/Click - zvuk, který se přehraje při stisku tlačítka OK/Cancel/ostatní
x - vzdálenost levého horního rohu objektu od levého okraje obrazovky, 0-úplně vlevo, 1-úplně vpravo
y - vzdálenost levého horního rohu objektu od horního okraje obrazovky, 0-úplně nahoře, 1-úplně dole
w - šířka objektu
h - výška objektu
wholeHeight - výška rozbaleného ComboBoxu

No a teď když máme definovány základní třídy objektů, můžeme se vrhnout na samotnou tvorbu dialogového okna :

class RscHelloWorld
{
idd=1000
movingEnable=1
class Controls
{

// ----------------------------------------
// --------- Dialog Appearence ------------
// ----------------------------------------

class Background: RscBackground //vytvoří základní rámeček celého dialogu
{
x=0.250000;
y=0.250000;
w=0.500000;
h=0.500000;
};
class SubBackground: RscGroupBox // vytvoří druhý rámeček uvnitř základníh, čímž se zvýrazní
{
x=0.270000;
y=0.270000;
w=0.460000;
h=0.460000;
};

// ----------------------------------------
// -------------- TextBoxes ---------------
// ----------------------------------------


class Title: RscTitle //Rámeček titulku dialogu s nadpisem "Hello, World !"
{
idc=1001
x=0.350000;
y=0.270000;
w=0.300000;
h=0.060000;
text="$STR_DLG_Title";
};

class TextBox1: RscTitle //Rámeček pro výpis textů
{
idc=1002
x=0.290000;
y=0.350000;
w=0.420000;
h=0.180000;
text="";
sizeEx="0.022";
};
class TextBox2: RscText //Textové pole pro výpis textů uvnitř 1.rámečku
{
idc=1003
x=0.300000;
y=0.360000;
w=0.400000;
h=0.160000;
colorBackground[]={0,0,0,0};
colorText[]={1,1,0,1};
text="$STR_TextBox2_1";
sizeEx="0.024";
};


// ----------------------------------------
// -------------- Buttons -----------------
// ----------------------------------------

class GetData: RscButton // Tlačítko "Získej údaje"
{
idc=2001;
x=0.580000;
y=0.550000;
text=$STR_BTN_GetData;
action="Player Exec ""onClickGetData.sqs""";
};

class ButtonCancel: RscButton //Tlačítko "Cancel"
{
idc=2002
x=0.385000;
y=0.640000;
w=0.130000;
h=0.050000;
text="$STR_BTN_CANCEL";
action="CloseDialog 1000; sd = Nil";
};
// ----------------------------------------
// -------------- ComboBoxes --------------
// ----------------------------------------

class CBSoldiers: RscCombo //Vytvoří ComboBox
{
idc=3001
style = ST_TITLE + ST_CENTER;
x=0.315000;
y=0.565000;
w=0.250000;
h=0.025000;
colorSelect[]={1,1,1,1};
colorText[]={0,0,0,0.750000};
rowHeight=0.025000;
};

// ----------------------------------------
// --------------- Pictures ---------------
// ----------------------------------------

class RuprtLogo: RscPicture //Umístí do dialogu obrazek
{
idc=4001
text="ruprtslogo.jpg";
x=0.581900;
y=0.610000;
w=0.107500;
h=0.115000;
colorText[]={1,1,1,1};
};
};
};

A toť vše.
Údaj Action u tlačítek je vlastně metoda, která se spustí při stisku daného tlačítka a provede veškeré příkazy uvedené v uvozovkách za rovnítkem. Příkazů může být několik, musí však být odděleny středníkem. Nastavení této metody lze za běhu programu změnit příkazem ButtonSetAction.

Teď je nutné si vytvořit soubor stringtable.csv, aby mohly být získávány texty pro všechny objekty dialogu a do tohoto souboru zapsat následující :

Language, Czech, English, Comment

STR_DLG_Title, "Hello, World !", "Hello, World !",

STR_TextBox2_1, "\nVyberte z ComboBoxu níže vojáka, jehož\n\nsouřadnice se vám zobrazí v tomto rámečku.", "\nChoose a soldier from ComboBox bellow\n\nto display his coordinates.",
STR_TextBox2_2, "Voják ""%1"" je na souřadnicích :\nOsa X = %2\nOsa Y = %3\nOsa Z = %4\núhel = %5°\nnázev jednotky = %6", "Soldier ""%1"" is on coordinates :\nX axis = %2\nY axis = %3\nZ axis = %4\nangle = %5°\nname of unit = %6",

STR_BTN_GetData, Získej údaje, Get Data,
STR_BTN_CANCEL, Ukončit, Cancel,

STR_CB_Txt_0, Vyberte vojáka, Choose a soldier,
STR_AddAction_0, Spustit dialog, Start dialog,

STR_Hint, "Nyní máte otevřené Akční menu.\nStiskem první položky\n""Spustit dialog""\notevřete dialog ""Hello, World !""", "The Action menu is opened now.\nPress first item to open\n""Hello, World !"" dialog.",
STR_Error, "Nevybral jste žádného vojáka !!!", "You havn't selected any soldier !!!",

Teď již máme hotovo vše abychom mohli dialog spustit příkazem check = CreateDialog "RscHelloWorld". Ale....... Tento dialog se nám sice otevře, ale nebude zcela funkční, neboť je ještě zapotřebí vytvořit pár jednoduchých scriptů, pomocí kterých budeme chování dialogu ovládat.
První script přidá položku do Akčního menu, kterou se aktivuje dialog a napoví hráči co že to má vlastně udělat. Je to všem velmi dobře známý init.sqs, který obsahuje pouze :

player AddAction [Localize"STR_AddAction_0", "activatedialog.sqs"]
~0.5
Hint Localize"STR_Hint"
Exit

Druhý script je o něco složitější neboť má za úkol aktivovat dialog a iniciovat jeho impicitní nastavení. Je to script activatedialog.sqs, který obsahuje tento kód :

_check = CreateDialog "RscHelloWorld"

; ******* Zde lze vytvořit chybovou rutinu pro případ, že by se dialog neotevřel *****
;? _check: GoTo "OK"
;TitleText["Něco je špatně dialog nelze otevřít.\nZkuste restartovat misi, nebo tak něco", "Plain"]
;ForceEnd
;Exit
;#OK
;******************************************************************

; ------------- Nastaví hodnoty položek ComboBoxu s IDC 3001 ----------------------
_countgrp = Count (Units Group Player)
_x = 0
lbAdd [3001, Localize"STR_CB_Txt_0"]
#Loop1
_x = _x + 1
_index3001 = _index3001 + [lbAdd [3001, Name (Units Group Player Select _x)]]
? _x < (_countgrp - 1): GoTo "Loop1"

; ------------ Nastaví implicitní položku ComboBoxu ---------------------------
lbSetCurSel [3001, 0]

Exit

No a na závěr vytvoříme poslední, bohužel taky nejsložitější script, který má za úkol zjistit hráčem požadovaná data a vypsat je do textového pole dialogu. Tento script si pojmenujeme třeba onClickGetData.sqs, a to proto, že se spustí při kliknutí na tlačítko "Získej data". Soubor obsahuje následující kód :

; ------- Získat hodnoty z vybrané položky CombnoBoxu -------
_cbindex = lbCurSel 3001

; ******************** Chybová rutina ***************************
; ***** v případě, že hráč nevybere žádnou položku Combo boxu *****
? _cbindex == 0: Hint Localize"STR_Error"; Exit
; ******************************************************************

; ---------- Získat údaje o pozici vybraného vojáka ---------
_soldiername = lbText [3001, _cbindex]
_soldierpos = GetPos (Units Group Player Select _cbindex)
_soldierposx = _soldierpos Select 0
_soldierposy = _soldierpos Select 1
_soldierposz = _soldierpos Select 2
Goto "GetAngle"
#Return
_unitname = Units Group Player Select _cbindex

; ------------- Vypsat získané údaje do TextBoxu ------------
ctrlSetText [1003, Format[Localize"STR_TextBox2_2", _soldiername, _soldierposx, _soldierposy, _soldierposz, _angle, _unitname]]

Exit


; --------- Subprocedura pro získání úhlu mezi vojákem a hráčem -----------
; --------- Např. pro použití v příkazu SetDir, příkaz DoWatch ------------
; --------- totiž ne vždy spolehlivě pracuje ------------------------------
#GetAngle
_playerpos = GetPos player
_playerposx = _playerpos Select 0
_playerposy = _playerpos Select 1
_distancex = _soldierposx - _playerposx
_distancey = _soldierposy - _playerposy
_absgoniometry = Abs(_distancex) + Abs(_distancey)

_asinderived = _distancex / _absgoniometry
_acosderived = _distancey / _absgoniometry

? _asinderived >= 0 AND _acosderived >= 0: _angle = Asin(_asinderived)
? _asinderived >= 0 AND _acosderived < 0: _angle = 180 - Asin(_asinderived)
? _asinderived < 0 AND _acosderived < 0: _angle = 180 + Abs(Asin(_asinderived))
? _asinderived < 0 AND _acosderived >= 0: _angle = 360 - Abs(Asin(_asinderived))

Goto "Return"


No a teď je to již opravdu vše. Pokud jste postupovali přesně podle tohoto návodu, měl by se Vám dialog "Hello, World !" bezproblémů otevřít a správně pracovat. Pro jistotu si však vše co je výše uvedeno můžete stáhnout odstud.