查看: 21100|回复: 29

[版务] RGSS 3 入门教程大家写(第二弹)

[复制链接]

…あたしは天

梦石
0
星屑
2308
在线时间
4033 小时
注册时间
2010-10-4
回帖
10548

开拓者贵宾

发表于 2014-7-1 09:19:39 | 显示全部楼层 |阅读模式

加入我们,或者,欢迎回来。

您需要 登录 才可以下载或查看,没有账号?注册会员

×
本帖最后由 taroxd 于 2015-2-9 17:39 编辑

活动时间:
已经结束。

活动规则:
阅读完活动概要后,详尽地回答帖子下方提出的问题,或者发布教程类的文章。
本帖在活动结束前严禁非教程类回复!灌水者好人卡-1 && 删帖!正好我觉得这活动奖励好人卡奖励得太多了,不是吗?
如果发现了现有回答中的错误,或者要对现有的回答进行完善,你可以使用点评或者短消息功能。
如果有什么疑难没有明白,也请使用点评或者短消息功能,或者在活动结束后回帖。

面向对象:
看完了《RGSS 3 入门教程大家写》,懂得了变量、运算、分支、循环、数组等基本知识与语法,可是却无法理解默认脚本,想要修改脚本却无从下手的同学。
看《理解RGSS3》以及RPG Maker VX Ace 帮助文档-脚本-实践篇,只能照着上面的代码写,却不能理解其中含义的同学。

回答要求:
每人可以选择若干问题回答,不限制回答个数。
讲解详细、易懂,没有技术上的错误,同时也不建议过于深入。
排版整洁,代码规范。代码用代码框框起来。
回答问题时可以占楼,之后再编辑。但是,占了楼就得把责任负到底!
允许重复回答同一问题,但是后回答的人得回答得更好才行哦。
不必拘泥于提出的问题。只要是你觉得对脚本学习有帮助的内容我没有兼顾到,都可以在此发布。

奖励:
版主好感度,作用是在VA区回答问题的时候奖励提升等等
你对论坛付出了自己的一份力。
那些(未解决)的标签,会贴上你的ID,并且链接到你回答的楼层。
对每一个问题,一个满足要求的解答,至少可以得到一张好人卡。
活动结束后,我会去申请VIP,奖励给贡献最多的若干人。

下面是问题。

*号表示使用频率较少的东西,在第一次学习时可以跳过。*号并不代表难度高低。
**号表示,这些知识几乎不会用到。但知道了这些,可以让你对脚本或脚本中的思想有更深刻的理解。
[box=Sienna]
预备部分
[/box]
我一点基础也没有,可以从这里开始学习吗?(余烬之中)
当我见到看不懂的代码时,我应该依次到哪里寻求帮助?(余烬之中)
要提高我的脚本水平和对代码的理解,我应该多读脚本还是多写脚本?为什么?(余烬之中)
别人写的脚本好多行前面都有空格,都有什么用?我每行前面应该打多少空格?(余烬之中)
我该在什么时候使用注释?注释上应该写什么内容?(余烬之中)
[box=Sienna]
VA区简单脚本问题选集
[/box]
(对于回答者,要求解释你写出脚本的全过程,并对你写的脚本进行解释分析)
(对于学习者,如果你这一块的内容看不懂,没关系,先跳过这一段往下学习,到时候回过头来再看吧。那时你一定会有新的体会。)

求教如何让编号为某一个变量的人暂时成为队列第一人?(余烬之中)
如何在每次状态A消失后自动附加状态B?(taroxd)
有没有办法让敌人hp越少越容易进入异常状态? (余烬之中)
如何从菜单中删去“整队”选项?(泉塚四季)
如何显示一个显示指定文字的窗口?要求文字能够在游戏中修改。(余烬之中)
[box=Sienna]
面向对象的编程
[/box]
类、对象、方法分别是什么?(余烬之中)
我为什么要使用类?使用类有什么好处?(余烬之中)
带有@前缀的变量是什么意思?(taroxd)
方法的参数是什么?可变参数和默认参数怎么用?(无脑之人)
属性是什么?attr系列是什么?(taroxd)
self是指什么?(RyanBern)
子类是什么?有什么用?(余烬之中)
脚本为什么都要插入到main以上,插入到其他地方不行吗?(kuerlulu)
*模块是什么?有什么用?(余烬之中)
*类方法和模块方法是什么?(taroxd)
**public、protected、private方法是什么?它们有什么意义?(无脑之人)
----------------------------------------------------------------------------------
☆读到这里,你已经可以去尝试阅读RGSS3默认脚本了。尝试理解每个类是干什么用的,大致了解原生脚本中每个方法的功能吧。
  接下来的部分,就会开始讲解一些 RGSS1、RGSS2 中很少出现的东西了。
[box=Sienna]
区块(block)
[/box]
区块是什么?如何使用?在哪些地方能使用?(taroxd)
*局部变量的作用域是什么?为什么说区块是闭包(closure)?(taroxd)
*{...}和do...end有什么区别?(kuerlulu)
*方法后面带有前缀&的参数是什么意思?“&:symbol”呢?(无脑之人)
**raw_proc和lambda对象如何生成?它们有什么区别?(taroxd)
[box=Sienna]
内建类简介
[/box]
数组有哪些常用的方法?能不能分别详细解释一下?(RyanBern)
哈希表是什么?怎么使用?(无脑之人)
$game_variables是什么东西?似乎很像是一个数组?(泉塚四季)
字符串有哪些常用方法?能不能分别详细解释一下?(taroxd)
范围(range)是什么?怎么使用?它在 case 语句中出现的意义是什么?(taroxd)
*符号是什么?它和字符串有什么区别和联系?(无脑之人)
*正则表达式如何配合字符串使用?(RyanBern)
[box=Sienna]
其他
[/box]
我为什么总是看到报错?应该怎么解决?(taroxd)
经常看到别人写的脚本里有好多alias,那是什么意思?使用 alias 有什么好处?(喵呜喵5)
*经常看到别人写的脚本里有 $imported ||= {},那是什么意思?他为什么要这样做?(喵呜喵5)
*默认脚本中的 rescue 是什么意思?我应该在什么情况下使用 rescue?(余烬之中)
*Ruby中的操作符?(无脑之人)
[box=Sienna]
接下来,我应该……
[/box]
(以下并没有顺序关系)
1. 阅读RGSS3默认脚本,并在注释、F1帮助文档的帮助下尝试理解它的结构、解读它。
    依旧难以理解?不懂得这些类的作用和关系? ← 《理解RGSS3》
2. 复习“VA区简单脚本问题选集”的部分。相信你一定会有更深刻的理解。
3. 阅读F1帮助文档-脚本-实践篇
4. 在提问区寻找简单的,已经解决的脚本问题。尝试在F1和Ruby文档的帮助下解读里面的脚本。
    为什么不是技术区? ← 如果你自己觉得学得不错,那么你可以去技术区找几个脚本试试。
5. 在提问区寻找简单的,尚未解决的脚本问题,并尝试自己写脚本回答。
6. 自制满足自己要求的脚本。如果有什么疑难,欢迎在提问区提问。会有很多脚本大触调教教导你的。
*7. 这里的所有教程都只是冰山一角,但对于原创脚本已经够用。
     继续深入学习Ruby,你会发现更广阔的世界。

点评

说起来你们怎么弄得那个框……如果可以的话帮我也打上好了……受姬开格式太蛋疼了  发表于 2014-7-3 00:21

评分

参与人数 4星屑 +69 收起 理由
无脑之人 + 50 把它改成奇怪的数
鑫晴 + 1 弄个表格吧~
野生君IR + 15 精品文章
kuerlulu + 3 没错之前那个chm我玩着玩着忘了.

查看全部评分

…あたしは天

梦石
0
星屑
2308
在线时间
4033 小时
注册时间
2010-10-4
回帖
10548

开拓者贵宾

 楼主| 发表于 2014-7-1 09:20:25 | 显示全部楼层
本帖最后由 taroxd 于 2014-7-2 05:57 编辑

本段作者为 @余烬之中

[fold=预备部分]

· 我一点基础也没有,可以从这里开始学习吗?
可以。你也可以和《RGSS3 入门教程大家写》的内容同步学习,两相印证。如果有什么问题,欢迎在VA区提问。

· 当我见到看不懂的代码时,我应该依次到哪里寻求帮助?
当你读其他的脚本,遇到了不懂的地方,首先要确保你大概了解了这个脚本的逻辑结构,可能你看不懂的原因是因为详细的内容在脚本的后面。

如果你只是对某句脚本的语法不大清楚,你应该自己猜想、尝试仿写一下,看看结果和你的预期有什么不同。

有irb的开irb,没有irb开RM新工程也凑合,善用p、msgbox之类的方法,再不明白就勇敢地发帖提问,VA提问区或者技术讨论区(如果还在的话)都可以。

如果你对脚本中出现的某个方法不熟悉,你应该首先确定方法的位置:是在RGSS3中定义的,还是在ruby核心库中定义的?(有时候甚至是作者自己定义的)然后查找F1帮助手册(VA的帮助手册只汉化了脚本部分,不过基础部分与VX是共通的,而且对我们没有影响,可以在这里下载)。

当然,也可以查阅ruby参考手册(汉化版见此,ruby版本1.8.1,官方英文版见此,ruby版本1.9.2,RGSS3采用的是r1.9.2,但是很多地方与1.8.1都是共通的)。

在库中找到你要的方法后,应该实际应用几次以加深印象。

· 要提高我的脚本水平和对代码的理解,我应该多读脚本还是多写脚本?为什么?
多写脚本。实践出真知,读的知识很容易忘,这就像做数学题和看例题的区别。

写一些简单的脚本,轻轻松松,又加深印象又有成就感,很容易激励人继续往下学习。一开始就玩大工程很容易受挫。

下面的“VA区简单脚本问题选集”就是一些可以拿来练手的简单脚本。如果你水平足够的话,可以不看下面的教程直接开始练习。

· 别人写的脚本好多行前面都有空格,都有什么用?我每行前面应该打多少空格?
当我们写一篇作文的时候,在每个段落的开头都要空两格,也就是缩进。

在脚本中,缩进用于提供代码的视觉组织,空行也有类似的功能。

应该为不同逻辑层次的代码提供不同长度的缩进,用空行把同一层次中、不同语义的代码区分开。

(实际上,这两点是一致的)

至于应该打多少空格…………

根据逻辑层次的深浅,决定缩进的长度,最外层不缩进,每进入一层,将缩进增加一个恒定的值

这个恒定的值,我们推荐:两个空格

为什么是俩空格呢?

当你在网络上搜寻ruby的推荐规范,他们都会告诉你,使用软tab,不要用硬tab(了解一下就够了),硬tab指的是你用键盘Q键左边那家伙打出来的缩进,软tab指的是用空格打出来的缩进,一般是两空格,少了效果不明显,多了打得费劲。

而且RGSS原装脚本都是俩空格…………当年我什么都不懂的时候,就是这样学会了缩进的。

顺便一提,在RM自带的脚本编辑器中,就算你用想硬tab,它会自动转换为两个空格的软tab缩进,所以Tab毫无压力

下面“注释”这一章里的示例,以及本教程所有代码的缩进都是规范的。

· 我该在什么时候使用注释?应该注释什么内容?

注释的目的之一是备忘:设想,你写了一个漂亮的任务系统发布在技术区,@怪蜀黍 给了你500糖。

一个月之后你收到一个来自@taroxd 的新提醒说发现了一个BUG,于是你打开你的工程开始修改……

………………

…………………………

……………………………………

你发现你已经看不懂你自己的代码了,你忘了一个叫做 fuck_i_hate_it 的方法是干什么用的,你不记得为什么类 Daddy 会出现在这里,你想不起来为什么这里要用 class_eval………………

这是一种目的,另外一种目的是说明,当你把你的脚本共享出来,就算你在发布帖写了详尽的说明,但是不要忘了,有各种意外情况比如从来不看发布帖使用说明的人和转载

原装脚本的注释方法是推荐的

在一个类的开头,这样注释,简短的说明类的功能
[pre lang="ruby" line="1"]
#=======================
# ** Window_Greet
#-----------------------
#  用于展示问候语的窗口
#=======================
[/pre]
在一个方法的开头,这样注释,说明方法的功能
[pre lang="ruby" line="1"]
#---------------------
# * 描绘问候语
#---------------------
[/pre]
在方法内部,如果过程过长,除了考虑空行分组,还可以加上注释,说明具体操作目的
[pre lang="ruby" line="1"]
def some_meth
  # 初始化
  @some_attr ||= {}
  # 查找指定项
  f = find_something
  if f # 如果存在
    # 执行操作并返回结果
    operate_with(f)
  else # 如果不存在
    # 返回替代品
    alt
  end
end
[/pre]

最重要的是:你应该尽量写出不用注释就很容易理解的代码,而不是把功夫花在注释上。

[/fold]

点评

它却是个没意义的量,但是却可以使用(具体通过什么,我不知道),所以恳求作者编写一个关于这个类的研究报告  发表于 2014-10-22 16:19
要求作者编写一个有关Game_Temp类的教程,原因是这个类是临时数据,从研究RGSS脚本的角度看来,里面的变量虽有存在的合法性,可是(接)  发表于 2014-10-22 16:17
回复

使用道具 举报

…あたしは天

梦石
0
星屑
2308
在线时间
4033 小时
注册时间
2010-10-4
回帖
10548

开拓者贵宾

 楼主| 发表于 2014-7-1 09:20:50 | 显示全部楼层
本帖最后由 taroxd 于 2014-7-2 13:30 编辑

[fold=面向对象的基础概念]

本段作者为@余烬之中

基础概念 真的只有概念 又长又冗杂 但你不得不知道

方法就是做一件事情所使用的方法

对象就是可以做事情的的主体

类就是一个以能够处理的方法对对象所进行的分类

这还是有点难以理解,这样你就懂了:

李华做物理实验

李华是一个对象,做是一个方法,物理实验是方法的参数(至于参数,我们稍后再讲)

把一个事件用一句话说出来,一般名词是对象,动词是方法,宾语是参数
如果要做物理实验,应该让谁来?应该是懂物理知识的人

这里懂物理知识的人就是一个类
李华是一个红星中学高二一班的好学生,而红星中学高二一班的好学生都是懂物理知识的人

这种情况,我们说:李华的类是红星中学高二一班的好学生。

而红星中学高二一班的好学生的父类(也叫基类、超类)是懂物理知识的人

反过来说,类懂物理知识的人的子类(也叫派生类)之一是红星中学高二一班的好学生

为什么要说“之一”呢?

因为一个类只能有一个父类,而一个父类可以有多个子类不要问我它妈是谁

有一件显而易见的事:红星中学高二一班的好学生不止李华一个,所以说李华是红星中学高二一班的好学生的一个实际例子

你要红星中学高二一班的好学生?行啊!这里就有一个例子:李华

于是,我们说:对象李华是类红星中学高二一班的好学生的一个实例

好 现在让我们回顾一下这些概念

方法 对象 类 参数 实例 父类 子类

回顾完毕

这些东西有什么意义

理解这些东西,可以让脚本的逻辑结构更清晰——这是不言而喻的

更重要的是,这种模型可以让脚本编写更加简单

~~~最重要的是……ruby是一门面向对象的语言,RGSS3是建立在ruby上的,RGSS3的原装脚本是用这种思想写的 不用不行啊~~~

忽略上面那句,说点实际的:

方法的作用很简单明了,假设没有方法,而想要扔肥皂给@fux2 和@咕噜 我们得这样:

  1. 从肥皂盒里拿出一块肥皂
  2. 瞄准[url=home.php?mod=space&uid=65553]@fux2[/url] 的脚下
  3. 抬手,把肥皂扔出去
  4. 从肥皂盒里拿出一块肥皂
  5. 瞄准[url=home.php?mod=space&uid=299746]@咕噜[/url] 的脚下
  6. 抬手,把肥皂扔出去
复制代码
倘若我们以后还要再扔其他人,就必须再写一遍,如果有方法

  1. # 目标是参数
  2. 定义方法 扔肥皂给(目标)
  3.   从肥皂盒里拿出一块肥皂
  4.   瞄准目标的脚下
  5.   抬手,把肥皂扔出去
  6. 结束定义
复制代码
我们就可以这样:

扔肥皂给(@fux2)和扔肥皂给(@咕噜)。

以后我们还可以扔肥皂给( @好男人 )。

至于类呢?

考虑这样一个方法:

工作

它应该是怎样的一个方法,会干什么?

…………

……………………

………………………………

对于不同的人(对象),同样的方法有不同的诠释

我们可以为每个人写一个方法:你是这样工作的 他是这样工作的 blablablabla...

但是我们会发现,总有一些人,他们工作的方法是一样的

把他们作为一类

