设为首页收藏本站|繁體中文

Project1

 找回密码
 注册会员
搜索
楼主: RyanBern
打印 上一主题 下一主题

[原创发布] 【14.7.5第二版更新】RGSS1脚本入门参考

[复制链接]

Lv4.逐梦者 (版主)

梦石
0
星屑
9532
在线时间
5073 小时
注册时间
2013-6-21
帖子
3580

开拓者贵宾剧作品鉴家

11
 楼主| 发表于 2013-10-14 20:44:56 | 只看该作者
本帖最后由 RyanBern 于 2015-7-31 11:25 编辑

[box=RoyalBlue]
第2章节:解密RGSS系统
[/box]

       在这一章节我们来全局地了解一下我们的RMXP到底是怎么工作的。在这我要说一点题外话,四年前我刚刚接触RMXP,当时年少无知,画了一张地图放上去,就点击那个开始测试按钮,心想会运行出来什么东西,当时的想法是,我除了画了一张地图,什么都没做,大概会运行错误吧,但是实际上,RMXP中已经自带了一个默认的系统。千万别小看这个系统,虽然功能还不够强大,但是作为我们学习的例子,肯定是十分合适的。
       注意啦,从这节之后,我们要具体学习如何解读RMXP的脚本和改造它,甚至我们可以自行编写脚本,这就是我写这个帖子的目的所在。
2.1  预置脚本
       打开RMXP的脚本编辑器,我们就会看到一堆乱七八糟的代码。心里不禁有一些发虚,我相信,即使有些朋友看了很多次F1,对着这些脚本也会一头雾水。没关系,我们一点点梳理一下它们的层次。
2.1.1  Game_Xxxx游戏对象脚本

       上面图片显示的就是游戏对象的脚本,游戏对象,就是在游戏进行过程中,和玩家互动的对象。比方说游戏地图,地图上的事件,发生打斗的敌人等等。这些对象都随着游戏进行而变化。与之相对的是数据脚本,这就是我们所说的数据库,它们是游戏开始之前就已经设置好的数据,一般是不可更改的。我们打开RMXP,很长时间都会花在游戏数据编辑上面。(比方说道具,武器,职业,状态,都是我们自己定义的,并且游戏进行中不会更改)
       那了解这部分内容,我们想想在整个游戏中都会遇到什么东西。

       首先是游戏的系统对象,比方说计时器,比方说各种音乐音效,这些对象的管理在Game_System中,我们打开这个脚本,就可以清楚地看到,这个脚本执行的是什么功能。当进入游戏画面中,系统会载入一个Game_System类的实例$game_system,这个全局变量是游戏中一直存在的,直到你退出游戏。而使用Game_System类的方法,就要利用$game_system这个实例。
例如,要让游戏播放一个BGM,就要输入:
$game_system.bgm_play(bgm),其中,bgm是代表一个音频对象(并不是指BGM的文件名),我们实际运用的时候,通常会调用这样的语句:
Audio.bgm_play(filename,volume,pitch),三个参数表示文件名,音量,节拍,后面两个参数可以省略。Audio其实是一个模块,我们初学还暂时用不到它的概念,我们只需要记住这样用就可以了。

       然后就是各种游戏开关和变量了,这些东西,就是我们在编写事件的时候用的,实际上游戏在运行时,也会创建它们。不过,这个东西可不是游戏一打开就有的,只是在开始新游戏或者载入存档之后才有的。原因也很简单,因为无论是开关还是变量,都是在玩家真正开始游戏的时候才有存在的意义,而在标题处是没有它的实际作用的。换言之,如果你想用某个开关的变换来改变标题画面的一些东西(比方说打开1号开关读档按钮无效,关闭1号开关读档按钮有效),这样的思路是行不通的。
  • Game_Switches——游戏开关的类
  • Game_SelfSwitches——游戏独立开关的类
  • Game_Variables——游戏变量的类

这三个类就是表示开关,独立开关,变量。对应生成的实例是:
  • $game_switches
  • $game_self_switches
  • $game_variables

       当然,这些东西只有真正进行游戏时才会有的。下面我们来一个个说该怎么用。
       $game_switches和$game_variables都是类似于数组的结构,我们可以像数组那样使用它。不过仅限于读取和改写两种操作,其余的类似于删除之类的操作则无法进行。
       因为数组是从0号单元开始,但是游戏设定里面,ID是从1开始的,因此这两个全局变量的0号单元都是nil,我们用的时候,不必担心这方面的问题。$game_switches[1]就是指的1号开关(事件编译器中的1号开关),$game_variables[1]就是指的1号变量。
       $game_self_switches是类似于Hash表的结构,我们可以像Hash表那样使用它。当然,我们也不能使用Hash表的全部功能,仅限于读取和改写两种操作而已。
       这个Hash表中的主键的类型,是一个含有3个元素的数组(Hash表的若干概念可以自行F1),[map_id,event_id,switch_tag],其中,第一个元素指的是地图ID,第二个元素指的是事件ID,第三个元素指的是开关编号(因为同一个事件的独立开关有4个,分别是A,B,C,D),这样某一个独立开关的信息就会完全确定。因此,让1号地图ID为2的事件的独立开关A打开,就要写:
RUBY 代码复制
  1. $game_self_switches[[1, 2, 'A']] = true

       注意,最后一个参数是单引号引出的大写字母。
       我们知道,如果利用事件编辑器,只能对本事件的独立开关进行操作,但是,有了上面这个语句,我们便可以在一个事件中对另一个事件的独立开关进行操作,这样能解决很多问题。

那么在这里我们留下一个小小的作业。
       【踩冰机关】请尽量用事件编写一个小游戏,主角踏入一片奇怪的冰地,冰地上有一个入口和一个出口,只有主角把规定区域内所有的地面都踩过一次(规定不允许重复踩踏规定区域内的任何一个地面,即所有地面必须踩一次而且只能踩一次),出口才会打开,否则出口不会打开。当主角踩踏某一块地面两次时,宣布失败,利用场所移动将主角传送到入口,并且重置地面(如果主角没有把地面全踩一遍就走到出口,按照规则,出口是封闭的,这时候主角只能第二次踩出口的那块地面,因此游戏也会失败)。允许使用的脚本仅限于对独立开关的操作。小提示:实际上,在地面块数比较少的情况下,利用开关也是可以做到的,但是开关的数量实在是太少,会造成很多浪费,在这里我们会看出独立开关的优越性。

       最后要说的是,这三个全局变量,每次存档后,都会写入存档文件里面,利用这个特性,我们可以做出很多新的脚本,具体的还要到后面我们再说。

       还有就是队伍,角色,敌人队伍,敌人。
  • Game_Battler——游戏战斗者的类,分成3部分定义,是Game_Actor和Game_Enemy的父类
  • Game_Actor——角色的类
  • Game_Enemy——敌人的类
  • Game_Troop——敌人队伍的类
  • Game_Party——角色队伍的类
  • Game_Actors——角色排列的类
  • Game_BattleAction——打斗行动的类,在Game_Battler内部使用

其中,拥有全局变量实例,并且全局变量能写入存档的是
  • $game_party #表示角色队伍
  • $game_actors #表示角色排列
  • $game_troop #表示敌人队伍

       这些内容,我们会在第3章节重点讲解,因为这里是大家对游戏的改动涉及最多的地方,因此我们肯定会详细说的,在这儿只是让大家了解个大概。
       注意,Game_Actors和Game_Actor是两个不同的东西,Game_Actors是类似于数组的类,$game_actors里面是按照角色ID存放各个角色的信息,而Game_Actor则是活生生的角色类,因此使用的时候不要弄混了(话说我刚写脚本的时候就经常弄混)。

       再有就是游戏地图,角色和事件,公共事件了。这些都是在地图上要处理的,而且非常直观。
  • Game_Map——游戏地图的类
  • Game_Character——角色和事件公用的父类,分成3部分定义
  • Game_Player——角色(在地图上)的类
  • Game_Event——事件(在地图上)的类
  • Game_CommonEvent——公共事件的类

其中,拥有全局变量实例,并且能被写入存档的是
  • $game_map #表示游戏地图
  • $game_player #表示地图上玩家的角色

       这里要说明的是,游戏的各种事件是地图里面附属的一个东西,因此只需要放在$game_map里面就可以了,至于为什么把玩家单单提出来做成一个类,是因为玩家和事件有着不同性质。

       另外是游戏画面,游戏图片。
  • Game_Screen——游戏画面的类
  • Game_Picture——图片的类

       其中$game_screen会被写入存档数据。
       Game_Screen是负责游戏画面闪烁,震动,色调变换,或者是天气设置的,这个肯定和地图不同,因此和Game_Map是分开的。
       Game_Picture是在$game_screen内部使用,事件编辑器里面的显示图片什么的,其实就是这个类的方法。这里我们改脚本改动较少,所以就在这里说一下而已。

       最后,游戏需要什么临时数据,比方说是否在战斗中啦,是否由事件调用存档啦,都是临时数据。这些临时数据统统放在一个类的实例里面。
       Game_Temp——游戏临时数据的类
       对应实例是$game_temp
       因为是临时数据,当然不会写入存档中,这个大家一定要注意。很多人写了新脚本,却把需要存储的数据放到$game_temp里面去,结果可想而知。

2.1.2  Sprite_Xxxx Spriteset_Xxxx精灵 活动块 活动块组

       实际上,游戏中显示到屏幕上的图片,都是由这些类生成的,不要以为$game_map表示地图,那么地图就是$game_map,实际上,负责生成并且显示图片的工作,是交给精灵(Sprite)完成的。
  • Sprite_Character——角色行走图活动块类,包括主角和事件的行走图
  • Sprite_Battler——战斗图活动块类,包括主角和敌人的战斗图
  • Sprite_Picture——图片活动块类
  • Sprite_Timer——计时器活动块类
  • Spriteset_Map——地图元件活动块组,是一些活动块的集合体,例如地图元件,角色行走图,远景图,雾图形,计时器
  • Spriteset_Battle——战斗画面活动块组,是一些活动块集合体,例如战斗图,战斗背景,计时器

       在这里我们只讲一个事情,很多人会发现,数据库中能够更改敌人战斗图的位置,却不能更改主角的战斗图位置,这是非常不方便的事情。那么怎样改变角色战斗图的位置呢?我开始以为答案在Sprite_Battler或者Spriteset_Battle里面,后来一看根本就不是,其实角色战斗图的位置在Game_Actor中的第567行(如果脚本是默认的):

       这下就可以改脚本了,这是竖版战斗改横版战斗或者45度角战斗的第一步啊!

2.1.3  Window_Xxxx窗口类

       这也是游戏中非常重要的一个类了,以后我们经常会跟它打交道。游戏里面所有带框框的基本都是这个类的实例,其中第三个Window_Selectable改,是我优化了Window_Selectable之后的脚本,原来的默认脚本是没有的。当然,窗口我们也是重点讲,不过还是不在这一章,因此大家翻开看看就好,起码知道每个脚本都是负责什么窗口的。

2.1.4  Arrow_Xxxx光标类脚本
       这个图我就不截了,如果是没有鼠标脚本的话,这个地方大家应该不会动(实际上我也没怎么动过)。这个类的内容,就是负责游戏里战斗场面指敌人或者指主角的光标,而不是窗口中的光标矩形,这个大家注意下就好。

2.1.5  Interpreter事件解释器脚本

       这个大概是脚本编辑器最庞大的脚本了吧,分割定义就有7个之多。而他们的作用,我不用多说,是事件党的最爱了吧。事件编辑器中所有的指令,都是这里的方法,说白了,RMXP把我们最常用的命令放到事件编辑器中,可以进行“傻瓜式”操作。
       当然,有些脚本就是对这些事件指令进行优化,比如我们熟知的物品得失提示脚本,就是更改了Interpreter类里面的内容。这个我们后面也要作详细说明。

2.1.6  Scene_Xxxx场景类脚本

       这也是我们经常会遇到的脚本,它们的作用就是处理一个个组合的场景。简单来说,一个场景包括很多个窗口,精灵,以及对所有输入的回应。注意,特别是对输入的回应,是在场景中进行的,也就是说,Window_Selectable定义的时候,并没有说输入空格或者回车后,窗口该怎么怎么变。真正对输入的反应,是在Scene中进行,因为一个场景的很多“元件”都是相关联的的,用一个场景统一处理他们,才是最好的选择。
       同样,我们在后面的章节,要重点讲场景的制作。

2.1.7  Main游戏脚本的入口
       在脚本的最后我们看到一个叫Main的脚本,翻开它,我们会看到只有短短的几行。这就相当于RGSS的主函数,整个程序就是从主函数出发来向下进行的。
       另外,在Main之前的位置,是我们插入各种外挂脚本的地方,我们不能随便地插入到脚本编辑器的任意位置,只能插到Main组之前,Scene组之后,这点大家一定要注意。
2.2  RGSS的工作过程
       实际上,脚本编译的顺序就是从上至下。注意,此时程序还未开始运行,Main组前面所有的脚本,都是进行各种变量和方法的定义,因此它们也只是静静地躺在那里。真正开始的是Main组中的脚本。
       那我们先看看Main组脚本里面都有什么:
