ZH-CN/脚本编写介绍 - 带有图形界面: Difference between revisions
(→添加组件) |
|||
Line 178: | Line 178: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
=== | ===管理按钮点击=== | ||
既然我们检测了玩家点击按钮,我们需要写代码来管理点击按钮之后发生的事情。 | 既然我们检测了玩家点击按钮,我们需要写代码来管理点击按钮之后发生的事情。 | ||
在 [[onClientGUIClick]] 事件中,我们告诉它要调用 clientSubmitLogin 函数,每当 btnLogin 被点击。 | 在 [[onClientGUIClick]] 事件中,我们告诉它要调用 clientSubmitLogin 函数,每当 btnLogin 被点击。 |
Revision as of 10:37, 12 February 2017
在MTA:SA中有一个重要的特点,那就是可以制作自定义的GUI(Graphic User Interface)。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 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),我们可以用 addEvent 和 addEventHandler 来完成.
-- 创建一个名为 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.