HU/Scripting the GUI - Tutorial 1

From Multi Theft Auto: Wiki

GUI készítése - Tutorial 1 (Gridlists)

Ebben a tutoriálban fel fogjuk fedezni a gridlist-ek használatát egy járműválasztási kijelzőn egy tetszőleges kliensoldali jármű xml olvasással.

Ne feledje, hogy ez a tutoriál feltételezi azt, hogy tisztában van az előző tutoriálban bemutatott összes tartalommal


A jármű kiválasztásának létrehozása

A GUI létrehozása

Kezdésként nyisson meg egy client oldali lua fájlt (ha követte a bevezetés a scriptelésbe oldalt, akkor ez a gui.lua lesz) amivel majd dolgozni fog.

Ebben a fájlban elfogjuk kezdeni megírni a saját function-ünket a GUI létrehozásához. Ahogy azt az előző tutoriálban említettük, a gui létrehozásakor 2 értéktípus közül tudunk választani: relative és absolute.

Ennek a tutoriálnak a céljából most absolute értékeket fogunk használni.


Ez létre fog hozni egy egyszerű GUI ablakot, egy gridlist-et, és egy gombot:

function createVehicleSelection()
	-- get the screen width and height
	local sWidth, sHeight = guiGetScreenSize()
	
	-- use some simple maths to position the window in the centre of the screen
	local Width,Height = 376,259
	local X = (sWidth/2) - (Width/2)
	local Y = (sHeight/2) - (Height/2)
	windowVehicleSelection = guiCreateWindow(X,Y,Width,Height,"Vehicle Selection Window",false)
	
	gridlistVehicleSelection = guiCreateGridList(10,26,357,192,false,windowVehicleSelection)
	-- add a "Vehicle" and a "Type" collumn to the gridlist
	guiGridListAddColumn(gridlistVehicleSelection,"Vehicle",0.2)
	guiGridListAddColumn(gridlistVehicleSelection,"Type",0.2)
	
	-- set the default width of the columns to roughly half the size of the gridlist (each)
	guiGridListSetColumnWidth(gridlistVehicleSelection,1,0.4,true)
	guiGridListSetColumnWidth(gridlistVehicleSelection,2,0.5,true)
	
	buttonCreate = guiCreateButton(121,227,120,20,"Create",false,windowVehicleSelection)
	
	-- add the event handler for when the button is clicked
	addEventHandler("onClientGUIClick",buttonCreate,createVehicleHandler,false)
	
	-- hide the GUI
	guiSetVisible(windowVehicleSelection,false)
	
	-- this will add all the vehicle options onto the gridlist, it is explained later in this tutorial
	populateGridlist()
end

Most ezt a function-t meg is kell hívnunk valamivel, különben a GUI sosem lesz létrehozva. Ahogy az előző tutoriálban is az onClientResourceStart-ot hívtuk segítségül, hogy elvégezze ezt a feladatot.

-- when the resource is started, create the GUI and hide it
addEventHandler("onClientResourceStart",getResourceRootElement(getThisResource()),
	function()
		createVehicleSelection()
	end
)

A GUI megjelenítése

Az előző tutoriállal ellentétben itt azt akarjuk, hogy a játékos képes legyen az ablakot megnyitni, amikor csak szeretné, és ne akkor, amikor a resource elindul. Így hozzáadhatunk egy parancsot ennek elvégzéséhez.

-- create the function that will show the window
function showVehicleSelection()
	-- if the window isnt visible, show it
	if not guiGetVisible(windowVehicleSelection) then
		guiSetVisible(windowVehicleSelection,true)
		
		showCursor(true,true)
	end
end

-- add the command /vehicleselection and set it to call the showVehicleSelection function
addCommandHandler("vehicleselection",showVehicleSelection)

A Gridlist feltöltése egy táblából

Most, hogy van egy alap GUI-nk, fel is kell töltenünk a gridlistet az összes járművünkel.

Ezt kezdésként egy egyszerű client oldali táblával fogjuk megcsinálni. A tutoriál későbbi részében ezt a módszert kibővítjük egy .xml fájl használatával az információk tárolásához. Kezdésként létrehozunk egy táblát, mely tartalmazza a kiválasztott járműveket, amelyeket majd le tudunk spawnolni.

A tábla tartalmazza a járműveket ebben a formában ["description"] = vehicle_id :