RUBY 代码复制
  1. begin
  2.   # 准备过渡
  3.   # 设置系统默认字体
  4.   Font.default_name = (["黑体"])
  5.   Graphics.freeze
  6.   # 生成场景对像 (标题画面)
  7.   $scene = Scene_Title.new
  8.   # $scene 为有效的情况下调用 main 过程
  9.   while $scene != nil
  10.     $scene.main
  11.   end
  12.   # 淡入淡出
  13.   Graphics.transition(20)
  14. rescue Errno::ENOENT
  15.   # 补充 Errno::ENOENT 以外错误
  16.   # 无法打开文件的情况下、显示信息后结束
  17.   filename = $!.message.sub("No such file or directory - ", "")
  18.   print("找不到文件 #{filename}。 ")
  19. end

       一开始先简短设置系统字体,准备画面过渡。
       然后就直接进入场景画面。
       注意,那个$scene是伴随这程序始终进行的全局变量,表示的就是当前场景本身。中间那个while循环是说,如果$scene的值不是nil,那么就调用$scene的main方法。也就是说,如果想退出这个循环,直接输入$scene = nil即可,这样的话,不但场景会退出,主函数也会跟着结束,因此整个游戏就退出了。
       在这里$scene一开始被赋予Scene_Title.new,也就是说我们即将进入标题画面,调用的也是标题画面的main方法。
       我们于是打开Scene_Title,在这里我就不放全部代码了。可以看到有下面的语句:
RUBY 代码复制
  1. $data_actors        = load_data("Data/Actors.rxdata")
  2. $data_classes       = load_data("Data/Classes.rxdata")
  3. $data_skills        = load_data("Data/Skills.rxdata")
  4. $data_items         = load_data("Data/Items.rxdata")
  5. $data_weapons       = load_data("Data/Weapons.rxdata")
  6. $data_armors        = load_data("Data/Armors.rxdata")
  7. $data_enemies       = load_data("Data/Enemies.rxdata")
  8. $data_troops        = load_data("Data/Troops.rxdata")
  9. $data_states        = load_data("Data/States.rxdata")
  10. $data_animations    = load_data("Data/Animations.rxdata")
  11. $data_tilesets      = load_data("Data/Tilesets.rxdata")
  12. $data_common_events = load_data("Data/CommonEvents.rxdata")
  13. $data_system        = load_data("Data/System.rxdata")

       这就是在游戏一开始,载入所有游戏数据库内所有数据,并保存在相应的全局变量里面。这些全局变量不会被更改,一直存在直到游戏退出。
RUBY 代码复制
  1. def command_new_game
  2.     # 演奏确定 SE
  3.     $game_system.se_play($data_system.decision_se)
  4.     # 停止 BGM
  5.     Audio.bgm_stop
  6.     # 重置测量游戏时间用的画面计数器
  7.     Graphics.frame_count = 0
  8.     # 生成各种游戏对像
  9.     $game_temp          = Game_Temp.new
  10.     $game_system        = Game_System.new
  11.     $game_switches      = Game_Switches.new
  12.     $game_variables     = Game_Variables.new
  13.     $game_self_switches = Game_SelfSwitches.new
  14.     $game_screen        = Game_Screen.new
  15.     $game_actors        = Game_Actors.new
  16.     $game_party         = Game_Party.new
  17.     $game_troop         = Game_Troop.new
  18.     $game_map           = Game_Map.new
  19.     $game_player        = Game_Player.new
  20.     # 设置初期同伴位置
  21.     $game_party.setup_starting_members
  22.     # 设置初期位置的地图
  23.     $game_map.setup($data_system.start_map_id)
  24.     # 主角向初期位置移动
  25.     $game_player.moveto($data_system.start_x, $data_system.start_y)
  26.     # 刷新主角
  27.     $game_player.refresh
  28.     # 执行地图设置的 BGM 与 BGS 的自动切换
  29.     $game_map.autoplay
  30.     # 刷新地图 (执行并行事件)
  31.     $game_map.update
  32.     # 切换地图画面
  33.     $scene = Scene_Map.new
  34.   end

       以新游戏命令为例,当玩家选择“新游戏”时,系统会做以上工作。
       其中我们看到直到这里才会生成各个游戏对象,如果是载入,那么就会载入各种游戏对象。
       最后一步将场景切换到地图场景中。这里把$scene变成Scene_Map.new,在所有场景的main函数中,都有一个无限循环loop do,其中有一句:
RUBY 代码复制
  1. if $scene != self
  2.   break
  3. end
这里的self是指被处理对象实例本身,刚才我们说$scene = Scene_Map.new,这就是所谓的$scene不再是被处理对象本身了,变成了其它的东西,表达式不满足,跳出main的主循环loop do。而后,我们回到Main组那里,$scene.main这个语句已经执行完毕,但是不满足循环终止的条件,即$scene还不是nil,因此Main组里面继续调用新的$scene的main方法,这时候$scene已经是Scene_Map的实例了,因此画面就会转到地图画面。
       整个游戏就是这样工作的。直到$scene == nil,游戏便终止了。

       第2章节的内容就到这里,接下来的第3章,我们要系统学习如何DIY游戏对象脚本Game_Xxxx,大家就敬请期待吧。

回复 支持 反对

使用道具 举报

Lv3.寻梦者

梦石
0
星屑
3489
在线时间
288 小时
注册时间
2013-10-13
帖子
262

开拓者

12
发表于 2013-10-16 21:00:23 | 只看该作者
很久之前好像看过类似的讲脚本的视频- -【RPGXP视频教学】脚本不是高手的专利
最近记性不太好= =...
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
50
在线时间
128 小时
注册时间
2013-9-9
帖子
121
13
发表于 2013-10-18 00:28:36 | 只看该作者
支持,对我非常有帮助,已经全部复制到学习机的电子书里晚上睡觉之前看,一定能记住。楼主要加油更新呀,我会努力学习的!

点评

明白~  发表于 2013-10-19 01:01
感谢支持!另外光记住是不够的,必要的时候写一写脚本,才能有更深刻的体会。  发表于 2013-10-18 08:19
回复 支持 反对

使用道具 举报

Lv4.逐梦者 (版主)

梦石
0
星屑
9532
在线时间
5073 小时
注册时间
2013-6-21
帖子
3580

开拓者贵宾剧作品鉴家

14
 楼主| 发表于 2013-10-19 12:11:17 | 只看该作者
本帖最后由 RyanBern 于 2015-8-1 19:53 编辑

[box=RoyalBlue]
第3章节:游戏对象的解读和应用
[/box]

       从这一章节开始我们要具体说说如何在原脚本的基础上改动脚本,甚至你可以自己做出一个脚本。如果你前面的概念不是很明白的话,没有关系,不过只要你关注这里的话,相信你一定能够成功写出一个脚本的。
3.1  战斗者(Game_Battler) 角色队伍(Game_Party)
3.1.1  Game_Battler Game_Actor (角色类)Game_Enemy(敌人类)
       作为可以出现在战斗中的对象,RGSS将角色和敌人分别定义,再根据他们的共同性质(比方说都有HP,都可以进行技能伤害等等)定义出一个父类Game_Battler。那我们先翻开Game_Battler,看看这里面的结构究竟是什么。
RUBY 代码复制
  1. attr_reader   :battler_name             # 战斗者 文件名
  2.   attr_reader   :battler_hue              # 战斗者 色相
  3.   attr_reader   :hp                       # HP
  4.   attr_reader   :sp                       # SP
  5.   attr_reader   :states                   # 状态
  6.   attr_accessor :hidden                   # 隐藏标志
  7.   attr_accessor :immortal                 # 不死身标志
  8.   attr_accessor :damage_pop               # 显示伤害标志
  9.   attr_accessor :damage                   # 伤害值
  10.   attr_accessor :critical                 # 会心一击标志
  11.   attr_accessor :animation_id             # 动画 ID
  12.   attr_accessor :animation_hit            # 动画 击中标志
  13.   attr_accessor :white_flash              # 白色屏幕闪烁标志
  14.   attr_accessor :blink                    # 闪烁标志
  15.   #--------------------------------------------------------------------------
  16.   # ● 初始化对像
  17.   #--------------------------------------------------------------------------
  18.   def initialize
  19.     @battler_name = ""
  20.     @battler_hue = 0
  21.     @hp = 0
  22.     @sp = 0
  23.     @states = []
  24.     @states_turn = {}
  25.     @maxhp_plus = 0
  26.     @maxsp_plus = 0
  27.     @str_plus = 0
  28.     @dex_plus = 0
  29.     @agi_plus = 0
  30.     @int_plus = 0
  31.     [url=home.php?mod=space&uid=292300]@Hidden[/url] = false
  32.     @immortal = false
  33.     @damage_pop = false
  34.     @damage = nil
  35.     @critical = false
  36.     @animation_id = 0
  37.     @animation_hit = false
  38.     @white_flash = false
  39.     @blink = false
  40.     @current_action = Game_BattleAction.new
  41.   end

我们先看attr属性定义的部分。
battler_name和battler_hue是战斗图的文件名和色相,这个大家一般不用管,都是在数据库里面设置好的。
states指的是当前战斗者被附加上的状态的ID,是一个数组。
hidden和immortal指的是敌人的专有属性,分别是是否隐藏(就是中途出现)和不死之身标志,这个也是在数据库定义的。虽然定义在了Game_Battler里面,但是如果是Game_Actor的实例,使用这两个属性是无效的。
damage是别的战斗者对自身造成的伤害,注意,并非自己的某种战斗行动给他人造成的伤害。
       我们再看initialize方法部分。
       我们会发现,这里面多了几个没有声明成属性的实变量。证明这些变量是没有公开的。
       @states_turn是一个Hash,里面存放的是当前具有的状态距离自动解除还需要的回合数。这里用Hash表而不用数组是因为考虑到一个战斗者身上的状态毕竟是少数,用Hash可以更加灵活。比方说1号状态经过3回合才能进行自动解除(注意,自动解除并不是真正解除,而是数据库中的这个部分:
),
那么@states_turn[1]的值就是3。这样每过一个回合,所有状态的剩余回合都-1,达到0的进行自动解除。另外,如果这里的一个主键的值是-1,那么它是一个自动状态(就是防具附加的状态),这种状态是不进入自动解除的,除非你卸下防具。
       @maxhp_plus,@maxsp_plus等6个属性值的是各种能力值的加成,这些变量也是针对角色来说的,主要是在用事件增加角色的能力值或者用道具增加能力值的时候,储存增加的量。注意这些值和等级是没有关系的。
       我们来看看下面的几个方法。
       细心的朋友可能发现,在这个类别中,没有写类似于attr_accessor :maxhp这样的东西,这是因为attr_accessor只是定义简单读取和写入操作,并没有其它的功能。而我们知道,一个角色的maxhp,取决于他的等级,状态,能力值加成等等,如果只是简单定义attr_accessor :maxhp,这样对maxhp进行更改就会变得很复杂,倒不如直接一次性取得所有的因素。
RUBY 代码复制
  1. def maxhp
  2.     n = [[base_maxhp + @maxhp_plus, 1].max, 999999].min
  3.     for i in @states
  4.       n *= $data_states[i].maxhp_rate / 100.0
  5.     end
  6.     n = [[Integer(n), 1].max, 999999].min
  7.     return n
  8.   end

       这里我们可以看到,获取maxhp的第一步,是计算基础maxhp和加成maxhp的和,再进行范围修正。第二步是进行状态的修正,我们知道,在数据库中的状态一栏,我们可以通过状态来改变maxhp值。最后那个n = [[Integer(n),1].max,999999].min表示先把n变成整数,然后再进行属性修正。
       在这里,我们发现在第二行有个base_maxhp,很多人会好奇这是个什么东西,不是实变量,在Game_Battler里面也找不到相应的方法,更不可能是局部变量。其实,这个东西确实是一个方法,但是不是在Game_Battler中,而是在它的两个子类Game_Actor和Game_Enemy中,因为两个子类对应的方法不一样,所以要分开定义。这就意味着虽然这个方法看似在这个类中没出现,但是如果我们在其子类中定义了它,则仍然可以在子类中使用。在后面我们会看到它的庐山真面目。
RUBY 代码复制
  1. def make_action_speed
  2.     @current_action.speed = agi + rand(10 + agi / 4)
  3.   end

       这个方法的作用是决定战斗者的行动速度,即速度越大的越先行动。agi是战斗者的速度,后面那个是上下浮动的随机分散值。这就说明了一个战斗者速度对行动先后的重要性。这也能说明,速度大的战斗者行动的顺序很可能排到前面(受到后面浮动随机分散的影响)。因此我们如果要更改这种排序模式,改变这个方法就OK。
       然后就是这个增加状态的方法。
