码迷,mamicode.com
首页 > 其他好文 > 详细

饥荒MOD lua编程0基础入门

时间:2016-07-10 19:18:43      阅读:2076      评论:0      收藏:0      [点我收藏+]

标签:

前言

原贴写于饥荒游戏贴吧,为了使文章针对性更强,将原文切割并精简。此贴主要为编程0基础的modder讲解一些编程的基础知识。至于说有关饥荒框架的介绍,则会放在另一篇文章里讲解。

编程0基础的人,要想学习制作MOD,难度是比较大的,因为缺乏一些基本的编程概念,只懂得复制别人的代码或者在它们的基础上稍加改变,遇到稍微复杂一点的代码,就束手无策了。对于MOD崩溃或错误,也几乎没办法自行处理。但我也不推荐先去学一门编程语言之后再来学习MOD代码,这是没有必要的。事实上饥荒MOD里用到的基本编程知识都比较简单,所使用的lua语言相比c之类的强类型语言,也已经做了许多简化。单纯想要做MOD的话,只需要了解一些基本知识和概念就可以了。

以下内容全部基于lua语言。

标识符

一个名字

给常量,变量,函数,类一个名字,这样我们才能通过名字来使用它。一般使用英文字母、数字和下划线的组合来命名。
推荐命名规则

  • 常量:每个字母都是大写,在每个单词之间加下划线。
  • 变量和函数:第一个单词首字母小写,后续单词的首字母大写,其他都是小写。
  • 类:所有单词首字母大写。

这个只是个人推荐的命名规则,读者可以根据自己的喜好决定命名规则。

变量

可以通过符号’=’赋值改变的量。

典型代表:人物的饥饿值。这个值在游戏里几乎每时每刻都在不停变化着,这样一来,我们就可以根据不同的变化,设置不同的效果,比如沃尔夫冈不同的饥饿值会有不同的形态,这个就是通过检测饥饿值来实现的。

常量

程序运行时,不会被改变的量

实际上,lua语言里没法自己定义常量。但是,对于某些量,我们不需要在游戏运行的过程中改变,又需要引用它。比如说长矛攻击力,在游戏过程中不需要改变,但官方所做的所有的武器的攻击力都是长矛攻击力的某倍数,这又需要引用它进行计算。这时候,不妨就把长矛攻击力看作是一个常量,用一个变量将其定义下来(SPEAR_DAMAGE,这个定义在tuning.lua里)

作用域

变量的生效区域。

在作用域以外的域内,如果你引用这个变量,又没有域内的同名变量,就会造成出错。
lua中对一个变量的作用域只有两个选项:local 和global。默认不加local修饰的变量为global(全局)变量,加了local的为局部变量。全局变量的作用域为整个程序。局部变量的作用域则在所定义的域中。一个选择控制结构内,一个函数体内,或者一个文件内,都是一个域。
这个作用域的主要价值在于,使得系统免于混乱。比如说,在被攻击的时候,需要计算所受到的伤害,为了方便进行多次计算,我们把这个数值设置为一个变量。但是,面对的敌人的攻击力会有变化,自身的防具减伤也会有变化,这时候我们就希望,计算结束,变量被引用到血量变化之后,这个变量能消失掉,不会影响我们下一次计算其他的伤害。这就是局部变量的重要作用。当然,局部变量还有另一个好处就是读取它的数据,要比全局变量快一些,不过,提高MOD性能不是本篇教程的重点,就不详细展开了。总而言之,全局变量,不到必不得已的情况,应当尽可能少用。

在上面我多次提到了声明和引用,可能有些人不太清楚这两个术语的意思。我这里就顺带把定义和赋值一起讲了。

定义和声明

定义,就是告诉系统,我设置了的这个变量/常量是什么。
声明,就是告诉系统,我设置了一个变量/常量,你给我记好了。

定义和声明是不一样的,但常常会混在一起。如果你只是想写写mod的话,不了解他们的区别也没关系,只认为是定义就够了。

引用

引用就是告诉系统,我要用这个变量/常量来做某某事。

比如说用于某个表达式的计算,系统就会帮你读取储存在其中的数据。

赋值

赋值就是告诉系统,往这个变量/常量里存入你给出的数据。

注意,此处的数据,不仅仅指数字,可以是lua语言允许的任何类型,比如说一段文字(字符串),一个布尔值(真或假)等等。在某些语言比如C语言中,定义和赋值是可以分开的。但在饥荒MOD的脚本语言lua中,这两者是连在一起的。对一个变量的第一次赋值,就是对它的定义了。

数据类型

