ZH-CN/脚本编写介绍
对于 MTA 服务器而言,“资源” (Resource) 是十分重要的一个概念。一个资源本身表现为一个包含了一系列文件的文件夹或压缩文件 (zip),外包含一个用于指示服务器如何加载资源以及资源所包含文件说明的元数据文件 (Meta File)。可把资源的概念视作是运行在操作系统之上的程序 —— 它可以被启动或停止执行,且多个资源可以同时运行在服务器上。
所谓脚本编程,本质上即是在编写资源。资源可以定义它自身的类型为游戏模式、地图等。MTA 自带一些可供您的游戏模式使用的可选辅助用资源;例如 maplimits,一个用于限制玩家行动范围的资源;或是 deathpickups,用于创建武器拾取物以供辅助死亡竞技 (Deathmatch) 游戏模式。
Tip: 您应该使用支持 Lua 格式代码的编辑器以编写脚本,以提高脚本编写的效率。我们建议使用 Notepad++ 或 LuaEdit。我们也提供测试版的 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。
接下来,我们将会在脚本中添加一条用于在玩家附近刷出交通工具的游戏指令。您可以跳过本章节并阅读更为高级的地图管理器教程;您也可以阅读图形操作界面 (GUI) 脚本编写介绍教程以学习如何在 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)是必不可少的参数,但是您可以随意命名这些参数以符合您的代码风格。
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 函数
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 函数是如何被调用的:
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
Now we need another function, one to spawn a vehicle. We once again search for it on the 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.
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.
function createVehicleForPlayer(thePlayer, command, vehicleModel) local x,y,z = getElementPosition(thePlayer) -- get the position of the player x = x + 5 -- add 5 units to the x position -- create the vehicle and store the returned vehicle element in the ''createdVehicle'' variable local createdVehicle = createVehicle(tonumber(vehicleModel),x,y,z) end
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.
Now we have our complete script:
function createVehicleForPlayer(thePlayer, command, vehicleModel) local x,y,z = getElementPosition(thePlayer) -- get the position of the player x = x + 5 -- add 5 units to the x position local createdVehicle = createVehicle(tonumber(vehicleModel),x,y,z) -- check if the return value was ''false'' if (createdVehicle == false) then -- if so, output a message to the chatbox, but only to this player. outputChatBox("Failed to create vehicle.",thePlayer) end end addCommandHandler("createvehicle", createVehicleForPlayer)
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.
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.
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.
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:
<script src="client.lua" type="client" />
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.
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
/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>
- The commands.lua provides some admin commands, like banning a player, muting or something else that can be used to admin the server
- The client.lua provides a GUI to be able to perform the mentioned actions easily
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
/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>
- The counterstrike.lua contains similiar to the following features:
- 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
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.
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
/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>
- The airport.map in a XML file that provides information about the map to the gamemode, these may include:
- 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:
- 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
- .. 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. 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:
<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>
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 the first chapter.
This example will output a message with the name of the player who died:
function playerDied(totalAmmo, killer, killerWeapon, bodypart) outputChatBox(getPlayerName(source).." died!") end addEventHandler("onPlayerWasted",getRootElement(),playerDied)
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 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.
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.
See also: