Project1
标题: 【教程】【不定期更新】实用型XP脚本编写小技巧#13 [打印本页]
作者: RyanBern 时间: 2015-1-17 12:55
标题: 【教程】【不定期更新】实用型XP脚本编写小技巧#13
本帖最后由 RyanBern 于 2017-1-5 20:45 编辑
这个帖子记录了我在编写RMXP脚本时候学会的若干技巧,在编写脚本的时候我会把一些有价值的想法记录在这个帖子中,此帖会不定期更新。
此帖涉及到的技巧多实用性,没有特别高深的技术,应该很容易就能学会。不过,具体问题具体分析,这个教程里面的脚本一般不能直接拿过来实用,也只是提供了一个处理该类问题的思路,所以请不要问“这脚本怎么不能实现XX效果”这样的问题。
另外请大家多多提出批评意见。
技巧1:数据库内容扩展
【更新】2015.01.17
【摘要】默认的数据库提供的功能大概不能满足一些人的需要,如果你想要给一个特技定义“冷却时间”的属性,但是数据库并没有相应的设置区域,这时候该如何处理?是打开脚本编辑器直接设置固定值,还是使用外部文件存储这一设定?原则上说,这个本来是数据库的设定,只是数据库的功能不够而已,因此我们希望还可以在数据库中设置自己的自定义属性,而不是把数据编辑转移到脚本编辑器中。以下这个方法经过测试,稳定性较好,并且能满足多数需求。
【正文】
技巧2:额外信息的存储
【更新】2015.01.17
【摘要】在编写一些XP的脚本系统中,不可避免要涉及到数据的存储问题,特别是用户存档数据的存储。这类问题如果处理不好,那么写出来的脚本的兼容性会很差,而且修改也十分麻烦。这个方法利用默认脚本的一些结构,可以被用作比较简单的额外信息存储,并且实现起来也不是非常麻烦。
【正文】
技巧3:让Sprite和Window动起来
【更新】2015.01.17
【摘要】在各种场景中,我们希望我们界面中的元素运动起来,而不是静止地呆在原地。例如在菜单场景中,我们希望命令窗口从屏幕外飞入到场景中,要一个动态的效果。我们自然会想到可以通过更改坐标让它动起来。再比如我们需要一个图片淡出画面,而不是一下子从画面上消失,我们就会想通过更改不透明度来实现这个效果。原理是很简单的,但是怎么样处理才比较妥当呢?下面这个技巧通过定义了一些额外的接口,能够让Sprite和Window随心所欲地移动。
【正文】
技巧4:装备附带技能——简洁到了极致
【更新】2015.01.23
【摘要】装备附带技能是一个很久远的问题,要解决它也不是很困难。但是,实现装备附带技能的方法却非常重要。这个技巧实际上没有什么新鲜的东西,在上文中也不断提及。其核心就是“永远不要更改原始数据”。注意到了这一点,我们就能用很短的篇幅来完成装备附带技能的脚本。
【正文】
技巧5:事件和脚本结合——给地图事件做个计时器
【更新】2015.01.24
【摘要】相信一定有人想利用RMXP做一个类似于农场的游戏,希望达到的最基本效果是让地图上的某些事件随着游戏时间的变化而变化。例如,玩家在某一时刻种下一颗种子,然后游戏时间经过15分钟后,终于长出了果实,这就是一个简易的农场事件。由于这个效果涉及到了地图上的事件,因此在实现的过程中,必须要考虑地图事件的刷新机制。如果完全通过事件实现,制作起来比较麻烦,而且会带来一些效率上的问题。下面这个技巧,将事件和脚本配合使用,利用脚本制作核心的计时器功能,利用事件完成地图上的刷新,比较有参考价值。
【正文】
技巧6:二周目制作
【更新】2015.02.18
【摘要】二周目的设计在RPG游戏中是个很不错的设定,它增加了游戏的可玩性,适度延长游戏的时间。但是,RMXP默认的系统中不具有二周目/多周目的设定,因此我们要想办法在玩家一周目通关之后,选择“新游戏”进入游戏,就会进入二周目的剧情。一般来说,二周目的信息不会保存在存档中,因此需要借助其他文件来存储这个信息。这个技巧利用游戏原有数据库文件,巧妙地将二周目信息隐藏在其中。为我们制作游戏提供了新的思路。
【正文】
技巧7:多项选择
【更新】2015.03.19
【摘要】大家都知道,RMXP的事件指令的第二条就是“显示选择项”,当主角和NPC对话之后,NPC会让主角做个选择。但是这个事件指令的不好的地方就是它只能设置四个选择项,当选择多于这个数目时,就要想各种办法了。例如,通过反复使用“显示选择项”事件指令,牺牲2个选项槽,把这个选项改为“上一页”和“下一页”,然后再考虑多个选项之间的衔接问题。这样做不是很方便,也使得选项过于臃肿。如果单纯利用脚本,那么如何设置选择之后的分歧,也是一个问题。这个技巧运用了脚本事件相结合的办法,能够制作出使用起来比较方便的多项选择系统。
【正文】
技巧8:椭圆形状的blt
【更新】2015.06.28
【摘要】众所周知,Bitmap类的#blt方法有类似于截图传送的功能,它可以把一张bitmap的部分复制到另一个bitmap当中。这个方法很方便,但是它也有很多的拓展空间。比如说,blt方法只能截取矩形区域,而不能截取其他形状的区域。如果我们对区域有所要求的话,原始的blt就不能满足了。这个技巧为Bitmap类新定义了一个#ellipse_blt方法,它可以将传送源位图的一个椭圆区域截取到目标bitmap上。
【正文】
技巧9:地图事件数据的扩展
【更新】2015.06.29
【摘要】技巧9实际是对技巧1的进一步补充,在技巧1中,我们对数据库的功能进行了扩展。但是地图上的事件某种意义上讲也是游戏数据的一部分,那么如何在地图事件上附加额外信息呢?下面就要介绍两种附加信息的方法:利用事件的名字和事件当中的注释。
【正文】
技巧10:记录事件位置
【更新】2015.07.01
【摘要】RGSS1中,切换地图之后,重新进入该地图会导致事件的位置重置。究其原因,是因为游戏并没有储存各个地图的信息,而是在每次进入地图的时候,重新载入原始的地图数据,这导致事件归位。所以,我们必须要在主角移动之前将地图上的事件位置储存起来,这需要利用我们技巧2。当然,并不是所有事件都需要记录位置,我们利用技巧9来对特殊的事件进行设置,让RGSS系统知道哪些事件需要记住位置,哪些事件不需要。
【正文】
技巧11:另类的alias实现
【更新】2015.09.15
【摘要】在自己编写脚本中使用alias关键字,能够节省插件脚本的篇幅,还有减少脚本冲突的可能性的效果。在这个技巧里面,我则要介绍另一种alias的实现方式,利用这个方法,起到的效果和alias差不多,但是完全不用使用alias关键字,而且也不必想方设法给新方法起不同的名字了(从而达到防止Stack Level Too Deep的错误)。 注:本技巧参考了这里的帖子。
【正文】
技巧12:简单的数据库破限
【更新】2016.07.14
【摘要】众所周知 RMXP 对数据库元素的数量基本上都有 999 的上限,那么总有这个数量不够的情况。遇到这种情况我们该怎么办呢?你可能想到 DKRM,但是 DKRM 的资源不是那么好找,你也可能想到用脚本将多余的数据补上去,但是总觉得写起来很麻烦。这里介绍了一种在不改动编辑器的条件下轻松突破这个限制的一种办法,可能以前有人已经做过,不过我还是在这里更新一下吧。
【正文】
技巧13:简易召唤系统——我们当中出现了一个叛徒
【更新】2017.01.05
【摘要】这个技巧巧妙利用了敌人出现/隐身的设定,制作了一个简易的召唤系统。并且需要极少量的脚本代码,非常适合事件党学习(以及出成考场题目)。
【正文】
作者: chd114 时间: 2015-1-17 13:04
其实用- Module ML end
- Module ML::Skillcd
- Skill={
- 1=>2,#1号技能2回合CD
- }
- end
复制代码 和- Module ML end
- Module ML::Color
- Skill={
- 10=>4,#10号技能4号颜色
- }
- end
复制代码 什么的读取的时候用ML::Color::Skill[X]就好了吧?
作者: taroxd 时间: 2015-1-17 13:10
本帖最后由 taroxd 于 2015-1-17 15:03 编辑
<del>以下内容均为卖萌扯淡</del>
没记错的话 XP 修改内部脚本的话是需要 F12 guard 的吧
所以不管怎么说,我现在连 alias 关键字都完全不用了
class Module
new_name = :alias_method_without_F12_guard
old_name = :alias_method
unless method_defined? new_name
alias_method new_name, old_name
end
def alias_method(new_name, old_name)
unless method_defined? new_name
alias_method_without_F12_guard new_name, old_name
end
end
end
class Module
new_name = :alias_method_without_F12_guard
old_name = :alias_method
unless method_defined? new_name
alias_method new_name, old_name
end
def alias_method(new_name, old_name)
unless method_defined? new_name
alias_method_without_F12_guard new_name, old_name
end
end
end
---
看到 merge_party 好亲切~ 咳咳
https://rpg.blue/thread-367044-1-1.html
你的代码里也是考虑到了存档里不存在的情况(@reserved_parties ||= {}),不过这个东西实在是不需要每次都写啊- -
甚至根本不需要重定义 initialize。可以直接定义一个(可以私有)的方法 reserved_parties。
class Game_System
private
def reserved_parties
@reserved_parties ||= {}
end
end
这样之后每次访问都调用 reserved_parties 方法就可以了
作者: 你最珍贵 时间: 2015-1-18 19:45
希望有个怎么把脚本写得简便短小精悍的教程
作者: taroxd 时间: 2015-1-18 19:50
本帖最后由 taroxd 于 2015-1-18 19:59 编辑
关于窗口精灵的运动,@余烬之中 写过一个更加通用的:
https://github.com/ShadowMomo/Sm ... ter/Smomo%20Core.rb 中的 transition 方法
我也写过一个不那么通用的(没有上面那个通用,需要手工 update):
https://rpg.blue/thread-365971-1-1.html 中的 Taroxd::Transition 类。
总之,这个变化的模式不应该局限于 X 坐标和 Y 坐标。
---
另外,我记得坐标是不需要变成整数的。就算赋值为整数似乎也会在内部转成浮点数的。
作者: 英顺的马甲 时间: 2015-1-19 10:18
本帖最后由 英顺的马甲 于 2015-1-19 10:51 编辑
很多时候与其重定义method不如重定义变量
额...好吧,就放个例子
https://rpg.blue/forum.php?mod=r ... 990&pid=2575524
与其
class Game_Party
def item_number(id)
@items[@actors[0].id] ? (@items[@actors[0].id].include?(id) ? @items[@actors[0].id][id] : 0) : 0
end
def weapon_number(id)
@weapons[@actors[0].id] ? (@weapons[@actors[0].id].include?(id) ? @weapons[@actors[0].id][id] : 0) : 0
end
def armor_number(id)
@armors[@actors[0].id] ? (@armors[@actors[0].id].include?(id) ? @armors[@actors[0].id][id] : 0) : 0
end
def gain_item(id, n)
@items[@actors[0].id] = {} unless @items[@actors[0].id]
if item_id > 0
@items[@actors[0].id][id] = [[item_number(id) + n, 0].max, 99].min
end
end
def gain_weapon(id, n)
@weapons[@actors[0].id] = {} unless @weapons[@actors[0].id]
if id > 0
@weapons[@actors[0].id][id] = [[weapon_number(id) + n, 0].max, 99].min
end
end
def gain_armor(id, n)
@armors[@actors[0].id] = {} unless @armors[@actors[0].id]
if id > 0
@armors[@actors[0].id][id] = [[armor_number(id) + n, 0].max, 99].min
end
end
end
class Game_Party
def item_number(id)
@items[@actors[0].id] ? (@items[@actors[0].id].include?(id) ? @items[@actors[0].id][id] : 0) : 0
end
def weapon_number(id)
@weapons[@actors[0].id] ? (@weapons[@actors[0].id].include?(id) ? @weapons[@actors[0].id][id] : 0) : 0
end
def armor_number(id)
@armors[@actors[0].id] ? (@armors[@actors[0].id].include?(id) ? @armors[@actors[0].id][id] : 0) : 0
end
def gain_item(id, n)
@items[@actors[0].id] = {} unless @items[@actors[0].id]
if item_id > 0
@items[@actors[0].id][id] = [[item_number(id) + n, 0].max, 99].min
end
end
def gain_weapon(id, n)
@weapons[@actors[0].id] = {} unless @weapons[@actors[0].id]
if id > 0
@weapons[@actors[0].id][id] = [[weapon_number(id) + n, 0].max, 99].min
end
end
def gain_armor(id, n)
@armors[@actors[0].id] = {} unless @armors[@actors[0].id]
if id > 0
@armors[@actors[0].id][id] = [[armor_number(id) + n, 0].max, 99].min
end
end
end
不如class Bag
def initialize
@data = {}
end
def method_missing(name, *args, &block)
@data[$game_party.actors[0].id] ||= {}
begin
@data[$game_party.actors[0].id].send(name, *args, &block)
rescue Exception
raise($!.class, $!.message, caller)
end
end
end
class Game_Party
alias multi_bag_init initialize unless defined?(multi_bag_init)
def initialize
multi_bag_init
@items = Bag.new
@weapons = Bag.new
@armor = Bag.new
end
end
class Bag
def initialize
@data = {}
end
def method_missing(name, *args, &block)
@data[$game_party.actors[0].id] ||= {}
begin
@data[$game_party.actors[0].id].send(name, *args, &block)
rescue Exception
raise($!.class, $!.message, caller)
end
end
end
class Game_Party
alias multi_bag_init initialize unless defined?(multi_bag_init)
def initialize
multi_bag_init
@items = Bag.new
@weapons = Bag.new
@armor = Bag.new
end
end
作者: taroxd 时间: 2015-2-8 21:45
本帖最后由 taroxd 于 2015-2-9 07:46 编辑
第四个,其实 VA 的 skill_learn? 并没有考虑附加的技能。
这里有我稍稍做的改动(让怪物也有技能):http://taroxd.github.io/rgss/Tar ... AE%BE%E7%BD%AE.html
第五个,我会更倾向于这么做(删去了并行事件,效率更高,事件页更简洁美观):
# 事件脚本的调用方式:this_event.set_timer(3600, 'B')
# this_event 容易实现,因此不再赘述
# 其实我本来想用这种更优雅的方式:
# this_event.set_timer(3600) do
# self_switch.b = true
# end
# 但考虑到 block 不便存档,于是还是算了。
class Game_Event
# *args: 帧数, 独立开关对应的字母
def set_timer(*args)
self_switch['timer'] = args # self_switch 对象很容易实现,见 [url]http://taroxd.github.io/rgss/%E4%BA%8B%E4%BB%B6%E8%84%9A%E6%9C%AC%E6%89%A9%E5%B1%95.html[/url]
end
alias_method :timer_update, :update
def update
timer_update
args = self_switch['timer']
return unless args
if args[0] > 0
args[0] -= 1
else
self_switch[args[1]] = true
self_switch['timer'] = nil
end
end
end
# 事件脚本的调用方式:this_event.set_timer(3600, 'B')
# this_event 容易实现,因此不再赘述
# 其实我本来想用这种更优雅的方式:
# this_event.set_timer(3600) do
# self_switch.b = true
# end
# 但考虑到 block 不便存档,于是还是算了。
class Game_Event
# *args: 帧数, 独立开关对应的字母
def set_timer(*args)
self_switch['timer'] = args # self_switch 对象很容易实现,见 [url]http://taroxd.github.io/rgss/%E4%BA%8B%E4%BB%B6%E8%84%9A%E6%9C%AC%E6%89%A9%E5%B1%95.html[/url]
end
alias_method :timer_update, :update
def update
timer_update
args = self_switch['timer']
return unless args
if args[0] > 0
args[0] -= 1
else
self_switch[args[1]] = true
self_switch['timer'] = nil
end
end
end
作者: taroxd 时间: 2015-2-18 11:44
技巧6里面,自己测试游戏的时候保存一下游戏就没有了吧……还不如保存在另一个文件里呢。
作者: gonglinyuan 时间: 2015-2-19 16:32
很有营养的东西。。
不过对于技巧6最好能够提供把二周目存档放在另一个文件的做法(类似Global_save之类的),不然不仅影响了严谨性(把item拿来做存档),而且也不方便转移存档呢(比如把存档拷到另一台电脑发现二周目又变回了一周目)
作者: RyanBern 时间: 2015-3-19 15:22
更新技巧6、7,自顶。
技巧6是二周目的制作思路,虽然少用但是值得借鉴一下。
技巧7是一个多项选择插件的简易实现版,接入后再结合事件编辑器可以达到不错的效果,而且没有违和感。
借助这个思路,或许可以做出道具选择插件(即NPC向主角索要一个道具,主角选择道具之后对话继续)之类的效果。
作者: chd114 时间: 2015-7-18 13:32
RyanBern 发表于 2015-3-18 22:22
更新技巧6、7,自顶。
技巧6是二周目的制作思路,虽然少用但是值得借鉴一下。
多周目也同技巧6吧
作者: gonglinyuan 时间: 2015-9-15 08:15
关于技巧10:记录事件位置
我先前没有忘了这个帖子里有这个内容,搜了半天也没有搜到,只好自己写了一个。现在回来看看这个部分,对比了以下各种实现细节,发现这样实现好像还是有点问题的,就是如果一个事件【固定朝向】了之后turn_left、turn_right等方法会失效,导致事件的朝向不能被记录下来。
作者: 喵呜喵5 时间: 2015-9-15 14:02
技巧11 另类alias 在RMVA的Ruby1.9中能用吗?
作者: taroxd 时间: 2015-9-15 16:09
本帖最后由 taroxd 于 2015-9-15 16:29 编辑
在较新版本的 Ruby 中:
C = Class.new
class C < C.clone # 这里是打开 C 而不是覆盖常量 C,而 C.clone 不是原来 C 的父类 Object,因此会报错
end
# superclass mismatch for class C (TypeError)
C = Class.new
class C < C.clone # 这里是打开 C 而不是覆盖常量 C,而 C.clone 不是原来 C 的父类 Object,因此会报错
end
# superclass mismatch for class C (TypeError)
所以,要用这种方式的话,应该是这样的代码
class C
def c
1
end
end
class C < Object.send :remove_const, :C
def c
super + 1
end
end
class C
def c
1
end
end
class C < Object.send :remove_const, :C
def c
super + 1
end
end
然而这种方式不仅会让继承链变得很难看,而且很容易导致问题。
class Game_BattlerBase
# 默认脚本
end
class Game_Battler < Game_BattlerBase
# 默认脚本
end
# 若这一行是
# class Game_BattlerBase < Object.send :remove_const, :Game_BattlerBase
# 也一样会导致问题
class Game_Battler < Object.send :remove_const, :Game_Battler
# A 的插件脚本
end
class Game_Battler < Game_BattlerBase
# B 的插件脚本
end
# superclass mismatch for class Game_Battler (TypeError)
class Game_BattlerBase
# 默认脚本
end
class Game_Battler < Game_BattlerBase
# 默认脚本
end
# 若这一行是
# class Game_BattlerBase < Object.send :remove_const, :Game_BattlerBase
# 也一样会导致问题
class Game_Battler < Object.send :remove_const, :Game_Battler
# A 的插件脚本
end
class Game_Battler < Game_BattlerBase
# B 的插件脚本
end
# superclass mismatch for class Game_Battler (TypeError)
很明显,这个脚本冲突就是 A 的锅
不想发生命名冲突的话,应该是这样的方式比较好
class C
def c
1
end
end
class C
original_c = instance_method :c
define_method :c do
original_c.bind(self).call + 1
end
end
class C
def c
1
end
end
class C
original_c = instance_method :c
define_method :c do
original_c.bind(self).call + 1
end
end
接着推销一下这个:http://taroxd.github.io/rgss/Tar ... AE%BE%E7%BD%AE.html
当然,还是更高版本 Ruby 中的 prepend 和 refine 最舒服。
class C
def c
1
end
end
module MyPatchToC
def c
super + 1
end
end
C.prepend MyPatchToC
class C
def c
1
end
end
module MyPatchToC
def c
super + 1
end
end
C.prepend MyPatchToC
# 在我自己的脚本里,我就直接把 id 猴补到 Fixnum 里面了。
# 为了在某些场合既能传物品(装备、角色等等)id作为参数,也能传入物品的实例作为参数
# 其实我挺希望能这样用 refine 的
module MyPatchToFixnum
refine Fixnum do
alias_method :id, :to_int
end
end
class AnotherClass
using MyPatchToFixnum
def self.show(item_or_item_id)
puts item_or_item_id.id
end
end
# assume that $data_items exists
AnotherClass.show($data_items[1]) # 1
AnotherClass.show(1) # 1
1.id # undefined method `id' for 1:Fixnum (NoMethodError)
# 在我自己的脚本里,我就直接把 id 猴补到 Fixnum 里面了。
# 为了在某些场合既能传物品(装备、角色等等)id作为参数,也能传入物品的实例作为参数
# 其实我挺希望能这样用 refine 的
module MyPatchToFixnum
refine Fixnum do
alias_method :id, :to_int
end
end
class AnotherClass
using MyPatchToFixnum
def self.show(item_or_item_id)
puts item_or_item_id.id
end
end
# assume that $data_items exists
AnotherClass.show($data_items[1]) # 1
AnotherClass.show(1) # 1
1.id # undefined method `id' for 1:Fixnum (NoMethodError)
以及,如果不是做 RM 插件的话,这种技术的实际应用真的很少……
作者: 工藤~新一じ 时间: 2016-5-26 23:48
求不算挖坟。
技巧5的事件和脚本结合——给地图事件做个计时器解释效果是我想实现的
于是我重新开一个工程,把脚本放到Main之上
再这样做事件,发现没有效果啊
-
1.png
(78.13 KB, 下载次数: 24)
-
2.png
(82.42 KB, 下载次数: 27)
-
3.png
(78.6 KB, 下载次数: 24)
作者: jiushiainilip19 时间: 2016-7-15 14:07
技巧3 能够做到一个图片原地旋转的效果吗?{:2_249:}
作者: RyanBern 时间: 2016-7-16 13:22
当然可以,需要你定义一个rotate_effect,这个旋转效果是指定一个速度然后永远旋转下去,如果想要改成旋转一个固定角度则和前面的move_effect类似。
def rotate_effect(speed)
@rotate_speed = speed
if @rotate_speed == 0
@rotate_effect = false
return
end
@rotate_effect = true
end
def rotating?
return @rotate_effect
end
def rotate_effect(speed)
@rotate_speed = speed
if @rotate_speed == 0
@rotate_effect = false
return
end
@rotate_effect = true
end
def rotating?
return @rotate_effect
end
然后你需要自己定义update
def update
if self.rotating?
self.angle += @rotate_speed
end
end
def update
if self.rotating?
self.angle += @rotate_speed
end
end
代码未测试,可能有BUG,遇到笔误等问题请自行修改。
作者: 云海尘清 时间: 2016-8-1 11:46
关于技巧8:椭圆形状的blt
我还在学习脚本,对此有一些困难
能否发一个范例供学习
谢谢
作者: lcr 时间: 2017-8-25 15:06
关于技巧12:数据突破
是复制在什么地方 是复制在自己新建的地方吗? 不知道怎么用 我是小白 求大神详细
作者: RyanBern 时间: 2017-8-25 23:47
本帖最后由 RyanBern 于 2017-8-25 23:49 编辑
第一步:复制第一段脚本到脚本编辑器的任何地方。
第二步:新建一个事件,在事件指令中选择【脚本】,然后里面命令写入
set_item_max("CommonEvent", 1200)
set_item_max("CommonEvent", 1200)
即可修改公共事件的最大值为 1200。如果是别的数字请改动第二个参数。
第三步:启动游戏,并执行这个事件(执行的时候没有任何外在的效果,只要确认触发这个事件就行)
第四步:关闭游戏,关闭工程,重新载入工程即可看到效果。
作者: 笹舟丶萚 时间: 2018-7-16 09:16
- class Window_Choice < Window_Selectable
- def initialize
- x = 2
- super(0, 0, x, 248)
复制代码
那个,新人问一下,多个选项脚本里这么写为啥出错啊
欢迎光临 Project1 (https://rpg.blue/) |
Powered by Discuz! X3.1 |