对变量赋值,就是给它写入数据,那就涉及到了数据类型的问题。在初次赋值给一个变量时,所给予的值的数据类型,就是变量的数据类型。此后再给这个变量赋值,就必须赋予同一数据类型的值,如果值不同,就会导致系统崩溃(除了nil)。这里不展开细讲,只针对lua语言,简单地列出MOD中常用的几种类型

  • nil:表示无效值,可以给任何数据类型的变量赋这个值。实际效果相当于删除这个变量
  • boolean:包含两个值:false和true(假和真)
  • string:字符串,用一对双引号或单引号括起来
  • function:函数,这个会在下面讲
  • table:表,这个概念会在后面讲

函数

这是编程里的一个非常重要的概念。

函数与变量的区别,可以做这样的类比:一个变量,就好比是一个属性,你可以给一个客体以某个属性,让它可以被描述,比如说,属性:可以被烧毁。而函数,则是一种操作方法,你让一个客体拥有一个函数,就是让它有某种操作。比如说,操作方法:被烧毁的具体步骤和操作。

函数由函数名,参数表和函数体组成。函数也和变量一样,能被引用,也有作用域。不过与变量不同的是,函数需要单独定义,在不同的编程语言中,函数的定义格式不一样,但都少不了上面所说的三个基本组成。在lua中,函数也可以看成是变量,可以被赋值。另外,函数可以有返回值,也就是把计算的结果返回,供另一个函数或者表达式使用。

Lua中,函数定义的基本格式如下为:

function 函数名(参数表)
函数体
end

如果希望函数的作用域是局部的,则在function 前面添加local。这样,你将无法在其作用域之外调用该函数。
函数是怎样工作的呢?首先,你需要明白,定义函数并不会让函数工作。只有执行了函数语句才会让它工作。还是拿计算伤害来做例子。你定义了怎么计算伤害的函数,参数为攻击者的攻击力和防御者的护甲。这个函数在定义之后,本身并不会立刻工作。只有你设定了一系列的流程,让函数在出现攻击状态的时候触发,才能算是执行了这个函数。函数执行的时候,输入了两个参数:攻击者的攻击力和防御者的护甲。在函数体中,经过一系列的计算,得到了结果,利用return返回来,由变量接收或者加在各种表达式里使用。需要注意的是,即使是没有参数的函数,在执行时,也必须写成这样的形式: 函数名(实际参数表)

代码例子:

--定义了函数caldamage,但没有执行
function caldamage(attack,armor)
        return attack-armor
end
local damage = caldamage(10,8) --这里执行了函数caldamage,并把计算的结果返回,赋值给damage

函数的作用是什么呢?就是使得你的编程显得更有逻辑,模块化,还能减少代码的使用量。定义好一个函数之后,就不再需要管这个函数里面详细的执行过程(也就是函数体写了什么),我们只需要知道这个函数的名字,参数表和返回值,和这个函数有什么作用。因为在饥荒的MOD中,大量的函数是没有返回值的(也就是返回值为nil),执行这样的函数,目的在于使用它的功能。
函数是做饥荒MOD时最重要的东西。我们做MOD,主要的目标就是修改或者向游戏添加函数。了解这些函数在什么时候会触发,需要哪些参数,有什么功能,返回值是什么,是非常重要的。

我们是在原游戏的基础上做MOD,也就是说,有很多已经定义好了的函数可以供我们使用。打个比方,饥荒这个游戏,就好比一部车,函数就是这部车上面的零件,它让这部车能够拥有某些功能:启动,刹车等等。我们现在觉得这部车不能满足我们的需要了,那么,很显然的,做适当的改装,要比重新造一部车容易。做MOD,就好比是做一些改装。既然是改装,那你就有必要了解到,你所需要改装的部分,需要哪些零件。有些核心零件是非要弄清楚不可的。

现在饥荒MOD本身的结构是非常开放的,但官方没有给出详细的说明文档,当我们想要实现一项功能的时候,我们不知道官方有没有给出来,怎么办呢?我的建议是,思考一下游戏里的各种功能,以及他人已经发布的MOD,有没有和你的需求类似的,去参考一下相应的代码。易宁修改也是一个很好的参考,但易宁修改毕竟是直接修改游戏的核心文件,与MOD还是有一些区别的,所以要使用的话,前提是理解其含义。

表不是一门编程语言的必须概念,但这个概念在饥荒MOD里使用得非常频繁。游戏的整个框架,也非常依赖于表。表的重要作用也和函数一样,是为了让你的编程显得逻辑清晰。比方说,现在有4个个体,a,b,c,d,有多项属性描述:health、sanity、hunger、damage、armor、attack_period、walkspeed、runspeed。这些属性,对于4个个体来说,有的有,有的没有,我们要怎么组织起来呢?用多张表连起来,就是一个好主意。首先,我们来给属性分一下类,health、sanity、hunger是饥荒中的三大基本属性,各自单独成一类,damage、armor、attack_period是和战斗有关的,分类为combat,至于walkspeed、runspeed则是和移动有关的,分类为locomotor。那么,我们就有多张表了:

一张总表:

个体属性表

属性 a b c d
health a的血 b的血 c的血 d的血
sanity a的精神 b的精神 c的精神 d的精神
hunger a的饥饿 b的饥饿 c的饥饿 d的饥饿
combat a的战斗属性 b的战斗属性 c的战斗属性 d的战斗属性
locomotor a的移动属性 b的移动属性 c的移动属性 d的移动属性


这个表中的每一个元素,都是一张表,现在不妨取a的战斗属性表出来,是这样的:

个体属性表

damage armor attack_period
数值 20 50 3



那么,我们想要引用a1的damage的时候,怎么办呢?先在总表第一横栏中找到a,然后在竖栏中找到combat,这样我们就得到了提示:转去找a1的战斗属性表。然后在战斗属性表中,我们在横栏中找到了damage,这时候竖栏中只有一项,就不必再查找了。我们在查找过程中,寻找的a,combat,damage 就是所谓的索引。我们按先横后竖的顺序查找的,a为1级索引,combat为2级,damage为3级。按顺序最后找到damage的具体的值(20),就是所谓的值。这个值不仅仅是数值,比如说在总表中找到的a的战斗属性表,也可以称为值。再拓展一些,如果说战斗属性表中的竖表不只有一项,而是有两项:max,min,此时我们想要查damage最大值,该如何呢?那就要增加一个四级索引max。当你想要引用a的伤害最大值时,在编程里的调用语句,你就可以写a.combat.damage.max

结合饥荒Mod编程,我们常常会看到类似这样的一条语句

inst.components.sanity:DoDelta(-10)`

这句话的意思是当前对象的精神减10。
具体是怎么操作的呢?首先,游戏里这么多个体,要在茫茫人海中找到你,必须要有个名字,这个名字就是inst,然后,inst下有很多属性类,我们需要的精神值,归类为components,也就是组件。组件这个概念,是饥荒为了编程上的逻辑清晰而创造出来的一个概念,会在介绍饥荒编程框架的文章里详细说明。然后我们继续在components表里找包含着精神值和操作精神值的函数的表,就是sanity。这时候你可以看到sanity后面是冒号:而不是之前的那些点号. 这是因为,我们是希望执行这个函数。如果是想要引用这个函数做其他操作的话,还是要用点号. 的。学过C++的人都会了解类的概念,对这个肯定不陌生。没学过的人呢,看我在下面关于类的解释。

类和面向对象编程

类这个概念,就是在面向对象编程的思想上发展起来的。为什么要使用类呢?就是因为面向对象编程显得逻辑结构清晰,易于实现、互动和维护。那么,什么是一个类呢?在编程上,可以理解为一些变量和函数的集合。这个集合是封装起来的,其中有一些变量和函数你可以访问和引用,称为公有变量和公有函数,还有一些是你访问不到,也无法使用的,就是私有变量和私有函数。需要注意的是,类本身只是一个逻辑结构,并不是实体。用现实的东西举个例子,自行车就是一个类的概念,它有很多基本属性:颜色,材质等等,也有很多操作:骑、前进、刹车等等。属性就是变量,操作就是函数。颜色、材质,是你能够看到的,就是公有变量,而内部的转盘的颜色你看不到,就是私有变量。而骑和刹车的操作,是你能够决定的,就是公有函数。而前进这个操作,你没法直接进行,你必须要反复踩踏板,才能让自行车前进,所以前进就是私有函数。在头脑中想到自行车这个概念,就是类。而想到你的自行车,就是一个具体的实体。

结合到饥荒MOD里,sanity这就是一个类,这里面有很多属性:当前精神值,最大精神值等等,也有很多操作:精神增加/减少,设置当前精神值,设置最大精神值等等。而具体到一个人物的sanity,那就是这个类的实体了。
实际上,在lua里,只有表,没有类这个概念。但是饥荒的游戏制作者为了编程方便,还是用某种手段,在表的基础上,类这个概念创造出来了。我们只需要认识到,怎样使用一个类就可以了。
引用类中的变量,操作方法就和引用表中元素一样。如果想要调用函数,则需要将点号.改成冒号:,并且在函数名后面添加(函数参数表)。
比如说人物的当前精神值,人物的最大精神值等等,同时也有一些操作函数,比如上面举例的DoDelta。我之前说过了,在lua里,函数也可以看成变量。如果你想要引用这个函数,比如说引用去给一个函数赋值,那么,上面的冒号:就要改成点号.,而且后面的”(-10)”也要去掉。如果你想要执行这个函数,那就要用冒号: 并且添加相应的参数。

以上就是全部的0基础入门内容了。
理解了这部分内容,就可以去看下一篇,饥荒MOD框架概述了。

饥荒MOD lua编程0基础入门

标签:

原文地址:http://blog.csdn.net/longfei_aot/article/details/51860005

(0)
(5)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!