ZH-CN/脚本编写介绍 - 带有图形界面: Difference between revisions

From Multi Theft Auto: Wiki
Jump to navigation Jump to search
No edit summary
 
(42 intermediate revisions by 4 users not shown)
Line 5: Line 5:


==一个做登陆窗口的教程==
==一个做登陆窗口的教程==
在这个教程中我们会使用两个编辑框和一个按钮制作一个登录窗口。 窗口会在玩家加入游戏的时候出现,一旦按钮被点击,玩家将会被出生。 这个教程会继续我们在 [[Scripting Introduction|脚本介绍]] 中制作的游戏模式''(如果你已经使用了 [[Scripting Introduction|脚本介绍]],你需要在你的代码中移除或者注释掉在 "joinHandler" 行的 [[spawnPlayer]] 函数, 我们也将会在这个教程中使用一个gui替换掉它)''。 我们也会接触一下客户端脚本的编辑.
在这个教程中我们会使用两个编辑框和一个按钮制作一个登录窗口。 窗口会在玩家加入游戏的时候出现,一旦按钮被点击,玩家将会被出生。 这个教程会继续我们在 [[Scripting Introduction|脚本介绍]] 中制作的游戏模式''(如果你已经使用了 [[Scripting Introduction|脚本介绍]],你需要在你的代码中移除或者注释掉在 "joinHandler" 行的 [[spawnPlayer]] 函数, 我们也将会在这个教程中使用一个gui替换掉它)''。 我们也会接触一下客户端脚本的编辑。


===画一个窗口===
===画一个窗口===
所有的GUI只能在客户端制作。 这是一个很好的练习机会来把所有客户端脚本放在分开的目录。
所有的GUI只能在客户端编写。 这是一个很好的练习机会来把所有客户端脚本放在分开的目录。


浏览到目录 /你的MTA服务端目录/mods/deathmatch/resources/myserver/ , 然后创建一个名为 "client" 的目录。 在 /client/ 目录下, 创建一个文件,文件名为 "gui.lua".  
浏览到目录 /你的MTA服务端目录/mods/deathmatch/resources/myserver/,创建名为 client 和 server 两个文件夹,在 client 中创建一个名为 gui.lua 的文件 ,在 server 中创建一个名为 server.lua 的文件。


在这个文件里,我们将会写一个画窗口的函数。使用[[guiCreateWindow]]来创建一个窗口:
整个 myserver 脚本文件夹中文件情况如下:
<syntaxhighlight lang="xml">
/myserver
/meta.xml
/script.lua
/server
/server.lua
/client
/gui.lua
</syntaxhighlight>
 
'''注意,我们所有编写的代码必须要使用 UTF-8 编码脚本才能常执行(否则你输出的中文等会出现乱码)。'''
 
现在我们打开 gui.lua文件,我们将会写一个画窗口的函数。使用 [[guiCreateWindow]] 来创建一个窗口:
<syntaxhighlight lang="lua">
<syntaxhighlight lang="lua">
function createLoginWindow()
function createLoginWindow()
Line 28: Line 41:


===相对和绝对坐标===
===相对和绝对坐标===
注意:传给 guiCreateWindow 的最后一个参数在上面的例子中是 ''true''。这个表明了窗口的坐标和尺寸都是 '''相对的''',也就是说他们是一个相对于屏幕大小的 ''百分比''。我来解释一下:如果屏幕最左侧是0,那么最右侧就是1,那么X坐标为0.5将代表着屏幕中央。同理,屏幕顶部和底部也是一样的,最顶部为0,最底部为1,Y坐标为0.2则代表着是屏幕高度的20%。宽度和高度也是一样的道理(宽度为0.5则意味着窗口是屏幕的一半宽)。
注意:传给 [[guiCreateWindow]] 的最后一个参数在上面的例子中是 ''true''。这个表明了窗口的坐标和尺寸都是 '''相对的''',即 '''相对坐标''',也就是说他们是一个相对于屏幕大小的 ''百分比''。我来解释一下:如果屏幕最左侧是0,那么最右侧就是1,那么X坐标为0.5将代表着屏幕中央。同理,屏幕顶部和底部也是一样的,最顶部为0,最底部为1,Y坐标为0.2则代表着是屏幕高度的20%。宽度和高度也是一样的道理(宽度为0.5则意味着窗口是屏幕的一半宽)。


另外的,也可以用 '''绝对的''' (将传入 guiCreateWindow 的最后一个参数改为 ''false'' 即可)。绝对值被计算为父级的左上角开始到右下角的像素总数(如果没有GUI元素指定父级,那么父级是屏幕本身). 如果我们假设屏幕的分辨率为1920*1200,那么从屏幕左边开始为0像素,到屏幕右边为1920像素,X坐标为960代表屏幕的中点。同理,屏幕顶部为0,到屏幕底部则为1200,Y坐标为20代表着距离屏幕顶部的20个像素。宽度和高度也是相同的道理(宽度为50则意味着窗口为50个像素宽)。 ''你可以使用 [[guiGetScreenSize]] 和一点数学来计算某些绝对坐标。''
另外的,也可以用 '''绝对坐标''' (将传入 [[guiCreateWindow]] 的最后一个参数改为 ''false'' 即可)。绝对坐标被计算为父级的左上角开始到右下角的像素总数(如果没有GUI元素指定父级,那么父级是屏幕本身)。 如果我们假设屏幕的分辨率为1920*1200,那么从屏幕左边开始为0像素,到屏幕右边为1920像素,X坐标为960代表屏幕的中点。同理,屏幕顶部为0,到屏幕底部则为1200,Y坐标为20代表着距离屏幕顶部的20个像素。宽度和高度也是同理(宽度为50则意味着窗口为50个像素宽)。 ''你可以使用 [[guiGetScreenSize]] 和一点数学来计算某些绝对坐标。''


