查看: 2593|回复: 6

[胡扯] 终于把XP改成了VA

[复制链接]

遠航の猫咪

梦石
3
星屑
26947
在线时间
2423 小时
注册时间
2005-10-15
回帖
987

开拓者

发表于 2022-2-9 01:42:39 | 显示全部楼层 |阅读模式

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

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

×
本帖最后由 SailCat 于 2022-2-9 01:47 编辑

一个春节假期的成果……
444.png
abcd.png
efg.png

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

花了两天时间思考方案,然后实现出来以后,我大概已经看到了后面产出插件的速度会非常之快了

评分

参与人数 6赞 +6 收起 理由
sdgn + 1 精品文章
非常白菜 + 1 猫大太强啦!
哇哇哇啊叭叭 + 1 塞糖
小怪兽奇奇侠 + 1 好厉害呀!
saterick + 1 塞糖
89444640 + 1 膜拜猫大

查看全部评分

SailCat (小猫子·要开心一点) 共上站 24 次,发表过 11 篇文章 上 次 在: [2006年01月28日11:41:18 星期六] 从 [162.105.120.91] 到本站一游。
梦石
1
星屑
24088
在线时间
3339 小时
注册时间
2011-7-8
回帖
3862

开拓者

发表于 2022-2-9 02:44:29 | 显示全部楼层
猫姐怎么看va里SceneManager和Scene_Base的设计(包括无处不在的Fiber)?我总觉得不太直观,每次找某个scene的执行流程都要翻半天……
熟悉rgss和ruby,xp区版主~
正在填坑:《膜拜组传奇》讲述膜拜组和学霸们的故事。
已上steam:与TXBD合作的Reformers《变革者》
* 战斗调用公共事件 *
* RGSOS 网络脚本 *
回复

使用道具 举报

遠航の猫咪

梦石
3
星屑
26947
在线时间
2423 小时
注册时间
2005-10-15
回帖
987

开拓者

 楼主| 发表于 2022-2-9 08:07:47 | 显示全部楼层
本帖最后由 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给抽象出来就行了。
[pre lang="ruby"]#==============================================================================
# ■ Scene_Base
#------------------------------------------------------------------------------
#   游戏中全部场景的公用模块。
#==============================================================================
module Scene_Base
  #--------------------------------------------------------------------------
  # ● 主处理
  #--------------------------------------------------------------------------
  def main
    return unless need_create?
    SceneManager.start
    create
    execute_method_set("post_create")
    Graphics.transition unless @custom_transition
    update_scene until $scene != self
    Graphics.freeze
    execute_method_set("pre_terminate")
    terminate
  end
  #--------------------------------------------------------------------------
  # ● 建立预判
  #--------------------------------------------------------------------------
  def need_create?
    true
  end
  #--------------------------------------------------------------------------
  # ● 建立场景
  #--------------------------------------------------------------------------
  def create
    execute_method_set([:prepare_data, :create_spriteset,
      :create_all_sprites, :create_all_windows])
  end
  #--------------------------------------------------------------------------
  # ● 更新场景
  #--------------------------------------------------------------------------
  def update_scene
    Graphics.update
    Input.update
    update
  end
  #--------------------------------------------------------------------------
  # ● 结束场景
  #--------------------------------------------------------------------------
  def terminate
    execute_method_set([:dispose_spriteset, :dispose_all_sprites,
      :dispose_all_windows, :after_effect])
  end
  #--------------------------------------------------------------------------
  # ● 更新窗口
  #--------------------------------------------------------------------------
  def update_all_windows
    ivar = instance_variables.map {|i| instance_variable_get(i)}.flatten
    ivar.each {|i| i.update if i.is_a?(Window) and not i.disposed?}
  end
  #--------------------------------------------------------------------------
  # ● 释放精灵组
  #--------------------------------------------------------------------------
  def dispose_spriteset
    ivar = instance_variables.map {|i| instance_variable_get(i)}.flatten
    ivar.each {|i| i.dispose if i.is_a?(Spriteset)}
  end
  #--------------------------------------------------------------------------
  # ● 释放精灵
  #--------------------------------------------------------------------------
  def dispose_all_sprites
    ivar = instance_variables.map {|i| instance_variable_get(i)}.flatten
    ivar.each do |i|
      next unless i.is_a?(Sprite) and not i.disposed?
      i.bitmap.dispose unless i.bitmap.disposed?
      i.dispose
    end
  end
  #--------------------------------------------------------------------------
  # ● 释放窗口
  #--------------------------------------------------------------------------
  def dispose_all_windows
    ivar = instance_variables.map {|i| instance_variable_get(i)}.flatten
    ivar.each {|i| i.dispose if i.is_a?(Window) and not i.disposed?}
  end
  #--------------------------------------------------------------------------
  # ● 结束效果
  #--------------------------------------------------------------------------
  def after_effect
  end
  #--------------------------------------------------------------------------
  # ● 植入场景类
  #--------------------------------------------------------------------------
  [Scene_Title, Scene_Map, Scene_Menu, Scene_Item, Scene_Skill, Scene_Equip,
  Scene_Status, Scene_File, Scene_End, Scene_Battle, Scene_Shop, Scene_Name,
  Scene_Gameover, Scene_Debug].each do |scene|
    scene.send :include, self
    scene.send :remove_method, :main
  end
