Project1

标题: 终于把XP改成了VA [打印本页]

作者: SailCat    时间: 2022-2-9 01:42
标题: 终于把XP改成了VA
本帖最后由 SailCat 于 2022-2-9 01:47 编辑

一个春节假期的成果……




让XP的代码风格变成这个样子,我还是非常欣慰的,当然,个中甘苦自不必言
但是从接口上来说,至少是把VA的Features(VX向VA最重要的修改,没有之一)复刻了九成以上

花了两天时间思考方案,然后实现出来以后,我大概已经看到了后面产出插件的速度会非常之快了
作者: guoxiaomi    时间: 2022-2-9 02:44
猫姐怎么看va里SceneManager和Scene_Base的设计(包括无处不在的Fiber)?我总觉得不太直观,每次找某个scene的执行流程都要翻半天……
作者: SailCat    时间: 2022-2-9 08:07
本帖最后由 SailCat 于 2022-2-9 09:34 编辑
guoxiaomi 发表于 2022-2-9 02:44
猫姐怎么看va里SceneManager和Scene_Base的设计(包括无处不在的Fiber)?我总觉得不太直观,每次找某个sce ...


Fiber在事件解释器没用好,导致了VA并行事件执行30帧一秒的bug,而且跳帧的时候抓不到按键输入,在对话窗口还可以,XP没有Fiber,搞那些挂起等待非常之awkward,几乎所有都是一点一点的漏斗式return

Scene_Base的设计其实很好,自己写界面的时候,就知道有没有一套公用的处理方法的区别了(而且VX就有这个了),能省很多事情。
但是这个Scene_Base概括的实在太过于抽象了,我刚上手VA的时候也是这个问题,非常不明白,为什么update->update_basic要套两层,真的就是打底裤穿了两层那种感觉。惟一一个重定义了update_basic的战斗场景,里面还带了super,那你套两层真的是闲得啊

但SceneManager的设计我觉得就一般,主要是SceneManager里面的一套逻辑并不适用于RPG游戏的常见场景切入方式,大部分时候只是在SceneManager.goto而很少会用到call。而大部分都是goto的话,你要Manager提供的场景堆栈干嘛使呢
因为我自己也写了个SceneManager,最后把游戏场景串起来的时候,发现结果和VA一样,绝大部分时候还就是$scene=xx(XP/VX的样式)就完了。

关于Scene_Base,XP因为不能改变场景的继承关系(但凡我把所有的class Scene后面都写上小于号,原来的引擎就完全废了,基于原来引擎的其他插件也全废了),这里只能是用Module实现
XP的场景实际上都只定义了main,一个巨大的,包括了全流程的main,而我只要把main给抽象出来就行了。
RUBY 代码复制
  1. #==============================================================================
  2. # ■ Scene_Base
  3. #------------------------------------------------------------------------------
  4. #   游戏中全部场景的公用模块。
  5. #==============================================================================
  6. module Scene_Base
  7.   #--------------------------------------------------------------------------
  8.   # ● 主处理
  9.   #--------------------------------------------------------------------------
  10.   def main
  11.     return unless need_create?
  12.     SceneManager.start
  13.     create
  14.     execute_method_set("post_create")
  15.     Graphics.transition unless @custom_transition
  16.     update_scene until $scene != self
  17.     Graphics.freeze
  18.     execute_method_set("pre_terminate")
  19.     terminate
  20.   end
  21.   #--------------------------------------------------------------------------
  22.   # ● 建立预判
  23.   #--------------------------------------------------------------------------
  24.   def need_create?
  25.     true
  26.   end
  27.   #--------------------------------------------------------------------------
  28.   # ● 建立场景
  29.   #--------------------------------------------------------------------------
  30.   def create
  31.     execute_method_set([:prepare_data, :create_spriteset,
  32.       :create_all_sprites, :create_all_windows])
  33.   end
  34.   #--------------------------------------------------------------------------
  35.   # ● 更新场景
  36.   #--------------------------------------------------------------------------
  37.   def update_scene
  38.     Graphics.update
  39.     Input.update
  40.     update
  41.   end
  42.   #--------------------------------------------------------------------------
  43.   # ● 结束场景
  44.   #--------------------------------------------------------------------------
  45.   def terminate
  46.     execute_method_set([:dispose_spriteset, :dispose_all_sprites,
  47.       :dispose_all_windows, :after_effect])
  48.   end
  49.   #--------------------------------------------------------------------------
  50.   # ● 更新窗口
  51.   #--------------------------------------------------------------------------
  52.   def update_all_windows
  53.     ivar = instance_variables.map {|i| instance_variable_get(i)}.flatten
  54.     ivar.each {|i| i.update if i.is_a?(Window) and not i.disposed?}
  55.   end
  56.   #--------------------------------------------------------------------------
  57.   # ● 释放精灵组
  58.   #--------------------------------------------------------------------------
  59.   def dispose_spriteset
  60.     ivar = instance_variables.map {|i| instance_variable_get(i)}.flatten
  61.     ivar.each {|i| i.dispose if i.is_a?(Spriteset)}
  62.   end
  63.   #--------------------------------------------------------------------------
  64.   # ● 释放精灵
  65.   #--------------------------------------------------------------------------
  66.   def dispose_all_sprites
  67.     ivar = instance_variables.map {|i| instance_variable_get(i)}.flatten
  68.     ivar.each do |i|
  69.       next unless i.is_a?(Sprite) and not i.disposed?
  70.       i.bitmap.dispose unless i.bitmap.disposed?
  71.       i.dispose
  72.     end
  73.   end
  74.   #--------------------------------------------------------------------------
  75.   # ● 释放窗口
  76.   #--------------------------------------------------------------------------
  77.   def dispose_all_windows
  78.     ivar = instance_variables.map {|i| instance_variable_get(i)}.flatten
  79.     ivar.each {|i| i.dispose if i.is_a?(Window) and not i.disposed?}
  80.   end
  81.   #--------------------------------------------------------------------------
  82.   # ● 结束效果
  83.   #--------------------------------------------------------------------------
  84.   def after_effect
  85.   end
  86.   #--------------------------------------------------------------------------
  87.   # ● 植入场景类
  88.   #--------------------------------------------------------------------------
  89.   [Scene_Title, Scene_Map, Scene_Menu, Scene_Item, Scene_Skill, Scene_Equip,
  90.   Scene_Status, Scene_File, Scene_End, Scene_Battle, Scene_Shop, Scene_Name,
  91.   Scene_Gameover, Scene_Debug].each do |scene|
  92.     scene.send :include, self
  93.     scene.send :remove_method, :main
  94.   end
  95. end


execute_method_set这个方法是我自己写的公用函数,反射执行所有本类当中,以指定名称开头的所有方法(方法会排序),主要解决四个黑洞问题
RUBY 代码复制
  1. #------------------------------------------------------------------------
  2.       # ● 执行统一前缀名的方法集或者给定的方法集(需统一参数签名)
  3.       #------------------------------------------------------------------------
  4.       @@method_hash = Hash.new do |h, k|
  5.         h[k] = k[0].instance_methods.select {|m| m[0, k[1].size] == k[1]}.sort
  6.       end
  7.       def execute_method_set(set, *args)
  8.         return if set.empty?
  9.         methods = set.is_a?(String) ? @@method_hash[[self.class, set]] :
  10.           set.select {|m| respond_to?(m)}
  11.         methods.map {|name| send(name, *args)}
  12.       end