使用相对值和绝对值的不同点非常简单:使用绝对值创建gui经常精确地保持着相同的像素大小和坐标,然而使用相对值创建gui经常是与它父级gui大小的比值。
使用相对坐标和绝对坐标的不同点非常简单:使用绝对坐标创建gui经常精确地保持着相同的像素大小和坐标,然而使用相对坐标创建gui经常是与它父级gui大小的比值。


当你用手敲代码的时候,绝对值一般是更容易维护的。然而你的选择是根据你的目的而变化的。
当你用手敲代码的时候,绝对坐标一般来说更容易维护。然而你的选择是根据你的目的而变化的。


由于本介绍的目的,我们将使用相对值。
为了本介绍的目的,我们将使用'''相对坐标'''。


===Adding the components===
===添加组件===
Next, we'll add the text labels (saying "username:" and "password:"), edit boxes (for entering your data) and a button to log in.
接下来,我们要添加文本标签(里面写上 “帐号:” 和 “密码:”)、编辑框(为了输入你的数据)和一个登录按钮。


To create buttons we use [[guiCreateButton]] and to create edit boxes use [[guiCreateEdit]]:
我们可以使用 [[guiCreateButton]] 创建按钮,创建 [[guiCreateEdit]] 创建编辑框。


'''Note that we are now writing more code for our existing 'createLoginWindow' function. This is not a new function and is meant to replace what you already have.'''  
'''注意:我们现在正在给我们已经存在的 ‘createLoginWindow’函数写更多的代码。这不是一个新的函数,这个函数是用来替换你脚本中已经存在的那个。'''  
<syntaxhighlight lang="lua">
<syntaxhighlight lang="lua">
function createLoginWindow()
function createLoginWindow()
Line 50: Line 63:
local Width = 0.25
local Width = 0.25
local Height = 0.25
local Height = 0.25
wdwLogin = guiCreateWindow(X, Y, Width, Height, "Please Log In", true)
wdwLogin = guiCreateWindow(X, Y, Width, Height, "请登录", true)
-- define new X and Y positions for the first label
-- 给第一个文本标签定义新的X和Y坐标
X = 0.0825
X = 0.0825
Y = 0.2
Y = 0.2
-- define new Width and Height values for the first label
-- 给第一个文本标签定义新的宽度和高度
Width = 0.25
Width = 0.25
Height = 0.25
Height = 0.25
-- create the first label, note the final argument passed is 'wdwLogin' meaning the window
-- 创建第一个文本标签,注意最后一个传入的参数是 ‘wdwLogin’,这个参数是你刚刚创建的窗口(window)
-- we created above is the parent of this label (so all the position and size values are now relative to the position of that window)
-- 我们在它的父级gui上方创建了这个文本标签(所以所有的坐标和大小的值都是相对于这个窗口的)
guiCreateLabel(X, Y, Width, Height, "Username", true, wdwLogin)
guiCreateLabel(X, Y, Width, Height, "帐号", true, wdwLogin)
-- alter the Y value, so the second label is slightly below the first
-- 改变Y的值,所以第二个文本标签是在第一个下方的
Y = 0.5
Y = 0.5
guiCreateLabel(X, Y, Width, Height, "Password", true, wdwLogin)
guiCreateLabel(X, Y, Width, Height, "密码", true, wdwLogin)


Line 73: Line 86:
Y = 0.5
Y = 0.5
edtPass = guiCreateEdit(X, Y, Width, Height, "", true, wdwLogin)
edtPass = guiCreateEdit(X, Y, Width, Height, "", true, wdwLogin)
-- set the maximum character length for the username and password fields to 50
-- 设置编辑框的最大文字长度为50
guiEditSetMaxLength(edtUser, 50)
guiEditSetMaxLength(edtUser, 50)
guiEditSetMaxLength(edtPass, 50)
guiEditSetMaxLength(edtPass, 50)
Line 83: Line 96:
btnLogin = guiCreateButton(X, Y, Width, Height, "Log In", true, wdwLogin)
btnLogin = guiCreateButton(X, Y, Width, Height, "Log In", true, wdwLogin)
-- make the window invisible
-- 隐藏窗口
guiSetVisible(wdwLogin, false)
guiSetVisible(wdwLogin, false)
end
end
</syntaxhighlight>
</syntaxhighlight>
Note that every GUI component created is a child of the window, this is done by specifying the parent element (wdwLogin, in this case) when creating the component.
注意:上述代码中,每个GUI组件都被创建为 '''窗口(window)''' 的子级,这是当创建组件时通过指定父级元素(以上代码中为'''wdwLogin''')来完成的


This is very useful because not only does it mean that all the components are attached to the window and will move with it, but also that any changes done to the parent window will be applied down the tree to these child components. For example, we can now hide all of the GUI we just created by simply hiding the window:
这是很有用的,因为这不仅意味着所有组件都是附着在'''窗口'''并且移动的时候会带着这些组件一起移动,而且任何对父级'''窗口'''的改变都会被应用到这些子级元素。例如,我们可以隐藏所有的GUI,我们只要隐藏'''wdwLogin'''就行了:
<syntaxhighlight lang="lua">
<syntaxhighlight lang="lua">
guiSetVisible(wdwLogin, false) --hides all the GUI we made so we can show them to the player at the appropriate moment.
guiSetVisible(wdwLogin, false) --隐藏我们制作的所有的GUI,这样我们才能在适当的时候显示GUI
</syntaxhighlight>
</syntaxhighlight>


