Don'tStarve MOD Lua编程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还是有一些区别的,所以要使用的话,前提是理解其含义。

表不是一门编程语言的必须概念,但这个概念是lua内置的核心数据结构,在饥荒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)"也要去掉。如果你想要执行这个函数,那就要用冒号: 并且添加相应的参数。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容

  • 概述 这篇文章会阐述Don'tStarve代码的整体框架,帮助Mod新手们快速了解Don'tStarve Mod能...
    LongFei_aot阅读 8,944评论 0 43
  • ¥开启¥ 【iAPP实现进入界面执行逐一显】 〖2017-08-25 15:22:14〗 《//首先开一个线程,因...
    小菜c阅读 6,209评论 0 17
  • Nginx API for Lua Introduction ngx.arg ngx.var.VARIABLE C...
    吃瓜的东阅读 5,572评论 0 5
  • 这个系列教程很长,涉及到很多编程、游戏方面的概念。与其让你云里雾里地看着看着就放弃,不如先教你如何快速入门,通过模...
    LongFei_aot阅读 11,915评论 9 71
  • 有时候,恋爱不一定是为了爱情,也可能是因对方的执着而感动。 青春不一定是两个人的冲动,也可能是一方的主动,另一方的...
    雪初穗子阅读 187评论 0 0