end[/pre]

execute_method_set这个方法是我自己写的公用函数,反射执行所有本类当中,以指定名称开头的所有方法(方法会排序),主要解决四个黑洞问题
[pre lang="ruby"]      #------------------------------------------------------------------------
      # ● 执行统一前缀名的方法集或者给定的方法集(需统一参数签名)
      #------------------------------------------------------------------------
      @@method_hash = Hash.new do |h, k|
        h[k] = k[0].instance_methods.select {|m| m[0, k[1].size] == k[1]}.sort
      end
      def execute_method_set(set, *args)
        return if set.empty?
        methods = set.is_a?(String) ? @@method_hash[[self.class, set]] :
          set.select {|m| respond_to?(m)}
        methods.map {|name| send(name, *args)}
      end
[/pre]
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插件开源那个版本的顺序要求,看得让人想头大),但因为这个是反射,延迟绑定,而且方法本身可以排序,所以对同类插件的安装顺序要求就很低
555.png
这里,由于1000永远先于1500执行,所以这两个插件先放哪个后放哪个根本无所谓,如果不用这个方法而是先后alias(包括变相alias的方案)那放错了可能就完蛋了。XP内建有多少bug就是因为执行顺序搞反了整出来的……
4. 旧存档兼容
[pre lang="ruby"]#==============================================================================
# ■ Scene_Load
#==============================================================================
class Scene_Load
  #--------------------------------------------------------------------------
  # ● 读取存档数据
  #     file : 读取用文件对像 (已经打开)
  #--------------------------------------------------------------------------
  def_after :read_save_data do |file|
    execute_method_set("savefile_fallback") if $DEBUG
  end
  #--------------------------------------------------------------------------
  # ● 旧存档兼容处理
  #--------------------------------------------------------------------------
  def savefile_fallback
    $game_system.instance_variable_set(:@config,{}) if $game_system.config.nil?
  end
end[/pre]
以上7行代码已经一切尽在不言中,所谓旧存档兼容就是真的把存档给兼容了,而不是定义一大堆||=方法并将attr_accessor写成attr_writer来自己骗自己
因为旧存档兼容有的时候真的并不那么简单
[pre lang="ruby"]#==============================================================================
# ■ Scene_Load
#==============================================================================
class Scene_Load
  #--------------------------------------------------------------------------
  # ● 旧存档兼容
  #--------------------------------------------------------------------------
  def savefile_fallback_extend_equip
    $game_actors.instance_variable_get(:@data).each do |actor|
      if actor && actor.weapon_index.nil?
        actor.weapon_index = 0
        actor.make_equip_set
        set = [:@weapon_id, :@armor1_id, :@armor2_id, :@armor3_id, :@armor4_id]
        set.each_with_index do |sym, i|
          id = actor.instance_variable_get(sym)
          next if id == 0
          if i == 0
            $game_party.gain_weapon(id, 1)
          else
            $game_party.gain_armor(id, 1)
          end
          actor.instance_variable_set(sym, 0)
          if pos = actor.equip_kinds.index(i - 1)
            actor.equip(pos, id)
          end
        end
      end
    end
  end
end
[/pre]

点评

VA最应该上Fiber的Scene_Battle没有上……想整一个实时战斗最后还是得fallback到XP/VX的思路  发表于 2022-2-9 08:09

评分

参与人数 1赞 +1 收起 理由
guoxiaomi + 1 精品文章

查看全部评分

SailCat (小猫子·要开心一点) 共上站 24 次,发表过 11 篇文章 上 次 在: [2006年01月28日11:41:18 星期六] 从 [162.105.120.91] 到本站一游。
回复

使用道具 举报

梦石
1
星屑
24088
在线时间
3339 小时
注册时间
2011-7-8
回帖
3862

开拓者

发表于 2022-2-9 13:30:27 | 显示全部楼层
确实比想象中还要复杂很多,不过场景类的继承我觉得不是特别大的问题吧,因为继承关系在第一次出现时写一下就行了,后面不写也是可以的。
熟悉rgss和ruby,xp区版主~
正在填坑:《膜拜组传奇》讲述膜拜组和学霸们的故事。
已上steam:与TXBD合作的Reformers《变革者》
* 战斗调用公共事件 *
* RGSOS 网络脚本 *
回复

使用道具 举报

梦石
0
星屑
26087
在线时间
1595 小时
注册时间
2016-1-6
回帖
3441

超评测参与极短28参与极短27参与极短26参与极短25参与

发表于 2022-2-9 15:11:57 | 显示全部楼层
修改这么多代码,真不容易啊,高手啊。
回复

使用道具 举报

梦石
0
星屑
5901
在线时间
1145 小时
注册时间
2013-7-8
回帖
1850

极短23参与

发表于 2022-2-11 13:44:41 | 显示全部楼层
VA是什么XP?
回复

使用道具 举报

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

本版积分规则

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

在本版发帖返回顶部