RUBY 代码复制
  1. def add_state(state_id, force = false)
  2.     # 无效状态的情况下
  3.     if $data_states[state_id] == nil
  4.       # 过程结束
  5.       return
  6.     end
  7.     # 无法强制附加的情况下
  8.     unless force
  9.       # 已存在的状态循环
  10.       for i in @states
  11.         # 新的状态和已经存在的状态 (-) 同时包含的情况下、
  12.         # 本状态不包含变化为新状态的状态变化 (-)
  13.         # (ex : 战斗不能与附加中毒同时存在的场合)
  14.         if $data_states[i].minus_state_set.include?(state_id) and
  15.            not $data_states[state_id].minus_state_set.include?(i)
  16.           # 过程结束
  17.           return
  18.         end
  19.       end
  20.     end
  21.     # 无法附加本状态的情况下
  22.     unless state?(state_id)
  23.       # 状态 ID 追加到 @states 序列中
  24.       @states.push(state_id)
  25.       # 选项 [当作 HP 0 的状态] 有效的情况下
  26.       if $data_states[state_id].zero_hp
  27.         # HP 更改为 0
  28.         @hp = 0
  29.       end
  30.       # 所有状态的循环
  31.       for i in 1...$data_states.size
  32.         # 状态变化 (+) 处理
  33.         if $data_states[state_id].plus_state_set.include?(i)
  34.           add_state(i)
  35.         end
  36.         # 状态变化 (-) 处理
  37.         if $data_states[state_id].minus_state_set.include?(i)
  38.           remove_state(i)
  39.         end
  40.       end
  41.       # 按比例大的排序 (值相等的情况下按照强度排序)
  42.       @states.sort! do |a, b|
  43.         state_a = $data_states[a]
  44.         state_b = $data_states[b]
  45.         if state_a.rating > state_b.rating
  46.           -1
  47.         elsif state_a.rating < state_b.rating
  48.           +1
  49.         elsif state_a.restriction > state_b.restriction
  50.           -1
  51.         elsif state_a.restriction < state_b.restriction
  52.           +1
  53.         else
  54.           a <=> b
  55.         end
  56.       end
  57.     end
  58.     # 强制附加的场合
  59.     if force
  60.       # 设置为自然解除的最低回数 -1 (无效)
  61.       @states_turn[state_id] = -1
  62.     end
  63.     # 不能强制附加的场合
  64.     unless  @states_turn[state_id] == -1
  65.       # 设置为自然解除的最低回数
  66.       @states_turn[state_id] = $data_states[state_id].hold_turn
  67.     end
  68.     # 无法行动的场合
  69.     unless movable?
  70.       # 清除行动
  71.       @current_action.clear
  72.     end
  73.     # 检查 HP 及 SP 的最大值
  74.     @hp = [@hp, self.maxhp].min
  75.     @sp = [@sp, self.maxsp].min
  76.   end
  77.   def states_plus(plus_state_set)
  78.     # 清除有效标志
  79.     effective = false
  80.     # 循环 (附加状态)
  81.     for i in plus_state_set
  82.       # 无法防御本状态的情况下
  83.       unless self.state_guard?(i)
  84.         # 这个状态如果不是 full 的话就设置有效标志
  85.         effective |= self.state_full?(i) == false
  86.         # 状态为 [不能抵抗] 的情况下
  87.         if $data_states[i].nonresistance
  88.           # 设置状态变化标志
  89.           @state_changed = true
  90.           # 附加状态
  91.           add_state(i)
  92.         # 这个状态不是 full 的情况下
  93.         elsif self.state_full?(i) == false
  94.           # 将状态的有效度变换为概率、与随机数比较
  95.           if rand(100) < [0,100,80,60,40,20,0][self.state_ranks[i]]
  96.             # 设置状态变化标志
  97.             @state_changed = true
  98.             # 附加状态
  99.             add_state(i)
  100.           end
  101.         end
  102.       end
  103.     end
  104.     # 过程结束
  105.     return effective
  106.   end

       这是将战斗者自身增加状态的两个方法,第二个方法调用了第一个方法,而第一个方法是增加单个状态ID为state_id的状态,先是判断状态是否无效(即定义到了$data_states之外),然后是判断战斗者身上有没有抵抗这种状态的另一种状态(例如,“战斗不能”状态会解除所有其他的状态,如果给一个“战斗不能”的战斗者附加别的状态,不会成功),如果有则附加无效,然后是附加这个状态,并且产生一定的效果,然后对@state进行排序,排序的依据是状态的优先级,也就是下图:

       第二个方法是真正的增加状态方法,可以增加一组状态,并且有成功率判定的条件。先是判定是否防御了这个状态(如果角色穿上了相应装备就会进入这一过程),如果不能防御,看是否状态是“不能抵抗”,否则按照ABCDEF的有效度进行判定。A是1,B是2,等等(注意,F1中对此处的解释有误!)。因此,如果想改ABCDEF代表的成功率,就要改下面这一行:
RUBY 代码复制
  1. if rand(100) < [0,100,80,60,40,20,0][self.state_ranks[j]]

       比方说把A改成90%就把数组第二个数100改成90,就这么简单。
       接下来我们看Game_Battler的分割定义3,这个脚本整体说的是攻击效果的定义,包括技能能否使用的判断,技能效果,普通攻击效果,物品效果。因此,论坛上很多人问到,如何进行自定义攻击效果设置,其实改的都是这里。在这里我们举两个例子:
例1:技能需要消耗一定的道具
       这个问题已经很老了,相信大家也知道怎么处理,不过还是提一下。Game_Battler分割定义3的第一个函数就是判断技能能否使用,里面列出了各种情况,我们只要增加我们自定义的判定模式即可。比方说1号技能要消耗3个2号道具,就在函数的第一行写下这一句话就可以:
RUBY 代码复制
  1. if skill_id == 1 and $game_party.item_number(2) < 3
  2.   return false
  3. end

       这样,1号技能就有了个特殊判断,当然,要在1号技能的公共事件上设置使用后消耗3个2号物品即可,这个相信大家都会做。
例2:技能伤害加成
       这个就是修改skill_effect方法就可以了,注意它的两个参数分别是使用者和特技,进行相应调整就可以。比方说,如果角色持有含属性[火]的武器,释放含有属性[火]的技能,则获得20%的伤害加成,就可以写(假如火属性的ID为1):
RUBY 代码复制
  1. if self.is_a?(Game_Actor) && $data_weapons[self.weapon_id].element_set.include?(1) && skill.element_set.include?(1)
  2.   self.damage += self.damage / 5
  3. end

       这里的第一个条件self.is_a?(Game_Actor)不能省略,因为对于敌人类(Game_Enemy)来讲,是没有weapon_id这个属性的,会引发NoMethodError。这个is_a?是判断某个实例是否属于某个类的函数,对任意的实例都有效。例如:
RUBY 代码复制
  1. class A
  2. end
  3. class B < A
  4. end
  5. class C < A
  6. end
  7. ryan = B.new
  8. ryan.is_a?(A) #true 这里B类的实例当然属于A类
  9. ryan.is_a?(B) #true
  10. ryan.is_a?(C) #false ryan不是C类的实例

       这个语句希望大家能够熟练使用,很多判断没有这句话,似乎是很难完成的。后面的那些语法,参考F1就基本能够写出。

       接下来我们介绍一下Game_Actor,作为角色的类,有它的特殊性。
       我们看一下专属于Game_Actor类的变量。
RUBY 代码复制
  1. attr_reader   :name                     # 名称
  2.   attr_reader   :character_name           # 角色 文件名
  3.   attr_reader   :character_hue            # 角色 色相
  4.   attr_reader   :class_id                 # 职业 ID
  5.   attr_reader   :weapon_id                # 武器 ID
  6.   attr_reader   :armor1_id                # 盾 ID
  7.   attr_reader   :armor2_id                # 头防具 ID
  8.   attr_reader   :armor3_id                # 身体体防具 ID
  9.   attr_reader   :armor4_id                # 装饰品 ID
  10.   attr_reader   :level                    # 水平
  11.   attr_reader   :exp                      # EXP
  12.   attr_reader   :skills                   # 特技

       中间那个character_name和character_hue指的是角色行走图的文件名和色相,而不是战斗图的文件名和色相。在这里看出,默认系统里面,角色类才有装备,等级,经验的刻画。
我们再向下看,就会看到属于Game_Actor的各种方法。前面说到的base_maxhp也在其中。具体的我就不领着大家一个个说了,只举一个例子。
例:简单的装备附带技能系统
       这又是一个热门话题,不过很多人都解决过它。实际上,如果我们学习了这里,我们也能比较轻松地解决它,下面请看。(以下提供的解决办法还有很大提升空间,但是这个办法是最容易想到的办法)
步骤1:明确我们要做什么,如果给角色装备相应的装备,就让它学习相应的技能,卸下装备就遗忘相应的技能。注意:装备中的技能最好不要交叉(即两种不同种类的装备含有相同的技能),这样能让我们的系统简单一些,而且装备技能不可从其他渠道学习(即只能通过换上这种装备才能使用)。
步骤2:先要设定装备和技能的对应关系,有点像新数据库的构造,我们可以采用Hash表的方式构造一个数据库中无法设定的新数据。考虑到这个数据在全局都可以使用,应该定义成全局变量,而且定义在最外面的结构里面就可以了。
RUBY 代码复制
  1. $equipment_skill_table = {} #在这里定义的是Hash表
  2. $equipment_skill_table[[0,1]] = [1,2] #这里定义1号武器的附带技能为1号和2号技能
  3. # 注意,Hash表的主键是一个二元数组,第一个量表示装备种类,第二个量表示装备的ID,这样定义具有和系统的一致性。

步骤3:增加装备的同时增加技能。翻开Game_Actor,我们看到了下面的方法:
RUBY 代码复制
  1. def equip(equip_type, id)
  2.     case equip_type
  3.     when 0  # 武器
  4.       if id == 0 or $game_party.weapon_number(id) > 0
  5.         $game_party.gain_weapon(@weapon_id, 1)
  6.         @weapon_id = id
  7.         $game_party.lose_weapon(id, 1)
  8.       end
  9.     when 1  # 盾
  10.       if id == 0 or $game_party.armor_number(id) > 0
  11.         update_auto_state($data_armors[@armor1_id], $data_armors[id])
  12.         $game_party.gain_armor(@armor1_id, 1)
  13.         @armor1_id = id
  14.         $game_party.lose_armor(id, 1)
  15.       end
  16.     when 2  # 头
  17.       if id == 0 or $game_party.armor_number(id) > 0
  18.         update_auto_state($data_armors[@armor2_id], $data_armors[id])
  19.         $game_party.gain_armor(@armor2_id, 1)
  20.         @armor2_id = id
  21.         $game_party.lose_armor(id, 1)
  22.       end
  23.     when 3  # 身体
  24.       if id == 0 or $game_party.armor_number(id) > 0
  25.         update_auto_state($data_armors[@armor3_id], $data_armors[id])
  26.         $game_party.gain_armor(@armor3_id, 1)
  27.         @armor3_id = id
  28.         $game_party.lose_armor(id, 1)
  29.       end
  30.     when 4  # 装饰品
  31.       if id == 0 or $game_party.armor_number(id) > 0
  32.         update_auto_state($data_armors[@armor4_id], $data_armors[id])
  33.         $game_party.gain_armor(@armor4_id, 1)
  34.         @armor4_id = id
  35.         $game_party.lose_armor(id, 1)
  36.       end
  37.     end
  38.   end

       这样,以武器为例,我们在when 0这个结构层次上做如下改动:
RUBY 代码复制
  1. when 0
  2. if id == 0 or $game_party.weapon_number(id) > 0
  3.   $game_party.gain_weapon(@weapon_id, 1)
  4.   # 去掉被卸下装备的附带技能
  5. # 对应技能设定的情况下
  6.   if $equipment_skill_table.include?([0,@weapon_id])
  7. for skill_id in $equipment_skill_table[[0,id]]
  8.       self.forget_skill(skill_id)
  9. end
  10.   end
  11.   @weapon_id = id
  12.   $game_party.lose_weapon(id, 1)
  13.   # 增加新装备上的技能
  14.   # 对应技能设定的情况下
  15.   if $equipment_skill_table.include?([0,id])
  16. for skill_id in $equipment_skill_table[[0,id]]
  17.   self.learn_skill(skill_id)
  18. end
  19.   end
  20. end

       如法炮制剩下的几个when分支就OK了。
       再次提醒一下,$equipment_skill_table一定要先定义再使用,定义位置随意,但是最好定义在最外部的层次上(class保留字的外面)。
       关于Game_Enemy类的,没有什么多说的,但是我们也可以自定义一些内容。
       例如:能力值破限。在数据库中无法设置敌人的力量(str)为999以上,但是在这里我们就可以做到。如果要设置1号敌人的力量为1500,则找到base_str那一个函数,改写成:
RUBY 代码复制
  1. def base_str
  2.   return @enemy_id == 1 ? 1500 :$data_enemies[@enemy_id].str
  3. end

       利用条件表达式就可以轻松完成。不过要定义多个敌人能力值破限,就要定义一个自定义的数据库,然后再进行函数值返回,这里就不说了。
       我们向下看,可以知道,敌人的普通攻击是没有属性的,也没有附加状态的变化,用类似的方法,我们可以修改这部分的内容,这里就不说了。

       在这里我们留下第一个作业题目。请设定一个被动技能,它的效果是,学习该技能的角色会降低所有状态(不能抵抗的除外)的命中可能性(默认是20%),即如果原来角色对状态1的有效度是C(60%命中),则学习该技能后降为D(40%)。