-- create the table and fill it with a selection of vehicle ids and names
local vehicleSelectionTable = {
	["Sparrow"] = 469,
	["Stuntplane"] = 513,
	["BF-400"] = 581,
	["Freeway"] = 463,
	["Speeder"] = 452,
	["Jester"] = 559,
	["Sabre"] = 475,
	["Police Ranger"] = 599,
	["Utility Van"] = 552,
	["Tug"] = 583
}

Helyezze ezt a kódot a script legtetejére (nem kell, hogy egy funkción belül legyen).

Most megírhatjuk a "populateGridlist" function, amely feltölti a Gridlist-et a táblázatban szereplő összes járművel. Ehhez egyszerűen csak végig kell mennünk a táblában szereplő értékeken, és hozzáadni a gridlist-hez. Valamint beállítjuk a rejtett adatokat a guiGridListSetItemData használatával, hogy tároljuk a járművek id-jét:

function populateGridlist()
	-- loop through the table
	for name,vehicle in pairs(vehicleSelectionTable) do
		-- add a new row to the gridlist
		local row = guiGridListAddRow(gridlistVehicleSelection)

		-- set the text in the first column to the vehicle name
		guiGridListSetItemText(gridlistVehicleSelection,row,1,name,false,false)
		-- set the text in the second column to the vehicle type
		guiGridListSetItemText(gridlistVehicleSelection,row,2,getVehicleType(vehicle),false,false)
		
		-- set the data for gridlist slot as the vehicle id
		guiGridListSetItemData(gridlistVehicleSelection,row,1,tostring(vehicle))
	end
end

A klikk kezelése

Most, hogy elkészült a GUI-nk, észlelnünk is kell minden kattintást a "Create" gombon. Már hozzácsatoltuk az onClientGUIClick event-et a buttonCreate-hez, így most meg kell írjuk azt a function-t, ami meghívja. Ebben a function-ben elvégzünk néhány alapvető hibaellenőrzést, mint például, hogy meggyőződjünk arról, hogy a jármű az a listából lett-e kiválasztva. Ezután használhatunk néhány számítási feladatot, hogy megtaláljuk a játékos pozícióját, majd elküldjök ezt az infórmációt a szervernek, hogy lespawnolja a járművet a játékos elé (triggerServerEvent használatával)

function createVehicleHandler(button,state)
	if button == "left" and state == "up" then
		-- get the selected item in the gridlist
		local row,col = guiGridListGetSelectedItem(gridlistVehicleSelection)
		
		-- if something is selected
		if row and col and row ~= -1 and col ~= -1 then
			-- get the vehicle id data from the gridlist that is selected
			local selected = guiGridListGetItemData(gridlistVehicleSelection,row,col)
			
			-- make sure the vehicle id is a number not a string
			selected = tonumber(selected)
						
			-- get the players position and rotation
			local rotz = getPedRotation(getLocalPlayer())
			local x,y,z = getElementPosition(getLocalPlayer())
			-- find the position directly infront of the player
			x = x + ( math.cos ( math.rad ( rotz+90 ) ) * 3)
			y = y + ( math.sin ( math.rad ( rotz+90 ) ) * 3)
			
			if selected and x and y and z then
				-- trigger the server
				triggerServerEvent("createVehicleFromGUI",getRootElement(),selected,x,y,z)
				
				-- hide the gui and the cursor
				guiSetVisible(windowVehicleSelection,false)
				showCursor(false,false)
			else
				outputChatBox("Invalid arguments.")
			end
		else
			-- otherwise, output a message to the player
			outputChatBox("Please select a vehicle.")
		end
	end
end

A jármű létrehozása

Ezen a ponton az összes szükséges client oldali kódunk már megvan, szóval nyisson meg egy szerver oldalu .lua fájlt, amivel majd dolgozni fog.

Mindenekelőtt a szerver oldalon meg kell adnunk egy egyedi event-et, amit már meghívtunk a client oldalról. Ezt megtehetjük az addEvent és az addEventHandler használatával. Végezetűl szükségünk lesz egy pici function-re, hogy létrehozzuk a járművet:

function createMyVehicle(vehicleid,x,y,z)
	-- check all the arguments exist
	if vehicleid and x and y and z then
		createVehicle(vehicleid,x,y,z)
	end
end

addEvent("createVehicleFromGUI",true)
addEventHandler("createVehicleFromGUI",root,createMyVehicle)

Vegye figyelembe a "root" változó használatát. Ez egy MTA változó, amely tartalmazza a root elemet; minden resource-nak van egy.

Ezzel be is fejeztük a tutoriál első részét. Mostanra rendelkeznie kell egy alap, működő jármű spawnoló script-el.