===Using the function we wrote===
===使用我们编写完成的功能===
The createLoginWindow function is now complete, but it won't do anything until we call it. It is recommended to create all GUI when the client resource starts, hide them, and show them to the player later when needed. Therefore, we'll write an event handler for "[[onClientResourceStart]]" to create the window:
'''createLoginWindow''' 功能编写完成,但是它'''不会'''执行,必须要我们'''调用'''它才可以执行。建议在'''客户端启动资源时'''创建所有的GUI,当前不需要显示时可以进行隐藏(如果需要立即显示就不用隐藏),或在我们需要用到它的时候显示给玩家。
 
因此,我们需要为GUI窗口创建一个'''事件触发器''',事件触发器的事件为 "[[onClientResourceStart]]"
<syntaxhighlight lang="lua">
<syntaxhighlight lang="lua">
-- attach the event handler to the root element of the resource
-- 将事件处理程序添加到资源的根元素
-- this means it will only trigger when its own resource is started
-- 只有在这个资源本启动时才会触发
addEventHandler("onClientResourceStart", getResourceRootElement(),  
addEventHandler("onClientResourceStart", getResourceRootElement(),  
function ()
function ()
Line 106: Line 121:
</syntaxhighlight>
</syntaxhighlight>


As this is a log in window, we now need to show the window when the player joins the game.
因为脚本是登陆界面,所以我们要在玩家进入游戏后显示出来。
This can be done using the same event, "[[onClientResourceStart]]", so we can modify the above code to include showing the window:
也可以使用 "[[onClientResourceStart]]" 事件来完成,所以我们将上面的代码进行修改,来达到我们的目的:


'''Note that we are now writing more code for our existing 'onClientResourceStart' handler. This is not a new event handler and is meant to replace what you already have.'''  
'''注意,我们正在为现有的 'onClientResourceStart' 事件处理程序编写更多的代码。不是新创建的一个事件处理程序。'''  
<syntaxhighlight lang="lua">
<syntaxhighlight lang="lua">
addEventHandler("onClientResourceStart", getResourceRootElement(),  
addEventHandler("onClientResourceStart", getResourceRootElement(),  
function ()
function ()
-- create the log in window and its components
-- 创建已经编写好的窗口以及窗口组件
createLoginWindow()
createLoginWindow()


-- output a brief welcome message to the player
-- 输出一个欢迎信息给玩家
                 outputChatBox("Welcome to My MTA:SA Server, please log in.")
                 outputChatBox("欢迎来到我的服务器,请登陆。")


-- if the GUI was successfully created, then show the GUI to the player
-- 用if函数判断窗口是否创建成功,如果成功将窗口显示给玩家
        if (wdwLogin ~= nil) then
        if (wdwLogin ~= nil) then
guiSetVisible(wdwLogin, true)
guiSetVisible(wdwLogin, true)
else
else
-- if the GUI hasnt been properly created, tell the player
-- 如果窗口创建失败时,告诉玩家窗口创建失败
outputChatBox("An unexpected error has occurred and the log in GUI has not been created.")
outputChatBox("出现错误,没有成功创建GUI画面")
        end  
        end  


-- enable the players cursor (so they can select and click on the components)
-- 启用玩家的鼠标显示,以便点击窗口中的组件
        showCursor(true)
        showCursor(true)
-- set the input focus onto the GUI, allowing players (for example) to press 'T' without the chatbox opening
-- 设置玩家绑定的所有按键都不生效,包括'T '键的本地发言,以保证输入的信息都输入到编辑框内
        guiSetInputEnabled(true)
        guiSetInputEnabled(true)
end
end
Line 135: Line 150:
</syntaxhighlight>
</syntaxhighlight>


Note that we have a simple security check before making the window visible, so in the unlikely event that the window has not been created, meaning wdwLogin is not a valid element, we don't get an error and just inform the player what has happened.
'''注意,在显示窗口之前,我们使用了一个 if判断句 来判断窗口是否创建成功。创建成功的情况下将显示出来。创建不成功就意味着 wdwLogin 不是一个元素,服务器也不会出现错误提示,只会提示玩家窗口创建失败。'''
In the next step, we will create the button functionality for the log in button.
 
下一步我们将给按钮(button)创建点击后的功能


==Scripting the button==
==给按钮写一个功能==
Now that we have created our GUI and shown it to the player, we need to make it work.
既然我们已经创建了我们的GUI并且展示给了玩家看,我们需要让它运行。


===Detecting the click===
===检测点击===
When the player clicks on any part of the GUI, the event "[[onClientGUIClick]]" will be triggered for the GUI component you clicked on. This allows us to easily detect any clicks on the GUI elements we want to use.
当玩家点击GUI上任何一部分,"[[onClientGUIClick]]" 事件会因为你点击了GUI组建而被触发。这允许我们更简单地去检测我们想要使用的GUI元素上的点击。
For example, we can attach the event to the btnLogin button to catch any clicks on it:
举个例子,我们能够在 bthLogin 按钮上附加这个事件来抓取在这个GUI元素上的任何点击:
<syntaxhighlight lang="lua">
<syntaxhighlight lang="lua">
-- attach the event onClientGUIClick to btnLogin and set it to trigger the 'clientSubmitLogin' function
-- 附加 onClientGUIClick btnLogin 上然后设置触发函数为 'clientSubmitLogin'
addEventHandler("onClientGUIClick", btnLogin, clientSubmitLogin, false)
addEventHandler("onClientGUIClick", btnLogin, clientSubmitLogin, false)
</syntaxhighlight>
</syntaxhighlight>
'''Note the final argument passed is "false". This indicates that the event will only trigger directly on btnLogin, not if the event has propagated up or down the tree. Setting this to "true" while attaching to gui elements will mean that clicking on any element in the same branch will trigger this event.'''
'''注意:最后一个参数传入的是 "false" 。这表明了这个事件只会直接由 btnLogin 触发,除非事件已经往上或者往下传播树。如果附加事件给gui的时候设置这个值为 "true" ,点击这个gui以及它的任何子级gui都会触发这个事件'''


This line of code can now be added inside the createLoginWindow function. It is a common mistake to try and attach events to non-existant GUI elements, so make sure you always attach your events '''after''' the gui element (in this case, the button) has been created:
这行代码能够被添加进 createLoginWindow 函数。尝试附加一个事件到一个不存在的GUI元素是一个普遍的错误,所以请你确保要在创建GUI之后附加你的事件到GUI元素上(上述代码中为 btnLogin)。'''


'''Note that we are now writing more code for our existing 'createLoginWindow' function.'''  
'''注意:我们正在为目前存在的 'createLoginWindow' 函数编写更多的代码。'''
<syntaxhighlight lang="lua">
<syntaxhighlight lang="lua">
function createLoginWindow()
function createLoginWindow()
-- create all our GUI elements
-- 创建所有需要的GUI元素
...
...


-- now add our onClientGUIClick event to the button we just created
-- 现在附加事件 onClientGUIClick 到我们刚刚创建的按钮上
addEventHandler("onClientGUIClick", btnLogin, clientSubmitLogin, false)
addEventHandler("onClientGUIClick", btnLogin, clientSubmitLogin, false)
</syntaxhighlight>
</syntaxhighlight>


===Managing the click===
===管理点击===
Now that we can detect when the player clicks on the button, we need to write code to manage what happens when they do.
既然我们检测了玩家点击按钮,我们需要写代码来管理点击按钮之后发生的事情。
In our [[onClientGUIClick]] event handle, we told it to call the function clientSubmitLogin whenever btnLogin is clicked.
[[onClientGUIClick]] 事件中,我们告诉它要调用 clientSubmitLogin 函数,每当 btnLogin 被点击。
Therefore, we can now use the function clientSubmitLogin to control what happens when the button is clicked:
因此,我们现在能够使用 clientSubmitLogin 函数来控制点击按钮之后接下来所发生的事:
<syntaxhighlight lang="lua">
<syntaxhighlight lang="lua">
-- create the function and define the 'button' and 'state' parameters
-- 创建函数并定义 'button' 'state' 参数 (这些参数已经自动地由 onClientGUIClick 传入)
-- (these are passed automatically by onClientGUIClick)
function clientSubmitLogin(button,state)
function clientSubmitLogin(button,state)
-- if our login button was clicked with the left mouse button, and the state of the mouse button is up
        -- 如果我们的登录按钮被鼠标左键点击了,并且状态(state)是松开左键
if button == "left" and state == "up" then
if button == "left" and state == "up" then
-- move the input focus back onto the game (allowing players to move around, open the chatbox, etc)
-- 关闭输入免打扰模式(使得输入的时候能够触发快捷键)
guiSetInputEnabled(false)
guiSetInputEnabled(false)
-- hide the window and all the components
-- 隐藏窗口以及其它附属于窗口的GUI组件
guiSetVisible(wdwLogin, false)
guiSetVisible(wdwLogin, false)
-- hide the mouse cursor
-- 隐藏光标
showCursor(false)
showCursor(false)
end
end
end
end
</syntaxhighlight>
</syntaxhighlight>
Now, when the button is clicked, the window will be hidden and all controls will be returned to the player. Next, we will tell the server to allow the player to spawn.
现在,当按钮被点击,窗口会被隐藏并且恢复对玩家的操控。接下来,我们将会告诉服务器出生玩家(spawn player)


===Triggering the server===
===触发服务器事件===
Triggering the server can be done using [[triggerServerEvent]]. This allows you to trigger a specified event on the server from the client. The same can be done in reverse using [[triggerClientEvent]].
客户端触发服务器端事件使用 [[triggerServerEvent]] 函数来完成。这个函数可以从客户端上触发指定的服务器端事件。同样的,服务器端触发客户端事件使用 [[triggerClientEvent]] 函数完成.因为我们要触发'''服务器端'''事件,所以在这里我们使用 [[triggerServerEvent]] 函数来调用我们'''自定义'''的服务器事件,服务器事件命名为 "submitLogin",这个事件能够控制玩家的重生。
Here, we use the [[triggerServerEvent]] function to call our own custom event on the server, named "submitLogin", which will then control the spawning of the player serverside.


'''Note that we are now writing more code for our existing 'clientSubmitLogin' function. This is not a new function and is meant to replace what you already have.'''  
'''注意,我们是在为现有的功能 'clientSubmitLogin' 编写更多的代码,并不是一个全新的函数。'''  
<syntaxhighlight lang="lua">
<syntaxhighlight lang="lua">
function clientSubmitLogin(button,state)
function clientSubmitLogin(button,state)
if button == "left" and state == "up" then
if button == "left" and state == "up" then
-- get the text entered in the 'username' field
-- 获取账户编辑框中的账户名
local username = guiGetText(edtUser)
local username = guiGetText(edtUser)
-- get the text entered in the 'password' field
-- 获取密码编辑框中的密码
local password = guiGetText(edtPass)
local password = guiGetText(edtPass)


-- if the username and password both exist
-- 如果用户名和密码都不为空时向下执行
if username and password then
if username and username ~= "" and password and password ~= "" then
-- trigger the server event 'submitLogin' and pass the username and password to it
-- 调用服务器端事件 'submitLogin' 并向服务器端传递账户名和密码
triggerServerEvent("submitLogin", getRootElement(), username, password)
triggerServerEvent("submitLogin", getRootElement(), username, password)


-- hide the gui, hide the cursor and return control to the player
-- 恢复玩家的按键绑定(比如T键本地聊天),隐藏玩家的GUI画面,取消玩家鼠标的显示
guiSetInputEnabled(false)
guiSetInputEnabled(false)
guiSetVisible(wdwLogin, false)
guiSetVisible(wdwLogin, false)
showCursor(false)
showCursor(false)
else
else
-- otherwise, output a message to the player, do not trigger the server
-- 用户名或密码有一个为空时,向玩家返回消息,并且不触发服务端事件
-- and do not hide the gui
-- 同时不隐藏GUI画面
outputChatBox("Please enter a username and password.")
outputChatBox("请输入用户名和密码。")
end
end
end
end
Line 214: Line 228:
</syntaxhighlight>
</syntaxhighlight>


===Creating the serverside event===
===创建服务器事件===
At this point we now have all the code needed on the client side, so open up your serverside 'script.lua' file (from the [[Scripting Introduction|Introduction to Scripting]]) or another suitable serverside file to work with.
'''我们现在已经编写完成我们所需的客户端代码,我们下面需要编写服务器端代码,服务器端代码需要写在 server 文件夹中的 server.lua 文件,因此我们打开 server.lua 文件,进行下面的编写。'''


On the server side, recall that we are spawning the player as soon as they login.
在服务器端上,我们想一下刚才所写的客户端代码,玩家一旦登陆就生成(重生)。
So, first of all, we will need to define the custom event that we used before on the client. This can be done using [[addEvent]] and [[addEventHandler]].
所以,现在我们需要添加'''客户端'''调用的服务器端事件(submitLogin),我们可以用 [[addEvent]] [[addEventHandler]] 来完成.
<syntaxhighlight lang="lua">
<syntaxhighlight lang="lua">
-- create our loginHandler function, with username and password parameters (passed from the client gui)
-- 创建一个名为 loginHandler 的功能,使用额外的两个参数 username password (这是从客户端调用时传递的)
function loginHandler(username,password)
function loginHandler(username,password)


end
end


-- define our custom event, and allow it to be triggered from the client ('true')
-- 我们添加自定义事件,命名为 submitLogin(事件名需要打英文双引号)。并允许客户端调用它(后面的true参数)。
addEvent("submitLogin",true)
addEvent("submitLogin",true)
-- add an event handler so that when submitLogin is triggered, the function loginHandler is called
-- 添加一个事件处理程序,在 submitLogin 事件触发时调用 loginHandler 功能。
addEventHandler("submitLogin",root,loginHandler)
addEventHandler("submitLogin",root,loginHandler)
</syntaxhighlight>
</syntaxhighlight>


===Logging in===
===登陆===
Now we have a function that is called through the custom event 'submitLogin', we can start to work on logging in and spawning the player, using our 'loginHandler' function:
现在我们已经定义好事件 'submitLogin' 以及调用时触发的功能,现在我们需要给 'loginHandler' 功能编写更多的代码以实现登陆和生成(重生)玩家。
<syntaxhighlight lang="lua">
<syntaxhighlight lang="lua">
function loginHandler(username,password)
function loginHandler(username,password)
-- check that the username and password are correct
-- 用 if 函数判断用户名和密码是否正确
if username == "user" and password == "apple" then
if username == "user" and password == "apple" then
-- the player has successfully logged in, so spawn them
-- 判断 client 变量是否存在,以便生成(重生)玩家
if (client) then
if (client) then
spawnPlayer(client, 1959.55, -1714.46, 10)
spawnPlayer(client, 1959.55, -1714.46, 10)
fadeCamera(client, true)
fadeCamera(client, true)
                         setCameraTarget(client, client)
                         setCameraTarget(client, client)
outputChatBox("Welcome to My Server.", client)
outputChatBox("欢迎来到我的服务器", client)
end
end
else
else
-- if the username or password are not correct, output a message to the player
-- 如果用户名或密码不正确向玩家输出错误信息
outputChatBox("Invalid username and password. Please re-connect and try again.",client)
outputChatBox("用户名或密码错误,请重新连接登陆。",client)
         end
         end
end
end
Line 253: Line 267:
addEventHandler("submitLogin",root,loginHandler)
addEventHandler("submitLogin",root,loginHandler)
</syntaxhighlight>
</syntaxhighlight>
'''For the purposes of this tutorial, a very basic username and password system is shown. For a more comprehensive alternative, you can use the Account System or a MySQL database.'''
'''为了教程的目的,本教程中使用的非常简单的用户名(user)和密码(apple)。在服务器安全方面,你可以才用MySql数据库来存储用户的账户和密码以及数据信息。'''


Also note the use of the variable "client", it's an internal variable used by MTA to identify the player who triggered the event.
要注意 '''client''' 变量的使用,它是MTA用来标识触发服务端事件的'''客户端玩家'''(也就是说 client 变量就是触发(调用)服务器端事件的那个'''玩家''')。




Finally, do not forget to include the new gui.lua file in the meta.xml of the main resource, and label it as a client script:
最后,别忘记修改 meta.xml 文件,在<meta></meta>中加入下面两行,不要忘记将 gui.lua 标记为客户端属性(因为GUI只能在客户端使用),将 server.lua 标记为服务器端属性(因为 server.lua 中代码是需要在服务端中执行的)。
<syntaxhighlight lang="xml">
<syntaxhighlight lang="xml">
<script src="client/gui.lua" type="client" />
<script src="client/gui.lua" type="client" />
<script src="server/server.lua" type="server" />
</syntaxhighlight>
</syntaxhighlight>




At this point, we now have a basic login window that checks the player's username and password when the login button is clicked. If they are correct, the player is automatically spawned.
我们现在完成了一个简单的登陆窗口,在点击登陆时会检测用户名和密码是正确,在正确的情况下生成(重生)出玩家。


For further help with GUI, see the [[:Category:GUI_Tutorials|GUI tutorials]].
有关GUI的进一步帮助,请参考 [[:Category:GUI_Tutorials|GUI tutorials]]


[[Category:GUI_Tutorials]]
[[Category:GUI_Tutorials]]
Line 272: Line 287:
[[ru:Introduction to Scripting the GUI]]
[[ru:Introduction to Scripting the GUI]]
[[es:Introducción a la Programación de GUI]]
[[es:Introducción a la Programación de GUI]]
[[en:Introduction to Scripting the GUI]]
[[fr:Introduction GUI]]
[[it:Introduzione_allo_scripting_della_GUI]]
[[pt-br:Introducao ao GUI scripting]]

Latest revision as of 13:09, 7 February 2021

在MTA:SA中有一个重要的特点,那就是可以制作自定义的GUI(Graphic User Interface)。GUI由窗口,按钮,编辑框,复选框等组成,拥有在图形环境中大部分的基础控件。它们可以在玩家正在游戏的时候显示,常常被用来代替传统命令的输入和输出。

管理员后台GUI

一个做登陆窗口的教程

在这个教程中我们会使用两个编辑框和一个按钮制作一个登录窗口。 窗口会在玩家加入游戏的时候出现,一旦按钮被点击,玩家将会被出生。 这个教程会继续我们在 脚本介绍 中制作的游戏模式(如果你已经使用了 脚本介绍,你需要在你的代码中移除或者注释掉在 "joinHandler" 行的 spawnPlayer 函数, 我们也将会在这个教程中使用一个gui替换掉它)。 我们也会接触一下客户端脚本的编辑。

画一个窗口

所有的GUI只能在客户端编写。 这是一个很好的练习机会来把所有客户端脚本放在分开的目录。

浏览到目录 /你的MTA服务端目录/mods/deathmatch/resources/myserver/,创建名为 client 和 server 两个文件夹,在 client 中创建一个名为 gui.lua 的文件 ,在 server 中创建一个名为 server.lua 的文件。

整个 myserver 脚本文件夹中文件情况如下:

/myserver
	/meta.xml
	/script.lua
	/server
		/server.lua
	/client
		/gui.lua

注意,我们所有编写的代码必须要使用 UTF-8 编码脚本才能常执行(否则你输出的中文等会出现乱码)。

现在我们打开 gui.lua文件,我们将会写一个画窗口的函数。使用 guiCreateWindow 来创建一个窗口:

function createLoginWindow()
	-- 定义窗口的X和Y坐标
	local X = 0.375
	local Y = 0.375
	-- 定义窗口的宽度和高度
	local Width = 0.25
	local Height = 0.25
        -- 创建一个窗口,并且把它放入变量 'wdwLogin' 里
        -- 点击这个函数的名字可以阅读这个函数的相关信息
	wdwLogin = guiCreateWindow(X, Y, Width, Height, "请登录", true)
end

相对和绝对坐标

注意:传给 guiCreateWindow 的最后一个参数在上面的例子中是 true。这个表明了窗口的坐标和尺寸都是 相对的,即 相对坐标,也就是说他们是一个相对于屏幕大小的 百分比。我来解释一下:如果屏幕最左侧是0,那么最右侧就是1,那么X坐标为0.5将代表着屏幕中央。同理,屏幕顶部和底部也是一样的,最顶部为0,最底部为1,Y坐标为0.2则代表着是屏幕高度的20%。宽度和高度也是一样的道理(宽度为0.5则意味着窗口是屏幕的一半宽)。

另外的,也可以用 绝对坐标 (将传入 guiCreateWindow 的最后一个参数改为 false 即可)。绝对坐标被计算为父级的左上角开始到右下角的像素总数(如果没有GUI元素指定父级,那么父级是屏幕本身)。 如果我们假设屏幕的分辨率为1920*1200,那么从屏幕左边开始为0像素,到屏幕右边为1920像素,X坐标为960代表屏幕的中点。同理,屏幕顶部为0,到屏幕底部则为1200,Y坐标为20代表着距离屏幕顶部的20个像素。宽度和高度也是同理(宽度为50则意味着窗口为50个像素宽)。 你可以使用 guiGetScreenSize 和一点数学来计算某些绝对坐标。

使用相对坐标和绝对坐标的不同点非常简单:使用绝对坐标创建gui经常精确地保持着相同的像素大小和坐标,然而使用相对坐标创建gui经常是与它父级gui大小的比值。

当你用手敲代码的时候,绝对坐标一般来说更容易维护。然而你的选择是根据你的目的而变化的。

为了本介绍的目的,我们将使用相对坐标

添加组件

接下来,我们要添加文本标签(里面写上 “帐号:” 和 “密码:”)、编辑框(为了输入你的数据)和一个登录按钮。

我们可以使用 guiCreateButton 创建按钮,创建 guiCreateEdit 创建编辑框。

注意:我们现在正在给我们已经存在的 ‘createLoginWindow’函数写更多的代码。这不是一个新的函数,这个函数是用来替换你脚本中已经存在的那个。

function createLoginWindow()
	local X = 0.375
	local Y = 0.375
	local Width = 0.25
	local Height = 0.25
	wdwLogin = guiCreateWindow(X, Y, Width, Height, "请登录", true)
	
	-- 给第一个文本标签定义新的X和Y坐标
	X = 0.0825
	Y = 0.2
	-- 给第一个文本标签定义新的宽度和高度
	Width = 0.25
	Height = 0.25
	-- 创建第一个文本标签,注意最后一个传入的参数是 ‘wdwLogin’,这个参数是你刚刚创建的窗口(window)
	-- 我们在它的父级gui上方创建了这个文本标签(所以所有的坐标和大小的值都是相对于这个窗口的)
	guiCreateLabel(X, Y, Width, Height, "帐号", true, wdwLogin)
	-- 改变Y的值,所以第二个文本标签是在第一个下方的
	Y = 0.5
	guiCreateLabel(X, Y, Width, Height, "密码", true, wdwLogin)
	

	X = 0.415
	Y = 0.2
	Width = 0.5
	Height = 0.15
	edtUser = guiCreateEdit(X, Y, Width, Height, "", true, wdwLogin)
	Y = 0.5
	edtPass = guiCreateEdit(X, Y, Width, Height, "", true, wdwLogin)
	-- 设置编辑框的最大文字长度为50
	guiEditSetMaxLength(edtUser, 50)
	guiEditSetMaxLength(edtPass, 50)
	
	X = 0.415
	Y = 0.7
	Width = 0.25
	Height = 0.2
	btnLogin = guiCreateButton(X, Y, Width, Height, "Log In", true, wdwLogin)
	
	-- 隐藏窗口
	guiSetVisible(wdwLogin, false)
end

注意:上述代码中,每个GUI组件都被创建为 窗口(window) 的子级,这是当创建组件时通过指定父级元素(以上代码中为wdwLogin)来完成的

这是很有用的,因为这不仅意味着所有组件都是附着在窗口并且移动的时候会带着这些组件一起移动,而且任何对父级窗口的改变都会被应用到这些子级元素。例如,我们可以隐藏所有的GUI,我们只要隐藏wdwLogin就行了:

guiSetVisible(wdwLogin, false) --隐藏我们制作的所有的GUI,这样我们才能在适当的时候显示GUI

使用我们编写完成的功能

createLoginWindow 功能编写完成,但是它不会执行,必须要我们调用它才可以执行。建议在客户端启动资源时创建所有的GUI,当前不需要显示时可以进行隐藏(如果需要立即显示就不用隐藏),或在我们需要用到它的时候显示给玩家。

因此,我们需要为GUI窗口创建一个事件触发器,事件触发器的事件为 "onClientResourceStart":

-- 将事件处理程序添加到资源的根元素
-- 只有在这个资源本启动时才会触发
addEventHandler("onClientResourceStart", getResourceRootElement(), 
	function ()
		createLoginWindow()
	end
)	

因为脚本是登陆界面,所以我们要在玩家进入游戏后显示出来。 也可以使用 "onClientResourceStart" 事件来完成,所以我们将上面的代码进行修改,来达到我们的目的:

注意,我们正在为现有的 'onClientResourceStart' 事件处理程序编写更多的代码。不是新创建的一个事件处理程序。

addEventHandler("onClientResourceStart", getResourceRootElement(), 
	function ()
		-- 创建已经编写好的窗口以及窗口组件
		createLoginWindow()

		-- 输出一个欢迎信息给玩家
                outputChatBox("欢迎来到我的服务器,请登陆。")

		-- 用if函数判断窗口是否创建成功,如果成功将窗口显示给玩家
	        if (wdwLogin ~= nil) then
			guiSetVisible(wdwLogin, true)
		else
			-- 如果窗口创建失败时,告诉玩家窗口创建失败
			outputChatBox("出现错误,没有成功创建GUI画面")
	        end 

		-- 启用玩家的鼠标显示,以便点击窗口中的组件
	        showCursor(true)
		-- 设置玩家绑定的所有按键都不生效,包括'T '键的本地发言,以保证输入的信息都输入到编辑框内
	        guiSetInputEnabled(true)
	end
)	

注意,在显示窗口之前,我们使用了一个 if判断句 来判断窗口是否创建成功。创建成功的情况下将显示出来。创建不成功就意味着 wdwLogin 不是一个元素,服务器也不会出现错误提示,只会提示玩家窗口创建失败。

下一步我们将给按钮(button)创建点击后的功能

给按钮写一个功能

既然我们已经创建了我们的GUI并且展示给了玩家看,我们需要让它运行。

检测点击

当玩家点击GUI上任何一部分,"onClientGUIClick" 事件会因为你点击了GUI组建而被触发。这允许我们更简单地去检测我们想要使用的GUI元素上的点击。 举个例子,我们能够在 bthLogin 按钮上附加这个事件来抓取在这个GUI元素上的任何点击:

-- 附加 onClientGUIClick 到 btnLogin 上然后设置触发函数为 'clientSubmitLogin'
addEventHandler("onClientGUIClick", btnLogin, clientSubmitLogin, false)

注意:最后一个参数传入的是 "false" 。这表明了这个事件只会直接由 btnLogin 触发,除非事件已经往上或者往下传播树。如果附加事件给gui的时候设置这个值为 "true" ,点击这个gui以及它的任何子级gui都会触发这个事件

这行代码能够被添加进 createLoginWindow 函数。尝试附加一个事件到一个不存在的GUI元素是一个普遍的错误,所以请你确保要在创建GUI之后附加你的事件到GUI元素上(上述代码中为 btnLogin)。

注意:我们正在为目前存在的 'createLoginWindow' 函数编写更多的代码。

function createLoginWindow()
	-- 创建所有需要的GUI元素
	...

	-- 现在附加事件 onClientGUIClick 到我们刚刚创建的按钮上
	addEventHandler("onClientGUIClick", btnLogin, clientSubmitLogin, false)

管理点击

既然我们检测了玩家点击按钮,我们需要写代码来管理点击按钮之后发生的事情。 在 onClientGUIClick 事件中,我们告诉它要调用 clientSubmitLogin 函数,每当 btnLogin 被点击。 因此,我们现在能够使用 clientSubmitLogin 函数来控制点击按钮之后接下来所发生的事:

-- 创建函数并定义 'button' 和 'state' 参数 (这些参数已经自动地由 onClientGUIClick 传入)
function clientSubmitLogin(button,state)
        -- 如果我们的登录按钮被鼠标左键点击了,并且状态(state)是松开左键
	if button == "left" and state == "up" then
		-- 关闭输入免打扰模式(使得输入的时候能够触发快捷键)
		guiSetInputEnabled(false)
		-- 隐藏窗口以及其它附属于窗口的GUI组件
		guiSetVisible(wdwLogin, false)
		-- 隐藏光标
		showCursor(false)
	end
end

现在,当按钮被点击,窗口会被隐藏并且恢复对玩家的操控。接下来,我们将会告诉服务器出生玩家(spawn player)

触发服务器事件

客户端触发服务器端事件使用 triggerServerEvent 函数来完成。这个函数可以从客户端上触发指定的服务器端事件。同样的,服务器端触发客户端事件使用 triggerClientEvent 函数完成.因为我们要触发服务器端事件,所以在这里我们使用 triggerServerEvent 函数来调用我们自定义的服务器事件,服务器事件命名为 "submitLogin",这个事件能够控制玩家的重生。

注意,我们是在为现有的功能 'clientSubmitLogin' 编写更多的代码,并不是一个全新的函数。

function clientSubmitLogin(button,state)
	if button == "left" and state == "up" then
		-- 获取账户编辑框中的账户名
		local username = guiGetText(edtUser)
		-- 获取密码编辑框中的密码
		local password = guiGetText(edtPass)

		-- 如果用户名和密码都不为空时向下执行
		if username and username ~= "" and password and password ~= "" then
			-- 调用服务器端事件 'submitLogin' 并向服务器端传递账户名和密码
			triggerServerEvent("submitLogin", getRootElement(), username, password)

			-- 恢复玩家的按键绑定(比如T键本地聊天),隐藏玩家的GUI画面,取消玩家鼠标的显示
			guiSetInputEnabled(false)
			guiSetVisible(wdwLogin, false)
			showCursor(false)
		else
			-- 用户名或密码有一个为空时,向玩家返回消息,并且不触发服务端事件
			-- 同时不隐藏GUI画面
			outputChatBox("请输入用户名和密码。")
		end
	end
end

创建服务器事件

我们现在已经编写完成我们所需的客户端代码,我们下面需要编写服务器端代码,服务器端代码需要写在 server 文件夹中的 server.lua 文件,因此我们打开 server.lua 文件,进行下面的编写。

在服务器端上,我们想一下刚才所写的客户端代码,玩家一旦登陆就生成(重生)。 所以,现在我们需要添加客户端调用的服务器端事件(submitLogin),我们可以用 addEventaddEventHandler 来完成.

-- 创建一个名为 loginHandler 的功能,使用额外的两个参数 username 和 password (这是从客户端调用时传递的)
function loginHandler(username,password)

end

-- 我们添加自定义事件,命名为 submitLogin(事件名需要打英文双引号)。并允许客户端调用它(后面的true参数)。
addEvent("submitLogin",true)
-- 添加一个事件处理程序,在 submitLogin 事件触发时调用 loginHandler 功能。
addEventHandler("submitLogin",root,loginHandler)

登陆

现在我们已经定义好事件 'submitLogin' 以及调用时触发的功能,现在我们需要给 'loginHandler' 功能编写更多的代码以实现登陆和生成(重生)玩家。

function loginHandler(username,password)
	-- 用 if 函数判断用户名和密码是否正确
	if username == "user" and password == "apple" then
		-- 判断 client 变量是否存在,以便生成(重生)玩家
		if (client) then
			spawnPlayer(client, 1959.55, -1714.46, 10)
			fadeCamera(client, true)
                        setCameraTarget(client, client)
			outputChatBox("欢迎来到我的服务器", client)
		end
	else
		-- 如果用户名或密码不正确向玩家输出错误信息
		outputChatBox("用户名或密码错误,请重新连接登陆。",client)
        end			
end

addEvent("submitLogin",true)
addEventHandler("submitLogin",root,loginHandler)

为了教程的目的,本教程中使用的非常简单的用户名(user)和密码(apple)。在服务器安全方面,你可以才用MySql数据库来存储用户的账户和密码以及数据信息。

要注意 client 变量的使用,它是MTA用来标识触发服务端事件的客户端玩家(也就是说 client 变量就是触发(调用)服务器端事件的那个玩家)。


最后,别忘记修改 meta.xml 文件,在<meta></meta>中加入下面两行,不要忘记将 gui.lua 标记为客户端属性(因为GUI只能在客户端使用),将 server.lua 标记为服务器端属性(因为 server.lua 中代码是需要在服务端中执行的)。

<script src="client/gui.lua" type="client" />
<script src="server/server.lua" type="server" />


我们现在完成了一个简单的登陆窗口,在点击登陆时会检测用户名和密码是正确,在正确的情况下生成(重生)出玩家。

有关GUI的进一步帮助,请参考 GUI tutorials