3.1.2  角色队伍Game_Party
       这个也是我们经常修改的类别,下面是Game_Party的脚本开头。游戏里面Game_Party的实例是$game_party
RUBY 代码复制
  1. #--------------------------------------------------------------------------
  2.   # ● 定义实例变量
  3.   #--------------------------------------------------------------------------
  4.   attr_reader   :actors                   # 角色
  5.   attr_reader   :gold                     # 金钱
  6.   attr_reader   :steps                    # 步数
  7.   #--------------------------------------------------------------------------
  8.   # ● 初始化对像
  9.   #--------------------------------------------------------------------------
  10.   def initialize
  11.     # 建立角色序列
  12.     @actors = []
  13.     # 初始化金钱与步数
  14.     [url=home.php?mod=space&uid=236945]@gold[/url] = 0
  15.     @steps = 0
  16.     # 生成物品、武器、防具的所持数 hash
  17.     @items = {}
  18.     @weapons = {}
  19.     @armors = {}
  20.   end

       我们注意到,角色,金钱,步数是整个队伍共有的东西。而我们看到,道具,武器,防具却没有用attr来定义属性,因为物品之类的东西,RGSS中设置了上限(默认99),如果直接用attr来定义,则还需要进行范围修正的处理。由于没有定义相应的方法,因此用$game_party.items[1]这样的方法取得1号物品的个数是不可以的。也无法通过$game_party.gold = 100这样的语句修改金钱(因为gold属性只是attr_reader定义的,只能读取而不能写入)。
       $game_party.actors指的是当前队伍中所有的队员,注意,$game_party.actors可以看作是$game_actors的一个子集,随着角色信息的变化,二者的数据时同步的。由于系统限制,默认$game_party.actors中最大人数为4,如果要做一个队伍脚本的话,首先就要突破这个限制。
       我们向后看,定义的方法包括角色的添加和删除,各种队伍物品的添加和删除,最后还有确定角色目标的方法,这个都不难理解,需要的时候,甚至都可以翻过来参考,不过我还是建议大家把这些方法熟练记住。
       例如,现在要清除队伍中所有的物品。如果要是不知道脚本的话,那就麻烦大了,用事件的话是会累死人的。但是知道了队伍脚本,就不一样了,于是我们可以写:
RUBY 代码复制
  1. def clear_items
  2.   @items = {}
  3. end

       把这个方法定义在Game_Party的内部,然后在事件中插入脚本$game_party.clear_items即可。
       注意@items没有被外部化,因此用$game_party.items[id] = 0这样的语句是不可以的,重申!
       大家可能看到Game_Party里面的方法简单易懂,因此涉及到物品,金钱等变量时,一定要参考Game_Party。
RUBY 代码复制
  1. def add_actor(actor_id)
  2.     # 获取角色
  3.     actor = $game_actors[actor_id]
  4.     # 同伴人数未满 4 人、本角色不在队伍中的情况下
  5.     if @actors.size < 4 and not @actors.include?(actor)
  6.       # 添加角色
  7.       @actors.push(actor)
  8.       # 还原主角
  9.       $game_player.refresh
  10.     end
  11.   end

       上面的脚本说明,$game_party.actors中最多只能容纳4个成员,如果队伍人数已满,再添加角色是无效的,有很多人认为用事件的“添加队员”指令,就一定会成功,其实不然。如果想让队伍中的人数突破限制的话,就必须首先在这里做文章。当然改成5人战斗脚本光改这里肯定是不够的,我们必须要进行大量的整合,这里就不细说了。

3.1.3  地图角色Game_Character及它的两个子类Game_Player(角色)和Game_Event(事件)[/size]
这些脚本都是处理地图画面上角色以及NPC移动的类,我们先从Game_Character看起。
RUBY 代码复制
  1. attr_reader   :id                       # ID
  2.   attr_reader   :x                        # 地图 X 坐标 (理论坐标)
  3.   attr_reader   :y                        # 地图 Y 坐标 (理论坐标)
  4.   attr_reader   :real_x                   # 地图 X 坐标 (实际坐标 * 128)
  5.   attr_reader   :real_y                   # 地图 Y 坐标 (实际坐标 * 128)
  6.   attr_reader   :tile_id                  # 元件 ID  (0 为无效)
  7.   attr_reader   :character_name           # 角色 文件名
  8.   attr_reader   :character_hue            # 角色 色相
  9.   attr_reader   :opacity                  # 不透明度
  10.   attr_reader   :blend_type               # 合成方式
  11.   attr_reader   :direction                # 朝向
  12.   attr_reader   :pattern                  # 图案
  13.   attr_reader   :move_route_forcing       # 移动路线强制标志
  14.   attr_reader   :through                  # 穿透
  15.   attr_accessor :animation_id             # 动画 ID
  16.   attr_accessor :transparent              # 透明状态

       在这里我们看到了Game_Character的一些属性,这里的x和y相当于我们地图编辑器中的这个:
       是理论的坐标值,实际坐标值和理论坐标值有个换算关系,实际坐标 = 理论坐标 * 128,这个记住就好。我们写脚本的时候,理论坐标用得最多。Character_name和character_hue表示的是角色行走图的文件名和色相,这个和我们刚才讲Game_Actor里面的属性几乎是一样的,不过Game_Character作为角色行走图类,这个属性和Game_Actor的还是有本质区别。
       pattern指的是角色行走图的图案序号,这是个什么东西呢?我们知道角色走动的时候,会有踏步动画,一个角色行走图文件是4*4个图形,不同横行代表不同方向,同一横行的四个图案就是用来做成动画的,这个pattern就是指同一横行的图形号码。
       direction指的是行走图的朝向,在RGSS中,数字2、4、6、8分别代表下,左,右,上。这个怎么记忆呢?我们看看电脑上的数字小键盘,这个对应关系,正好是小键盘数字和方向的对应。
       接下来我们看下这里面的各种方法。RGSS有一套默认的角色移动规则,这类规则我们也不用做太多改变,已经是比较合理的了。因此下面的函数我们看看形式即可。
RUBY 代码复制
  1. def screen_z(height = 0)
  2.     # 在最前显示的标志为 ON 的情况下
  3.     if @always_on_top
  4.       # 无条件设置为 999
  5.       return 999
  6.     end
  7.     # 通过实际坐标和地图的显示位置来求得画面坐标
  8.     z = (@real_y - $game_map.display_y + 3) / 4 + 32
  9.     # 元件的情况下
  10.     if @tile_id > 0
  11.       # 元件的优先不足 * 32
  12.       return z + $game_map.priorities[@tile_id] * 32
  13.     # 角色的场合
  14.     else
  15.       # 如果高度超过 32 就判定为满足 31
  16.       return z + ((height > 32) ? 31 : 0)
  17.     end
  18.   end

       例如,我们看一下这个画面z坐标的方法。因为我们是调查的行走图在画面上的z坐标,而不是元件,因此我们不要随便引入优先级这个概念。首先,我们知道,事件选项里面有一个“总在最前面显示”,这种事件的z坐标是最高的。另外,我们要注意,表示角色的Game_Player是没有这个属性的,因此角色永远也不能成为画面最上层的东西。下面我们来看其他情况的z坐标。这里先计算了一下画面的y坐标,这是为什么呢?因为我们知道表示角色和事件的图形都有一定的高度,而在地图上,一个格子的大小是32*32,超出的部分就要向上下左右扩张,而我们知道,一个NPC图形的大小是32*48,RGSS中,默认把图形的中下方的32*32区域放在指定坐标的格子中,因此角色头部就会覆盖他上方的格子。

       比方说上图这种情况,我们要让y坐标大的角色z坐标也稍微大一点,这样可以覆盖他上方的角色图形。当然我们知道,角色图形可以是元件,那么元件的z坐标就和它的优先度有关。在最后一行我们可以看到,1个优先度=32个z坐标,也就是说,角色和事件图形的优先度是大于0小于1的,因此只要优先度超过1的元件,都会把事件盖住。
       Game_Character下面两页几乎都是写移动和跳跃规则的方法。因此,我们除了在事件中运用“设置移动路线”来让角色移动外,还可以直接用脚本来让角色运动。例如写$game_player.move_left就可以让主角左移动一格。

       然后我们看Game_Player。
       因为在主角移动时,画面会始终跟随主角移动,因此Game_Player里面多了个center(x,y)方法。这个大家看看就行了,没必要完全搞懂。
RUBY 代码复制
  1. def increase_steps
  2.     super
  3.     # 不是强制移动路线的场合
  4.     unless @move_route_forcing
  5.       # 增加步数
  6.       $game_party.increase_steps
  7.       # 步数是偶数的情况下
  8.       if $game_party.steps % 2 == 0
  9.         # 检查连续伤害
  10.         $game_party.check_map_slip_damage
  11.       end
  12.     end
  13.   end

       在这个方法中,我们终于知道了游戏为什么要记录主角的步数。很多人认为新工程里面主菜单显示“步数”完全是个多余之举,确实我也认为没有显示的必要。步数是用来生成遇敌计数和检查连续伤害用的,这里,地图用的连续伤害是每走2步就减去1%的HP,当然你可以对此进行更改。
RUBY 代码复制
  1. def refresh
  2.     # 同伴人数为 0 的情况下
  3.     if $game_party.actors.size == 0
  4.       # 清除角色的文件名及对像
  5.       @character_name = ""
  6.       @character_hue = 0
  7.       # 分支结束
  8.       return
  9.     end
  10.     # 获取带头的角色
  11.     actor = $game_party.actors[0]
  12.     # 设置角色的文件名及对像
  13.     @character_name = actor.character_name
  14.     @character_hue = actor.character_hue
  15.     # 初始化不透明度和合成方式子
  16.     @opacity = 255
  17.     @blend_type = 0
  18.   end

       在这里我们明白了,地图上角色的图形和队伍中第一名角色的图形是一致的。因此,我们完全可以把它改成别的。在Game_Party中我们可以增加“领队”这一属性,然后把中间的actor = $game_party.actors[0]换成actor = $game_party.leader即可。当然Game_Party的leader还需要定义。另外,我们经常遇到主角变更交通工具的情况,比方说航海的时候,主角图形则要显示为一艘船。这个我们纯用事件是无法做到的(我们可以通过改变队伍中第一个角色的行走图来获得这个效果,但是你打开菜单,会发现角色的脸谱也变成了一艘船,这个显然很不合理),我们仍然可以在这里进行改动。例如,设置一个航海中的标志,如果队伍在航海中,则直接设置@character_name和@character_hue即可。
       在Game_Player的update方法中,我们看到了输入的处理。
RUBY 代码复制
  1. case Input.dir4
  2. when 2
  3.   move_down
  4. when 4
  5.   move_left
  6. when 6
  7.   move_right
  8. when 8
  9.   move_up
  10. end

       这是对4方向输入的处理,用Input.dir4,游戏中还有一个对8方向的输入处理Input.dir8,增加了4个斜向的移动,1、3、7、9分别表示,左下,右下,左上,右上,还是跟小键盘的方位是一样的(注:F1再次出现错误,F1原来写的是数字1到8表示8方向输入,这是不对的,应该是1、2、3、4、6、7、8、9才对)。
RUBY 代码复制
  1. case Input.dir8
  2. when 1
  3.   move_lower_left
  4. when 2
  5.   move_down
  6. when 3
  7.   move_lower_right
  8. when 4
  9.   move_left
  10. when 9
  11.   move_upper_right
  12. when 6
  13.   move_right
  14. when 7
  15.   move_upper_left
  16. when 8
  17.   move_up
  18. end

       这个是修改后8方向是输入,大家可以试一下。
       关于Game_Xxxx类脚本我们就要说这些,大家有兴趣的话可以多看看脚本,会有更多意想不到的发现。

3.2  自制游戏对象
       看了这么多游戏对象的脚本,是不是有自制游戏对象的念头了呢?那我们在这一小节就说说如何自制属于你自己的游戏对象脚本。
3.2.1  起步
       首先要明白,你自己想自定义的游戏数据到底属不属于游戏对象,如果不属于(比方说你要建立一个数据库的类,这就不属于游戏对象),那么就不应该用游戏对象的思路。总的来说,随着游戏进行,随时都有改变可能,并且跟玩家进行直接互动的对象,大多都是游戏对象。下面我们来看一个例子:
例如,制作一个真实商店,要求是商店尽可能还原真实生活中的商店,有库存限制,你卖给这个商店的物品,商店也会原封不动显示出来。这就是一个游戏对象。
3.2.2  分析属性和方法
       下面我们要分析一下,我们建立的游戏对象应该有哪些内容和方法。
       还是以真实商店为例。
       我们需要的是商店的最大库存(所有商品的最大数量,为了方便起见,对于同一个商店,所有商品的最大库存都是一样的),以及商店里面各种商品的数量。除此以外,暂时还未想到其他的属性。
       真实商店的方法很简单,就是出货和进货,不过考虑到物品分为道具,武器,防具3种,我们可能要定义很多方法。
       暂时想到这么多,然后我们就可以开工了。
