【对象】
Ruby 是面向对象的语言。这里,所谓对象可以解释为任何东西。

对象和类Ruby 所使用的一切数据就称为对象。

3               # 整数对象
1.5             # 浮点数对象
"Ruby"          # 字符串对象
1..10           # 范围对象

而对象的种类就称为类。比如字符串对象是属于 String 类,范围对象是属于 Range 类。关于 Ruby 中所有类的详细介绍,见 RGSS 参考 中的 内部类 部分。

要强调特定类所属的对象,有时使用实例这种说法。所谓类可以说是其所属对象的描述。大的设计方案都有很多类,决定对象的动作。对象必定是属于哪个类的。因此,「属于这个类的对象」也可以说成是「这个类的实例」。

方法尝试着看看 String 类的部分吧。现在或许不太了解其中的意思,但不用太介意。实际中自已编写脚本时将会经常参照这个参考,有必要提早有所了解。

看到方法栏下方列出的项目了吧。这些就是该类所属对象专用的函数即方法。

方法列表中有个名称为 size 的项目。使用这个看看吧。

a = "Ruby"

p a.size        # => 4

String 类的 size 方法,是返回字符串字节数的方法。这里所说的「字节」指的是容量的意思。比如半角英文字母 1 个字符就是 1 个字节。"Ruby" 这个字符串是由 4 个字母组成所以返回的字节数就是 4(顺便说一下,全角的 1 个字符等于 3 个字节。在中国使用的是 gb2312 文字编码其 1 个全角字符等于 2 个字节,而 RGSS 使用的是 UTF-8 文字编码)。

再来试试这个吧。把字符串转换为大写字母的 upcase 方法。

a = "Ruby"

p a.upcase      # => "RUBY"

上面示例中,调用对象的方法时于变量名称后面会加上符号“.”,在其后面记述要调用的方法。而操作的对象称为作用实例。这里 a.upcase 中的 a 就是作用实例。

但是,对字符串以外的对象调用 String 类的方法比如 upcase 方法的话会怎样呢?

a = 3

p a.upcase      # ERROR!!

这样会出现错误。因为 3 是一个整数对象属于 Fixnum 类,而 upcase 在 Fixnum 类的方法中没有定义。

还有,没有必要完全记住这些方法的名称。在对字符串进行某些操作时要使用 String 类的方法,直接打开 GRSS 参考进行查找会十分方便。使用方法的次数多了,自然就会记住它们。

父类3 和 65 等整数,是 Fixnum 类的实例。

看到 Fixnum 类的方法列表了吧,它的方法数量是如此的少会不会觉得不可思议。当然整数能使用的方法不只这几个。关键在父类这栏里。到 Integer 里面去看看吧。

在 Integer 类中有个名称为 chr 的方法。这是个将数字转换为该数字编码的文字并返回的方法。试试看吧。

a = 65

p a.chr         # => "A"

上例正确的返回了一个结果。但是 65 是 Fixnum 类的一个实例,为何调用 Integer 类的方法 chr 也不出现错误呢?这是因为 Integer 类是 Fixnum 类的父类,而 Fixnum 类继承了父类的方法。

所谓继承,就是在一个原有类中扩充定义一个新类,其新定义的类可以使用原有类的方法和属性。这样原来的类就称为父类,而新定义的类则称为子类。

总之,Fixnum 类是 Integer 类中扩充定义的一个新类。也可以这样说,Integer 类是 Fixnum 类的父类。Fixnum 类继承了 Integer 类的性质,也同样继承了父类的方法。当然,继承的还包括 Integer 类的父类 Numeric 类,以及 Numeric 类的父类 Object 类中定义的方法,Fixnum 类的实例都可以使用。

看到这里也许有人会问,为何要特意把这些类给分开呢?回答会有一点困难,继承的意思,是保持被继承者的性质但却同时拥有自己独有的特性。比如,浮点数是 Float 类的实例,这个类是从 Numeric 类中继承而来的。整数和浮点数的性质是不相同的,然而无论哪个都是表示「数字」这个概念却是共同的,所以在 Numeric 类中定义共同的部分而不用在各个子类中重复定义。
【显示图片】
在学习完对象后让我们稍稍轻松一下,来使用 RGSS 的游戏程序显示图片吧。

Graphics 模块在 RGSS 游戏程序中最重要的应当是 Graphics 模块。它是用来处理游戏中所有图片的方法集合,在游戏的画面中显示图片,就要使用到该模块的功能。

所谓模块,可以考虑成收集了相同范围方法的如容器那样的东西。和类比较相似,然而却不能生成模块的实例,也没有那个必要。

那么首先,请按下面那样输入。

loop do
  Graphics.update
end

这个是用 loop do ~ end 语句作成的无限循环。这个处理是无限反复运行 Graphics.update 操作的意思。中间的那行,是调用 Graphics 模块的 update 方法。因为这个是和调用对象方法相同的形式,所以会比较容易理解吧。

Graphics.update 完成的是更新游戏画面,然后前进 1 帧这样的任务。 这个时候,会向画面中重新同时显示出图片。这个形式是 RGSS 游戏运行的基础。 而运行 RPG 大规模游戏的复杂的脚本,也不过是在这个基本形式上添加各种各样的处理而已。

还有,运行上面的示例后什么也没有显示。这只是个反复显示乌黑画面的处理而已。下面我们就来学习实际显示图片的方法吧。

精灵和位图向刚才的脚本中加入 2 行,如下所示。如果画面左上角显示出小恶魔的图片就表示成功了。

devil = Sprite.new
devil.bitmap = Bitmap.new("Graphics/Battlers/075-Devil01")

loop do
  Graphics.update
end

这里出现了 Sprite 和 Bitmap 两个新类。Sprite 是在游戏画面上显示某些图片的基本概念即精灵的类,Bitmap 是精灵传输元位图本身内容的类。

第一行,是新生成一个 Sprite 类的实例,并把那个实例代入变量 devil。生成整数和字符串的实例比如 3 或 "Ruby" 等可以直接写入,然而生成精灵这样的实例时就必需以 Sprite.new 这样的方式生成。

第二行,是读取 RGSS-RTP 中所含的图片文件 ("Graphics/Battlers/075-Devil01") 并作为 Bitmap 类的一个新实例。而且,对 devil 对象的 bitmap 属性(后述)进行设定。也就是设定这个位图为精灵传输元的意思。

指定 Bitmap.new 参数的路径名,分隔符号是使用 / 而不是 这点要特别注意。我们在字符串「控制码」这项中学习了 在字符串中具有特殊的意义,而在这里使用 / 才是正确的。

属性Sprite 类中像 bitmap 那样的特性称为属性。实际上这并不是 Ruby 语言的概念,而是 RGSS 的独特用语。在游戏程序中使用赋值运算符在多个方面对其内容特性进行设定,这些就称为属性。例如,更改表示精灵座标的 x、y 属性数值就是这样。

devil = Sprite.new
devil.bitmap = Bitmap.new("Graphics/Battlers/075-Devil01")
devil.x = 320
devil.y = 240

loop do
  Graphics.update
end

上面第三行,是把指定精灵 X 座标的属性 x 设定为值 320。同样的第四行,是把指定精灵 Y 座标的属性 y 设定为值 240。这样运行的话,精灵就会显示在画面中央位置附近。

让我们进一步加工一下。

devil = Sprite.new
devil.bitmap = Bitmap.new("Graphics/Battlers/075-Devil01")
devil.ox = devil.bitmap.width / 2
devil.oy = devil.bitmap.height / 2
devil.x = 320
devil.y = 240

loop do
  Graphics.update
end

这样就变成完全显示在画面正中央了。这个 ox、oy 属性的设定,更改了精灵的原点。这和 RPGXP 的事件指令「显示图片」中选择「左上」、「中心」是一样的概念。默认状态下,原点在图片的左上。赋值运算符的右侧调用的 width、height 是 Bitmap 类的方法,作用是分别取得图片的宽和高。这里使用了其值的 1/2,也就是把图片的中心点设定为原点。

现在,可以去看一看 Sprite 类和 Bitmap 类的说明了,是不是觉得写得很复杂不知如何理解?使用这些有趣的属性和方法,自己动手试一试吧。这才是学习 RGSS 的捷径。
【数组】
想一起处理多个对象就要使用数组。

数组的生成在 [] 符号中间排列多个单元就生成了一个数组。如下例所示。

a = ["艾力克斯","布莱恩","卡罗来"]

上述程序建立了一个包含 "艾力克斯"、"布莱恩"、"卡罗来" 这三个字符串单元的数组,并把这个数组代入变量 a 为其赋值。

要取出数组中对象,如下例所示。

a = ["艾力克斯","布莱恩","卡罗来"]

p a[0]          # => "艾力克斯"
p a[1]          # => "布莱恩"
p a[2]          # => "卡罗来"

上述中,a[0] 和 a[1] 等各个单元,能和其它变量那样单独使用。这里 0、1、2 那样的数字被称为数组的附加码。附加码不是从 1 开始而是从 0 开始的,这点一定要注意。从 0 开始,单元从左至右依次为 0、1、2 ……。

数组中除了字符串外还可以包含其它任意的对象。而且,使用 p 命令也可以不指定附加码,那样就会显示数组的全部单元。

a = [2000,"艾力克斯",true,1..50]

p a             # => [2000,"艾力克斯",true,1..50]

数组单元的重赋值数组的各单元的使用和普通的变量一样,也能进行变量的重赋值。

a = [10, 20, 30]
a[0] = 100

p a             # => [100, 20, 30]

该示例中,首先是建立了一个由 10、20、30 这三个数字单元组成的数组,然后向第一个单元里重赋值代入 100。这时 a[0] 原先的数值 10 就被代入的 100 所代替,显示的结果就更改了。这个处理和普通变量的重赋值是一样的。

指定超过最初数组附加码范围的单元并为之赋值,会自动扩大数组添加新的单元。

a = [10, 20, 30]
a[3] = 40

p a             # => [10, 20, 30, 40]

建立数组时只有 3 个单元,现在为在其范围外的 a[3] 赋值,就会自动添加第 4 个单元扩大数组。C 语言等其它语言是不充许这样赋值的,而 Ruby 这种灵活的语言就可以放心添加。

如果跳过范围的话会如何呢?

a = [10, 20, 30]
a[5] = 60

p a             # => [10, 20, 30, nil, nil, 60]

中间就会出现 nil 值。这个与 true 和 false 等一样是常量的一种,意思是「什么都没有」。和 true 表示「真」,false 表示「伪」一样,nil 表示「无」。

数组的操作数组是 Array 类的实例。可以调用这个类定义的方法对数据进行操作。

比如,想知道数组的单元数量的话就可以使用 size 方法。和 String 类的 size 方法返回字符串字节数相似,Array 类的 size 方法是返回数组的单元数。数组为空时就返回 0。

a = ["艾力克斯","布莱恩","卡罗来"]

p a.size        # => 3

想知道数组中是否含有特定的值的话,就要使用到 include? 方法。

a = ["艾力克斯","布莱恩","卡罗来"]

p a.include?("布莱恩")            # => true
p a.include?("小田纯一狼")        # => false

第三行中使用 include? 方法,是确认数组中是否含有字符串 "布莱恩"。上例在第一行生成数组时设定有字符串 "布莱恩",所以输出结果为 true(真)。同样第四行是确认数组中是否含有字符串 "小田纯一狼"。然而这个字符串在数组中是不存在的,所以输出结果就为 false(伪)。

数组的循环使用 for 循环,即使没有设定对象的附加码也能指定数组单元。

a = ["艾力克斯","布莱恩","卡罗来"]

for s in a
  p s
end

这里使用变量 s,循环代入数组 a 中所包含的单元。循环的范围是数组 a 包含的所有单元,按 "艾力克斯"、"布莱恩"、"卡罗来" 的顺序在对话框中显示,最后循环结束。
【Hash 表】
Hash 表是一种和数组比较类似的数据构造形式。

Hash 表的生成Hash 表也称为关联数组。和数组不同的是,作为取出数值 的主键(相当于数组的附加码),可以使用任意的对象。 在 {} 符号中间排列多个单元就生成了一个哈希表。

a = {"艾力克斯"=>2000, "杰克"=>2003}

这个例子中,"艾力克斯" 和 "杰克" 就是取出数值的主 键,2000 和 2003 就是实际的数值。这样, 使用 => 符号把主键和数值连接起来就形成了 Hash 表。

要取出 Hash 表中对象的时候,应按如下操作。

a = {"艾力克斯"=>2000, "杰克"=>2003}

p a["艾力克斯"]        # => 2000
p a["杰克"]            # => 2003
p a["宇佐助"]          # => nil

从上例最后一行可以得知,当指定主键不存在时就会返回 nil。

这里是以字符串为主键,当然不使用字符串也可以。作为主键还可以使用整数等其它数据类型,在有很多编号单元的情况下使用哈希表会比使用数组更加方便。

另外,哈希表的反操作,也就是「检查与数值对应的主键」的情况下,在 [] 中写入数值是不能得到其主键的。这里不再多作陈述,该种操作方法我们会在后面特别介绍。

Hash 表数值的添加实际操作中,Hash 表可以在程序中直接指定数据建立, 亦可以使用先建立一个空的 Hash 表然后向其中添加单元数据等多种方法生成。 生成一个空的 Hash 表只需写入 {} 就行了。

a = {}

向 Hash 表中添加数值,和数组单元的重赋值完全相同。

a = {}
a["艾力克斯"] = 2000

p a                 # => {"艾力克斯"=>2000}

Hash 表的操作Hash 表是 Hash 类的对象。 同数组一样,可以调用这个类定义的方法对数据进行操作。

比如要从 Hash 表中删除数值的话,就要使用 delete 方法。

a = {"艾力克斯"=>2000, "杰克"=>2003}
a.delete("杰克")

p a                 # => {"艾力克斯"=>2000}

想知道 Hash 表中是否含有某个特定主键的情况下,就要使用 include? 方法。

a = {"艾力克斯"=>2000, "杰克"=>2003}

p a.include?("艾力克斯")    # => true
p a.include?("宇佐助")      # => false

第三行中使用 include? 方法,是确认哈希表中是否含有主键 "艾力克斯"。上例在第一行生成哈希表时主键设定有 "艾力克斯",所以输出结果为 true(真)。同样第四行是确认哈希表中是否含有主键 "宇佐助"。然而这个主键在哈希表中是不存在的,所以输出结果就为 false(伪)。

Hash 表的循环在 Hash 类中,有取得全部主键排列的 keys 方法。

a = {3=>6, 4=>10, 5=>15}
p a.keys            # => [5, 3, 4]

这样,=> 左侧的主键会全部排列出来。 但 Hash 表中是没有顺序的,所以排列会无顺序可言。

前节中,我们学习了使用 for 循环指定数组的方法。 现在让我们来用 for 循环指定 keys 方法的返回值,作成 Hash 表主键的循环吧。

a = {3=>6, 4=>10, 5=>15}

for k in a.keys
  p k
end

运行上例就会按不同顺序显示 3、4、5。

而且,使用 values 方法的话,还能取得 Hash 表全部数值的排列。

a = {3=>6, 4=>10, 5=>15}

for v in a.values
  p v
end

运行上例就会按不同顺序显示 6、10、15。
【定义类】
基础篇的最后,让我们来学习定义新类的方法。

类的定义直到今天,我们都是一直在使用 String 类、Array 类或者 Sprite 类等这些系统内部类的方法来进行学习。实际上如果想使用类,也可以自己定义。所谓「建立类」几乎和「程序设计」是一个意思。其实预置脚本中,除外最后一个 Main 组其余的全部都是类定义。

比如新定义一个 Person 类,可以如下操作。

class Person
end

定义类的话这样就行了。但现在只是个「什么都没有的类」,让我们再来进行以下操作。

alex = Person.new

这样就作成一个 Person 类的实例。

想要指定父类也很简单。比如,定义继承于父类 Monkey 类的 Person 类可以如下操作。

class Person < Monkey
end

方法的定义方法的定义,和定义普通函数的情况相同。如果在类定义当中加上 def ~ end 的话,就会自动成为该类的方法。

class Person
  def hello
    print "您好"
  end
end

这样,就定义了调用后会显示“您好”字符串的 hello 方法。再进行如下操作。


alex = Person.new
alex.hello                  # => 您好

这样就能调用 hello 方法了。

实变量属于个体对象的变量称为实变量。名称以 @ 开头的变量,自动作为实变量使用。局部变量只有在 def ~ end 等结构内部使用时才有效,然而使用实变量的话,只要其对象存在,变量的值就会被保存。实变量一般作为对象的属性来使用。

class Person
  def name
    return @name
  end
  def name=(name)
    @name = name
  end
  def hello
    print "我是" + @name + ""
  end
end

alex = Person.new
alex.name = "艾力克斯"
alex.hello                  # => 我是艾力克斯

上面的示例是先准备 @name 实变量,使用 name、name= 方法把对象附加上 @name 实变量作为属性。像这样的方法称为附加属性。附加属性也能更简单的像下面那样编写。

  attr_accessor :name

这样编写是,自动定义与 : 符号右侧名称相同的实变量对应属性的取得和注解方法。上例中是生成 @name 实变量并附加成为属性。总之,

  def name
    return @name
  end
  def name=(name)
    @name = name
  end

是与上面相同的意思。

在预置脚本中 attr_accessor 应用得很多。但在只定义属性的取得方法的情况下,一般使用 attr_reader 代替 attr_accessor。

对象初始化生成对象时,把实变量初始化为某个特定的值。先定义一个名称为 initialize 的方法,在使用 new 生成新实例时会自动调用该方法。

class Person
  def initialize
    @name = "无名先生"
  end
end

从第二行开始的 initialize 方法的定义,表示实变量 @name 的初始值被赋值为字符串 "无名先生"。这样,生成新的 Person 对象的时候,会自动以 "无名先生" 初始化实变量。

initialize 方法中设定有临时参数的情况下,new 会取该参数为初始值。

class Person
  def initialize(name)
    @name = name
  end
end

alex = Person.new("艾力克斯")

上述示例中,initialize 方法里设定有临时参数 name,那么实变量 @name 的初始值就会使用该参数。参数 name 实际在 Person 类 new 时给予。最后一行,生成 Person 类对象时指定参数为 "艾力克斯",那么 @name 的初始值也就设定为 "艾力克斯"。

方法的重载父类中已定义的方法在子类中再次被定义称为重载。在子类方法中调用父类相同方法的情况下,要使用关键词 super。

class Hahaha
  def laugh(x)
    return "哈" * x
  end
end

class Wahaha < Hahaha
  def laugh(x)
    return "哇" + super
  end
end

man = Wahaha.new
p man.laugh(4)      # => "哇哈哈哈哈"

上述示例是,对新建实例调用 Wahaha 类的 laugh 方法,其中 super 能调用其父类 Hahaha 类的 laugh 方法。那么,最后一行调用 laugh 方法可以理解为 "哇" + "哈" * 4,于是输出结果就为 "哇哈哈哈哈"。
【解读篇】
【数据库】
在解读篇,我们会就预置脚本是做什么的作详细的说明。

本章将介绍 RPGXP 的数据库在脚本内是如何使用的。

$data_xxxx请从脚本编辑器左侧目录,查找名称为 Scene_Title 的组。在这个组的右侧窗口中,会发现如下的部分。这个是在显示游戏标题画面之前运行的脚本。

    # 载入数据库
    $data_actors        = load_data("Data/Actors.rxdata")
    $data_classes       = load_data("Data/Classes.rxdata")
    $data_skills        = load_data("Data/Skills.rxdata")
    $data_items         = load_data("Data/Items.rxdata")
    $data_weapons       = load_data("Data/Weapons.rxdata")
    $data_armors        = load_data("Data/Armors.rxdata")
    $data_enemies       = load_data("Data/Enemies.rxdata")
    $data_troops        = load_data("Data/Troops.rxdata")
    $data_states        = load_data("Data/States.rxdata")
    $data_animations    = load_data("Data/Animations.rxdata")
    $data_tilesets      = load_data("Data/Tilesets.rxdata")
    $data_common_events = load_data("Data/CommonEvents.rxdata")
    $data_system        = load_data("Data/System.rxdata")

作一下说明,这段的意思是从 RPGXP 保存的文件中读取数据库信息,并在脚本中代入到各种数据变量为其赋值。这些以 $ 符号开头的变量是全局变量,因此这些数据在后面其它的脚本中也能引用。

其中的 load_data 是 RGSS 内部函数 之一,是一个从指定文件中读取数据的函数。比如第一行,就是表示从 Date 文件夹中的 Actors.rxdata 文件中读取数据,并代入全局变量 $data_actors 中为其赋值。

数据库的内容各个对象的具体情况如下所示。

变量名称 内容 所属类
$data_actors 角色 RPG::Actor 数组
$data_classes 职业 RPG::Class 数组
$data_skills 特技 RPG::Skill 数组
$data_items 物品 RPG::Item 数组
$data_weapons 武器 RPG::Weapon 数组
$data_armors 防具 RPG::Armor 数组
$data_enemies 敌人 RPG::Enemy 数组
$data_troops 队伍 RPG::Troop 数组
$data_states 状态 RPG::State 数组
$data_animations 动画 RPG::Animation 数组
$data_tilesets 图块 RPG::Tileset 数组
$data_common_events 公共事件 RPG::CommonEvent 数组
$data_system 系统 RPG::System

从角色到公共事件的这 12 种数据,是和数据库中标记的号码相对应的数组。但是 RPGXP 的数据 ID 是从 1 号开始的,为了使数据 ID 和数组的附加码一致,所以数组的第一个(0 号)单元为 nil。

数据库的使用比如下一个脚本是表示角色中 1 号人物的名称。

$data_actors[1].name

下一个脚本是表示敌人中 2 号敌人的 MaxHP。

$data_enemies[2].maxhp

下一个脚本是表示物品中 3 号物品指定图标的文件名。

$data_items[3].icon_name

下一个脚本是表示系统中 4 号开关的名称。

$data_system.switches[4]

这些要点,请在参考中参照数据的属性(变量)名称来查看。想以 p 函数显示这些数据的话,可以使用事件指令的「脚本」命令来执行,这样会很方便。
【游戏对象】
数据库,原则上其中的数据在游戏中是不变的,不会进行数据的重新写入。与之相对,比如地图画面上人物的移动等,这种情况下发生数据改变的是游戏对象。

$game_xxxx在 Scene_Title 组中,command_new_game 方法下有如下的部分。这个是在标题画面中选择「新游戏」时运行的脚本。

    # 生成各种游戏对像
    $game_temp          = Game_Temp.new
    $game_system        = Game_System.new
    $game_switches      = Game_Switches.new
    $game_variables     = Game_Variables.new
    $game_self_switches = Game_SelfSwitches.new
    $game_screen        = Game_Screen.new
    $game_actors        = Game_Actors.new
    $game_party         = Game_Party.new
    $game_troop         = Game_Troop.new
    $game_map           = Game_Map.new
    $game_player        = Game_Player.new

和数据库的 $data_xxxx 相同,这些也是全局变量。后面是生成 Game_Xxxx 名称类的实例,并代入到变量为其赋值。这些 Game_Xxxx 名称应该认识吧。在脚本编辑器组列表里,最上面的一些组就是这样的名称。也就是说,这些组定义的类在这里被实例化了。

游戏对象的内容各个对象的具体情况如下所示。和数据库的情况不同,这些对象所属的类不是 RGSS 预先设定的类,而是在脚本中定义的。

变量名称 内容 所属类
$game_temp 临时数据 Game_Temp
$game_system 系统数据 Game_System
$game_switches 开关 Game_Switches
$game_variables 变量 Game_Variables
$game_self_switches 独立开关 Game_SelfSwitches
$game_screen 画面效果 Game_Screen
$game_actors 角色排列 Game_Actors
$game_party 同伴 Game_Party
$game_troop 队伍 Game_Troop
$game_map 地图 Game_Map
$game_player 主角 Game_Player

这些类原则上只用于提供数据构造,并不具备显示图标或处理键盘输入的功能。但是 Game_Player 类为了方便,含有方向键的移动和决定键开始事件等处理。

其他关联类上述是从全局变量中直接使用的游戏对象,其实这些对象内部,还进一步包含一些其他类的对象。如下所示。

类 内容 说明
Game_Picture 图片 Game_Screen 的内部使用
Game_Actor 角色 Game_Actors 的内部使用
Game_Enemy 敌人 Game_Troop 的内部使用
Game_CommonEvent 公共事件 Game_Map 的内部使用
Game_Event 地图事件 Game_Map 的内部使用

另外,还有下面这两个重要的类。这两个类在脚本中的数量非常多,被分割成多个组定义。

arrow
arrow
    全站熱搜

    戮克 發表在 痞客邦 留言(0) 人氣()