1. 用module mix-in代替类的继承导致的私有方法无处安放的问题,Mix-in不覆写同名方法,而是被覆写,要实现覆写就得上remove_method,冲突警告(Scene_Base最下面那个remove_method :main我测试了整整半个月时间),有这个机制就可以实现VA那样的碎片化封装
2. 子类需要在父类的某方法前半段和后半段之间插东西的问题(VA的方法写的过碎了,以至于不存在这个问题,但XP里几乎到处都是,方法块太整,大部分是基于业务的逻辑,当时改Spriteset#update修精灵动画bug是真的想哭啊)。这其中一部分我的core engine给拆了,但本着最小化修改(虽然接口100%维持,但改得越少冲突可能越低)的原则,对于Scene只是加了这两处基于方法名约定的接口
3. 由2的问题衍申出来的,为了避免不同的插件在中间插东西,插件的安装有严格顺序(Yanfly MV插件开源那个版本的顺序要求,看得让人想头大),但因为这个是反射,延迟绑定,而且方法本身可以排序,所以对同类插件的安装顺序要求就很低

这里,由于1000永远先于1500执行,所以这两个插件先放哪个后放哪个根本无所谓,如果不用这个方法而是先后alias(包括变相alias的方案)那放错了可能就完蛋了。XP内建有多少bug就是因为执行顺序搞反了整出来的……
4. 旧存档兼容
RUBY 代码复制
  1. #==============================================================================
  2. # ■ Scene_Load
  3. #==============================================================================
  4. class Scene_Load
  5.   #--------------------------------------------------------------------------
  6.   # ● 读取存档数据
  7.   #     file : 读取用文件对像 (已经打开)
  8.   #--------------------------------------------------------------------------
  9.   def_after :read_save_data do |file|
  10.     execute_method_set("savefile_fallback") if $DEBUG
  11.   end
  12.   #--------------------------------------------------------------------------
  13.   # ● 旧存档兼容处理
  14.   #--------------------------------------------------------------------------
  15.   def savefile_fallback
  16.     $game_system.instance_variable_set(:@config,{}) if $game_system.config.nil?
  17.   end
  18. end

以上7行代码已经一切尽在不言中,所谓旧存档兼容就是真的把存档给兼容了,而不是定义一大堆||=方法并将attr_accessor写成attr_writer来自己骗自己
因为旧存档兼容有的时候真的并不那么简单
RUBY 代码复制
  1. #==============================================================================
  2. # ■ Scene_Load
  3. #==============================================================================
  4. class Scene_Load
  5.   #--------------------------------------------------------------------------
  6.   # ● 旧存档兼容
  7.   #--------------------------------------------------------------------------
  8.   def savefile_fallback_extend_equip
  9.     $game_actors.instance_variable_get(:@data).each do |actor|
  10.       if actor && actor.weapon_index.nil?
  11.         actor.weapon_index = 0
  12.         actor.make_equip_set
  13.         set = [:@weapon_id, :@armor1_id, :@armor2_id, :@armor3_id, :@armor4_id]
  14.         set.each_with_index do |sym, i|
  15.           id = actor.instance_variable_get(sym)
  16.           next if id == 0
  17.           if i == 0
  18.             $game_party.gain_weapon(id, 1)
  19.           else
  20.             $game_party.gain_armor(id, 1)
  21.           end
  22.           actor.instance_variable_set(sym, 0)
  23.           if pos = actor.equip_kinds.index(i - 1)
  24.             actor.equip(pos, id)
  25.           end
  26.         end
  27.       end
  28.     end
  29.   end
  30. end

作者: guoxiaomi    时间: 2022-2-9 13:30
确实比想象中还要复杂很多,不过场景类的继承我觉得不是特别大的问题吧,因为继承关系在第一次出现时写一下就行了,后面不写也是可以的。
作者: plain666    时间: 2022-2-9 15:11
修改这么多代码,真不容易啊,高手啊。
作者: 我是大仙    时间: 2022-2-11 13:44
VA是什么XP?




欢迎光临 Project1 (https://rpg.blue/) Powered by Discuz! X3.1