(以#开头的是注释 不影响实际的效果)

  1. 定义类 清洁工
  2.   定义方法 工作
  3.     说 我不知道我要干什么
  4.   结束定义
  5.   定义方法 休息
  6.     说 我在休息
  7.   结束定义
  8. 结束定义
  9. #-------
  10. 定义类 擦黑板的 父类是 清洁工
  11.   定义方法 工作
  12.     洗拖把
  13.     前拖拖
  14.     后拖拖
  15.     里拖拖
  16.     外拖拖
  17.   结束定义
  18. 结束定义
  19. #-------
  20. 定义类 扫地的 父类是 清洁工
  21.   定义方法 工作
  22.     洗抹布
  23.     左擦擦
  24.     右擦擦
  25.     上擦擦
  26.     下擦擦
  27.   结束定义
  28. 结束定义
复制代码
然后

  1. 李华的同学甲 是 一个扫地的 并且 李华的同学乙 是 一个擦黑板的
  2. 李华的同学甲工作 并且 李华的同学乙休息
复制代码
好吧我受够了:
[pre lang="ruby" line="1"]
(classmateA = Sweeper.new) and (classmateB = Wiper.new)
classmateA.work and classmateB.rest
[/pre]
能看懂吧?

What? and不是这么用的?……我们后面再说。

话归正传,在这个例子中,我们看到了继承,继承指的是由父类变出子类的过程,定义一个类的同时,声明它的父类,就完成了继承

对了 不要关心为什么上面那个扫地的在擦黑板,而擦黑板的坐着休息 他与她在谈恋爱

注意啦 现在开始用英文写脚本了

定义类,使用class,这个英文单词本身就是类的意思

声明父类,使用左尖括号<,有点像个箭头,由父类指向子类,表示派生

定义方法,使用def,英文全名是define,定义的意思,但只用来定义方法

结束定义统一是end,英文结束的意思

把上面的俩苦逼同学再写一遍
[pre lang="ruby" line="1"]
# 清洁工
class Cleaner # 这个Cleaner是名字 必须是大写字母开头想必不用我说
  def work # 方法名 可以是小写字母、汉字或下划线开头
    puts "我不知道我要干什么"
  end
  # puts是什么?
  #  向控制台写文字的方法 它会把它的参数写下来
  # 为什么这里的[我不知道我要干什么]加了双引号?
  #  你要让电脑明白:这是一句话,不是用来执行的脚本
  def rest
    puts "我在休息"
  end
end
#------
# 擦黑板的
class Wiper < Cleaner # 这就是上面那个Cleaner
  def work
    # 洗拖把
    # 拖来拖去 # 这些方法我就不再一一实现了
  end
end
#------
# 扫地的
class Sweeper < Cleaner
  def work
    # 洗抹布
    # 擦来擦去
  end
end
[/pre]
这里出现了三个同名的方法:work work 和 work,其实这里暗藏玄机哦~

第一个work 作为第一次出现的方法,没什么特殊的

第二个work 它出现在Wiper内部,而Wiper是Cleaner的子类

很显然,父类有的方法,子类一定有,不然我要继承干什么?在这里,你可以看做是Wiper的内部出现了第二次work的定义

这个定义会覆盖掉之前的定义,这样Wiper有了它独有的work

第三个work 在Sweeper内,也是Cleaner的子类,同理覆盖了从第一个work里继承来的内容

所以,三个类,有了三个不同的work

…………

……………………

………………………………

好吧,不要打我,就是这么简单,可能我说的有点绕……

你们可以想到,rest虽然只出现了一次,但是三个类中都是有这个东西的

简单的讲一讲模块

* 这一段在学习时可以跳过

在上面,我们看到,李华是红星中学高二一班的好学生,而红星中学高二一班的好学生都是懂物理知识的人,我们要做物理实验需要懂物理知识的人所以李华可以胜任。

(某对象o属于某类A,某类A的父类是某类B,某类B具有某实例方法m,所以对象o具有方法m)

但是,如果我们现在要调查北京市2006-2011年全市狗尾巴草种群数量变化,需要懂生物知识的人,李华也懂生物,所以我们让他的某个祖先类为懂生物知识的人………………

好像哪里不对劲?

我们刚才已经说过,某个类只能有一个父类,这个父类当然也是一个类,它也只能有一个父类……层层下去,关系应该是这样的(<的左边是子类 右边是父类)

子类 < 父类 < 父类的父类 < 父类他爷爷 < ...... < 祖先类 < ...... (< Object)

后面那个Object不需要理解,但是你们应该猜得到,它是一个类,是所有类的父类或祖先类

现在,我们看到,李华的类的父类是懂物理知识的人,而现在他的某一个祖先类是懂生物知识的人…………

难道说,所有懂物理知识的人都是懂生物知识的人?或者所有懂生物知识的人都是懂物理知识的人?

坑爹呢你是

因为我们没有办法把这两个类并排(某个类只能有一个老爸,别管干爹了…………都说了不要问他妈是谁!),而且李华肯定不止拥有这两种能力,比如说,李华还是个学生

这时,我们应该选择最能代表事物根本属性的类作为父类,将另外的作为模块(终于讲到你了)

在模块中,我们可以像在类中一样定义方法,但是模块不能创建对象

在这里,我们的李华属于类A红星中学高二一班的学生(问什么在前面加个A?我说过的,类名必须以大写字母开头),这个类应该是这样的继承下来:

红星中学高二一班的学生 < 红星中学高二年级的学生 < 红星中学的学生 < 高中生 < 学生 < 青少年 < 超人

与此同时,应该在其中的某个类中混入模块

当我们向某个类A中混入模块M,在M中定义的所有方法,A也能拥有

模块的混入没有限制,想要几个就要几个

这里,我们要把懂物理知识的人作为模块,懂生物知识的人做为另一个模块,把它们混入红星中学高二一班的学生中,因为红星中学高二一班的学生都懂这些东西,而红星中学高二年级的学生不一定都懂,好班级就是不一样(打死)

而且这样还有个好处:当某个老师需要这种能力的时候,我们只需要混入这两个模块,而不需要更改学生或老师的继承结构

声明模块 使用module
[pre lang="ruby" line="1"]
module KnowPhysics # 模块的名字也必须以大写字母开头
# 懂物理
  def do_physics_experiment(data) # 做物理实验
    if data == "复述质能方程" # 这里的data是上面的data,也就是方法的参数
      puts "E = mc²" # 还记得puts吗?
    else
      puts "我不会"
    end
  end
end
#-----
module KnowBiology # 懂生物
  def do_biology_research(data) # 做生物调查
    puts "我不会#{data}" # 这里的写法你们将会很熟悉
  end
end
#-----
# 红星中学高二一班的学生 < 红星中学高二年级的学生
class StudentInRedStarMiddleSchoolGradeTwoClassOne < StudentInRedStarMiddleSchoolGradeTwo
  # 后面这个父类我就不再定义了
  # 开始混入模块
  include KnowPhysics
  include KnowBiology
  # 这样就混入了
  # 下面的方法有两个参数
  def conduct(what, data) # do 是关键字,尽可能不要单独使用
    # 这里的分支更加适合用 case 语句,有兴趣的同学可以自行学习
    if what == "物理实验"
      do_physics_experiment(data)
    elsif what == "生物调查"
      do_biology_research(data)
    else
      puts "我不会"
    end
  end
end
#
Lihua = StudentInRedStarMiddleSchoolGradeTwoClassOne.new
Lihua.conduct("物理实验", "复述质能方程")
#=> E = mc²
[/pre]
什么?做物理实验和复述质能方程有什么关系?你不知道现在的高中生做的实验都是黑板实验吗?  

[/fold]
回复

使用道具 举报

…あたしは天

梦石
0
星屑
2308
在线时间
4033 小时
注册时间
2010-10-4
回帖
10548

开拓者贵宾

 楼主| 发表于 2014-7-1 09:21:38 | 显示全部楼层
本帖最后由 taroxd 于 2014-7-6 12:38 编辑

[fold=面向对象的基础概念 2]

本段作者为@taroxd

能把枯燥的理论看到这里,真是坚强。现在已经忘得差不多了吧?

这里我们利用RGSS3的脚本来做一个简单的复习。

这里是一个例子: Game_Actor < Game_Battler < Game_BattlerBase
点开 Game_Actor ,你可能会对这些代码眼花缭乱。让我们简单的来看过一遍。
对象艾里克是Game_Actor类的一个实例

如果你看懂了这句话,很好,说明你还记得这些概念。
[pre lang="ruby" line="1"]
  #--------------------------------------------------------------------------
  # ● 初始化对象
  #--------------------------------------------------------------------------
  def initialize(actor_id)
    super()
    setup(actor_id)
    @last_skill = Game_BaseItem.new
  end
[/pre]
initialize 是在任何一个对象产生的时候自动调用的方法。
在这里,它是这样定义的。
def initialize(actor_id): 它告诉我们,要生成一个 Game_Actor 类的对象,我们应该告诉这个方法这位角色的id。
在数据库里,艾里克的id是1。我们马上会用到他。
super(): 调用父类 Game_Battler 的同名方法 initialize,并且用一个空的括号表示不传入任何参数。
setup(actor_id): 调用 setup 方法,并且传入参数 actor_id。我们先不去管它。
@last_skill = Game_BaseItem.new:将一个实例变量@last_skill赋值为Game_BaseItem的一个新的实例。我们也先不去管他。

接着我们又在 Game_BattlerBase 里面看到了方法 atk,虽然看不懂这个方法是怎么实现的,但我们可以猜到,Game_Actor继承了它的方法,并且这个方法能告诉我们一个战斗者的攻击力。

我们建立一个新的工程,在“测试”-“显示主控制台”上打个钩钩,然后打开脚本编辑器,把 Main 中的那一行脚本删掉,改为下面的代码。

[pre lang="ruby" line="1"]
DataManager.init # 读取数据库

eric = Game_Actor.new(1) # 还记得刚才的initialize是怎么说的吗?
puts eric.atk

rgss_stop # 停止游戏画面
[/pre]

瞬间,我们在控制台上看到了艾里克的攻击力。在战斗测试界面中选中艾里克,并且点一下“初始化”,看一看攻击力是不是一样?

所以,使用类的好处就是,我们不知道脚本是怎么得到atk这个数值的,但我们只要会使用即可。
我们现在已经成功获得了艾里克的攻击力。

我们再来看看 Game_Actor 类:
[pre lang="ruby" line="1"]
  #--------------------------------------------------------------------------
  # ● 等级上升
  #--------------------------------------------------------------------------
  def level_up
    @level += 1
    self.class.learnings.each do |learning|
      learn_skill(learning.skill_id) if learning.level == @level
    end
  end
[/pre]

很好,一段乱七八糟的东西我完全看不懂。但是根据我们刚才的学习,我们知道,正如李华可以做物理实验一样,艾里克可以升级。
艾里克升级没有宾语,也就是说,这个方法不需要传入参数。

继续在Main里玩:
[pre lang="ruby" line="1"]
DataManager.init # 读取数据库

eric = Game_Actor.new(1)
puts "升级前,#{eric.name}的攻击力是:#{eric.atk}"
eric.level_up
puts "升级后,#{eric.name}的攻击力是:#{eric.atk}"

rgss_stop # 停止游戏画面
[/pre]

怎么样,成功了吗?
这是一个很好的开始,我相信你已经对“类”、“实例”、“方法”、“继承”这些东西有了初步的了解。
虽然你还不会编写自己的类,也还不能理解Game_Actors里面的内容,
但你已经了解了如何去使用它。

你也许会好奇地问:我找遍了Game_Actor的祖宗,也没有找到name方法的定义啊?
这就要涉及到属性与attr系列的问题。我们会在之后讲到。

等等,那一行DataManager.init是什么东西你没有告诉我啊?
谁说我没有告诉你。注释里不都写着“读取数据库”么(pia)

这就是这一段的主题:类方法与模块方法。
我们打开DataManager的脚本来看一看。

[pre lang="ruby" line="1"]
module DataManager
# ...
  def self.init
    @last_savefile_index = 0
    load_database
    create_game_objects
    setup_battle_test if $BTEST
  end
# ...
end
[/pre]

这个方法的定义和我们之前定义的方法有些不同。我们看到def后面跟随了一个self。
试想,如果我们不加这个self,我们应该是这样调用这个方法的。
[pre lang="ruby" line="1"]
class SomeData
  include DataManager
  # ...
end

data = SomeData.new
data.init
[/pre]
我们是对一个实例调用这个方法,因此这样的方法叫做实例方法。
但我们刚才见到了,如果def后面,方法名前面加了个self.,那就说明这个方法是模块方法/类方法。
这样的方法,是由模块或者类本身来调用,就像 DataManager.init 那样。
这其实是一个变相的全局方法,但我们推荐这么使用。

至于模块的实例变量是什么,这个问题并不属于入门的范畴。有兴趣的同学请参考 https://rpg.blue/thread-108581-1-1.html
事实上,使用模块的实例变量也是定义模块方法的原因之一。

这样,我们使用模块就有了另一个好处:命名空间(namespace)
试想,如果我们直接在顶层定义:
[pre lang="ruby" line="1"]
def init_data
  #...
end
[/pre]
这样虽然整个程序都可以调用,但是要冒着很大的重名风险。
而像默认脚本这样,我们依旧在整个程序中都可以调用 DataManager.init。但是方法重名的风险就少了很多。
同时,模块也可以作为常量的命名空间。
比如说RGSS3的内置类 RPG::Skill,这就是在模块 RPG 里面的一个常量。
如果直接定义在外部,把类的名字叫做 Skill 的话,这简直就是追求着重名嘛!

总之,用了模块,防止命名冲突,赞!
[/fold]

评分

参与人数 1星屑 +100 梦石 +1 收起 理由
VIPArcher + 100 + 1 认可答案

查看全部评分

回复

使用道具 举报

梦石
1
星屑
23236
在线时间
9564 小时
注册时间
2012-6-19
回帖
7018

开拓者短篇九导演组冠军

发表于 2014-7-1 12:32:33 | 显示全部楼层
本帖最后由 喵呜喵5 于 2015-12-7 19:09 编辑

[fold=【口胡教程】如何用alias和$imported将自己装成一个脚本触]开头先说结论,如果你没有使用别人的脚本,如果你自己写的脚本基本都是放在脚本插入区的最顶端的,如果你学脚本是为了在原本的脚本上修修改改,alias和$imported对你来说完-全-没-用。因为很重要所以再说一次,这两个东西基本上在RGSS3中的用法就是为了让你的脚本能够更加兼容,如果你的脚本本来就是自己用的不需要什么兼容性的话去了解这两个东西还不如好好回头看看Windows怎么用Sprite怎么用,起码学会了这么两个使用起来效果立竿见影看起来高大上的东西能让你在妹子或者非妹子面前装装大爷,啊对,装逼结束后请记得补上“我的脚本是给我自己用的,你们的游戏根本装载不上。”
好了,扯了这么多我也已经手一滑把最终结论打出来了,不论是alias还是$imported,在RGSS3也就是我们俗称的RMVA脚本中最大的功能就两个字,兼容。接下去就来谈谈这两个玩意儿究竟是怎么实现脚本兼容的,顺带也会提提脚本冲突的原理。虽然已经废话了388个字了,再正式开始之前还得多废话一句,接下去的大部分用来说明alias和$imported的代码我将使用伪代码,也就是形如
  1. class 我的歌
  2.   def 洗澡歌
  3.     我爱洗澡皮肤好好   
  4.   end
  5. end
复制代码
这样的扔进脚本编辑器绝对会被报错踢出来的代码,class后面跟的是类名(以及类对应的实例名),def后面跟的是方法名,依次类推,其他中文你就当是在屏幕上显示了一段对应的文字这样就好。咱才不负责写出你可以用的Ruby语法完全正确的代码呢哼。
下面先来看这段代码
  1. class 我的歌
  2.   def 洗澡歌
  3.     我爱洗澡皮肤好好
  4.   end
  5. end
复制代码
先默认现在已经有一个名叫我的歌的我的歌类实例了,现在执行 我的歌.洗澡歌 这条指令后,系统显示出了一段歌词:我爱洗澡皮肤好好。

上面那些就是任何一段脚本都在做的事情。

“你-特-么-在-逗-我!?”

别闹,我说的是认真的。
生成一个实例,执行一个方法。说的简单一点,脚本从头到尾做的就是这种简单的事情,大部分会用事件指令制作游戏的人其实一定程度上已经掌握了编程的思想了,所以请不要把脚本想的太复杂。当然,学会了语文不代表你就能写出小说,学会了Ruby语言不代表你知道如何写RGSS3脚本,比起埋头苦读存在手机里或是什么地方的大坨大坨的文字教程,自己亲自去试试反而能够更快的掌握如何写RGSS3脚本。
抱歉跑题了,回到刚才的代码上。
脚本说穿了就是一段代码,虽然执行的类可能不是“我的歌”而是Windows_Message,就算执行的方法不是“洗澡歌”而是refresh,就算最终结果不是显示一段歌词而是开始一场战斗,本质上整个游戏还是在一条一条的执行脚本编辑器中的那些代码。
那么修改脚本是在做什么呢?
  1. class 我的歌
  2.   def 洗澡歌
  3.   我爱洗澡皮肤好好
  4.   噢噢噢噢
  5.   end
  6. end
复制代码
看,这个就是我们修改脚本时所做的工作,歌词不再是一句而变成两句了。
那么插入别人写的脚本(大部分情况下)又是在做什么呢?
  1. class 我的歌
  2.   def 洗澡歌
  3.   我爱洗澡皮肤好好
  4.   end
  5. end
  6. class 我的歌
  7.   def 洗澡歌
  8.   我爱洗澡皮肤好好
  9.   噢噢噢噢
  10.   end
  11. end
复制代码
这就是插入别人写的脚本后的事情。
因为前后两个类都是“我的歌”类,所以上面的脚本可以看成这个样子:
  1. class 我的歌
  2.   def 洗澡歌
  3.   我爱洗澡皮肤好好
  4.   end
  5.   def 洗澡歌
  6.   我爱洗澡皮肤好好
  7.   噢噢噢噢
  8.   end
  9. end
复制代码
反之,下面这段脚本你也可以看成或者改成上面那样,将同一个类中的每一个方法(def~end)分别扔到一个新的同名的类里面(class~end),然后保持原本的上下关系将每一个(class~end)作为脚本插入到脚本区里面……这里说一句题外话,此前有人问我如何给脚本加密,然后我就告诉了他上面这个方法,然后就没有然后了。
但是还是有问题啊,class 我的歌里面特么有两个洗澡歌啊,执行我的歌.洗澡歌的时候该唱哪首?说的比较装逼一点,洗澡歌方法出现二义性了。
因为解释起来很麻烦,下面先放出结果,你们自己感受一下:
[box=PaleGreen]
  1. class 我的歌
  2.   def 洗澡歌
  3.   我爱洗澡皮肤好好
  4.   end
  5.   def 洗澡歌
  6.   我爱洗澡皮肤好好
  7.   噢噢噢噢
  8.   end
  9. end
复制代码
执行 我的歌.洗澡歌 ,显示歌词 我爱洗澡皮肤好好,噢噢噢噢
  1. class 我的歌  
  2.   def 洗澡歌
  3.   我爱洗澡皮肤好好
  4.   噢噢噢噢
  5.   end
  6.   def 洗澡歌
  7.   我爱洗澡皮肤好好
  8.   end
  9. end
复制代码
执行 我的歌.洗澡歌 ,显示歌词 我爱洗澡皮肤好好

[/box]
细心或者粗心的你现在都能够发现了吧,当class中出现了两个同名的方法时,电脑只会执行排在后面的方法,排在前面的方法被后面的方法覆盖掉了。
换到自带脚本里面来的话,就是这样的感觉:
[box=PaleGreen]
RGSS3 原生脚本:
  1. class 战斗界面
  2.   def 开始战斗
  3.   准备各种各样的敌人
  4.   主角唱首歌
  5.   执行战斗指令
  6.   end
  7.   ……
  8. end
复制代码
RGSS3战斗显示回合数脚本:
  1. class 战斗界面
  2.   def 开始战斗
  3.     生成显示回合数的高大上窗口
  4.   准备各种各样的敌人
  5.   执行战斗指令
  6.   end
  7.   ……
  8. end
复制代码
最终执行的脚本:
  1. class 战斗界面
  2.   def 开始战斗
  3.     生成显示回合数的高大上窗口
  4.   准备各种各样的敌人
  5.   执行战斗指令
  6.   end
  7.   ……
  8. end
复制代码
[/box]
所以我前面都说了嘛,插入别人写的脚本(大部分时候)实际上就是在覆盖原本的脚本。
于是问题就来了,请看下面的代码:
[box=PaleGreen]
原本的脚本:
  1. class 我的歌
  2.   def 洗澡歌
  3.   我爱洗澡皮肤好好
  4.   噢噢噢噢
  5.   end
  6. end
复制代码
插入的第一个脚本:
  1. class 我的歌
  2.   def 洗澡
  3.   不停的演唱 洗澡歌
  4.   如果 洗澡歌 第五遍唱到 噢噢噢噢 的时候就打肥皂
  5.   end
  6. end
复制代码
插入的第二个脚本:
  1. class 我的歌
  2.   def 洗澡歌
  3.   我爱洗澡皮肤好好
  4.   end
  5. end
复制代码
最终执行的脚本:
  1. class 我的歌
  2.   def 洗澡歌
  3.   我爱洗澡皮肤好好
  4.   end
  5.   def 洗澡
  6.   不停的演唱 洗澡歌
  7.   如果 洗澡歌 第五遍唱到 噢噢噢噢 的时候就打肥皂
  8.   end
  9. end
复制代码
执行 我的歌.洗澡 显示歌词:我爱洗澡皮肤好好我爱洗澡皮肤好好我爱洗澡皮肤好好我爱洗澡皮肤好好…………

…………我爱洗澡皮肤好好…………

……你大爷的到底我什么时候能打肥皂。

[/box]
两个脚本各自对原本的脚本进行了修改,一个新增了一个边洗澡边唱歌的方法,一个修改了唱歌的歌词,两个脚本单独执行的时候什么问题都没有,但是现在两个脚本放到一起时不停的冲水的洗澡死循环就出现了。往大了说,这个就是不同的脚本会发生冲突的原因。

那么,如何解决这个脚本冲突?哼哼哼,写了2000字终于轮到这个教程的主角alias和$imported登场了。没错,解决脚本冲突用的就是这两个玩意儿。

首先是$imported的用法,上面那个脚本冲突产生的原因是由于洗澡脚本并不知道还插入了第二个脚本,第二个脚本还修改了洗澡脚本需要判定的数据。反过来说,如果洗澡脚本知道插入第二个脚本时会修改数据的话……
  1. class 我的歌
  2.   def 洗澡
  3.   不停的演唱 洗澡歌
  4.   如果 B那个深井冰写的改歌词脚本也被插入到了这个工程中 的时候
  5.       如果 洗澡歌 第五遍唱到 我爱洗澡皮肤好好 的时候就打肥皂
  6.   否则
  7.       如果 洗澡歌 第五遍唱到 噢噢噢噢 的时候就打肥皂
  8.   如果结束
  9.   end
  10. end
复制代码
看,死循环冲突就这样解决了。
虽然上面的理论说的头头是道的,但是实际操作起来时这里还有一个很重要的问题。
“你大爷的我怎么知道B那个深井冰写的脚本有没有插入到这个工程啊!(#=△=)”
这时候就轮到$imported登场了。
虽然前面一直称这个东西为$imported,但是这个玩意其实还可以是$rgsslab、还可以是$rsi、还可以是$m5script、还可以是$我的小呀小苹果,只要是一个命名独特不容易重名的全局变量都可以,对于上面那个修改了洗澡歌歌词的神经病脚本作者B来说,他要在脚本最开头添加这样的语句:
  1. $imported ||= {}
  2. $imported["洗澡歌的歌词被B大爷我修改啦!"] = true
复制代码
第一行代码的作用是检测$imported这个全局变量是否存在,如果不存在的话就生成一个新的空白哈希表。
第二行代码的作用是往这个哈希表里面插入一个名为“洗澡歌的歌词被B大爷我修改啦!”的键,键值则是为真的布尔值。
之后,那个拿洗澡歌唱到第几句作为是否打肥皂的依据的倒霉脚本师A在脚本中就可以这么写他的脚本了。
  1. class 我的歌
  2.   def 洗澡
  3.   不停的演唱 洗澡歌
  4.   如果 $imported["洗澡歌的歌词被B大爷我修改啦!"] 的时候
  5.       如果 洗澡歌 第五遍唱到 我爱洗澡皮肤好好 的时候就打肥皂
  6.   否则
  7.       如果 洗澡歌 第五遍唱到 噢噢噢噢 的时候就打肥皂
  8.   如果结束
  9.   end
  10. end
复制代码
由于键值"洗澡歌的歌词被B大爷我修改啦!"不存在时返回的布尔值一定是false,所以通过一个简单的$imported,这个洗澡脚本不仅能够在原生脚本中使用,还能与那个修改了洗澡歌的魂淡脚本共存了!
既然都说到了这里,现在就不得不提一下规范问题了。
从上面的整个分析过程中我们会注意到不少的问题。
首先,脚本师A要判定脚本师B的脚本有没有存在就必定要求脚本师B在自己的脚本中写上import的代码$imported["洗澡歌的歌词被B大爷我修改啦!"]。如果脚本师B是一个放荡不羁笑点低的狗犊子根本就没在自己的脚本中写上这两行代码,脚本师A在判断有没有载入B的脚本的时候胸口大概就得飞过万千草泥马了。
其次,脚本师B的import代码$imported["洗澡歌的歌词被B大爷我修改啦!"]是一个哈希表,如果还有一个脚本师名字叫C,他也在自己的脚本中写了一个import代码$imported["洗澡歌的歌词被B大爷我修改啦!"],脚本师A在判断有没有载入B的脚本的时候胸口大概就得飞过万千草泥马了。
第三,脚本师A首先要知道脚本师B写过了这个会修改歌词的脚本,如果脚本师A连脚本师B是人是狗都不知道或者脚本师B就是个RM圈随处可见的小P孩,脚本师A在得知需要判断有没有载入B的脚本的时候胸口大概就得飞过万千草泥马了。
最后再提一点,嗯,先上代码。
[box=PaleGreen]
这个是原始的脚本:
  1. class 我的歌
  2.   def 小苹果
  3.   你是
  4.   end
  5. end
复制代码
星期一,某一个喵大爷心血来潮,决定多显示一句歌词,于是他写了这么一段脚本:
  1. $imported ||= {}
  2. $imported["星期一的喵大爷"] = true
  3. class 我的歌
  4.   def 小苹果
  5.   你是
  6.   我的
  7.   end
  8. end
复制代码
星期二,某一个喵大爷心血来潮,决定多显示一句歌词,于是他写了这么一段脚本,当然,周二的喵大爷和星期一的喵大爷是好碰友,他写了这么一段脚本:
  1. $imported ||= {}
  2. $imported["星期二的喵大爷"] = true
  3. class 我的歌
  4.   def 小苹果
  5.   你是
  6.   如果 $imported["星期一的喵大爷"]
  7.       我的
  8.   如果结束
  9.   小呀
  10.   end
  11. end
复制代码
星期三,前面两个喵大爷的好友来拜访他们时顺便写了个与他们的脚本相兼容的脚本。
  1. $imported ||= {}
  2. $imported["星期三的喵大爷"] = true
  3. class 我的歌
  4.   def 小苹果
  5.   你是
  6.   如果 $imported["星期二的喵大爷"] 并且没有 $imported["星期二的喵大爷"]
  7.       我的
  8.   如果结束
  9.   如果 $imported["星期二的喵大爷"]
  10.       我的
  11.       小呀
  12.   如果结束
  13.   小苹果
  14.   end
  15. end
复制代码
就这样循环下去,过了一周后,星期一的喵大爷重新修改了自己的脚本:
  1. $imported ||= {}
  2. $imported["星期一的喵大爷"] = true
  3. class 我的歌
  4.   def 小苹果
  5.   你是
  6.   我的!
  7.   end
  8. end
复制代码
“你特么在逗我!?”剩下2只喵大爷怀着被爆菊一般想死的心情重新修改了自己的代码:
  1. $imported ||= {}
  2. $imported["星期二的喵大爷"] = true
  3. class 我的歌
  4.   def 小苹果
  5.   你是
  6.   如果 $imported["星期一的喵大爷"]
  7.       我的!
  8.   如果结束
  9.   小呀
  10.   end
  11. end
复制代码
  1. $imported ||= {}
  2. $imported["星期三的喵大爷"] = true
  3. class 我的歌
  4.   def 小苹果
  5.   你是
  6.   如果 $imported["星期二的喵大爷"] 并且没有 $imported["星期二的喵大爷"]
  7.       我的!
  8.   如果结束
  9.   如果 $imported["星期二的喵大爷"]
  10.       我的!
  11.       小呀
  12.   如果结束
  13.   小苹果
  14.   end
  15. end
复制代码
结果又到了一个周二,知名的不入流脚本师本文的作者喵呜喵5发布了一段惊天地泣鬼神的大众脚本,一时间人人竞相将这个脚本插入到自己的工程,周一周二周三的喵大爷也不例外,他们怀着虔诚的心情将喵呜喵5的脚本插入到自己的工程中,然后发现脚本代码是长这样的:
  1. $imported ||= {}
  2. $imported["喵呜喵5"] = true
  3. class 我的歌
  4.   def 小苹果
  5.   《小苹果》
  6.   你是
  7.   end
  8. end
复制代码
“妈蛋还让不让人活了你修改一行代码我们喵大爷三兄弟的脚本都得随着你的脚本进行修改!去你丫的$imported!去你丫的脚本兼容性!退圈走人!”
[/box]
前面已经说了,大部分脚本都是在对原本的方法进行修改,自带脚本的代码就那么点,外挂脚本那么多,一个个都虎视眈眈的瞪着准备改原生方法呢?今天我修改一行脚本明天所有脚本都得为了兼容性跟着我的脚本一起修改?哼哼,这种时候就该轮到alias登场了。
alias,或者别名方法,究竟是在做什么呢?我们继续看下面这段伪代码
  1. class 我的歌
  2.   def 小苹果
  3.   你是我的小呀小苹果
  4.   end
  5. end
  6. class 我的歌
  7.   alias 红富士 小苹果
  8.   def 小苹果
  9.   怎么爱你都不嫌多
  10.   end
  11. end
复制代码
按照我们前面所介绍的,原本的小苹果方法被后面的小苹果方法覆盖了,已经不存在了,现在执行 我的歌.小苹果 时显示的歌词变成了“怎么爱你都不嫌多”,但是在后面那段脚本的开头有一个alias 红富士 小苹果 又是在做什么?
执行 我的歌.红富士 显示歌词 你是我的小呀小苹果。

本来应该被覆盖掉的小苹果居然又回来了=口=。那么如果我们这么写代码呢?
  1. class 我的歌
  2.   alias 红富士 小苹果
  3.   def 小苹果
  4.   红富士
  5.   怎么爱你都不嫌多
  6.   end
  7. end
复制代码
执行 我的歌.小苹果 显示歌词 你是我的小呀小苹果 怎么爱你都不嫌多。

不仅保留了原本的歌词,新的脚本添加的歌词也显示出来了。
更进一步,我们在上两个脚本后面再插入一个脚本:
  1. class 我的歌
  2.   alias 大蜜瓜 小苹果
  3.   def 小苹果
  4.   大蜜瓜
  5.   多多多多多
  6.   end
  7. end
复制代码
执行 我的歌.大蜜瓜 显示歌词 你是我的小呀小苹果 怎么爱你都不嫌多。
执行 我的歌.小苹果 显示歌词 你是我的小呀小苹果 怎么爱你都不嫌多 多多多多多。

使用alias以后,妈妈再也不用担心我的脚本把别人的脚本覆盖掉了!
那么,做个总结吧。
alias是个别名方法,与其说别名不如说是克隆了一个与代码执行到这个位置时的该方法一模一样的新方法,在上面的例子中,红富士这个别名里保留了“你是我的小呀小苹果”,大蜜瓜这个别名中保留了“红富士、怎么爱你都不嫌多”。
什么,你问我保存了这些又有什么用?
现在,假设原生的脚本被哪个手贱的勇敢少年改成了这样:
  1. class 我的歌
  2.   def 小苹果
  3.   你是我的小呀小苹果YOO!
  4.   end
  5. end
复制代码
怎么办啊,如果周一周二周三的喵大爷在的话就成草泥马大聚会了。
但是写红富士这个别名的脚本师才懒得理你,因为在勇敢的少年修改这个脚本的那一瞬间,红富士这个别名里保留的内容已经变成了“你是我的小呀小苹果YOO!”。因为不管原本的脚本怎么修改都不会影响到自己的脚本了,红富士脚本师将全部的精力都放在了写自己的脚本中,第二天他的脚本变成了这样:
  1. class 我的歌
  2.   alias 红富士 小苹果
  3.   def 小苹果
  4.   红富士
  5.   怎么爱你都不嫌多YOOOO!
  6.   end
  7. end
复制代码
但是,大蜜瓜也不是吃素的!因为大蜜瓜也使用了alias,所以大蜜瓜里面储存的代码自动变成了“红富士 怎么爱你都不嫌多YOOOO!”,大蜜瓜不仅不用因为红富士的操蛋行为而修改自己的代码还完全不用担心兼容的问题,活该没有女朋友的他甚至还兴奋的将三只喵大爷以及喵呜喵5的脚本都顺手进行了修改。
[box=PaleGreen]
  1. class 我的歌
  2.   alias 星期一 小苹果
  3.   def 小苹果
  4.   星期二
  5.   我的!
  6.   end
  7. end
复制代码
  1. class 我的歌
  2.   alias 星期二 小苹果
  3.   def 小苹果
  4.   星期二
  5.   小呀
  6.   end
  7. end
复制代码
  1. class 我的歌
  2.   alias 星期三 小苹果
  3.   def 小苹果
  4.   星期三
  5.   小苹果
  6.   end
  7. end
复制代码
  1. class 我的歌
  2.   alias 喵呜喵5 小苹果
  3.   def 小苹果
  4.   《小苹果》
  5.   喵呜喵5
  6.   end
  7. end
复制代码
[/box]结果现在这四个人的代码完全兼容了!
结果喵呜喵5和三个喵大爷果然还是一脸“角虫的世界我不懂”的表情退圈了!
扯淡扯了五千五百字,坚持看到这里的你能够理解alias的用法了吗?
用RMVA自带的帮助文档来解释的话,alias就是一个被覆盖方法的避难所。保存进alias里的方法将会维持保存位置的代码的覆盖程度,之后再出现同名的方法时,保存进alias中的那个方法就不会再被同名的方法所覆盖。
当然,alias也不是万能的,比如下面这种情况:
  1. class 自带脚本
  2.   def 我爱洗澡
  3.   我爱洗澡
  4.   皮肤好好
  5.   end
  6. end
复制代码
现在周五的喵大爷打算将这个方法修改成这样:
  1. class 自带脚本
  2.   def 我爱洗澡
  3.   我爱洗澡
  4.   YOOOOO
  5.   皮肤好好
  6.   end
  7. end
复制代码
这时候,不论如何进行alias,周五的喵大爷都束手无策,因为alias储存的是整个方法,不是某一句代码。
最后再提一点,和$imported中如果不同的脚本在哈希表使用相同的键值其他脚本判断起来会出错一样,alias方法中给方法设置的别名也不能是相同的。在使用alias方法时请务必注意这一点。
回到文章的开头,不论是alias还是$imported,在RGSS3也就是我们俗称的RMVA脚本中最大的功能就两个字,兼容。多人协作也好,自己写脚本也好,如果这篇到处都是口胡的教程能够真正的提升你的脚本质量,让你能够写出与更多脚本相兼容的脚本,又或者,至少能够让你看懂脚本中的alias以及$imported究竟是做什么用的话我就再欣慰不过了。
最后就以一些常见(?)的alias的用法来做结尾的吧:
[box=PaleGreen]
一、承前启后式alias
  1. alias 浴室里的其他人 浴室
  2. def 浴室
  3.   浴室里的其他人
  4.   我  
  5. end
复制代码
  1. alias 浴室里的其他人 浴室
  2. def 浴室
  3.   我
  4.   浴室里的其他人
  5. end
复制代码
二、计算结果式
  1. alias 此前的计算结果 年龄
  2. def 年龄
  3.   我的年龄 = 此前的计算结果
  4.   我的年龄 -= 18
  5. end
复制代码
三、条件选择式
  1. alias 此前的计算结果 年龄
  2. def 年龄
  3.   我的年龄 = 此前的计算结果 大于18 吗?(是的:18)/ (不是的:此前的计算结果)
  4. end
复制代码
四、中文编程式
  1. alias 我的年龄 age
  2. def 原本无alias时的计算年龄
  3.   年龄 = age(1,2,3,4,5,6,7)
  4. end
  5. def alias后可以使用的计算年龄
  6.   年龄 = 我的年龄(1,2,3,4,5,6,7)
  7. end
复制代码
[/box][/fold]

点评

喵大爷可真能贫, 现已加入chm超值豪华教程, 统计字数: 5173 + 3307 = 8480 字  发表于 2014-7-11 12:26

评分

参与人数 1星屑 +200 梦石 +2 收起 理由
taroxd + 200 + 2 认可答案 extra

查看全部评分

回复

使用道具 举报

梦石
0
星屑
50
在线时间
269 小时
注册时间
2014-5-9
回帖
124

开拓者

发表于 2014-7-1 12:36:34 | 显示全部楼层
本帖最后由 泉塚四季 于 2016-1-10 12:44 编辑

[fold=$game_variables是什么东西?似乎很像是一个数组?]

实际上,RMVA中所有的事件指令都可以看作一段程序代码,这些程序代码和脚本中的代码并没有什么区别,只是一个有中文你看的更清晰一些而已。每一则指令都与脚本中的一段语句一一对应,而$game_variables对应的则是事件中的变量,也就是下面这张图中你所操作的东西。

图片1.png

稍微看过事件脚本教程的人应该都有看到类似这样的用法:
  1. 变量操作:
  2. $game_variables[编号] = 数值
复制代码
正如前文所说,每一则事件指令都与脚本中的一段语句一一对应,上面这个截图中执行这个指令完成的工作在脚本代码中就可以表示为 $game_variables[1] = 0,那么,$game_variables究竟是怎么和事件中这条指令对应起来的呢?
为了理解的更清楚一些,我们一起来新建一个工程,在这个全新的工程的脚本编辑器中全局搜索一下所有包含$game_variables的脚本。

图片2.png

似乎找到了不少呢,这次我们就从截图中选中的那个脚本Game_Variables脚本看起。
这个是脚本的内容:
  1. #encoding:utf-8
  2. #==============================================================================
  3. # ■ Game_Variables
  4. #------------------------------------------------------------------------------
  5. #  处理变量的类。本质上是套了个壳的 Array 。本类的实例请参考 $game_variables 。
  6. #==============================================================================

  7. class Game_Variables
  8.   #--------------------------------------------------------------------------
  9.   # ● 初始化对象
  10.   #--------------------------------------------------------------------------
  11.   def initialize
  12.     @data = []
  13.   end
  14.   #--------------------------------------------------------------------------
  15.   # ● 获取变量
  16.   #--------------------------------------------------------------------------
  17.   def [](variable_id)
  18.     @data[variable_id] || 0
  19.   end
  20.   #--------------------------------------------------------------------------
  21.   # ● 设置变量
  22.   #--------------------------------------------------------------------------
  23.   def []=(variable_id, value)
  24.     @data[variable_id] = value
  25.     on_change
  26.   end
  27.   #--------------------------------------------------------------------------
  28.   # ● 设置变量时的处理
  29.   #--------------------------------------------------------------------------
  30.   def on_change
  31.     $game_map.need_refresh = true
  32.   end
  33. end
复制代码
并不是多复杂的脚本,让我们一句一句来分析吧。
  1. #  处理变量的类。本质上是套了个壳的 Array 。本类的实例请参考 $game_variables  。
复制代码
首先是注释,我们只要去关注“本类的实例参考$game_variables”这一句就行了。实例是面向对象的编程中的术语,这里并不打算讨论。如果不知道这句是什么意思的话你可以粗浅的理解为$game_variables就是这个Game_Variables的电话号码,在脚本中要寻找Game_Variables时我们需要通过$game_variables这个电话号码来找他(这个说法并不准确,有条件的话还是建议你去详细了解一下实例的概念)。
  1. class Game_Variables
复制代码
这个是脚本的开头部分,可以看出这个Game_Variables并没有父类。
  1.   def initialize
  2.     @data = []
  3.   end
复制代码
Game_Variables的载入方法,也就是执行Game_Variables.new这个脚本语句时Game_Variables所做的事情。在这个载入方法中只做了一件事,设置了一个名为@data的实例变量,并且@data是一个空的数组。
  1.   def [](variable_id)
  2.     @data[variable_id] || 0
  3.   end
复制代码
Game_Variables的[]方法,也就是执行$game_variables[n]时的方法,n的数值就是这个方法的variable_id参数。执行这个方法后电脑首先会检查@data[variable_id]的数值。@data前面已经说了,是一个数组。读取数组中某个元素大家想必已经在熟悉不过了吧,例如@data[0]就是读取数组的第一个元素……至于代码后半句的|| 0的作用,请看下面这段代码:
  1. array = [1,2]
  2. array[0] = 1
  3. array[1] = 2
  4. array[2] = nil
  5. array[2] = array[2] || 0
  6. array[3] = array[3] || 0
复制代码
通过 || 0 这个操作,所有数组中不存在的元素将会自动替换成0。因此,我们在事件中使用变量操作时所有变量的默认值才会是0。同理,只要这么修改的话:
  1.   def [](variable_id)
  2.     @data[variable_id] || 1
  3.   end
复制代码
执行变量操作的时候没有用过的变量的数值就变成1了。
但是,前面已经介绍过的initialize在载入时不是将@data设置成一个空的数组了吗,不论怎么返回@data[variable_id]都是不存在的元素啊,@data这个数组是怎么被修改的呢?我们继续看下一个方法。
  1.   def []=(variable_id, value)
  2.     @data[variable_id] = value
  3.     on_change
  4.   end
复制代码
这是一个[]=方法,也就是执行$game_variables[n] = value时的方法,用中文来讲的话就是给变量赋值的方法。下面以执行$game_variables[1] = 2这个语句时发生的全部事情为例详细说明一下:
[box=LemonChiffon]
  1. $game_variables[1] = 2
复制代码
↓
$game_variables是Game_Variables的电话号码,进入Game_Variables中。
↓
执行的是[]=方法,寻找这个方法
↓
找到了这个方法,
  1.   def []=(variable_id, value)
  2.     @data[variable_id] = value
  3.     on_change
  4.   end
复制代码
方法需要的参数是variable_id和value这两个
↓
根据执行的指令$game_variables[1] = 2,参数variable_id是1,参数value是2
↓
执行方法,将variable_id替换为1,value替换为2,代码变成了
  1. @data[1] = 2
复制代码
[/box]
@data是一个数组,所以上面那句指令在做的,实际上就是你再熟悉不过的数组的赋值了。
最后再说一下,执行完@data[1] = 2之后还执行了on_change这个方法,on_change是Game_Variables中的最后一个方法,内容是:
  1.   def on_change
  2.     $game_map.need_refresh = true
  3.   end
复制代码
这个方法的作用是判断变量的数值改变以后地图上有没有出现条件满足的事件页,如果有的话就让对应的事件切换事件页。这里就不深入讨论了。

下面重新搜索一下$game_variables,分析一下它在各个代码中的实际功能吧。
首先是module DataManager中
  1. $game_variables     = Game_Variables.new
复制代码
这个语句就是给Game_Variables设置$game_variables这个电话号码。如果将这个语句改成这样:
  1. $game_var     = Game_Variables.new
复制代码
以后在脚本中就没办法使用$game_variables[1] = 2这样的语句修改变量的数值而只能使用$game_var[1] = 2这样的语句来修改了。

我们继续搜索
  1. contents[:variables]     = $game_variables
复制代码
这个是保存存档时执行的指令,将$game_variables保存进了存档,保存进存档的自然不只有一个“电话号码”,Game_Variables里的所有数值也随着$game_variables一起保存了进去。
  1. $game_variables     = contents[:variables]
复制代码
读取存档的指令,将$game_variables这个“电话号码”替换成了存档里保存的“电话号码”,虽然名字相同,两个“电话号码”打过去找到的Game_Variables可是不同的哟。

继续搜索,在Game_Battler中找到了这样一句代码。
  1. value = item.damage.eval(user, self, $game_variables)
复制代码
图片4.png

伤害的计算公式里不是允许输入v[n]来获取n号变量的数值吗,这个操作对应到脚本里,计算伤害时确实就用上了储存变量的数值的$game_variables。至于为什么获得数值用的是$game_variables而不是上面所说的$game_variables[n]这里并不详细分析。

继续搜索,在Game_Event中看到了这样一句代码
  1. return false if $game_variables[c.variable_id] < c.variable_value
复制代码
这句代码对应事件页事件出现条件的“变量”这个出现条件
图片3.png
如果变量的设置和上面的截图一样的话,这句代码可以这么翻译:
  1. (告诉电脑:“事件不出现”)如果($game_variables[2] < 2)的时候
复制代码
继续搜索,在Game_Interpreter中能够找到大量包含$game_variables的语句,和上面的分析一样:
$game_variables[n]调用了Game_Variables中的[]方法,他的作用是获取@data数组中n号位置元素的数值。
$game_variables[n]=value调用了Game_Variables中的[]=方法,他的作用是将@data数组中n号位置元素的数值修改成value。当然,[]=除了修改元素的数值外还代表了+=、-=这样的操作符,详细的可以通过Game_Interpreter中的operate_variable方法来自己体会:
  1.   def operate_variable(variable_id, operation_type, value)
  2.     begin
  3.       case operation_type
  4.       when 0  # 代入
  5.         $game_variables[variable_id] = value
  6.       when 1  # 加法
  7.         $game_variables[variable_id] += value
  8.       when 2  # 减法
  9.         $game_variables[variable_id] -= value
  10.       when 3  # 乘法
  11.         $game_variables[variable_id] *= value
  12.       when 4  # 除法
  13.         $game_variables[variable_id] /= value
  14.       when 5  # 取余
  15.         $game_variables[variable_id] %= value
  16.       end
  17.     rescue
  18.       $game_variables[variable_id] = 0
  19.     end
  20.   end
复制代码
最后,$game_variables还出现在了下面这些地方,这里简单的提一下他们出现的意义:
  • Window_Base:对应显示文字指令中使用\v[n]能够读取n号变量的数值。(变量的读取)
  • Window_NumberInput:对应事件指令中的数值输入处理,能够修改变量的数值。(变量的修改)
  • Window_KeyItem:对应事件指令中的物品选择处理,变量的数值将被修改成选择物品的ID。(变量的修改)
  • Window_DebugRight:对应工程模式下按F9出现的调试窗口中显示、修改变量数值的功能。(变量的读取和修改)
[/fold]




[fold=如何从菜单中删去“整队”选项?]

“删掉整队选项”这个问题很多做解谜游戏的新人都曾经问过。这并不是多么困难的问题,一般情况下可以用这么一个标准答案来回答:

[box=LemonChiffon]打开脚本编辑器,找到Window_MenuCommand。
↓
在里面搜索到这样的代码,默认情况下在脚本的56~58行
  1.   def add_formation_command
  2.     add_command(Vocab::formation, :formation, formation_enabled)
  3.   end
复制代码
↓
将这段代码改成这个样子
  1.   def add_formation_command
  2.     #add_command(Vocab::formation, :formation, formation_enabled)
  3.   end
复制代码
↓
保存工程,完成。[/box]

整个操作并没有什么特别的难点,即使是新人也能很快的掌握。但是,这里我打算讲的深入一些。具体要讲的就是“为什么将这行前面加加一个#号就能删除整队指令了?”,进一步的,如果能理解我将的内容,你还能学会“如何在菜单中添加一个指令”相关的知识。

首先先从最基本的讲起,#号是注释符号,和事件指令中的注释类似,在脚本中被注释的代码是不会执行的,在脚本编辑器中这样的代码显示成绿色的。
图片5.png
由于我们将add_command前添加了注释的符号,所以add_command这条指令不会执行了,于是菜单中的整队指令就消失了。反过来说,由于这个add_command方法,菜单中才会出现整队这个指令。那么,这个add_command方法究竟做了什么才往菜单中添加了整队指令呢?
我们全局搜索一下add_command。
图片6.png

搜索后我们得到了大量的结果,大部分的add_command都只是在调用add_command这个方法而已,唯一有定义add_command的是包括菜单指令窗口、装备窗口、标题画面选项窗口在内所有包含选项的窗口的父类Window_Command。
这个方法只有一句代码,我们现在跳转过去仔细分析一下他究竟都做了什么:
  1.   #--------------------------------------------------------------------------
  2.   # ● 添加指令
  3.   #     name    : 指令名称
  4.   #     symbol  : 对应的符号
  5.   #     enabled : 有效状态的标志
  6.   #     ext     : 任意的扩展数据
  7.   #--------------------------------------------------------------------------
  8.   def add_command(name, symbol, enabled = true, ext = nil)
  9.     @list.push({:name=>name, :symbol=>symbol, :enabled=>enabled, :ext=>ext})
  10.   end
复制代码
@list是Window_Command的一个实例变量,他是一个数组。@list的定义在initialize方法中的第一句代码,clear_command_list方法中可以找到。
  def clear_command_list
    @list = []
  end
push方法往@list数组中插入了一个一个元素,在这个方法中,插入数组的元素是一个由方法参数组成的哈希表,以刚才的插入整队指令为例:
现在假设@list是一个空数组[],脚本执行了下面的语句:
  1. add_command(Vocab::formation, :formation, formation_enabled)
复制代码
现在,@list变成了这样
  1. @list = [
  2. {:name=>数据库中整队指令的名称, :symbol=>:formation,
  3. :enabled=>判定是否允许整队代码的最终结果,:ext=>无数据}
  4. ,
  5. ]
复制代码
第一次见到数组中包含哈希表的例子,感觉有点混乱?别担心,我们紧接着再执行几个add_command指令试试看:
  1. add_command(1,2,3,4)
  2. add_command(5,6,7,8)
复制代码
现在,@list变成了这样
  1. @list = [
  2. {:name=>数据库中整队指令的名称, :symbol=>:formation,
  3. :enabled=>判定是否允许整队代码的最终结果,:ext=>无数据},
  4. {:name=>1, :symbol=>2, :enabled=>3,:ext=>4},
  5. {:name=>5, :symbol=>6, :enabled=>7,:ext=>8},
  6. ]
复制代码
先不用在意这样的代码是否可以执行,从上面的分析我们了解到add_command只是往名为@list的数组中添加进了一个哈希表作为元素而已。
这个@list是做什么用的?接下去让我们仔细研究一下整个Window_Command的类。
  1.   def initialize(x, y)
  2.     clear_command_list
  3.     make_command_list
  4.     super(x, y, window_width, window_height)
  5.     refresh
  6.     select(0)
  7.     activate
  8.   end
复制代码
在initialize方法中和其他窗口类相区别的是在生成窗口前出现了clear_command_list和make_command_list这两个方法,clear_command_list这个方法之前已经说过了,执行后生成了@list这个空数组。make_command_list这个方法在Window_Command中并没有内容,而在其子类,比如刚刚我们的菜单Window_MenuCommand类中,专门用来执行add_command这个的方法。
  1.   def make_command_list
  2.     add_main_commands
  3.     add_formation_command
  4.     add_original_commands
  5.     add_save_command
  6.     add_game_end_command
  7.   end
  8. def add_main_commands
  9.     add_command(Vocab::item,   :item,   main_commands_enabled)
  10.     add_command(Vocab::skill,  :skill,  main_commands_enabled)
  11.     add_command(Vocab::equip,  :equip,  main_commands_enabled)
  12.     add_command(Vocab::status, :status, main_commands_enabled)
  13.   end  
  14.   def add_formation_command
  15.     add_command(Vocab::formation, :formation, formation_enabled)
  16.   end
  17.   def add_original_commands
  18.   end  
  19.   def add_save_command
  20.     add_command(Vocab::save, :save, save_enabled)
  21.   end
  22.   def add_game_end_command
  23.     add_command(Vocab::game_end, :game_end)
  24.   end
复制代码
现在我们已经发现了,在一个选项窗口添加选项的语句add_command会把所有的选项的数据推入到实例变量@list中。但是,推入之后会怎样呢?我们回到Window_Command的 initialize方法继续看下去。
super(x, y, window_width, window_height)
在生成窗口的时候,电脑调用了window_height这个方法来获取生成窗口的高度。
  1.   def window_height
  2.     fitting_height(visible_line_number)
  3.   end
复制代码
窗口的高度由visible_line_number(选项的行数)决定。
  1.   def visible_line_number
  2.     item_max
  3.   end
复制代码
visible_line_number(选项的行数)由item_max(选项的个数)决定。
  1.   def item_max
  2.     @list.size
  3.   end
复制代码
item_max(选项的个数)由@list究竟有多少个元素决定。

接着往下看,会发现initialize调用了一个refresh方法。
  1.   def refresh
  2.     clear_command_list
  3.     make_command_list
  4.     create_contents
  5.     super
  6.   end
复制代码
前三句代码执行后我们又一次重新生成了一个包含全部选项数据的@list数组。当然,在initialize中执行这些代码是完全没有意义的。refresh同时也用于选项的刷新,只有当菜单中的选项突然改变了的情况下这三行代码的功能才凸显出来。
最后一个super调用了其父类Window_Selectable中的refresh方法:
  1.   def refresh
  2.     contents.clear
  3.     draw_all_items
  4.   end
复制代码
contents.clear清楚了已经显示的选项的内容,接着执行了draw_all_items方法。
  1.   def draw_all_items
  2.     item_max.times {|i| draw_item(i) }
  3.   end
复制代码
如之前所说的item_max(选项的个数)由@list究竟有多少个元素决定。
这句代码的功能是执行选项个数次的描绘文字,描绘文字的内容又是什么呢?我们又回到了Window_Command中。
  1.   def draw_item(index)
  2.     change_color(normal_color, command_enabled?(index))
  3.     draw_text(item_rect_for_text(index), command_name(index), alignment)
  4.   end
复制代码
第一行是修改文字的颜色,第二行则是描绘文字的内容。值得注意的是 command_enabled?(index)和command_name(index)这两个方法,他们分别获取了@list中对应选项的数据。
  1.   def command_name(index)
  2.     @list[index][:name]
  3.   end  
  4.   def command_enabled?(index)
  5.     @list[index][:enabled]
  6.   end
复制代码
现在一切都很清晰了,能够显示选项的窗口是通过读取@list这个数组中的元素来往窗口中增加选项的。add_command则将选项的数据传送进了@list中。窗口随时可以通过读取@list来获取这个选项的数据。
最后说明一下@list中储存的内容的意义吧:
  • name:选项的名称,在描绘选项时会进行获取
  • symbol:选项的符号,这是一个标志,当玩家在对应符号的选项按下确定键后电脑就会执行对应符号包含的代码
  • enabled:选项是否可以选择。描绘选项文字时会进行判定,如果无法选择描绘半透明的选项。当按下按键时会进行判定,如果无法选择则提示失败。
  • ext:选项的扩展数据,和symbol的功能有点类似,不同的是ext不用于判断按下选项后具体执行哪句指令。
  • (在默认脚本中用在技能列表中选择“特技、魔法”的技能选择窗口中有用到,具体来说,这个指令中不论选择了特技还是魔法,接下去所做的事情都是一样的(显示技能窗口),所不同的只是窗口显示的内容而已,因此默认脚本中用相同的方式显示了窗口,在显示技能列表时再追加判断选项的扩展数据从而决定要显示的技能是特技还是魔法。)

至于为什么选择了某一个选项后能够执行对应的指令这里就不作说明了。
[/fold]

评分

参与人数 2星屑 +340 梦石 +2 收起 理由
taroxd + 200 + 2 认可答案 extra
喵呜喵5 + 140

查看全部评分

泉眼无声惜细流,树阴照水爱晴柔。
小荷才露尖尖角,早有蜻蜓立上头。
回复

使用道具 举报

梦石
0
星屑
122
在线时间
552 小时
注册时间
2012-8-18
回帖
1392
发表于 2014-7-2 00:58:45 | 显示全部楼层
本帖最后由 无脑之人 于 2014-7-3 13:14 编辑

于是怒来插一脚←←
方法的参数是什么呢?
事先注明:
1.这里不谈block的参数传递【涉及到并行赋值留给大触去谈好了】
2.这里也不谈block参数的获取【留给讲block的大触去谈好了】
3.这里还不谈hash参数【大家快去问LZ这个大触】


[fold]其实方法的参数本身上面已经讲的很好了,我来补充几个概念好了
首先,参数分为形参(parameter)和实参(argument),形参是指什么呢?

  1. def papapa(miaowumiao5)
  2.   miaowumiao5.sangshi?
  3. end
复制代码
在上面这段脚本里面,miaowumiao5指的就是形参,“miaowumiao5”就是形参的名字,形参有一个特点,他的名字变动不会影响方法/函数,就像λx.x+1等价于λy.y+1一样【这段请无视】

那么实参呢?
我们尝试调用这个方法

  1. class Miaowumiao5
  2.   def sangshi?
  3.     true
  4.   end
  5. end
  6. papapa(Miaowumiao5.new)
复制代码
此时这个Miaowumiao5.new就是实参,实际传进去的参数

概念脑补完毕,现在是参数列表的设计【设计泥垢】

我们假设我们要做一个输出文字的方法,它接受矩形,文本和对齐方式(0/1/2代表左对齐,居中对齐,右对齐)
实现就不写了,我们只看方法的定义:

  1. def print_text(rect, text, align)
  2.   #具体输出的语句...
  3. end
复制代码
这样我们使用这个方法的时候一般就这样写:
  1. print_text(Rect.new(0,0,120,32), "喵呜喵5是一个丧失", 0)
复制代码
紧接着我们可能会输出N多个丧失【大雾】,我们都希望他用左对齐,事实上国际惯例也是左对齐
于是我们可能会重写一个方法:

  1. def print_text_left(rect, text)
  2.   print_text(rect, text, 0)
  3. end
复制代码
非常机智,只不过我们调用时必须换掉原来的名字,这真是一件麻烦的事情,尤其是如果有好多个允许不填的参数时,你会发现你要写方法写到手软
ruby为了让你更high一些,提供了默认参数:
  1. def print_text(rect, text, align = 0)
  2.   #具体输出的语句...
  3. end
复制代码
噢,真是完美,我们可以愉快的输出丧失了【泥垢】

每次都要填一个rect感觉也好麻烦,能不能一句话解决所有输出呢?
封装到同一个方法里面也可以,但是总觉得要写好多一样的代码呢
首先我希望读者能够写出这样的代码:
  1. def print_text_ex(rect, texts, align = 0)
  2.   texts.each do |text|
  3.     print_text(rect, texts, align)
  4.     #暂且不考虑换到下一行的问题
  5.   end
  6. end
  7. print_text_ex(Rect.new(0,0,120,128), ["丧失*1","丧失*2","丧失*3","丧失*4"])
复制代码
不过呢,ruby毕竟很智能【噗】,所以它提供了可变参数这个语法糖,来减少你输入的麻烦:

  1. def print_text_ex(rect, align = 0, *texts)
  2.   texts.each do |text|
  3.     print_text(rect, texts, align)
  4.     #暂且不考虑换到下一行的问题
  5.   end
  6. end
  7. print_text_ex(Rect.new(0,0,120,128), 0, "丧失*1","丧失*2","丧失*3","丧失*4")
复制代码
我们建议,默认参数放在可变参数的前面,这样的意思就是
如果你只填一个矩形,那么默认左对齐,什么都不输出
如果你填了一个矩形,一个对齐方式,不过还是什么都不输出
如果你填了一个矩形,一个对齐方式,和一个或多个字符串,那么就按照对齐方式依次输出这些字符串

然后我们谈一谈参数的顺序问题
1.默认参数请务必放在可变参数的前面 papapa(*a,b=1) X
2.可变参数请务必只写一个 papapa(*a,b,*c) X
3.默认参数与默认参数之间,默认参数与可变参数之间,请务必不要加普通的参数 papapa(a=1,b,*c) X
4.默认参数和可变参数的前面和后面,在不违反上面的规则的前提下,都可以填普通参数 papapa(a,b,c,d=1,*e,f,g) √
5.【越俎代庖】block参数请务必放在参数列表最后面
6.【ruby 1.9以前的版本,如RMXP】请务必遵循【普通参数*m , 默认参数*n , 可变参数 , block参数】这样的顺序


【课外拓展】
由于和LZ这个大触深♂入♂交♂流(大雾)以后,咱来补充一个新特性【1.9+限定,XP/RGE党很遗憾】

方法的参数列表具有了像并行赋值一样能够拆开数组的特性

  1. def papapa((a,b,c),d)
  2.   p a,b,c,d
  3. end
  4. papapa([1,2,3],4) #=> 1 2 3 4
复制代码
每一个括号又相当于一个子参数列表,里面也可以有默认参数和可变参数等等(block参数别想了),要求与上面的顺序相同

这只是一个语法糖,如果读者可以理解的话那么就是吃掉它了,理解不了也没关系,基本上用不到的XD
【/课外拓展】


完毕☆[/fold]

点评

你才是丧失  发表于 2014-7-2 01:12

评分

参与人数 1星屑 +200 梦石 +1 收起 理由
taroxd + 200 + 1 认可答案 extra

查看全部评分

我要填坑!我要背单词!我要学日语!我要每天锻炼!
好吧呵呵= =
回复

使用道具 举报

…あたしは天

梦石
0
星屑
2308
在线时间
4033 小时
注册时间
2010-10-4
回帖
10548

开拓者贵宾

 楼主| 发表于 2014-7-2 13:06:42 | 显示全部楼层
本帖最后由 taroxd 于 2014-7-7 21:02 编辑

[fold=实例变量与属性]
我们已经了解了类的意义。现在开始,我们就要来制作我们自己的类了。
先从一段简单的代码开始看起。
[pre lang="ruby" line="1"]
class Person
  # 初始化
  def initialize(name)
    @name = name
  end
end

eric = Person.new("Eric")
[/pre]
这是什么意思呢?
[pre lang="ruby" line="1"]
def initialize(name)
[/pre]
复习一下前面所学的。
在每个对象生成时,都会调用类的 initialize 方法。这里的 name 是方法的参数。
我们在下一行就会用到它。

[pre lang="ruby" line="1"]
@name = name
[/pre]
我们知道,等号在Ruby中的含义是赋值。
于是,我们可以这样理解:我们把参数 name 的值赋给了变量 @name。

我们把以@开头的变量称为实例变量。@也是变量名的一部分。

顾名思义,实例变量的值是一个实例独有的。它往往可以用来描述一个对象的状态。

在这个例子中,eric是一个Person类的实例,他具有一个实例变量@name,变量的值是"Eric"

为什么要用实例变量而不能用普通的变量呢?
因为在 initialize 方法运行结束之后,name作为一个局部变量消失了。只有@name作为对象的实例变量,在对象消失之前都不会消失。
于是,我们就把eric的名字保存在了对象里,留待以后的使用。


之前说过,类定义了一个对象可以做的事情。(什么?没说过?那就是现在说的。)
我们来试着让艾里克学会打招呼。

[pre lang="ruby" line="1"]
class Person
  # 初始化
  def initialize(name)
    @name = name
  end

  # 打招呼
  def greet
    print "Hello, I am ", @name
    puts  # 换行
  end
end

eric = Person.new("Eric")
# "#=>" 常用来表示方法的返回值或输出结果
eric.greet #=> Hello, I am Eric
[/pre]

你看,我说的没错吧。即使 initialize 方法结束了,name已经消失,但是只要艾里克还在,他的名字@name也会与他同在。

不过,如果我在方法的外部,我想要知道艾里克的名字呢?
我们来想些办法试一试
[pre lang="ruby" line="1"]
p @name     #=> nil
p eric.name #=> NoMethodError
[/pre]
诶怎么这样?那我要怎么知道 eric 叫什么名字啊
艾里克说:因为我现在没有告诉你我的名字。嗯,让我现在来告诉你。

[pre lang="ruby" line="1"]
class Person
  # 初始化
  def initialize(name)
    @name = name
  end

  # 打招呼
  def greet
    print "Hello, I am ", @name
    puts
  end

  # 获取名字
  def name
    return @name
  end
end

eric = Person.new("Eric")

[/pre]

这里的 return 又是什么意思呢?他表示方法的返回值,就相当于一个函数计算的结果。
我们假设有函数 f(x) = x + 1
那么可以得到 f(1) = 2
此时 1 就是函数的参数。2 就称为函数的返回值。这和我们所说的方法很相似吧?
我们用Ruby来实现这个函数
[pre lang="ruby" line="1"]
def f(x)
  return x + 1
end

p f(1)     #=> 2
p f(f(2))  #=> 4
y = f(5)
p y        #=> 6
[/pre]
用你的数学知识应该很好理解这些代码吧。方法和函数,本质上都是一个东西。
另外多说一句,Ruby的任何方法和表达式都有返回值,Ruby方法的返回值是方法中最后一条表达式的返回值。


艾里克要把名字告诉外面的世界,重点在这样一个方法。
[pre lang="ruby" line="1"]
class Person
  def name
    return @name # 调用这个方法的时候,将实例变量 @name 返回。
  end
end
[/pre]

我们来试着调用一下这个方法:
[pre lang="ruby" line="1"]
puts eric.name #=> Eric
[/pre]

我们类比一下前面那个函数。
p f(1) 也就相当于 p 2,因为 f(1) 返回了 2
那么,puts eric.name 也就相当于 puts "Eric",因为 name 方法返回了 eric 的实例变量 @name,也就是 "Eric"
所以,我们成功地在外部获取了 eric 的实例变量!完美!
我们把这样的过程叫做实例变量的公开。

事实上,因为这个功能太常用了,Ruby为我们提供了偷懒的工具:attr_reader
使用的方式非常简单:
[pre lang="ruby" line="1"]
class Person

  # 下面的两种写法是等价的

  # 写法 1
  attr_reader :name

  # 写法 2
  def name
    return @name
  end

end
[/pre]
我们来一个更长的例子,来复习我们之前学到的东西。
借助注释,看懂了这个例子,你就掌握了 attr_reader。
[pre lang="ruby" line="1"]
class Person

  attr_reader :name # 把实例变量 @name 公开

  # 初始化
  def initialize(name)
    # 把方法的参数 name 赋值给对象的实例变量 @name
    @name = name
  end

  # 打招呼
  def greet
    print "Hello, I am ", @name
    puts   # 单单一个 puts 可以有换行的作用
  end

  # 向另一个人打招呼
  def greet_to(person)
    # person.name 可以获取方法的参数 person 的名字
    # 因为我们已经通过 attr_reader 公开了 person 的实例变量 @name
    puts "Hi #{person.name}, I am #{@name}."
  end
end

eric = Person.new("Eric")
natalie = Person.new("Natalie")

eric.greet_to(natalie) # Hi Natalie, I am Eric.
[/pre]

我相信你已经看懂了 attr_reader 的使用方式,并且已经知道在 greet_to 里面发生了什么了。
很好,我们进入下一个环节。

艾里克说,这个名字太挫了,我要改名!
考虑到实例变量也是变量,可以随意赋值。我们大概想得到这样的方式。

[pre lang="ruby" line="1"]
class Person
  # ... 同上

  def change_name(new_name)
    @name = new_name
  end
end

eric = Person.new('Eric')
eric.change_name("艾里克")
puts eric.name #=> 艾里克
[/pre]

我们定义了方法 change_name,它接受一个参数 new_name
在执行方法的过程中,我们把 @name 赋值为 new_name
真是一个好主意!我们用一个方法change_name,就成功地改变了eric的名字!

等等,有什么更自然的方式没有?
如果我能像 eric.name = "艾里克" 这样的方式赋值,岂不是看起来更加舒服?
好消息是,Ruby可以定义这样的方法。定义如下:
[pre lang="ruby" line="1"]
class Person

  # ... 同上

  def name=(new_name)
    @name = new_name
  end
end

eric = Person.new('Eric')
eric.name = "艾里克"
puts eric.name #=> 艾里克
[/pre]
高端大气上档次!
当我们使用 eric.name = "艾里克" 这样的语句时,Ruby 已经贴心地帮我们转换为调用 name= 方法。
注意,=也是这个方法名的一部分。因此定义的时候,name和=之间不能加空格。
也就相当于 eric.name=("艾里克"),"艾里克" 是方法的参数,也就是上面代码中的 new_name。
执行了这个方法,@name 就被赋值成了 new_name,也就是艾里克。
因为这个功能也很常用了,Ruby也为我们提供了偷懒的工具:attr_writer
食用方法与 attr_reader 相同,看懂了 attr_reader 的你,这里也一定会举一反三,触类旁通的。

举一反三?第三个呢?

...

Ruby 还有一个工具:attr_accessor,它表示即可读又可写。
也就是说,attr_accessor 等价于 attr_reader 加上 attr_writer

于是,类的基础部分讲完了。回去看看RGSS3默认脚本,是不是可以多理解了一些东西了?
[/fold]

点评

你发现就好。我真不知道怎么写才能区分的开  发表于 2014-7-18 09:28
大人,建议把第11段代码的person、name等变量区分一下。折腾半天发现此name非彼name,此person非彼Person  发表于 2014-7-18 09:23
我努力了……我果然是不会写教程……  发表于 2014-7-7 16:44
嗯……我在想怎么讲……  发表于 2014-7-7 16:06
attr真的没看懂....能讲细一点吗?恕我愚钝。  发表于 2014-7-7 16:04
回复

使用道具 举报

梦石
0
星屑
9557
在线时间
5074 小时
注册时间
2013-6-21
回帖
3459

开拓者贵宾剧作品鉴家

发表于 2014-7-2 13:56:44 | 显示全部楼层
本帖最后由 RyanBern 于 2014-7-6 10:37 编辑

[fold=self是什么]
解释一下什么是self,在RM的F1里面,是这么解释self的,说它是一个伪变量,地位比较特殊。不能改变伪变量的值,如果强行改变会发生错误。下方跟着一句简单得不能再简单的东西,"当前方法的执行对象本身",这到底是个啥?

如果让Ryan解释,那么我的解释恐怕要比这还简单——self的意思就是"这个对象"。

喂喂喂,说啥呢,你这不是坑爹么?

这个解释不是我自己想出来的,而是在课上学的……如果你学过了C++/Java等语言的话,那里面没有self,倒是有个关键字叫this,this的意思就是"这个",在C++/Java里面我们管它叫this指针,指向的就是这个对象。而在这里,this和self及其地相似,如果你理解了this,也就理解了self。

说了半天跟没说一样,差评!
那好,如果这样理解不了,那我们看看F1给的解释,"当前方法执行对象本身"。

我们都知道,定义一个方法,里面涉及到很多东西,如果涉及到其他对象,可以利用参数传入的引用(形式参数)轻松访问到。但是,只有一个对象没法访问到,那就是执行这个操作的对象本身。我们来举个例子吧~
  1. class Person
  2.   attr_accessor :name
  3.   def initialize(name)
  4.     @name = name
  5.   end
  6.   def beat(target)
  7.     print @name + "狠狠地揍了" + target.name + "一下"
  8.   end
  9. end
  10. ryan = Person.new("Ryan")
  11. be = Person.new("Bad Egg")
  12. ryan.beat(be) # => Ryan狠狠地揍了Bad Egg一下
复制代码
如果调用
  1. ryan.beat(ryan) # => Ryan狠狠地揍了Ryan一下
复制代码
Ryan这是疯了,在这儿自残呢。
因此我们让Ryan聪明点,如果beat的对象是自己的话,就不去自残了。
  1. class Person
  2.   def beat(target)
  3.     if target == self
  4.       print "我不做自残的事儿!"
  5.     else
  6.       print @name + "狠狠地揍了" + target.name + "一下"
  7.     end
  8.   end
  9. end
复制代码
这里就用了self关键字,表示执行该行为的对象本身。ryan对象调用了beat方法,那么beat方法内部的self就等同于ryan,如果be对象调用了beat方法,那么方法内部的self等同于be。如果不用self,恐怕很难访问到自己了。值得注意的是,self之所以称为不能更改的伪变量,是因为它的值不受程序员的控制,当前调用该方法的对象是谁,self就是谁。

在这里说一下一个小小的辨析。有人认为,如果@x是某类A的一个属性,那么@x = 1和self.x = 1没有区别。这是不对的。@x=1是单纯地把成员变量@x的值改为1,而self.x调用的是一个方法x=(x),然后进行操作。
  1. class A
  2.   def initialize
  3.     @x = 0
  4.   end
  5.   def x=(val)
  6.     @x = val + 1000
  7.   end
  8.   def fun1
  9.     @x = 1   # 单纯修改@x
  10.     p @x
  11.   end
  12.   def fun2
  13.     self.x = 1 # 调用x=(val)方法修改x
  14.     p @x
  15.   end
  16. end
  17. a = A.new
  18. a.fun1 # => 1
  19. a.fun2 # => 1001
复制代码
从这个方面我们可以看出,圆点运算符"."的意义就是调用方法,而不是访问某具体的成员变量,这个和C++/Java不同,因此在这方面,我的建议就是忘了属性吧,你其实调用的都是方法,真正的属性你是调用不了的。由于Ruby中,对方法调用如果方法本身可以不写参数,那么调用的时候就可以不必写空的圆括号(),这就导致调用方法看起来就好像访问对象的属性一样,但是你要明白,出现在原点运算符后面的,都是一个方法的名字。(好像不太对?不服来找我呀~我会告诉你attr_reader:Xx就等同于def Xx~return @Xx~end么)
[/fold]

评分

参与人数 1星屑 +132 梦石 +1 收起 理由
taroxd + 132 + 1 认可答案 extra

查看全部评分

回复

使用道具 举报

梦石
0
星屑
122
在线时间
552 小时
注册时间
2012-8-18
回帖
1392
发表于 2014-7-3 23:20:04 | 显示全部楼层
本帖最后由 无脑之人 于 2014-7-4 13:15 编辑

Symbol

Symbol看起来像一种特殊的字符串,他具有String的一部分方法,那么,他与String究竟有什么不同呢?

[fold]首先,Symbol里面的字符要符合方法名或者实例变量/全局变量名的格式,否则就会是:"xxx"这样的形式,说真的这样意义不大
Symbol是静态的,是立即值的一种,你输出每一个相同的Symbol的object_id,你会发现他们都是一样的,通俗一点来说,就像1只有一个一样,每个Symbol都只有一个实体
而String不同,String就是一段可以操作可以修改的字符串,修改过里面的字符后String的object_id是没有变化的,String传递的时候是按引用传递,也就是说传递给方法的String是原来的String,而Symbol是副本【不必担心开销,他们的传递其实本质上毫无区别】

Symbol这样的特点,使其具有这样的用途:
1.用作Hash的索引
每个Symbol的值都是固定的,并且Symbol不会被改动,因此用它来获取整数值和进行相等比较的时候更加容易并且是快速的【创建时便已经算好了,O(1)】,而String是可以改动的,因此他在获取对应整数值和比较的时候都相对较慢【现算,O(n)】。
因此如果需要hash并且用字符串做索引,请务必换Symbol
【Plus:对于一些正则之类的,以及Cache模块这种需要使用String的,那就用String吧】
2.进行ruby-metaprogramming
这是一个大坑,我不讲,我只想说Ruby进行元编程的时候以Symbol为主要传递方法名的方式
不过值得一提的是,Symbol与attr系列有着密不可分的联系【不要问我attr为什么在ruby-metaprogramming里面】
attr系列语法:

  1. class Miao
  2.   attr_reader :sangshi
  3.   attr_writer :hentai
  4.   attr_accessor :baka
  5. end
复制代码
我们可以看到语法中用到了冒号,在最开始学习Ruby时我一直认为这是一个特殊的语法,实际上,这只是一个方法调用:

  1. class Miao
  2.   attr_reader( :sangshi)
  3.   attr_writer( :hentai)
  4.   attr_accessor( :baka)
  5. end
复制代码
也就是说,我们其实是给attr系列方法传递了一个Symbol!并且,attr系列方法都是可变参数的,也就是说,我们可以写出这样的代码:

  1. class Miao
  2.   attr_accessor :baka,:sangshi,:hentai
  3. end
复制代码
啊,真是神sang奇shi,在其他的譬如send,instance_variable_get之类的方法里面,也都一般使用Symbol进行方法名传递
3.hash参数/关键字参数
很少用过,详询LZ这个大触

创建Symbol的方法:
  :xxx :@a :$ww :a123 都是合格的Symbol字面值
  【PS:创建奇怪的Symbol可以用 :"abc@123$$XX" 这样的形式,不过不推荐 】
  另外还可以"abc".to_sym这样来转化

  【层主的碎碎念:一直以为Symbol是类似C/C++的enum……没想到尼玛居然是仿Lisp的……由于上面三个用途没有一个讲了……于是就不做示例代码了……】

  完毕~【这一节好空】[/fold]

评分

参与人数 1梦石 +1 收起 理由
taroxd + 1 认可答案

查看全部评分

我要填坑!我要背单词!我要学日语!我要每天锻炼!
好吧呵呵= =
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册会员

本版积分规则

Powered by Discuz! X5.0 © 2001-2026 Discuz! Team.

在本版发帖返回顶部