3.2.3  写出游戏对象脚本
       我们现在,就可以根据我们的分析,来写出真实商店脚本的游戏对象部分了。先是定义各种属性,考虑到道具,武器,防具的不同性质,我们把它们定义在三个表中。
RUBY 代码复制
  1. class Game_VisualShop
  2.   attr_reader :max_number
  3.   attr_reader :shop_goods_item
  4.   attr_reader :shop_goods_weapon
  5.   attr_reader :shop_goods_armor
  6. end

       接下来定义初始化:
RUBY 代码复制
  1. def initialize(max_number,items,weapons,armors)
  2.   @max_number = max_number
  3.   @shop_goods_item = {}
  4.   @shop_goods_weapon = {}
  5.   @shop_goods_armor = {}
  6.   for j in items
  7.     @shop_goods_item[j] = @max_number
  8.   end
  9.   for j in weapons
  10.     @shop_goods_weapon[j] = @max_number
  11.   end
  12.   for j in armors
  13.     @shop_goods_armor[j] = @max_number
  14.   end
  15. end

       这个初始化就完成了,我们注意,传进去的参数有4个,分别是库存最大数量,各种物品的数组,数组里面是物品的ID,我们这里用ID而不是用物品的实例,是为了模仿游戏主角背包的存储模式,这个一定要注意。然后就是把商店各种物品数量初始化为库存最大值。这个也是看个人需要。我们用的是Hash表来表示现有的库存,这个也是借鉴了Game_Party中对于队伍中各种物品武器防具的刻画。写到这里,我们可以考虑给真实商店增加一个变量,表示商店中可以显示库存为0的物品(其余的库存为0的物品就直接不显示)。这是因为你卖给这个商店的物品和这个商店原有的物品本质是不一样的。这是一个二维数组,含有3个一维数组,分别表示总显示的道具、武器、防具。因此在initialize的最后,加入
RUBY 代码复制
  1. @always_shown = []
  2. @always_shown[0] = @shop_goods_item.keys
  3. @always_shown[1] = @shop_goods_weapon.keys
  4. @always_shown[2] = @shop_goods_armor.keys

这个显然我们不用把它公开化,放在这里就可以了。
       然后是各种方法,也是非常简单的。
RUBY 代码复制
  1. def import(type,id,number)
  2.   case type
  3.   when 0
  4.     if @shop_goods_item[id] == nil
  5.       @shop_goods_item[id] = 0
  6.     end
  7.     @shop_goods_item[id] += number
  8.     if @shop_goods_item[id] > @max_number
  9.       @shop_goods_item[id] = @max_number
  10.     end
  11.     if @shop_goods_item[id] <= 0
  12.       @shop_goods_item[id] = 0
  13.       unless @always_shown[0].include?(id)
  14.         @shop_goods_item.delete(id)
  15.       end
  16.     end
  17.   when 1
  18.     …..
  19.   end
  20. end

       这是一个通用的商店进货的方法,type表示种类,0为道具,1为武器,2为防具。id是对应类型的id,number是数量。添加的时候,考虑到某物品的Hash个数可能不存在,因此要进行一步判断。然后再进行增加操作,并修改范围。如果最终个数是0并且不是永远显示的话,直接从相应Hash表中删除。这里只给出了道具部分的定义,武器和防具部分定义是类似的。
       有了这个东西,出货的方法就很容易写了。
RUBY 代码复制
  1. def export(type,id,number)
  2.   import(type,id,-number)
  3. end

       直接调用import方法,并将number取成负值即可。
       到这里我们所有的基本方法就定义完了,Game_VisualShop也告一段落,不过,现在它只是一个游戏对象,如何将它和玩家互动起来,我们还需要学习后面的内容。

       第3章的讲解就到这里,在下一章我们会将到窗口的使用,大家敬请期待吧。
回复 支持 反对

使用道具 举报

Lv3.寻梦者

火烧大神

梦石
0
星屑
1823
在线时间
942 小时
注册时间
2012-1-1
帖子
1777
15
发表于 2013-10-19 22:16:49 | 只看该作者
我不知道为什么不论是RGSS还是C语言,都是从计算加减乘除开始的,这样很容易让我们丧失继续学习下去的耐心,我们想要做的是游戏而非计算!

点评

连加减乘除都做不好的话,还想做什么呢?  发表于 2013-10-20 09:11
游戏无时无刻都在计算。况且电脑的另一个名字就叫“计算机”。  发表于 2013-10-19 23:13

火兔游戏官网上线啦!!
戳 >>> www.huotuyouxi.com <<<戳
回复 支持 反对

使用道具 举报

Lv4.逐梦者 (版主)

梦石
0
星屑
9532
在线时间
5073 小时
注册时间
2013-6-21
帖子
3580

开拓者贵宾剧作品鉴家

16
 楼主| 发表于 2013-10-20 08:25:03 | 只看该作者
火烧兔子 发表于 2013-10-19 22:16
我不知道为什么不论是RGSS还是C语言,都是从计算加减乘除开始的,这样很容易让我们丧失继续学习下去的耐心 ...

写游戏切忌急功近利,学脚本也是。只有先弄清楚基础知识,才能够深刻理解游戏的抽象数据类型。但是考虑到实际的运用价值,这些计算确实很难让人有学习下去的欲望。大概是大家对计算都熟悉得很吧,因此我这个帖子没有涉及很多计算和语法方面,而是侧重于理解游戏的数据结构。不过,如果要踏踏实实成为“脚本党”的话,计算方面的东西是马虎不得的。
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
50
在线时间
87 小时
注册时间
2011-5-18
帖子
37
17
发表于 2013-10-20 20:09:13 | 只看该作者
自称做游戏的,一般两类人,一是改素材的,利用现有引擎,几乎不懂脚本。
二是偏向做程序的,甚至直接用 C++全部自己写最基本的窗口渲染。

楼主这个文章我认真地看了,拨开了我心中的云雾。力挺!
我是一个完全没程序基础的,本来想学 C++ 或者 Java之类的,打发时间娱乐而已。
但苦于无法入门。非常偶然的机会发现,RMXP。并且发现 66这个好地方,
一大堆免费,并且直观的教程。
很多身边的朋友劝我先学 Java,其实我觉得不然,有兴趣才有动力。
先学高级的,再在漫长的过程中挖掘基础的,也是很好的。

短短两个月里,我自己也总结出不少菜鸟心得。要再等几个月我有完整作品了再一起发布。
最近在拆解 66 fan 的 RTAB,很长很复杂,但我决定把它完全拆开,
将各个优秀的功能分解成外挂脚本,方便"脚本党"的使用。
这几天卡在相机移动这个地方了,不过也快了。

期待楼主早点讲讲 RMXP里面 update 循环刷新的机制。
踏上寻找灵魂归宿之旅。
回复 支持 反对

使用道具 举报

Lv4.逐梦者 (版主)

梦石
0
星屑
9532
在线时间
5073 小时
注册时间
2013-6-21
帖子
3580

开拓者贵宾剧作品鉴家

18
 楼主| 发表于 2013-10-20 21:06:05 | 只看该作者
lucifer4223 发表于 2013-10-20 20:09
自称做游戏的,一般两类人,一是改素材的,利用现有引擎,几乎不懂脚本。
二是偏向做程序的,甚至直接用 C+ ...

感谢你的支持!一直以来,我认为掌握了脚本就是掌握了写游戏的主动权,否则始终也不明白游戏运行的机理。当然,我觉得很多人写脚本纯粹是兴趣所在,这也是我们能继续把游戏创作下去的动力。这个帖子本身难度不高,而且只要你看过F1的基础篇,就可以从这里收获点什么。

这个帖子,最适合一些渴望写脚本,有一些基本知识,但是写出的脚本总也通不过的朋友们。我想,职业写脚本的人毕竟是少数,大多数人也是一点点摸索,一点点理解,经过不断尝试——犯错的反复中,摸清RMXP的脾气。我其实就是这样过来的……

分解RTAB系统,这个真是很好的计划。早就听说RTAB是个神器,里面的任何一部分都有我们借鉴的机会,因此要加油啊。

我写脚本时间不算太短,但是游戏却一个也没做出来,这也算是遗憾吧,毕竟,业余脚本党专注于脚本,对真正完成一个游戏的事情还是欠缺很多动力的。
回复 支持 反对

使用道具 举报

Lv4.逐梦者 (版主)

梦石
0
星屑
9532
在线时间
5073 小时
注册时间
2013-6-21
帖子
3580

开拓者贵宾剧作品鉴家

19
 楼主| 发表于 2013-10-22 21:40:58 | 只看该作者
本帖最后由 RyanBern 于 2015-8-2 13:24 编辑

[box=RoyalBlue]
第4章节:窗口的使用
[/box]

       接下来我们终于要学习窗口和精灵了,这我想也是大家特别想知道的。因为在这之前,我们都是同抽象的数据打交道,难免有些枯燥,那么在这一章,我们就要把我们自己脑中所想,全部展现在画面中。下面我们就开始吧。
4.1  窗口的使用
4.1.1各种概念
       在最开始,我们要明白一个窗口到底是什么,它到底有哪些属性。因此,我们必须要先介绍下面几个类。
①rect:矩形的类
       顾名思义,这个类表示矩形,但这种东西是不能直接显示出来的。矩形是一种基本的类,它的作用在很多高级的类中体现尤其明显。矩形有四个属性,x坐标,y坐标,宽度,高度。其中x坐标和y坐标是相对而言的。
②viewport:视口的类
       这个类是一个比较难理解的类,我在最开始也没明白这是什么东西。在屏幕上生成可见的对象都必须要指定视口viewport。简单说来,你面前有一堵墙,墙上有一个窗户可以看到墙对面的东西,那么这个窗户就可以类比成视口,你只能看到视口内有的东西,视口之外,即使有内容存在,你是看不到的。
       我们举个简单的例子,大家打开脚本编辑器,在Window_BattleStatus中的initialize方法中,输入self.back_opacity = 0

       然后随便找一个640*480的战斗图片(注意大小是640*480),设置为一个地图的战斗图,然后进入战斗场景,画面会变成这样:

       下面的640*160的矩形区域全部都是黑色的,这就说明图片显示不全,只有上半部分显示了出来。我们说过图片是640*480的,因此出现这种情况是我们视口的设置问题。打开Spriteset_Battle,我们在前面发现了这个:

       我们看到生成战斗背景的视口是640*320,因此我们的640*480图片才显示不出来。
       因此,无论图片是什么,生成的可视对象都必须在视口之内。
       视口的提出为我们管理界面上的可显示元素提供方便,因为视口的移动,色相改变,闪烁只会影响视口内部的元素,而视口外部的元素不受影响。
③bitmap:位图的类
       我们可以认为这一个类专门表示图片,但是要注意,Bitmap对象只是一个数据而已,并不能直接作为显示在屏幕中的对象。所以,bitmap是没有x坐标和y坐标这一属性的,这个要千万注意。但是,作为游戏的一种数据,在游戏运行的时候,必须要进入到游戏的内存中,RGSS里面有个高速缓存,专门用来保存这些临时的图片,用的时候就要在这里读取。因为高速缓存空间有限,因此我们要学会将不用的bitmap释放掉。
       我们来看看bitmap里面都有什么方法。
       dispose:释放位图,即将不用的位图从高速缓存中释放掉,我们在写程序时,必须有及时释放的好习惯,当然如果一个位图被频繁的使用,就不用释放了。
       clear:清除位图全体,即将位图的内容变成一个空的“画布”
       blt(x,y,src_bitmap,src_rect,opacity):这个方法非常重要,大家一定要熟练掌握。这个方法的作用有点类似于两个位图之间的拷贝,意思就是将位图A指定矩形的内部内容拷贝到本位图中,放到指定坐标的位置。下图更加直观说明了这个事情。

       这样大家应该理解了吧。
       fill_rect(x,y,width,height,color)
       fill_rect(rect,color):上面两个都是在位图中绘制实心矩形,可以用矩形的各种信息,也可以直接把矩形放在参数中,要指定填充的颜色,利用这两个函数,我们可以在bitmap中绘制矩形,然后把它显示出来(要用到精灵)。朴素的HP条SP条就是这样描绘的。
       draw_text(x,y,width,height,str,align):在位图的指定区域描绘文字,align是对齐方式。这有点类似于Windows画图中的创建文字。这个大家也要熟练使用。F1中也说,这个处理要花费时间,尽量不要重复描绘字符串(意思就是只有在文字改变的时候才可描绘,比较省CPU,防止游戏卡顿)。
       这些方法大家一定要熟练掌握,下面我们正式开始窗口的学习。

