ZH-CN/OOP介绍
此编程教程能够向你解释什么是OOP,教你如何来使用MTA的OOP功能。当前页面由thisdp翻译,英文原版由qaisjp撰写 Forum post.
关于OOP的介绍
OOP 是指 面向对象程序设计. OOP是将所有函数都封装入每一个接口进行访问,这样的接口我们称之为实例。一个实例可以是由元素类、数据库类、玩家类、载具类等实例化出来对象。
类,即Class。实例(Instance)是对象(Object)。
用类创建对象(实例)称为实例化(Instantiate)。
一般来说,在Lua中,所有东西都是面向过程的,因此你会写出如下代码:
local vehicle = createVehicle(411, 0, 0, 3) setVehicleDamageProof(vehicle, true) setElementFrozen(vehicle, true) setElementHealth(vehicle, 1000) setElementVelocity(vehicle, 0.2, 0.2, 0.2) destroyElement(vehicle)
很多情况下,你可能知道你在干什么。变量通常和类型相关联,在onVehicleExplode事件中,如果让你给"一辆已经爆炸(exploded)的载具(Vehicle)"命名,你可能第一个想到的就是explodedVehicle,或者至少在内容上很可能会受到来自事件中explode的暗示。因此,在面向过程的程序设计中,你需要写一个很长的函数,并且需要手动引用载具(explodedVehicle)。是不是感觉戴上了痛苦面具一样?面向过程的程序设计就有异于这种方法,而且它的编程方式是面向每一个"对象"。你不再需要调用一个函数,并且在这个函数中引用载具,而是可以直接在类中调用函数。
你可能会想你可以创建的并且能够传入函数的都是元素。比如一辆载具是元素,一个玩家是元素……任何东西只要是元素都存在对应的类。比如Vehicle创建了一个实例,但是"vehicle"并不是一个元素,而是一个实例(或者说是对象)。这边我给出一张关于类和元素的文氏图,希望能够让你有一个概念。
左边的排列的函数呈现了返回值属于什么类型。我们得到了类,元素和“问题儿童”。 问题儿童在代码中并不是真正的分类,只是它们打破了正常的函数使用规则。资源(resources)、载具(vehicles)和队伍(teams),你能用的所有东西都是类。
所有的元素都是类,你可以这么干:
destroyElement(ped)
但是你不能这么干:
destroyElement(resource)
这些“问题儿童”是比较奇怪的东西,你不能对它们使用在元素函数"Element functions"部分列出的函数,事实上所有的元素都不可以一个不落地使用所有的函数),但是你可以对它们使用destroyElement()函数。 类也存在子类,比如对于玩家(Player),关系类似于“元素->行人->玩家”(Element -> Ped -> Player)。所有的玩家(Player)都是行人(Ped),所有的行人(Ped)都是元素(Element)。不是所有的行人(Ped)都是玩家(Player),同样的,不是所有的元素(Element)都是玩家(Player)。最主要的一点是,你可以创建或取得的几乎所有东西都存在类。
上述代码可用以下代码代替:
local vehicle = createVehicle(411, 0, 0, 3) vehicle:setDamageProof(true) vehicle:setFrozen(true) vehicle:setHealth(1000) vehicle:setVelocity(0.2, 0.2, 0.2) vehicle:destroy()
它的工作原理和表(table)非常类似,就像customTable.setSomething(),只是:是lua用来把customTable:setSomething()转化成customTable.setSomething(customTable)。这是lua本身自带的语法糖,你不需要特地关心这个。
那些函数都是非常有用的,但是对于OOP而言,变动比较多,我会在下面详细解释。
实例化和变量
OOP移除了函数的所谓"创建"的意义,取而代之的是实例化。例如原来是createVehicle,在OOP中,我们只需要使用Vehicle,它们以同一种方式运作,或者说,我们可以认为Vehicle = createVehicle。是不是很科幻,很花里胡哨呢?
它们的唯一区别就在于,使用OOP的情况下,你可以省略掉一些多余的部分,Vehicle没有这些多余的部分,但是玩家(Player)是肯定有的。比如本来要写成getPlayerFromName(),在OOP的语法下,只需要使用Player.getFromName()。是不是非常简单呢?这也确实是一种组织代码的好方法。
Tip: Vehicle() 也是可以用的,因为它会访问 Vehicle.create 函数,这样就能够让你在创建一个对象的时候省略 .create 了 |
既然OOP比面向过程更高级,也已经继承了许多东西过来,但是为了化繁为简,所有函数对应的变量要求最多只能有一个输入。我们已经把getElementDimension() 缩短为 element:getDimension() 了,但是我们还可以更进一步: element.dimension。是不是很像个变量呢?它可以像普通的变量一样进行赋值,就比如:
local function incrementDimension() local player = Player.getRandom() -- 随机抽取一位幸运玩家 player.dimension = player.dimension + 1 -- 维度增加1 end setTimer(incrementDimension, 60*1000, 10) -- 设置一个60秒的定时器,一共运行10次,每次运行调用函数incrementDimension
上述代码设置了一个60秒的定时器,一共运行10次,也就是说在接下来的10分钟内每分钟都会随机抽取一位幸运玩家,让其维度+1。
向量
player.position当然也可以用!但是你怎么能够用一个变量。。。来同时改变三个参数?答案是 向量。 向量类非常强大,他们有多种形式。为了能够在这里解释的清楚一点,我将会在接下来的例子中使用3维向量。使用向量也是一件特别简单的事,当然,如果你开心的话。只要是出现了位置(position)的地方,你就可以用向量。
因此,下面这个例子是关于使用向量来创建一辆载具并且移动到地图中心。
-- 首先,创建一个3维向量 local position = Vector3(300, -200, 2) -- 远处某一点 local vehicle = Vehicle(411, position) -- 在这点创建一辆车 vehicle.position = centreOfMap - Vector3(300, -200, 0) -- 把这辆车移动到地图中心处上方两个单位。
没错,我使用了减号。向量对于位置和旋转角度而言只是一个非常高级的工具,你可以通过向量对他们使用数学运算。所以,你能在第一行看到,我以坐标300,-200,2创建了一个3D向量,然后第二行我创建了一辆车在那个位置。
vehicle.position 返回一个向量,它有点像没有"()"的setElementPosition,仅仅是一个变量。因此,在第三行,我改变了这个代表着车辆坐标的。这里就是数学起作用的地方了,把上述向量运算展开就能得到如下等价的表达式:
x = 300 - 300 y = -200 - -200 z = 2 - 0
向量的数学运算比较复杂,但是它无疑能够使用出数学的各种神奇的魔法效果,找一找有关向量和矩阵的实用页面链接,进去浏览一下文档,来帮助理解上述代码。
如何理解OOP文档
OOP语法的使用文档非常易懂,并且也是由面向过程的语法提供支持。为了尽可能保持简单,所有的文档都统一了格式:
OOP 语法 什么是OOP?
- 提示: Set the variable to nil to execute removePedFromVehicle
- 方法: ped:warpIntoVehicle(...)
- 变量: .vehicle
- 对称函数: getPedOccupiedVehicle
- 有的时候页面中会增加一个"提示"来说明此OOP函数的一些不同点以及特殊功能。
- 方法可以 以player: 或者 Player. 开头,前者代表玩家(Player)实例(player),比如(setPlayerHealth),后者是玩家(Player)类下的静态方法,比如(getRandomPlayer)。
- 对称函数能够允许你找到当前函数的对称部分,比如(setElementPosition)的对称函数为(getElementPosition)。这个在绝大部分情况下都是可以通过当前函数页面来推断出来的。
如果你想要对wiki做出一些贡献,请务必浏览简体中文OOP模板,或者英文版本的OOP模板
实用的页面连接
其他实用的OOP相关的页面