A második részben megnézzük a gridlist adatok kezelhetőségét, és a client oldali xml fájlok olvasását.

A kód bővítése

Ez a rész számos módon fogja részletezni a már meglévő kódunk fejlesztését.

Adatok importálása

Egy nagy előrehaladás az előző módszerhez képest, hogy külső fájlokat használunk a jármű információinak tárolásához, ez lehetővé teszi a számunkra, hogy gyorsabban és egyszerűbben kezeljünk nagy mennyiségű adatokat. Ebben a tutoriálban clinet oldali xml fájlt fogunk használni az információ tárolásához.

Mindenek előtt létre kell hoznia egy xml fájlt, szóval keresse meg a resource könyvtárát, és hozzon létre egy új vehicles.xml fájlt:

Ennek a tutoriálnak a céljából csak egy kis részét fogjuk felvenni a teljes járműkészletből, azonban a fájl könnyen bővíthető, így a későbbiekben majd az összeset hozzá fogjuk tudni adni

Ha még nem biztos az xml adatok felépítésében, akkor böngézhet a MTA xml function-ok között a további információkért.

<root>
	<group type="Bikes">
		<vehicle id="581" name="BF-400"/>
		<vehicle id="463" name="Freeway"/>
		<vehicle id="481" name="BMX"/>
	</group>
	<group type="Boats">
		<vehicle id="472" name="Coastguard"/>
		<vehicle id="452" name="Speeder"/>
	</group>
	<group type="Helicopters">
		<vehicle id="487" name="Maverick"/>
		<vehicle id="469" name="Sparrow"/>	
	</group>
	<group type="Planes">
		<vehicle id="593" name="Dodo"/>
		<vehicle id="513" name="Stuntplane"/>	
	</group>
	<group type="Sports Cars">
		<vehicle id="565" name="Flash"/>
		<vehicle id="559" name="Jester"/>
		<vehicle id="477" name="ZR-350"/>	
	</group>
	<group type="2-Door">
		<vehicle id="474" name="Hermes"/>
		<vehicle id="475" name="Sabre"/>
	</group>
	<group type="Emergency">
		<vehicle id="416" name="Ambulance"/>
		<vehicle id="599" name="Police ranger"/>
	</group>
	<group type="Industrial">
		<vehicle id="573" name="Dune"/>
		<vehicle id="552" name="Utility van"/>	
	</group>
	<group type="Misc">
		<vehicle id="457" name="Caddy"/>
		<vehicle id="583" name="Tug"/>
	</group>
</root>

Miután létrehozta a fájlt, ne felejtse el hozzáadni a resource meta.xml fájljához a megfelelő client taggal.

Vegye figyelembe a "type" tagot. Ez a groupon belüli járművek tetszőleges leírása, ami átnevezhető, vagy hozzáadható bármilyen szöveg. Hasonlóképenn a vehicle "name" tag is csak egy szöveges leírás a járműről, és nem kell, hogy a jármű valódi neve legyen.

Most, hogy rendelkezünk az adatokkal, importálni tudjuk a játékba és megjeleníteni a GUI-n. Ha követte ezt a tutoriált az első résztől kezdve, akkor ez az új kód a 'populateGridlist' function-ben lévő kódot cseréli ki. Először be kell töltenünk a fájlt:

function populateGridlist()
	-- load the file and save the root node value it returns
	local rootnode = xmlLoadFile("vehicles.xml")
	
	-- check that the file exists and has been correctly loaded
	if rootnode then		
		-- unload the xml file
		xmlUnloadFile(rootnode)
	end
end

Mindig bizonyosodjon meg arróla, hogy lezárta a fájlt, mikor már végeztél a szerkesztésével.


Most már elkezdhetjük az adatok gyűjtését, és hozzáadhatjuk a gridlist-hez. We can do this by looping through the xml data (in a similar manner to looping through the vehicle table earlier in this tutorial) and adding each entry into the list.

function populateGridlist()
	local rootnode = xmlLoadFile("vehicles.xml")
	
	if rootnode then		
		-- first, we find every "group" node (they are direct children of the root)
		for _,group in ipairs(xmlNodeGetChildren(rootnode)) do
			-- add a new row to the gridlist
			local row = guiGridListAddRow(gridlistVehicleSelection)

			-- get the group name
			local name = xmlNodeGetAttribute(group,"type")
			
			-- set the text in the first column to the group type and indicate it is a section ('true')
			-- (sections on gridlists show up in bold and cannot be clicked)
			guiGridListSetItemText(gridlistVehicleSelection,row,1,name,true,false)			
		
			-- then, for every group that we find, loop all of its children (the vehicle nodes)
			-- and add them into the gridlist
			for _,vehicle in ipairs(xmlNodeGetChildren(group)) do
				-- add a new row to the gridlist
				row = guiGridListAddRow(gridlistVehicleSelection)

				-- get the vehicle name and id
				name = xmlNodeGetAttribute(vehicle,"name")
				local id = xmlNodeGetAttribute(vehicle,"id")
				
				-- set the text in the first column to the vehicle name
				guiGridListSetItemText(gridlistVehicleSelection,row,1,name,false,false)
				-- set the text in the second column to the vehicle type
				guiGridListSetItemText(gridlistVehicleSelection,row,2,getVehicleType(tonumber(id)),false,false)		
				
				-- finally, set the vehicle id as data so we can access it later
				guiGridListSetItemData(gridlistVehicleSelection,row,1,tostring(id))
			end
		end	
	
		-- unload the xml file now that we are finished
		xmlUnloadFile(rootnode)
	end
end

Figyelje meg a for ciklus xmlNodeGetChildren függvénnyel való használatát a kódban. Ez sok időt, és energiát takarít meg nekünk, mivel nem kell manuálisan megírnunk minden egyes group-ot és járművet a listába.

Mostanra már rendelkeznie kell egy teljesen működő járműválasztási menüvel, amely minden adatot a client oldali xml fájlból olvas. Következőnek pedig megnézzük, hogy hogyan csináljuk meg a megjelenését a lenyíló részeknek a gridlistben.

A Gridlist megnyitása/összecsukása

A gridlistben lévő lenyitható menük lehetővé teszik, hogy egy sokkal átláthatóbb panelt készítsünk. Ezzel helyet takarít meg a képernyőn, és sokkal kényelmesebbé teszi az egész menüt.

Az adatok betöltése

Ennek a hatásnak az eléréshez (mivel ez nem a gridlisták természetes tulajdonsága) a járműinformációkat egy táblázatba kell betöltenünke (az xml fájlból), ahelyett, hogy közvetlenül a gridlist-be helyeznénk. Ha követte ezt a tutoriált az első résztől kezdve, akkor ez az új kód a 'populateGridlist' function-ben lévő kódot cseréli ki

Ez is ugyanúgy működik, mint az előző példában szereplő xml betöltési rész, csak ahelyett, hogy az adatokat a grlidlist-be mentenénk, helyette egy táblába mentjük el. Beállítjuk a "header" elementData-t is a gridlist cellákban, hogy meg tudjuk különböztetni, hogy melyik elem egy "group" és melyik egy jármű.

function populateGridlist()
	local rootnode = xmlLoadFile("vehicles.xml")
	
	-- create a blank global table to store our imported data
	vehicleTable = {}
	
	if rootnode then		
		for _,group in ipairs(xmlNodeGetChildren(rootnode)) do
			-- create an entry in the gridlist for every vehicle "group"
			local row = guiGridListAddRow(gridlistVehicleSelection)

			local name = xmlNodeGetAttribute(group,"type")
			
			guiGridListSetItemText(gridlistVehicleSelection,row,1,name,false,false)	

			-- add an entry containing the group name into the table
			vehicleTable[name] = {}
			
			-- we will use the custom data "header" to indicate that this entry can be expanded/collapsed
			guiGridListSetItemData(gridlistVehicleSelection,row,1,"header")	
		
			-- then, for every group that we find, loop all of its children (the vehicle nodes) and store them in a table
			for _,vehicle in ipairs(xmlNodeGetChildren(group)) do
				local vname = xmlNodeGetAttribute(vehicle,"name")
				local id = xmlNodeGetAttribute(vehicle,"id")			
			
				-- insert both the vehicle id and the vehicle description into the table
				table.insert(vehicleTable[name],{id,vname})
			end
		end	
	
		-- set element data on the gridlist so we know which group is currently showing
		setElementData(gridlistVehicleSelection,"expanded","none")
	
		-- unload the xml file now that we are finished
		xmlUnloadFile(rootnode)
	end
end

Most, hogy tároltuk az összes adatot, gyorsan kezelni is tudjuk, amikor szükséges.

Dupla kattintás kezelése

Annak engedélyezéséhez, hogy a játékos egyszerűen tudjon duplán kattintani a group nevére, hogy azt lenyissa/összecsukja az onClientGUIDoubleClick event-et kell használunk. Ha ezt a gridlist-hez csatoljuk, akkor az összes dupla kattintást a group neveken észlelni tudjuk:

-- attach the onClientGUIDoubleClick event to the gridlist and set it to call processDoubleClick
addEventHandler("onClientGUIDoubleClick",gridlistVehicleSelection,processDoubleClick,false)

Adja hozzá ezt a sort a createVehicleSelection-hoz, a létrehozott gridlist után.


Mivel a gridlist-ek nem önálló GUI elemek, ezért rögzítenünk kell az összes kattintást a gridlist-ről, majd feldolgozni, hogy kitudjuk szűrűni azt, amelyiket nem akarjuk figyelembe venni. Ezt megtehetjük egy kiválasztott elem ellenőrzésével, majd ellenőrizzük, hogy az elem az a mi egyik "group" értékünk (a korábban említett "header" adat használatával):

function processDoubleClick(button,state)
	if button == "left" and state == "up" then
		local row,col = guiGridListGetSelectedItem(gridlistVehicleSelection)
		
		-- if something in the gridlist has been selected
		if row and col and row ~= -1 and col ~= -1 then		
			-- if it is a header
			if guiGridListGetItemData(gridlistVehicleSelection,row,col) == "header" then
				local selected = guiGridListGetItemText(gridlistVehicleSelection,row,col)
				
				-- call the function to collapse or expand the menu and pass which section it is as an argument
				changeGridlistState(selected)
			end
		end
	end
end

Miután leszűkítettük a dupla kattintás, hogy csak a header-en történő kattintásra reagáljon, azután megfelelően tudjuk lenyitni/összecsuk őket. Ez könnyen megtehető úgy, hogy új szövegértékeket adunk meg a gridlistben az újonnan lenyitott/összezárt csoportot figyelembe véve, ami azt a meggyőző illúziót kelti, hogy a menüelemek lenyitothatók/összezárhatók.


Ne feledje, hogy a következő kódnak a nagy része újra felhasználásra került a tutorial előző részeiből. Bár általában ez rossz gyakorlat a kód részleteinek az ilyen módon történő másolása, ennek a példának a célja, hogy sokkal könnyebb legyen ezt megérteni.

function changeGridlistState(group)
	if group then
		-- if the group is already expanded, we want to collapse it
		if getElementData(gridlistVehicleSelection,"expanded") == group then
			-- first, we clear all previous data from the gridlist
			guiGridListClear(gridlistVehicleSelection)
			
			-- now, loop all the group entries in the vehicle table that we created earlier
			for group,_ in pairs(vehicleTable) do
				-- add each group to the list and mark them with "header"			
				local row = guiGridListAddRow(gridlistVehicleSelection)
				
				guiGridListSetItemText(gridlistVehicleSelection,row,1,group,false,false)	
				guiGridListSetItemData(gridlistVehicleSelection,row,1,"header")	
			end
			
			-- update the data to indicate that no groups are currently expanded
			setElementData(gridlistVehicleSelection,"expanded","none")
			
		-- otherwise, we want to expand it
		else
			guiGridListClear(gridlistVehicleSelection)
			
			local row = guiGridListAddRow(gridlistVehicleSelection)
			
			guiGridListSetItemText(gridlistVehicleSelection,row,1,group,false,false)	
			guiGridListSetItemData(gridlistVehicleSelection,row,1,"header")				
			
			-- loop every vehicle in the specified groups table
			for _,vehicle in ipairs(vehicleTable[group]) do
				-- add them to the gridlist and set the vehicle id as data
				row = guiGridListAddRow(gridlistVehicleSelection)
			
				-- format a "-" into the string to make it visually easier to distinguish between groups and vehicles
				guiGridListSetItemText(gridlistVehicleSelection,row,1,"- "..vehicle[2],false,false)	
				guiGridListSetItemText(gridlistVehicleSelection,row,2,getVehicleType(tonumber(vehicle[1])),false,false)	
											
				guiGridListSetItemData(gridlistVehicleSelection,row,1,tostring(vehicle[1]))
			end	

			-- update the data to indicate which group is currently expanded
			setElementData(gridlistVehicleSelection,"expanded",group)			
		end
	end
end

Ezzel be is fejeztük a tutoriál második részét.

A további GUI tutoriálokért látogassa meg a GUI készítése - Tutorial 2 (Gates and Keypads) oldalt.

Fordította

2018.11.25. Surge