4.1.2  窗口的基本知识 一般窗口的实现
       脚本编辑器中,所有窗口的父类Window没有给出,我们也无法得知它的代码,但是我们打开F1,搜索Window,就会了解Window类的方法和属性,这对我们来说是个很好的参考。
       窗口实际上是一种可以在屏幕上显示的图片,因此生成必须要指定视口。但是由于系统默认的缘故,不用额外加以设置。窗口以大量精灵构成,因此要有dispose方法。我们使用一个窗口完毕后,一定要把它释放掉。这个我们在介绍场景的时候会有说明。
       F1中将各种方法已经说得很详细,这个我就不用多说。不过还是强调几个地方吧。
       visible是可见与否的属性,如果你已经生成了一个窗口,但是不想显示它,就要用到visible属性,注意,隐藏不意味着释放。
       active是活动状态属性,主要出现在有光标的窗口中,处于活动状态的窗口可以接受命令,进行光标闪烁等,游戏不能同时处理2个以上活动的窗口(你没见过按一下方向键两个窗口跟着一起变的吧?),因此同一个场景中,活动的窗口至多1个。
       ox,oy是窗口内容原点的坐标,在这里我们可以把窗口看作一个小小的视口,里面的内容只能通过窗口显示出来。
       注意:游戏默认窗口内容显示的视口是窗口上下左右边界向内16个像素,因此窗口实际内容和窗口边界总有16个像素的间隔,这个就尽量不要更改了。
       作为所有Window的父类,Window里面可用的方法很少,我们来看看真正能作为显示窗口类的Window_Base:
先看initialize部分:
RUBY 代码复制
  1. def initialize(x, y, width, height)
  2.     super()
  3.     @windowskin_name = $game_system.windowskin_name
  4.     self.windowskin = RPG::Cache.windowskin(@windowskin_name)
  5.     self.x = x
  6.     self.y = y
  7.     self.width = width
  8.     self.height = height
  9.     self.z = 100
  10.   end

       这里,我们生成一个窗口对象要指定它的位置和大小,并且设定窗口外观(这里默认是数据库的窗口外观),最后是z坐标,窗口的高度。
       紧接着后面是个释放的方法,重定义:
RUBY 代码复制
  1. def dispose
  2.     # 如果窗口的内容已经被设置就被释放
  3.     if self.contents != nil
  4.       self.contents.dispose
  5.     end
  6.     super
  7.   end

       在这里我们看到,对窗口释放同时也释放了作为窗口内容显示的位图,这样的好处是让我们的操作变得简单。例如写@a.dispose这样的语句,不但释放了窗口本身,也释放了窗口内容位图,一举两得。
       后面是定义获取各种颜色的方法,这我就不多说了。
       刷新update:
RUBY 代码复制
  1. def update
  2.     super
  3.     # 如果窗口的外关被变更了、再设置
  4.     if $game_system.windowskin_name != @windowskin_name
  5.       @windowskin_name = $game_system.windowskin_name
  6.       self.windowskin = RPG::Cache.windowskin(@windowskin_name)
  7.     end
  8.   end

       这个update方法是刷新窗口,在RGSS中,有刷新作用的方法,原则上1帧调用一次,这个一定要注意。我们看到Window_Base的刷新很简单,就是要判定窗口外观的改变,并没有涉及内容的处理。因此我们在描绘内容时,尽量不要刷新它,我们刚才说到了draw_text需要花费时间,因此要避免每1帧都重新描绘文字。
       下面则是一些基本的描绘:
RUBY 代码复制
  1. def draw_actor_graphic(actor, x, y)
  2.     bitmap = RPG::Cache.character(actor.character_name, actor.character_hue)
  3.     cw = bitmap.width / 4
  4.     ch = bitmap.height / 4
  5.     src_rect = Rect.new(0, 0, cw, ch)
  6.     self.contents.blt(x - cw / 2, y - ch, bitmap, src_rect)
  7.   end

       角色图形的描绘,有3个参数,角色,描绘x坐标,描绘y坐标。
       第二行是取得角色的位图,把它存储在高速缓存中。用的模块方法RPG::Cache,这个大家自行F1就好。然后是设定显示的内容,我们不能把整个图片显示出来,只需要显示左上角那个正脸的图就可以了,因此后面3行全部在设置截取图片的位置,随后,用blt方法,把截取的位置原封不动传送到self.contents中,window的contents就是表示窗口内容的位图,这恰恰就是blt完成的工作:把一个位图里面指定位置内容传给另一个位图中。在窗口中描绘图片就是这么简单。
       下面我们再来看一个描绘文字的:
RUBY 代码复制
  1. def draw_actor_hp(actor, x, y, width = 144)
  2.     # 描绘字符串 "HP"
  3.     self.contents.font.color = system_color
  4.     self.contents.draw_text(x, y, 32, 32, $data_system.words.hp)
  5.     # 计算描绘 MaxHP 所需的空间
  6.     if width - 32 >= 108
  7.       hp_x = x + width - 108
  8.       flag = true
  9.     elsif width - 32 >= 48
  10.       hp_x = x + width - 48
  11.       flag = false
  12.     end
  13.     # 描绘 HP
  14.     self.contents.font.color = actor.hp == 0 ? knockout_color :
  15.       actor.hp <= actor.maxhp / 4 ? crisis_color : normal_color
  16.     self.contents.draw_text(hp_x, y, 48, 32, actor.hp.to_s, 2)
  17.     # 描绘 MaxHP
  18.     if flag
  19.       self.contents.font.color = normal_color
  20.       self.contents.draw_text(hp_x + 48, y, 12, 32, "/", 1)
  21.       self.contents.draw_text(hp_x + 60, y, 48, 32, actor.maxhp.to_s)
  22.     end
  23.   end

       描绘角色HP。这里面参数多了一个宽度width,大家注意观察就可以发现,在菜单中描绘HP,是有最大HP的,在战斗中则没有,就是由于二者宽度不同导致的。后面的draw_text大家看看,基本上都会用。在这里提示一下,draw_text能描绘字符串,而不能描绘数字本身,因此draw_text(0, 0, 32, 32, 250)这样来描绘数字250是行不通的,Object类有个方法叫做to_s,把本身转换为字符串,因此要写250.to_s,这样等同于"250"。
       另外,很多人被那些坐标还有宽度设置困扰,总也描绘不到合适的位置。这个其实也比较好解决,整个画面是640*480的,简单进行一些运算就会知道各种位置。还有,在窗口中进行的x,y都是指相对窗口内容位图的原点位置,并不是屏幕的位置,这个大家要注意。游戏默认字的高度是24个像素,一行的高度是32个像素,因此,如果5个默认大小的字连起来时,长度就是120(一个汉字的宽度=2个英文字母的宽度),因此根据这个就可以设定描绘文字矩形的宽度了。最后要注意,draw_text是不能换行的,如果文字太长,它会一直描绘下去,可能会出边界。

       有了这些知识,我们就可以来刻画一个最基本的窗口了。下面来小小测试一下。
RUBY 代码复制
  1. class Window_Test < Window_Base
  2.   def initialize
  3.     super(0,0,196,64)
  4.     self.contents = Bitmap.new(width – 32, height - 32)
  5.     refresh
  6.   end
  7.   def refresh
  8.     self.contents.clear
  9.     self.contents.draw_text(0,0,144,32,”RMXP is good”)
  10.   end
  11. end

       这样一个简单的窗口就定义好了。
       但是这样是无法在屏幕上显示的,要让它显示出来,通常要借助场景Scene的帮助。
       我们先学一点后面的东西,让我们的窗口显示出来。
       在Scene_Map的main函数里面添加这样的语句:

       然后再运行一下。效果如图。

       在这里我们改动了Scene_Map脚本,但是也很简单,先创建一个窗口,在场景结束后再释放它,就是这样。
       但是现在我们生成的窗口内容不能自动变化,如果我们要生成一个内容可能变化的窗口,这样就不行了。比方说,我们要在地图上显示角色的金钱,但是如果通过事件增加了队伍的金钱,窗口的内容是没有变化的。这就需要我们对update进行下改装,如果内容变更了,就重新描绘内容。我们知道draw_text是不适合反复调用的,因此我们只有在需要的时候,才能重新描绘窗口内容。首先要在refresh方法加上
RUBY 代码复制
  1. @gold = $game_party.gold

       然后我们修改update方法:
RUBY 代码复制
  1. def update
  2.   super
  3.   if $game_party.gold != @gold
  4.     refresh
  5.   end
  6. end

       这里,我们可以设置一个不公开的变量@gold,表示当前窗口描绘的金钱数量。然后对update进行重定义,让它能自动检查内容的变化。当然,我们也要对场景脚本进行改变,场景脚本里面有个update方法,在里面,我们加入对窗口的刷新@a.update,在这里我就不给出所有的代码了,作为练习,希望读者能写出一个在地图上显示金钱的窗口。
当然,这样简单的窗口,我们还是有方法能判断内容是否变更的,但是对于复杂的窗口,我们就无法判断了,因此,我们还需要掌握其他方法。

4.1.3  滚动窗口的实现
       下面我们学习如何使用滚动窗口。滚动窗口要用Window_Selectable。
       打开这个脚本,我们发现它是Window_Base的子类。
RUBY 代码复制
  1. #--------------------------------------------------------------------------
  2.   # ● 定义实例变量
  3.   #--------------------------------------------------------------------------
  4.   attr_reader   :index                    # 光标位置
  5.   attr_reader   :help_window              # 帮助窗口
  6.   #--------------------------------------------------------------------------
  7.   # ● 初始化对像
  8.   #     x      : 窗口的 X 坐标
  9.   #     y      : 窗口的 Y 坐标
  10.   #     width  : 窗口的宽
  11.   #     height : 窗口的高
  12.   #--------------------------------------------------------------------------
  13.   def initialize(x, y, width, height)
  14.     super(x, y, width, height)
  15.     @item_max = 1
  16.     @column_max = 1
  17.     @index = -1
  18.   end

       这里多了两个属性,光标位置(索引)和帮助窗口。考虑到很多滚动窗口(比方说物品和技能窗口)都要关联帮助窗口,因此需要设置这个属性。
       初始化也没什么特别的,多了2个内部的实变量@item_max和@colunm_max,分别是描绘项目的最大值和描绘列数的最大值。
       继续向下看,里面有计算窗口项目位置的各种方法。包括开头行,一页的最大项目数,一页的最大行数,都非常简单,这里就不说了。
       我们在这里看到,RMXP里面默认一行的高度是32,也就是说,上面的所有设置位置的方法,都是按照这个标准进行的。当然,这个功能并不完善,我们可以对Window_Selectable进行修改,让它能显示任意行高的项目。
       然后就是对输入的处理和光标矩形的更新,这里简单看看就可以了,不能完全理解也没有关系,只要记住这是干什么用的就好了。另外,Window_Selectable里面并没有给出光标明灭变化的方法,那个是在Window中定义的,只是我们看不到而已。
       下面我们以Window_Item为例,来看看滚动窗口该怎样制作。
