本帖最后由 SailCat 于 2022-2-9 09:34 编辑
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给抽象出来就行了。
#============================================================================== # ■ 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
#==============================================================================
# ■ 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
execute_method_set这个方法是我自己写的公用函数,反射执行所有本类当中,以指定名称开头的所有方法(方法会排序),主要解决四个黑洞问题
#------------------------------------------------------------------------ # ● 执行统一前缀名的方法集或者给定的方法集(需统一参数签名) #------------------------------------------------------------------------ @@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
#------------------------------------------------------------------------
# ● 执行统一前缀名的方法集或者给定的方法集(需统一参数签名)
#------------------------------------------------------------------------
@@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
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. 旧存档兼容
#============================================================================== # ■ 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
#==============================================================================
# ■ 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
以上7行代码已经一切尽在不言中,所谓旧存档兼容就是真的把存档给兼容了,而不是定义一大堆||=方法并将attr_accessor写成attr_writer来自己骗自己
因为旧存档兼容有的时候真的并不那么简单
#============================================================================== # ■ 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
#==============================================================================
# ■ 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
|