[Ruby] 纯文本查看 复制代码
#==============================================================================
# ■ 检视战场(独立场景版)
#------------------------------------------------------------------------------
# 【操作】
# ActorCommand 阶段按 A / Shift 打开;A / Shift / Esc / X 关闭。
# 左半为战斗日志:↑↓ 滚动(可按住快速滚动),→ 切到右侧
# 右半为人物查看:↑↓ 切换成员(首尾循环),← 切回左侧
# 打开时默认滚到最新一条。
#
# 【滚动提示】
# 左侧有未显示内容时,底部显示黑框 + "按 ↑ 上翻" / "按 ↓ 下翻"
#
# 【记录来源】
# Window_BattleLog#add_text / replace_text、$game_message.add
#
# 【转义符处理(记录时固定)】
# \V[n] → 变量当前值;\name[XX] → "XX:";其他转义符去掉
#
# 【数据库备注】
# 敌人:<说明:XX>(\n 变换行,自动换行)
# 状态:<状态名:XX>(无此标签则不显示)
#
# 【加载位置】
# Materials 下方、Main 上方,放在所有战斗脚本之后。
#==============================================================================
$battle_log_history = nil
$char_width_cache = {}
#--------------------------------------------------------------------------
# ● 滚动参数
#--------------------------------------------------------------------------
HOLD_START_FRAMES = 15 # 按住多少帧后开始连续滚动
HOLD_INTERVAL = 3 # 连续滚动间隔(帧)
#--------------------------------------------------------------------------
# ● 转义符处理
#--------------------------------------------------------------------------
module BattleLogSanitizer
def self.sanitize(text)
return text if text.nil?
s = text.dup
s.gsub!(/\\[Vv]\[(\d+)\]/) { $game_variables[$1.to_i].to_s }
s.gsub!(/\\[Nn]ame\[([^\]]*)\]/) { "#{$1}:" }
s.gsub!(/\\[A-Za-z_]+\[[^\]]*\]/, "")
s.gsub!(/\\[A-Za-z_]+/, "")
s.gsub!(/\\[.><|!^$]/, "")
s
end
end
#--------------------------------------------------------------------------
# ● Window_BattleLog — 记录
#--------------------------------------------------------------------------
class Window_BattleLog < Window_Selectable
alias history_log_add_text add_text
def add_text(text)
if $battle_log_history
s = BattleLogSanitizer.sanitize(text)
$battle_log_history << s unless s.nil? || s.empty?
end
history_log_add_text(text)
end
alias history_log_replace_text replace_text
def replace_text(text)
if $battle_log_history
s = BattleLogSanitizer.sanitize(text)
$battle_log_history << s unless s.nil? || s.empty?
end
history_log_replace_text(text)
end
end
#--------------------------------------------------------------------------
# ● Game_Message — 记录事件文字
#--------------------------------------------------------------------------
class Game_Message
alias history_log_message_add add
def add(text)
if $battle_log_history && SceneManager.scene.is_a?(Scene_Battle)
s = BattleLogSanitizer.sanitize(text)
$battle_log_history << s unless s.nil? || s.empty?
end
history_log_message_add(text)
end
end
#--------------------------------------------------------------------------
# ● Window_BattleHistoryLog — 全屏双面板窗口
#--------------------------------------------------------------------------
class Window_BattleHistoryLog < Window_Base
FONT_SIZE = 20
STATE_FONT_SIZE = 18
HINT_FONT_SIZE = 18
LINE_HEIGHT = 24
MAX_MEMBERS = 9
STATE_ICON_W = 24
def initialize
super(0, 0, Graphics.width, Graphics.height)
contents.font.size = FONT_SIZE
@active = false
@close_requested = false
@side = :left
@left_oy = 0
@member_index = 0
@data = []
@battler_list = []
@history_lines = []
@prev_press = {}
@hold = {}
self.z = 1000
self.openness = 0
end
def close_requested?; @close_requested; end
#--------------------------------------------------------------------------
# ● 激活:快照当前所有按键状态
#--------------------------------------------------------------------------
def activate
@active = true
@prev_press = {}
@hold = {}
[:UP, :DOWN, :LEFT, :RIGHT, :A, :B, :X].each do |k|
@prev_press[k] = Input.press?(k)
@hold[k] = nil
end
end
def deactivate; @active = false; end
def active?; @active; end
def left_width; (contents.width * 0.5).to_i - 8; end
def right_x; left_width + 16; end
def right_width; contents.width - right_x - 4; end
def right_top_height
[MAX_MEMBERS * LINE_HEIGHT + 4, contents.height - 80].min
end
def right_bottom_y; right_top_height + 6; end
#--------------------------------------------------------------------------
# ● 字符宽度缓存
#--------------------------------------------------------------------------
def char_width(ch)
key = [contents.font.size, ch]
$char_width_cache[key] ||= contents.text_size(ch).width
end
#--------------------------------------------------------------------------
# ● 设置数据
#--------------------------------------------------------------------------
def set_data(history, battlers)
@data = (history || []).dup
@battler_list = (battlers || []).first(MAX_MEMBERS)
@member_index = 0
@side = :left
@close_requested = false
rebuild
@left_oy = left_max_oy
refresh
end
def rebuild
contents.font.size = FONT_SIZE
@history_lines = []
@data.each { |text| @history_lines.concat(wrap_text(text, left_width)) }
end
#--------------------------------------------------------------------------
# ● 自动换行(逐字增量测量)
#--------------------------------------------------------------------------
def wrap_text(text, max_width)
return [""] if text.empty?
return [""] if text.nil?
lines = []
current = ""
current_w = 0
text.each_char do |c|
if c == "\n"
lines << current
current = ""
current_w = 0
next
end
w = char_width(c)
if current_w + w > max_width
lines << current
current = c
current_w = w
else
current << c
current_w += w
end
end
lines << current unless current.empty?
lines
end
def left_max_oy
[@history_lines.size * LINE_HEIGHT - contents.height, 0].max
end
#--------------------------------------------------------------------------
# ● 绘制
#--------------------------------------------------------------------------
def refresh
contents.clear
draw_left_panel
draw_left_hints
draw_separator
draw_right_panel
draw_focus_frame
end
def draw_left_panel
w = left_width
y = -@left_oy
@history_lines.each do |line|
if y + LINE_HEIGHT > 0 && y < contents.height
contents.draw_text(0, y, w, LINE_HEIGHT, line)
end
y += LINE_HEIGHT
break if y > contents.height
end
end
#--------------------------------------------------------------------------
# ● 左侧滚动提示:↑ 在顶部,↓ 在底部
#--------------------------------------------------------------------------
def draw_left_hints
has_prev = @left_oy > 0
has_next = @left_oy < left_max_oy
lh = LINE_HEIGHT
old_color = contents.font.color.clone
old_size = contents.font.size
contents.font.color = Color.new(255, 220, 100)
contents.font.size = HINT_FONT_SIZE
# 顶部:"↑ 上翻"
if has_prev
top_rect = Rect.new(0, 0, left_width, lh)
contents.fill_rect(top_rect, Color.new(0, 0, 0, 200))
contents.draw_text(top_rect, "↑ 上翻", 1)
end
# 底部:"↓ 下翻"
if has_next
bottom_rect = Rect.new(0, contents.height - lh, left_width, lh)
contents.fill_rect(bottom_rect, Color.new(0, 0, 0, 200))
contents.draw_text(bottom_rect, "↓ 下翻", 1)
end
contents.font.color = old_color
contents.font.size = old_size
end
def draw_separator
x = left_width + 4
contents.fill_rect(x, 0, 2, contents.height, Color.new(255, 255, 255, 60))
end
def draw_right_panel
x = right_x
w = right_width
@battler_list.each_with_index do |b, i|
y = i * LINE_HEIGHT
if i == @member_index && @side == :right
contents.fill_rect(x - 4, y - 1, w, LINE_HEIGHT + 2,
Color.new(255, 200, 50, 80))
draw_border(x - 4, y - 1, w, LINE_HEIGHT + 2)
end
contents.draw_text(x, y, w, LINE_HEIGHT, b.name)
end
contents.fill_rect(x, right_top_height, w, 2, Color.new(255, 255, 255, 80))
draw_member_detail(x, right_bottom_y, w, contents.height - right_bottom_y)
end
def draw_member_detail(x, y, w, h)
return if @battler_list.empty?
b = @battler_list[@member_index]
return unless b
contents.draw_text(x, y, w, LINE_HEIGHT, b.name)
y += LINE_HEIGHT
desc = get_description(b)
if desc && !desc.empty?
wrap_text(desc, w).each do |line|
break if y + LINE_HEIGHT > contents.height
contents.draw_text(x, y, w, LINE_HEIGHT, line)
y += LINE_HEIGHT
end
end
y += 6
draw_states_two_columns(b, x, y, w, contents.height - y)
end
#--------------------------------------------------------------------------
# ● 两列显示状态
#--------------------------------------------------------------------------
def draw_states_two_columns(battler, x, y, w, h)
return if h <= 0
old_size = contents.font.size
contents.font.size = STATE_FONT_SIZE
begin
states = battler.states
.sort_by { |s| [-s.priority, s.id] }
.select { |s| state_display_name(s) }
return if states.empty?
col_w = w / 2
text_max = col_w - STATE_ICON_W - 8
first_col = []
cur_y = 0
remaining = []
states.each do |s|
if cur_y + LINE_HEIGHT > h
remaining << s
next
end
name = state_display_name(s)
too_long = contents.text_size(name).width > text_max
first_col << [s, too_long, cur_y]
cur_y += LINE_HEIGHT
end
first_col.each do |s, too_long, offset_y|
name = state_display_name(s)
row_y = y + offset_y
draw_icon(s.icon_index, x, row_y)
if too_long
contents.draw_text(x + STATE_ICON_W, row_y,
w - STATE_ICON_W, LINE_HEIGHT, name)
else
contents.draw_text(x + STATE_ICON_W, row_y,
col_w - STATE_ICON_W, LINE_HEIGHT, name)
end
end
second_x = x + col_w
cur_y = 0
remaining.each do |s|
while cur_y + LINE_HEIGHT <= h
row_index = cur_y / LINE_HEIGHT
if row_index < first_col.size && first_col[row_index][1]
cur_y += LINE_HEIGHT
else
break
end
end
break if cur_y + LINE_HEIGHT > h
name = state_display_name(s)
row_y = y + cur_y
draw_icon(s.icon_index, second_x, row_y)
contents.draw_text(second_x + STATE_ICON_W, row_y,
col_w - STATE_ICON_W, LINE_HEIGHT, name)
cur_y += LINE_HEIGHT
end
ensure
contents.font.size = old_size
end
end
def get_description(battler)
return nil unless battler.is_a?(Game_Enemy)
note = battler.enemy.note
return nil if note.nil? || note.empty?
if note =~ /<说明[::]\s*([^>]+)>/m
return $1.gsub("\\n", "\n").strip
end
nil
end
def state_display_name(state)
return nil if state.note.nil?
if state.note =~ /<状态名[::]\s*([^>]+)>/
return $1.strip
end
nil
end
def draw_focus_frame
draw_border(0, 0, left_width, contents.height) if @side == :left
end
def draw_border(x, y, w, h)
c = Color.new(255, 200, 50)
contents.fill_rect(x, y, w, 2, c)
contents.fill_rect(x, y+h-2, w, 2, c)
contents.fill_rect(x, y, 2, h, c)
contents.fill_rect(x+w-2, y, 2, h, c)
end
#--------------------------------------------------------------------------
# ● 输入状态更新(每帧一次)
#--------------------------------------------------------------------------
def update_input_state
[:UP, :DOWN, :LEFT, :RIGHT, :A, :B, :X].each do |k|
now = Input.press?(k)
was = @prev_press[k] || false
just = now && !was
@prev_press[k] = now
if just
@hold[k] = 0
elsif now
@hold[k] = (@hold[k] || 0) + 1
else
@hold[k] = nil
end
end
end
# 首次按下(严格上升沿)
def just_pressed?(key)
@hold[key] == 0
end
# 首次按下 OR 按住后连续触发
def triggered?(key)
c = @hold[key]
return false if c.nil?
return true if c == 0
c >= HOLD_START_FRAMES && (c - HOLD_START_FRAMES) % HOLD_INTERVAL == 0
end
#--------------------------------------------------------------------------
# ● 更新
#--------------------------------------------------------------------------
def update
super
update_input_state
return unless @active && openness > 0
# 关闭
if just_pressed?(:A) || just_pressed?(:B) || just_pressed?(:X)
@close_requested = true
deactivate
close
return
end
if @side == :left
if just_pressed?(:RIGHT)
@side = :right
refresh
elsif triggered?(:UP)
@left_oy = [@left_oy - LINE_HEIGHT, 0].max
refresh
elsif triggered?(:DOWN)
@left_oy = [@left_oy + LINE_HEIGHT, left_max_oy].min
refresh
end
else
if just_pressed?(:LEFT)
@side = :left
refresh
elsif triggered?(:UP)
move_cursor(-1)
elsif triggered?(:DOWN)
move_cursor(1)
end
end
end
def move_cursor(delta)
return if @battler_list.empty?
@member_index = (@member_index + delta) % @battler_list.size
refresh
end
end
#--------------------------------------------------------------------------
# ● Scene_BattleHistory — 独立场景
#--------------------------------------------------------------------------
class Scene_BattleHistory < Scene_Base
def start
super
create_background
create_window
end
def create_background
@bg_sprite = Sprite.new
@bg_sprite.bitmap = Bitmap.new(Graphics.width, Graphics.height)
@bg_sprite.bitmap.fill_rect(0, 0, Graphics.width, Graphics.height,
Color.new(0, 0, 0, 200))
@bg_sprite.z = 500
end
def create_window
battlers = $game_party.members + $game_troop.members
@window = Window_BattleHistoryLog.new
@window.set_data($battle_log_history, battlers)
@window.openness = 255
@window.activate
end
def perform_transition
Graphics.transition(4)
end
def update
super
@window.update
if @window.close_requested? && @window.openness == 0
return_scene
end
end
def terminate
@window.dispose
@bg_sprite.dispose
super
end
end
#--------------------------------------------------------------------------
# ● Scene_Battle — 集成
#--------------------------------------------------------------------------
class Scene_Battle < Scene_Base
alias history_log_battle_start battle_start
def battle_start
$battle_log_history = []
history_log_battle_start
end
alias history_log_turn_end turn_end
def turn_end
history_log_turn_end
if $battle_log_history && !$battle_log_history.empty? && $battle_log_history.last != ""
$battle_log_history << ""
end
end
alias history_log_update update
def update
history_log_update
update_battle_history_log
end
def update_battle_history_log
@history_cooldown ||= 0
@history_cooldown -= 1 if @history_cooldown > 0
return if @history_cooldown > 0
return unless @actor_command_window
return unless @actor_command_window.active
return unless Input.trigger?(:A) || Input.trigger?(:X)
open_battle_history_log
end
def open_battle_history_log
saved_index = @actor_command_window ? @actor_command_window.index : 0
saved_oy = @actor_command_window ? @actor_command_window.oy : 0
@actor_command_window.deactivate if @actor_command_window
SceneManager.call(Scene_BattleHistory)
SceneManager.scene.main
if SceneManager.respond_to?(:force_recall)
SceneManager.force_recall(self)
else
SceneManager.instance_variable_set(:@scene, self)
end
if @actor_command_window && !@actor_command_window.disposed?
@actor_command_window.refresh
@actor_command_window.activate
@actor_command_window.select(saved_index)
@actor_command_window.oy = saved_oy
end
refresh_status
# 关键:解冻画面(子场景退出时执行了 Graphics.freeze)
perform_transition
@history_cooldown = 3
end
end
本帖来自P1论坛作者JasperCrazy,因Project1站服务器在国外有时候访问缓慢不方便作者交流学习,经联系P1站长fux2同意署名转载一起分享游戏制作经验,共同为国内独立游戏作者共同创造良好交流环境,原文地址:https://rpg. blue/forum.php?mod=viewthread&tid=499333 若有侵权,发帖作者可联系底部站长QQ在线咨询功能删除,谢谢。