RUBY 代码复制
  1. class Window_Item < Window_Selectable
  2.   #--------------------------------------------------------------------------
  3.   # ● 初始化对像
  4.   #--------------------------------------------------------------------------
  5.   def initialize
  6.     super(0, 64, 640, 416)
  7.     @column_max = 2
  8.     refresh
  9.     self.index = 0
  10.     # 战斗中的情况下将窗口移至中央并将其半透明化
  11.     if $game_temp.in_battle
  12.       self.y = 64
  13.       self.height = 256
  14.       self.back_opacity = 160
  15.     end
  16.   end
  17.   #--------------------------------------------------------------------------
  18.   # ● 获取物品
  19.   #--------------------------------------------------------------------------
  20.   def item
  21.     return @data[self.index]
  22.   end
  23.   #--------------------------------------------------------------------------
  24.   # ● 刷新
  25.   #--------------------------------------------------------------------------
  26.   def refresh
  27.     if self.contents != nil
  28.       self.contents.dispose
  29.       self.contents = nil
  30.     end
  31.     @data = []
  32.     # 添加项目
  33.     for i in 1...$data_items.size
  34.       if $game_party.item_number(i) > 0
  35.         @data.push($data_items[i])
  36.       end
  37.     end
  38.     # 在战斗中以外添加武器、防具
  39.     unless $game_temp.in_battle
  40.       for i in 1...$data_weapons.size
  41.         if $game_party.weapon_number(i) > 0
  42.           @data.push($data_weapons[i])
  43.         end
  44.       end
  45.       for i in 1...$data_armors.size
  46.         if $game_party.armor_number(i) > 0
  47.           @data.push($data_armors[i])
  48.         end
  49.       end
  50.     end
  51.     # 如果项目数不是 0 就生成位图、重新描绘全部项目
  52.     @item_max = @data.size
  53.     if @item_max > 0
  54.       self.contents = Bitmap.new(width - 32, row_max * 32)
  55.       for i in 0...@item_max
  56.         draw_item(i)
  57.       end
  58.     end
  59.   end
  60.   #--------------------------------------------------------------------------
  61.   # ● 描绘项目
  62.   #     index : 项目编号
  63.   #--------------------------------------------------------------------------
  64.   def draw_item(index)
  65.     item = @data[index]
  66.     case item
  67.     when RPG::Item
  68.       number = $game_party.item_number(item.id)
  69.     when RPG::Weapon
  70.       number = $game_party.weapon_number(item.id)
  71.     when RPG::Armor
  72.       number = $game_party.armor_number(item.id)
  73.     end
  74.     if item.is_a?(RPG::Item) and
  75.        $game_party.item_can_use?(item.id)
  76.       self.contents.font.color = normal_color
  77.     else
  78.       self.contents.font.color = disabled_color
  79.     end
  80.     x = 4 + index % 2 * (288 + 32)
  81.     y = index / 2 * 32
  82.     rect = Rect.new(x, y, self.width / @column_max - 32, 32)
  83.     self.contents.fill_rect(rect, Color.new(0, 0, 0, 0))
  84.     bitmap = RPG::Cache.icon(item.icon_name)
  85.     opacity = self.contents.font.color == normal_color ? 255 : 128
  86.     self.contents.blt(x, y + 4, bitmap, Rect.new(0, 0, 24, 24), opacity)
  87.     self.contents.draw_text(x + 28, y, 212, 32, item.name, 0)
  88.     self.contents.draw_text(x + 240, y, 16, 32, ":", 1)
  89.     self.contents.draw_text(x + 256, y, 24, 32, number.to_s, 2)
  90.   end
  91.   #--------------------------------------------------------------------------
  92.   # ● 刷新帮助文本
  93.   #--------------------------------------------------------------------------
  94.   def update_help
  95.     @help_window.set_text(self.item == nil ? "" : self.item.description)
  96.   end
  97. end

       这里的初始化大家应该都没问题了,然后在这里定义了一个方法item,返回当前光标所指的物品。这个方法是必要的,原因我们要到场景的地方再解释。最后就是refresh方法,用来描绘窗口内的所有内容。
       refresh里面大概分为以下几个层次。
       首先释放窗口内容,这样做的目的是节省内存。考虑到窗口内容可能比较多,用self.contents.clear反倒不如释放再重新生成来得直接。而且描绘的最大项目数也会随着队伍中物品的变化而变化,如果设置固定的contents大小,不便于后面的处理。
       接下来,就是制作窗口内容显示数据,把要显示的物品,武器,防具添加在一个@data数组里面,这是窗口的内部信息,没必要公开化。
       接下来就是描绘窗口内容,在窗口存在项目的时候,一个个描绘。
       描绘单个物品的方法draw_item(i)在后面定义,i指的是物品在@data中的索引,里面的结构清晰直观,大家看看就可以了。里面那个case语句希望大家熟练使用,case语句可以对实例进行类的判定,有了这个机制,写代码会比较方便。
       最后,定义刷新帮助文本的方法(Window_Selectable里面已经有说明)。
       怎么样?现在你可以写出有滚动光标的窗口了。

在这里我们留下一个小练习
       就是我们刚才提到的,Window_Selectable默认每一行高度是32,能否对其进行优化,让用户创建自定义行高的窗口,并且和原来的脚本不发生冲突?

4.2  一个滚动窗口的例子——真实商店
       不知道大家对之前介绍游戏对象的创建还有没有印象,在这一章节里面,我们要把商店的货物在窗口中描绘出来。如果忘了前面的内容的话,还请翻看下前面的帖子哦。
4.2.1  准备工作
       我们想想这个窗口要描绘出什么。肯定要描绘各种物品,它的剩余量和它的价格。我们利用Window_Selectable的滚动功能,来实现滚动处理。
4.2.2  Ruby代码
       有了上面的准备我们就可以写出代码了。
RUBY 代码复制
  1. class Window_Visual_ShopBuy < Window_Selectable
  2.   def initialize(shop_id)
  3.     super(0,128,368,480-128)
  4.     self.index = 0
  5.     @column_max = 1
  6.     @shop_current = $game_visual_shops[shop_id]
  7.     refresh
  8.   end
  9.   # 取得当前光标的物品
  10.   def item
  11.     return @data[self.index]
  12.   end
  13.   def refresh
  14.     # 内容被设置就释放,重新设置
  15.     if self.contents != nil
  16.       self.contents.dispose
  17.       self.contents = nil
  18.     end
  19.     # 设置各种项目数据
  20.     @data_items = []
  21.     @data_weapons = []
  22.     @data_armors = []
  23.     # 添加物品
  24.     for item_id in @shop_current.shop_goods_item.keys
  25.       @data_items.push($data_items[item_id])
  26.     end
  27.     # 排序按照物品ID
  28.     @data_items.sort!{|a,b| a.id – b.id}
  29.     # 添加武器
  30.     for weapon_id in @shop_current.shop_goods_weapon.keys
  31.       @data_weapon.push($data_weapons[item_id])
  32.     end
  33.     # 排序按照武器ID
  34.     @data_weapons.sort!{|a,b| a.id – b.id}
  35.     # 添加防具
  36.     for armor_id in @shop_current.shop_goods_armor.keys
  37.       @data_armors.push($data_armors[item_id])
  38.     end
  39.     # 排序按照物品ID
  40.     @data_armors.sort!{|a,b| a.id – b.id}
  41.     # 合并数组
  42.     @data = @data_items + @data_weapons + @data_armors
  43.     @item_max = @data.size
  44.     if @item_max > 0
  45.       self.contents = Bitmap.new(width - 32, row_max * 32)
  46.       for index in 0…@item_max
  47.         draw_item(index)
  48.       end
  49.     end
  50.   end
  51. end

       由于窗口显示的是三种不同的物品,因此设置数据要花一些时间,但是基本思路跟描绘道具一样,因为商店里面的物品在变化,因此不要一开始就创建bitmap。
       下面就是我们描绘物品的方法draw_item(index)了,注意,这个方法仍然定义在这个窗口内部,只不过是在这个帖子里面,写到外面了而已。
RUBY 代码复制
  1. def draw_item(index)
  2.   item = @data[index]
  3.   # 取得持有数量和库存
  4.   case item
  5.   when RPG::Item
  6.     number = $game_party.item_number(item.id)
  7.     number_left = @current_shop.shop_goods_item[item.id]
  8.   when RPG::Weapon
  9.     number = $game_party.weapon_number(item.id)
  10.     number_left = @current_shop.shop_goods_weapon[item.id]
  11.   when RPG::Armor
  12.     number = $game_party.armor_number(item.id)
  13.     number_left = @current_shop.shop_goods_armor[item.id]
  14.   end
  15.   # 设置颜色
  16.   if item.price <= $game_party.gold and number < 99 and number_left > 0
  17.     self.contents.font.color = normal_color
  18.   else
  19.     self.contents.font.color = disabled_color
  20.   end
  21.   # 开始描绘
  22.   x = 4
  23.   y = index * 32
  24.   rect = Rect.new(x, y, self.width - 32, 32)
  25.   self.contents.fill_rect(rect, Color.new(0, 0, 0, 0))
  26.   bitmap = RPG::Cache.icon(item.icon_name)
  27.   opacity = self.contents.font.color == normal_color ? 255 : 128
  28.   self.contents.blt(x, y + 4, bitmap, Rect.new(0, 0, 24, 24), opacity)
  29.   self.contents.draw_text(x + 28, y, 168, 32, item.name, 0)
  30.   self.contents.draw_text(x + 196, y, 80, 32, item.price.to_s, 2)
  31.   self.contents.draw_text(x + 276, y, 48, 32, “剩:”)
  32.   self.contents.draw_text(x + 308, y, 32, 32, number_left.to_s, 2)
  33. end

       最后别忘了定义update_help,这样就大功告成了。不过,我们暂时无法检验它的对错,有兴趣的朋友可以利用场景来检验一下。

       上图就是我们期待的效果,不过我们做的仅仅是左下角这个窗口哦,而且各种命令的处理还没有设定。

       窗口的学习我们告一段落,接下来我们将要学习场景的使用。大家就敬请期待吧。
回复 支持 反对

使用道具 举报

Lv4.逐梦者 (版主)

梦石
0
星屑
9532
在线时间
5073 小时
注册时间
2013-6-21
帖子
3580

开拓者贵宾剧作品鉴家

20
 楼主| 发表于 2013-10-29 17:51:39 | 只看该作者
本帖最后由 RyanBern 于 2015-8-24 11:57 编辑

[box=RoyalBlue]
第5章节:场景的使用(一)
[/box]

       场景的内容我们拆开来讲,第5章节主要说场景实现的一般过程,解读一些简单场景。第6章节我们主要是DIY一个我们自己想要的场景,并解读一个比较复杂的场景。
5.1  精灵的使用
       这个本来应该在上一章节就应该讲到的,但是写上一章的时候一时脑抽,忘记了,因此在这里补上吧。
5.1.1  精灵是什么
       所谓精灵(Sprite),就是一种能在屏幕上显示各种可见对象的类。例如我们上一章节提到的窗口,还有游戏地图显示的实现,都是靠精灵完成的。另外,拥有动画播放效果的精灵是RPG模块下的Sprite类,也是精灵的一种。在这里,我们只需要学习一下精灵的最基本使用方法就可以了,相信大家一定会举一反三的。
5.1.2  精灵的属性和方法
       既然是一种特殊的类,那我们有必要了解精灵的使用方法。翻开F1,输入 Sprite 搜素,我们便看到了精灵(Sprite)的原型。
       首先是类方法,生成一个新的精灵Sprite.new([viewport]),这里需要指定生成的视口Viewport,在第四章我们已经比较详细讲过视口的含义,和视口不同造成的显示区别。如果忘记了还请翻看19L的帖子哦。当然,这里viewport可以省略,这就默认精灵将直接显示在640*480的游戏窗口中。
       接下来是各种属性和方法:
  • dispose:方法,释放精灵,这个跟释放窗口的作用一样,如果一个精灵你不再使用它,那么需要把它从内存中释放。
  • bitmap:属性,作为精灵所显示的位图。注意,这个位图只是作为精灵显示的数据,实际显示在屏幕上的可能和这个位图略有不同。当然,在释放精灵之前,你有可能还要释放这个位图。释放的顺序则是先释放这个位图,再释放精灵本身。如果这个位图是利用Bitmap.new生成,则释放精灵之前,必须释放此位图;如果这个位图是利用RPG::Cache模块读取高速缓存,则可不必释放此位图。释放位图+精灵的代码一般写成这样:
    RUBY 代码复制
    1. sprite.bitmap.dispose
    2. sprite.dispose
  • src_rect:属性,传送位图的矩形,这个矩形的作用相当于“截取”原来位图的一部分,然后用精灵去显示。
  • visible:属性,精灵是否可见,如果想隐藏一个精灵而不是去释放它,请将visible置为false
  • ox,oy:属性,精灵原点坐标,我们可以把精灵占据的屏幕空间看作是一个矩形,默认这个原点在矩形的左上角。进行坐标变换和旋转的时候,需要参考这个原点。
  • zoom_x,zoom_y:属性,横向和纵向拉伸比例,浮点数。
  • angle:属性,表示逆时针旋转的角度,单位是度。

我们可以用下面几个图片来表示精灵的各种属性:

5.1.3  精灵的具体使用
       了解了精灵的各种属性,现在我们可以使用它了。
       我们要在屏幕上显示各种对象,需要借助Graphics模块。我们不必掌握Graphics的具体内容,在每个场景脚本中,已经在main方法中含有对Graphics模块的方法调用,在这里我们只需照葫芦画瓢就可以了。
       核心的方法,是通过Graphics.update完成的,我们翻开Scene类的脚本,也可以看到这一句。
       在Graphics.update之前,我们必须先生成一个精灵,然后Graphics会自动把它算在画面窗口里面显示的东西,然后通过调用update就可以显示出来了(同理,窗口对象也是,因为窗口本身就由大量精灵组成)。
       简单显示一个图片的代码如下:
RUBY 代码复制
  1. a = Sprite.new
  2. a.bitmap = RPG::Cache.picture("123.png") # 这里输入Graphics/Pictures下文件名
  3. loop do
  4.   Graphics.update
  5. end
  6. a.dispose # 跳出循环后释放,注意,这里利用RPG::Cache载入位图,可不必释放bitmap

       总结下过程就是设置——显示——释放,是不是很简单?

5.2  场景的简单使用
       下面我们就要来学习场景的使用了,这也是最重要的技术之一,希望大家能好好掌握。所谓场景,就是把一些游戏对象(如窗口,精灵)组合起来的一个综合画面,每一个场景都可以看作是一个小系统。可以反馈信息,接收信息,跟玩家互动。下面我们就来具体解读一下。
5.2.1  场景实现的一般步骤
       我们翻开任意一个场景脚本,都会发现开头有相似之处。先定义了main方法,有的是要定义initialize方法(有些则没有)。这个main方法是必要的,因为在前面我已经说过,Main组脚本就是不断调用场景的main方法来进行游戏的。而initialize是初始化场景类的某些具体的信息,而不是进行主处理,这点要记住。
       一般的场景main方法主要分3部分。
       设置(包括初始化)——(画面)刷新,更新——退出后的处理。
       以Scene_Title为例,下面是它的main方法。
RUBY 代码复制
  1. #--------------------------------------------------------------------------
  2.   # ● 主处理
  3.   #--------------------------------------------------------------------------
  4.   def main
  5.     # 战斗测试的情况下
  6.     if $BTEST
  7.       battle_test
  8.       return
  9.     end
  10.     # 载入数据库
  11.     $data_actors        = load_data("Data/Actors.rxdata")
  12.     $data_classes       = load_data("Data/Classes.rxdata")
  13.     $data_skills        = load_data("Data/Skills.rxdata")
  14.     $data_items         = load_data("Data/Items.rxdata")
  15.     $data_weapons       = load_data("Data/Weapons.rxdata")
  16.     $data_armors        = load_data("Data/Armors.rxdata")
  17.     $data_enemies       = load_data("Data/Enemies.rxdata")
  18.     $data_troops        = load_data("Data/Troops.rxdata")
  19.     $data_states        = load_data("Data/States.rxdata")
  20.     $data_animations    = load_data("Data/Animations.rxdata")
  21.     $data_tilesets      = load_data("Data/Tilesets.rxdata")
  22.     $data_common_events = load_data("Data/CommonEvents.rxdata")
  23.     $data_system        = load_data("Data/System.rxdata")
  24.     # 生成系统对像
  25.     $game_system = Game_System.new
  26.     # 生成标题图形
  27.     @sprite = Sprite.new
  28.     @sprite.bitmap = RPG::Cache.title($data_system.title_name)
  29.     # 生成命令窗口
  30.     s1 = "新游戏"
  31.     s2 = "继续"
  32.     s3 = "退出"
  33.     @command_window = Window_Command.new(192, [s1, s2, s3])
  34.     @command_window.back_opacity = 160
  35.     @command_window.x = 320 - @command_window.width / 2
  36.     @command_window.y = 288
  37.     # 判定继续的有效性
  38.     # 存档文件一个也不存在的时候也调查
  39.     # 有效为 @continue_enabled 为 true、无效为 false
  40.     @continue_enabled = false
  41.     for i in 0..3
  42.       if FileTest.exist?("Save#{i+1}.rxdata")
  43.         @continue_enabled = true
  44.       end
  45.     end
  46.     # 继续为有效的情况下、光标停止在继续上
  47.     # 无效的情况下、继续的文字显示为灰色
  48.     if @continue_enabled
  49.       @command_window.index = 1
  50.     else
  51.       @command_window.disable_item(1)
  52.     end
  53.     # 演奏标题 BGM
  54.     $game_system.bgm_play($data_system.title_bgm)
  55.     # 停止演奏 ME、BGS
  56.     Audio.me_stop
  57.     Audio.bgs_stop
  58.     # 执行过渡
  59.     Graphics.transition
  60.     # 主循环
  61.     loop do
  62.       # 刷新游戏画面
  63.       Graphics.update
  64.       # 刷新输入信息
  65.       Input.update
  66.       # 刷新画面
  67.       update
  68.       # 如果画面被切换就中断循环
  69.       if $scene != self
  70.         break
  71.       end
  72.     end
  73.     # 装备过渡
  74.     Graphics.freeze
  75.     # 释放命令窗口
  76.     @command_window.dispose
  77.     # 释放标题图形
  78.     @sprite.bitmap.dispose
  79.     @sprite.dispose
  80.   end

       我们看到,在注释“准备过渡”之前,进行的都是一些设置的工作,比如载入数据库,生成各种图片和窗口,在设置完毕之后,准备过渡到画面状态(此时画面上什么也没有),然后接着是一个无限循环loop do,按照一定顺序刷新各种东西,在这里我们看到了Graphics.update,刷新游戏画面,然后是Input.update,这里Input也是内部模块,用来处理输入用的,最后是一个单独的update,这个其实是该场景内部的刷新方法,需要我们在后面进行追加定义。刷新完毕后,判断场景是否已经被切换,即$scene变量是否在update之后发生改变。如果改变,退出无限循环,场景结束,进行各种事后处理(各种释放和过渡)。
       在这里,可能有些人注意到了,虽然这里的Sprite的位图是用RPG::Cache模块载入的,但是在释放精灵之前,仍然对此位图进行了释放操作。这似乎和我前面提到的释放原则不一致。在这里,我要仔细说明一下使用RPG::Cache模块的原则。首先,RPG::Cache模块存在的意义,就是将图片载入内存中,如果该图片需要重复被使用(或者被多个Sprite对象使用),那么将其放入缓存中可以节约内存,提高载入速度,所以RPG::Cache中的位图是可以不释放的。而对于一些使用频率较少的位图,则不必将其放入RPG::Cache中,或者是使用之后及时释放,以腾出更多空间来存储使用率更大的位图。
       整个过程清晰明了,需要我们做的,就是丰富这个骨架的内容。
       我们注意中间那个loop do的无限循环,原则上是1帧执行1次,因此里面放的方法都是定期需要重复执行的方法,这种方法通常我们命名为update(注意不是refresh),也就是说,放到这里的东西,都应该是我们需要反复刷新的,如果不需要反复刷新,则一般不要单独放进去(可以放到场景私有的update方法里,然后再进行判断)。

5.2.2  场景的具体实现
       接下来我们就可以解读一下场景的具体实现了。我们以Scene_Item为例,来具体说明一下。
RUBY 代码复制
  1. #--------------------------------------------------------------------------
  2.   # ● 主处理
  3.   #--------------------------------------------------------------------------
  4.   def main
  5.     # 生成帮助窗口、物品窗口
  6.     @help_window = Window_Help.new
  7.     @item_window = Window_Item.new
  8.     # 关联帮助窗口
  9.     @item_window.help_window = @help_window
  10.     # 生成目标窗口 (设置为不可见・不活动)
  11.     @target_window = Window_Target.new
  12.     @target_window.visible = false
  13.     @target_window.active = false
  14.     # 执行过度
  15.     Graphics.transition
  16.     # 主循环
  17.     loop do
  18.       # 刷新游戏画面
  19.       Graphics.update
  20.       # 刷新输入信息
  21.       Input.update
  22.       # 刷新画面
  23.       update
  24.       # 如果画面切换就中断循环
  25.       if $scene != self
  26.         break
  27.       end
  28.     end
  29.     # 装备过渡
  30.     Graphics.freeze
  31.     # 释放窗口
  32.     @help_window.dispose
  33.     @item_window.dispose
  34.     @target_window.dispose
  35.   end

       上面是Scene_Item的main方法,主要生成了3个窗口,帮助窗口,物品窗口,目标窗口。而最初,目标窗口是不可见的(因为你还没使用某个道具),而帮助窗口和物品窗口是互相关联的,必须设置好。然后就可以过渡了,整个过程非常清晰。
RUBY 代码复制
  1. def update
  2.     # 刷新窗口
  3.     @help_window.update
  4.     @item_window.update
  5.     @target_window.update
  6.     # 物品窗口被激活的情况下: 调用 update_item
  7.     if @item_window.active
  8.       update_item
  9.       return
  10.     end
  11.     # 目标窗口被激活的情况下: 调用 update_target
  12.     if @target_window.active
  13.       update_target
  14.       return
  15.     end
  16.   end

       这就是我们在上面说的,场景私有update方法的定义,这个方法定义非常重要,这决定了你的场景是否能和玩家互动。
       一般来说,update方法分为2部分,一是自动刷新,即无论玩家有没有操作,都必须刷新的对象;二是条件刷新,当玩家有一定操作时,进行刷新相应对象。执行的顺序是先自动刷新,再条件刷新,这个顺序不能变。我们在这里可以设置一个输入等待的机制,必须等待固定时间,才能接受玩家的输入(注意,自动刷新是一直进行的)。这种情况出现的时候有很多,比方说玩家按了一个按键,画面进行某种变换,变化持续时间是1秒(40帧),你不希望在画面变化的时候接受玩家的其他输入,这时候就有必要设置输入等待了。比方说  这里,我们先要在main方法内设置一个内部变量@wait_count,并初始化为0,表示等待计数的时间。然后在update的条件刷新之前,放上以下代码:
RUBY 代码复制
  1. if @wait_count > 0
  2.   @wait_count -= 1
  3.   return
  4. end

       这就表示在等待时间不为0的情况下,将等待时间减去1,注意,update方法是1帧调用一次,@wait_count等于多少,就意味着要等待多少帧。随后立即结束update方法,注意,那个return不能少,这个return是避免update进行条件更新的,因此在@wait_count大于0的情况下,系统无法接受玩家的输入。这个问题就被我们解决了。
       而在这里,条件刷新有两个地方,如果物品窗口被激活就调用update_item方法,如果目标窗口被激活就调用update_target方法。这个必须分开设置,因为不同状态下刷新的规则肯定是不一样的。另外还需要注意每一个分支下面,要有return,否则很可能出现在同一次update下进行两种以上更新的情况。
RUBY 代码复制
  1. #--------------------------------------------------------------------------
  2.   # ● 刷新画面 (物品窗口被激活的情况下)
  3.   #--------------------------------------------------------------------------
  4.   def update_item
  5.     # 按下 B 键的情况下
  6.     if Input.trigger?(Input::B)
  7.       # 演奏取消 SE
  8.       $game_system.se_play($data_system.cancel_se)
  9.       # 切换到菜单画面
  10.       $scene = Scene_Menu.new(0)
  11.       return
  12.     end
  13.     # 按下 C 键的情况下
  14.     if Input.trigger?(Input::C)
  15.       # 获取物品窗口当前选中的物品数据
  16.       @item = @item_window.item
  17.       # 不使用物品的情况下
  18.       unless @item.is_a?(RPG::Item)
  19.         # 演奏冻结 SE
  20.         $game_system.se_play($data_system.buzzer_se)
  21.         return
  22.       end
  23.       # 不能使用的情况下
  24.       unless $game_party.item_can_use?(@item.id)
  25.         # 演奏冻结 SE
  26.         $game_system.se_play($data_system.buzzer_se)
  27.         return
  28.       end
  29.       # 演奏确定 SE
  30.       $game_system.se_play($data_system.decision_se)
  31.       # 效果范围是我方的情况下
  32.       if @item.scope >= 3
  33.         # 激活目标窗口
  34.         @item_window.active = false
  35.         @target_window.x = (@item_window.index + 1) % 2 * 304
  36.         @target_window.visible = true
  37.         @target_window.active = true
  38.         # 设置效果范围 (单体/全体) 的对应光标位置
  39.         if @item.scope == 4 || @item.scope == 6
  40.           @target_window.index = -1
  41.         else
  42.           @target_window.index = 0
  43.         end
  44.       # 效果在我方以外的情况下
  45.       else
  46.         # 公共事件 ID 有效的情况下
  47.         if @item.common_event_id > 0
  48.           # 预约调用公共事件
  49.           $game_temp.common_event_id = @item.common_event_id
  50.           # 演奏物品使用时的 SE
  51.           $game_system.se_play(@item.menu_se)
  52.           # 消耗品的情况下
  53.           if @item.consumable
  54.             # 使用的物品数减 1
  55.             $game_party.lose_item(@item.id, 1)
  56.             # 再描绘物品窗口的项目
  57.             @item_window.draw_item(@item_window.index)
  58.           end
  59.           # 切换到地图画面
  60.           $scene = Scene_Map.new
  61.           return
  62.         end
  63.       end
  64.       return
  65.     end
  66.   end

       在这里我们终于看到了对各种输入的处理,知道了物品的使用效果和消耗,都是在update及其子方法里面实现的。注意,Window_Selectable中光标的移动不在此中,Window_Selectable内部的update上。这里大家粗略看一下就能明白大意,我就不多做介绍了。在这里注意refresh方法的调用,我们调用refresh方法,只是在窗口需要刷新的时候才进行调用。如果窗口需要刷新的原因是我们输入了某种指令,那么在指令的最后,一定要重新刷新窗口,否则一般不进行窗口内容再描绘的处理(因为这样太消耗时间了)。
       最后我们说一下带initialize方法的场景类。Scene类可以不用带initialize方法,但是在某些场合下,我们需要设置initialize方法。
       比方说Scene_Menu场景,就有initialize方法,这里的initialize方法非常简单。
RUBY 代码复制
  1. def initialize(menu_index)
  2.   @menu_index = menu_index
  3. end

       这是设置菜单初始光标位置,考虑到从不同场景退回Scene_Menu,菜单光标位置不同,才考虑加的这个内部变量。比方说从Scene_Item返回,要写$scene = Scene_Menu.new(0),这是因为物品选项在菜单的第一个位置上。其余的同理。

       场景类的基本知识就到这里,在第6章节,我们要亲自DIY一个场景,并解读一些复杂场景,大家敬请期待吧。
回复 支持 反对

使用道具 举报

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

本版积分规则

拿上你的纸笔,建造一个属于你的梦想世界,加入吧。
 注册会员
找回密码

站长信箱:[email protected]|手机版|小黑屋|无图版|Project1游戏制作

GMT+8, 2024-11-21 22:27

Powered by Discuz! X3.1

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表