You've reached a page on the Ren'Py wiki. Due to massive spam, the wiki hasn't been updated in over 5 years, and much of the information here is very out of date. We've kept it because some of it is of historic interest, but all the information relevant to modern versions of Ren'Py has been moved elsewhere.
Some places to look are:
Please do not create new links to this page.
以下のスクリプトによって、会話の履歴画面(Readback: 日本製のゲームで従来から使われている方式)を Ren'Py のゲームに追加できます。音声ファイルがゲーム中で使われている場合は、以前の会話をクリックすることで再生できます。音声がない場合は、会話が一行ずつテキストとして画面に表示されます。 (訳注: extendを使用した時の不具合やnvl clearで行間をあける等の改善をしたものがこちらで配布されています。
基本的には、マウスホイールが上にスクロールされると、履歴画面が表示されます。ボタンを使って履歴画面を表示させたい場合は、screen ブロックに以下のコードを追加してください。
textbutton '会話履歴' action [SetVariable("yvalue", 1.0), ShowMenu('text_history')]
例えば、ナビゲーター(画面右クリック時に表示されるボタン欄)にボタンを追加したい場合は、screen navigation ブロックのスクリプトを以下のように編集します。
#screens.rpy
screen navigation:
# The background of the game menu.
window:
style "gm_root"
# The various buttons.
frame:
style_group "gm_nav"
xalign .98
yalign .98
has vbox
textbutton _("Return") action Return()
## 履歴ボタンの追加
textbutton _("Text History") action [SetVariable("yvalue", 1.0), ShowMenu("text_history")]
#.....
以下のスクリプトをコピーし、readback.rpy などの名前で保存してください。
# readback.rpy
# 中ボタンスクロールによる会話履歴(リードバック)画面への切り替え
# this file is licensed under the terms of the WTFPL
# see http://sam.zoy.org/wtfpl/COPYING for details
# voice_replay function added by backansi from Lemma soft forum.
# Ren'Py 6.12 以上の環境向け
init -3 python:
# スタイル
style.readback_window.xmaximum = 760
style.readback_window.ymaximum = 500
style.readback_window.align = (.5, .5)
style.readback_frame.background = None
style.readback_frame.xpadding = 10
style.readback_frame.xmargin = 5
style.readback_frame.ymargin = 5
style.readback_text.color = "#fff"
style.create("readback_button", "readback_text")
style.readback_button.background = None
style.create("readback_button_text", "readback_text")
style.readback_button_text.selected_color = "#f12"
style.readback_button_text.hover_color = "#f12"
style.readback_label_text.bold = True
# コンフィグレーション変数の新規追加
config.locked = False
# 会話履歴のコンフィグレーション変数
config.readback_buffer_length = 100 # 履歴に保存される最大行数
config.readback_full = True # True = ロールバックの完全な置き換え, False = ゲームメニューのみからの表示 (デバッグモード)
config.readback_disallowed_tags = ["size"] # 履歴内で無視されるテキストタグ
config.readback_choice_prefix = ">> " # ユーザーが選択したメニュー項目の前に付くテキスト
# 追加終了
config.locked = True
init -2 python:
# 発言を記録する2つのカスタムキャラクター
class ReadbackADVCharacter(ADVCharacter):
def do_done(self, who, what):
store_say(who, what)
store.current_voice = ''
return
class ReadbackNVLCharacter(NVLCharacter):
def do_done(self, who, what):
store_say(who, what)
store.current_voice = ''
return
# this enables us to show the current line in readback without having to bother the buffer with raw shows
def say_wrapper(who, what, **kwargs):
store_current_line(who, what)
return renpy.show_display_say(who, what, **kwargs)
config.nvl_show_display_say = say_wrapper
adv = ReadbackADVCharacter(show_function=say_wrapper)
nvl = ReadbackNVLCharacter()
NVLCharacter = ReadbackNVLCharacter
# voice 関数を上書きし、会話履歴上のボタンをクリックした時に音声をリプレイできるようにする。
def voice(file, **kwargs):
if not config.has_voice:
return
_voice.play = file
store.current_voice = file
# menu 関数を上書きしてユーザーのメニュー選択を記録できるようにする。
def menu(items, **add_input):
newitems = []
for label, val in items:
if val == None:
narrator(label, interact=False)
else:
newitems.append((label, val))
rv = renpy.display_menu(newitems, **add_input)
# メニュー選択の記録
for label, val in items:
if rv == val:
store.current_voice = ''
store_say(None, config.readback_choice_prefix + label)
return rv
def nvl_screen_dialogue():
"""
Returns widget_properties and dialogue for the current NVL
mode screen.
"""
widget_properties = { }
dialogue = [ ]
for i, entry in enumerate(nvl_list):
if not entry:
continue
who, what, kwargs = entry
if i == len(nvl_list) - 1:
who_id = "who"
what_id = "what"
window_id = "window"
else:
who_id = "who%d" % i
what_id = "what%d" % i
window_id = "window%d" % i
widget_properties[who_id] = kwargs["who_args"]
widget_properties[what_id] = kwargs["what_args"]
widget_properties[window_id] = kwargs["window_args"]
dialogue.append((who, what, who_id, what_id, window_id))
return widget_properties, dialogue
# nvl menu 関数の上書き
def nvl_menu(items):
renpy.mode('nvl_menu')
if nvl_list is None:
store.nvl_list = [ ]
screen = None
if renpy.has_screen("nvl_choice"):
screen = "nvl_choice"
elif renpy.has_screen("nvl"):
screen = "nvl"
if screen is not None:
widget_properties, dialogue = nvl_screen_dialogue()
rv = renpy.display_menu(
items,
widget_properties=widget_properties,
screen=screen,
scope={ "dialogue" : dialogue },
window_style=style.nvl_menu_window,
choice_style=style.nvl_menu_choice,
choice_chosen_style=style.nvl_menu_choice_chosen,
choice_button_style=style.nvl_menu_choice_button,
choice_chosen_button_style=style.nvl_menu_choice_chosen_button,
type="nvl",
)
for label, val in items:
if rv == val:
store.current_voice = ''
store_say(None, config.readback_choice_prefix + label)
return rv
# Traditional version.
ui.layer("transient")
ui.clear()
ui.close()
ui.window(style=__s(style.nvl_window))
ui.vbox(style=__s(style.nvl_vbox))
for i in nvl_list:
if not i:
continue
who, what, kw = i
rv = renpy.show_display_say(who, what, **kw)
renpy.display_menu(items, interact=False,
window_style=__s(style.nvl_menu_window),
choice_style=__s(style.nvl_menu_choice),
choice_chosen_style=__s(style.nvl_menu_choice_chosen),
choice_button_style=__s(style.nvl_menu_choice_button),
choice_chosen_button_style=__s(style.nvl_menu_choice_chosen_button),
)
ui.close()
roll_forward = renpy.roll_forward_info()
rv = ui.interact(roll_forward=roll_forward)
renpy.checkpoint(rv)
for label, val in items:
if rv == val:
store.current_voice = ''
store_say(None, config.readback_choice_prefix + label)
return rv
## readback
readback_buffer = []
current_line = None
current_voice = None
def store_say(who, what):
global readback_buffer, current_voice
new_line = (preparse_say_for_store(who), preparse_say_for_store(what), current_voice)
readback_buffer = readback_buffer + [new_line]
readback_prune()
def store_current_line(who, what):
global current_line, current_voice
current_line = (preparse_say_for_store(who), preparse_say_for_store(what), current_voice)
# 発言内容からのテキストタグの除去
disallowed_tags_regexp = ""
for tag in config.readback_disallowed_tags:
if disallowed_tags_regexp != "":
disallowed_tags_regexp += "|"
disallowed_tags_regexp += "{"+tag+"=.*?}|{"+tag+"}|{/"+tag+"}"
import re
remove_tags_expr = re.compile(disallowed_tags_regexp) # remove tags undesirable in readback
def preparse_say_for_store(input):
global remove_tags_expr
if input:
return re.sub(remove_tags_expr, "", input)
def readback_prune():
global readback_buffer
while len(readback_buffer) > config.readback_buffer_length:
del readback_buffer[0]
# 会話履歴を表示するためのキーマップの上書き
def readback_catcher():
ui.add(renpy.Keymap(rollback=(SetVariable("yvalue", 1.0), ShowMenu("text_history"))))
ui.add(renpy.Keymap(rollforward=ui.returns(None)))
if config.readback_full:
config.rollback_enabled = False
config.overlay_functions.append(readback_catcher)
init python:
yvalue = 1.0
class NewAdj(renpy.display.behavior.Adjustment):
def change(self,value):
if value > self._range and self._value == self._range:
return Return()
else:
return renpy.display.behavior.Adjustment.change(self, value)
def store_yvalue(y):
global yvalue
yvalue = int(y)
# 会話履歴画面
screen text_history:
#use navigation
tag menu
if not current_line and len(readback_buffer) == 0:
$ lines_to_show = []
elif current_line and len(readback_buffer) == 0:
$ lines_to_show = [current_line]
elif current_line and not ( current_line == readback_buffer[-1] or False
if len(readback_buffer) == 1 else (current_line == readback_buffer[-2]) ):
$ lines_to_show = readback_buffer + [current_line]
else:
$ lines_to_show = readback_buffer
$ adj = NewAdj(changed = store_yvalue, step = 300)
window:
style_group "readback"
side "c r":
frame:
$ vp = ui.viewport(mousewheel = True, offsets=(0.0, yvalue), yadjustment = adj)
vbox:
null height 10
for line in lines_to_show:
if line[0] and line[0] != " ":
label line[0] # name
if line[1]:
# 音声がない場合は会話を表示するのみ
if not line[2]:
text line[1]
# 音声がある場合は、会話をボタンとして表示し、クリック時に再生する
else:
textbutton line[1] action Play("voice", line[2] )
null height 10
bar adjustment adj style 'vscrollbar'
textbutton _("Return") action Return() align (.97, 1.0)