ZH-CN/脚本编写介绍: Difference between revisions

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


所谓脚本编程,本质上即是在编写资源。资源可以定义它自身的类型为游戏模式、地图等。MTA 自带一些可供您的游戏模式使用的可选辅助用资源;例如 maplimits,一个用于限制玩家行动范围的资源;或是 deathpickups,用于创建武器拾取物以供辅助死亡竞技 (Deathmatch) 游戏模式。
所谓脚本编程,本质上即是在编写资源。资源可以定义它自身的类型为游戏模式、地图等。MTA 自带一些可供您的游戏模式使用的可选辅助用资源;例如 maplimits,一个用于限制玩家行动范围的资源;或是 deathpickups,用于创建武器拾取物以供辅助死亡竞技 (Deathmatch) 游戏模式。
{{Tip|您应该使用支持 Lua 格式代码的编辑器以编写脚本,以提高脚本编写的效率。我们建议使用 [http://notepad-plus.sourceforge.net/uk/site.htm Notepad++] 或 [http://luaedit.sourceforge.net/ LuaEdit]。我们也提供测试版的 [[MTASE|MTA 脚本编辑器]](目前仍在开发中)。}}
{{Tip|您应该使用支持 Lua 格式代码的编辑器以编写脚本,以提高脚本编写的效率。我们建议使用 [http://notepad-plus.sourceforge.net/uk/site.htm Notepad++] 或 [http://luaedit.sourceforge.net/ LuaEdit],这边比较推荐VSCode,可以在插件商店中寻找到MTASA的代码高亮。我们也提供测试版的 [[MTASE|MTA 脚本编辑器]](目前仍在开发中)。}}


==创建一个脚本==
==创建一个脚本==
Line 11: Line 11:
server/mods/deathmatch/resources/
server/mods/deathmatch/resources/


在该目录下,可以看到一系列 MTA 自带的示例脚本压缩文件。每个压缩文件都是一个 “资源”,在服务器启动时它们会被自动解压并被加载到服务器上运行。若需创建一个新资源,只需在该目录下新建一个任意名称的文件夹。在本教程中我们以 "myserver" 资源作为演示。
在该目录下,可以看到一系列 MTA 自带的示例脚本压缩文件。每个压缩文件都是一个 “资源”,在服务器启动时它们会被自动解压并被加载到服务器上运行。若需创建一个新资源,只需在该目录下新建一个任意名称的文件夹。在本教程中我们以 "myserver" 资源作为演示,注意:脚本资源创建时建议使用英文命名的文件夹(英文开头可带阿拉伯数字)。


现在请定位到以下路径:
现在请定位到以下路径:
Line 53: Line 53:
在玩家进入服务器前,必须要首先加载游戏模式。在服务器后台输入指令 "start myserver" 并按下回车键发送指令。服务器便会立即启动您刚刚所创建的游戏模式,并列出该游戏模式内所有的脚本代码性错误及警告提示。现在可以启动 MTA 客户端,点击 "Quick Connect" 并输入您在服务器后台上所看到的服务器 IP 地址及端口数。如果没有错误,几秒过后玩家就会出生在 Los Santos。
在玩家进入服务器前,必须要首先加载游戏模式。在服务器后台输入指令 "start myserver" 并按下回车键发送指令。服务器便会立即启动您刚刚所创建的游戏模式,并列出该游戏模式内所有的脚本代码性错误及警告提示。现在可以启动 MTA 客户端,点击 "Quick Connect" 并输入您在服务器后台上所看到的服务器 IP 地址及端口数。如果没有错误,几秒过后玩家就会出生在 Los Santos。


接下来,我们将会在脚本中添加一条用于在玩家附近刷出交通工具的游戏指令。您可以跳过本章节并阅读更为高级的[[Map manager|地图管理器]]教程;您也可以阅读[[图形操作界面 (GUI) 脚本编写介绍]]教程以学习如何在 MTA 下创建图形操作界面 (GUI) 及对其进行脚本编写。
接下来,我们将会在脚本中添加一条用于在玩家附近刷出交通工具的游戏指令。您可以跳过本章节并阅读更为高级的[[Map manager|地图管理器]]教程;您也可以阅读[[ZH-CN/脚本编写介绍 - 带有图形界面|脚本编写介绍 - 带有图形界面]]教程以学习如何在 MTA 下创建图形操作界面 (GUI) 及对其进行脚本编写。


==Creating a simple command==
==创建一个简单的游戏指令==
==创建一个简单的游戏指令==
打开先前我们所创建的 "script.lua" 文件。上文提到,我们需要创建一个可在玩家附近刷出交通工具的游戏指令。首先我们需要定义一个用于处理玩家输入指令的函数,以及一个用于创建玩家可从客户端控制台输入游戏指令的指令处理器。
打开先前我们所创建的 "script.lua" 文件。上文提到,我们需要创建一个可在玩家附近刷出交通工具的游戏指令。首先我们需要定义一个用于处理玩家输入指令的函数,以及一个用于创建玩家可从客户端控制台输入游戏指令的指令处理器。
Line 83: Line 82:
请记住前两个参数(thePlayer 及 commandName)是必不可少的参数,但是您可以随意命名这些参数以符合您的代码风格。
请记住前两个参数(thePlayer 及 commandName)是必不可少的参数,但是您可以随意命名这些参数以符合您的代码风格。


We called the [[addCommandHandler]] function this way already and since ''createVehicleForPlayer'' is a function too, it can be called that way as well. But we are using a command handler for that, which calls it in a similiar manner, internally.
我们已经调用了 [[addCommandHandler]] 函数;而由于 ''createVehicleForPlayer'' 也同样是一个函数,它也可以以相同的方式被内部所调用。
我们已经调用了 [[addCommandHandler]] 函数


For example: Someone types "createvehicle 468" ingame in the console to spawn a Sanchez, the command handler calls the createVehicleForPlayer function, as '''if''' we would have this line of code in the script:
例如:某玩家输入了游戏指令 "createvehicle 468" 以刷出 Sanchez,指令处理器随后会调用 createVehicleForPlayer 函数,然后我们看看这个 createVehicleForPlayer 函数是如何被调用的:
例如:某玩家输入了游戏指令 "createvehicle 468" 以刷出 Sanchez,指令处理器随后会调用 createVehicleForPlayer 函数,然后我们看看这个 createVehicleForPlayer 函数是如何被调用的:
<syntaxhighlight lang="lua">
<syntaxhighlight lang="lua">
Line 102: Line 99:
* 检查交通工具是否成功刷出,如果没有则输出错误信息
* 检查交通工具是否成功刷出,如果没有则输出错误信息


为了实现以上的需求,我们需要使用一些 API 函数。可以在[[Script Functions|服务端函数列表]]中找到我们所需的函数。首先我们需要一个用于取得玩家位置的函数。由于玩家属于 “元素” (Element),我们找到 '''针对元素的 API 函数''',并找到 [[getElementPosition]] 函数。单击该函数名便可阅读该函数的文档。文档内详细说明了函数的调用方法,返回值以及其示例代码。调用方法告诉我们在调用函数时应该提供什么参数。
为了实现以上的需求,我们需要使用一些 API 函数。可以在[[Script Functions|服务端函数列表]]中找到我们所需的函数。首先我们需要一个用于取得玩家位置的函数。由于玩家属于 “元素” (Element),我们找到 '''针对元素的 API 函数''',并找到 [[getElementPosition]] 函数。单击该函数名便可阅读该函数的文档。文档内详细说明了函数的调用语法,返回值以及其示例代码。函数调用语法告诉我们在调用函数时应该提供什么参数。


For [[getElementPosition]], the syntax is:
[[getElementPosition]] 的调用语法为:
<syntaxhighlight lang="lua">
<syntaxhighlight lang="lua">
float, float, float getElementPosition ( element theElement )
float, float, float getElementPosition ( element theElement )
</syntaxhighlight>
</syntaxhighlight>


The three ''float'' in front of the function name are the return type. In this case it means the function returns three floating point numbers. (x, y and z) Within the parentheses, you can see what arguments you have to submit. In this case only the element whose position you want to get, which is the player in our example.
函数名前的三个 "float" 表示的是返回值类型。本例中表示该函数返回三个浮点数(x、y 及 z)。括号内,可以看到调用该函数时所需提供的参数。本例中只需要传递一个表示欲获取其位置的元素,在这里即玩家。


<syntaxhighlight lang="lua">
<syntaxhighlight lang="lua">
function createVehicleForPlayer(thePlayer, command, vehicleModel)
function createVehicleForPlayer(thePlayer, command, vehicleModel)
-- get the position and put it in the x,y,z variables
-- 获取玩家位置并将信息放入 x、y 及 z 变量中
-- (local means, the variables only exist in the current scope, in this case, the function)
-- (local 的意思是定义一个局部变量,在其作用域以外的地方无法访问,本例中即函数作用域)
local x,y,z = getElementPosition(thePlayer)
local x,y,z = getElementPosition(thePlayer)
end
end
</syntaxhighlight>
</syntaxhighlight>


Next we want to ensure that the vehicle won't spawn directly in the player, so we add a few units to the ''x'' variable, which will make it spawn east from the player.
下一步,我们希望确保交通工具不会直接刷在玩家所在的位置(这样会玩家卡在车辆内),因此我们把 "x" 变量的值增加了少许,让车辆在距离玩家更东的位置被刷出。


<syntaxhighlight lang="lua">
<syntaxhighlight lang="lua">
function createVehicleForPlayer(thePlayer, command, vehicleModel)
function createVehicleForPlayer(thePlayer, command, vehicleModel)
local x,y,z = getElementPosition(thePlayer) -- get the position of the player
local x,y,z = getElementPosition(thePlayer) -- 获取玩家位置
x = x + 5 -- add 5 units to the x position
x = x + 5 -- x 坐标增加 5 个单位
end
end
</syntaxhighlight>
</syntaxhighlight>


Now we need another function, one to spawn a vehicle. We once again search for it on the [[Scripting Functions|Server Functions List]], this time - since we are talking about vehicles - in the '''Vehicle functions''' section, where we will choose [[createVehicle]]. In this function's syntax, we only have one return type (which is more common), a vehicle element that points to the vehicle we just created. Also, we see that some arguments are enclosed within [ ] which means that those are optional.
现在我们需要另一个用于刷出车辆的函数。再次打开 [[Scripting Functions|服务端函数列表]],这次因为要找的是和交通工具有关的,所以定位到 '''交通工具函数''',并阅读 [[createVehicle]] 的文档。根据该函数的调用语法,该函数只有一个返回类型(最常见的情况),表示的是创建了的交通工具元素。同样,我们可以看到在调用语法中有一些用方括号 [ ] 括起来的参数,这类参数说明它们是可选的,调用时可以不提供。


We already have all arguments we need for [[createVehicle]] in our function: The position we just calculated in the ''x,y,z'' variables and the model id that we provided through the command ("createvehicle 468") and can access in the function as ''vehicleModel'' variable.
在我们所定义的函数中,一切需要传递给 [[createVehicle]] 函数的参数都准备好了:计算好的 "x, y, z" 变量以及通过玩家输入的游戏指令 "createvehicle 468" 所得到的交通工具 ID,可以通过 ''vehicleModel'' 参数得到该 ID。


<syntaxhighlight lang="lua">
<syntaxhighlight lang="lua">
function createVehicleForPlayer(thePlayer, command, vehicleModel)
function createVehicleForPlayer(thePlayer, command, vehicleModel)
local x,y,z = getElementPosition(thePlayer) -- get the position of the player
local x,y,z = getElementPosition(thePlayer) -- 获取玩家位置
x = x + 5 -- add 5 units to the x position
x = x + 5 -- x 坐标增加 5 个单位
-- create the vehicle and store the returned vehicle element in the ''createdVehicle'' variable
        -- 创建交通工具并将返回值存入到 ''createdVehicle'' 变量内
local createdVehicle = createVehicle(tonumber(vehicleModel),x,y,z)
local createdVehicle = createVehicle(tonumber(vehicleModel),x,y,z)
end
end
</syntaxhighlight>
</syntaxhighlight>


Of course this code can be improved in many ways, but at least we want to add a check whether the vehicle was created successfully or not. As we can read on the [[createVehicle]] page under '''Returns''', the function returns ''false'' when it was unable to create the vehicle. Thus, we check the value of the ''createVehicle'' variable.
当然这些代码还有改善的余地,但至少我们还可以添加一个用于检测交通工具是否成功创建的代码。阅读 [[createVehicle]] 函数文档的 '''返回值''' 部分,该部分说明:若该函数返回 ''false'',则说明交通工具创建失败。因此,我们需要检查 ''createVehicle'' 变量的值以检测交通工具是否创建成功。


Now we have our complete script:
以下是完整的脚本代码:
<syntaxhighlight lang="lua">
<syntaxhighlight lang="lua">
function createVehicleForPlayer(thePlayer, command, vehicleModel)
function createVehicleForPlayer(thePlayer, command, vehicleModel)
local x,y,z = getElementPosition(thePlayer) -- get the position of the player
local x,y,z = getElementPosition(thePlayer) -- 获取玩家位置
x = x + 5 -- add 5 units to the x position
x = x + 5 -- x 坐标增加 5 个单位
local createdVehicle = createVehicle(tonumber(vehicleModel),x,y,z)
local createdVehicle = createVehicle(tonumber(vehicleModel),x,y,z)
-- check if the return value was ''false''
-- 检查返回值是否为 ''false''
if (createdVehicle == false) then
if (createdVehicle == false) then
-- if so, output a message to the chatbox, but only to this player.
                -- 如果是,将错误信息发送给该特定玩家
outputChatBox("Failed to create vehicle.",thePlayer)
outputChatBox("Failed to create vehicle.",thePlayer)
end
end
Line 158: Line 155:
</syntaxhighlight>
</syntaxhighlight>


As you can see, we introduced another function with [[outputChatBox]]. By now, you should be able to explore the function's documentation page yourself. For more advanced scripting, please check out the [[Map manager|Map Manager]].
上例代码中,还介绍了另一个用于发送客户端信息的 [[outputChatBox]] 函数。现在,您应该有能力可以自行阅读 API 函数文档了。若需要进一步学习高级脚本编写,请阅读[[Map manager|地图管理器]]教程。


==What you need to know==
==必知==
You already read some things about resources, command handlers and finding functions in the documentation in the first paragraph, but there is much more to learn. This section will give you a rather short overview over some of these things, while linking to related pages if possible.
目前为止,您已经接触到及知悉了有关资源、指令处理器及如何在文档中寻找 API 函数,但要学的还有很多。本节将粗略告诉您有关这些学习内容,您可以自行阅读相关教程。
===Clientside and Serverside scripts===
===客户端及服务端脚本===
You may have already noticed these or similiar terms (Server/Client) somewhere on this wiki, mostly in conjunction with functions. MTA not only supports scripts that run on the server and provide commands (like the one we wrote above) or other features, but also scripts that run on the MTA client the players use to connect to the server. The reason for this is, that some features MTA provides have to be clientside (like a GUI - Graphical User Interface), others should be because they work better and still others are better off to be serverside or just don't work clientside.
您可能曾经了解或是看到过所谓 “客户端及服务端脚本” (Server/Client Script) 的概念或名称了。事实上,您不仅能够编写典型的运行在服务器之上的,用于提供游戏指令处理等需求的脚本;还能够编写运行在 MTA 玩家客户端之上的脚本。之可以编写客户端脚本,原因是一些 MTA 所提供的特殊功能只能在客户端上执行(例如图形操作界面),或是在客户端上执行一些代码比在服务端上执行效率要高些。


Most scripts you will make (gamemodes, maps) will probably be serverside, like the one we wrote in the first section. If you run into something that can't be solved serverside, you will probably have to make it clientside. For a clientside script for example, you would create a ordinary script file (for example called ''client.lua'') and specify it in the meta.xml, like this:
大部分您所制作的资源(游戏模式、地图)可能都是服务端脚本,例如本教程前面所介绍的脚本。但如果您所编写的代码无法在服务端上执行,则您可能需要令其可在客户端上运行(即编写客户端脚本)。若要开始尝试编写一个客户端脚本,您需要创建一个脚本文件(例如,把它命名为 ''client.lua''),并修改 meta.xml 如下:
<syntaxhighlight lang="xml">
<syntaxhighlight lang="xml">
<script src="client.lua" type="client" />
<script src="client.lua" type="client" />
</syntaxhighlight>
</syntaxhighlight>
The ''type'' attribute defaults to 'server', so you only need to specify it for clientside scripts. When you do this, the clientside script will be downloaded to the player's computer once he connects to the server. Read more about [[Client side scripts]].
''type'' 属性默认值为 'server'(即默认服务端脚本),因此只有在需要指定其是客户端脚本时才显式指定 ''type'' 属性。一个脚本文件一旦其 ''type'' 属性的值设置为 ''client'',则玩家连接到服务器时,该脚本文件会自动下载到玩家客户端。有关更多信息,请阅读[[客户端脚本]]教程。


===More complex resources===
===更复杂的资源===
The previous section showed briefly how to add clientside scripts to the resource, but there is also much more possible. As mentioned at the very top of this page, resources can be pretty much everything. Their purpose is defined by what they do. Let's have some theoretical resources, by looking at the files it contains, the ''meta.xml'' and what they might do:
上一节简单介绍了如何添加一个客户端脚本,但其实您什么都可以做到。正如本教程开头提到过,资源可以是任何类型的游戏模块。这些资源本身做什么或提供什么功能,便决定了它们的类型是什么。首先我们来分析一下下面的这些资源:


====First example - A utility script====
====第一个示例资源 —— 辅助类资源====
<syntaxhighlight lang="xml">
<syntaxhighlight lang="xml">
/admin_commands
/admin_commands
Line 189: Line 186:
</syntaxhighlight>
</syntaxhighlight>


* The ''commands.lua'' provides some admin commands, like banning a player, muting or something else that can be used to admin the server
* ''commands.lua'' 提供一些管理员指令,例如封禁玩家、玩家禁言等
* The ''client.lua'' provides a GUI to be able to perform the mentioned actions easily
* ''client.lua'' 提供图形操作界面以简化玩家操作


This example might be running all the time (maybe even auto-started when the server starts) as it's useful during the whole gaming experience and also wont interfere with the gameplay, unless an admin decides to take some action of course.
该示例资源可以随时被使用(也可以设置为随服务器而自动启动),原因是该资源对于整个游戏流程而言有一定的帮助(管理服务器)且不会影响到正常的游戏过程。


====Second example - A gamemode====
====第二个示例资源 —— 游戏模式====
<syntaxhighlight lang="xml">
<syntaxhighlight lang="xml">
/counterstrike
/counterstrike
Line 209: Line 206:
</syntaxhighlight>
</syntaxhighlight>


* The ''counterstrike.lua'' contains similiar to the following features:
* ''counterstrike.lua'' 包含有类似于以下功能的实现:
** Let players choose their team and spawn them
** 允许玩家选择其团队并刷出玩家
** Provide them with weapons, targets and instructions (maybe read from a Map, see below)
** 把武器、目标及游戏指示提供给玩家
** Define the game's rules, e.g. when does the round end, what happens when a player dies
** 规定游戏规则。例如:一盘游戏的时间、玩家死后会发生什么等
** .. and maybe some more
** 等等等等...
* The ''buymenu.lua'' is a clientside script and creates a menu to buy weapons
* ''buymenu.lua'' 是一个客户端脚本,其用于创建武器购买菜单(使用到了图形操作界面,因此必须写为客户端脚本)


This example can be called a gamemode, since it not only intereferes with the gameplay, but actually defines the rules of it. The ''type'' attribute indicates that this example works with the [[Map manager]], yet another resource that was written by the QA Team to manage gamemodes and map loading. It is highly recommended that you base your gamemodes on the techniques it provides.
该示例资源便是一个游戏模式,因为它不但影响到了游戏过程,还实际规定了''游戏规则''。该资源的 ''type'' 属性表明了该资源是运行在[[地图管理器]]上的,但 QA 团队编写的另一个资源可以用于帮助您管理游戏模式以及地图加载。强烈建议您在该辅助资源的基础上编写您的游戏模式。


This also means that the gamemode probably won't run without a map. Gamemodes should always be as generic as possible. An example for a map is stated in the next example.
这可能也说明了游戏模式在没有地图的情况下无法运行。游戏模式本身就应该包含有至少一个地图。下一个示例将会介绍地图。


====Third example - A Map====
====第三个示例 —— 地图====
<syntaxhighlight lang="xml">
<syntaxhighlight lang="xml">
/cs-airport
/cs-airport
Line 235: Line 232:
</syntaxhighlight>
</syntaxhighlight>


* The ''airport.map'' in a XML file that provides information about the map to the gamemode, these may include:
* XML 格式的 ''airport.map'' 文件向游戏模式提供了有关该地图的信息,包括:
** Where the players should spawn, with what weapons, what teams there are
** 玩家的出生点、出生时配备的武器,以及玩家所属的团队
** What the targets are
** 玩家的目标
** Weather, World Time, Timelimit
** 天气、世界时间、时间限制
** Provide vehicles
** 提供交通工具
* The ''airport.lua'' might contain map-specific features, that may include:
* ''airport.lua'' 可能包含了一些辅助地图自身的功能:
** Opening some door/make something explode when something specific happens
** 当特定的事情发生时打开特定的门或使某物爆炸
** Create or move some custom objects, or manipulate objects that are created through the .map file
** 创建或移动一些自定义对象,或是控制通过 .map 文件所创建的对象
** .. anything else map-specific you can think of
** 等等等等...


As you can see, the ''type'' attribute changed to 'map', telling the [[Map manager]] that this resource is a map, while the ''gamemodes'' attribute tells it for which gamemodes this map is valid, in this case the gamemode from the above example.
正如您所见的,''type'' 属性改为了 'map',告诉[[地图管理器]]该资源为地图;而 ''gamemodes'' 属性指明了该地图只有在什么游戏模式下才能够运行,在本例中所指定的游戏模式即上面所创建的。
What may come as a surprise is that there is also a script in the Map resource. Of course this is not necessarily needed in a map, but opens a wide range of possibilities for map makers to create their own world within the rules of the gamemode they create it for.
地图资源内也可以包含脚本。当然,地图并不一定需要包含脚本,但是这可以在游戏模式所设计的游戏规则以外再允许地图创造者设计这些地图自己的游戏规则。


The ''airport.map'' file might look similiar to this:
''airport.map'' 文件内容可能如下:
<syntaxhighlight lang="xml">
<syntaxhighlight lang="xml">
<map mode="deathmatch" version="1.0">
<map mode="deathmatch" version="1.0">
Line 265: Line 262:
</syntaxhighlight>
</syntaxhighlight>


When a gamemode is started with a map, the map resources is automatically started by the mapmanager and the information it contains can be read by the gamemode resource. When the map changes, the current map resource is stopped and the next map resource is started. For a more in-depth explanation and examples of how map resources are utilized in the main script, please visit the [[Writing Gamemodes]] page.
当游戏模式及地图同时被加载,地图管理器会自动启动地图资源,随后游戏模式资源会开始读取地图信息;当切换到其他地图时,当前的地图资源会被停止,然后轮到下一张地图资源被加载。请阅读[[编写游戏模式]]教程以学习更深入的地图资源管理模式。


===Events===
===事件===
Events are the way MTA tells scripts about things that happen. For example when a player dies, the [[onPlayerWasted]] event is triggered. In order to perform any actions when a player dies, you have to prepare yourself similiar to adding a command handler, as shown in [[#Writing_the_script|the first chapter]].
MTA 会以触发事件的形式告知脚本发生了什么。例如,当任意玩家死亡时,[[onPlayerWasted]] 事件会被触发。为了能够及时地在任意玩家死亡时能够执行一些操作,您需要自定义一个用于处理该类型事件的事件处理器。


This example will output a message with the name of the player who died:
下列示例代码会在任意玩家死后,向所有玩家发送该玩家死亡的信息:
<syntaxhighlight lang="lua">
<syntaxhighlight lang="lua">
function playerDied(totalAmmo, killer, killerWeapon, bodypart)
function playerDied(totalAmmo, killer, killerWeapon, bodypart)
Line 278: Line 275:
</syntaxhighlight>
</syntaxhighlight>


Instead of showing what arguments are needed, the documentation page for Events shows what parameters are passed to the handler function, similiar to the way a [[#About_command_handlers|command handler]] does, just that it is different from event to event. Another important point is the ''source'' variable, that exists in handler functions. It doesn't have to be added to the parameter list of the function, but it still exists. It has a different value from event to event, for player events (as in the example above) it is the player element. As another example, you can take a look at the basic spawning player script in the first section to get an idea how ''source'' is used.
事件文档列举了不同事件触发后将会传递到事件处理函数的不同参数。请留意 ''source'' 变量,虽然其并非是参数,但每个处理函数都隐式包含有该变量。在不同的事件中,该变量表示的概念不同;例如玩家事件的处理函数中,该变量表示触发了事件的玩家元素。
 
==然后呢?==
现在,您应该已经大概了解了 MTA 脚本编写的一些基础知识以及如何阅读文档了。[[首页]]提供有更多的信息、教程以及参考链接。您可以随时阅读这些文章以更深入地学习 MTA 脚本编写。
{{note|强烈建议您开始阅读[[ZH-CN/脚本调试教程|脚本调试教程]]。编写脚本时,调试是十分有必要的;我们也建议您参考[[预定义变量列表]]中的信息以帮助您更高效率地编写 MTA 脚本。}}
'''相关参考:'''


==Where to go from here==
You should now be familiar with the most basic aspects of MTA scripting and also a bit with the documentation. The [[Main Page]] provides you with links to more information, Tutorials and References that allow a deeper look into the topics you desire to learn about.
{{note|From here we recommend reading the [[debugging]] tutorial. Good debugging skills are an absolute necessity when you are making scripts. We also recommend you to use the [[predefined variables list]] to help you with certain tasks and make scripting easier and faster.}}
'''See also:'''
* [[Advanced Topics]]
* [[Advanced Topics]]
[[es:Introducción a la Programación]]
 
[[it:Introduzione allo scripting]]
[[en:Scripting Introduction]]
[[nl:Scripting_introductie]]
[[pt-br:Introdução ao Scripting]]
[[ru:Scripting Introduction]]
[[ar:مقدمه_في_البرمجه]]

Latest revision as of 06:22, 2 May 2020

对于 MTA 服务器而言,“资源” (Resource) 是十分重要的一个概念。一个资源本身表现为一个包含了一系列文件的文件夹或压缩文件 (zip),外包含一个用于指示服务器如何加载资源以及资源所包含文件说明的元数据文件 (Meta File)。可把资源的概念视作是运行在操作系统之上的程序 —— 它可以被启动或停止执行,且多个资源可以同时运行在服务器上。

所谓脚本编程,本质上即是在编写资源。资源可以定义它自身的类型为游戏模式、地图等。MTA 自带一些可供您的游戏模式使用的可选辅助用资源;例如 maplimits,一个用于限制玩家行动范围的资源;或是 deathpickups,用于创建武器拾取物以供辅助死亡竞技 (Deathmatch) 游戏模式。

[[{{{image}}}|link=|]] Tip: 您应该使用支持 Lua 格式代码的编辑器以编写脚本,以提高脚本编写的效率。我们建议使用 Notepad++LuaEdit,这边比较推荐VSCode,可以在插件商店中寻找到MTASA的代码高亮。我们也提供测试版的 MTA 脚本编辑器(目前仍在开发中)。

创建一个脚本

第一步,我们将开始一步一步学习如何编写一个能让玩家到处行走的简单脚本。

脚本都在哪里存放着?

首先让我们来看看脚本的文件结构。打开 MTA 服务器目录,并定位到以下路径:

server/mods/deathmatch/resources/

在该目录下,可以看到一系列 MTA 自带的示例脚本压缩文件。每个压缩文件都是一个 “资源”,在服务器启动时它们会被自动解压并被加载到服务器上运行。若需创建一个新资源,只需在该目录下新建一个任意名称的文件夹。在本教程中我们以 "myserver" 资源作为演示,注意:脚本资源创建时建议使用英文命名的文件夹(英文开头可带阿拉伯数字)。

现在请定位到以下路径:

server/mods/deathmatch/resources/myserver/

定义您的资源

为了能够让服务器得知资源内所包含的数据,每个资源内都必须包含一个位于资源根目录下的 "meta.xml" 文件(本例即 "myserver" 文件夹为资源根目录)。因此我们需要新建该文件,并以记事本方式打开。

在 "meta.xml" 文件内输入以下的代码:

<meta>
     <info author="YourName" type="gamemode" name="My Server" description="My first MTA server" />
     <script src="script.lua" />
</meta>

在 "<info />" 标签中,有一个用于指定资源类型的 "type" 属性。以上代码中指定了该资源类型为 "gamemode"(游戏模式),其他可选的资源类型还有常规包含文件或 "map"(地图),后文将会详细解释。目前只需要知道游戏模式是所有服务器的必不可少的核心,任意服务器都必须要有一个游戏模式。

"<script />" 标签指明了该资源内所包含的脚本文件,后文将会详细解释。

创建一个简单的脚本

注意上例代码中的 "<script />" 标签中并没有将 .lua 文件指定在其他目录路径下,因此现在需要创建一个与 meta.xml 文件同目录下的 .lua 脚本文件;随后,请把以下代码复制到新创建的 script.lua 文件中:

local spawnX, spawnY, spawnZ = 1959.55, -1714.46, 10
function joinHandler()
	spawnPlayer(source, spawnX, spawnY, spawnZ)
	fadeCamera(source, true)
	setCameraTarget(source, source)
	outputChatBox("Welcome to My Server", source)
end
addEventHandler("onPlayerJoin", getRootElement(), joinHandler)

当玩家进入服务器后,脚本会将玩家刷到上例代码所指定的出生点坐标 (x, y, z)。注意必须要使用 "fadeCamera" 函数以避免屏幕全黑的情况;同样,DP2 以后的 MTA 版本中,也同样需要使用 "setCameraTarget" 函数以设置玩家摄像机的目标,否则玩家进入服务器后只会见到游戏内蓝色的天空。

source 变量表示的是事件的触发者。由于以上代码会在玩家进入服务器时被触发,因此您需要使用该变量以确定是哪位玩家进入了服务器。因此上例代码只会影响到新进入了服务器的玩家,而非服务器内的所有玩家。

上例代码中,我们可以看到一个 addEventHandler 函数;从中可看见 3 个参数:'onPlayerJoin',其表明何时代码会被触发;getRootElement(),其表明了可能的事件触发者(getRootElement() 表示的是游戏内的一切事物或玩家,因此该事件可能是由游戏内的任意事物或玩家所触发的)。以及 joinHandler,其表明哪个函数在该事件 ('onPlayerJoin') 触发后会被执行。其他的代码细节会在后文中详细介绍,现在就启动服务器然后测试一下代码效果吧!

执行脚本

只需要执行 server/ 目录下的可执行文件即可启动服务器。启动服务器后,首先可以从服务器后台上看到一系列的服务器基本信息;注意 port number(端口数)信息,在进入服务器前需要使用到该信息。随后服务器会加载所有位于 mods/deathmatch/resources 目录下的资源,最后会看到提示 "ready to accept connections!",即表明服务器启动成功。

在玩家进入服务器前,必须要首先加载游戏模式。在服务器后台输入指令 "start myserver" 并按下回车键发送指令。服务器便会立即启动您刚刚所创建的游戏模式,并列出该游戏模式内所有的脚本代码性错误及警告提示。现在可以启动 MTA 客户端,点击 "Quick Connect" 并输入您在服务器后台上所看到的服务器 IP 地址及端口数。如果没有错误,几秒过后玩家就会出生在 Los Santos。

接下来,我们将会在脚本中添加一条用于在玩家附近刷出交通工具的游戏指令。您可以跳过本章节并阅读更为高级的地图管理器教程;您也可以阅读脚本编写介绍 - 带有图形界面教程以学习如何在 MTA 下创建图形操作界面 (GUI) 及对其进行脚本编写。

创建一个简单的游戏指令

打开先前我们所创建的 "script.lua" 文件。上文提到,我们需要创建一个可在玩家附近刷出交通工具的游戏指令。首先我们需要定义一个用于处理玩家输入指令的函数,以及一个用于创建玩家可从客户端控制台输入游戏指令的指令处理器。

-- 定义一个由指令处理器所调用的函数,并提供三个参数:thePlayer, command, vehicleModel
function createVehicleForPlayer(thePlayer, command, vehicleModel)
   -- 创建交通工具的代码
end

-- 创建一个指令处理器
addCommandHandler("createvehicle", createVehicleForPlayer)

"提示:您可以点击示例代码中的函数名以查看该 API 函数的文档。"

有关指令处理器

addCommandHandler 的第一个参数表明了玩家可以从客户端输入的游戏指令(同时也表明新建了这个游戏指令);第二个参数是该游戏指令发送到服务端后所调用的函数,本例中该函数名为 "createVehicleForPlayer"。

如果您有一定的编程经验,您应当知道调用函数的方法是这样的:

functionName(参数1, 参数2, 参数3, ..)
functionName(thePlayer, commandName, argument3, ..)

看看上例代码,我们发现参数1 为 thePlayer,参数2 为 commandName。thePlayer 表明哪位玩家发送了该游戏指令;而 commandName 表明游戏指令字符串。因此如果玩家发送了指令 "/greet",commandName 便是 "greet"(不包含指令前面的斜杠 "/")。参数3 是玩家额外的输入,后文中将会提到。 请记住前两个参数(thePlayer 及 commandName)是必不可少的参数,但是您可以随意命名这些参数以符合您的代码风格。

我们已经调用了 addCommandHandler 函数;而由于 createVehicleForPlayer 也同样是一个函数,它也可以以相同的方式被内部所调用。

例如:某玩家输入了游戏指令 "createvehicle 468" 以刷出 Sanchez,指令处理器随后会调用 createVehicleForPlayer 函数,然后我们看看这个 createVehicleForPlayer 函数是如何被调用的:

createVehicleForPlayer(thePlayer,"createvehicle","468") -- thePlayer 表示的是输入了 createvehicle 指令的玩家

可以看到,它提供了几个参数:输入了指令的玩家,指令字符串本身(不包括前面的斜杠 '/')以及核心指令 createvehicle 以后的任意文本,本例中 "468" 为核心指令以后的文本,其为 Sanchez 交通工具的 ID。所有的指令处理器中,前两个参数都是一样且必须有的(参见 addEventHandler 文档)。因此,任意的指令处理器中,您都必须得要先定义这两个基本参数,再附加任意数量的附加参数。(附加参数有多少个,核心指令后的附加指令信息就有多少;本例中,createvehicle 有一个表示交通工具 ID 的附加指令信息,因此附加参数数量同为一个)

"注意:必须要在定义了指令处理函数之后再创建指令处理器,否则指令处理器届时将无法找到其处理函数。"

编写函数

为了完成我们所创建的函数,我们得先想想我们要在该函数内完成的一些事:

  • 获取玩家位置以便知道在何处刷出交通工具(我们希望在玩家身边刷出)
  • 计算最理想的交通工具刷出点(我们不希望玩家会卡在车辆内)
  • 刷出交通工具
  • 检查交通工具是否成功刷出,如果没有则输出错误信息

为了实现以上的需求,我们需要使用一些 API 函数。可以在服务端函数列表中找到我们所需的函数。首先我们需要一个用于取得玩家位置的函数。由于玩家属于 “元素” (Element),我们找到 针对元素的 API 函数,并找到 getElementPosition 函数。单击该函数名便可阅读该函数的文档。文档内详细说明了函数的调用语法,返回值以及其示例代码。函数调用语法告诉我们在调用函数时应该提供什么参数。

getElementPosition 的调用语法为:

float, float, float getElementPosition ( element theElement )

函数名前的三个 "float" 表示的是返回值类型。本例中表示该函数返回三个浮点数(x、y 及 z)。括号内,可以看到调用该函数时所需提供的参数。本例中只需要传递一个表示欲获取其位置的元素,在这里即玩家。

function createVehicleForPlayer(thePlayer, command, vehicleModel)
	-- 获取玩家位置并将信息放入 x、y 及 z 变量中
	-- (local 的意思是定义一个局部变量,在其作用域以外的地方无法访问,本例中即函数作用域)
	local x,y,z = getElementPosition(thePlayer)
end

下一步,我们希望确保交通工具不会直接刷在玩家所在的位置(这样会玩家卡在车辆内),因此我们把 "x" 变量的值增加了少许,让车辆在距离玩家更东的位置被刷出。

function createVehicleForPlayer(thePlayer, command, vehicleModel)
	local x,y,z = getElementPosition(thePlayer) -- 获取玩家位置
	x = x + 5 -- x 坐标增加 5 个单位
end

现在我们需要另一个用于刷出车辆的函数。再次打开 服务端函数列表,这次因为要找的是和交通工具有关的,所以定位到 交通工具函数,并阅读 createVehicle 的文档。根据该函数的调用语法,该函数只有一个返回类型(最常见的情况),表示的是创建了的交通工具元素。同样,我们可以看到在调用语法中有一些用方括号 [ ] 括起来的参数,这类参数说明它们是可选的,调用时可以不提供。

在我们所定义的函数中,一切需要传递给 createVehicle 函数的参数都准备好了:计算好的 "x, y, z" 变量以及通过玩家输入的游戏指令 "createvehicle 468" 所得到的交通工具 ID,可以通过 vehicleModel 参数得到该 ID。

function createVehicleForPlayer(thePlayer, command, vehicleModel)
	local x,y,z = getElementPosition(thePlayer) -- 获取玩家位置
	x = x + 5 -- x 坐标增加 5 个单位
        -- 创建交通工具并将返回值存入到 ''createdVehicle'' 变量内
	local createdVehicle = createVehicle(tonumber(vehicleModel),x,y,z)
end

当然这些代码还有改善的余地,但至少我们还可以添加一个用于检测交通工具是否成功创建的代码。阅读 createVehicle 函数文档的 返回值 部分,该部分说明:若该函数返回 false,则说明交通工具创建失败。因此,我们需要检查 createVehicle 变量的值以检测交通工具是否创建成功。

以下是完整的脚本代码:

function createVehicleForPlayer(thePlayer, command, vehicleModel)
	local x,y,z = getElementPosition(thePlayer) -- 获取玩家位置
	x = x + 5 -- x 坐标增加 5 个单位
	local createdVehicle = createVehicle(tonumber(vehicleModel),x,y,z)
	-- 检查返回值是否为 ''false''
	if (createdVehicle == false) then
                -- 如果是,将错误信息发送给该特定玩家
		outputChatBox("Failed to create vehicle.",thePlayer)
	end
end
addCommandHandler("createvehicle", createVehicleForPlayer)

上例代码中,还介绍了另一个用于发送客户端信息的 outputChatBox 函数。现在,您应该有能力可以自行阅读 API 函数文档了。若需要进一步学习高级脚本编写,请阅读地图管理器教程。

必知

目前为止,您已经接触到及知悉了有关资源、指令处理器及如何在文档中寻找 API 函数,但要学的还有很多。本节将粗略告诉您有关这些学习内容,您可以自行阅读相关教程。

客户端及服务端脚本

您可能曾经了解或是看到过所谓 “客户端及服务端脚本” (Server/Client Script) 的概念或名称了。事实上,您不仅能够编写典型的运行在服务器之上的,用于提供游戏指令处理等需求的脚本;还能够编写运行在 MTA 玩家客户端之上的脚本。之可以编写客户端脚本,原因是一些 MTA 所提供的特殊功能只能在客户端上执行(例如图形操作界面),或是在客户端上执行一些代码比在服务端上执行效率要高些。

大部分您所制作的资源(游戏模式、地图)可能都是服务端脚本,例如本教程前面所介绍的脚本。但如果您所编写的代码无法在服务端上执行,则您可能需要令其可在客户端上运行(即编写客户端脚本)。若要开始尝试编写一个客户端脚本,您需要创建一个脚本文件(例如,把它命名为 client.lua),并修改 meta.xml 如下:

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

type 属性默认值为 'server'(即默认服务端脚本),因此只有在需要指定其是客户端脚本时才显式指定 type 属性。一个脚本文件一旦其 type 属性的值设置为 client,则玩家连接到服务器时,该脚本文件会自动下载到玩家客户端。有关更多信息,请阅读客户端脚本教程。

更复杂的资源

上一节简单介绍了如何添加一个客户端脚本,但其实您什么都可以做到。正如本教程开头提到过,资源可以是任何类型的游戏模块。这些资源本身做什么或提供什么功能,便决定了它们的类型是什么。首先我们来分析一下下面的这些资源:

第一个示例资源 —— 辅助类资源

/admin_commands
	/meta.xml
	/commands.lua
	/client.lua
<meta>
	<info author="Someguy" description="admin commands" />
	<script src="commands.lua" />
	<script src="client.lua" type="client" />
</meta>
  • commands.lua 提供一些管理员指令,例如封禁玩家、玩家禁言等
  • client.lua 提供图形操作界面以简化玩家操作

该示例资源可以随时被使用(也可以设置为随服务器而自动启动),原因是该资源对于整个游戏流程而言有一定的帮助(管理服务器)且不会影响到正常的游戏过程。

第二个示例资源 —— 游戏模式

/counterstrike
	/meta.xml
	/counterstrike.lua
	/buymenu.lua
<meta>
	<info author="Someguy" description="Counterstrike remake" type="gamemode" />
	<script src="counterstrike.lua" />
	<script src="buymenu.lua" type="client" />
</meta>
  • counterstrike.lua 包含有类似于以下功能的实现:
    • 允许玩家选择其团队并刷出玩家
    • 把武器、目标及游戏指示提供给玩家
    • 规定游戏规则。例如:一盘游戏的时间、玩家死后会发生什么等
    • 等等等等...
  • buymenu.lua 是一个客户端脚本,其用于创建武器购买菜单(使用到了图形操作界面,因此必须写为客户端脚本)

该示例资源便是一个游戏模式,因为它不但影响到了游戏过程,还实际规定了游戏规则。该资源的 type 属性表明了该资源是运行在地图管理器上的,但 QA 团队编写的另一个资源可以用于帮助您管理游戏模式以及地图加载。强烈建议您在该辅助资源的基础上编写您的游戏模式。

这可能也说明了游戏模式在没有地图的情况下无法运行。游戏模式本身就应该包含有至少一个地图。下一个示例将会介绍地图。

第三个示例 —— 地图

/cs-airport
	/meta.xml
	/airport.map
	/airport.lua
<meta>
	<info author="Someguy" description="Counterstrike airport map" type="map" gamemodes="counterstrike" />
	<map src="airport.map" />
	<script src="airport.lua" />
</meta>
  • XML 格式的 airport.map 文件向游戏模式提供了有关该地图的信息,包括:
    • 玩家的出生点、出生时配备的武器,以及玩家所属的团队
    • 玩家的目标
    • 天气、世界时间、时间限制
    • 提供交通工具
  • airport.lua 可能包含了一些辅助地图自身的功能:
    • 当特定的事情发生时打开特定的门或使某物爆炸
    • 创建或移动一些自定义对象,或是控制通过 .map 文件所创建的对象
    • 等等等等...

正如您所见的,type 属性改为了 'map',告诉地图管理器该资源为地图;而 gamemodes 属性指明了该地图只有在什么游戏模式下才能够运行,在本例中所指定的游戏模式即上面所创建的。 地图资源内也可以包含脚本。当然,地图并不一定需要包含脚本,但是这可以在游戏模式所设计的游戏规则以外再允许地图创造者设计这些地图自己的游戏规则。

airport.map 文件内容可能如下:

<map mode="deathmatch" version="1.0">
	<terrorists>
		<spawnpoint posX="2332.23" posY="-12232.33" posZ="4.42223" skins="23-40" />
	</terrorists>
	<counterterrorists>
		<spawnpoint posX="2334.23443" posY="-12300.233" posZ="10.2344" skins="40-50" />
	</counterterrorists>

	<bomb posX="23342.23" posY="" posZ="" />
	
	<vehicle posX="" posY="" posZ="" model="602" />	
	<vehicle posX="" posY="" posZ="" model="603" />	
</map>

当游戏模式及地图同时被加载,地图管理器会自动启动地图资源,随后游戏模式资源会开始读取地图信息;当切换到其他地图时,当前的地图资源会被停止,然后轮到下一张地图资源被加载。请阅读编写游戏模式教程以学习更深入的地图资源管理模式。

事件

MTA 会以触发事件的形式告知脚本发生了什么。例如,当任意玩家死亡时,onPlayerWasted 事件会被触发。为了能够及时地在任意玩家死亡时能够执行一些操作,您需要自定义一个用于处理该类型事件的事件处理器。

下列示例代码会在任意玩家死后,向所有玩家发送该玩家死亡的信息:

function playerDied(totalAmmo, killer, killerWeapon, bodypart)
	outputChatBox(getPlayerName(source).." died!")
end
addEventHandler("onPlayerWasted",getRootElement(),playerDied)

事件文档列举了不同事件触发后将会传递到事件处理函数的不同参数。请留意 source 变量,虽然其并非是参数,但每个处理函数都隐式包含有该变量。在不同的事件中,该变量表示的概念不同;例如玩家事件的处理函数中,该变量表示触发了事件的玩家元素。

然后呢?

现在,您应该已经大概了解了 MTA 脚本编写的一些基础知识以及如何阅读文档了。首页提供有更多的信息、教程以及参考链接。您可以随时阅读这些文章以更深入地学习 MTA 脚本编写。

[[{{{image}}}|link=|]] Note: 强烈建议您开始阅读脚本调试教程。编写脚本时,调试是十分有必要的;我们也建议您参考预定义变量列表中的信息以帮助您更高效率地编写 MTA 脚本。

相关参考: