马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
本帖最后由 Maz马 于 2026-1-25 20:13 编辑
手机聊天信息是老东西了,做的人很多
这个是很早之前黑凤梨老师推荐我看的,得有两年了吧,希望代码不会过时(
原帖出处:
https://nighten.itch.io/yet-another-phone-renpy
26/1/25更新
(在此之前使用的注意新的版本,更新了动态字符串对齐,不想重复复制和一行行对照的请移至最下方)
但是原帖是一个工程包,相当杂乱,而且布局用的都是绝对数据,很难适配
刚好最近帮人写用到了,我就,来都来了,那就顺手(
提取了参数,方便小白使用,我相信东一榔头西一棒槌的布局是大家最头疼的...
另外写了缩略图放大功能,用字典来自动匹配角色的头像 头像框 对话框
会screen的也可以自行扩展
(一如既往,由于基本框架不是我的,所以投教程)
(我真是@Koji 一手带大,这个很早之前他也帮过忙)
第一部分 系统
[RenPy] 纯文本查看 复制代码 image alphaimage = "#0000" # 用于测试的占位透明图
### 布局变量 (只要按需插入图片、根据你自己项目调整,其他部分完全不用管)################################################################
# 覆写旁白角色,添加发送信息的音效回调函数,不希望旁白有音效直接注释掉
define nvl_narrator = NVLCharacter(who_style='nvl_label',what_style='nvl_thought',window_style='nvl_entry',type='nvl',mode='nvl',clear=False,kind=adv,callback=Phone_NarratorSound)
# 用于旁白信息的音频
define audio.phonenarrator = "audio/sound_phone_narratortext.mp3"
# 用于己方信息的音频
define audio.phonesend = "audio/sound_phone_sendtext.mp3"
# 用于对方信息的音频
define audio.phonereceive = "audio/sound_phone_receivetext.mp3"
default phone_bg = "alphaimage" # 手机背景图片
default phone_fg = "alphaimage" # 手机外框图片
# 默认 头像尺寸
default phone_avatar_xysize = (120,120)
# 默认 头像图片
default phone_avatar = "alphaimage"
# 默认 头像框图片
default phone_avatar_frame = "alphaimage"
# 默认 旁白的气泡
default phone_n_frame = "alphaimage"
# 默认 发信者的气泡
default phone_s_frame = "alphaimage"
# 默认 收信者的气泡
default phone_r_frame = "alphaimage"
default phone_n_color = "#FFFF" # 旁白文本颜色
default phone_s_color = "#FFFF" # 发信者文本颜色
default phone_r_color = "#000F" # 收信者文本颜色
default phone_xy = (0.5,0.5) # 手机界面在屏幕上的坐标
default phone_xysize = (900,1750) # 手机界面的尺寸(就是外框的尺寸)
default p_frame_xy = (0.5,0.56) # 聊天窗口在手机界面里的坐标
default p_frame_xysize = (890,1200) # 聊天窗口的尺寸,一般略小于手机(就是比外框略小)
default p_frame_spacing = 10 # 聊天消息之间的上下间隔
default p_narrator_width = 580 # 旁白消息的最大宽度
default p_narrator_padding = (20,10,20,15) # 旁白消息文本到气泡框架的左上右下边距
default p_narrator_textsize = 34 # 旁白消息的字体大小
default p_message_width = 580 # 普通消息的最大宽度
default p_message_padding = (20,10,20,15) # 普通消息文本到气泡框架的左上右下边距
default p_message_textsize = 34 # 普通消息的字体大小
default p_message_xoffset = 0 # 普通消息距离侧方边界的偏移
### 系统变量 #############################################################################
default nvl_mode = "phone" # 区分正常NVL模式和手机组件的变量
define config.nvl_list_length = 999 # 最大对话记录数(在gui中也存在一条,这里直接覆写)
define gui.nvl_thought_width = 0 # 旁白消息的最小宽度(覆写gui,否则上方的最大宽度会受到影响)
define gui.nvl_text_width = 0 # 普通消息的最小宽度(覆写gui,否则上方的最大宽度会受到影响)
# 这里的最小宽度是消息气泡的最小宽度,如果文本比最小宽度长则自适应。否则文本比最小宽度短,气泡也会维持最小宽度。
# 上方的最大宽度,是字数到达一定宽度后,文本换行的宽度。一般来说,最小宽度需要一个比最大宽度小的值。
# 由于这里的变量是主要的接口和覆写原生自带的配置参数,因此单独划分
#########################################################################################
init -1 python:
#可入参的showscreen文本标签
def show_screen_handler(target):
if "," in target:
screen_name, params_str = target.split(",", 1)
params = {}
for pair in params_str.split(","):
if "=" in pair:
key, val = pair.split("=", 1)
params[key.strip()] = val.strip()
renpy.show_screen(screen_name, **params)
else:
renpy.show_screen(target)
return True
config.hyperlink_handlers["show"] = show_screen_handler
def showscreen_tag(tag, argument, contents):
return [
(renpy.TEXT_TAG, f"a=show:{argument}"),
*contents,
(renpy.TEXT_TAG, "/a")
]
config.custom_text_tags["showscreen"] = showscreen_tag
# 旁白消息音效
def Phone_NarratorSound(event,interact=True,**kwargs):
if event == "show_done":
renpy.sound.play(audio.phonenarrator)
# 己方消息音效
def Phone_SendSound(event,interact=True,**kwargs):
if event == "show_done":
renpy.sound.play(audio.phonesend)
# 对方消息音效
def Phone_ReceiveSound(event,interact=True,**kwargs):
if event == "show_done":
renpy.sound.play(audio.phonereceive)
#global current_speaker 无视,被我优化掉了
#if not interact:
# return
#if event == "begin":
# current_speaker = name
# 消息动画效果(左右弹出,1为右,-1为左)
transform message_appear(pDirection):
alpha 0.0
xoffset 50 * pDirection
parallel:
ease 0.5 alpha 1.0
parallel:
easein_back 0.5 xoffset 0
# 头像动画效果(快速放大)
transform message_appear_icon():
zoom 0.0
easein_back 0.5 zoom 1.0
# 旁白动画效果(向上弹出)
transform message_narrator():
alpha 0.0
yoffset -50
parallel:
ease 0.5 alpha 1.0
parallel:
easein_back 0.5 yoffset 0
# 一个用于聊天中缩略图放大功能的界面
screen phone_cg(phonecg):
zorder 102
modal True
frame:
anchor (0.5,0.5)
xysize phone_xysize
pos phone_xy
background phone_bg
foreground phone_fg
imagebutton:
align (0.5,0.5)
idle phonecg
action Hide("phone_cg")
# 手机对话界面主屏幕
screen phone_dialogue(dialogue,items=None):
zorder 101
frame:
anchor (0.5,0.5)
xysize phone_xysize
pos phone_xy
background phone_bg
foreground phone_fg
viewport: # 可滚动区域
anchor (0.5,0.5)
xysize p_frame_xysize
pos p_frame_xy
draggable True
mousewheel True
yinitial 1.0 # 跟踪底部
vbox:
xalign 0.5
xsize p_frame_xysize[0]
spacing p_frame_spacing
use phone_nvltext(dialogue)
# 选项部分
if items != None:
vbox:
xalign 1.0
xoffset -phone_avatar_xysize[0]//2-p_message_xoffset
for i in items:
textbutton i.caption:
background Frame(phone_s_frame,30,30,30,30) at message_narrator
padding p_message_padding
xsize p_message_width
text_size p_message_textsize
text_xalign 0.5
action i.action
# 手机消息显示组件
screen phone_nvltext(dialogue):
zorder 101
# 动态刷新角色数据库
$ update_phone_avatars()
# 初始化 上一条消息发送者
$ previous_d_who = None
# 遍历所有对话
for id_d,d in enumerate(dialogue):
# 旁白消息
if d.who == None:
frame:
align (0.5,0.5)
background Frame(phone_n_frame,30,30,30,30)
padding p_narrator_padding
if d.current:
at message_narrator
text d.what:
align (0.5,0.5)
xsize p_narrator_width
size p_narrator_textsize
color phone_n_color
text_align 0.5
italic True
slow_cps False
id d.what_id
# 角色消息
else:
# 从字典获取头像,头像框,对话框,如果为None或捕获不到,使用默认
if d.who in phone_avatars and phone_avatars[d.who]["frame"]:
$ message_frame = phone_avatars[d.who]["frame"]
elif d.who == mc_nvl.name:
$ message_frame = phone_s_frame
else:
$ message_frame = phone_r_frame
if d.who in phone_avatars and phone_avatars[d.who]["avatar"]:
$ message_avatar = phone_avatars[d.who]["avatar"]
else:
$ message_avatar = phone_avatar
if d.who in phone_avatars and phone_avatars[d.who]["avatar_frame"]:
$ message_avatar_frame = phone_avatars[d.who]["avatar_frame"]
else:
$ message_avatar_frame = phone_avatar_frame
hbox:
if d.who == mc_nvl.name:
box_reverse True
xalign 1.0 xoffset -p_message_xoffset
else:
xalign 0.0 xoffset p_message_xoffset
spacing 10
fixed:
xysize phone_avatar_xysize
if d.current: # 只有当前消息才会应用动画
at message_appear_icon()
add message_avatar align (0.5,0.5) # 添加头像
add message_avatar_frame align (0.5,0.5) # 添加头像框
vbox:
#if d.who != mc_nvl.name and previous_d_who != d.who:
#取消注释添加缩进:玩家不显示姓名,同时,只在新角色说话时才显示姓名
text d.who:
if d.who == mc_nvl.name:
textalign 1.0
xalign 1.0
# 消息气泡
frame:
background Frame(message_frame,30,30,30,30)
padding p_message_padding
if d.current: # 只有当前消息才会应用动画
if d.who == mc_nvl.name:
at message_appear(1) # 发送消息从右出现
else:
at message_appear(-1) # 接收消息从左出现
# 消息文本
text d.what:
align (0.5,0.5)
xsize p_message_width
size p_message_textsize
slow_cps False
if d.who == mc_nvl.name:
color phone_s_color
text_align 1.0
else:
color phone_r_color
id d.what_id
# 更新 上一条消息发送者
$ previous_d_who = d.who
第二部分 使用方法
[RenPy] 纯文本查看 复制代码 # 测试用图片-头像
image phone tx01:
Solid("#0b0")
xysize(30,30)
image phone tx02:
Solid("#b00")
xysize(30,30)
image phone tx03:
Solid("#000")
xysize(30,30)
# 测试用图片-可放大的CG
image phone cg01:
Solid("#000")
xysize(300,300)
image phone cg01ex:
Solid("#000")
xysize(608,1080)
image phone cg02:
Solid("#000")
xysize(300,300)
image phone cg02ex:
xysize(608,1080)
Solid("#000")
pause 0.5
Solid("#00f")
pause 0.5
Solid("#0ff")
pause 0.5
Solid("#fff")
pause 0.5
repeat
# 定义角色
# 写法如下,只是增加了kind=nvl参数,然后callback=Phone_NarratorSound/SendSound/ReceiveSound(发信息时角色自动使用的音效,对应第一部分设置的三个音效)
define mc_nvl = Character("Nighten",kind=nvl,callback=Phone_SendSound)# 这是第一视角的角色,定义一个新的或者对应上你的角色
#define mc_nvl = 和你的主角内容相同,只是把kind=nvl加进去,再填callback=音效函数
define niko_nvl = Character("妮可",kind=nvl,callback=Phone_ReceiveSound)# 范例角色
define ll_nvl = Character("lovelive",kind=nvl,callback=Phone_ReceiveSound)# 范例角色
# 数据列表,由于这个系统读取的不是实例,而是记录了字符串,所以要多写一个字典来查找角色
# 结构为 {某个角色:某个角色的数据字典}
# 角色数据字典结构为 {"avatar":角色头像,"avatar_frame":角色头像框,"frame":角色对话框}
# 角色数据均可填None,为None时系统使用第一部分设置的默认头像/头像框/对话框
# 照抄例子写应该不难
init python:
def update_phone_avatars():
store.phone_avatars = {
# 玩家(发送方)
renpy.translate_string(mc_nvl.name):{"avatar":"phone tx01","avatar_frame":None,"frame":None},
renpy.translate_string(niko_nvl.name):{"avatar":"phone tx02","avatar_frame":None,"frame":None},
renpy.translate_string(ll_nvl.name):{"avatar":"phone tx03","avatar_frame":None,"frame":None},}
label start:
"开始"
call phonelabel_01
"现在,你完成了"
return
# 如何写一个手机信息事件:
label phonelabel_01:
$ nvl_mode = "phone" # 先打开手机模式
nvl_narrator "这是旁白的写法"
niko_nvl "使用专用的角色开始对话"
niko_nvl "这样插入一张 emoji 或 小图{image=phone/emoji/clap.png}"
menu(nvl=True):
nvl_narrator "选项菜单要入参 nvl=True"
"选项1":
pass
"选项2":
pass
mc_nvl "这是另一边的角色在说话"
niko_nvl "然后是 一个可放大的图像,点击图像将会放大,但形状要自己裁切"
niko_nvl "{showscreen=phone_cg,phonecg=phone cg01ex}{image=phone cg01}{/showscreen}"
niko_nvl "phonecg=phone cg01ex 是放大的图片,image=phone cg01 是缩略图"
niko_nvl "然后是 一个可播放的图像,点击图像将会放大,且开始播放"
niko_nvl "{showscreen=phone_cg,phonecg=phone cg02ex}{image=phone cg02}{/showscreen}"
niko_nvl "phonecg=phone cg02ex 是放大的帧动画,image=phone cg02 是缩略图"
mc_nvl "现在尝试互动,这是右边的角色在说话"
ll_nvl "现在尝试跳过对话"
# interact=False 这个参数似乎可以用来制作 “被阅读过的消息记录”,也就是不需要从头点到尾
# 但暂时没时间研究is_seen相关的代码,先插眼,应该不难写,有大神也可以留言
ll_nvl "这句话不需要点击会自己跳过"(interact=False)
ll_nvl "现在尝试多角色对话"
niko_nvl "这是左边的 [niko_nvl] 在说话"
ll_nvl "这是左边的 [ll_nvl] 在说话"
nvl clear # 清空nvl界面,释放缓存
$ nvl_mode = None # 关闭手机模式(养成封闭习惯)
return
第三部分 修改
以上部分可以直接建成新文件,以下是需要在源文件screen.rpy中修改的,只加一句话
[RenPy] 纯文本查看 复制代码 screen nvl(dialogue, items=None):
zorder 101
### 在这里增加应用手机系统的分支 ###
if nvl_mode == "phone":
use phone_dialogue(dialogue,items)
else:
### 以下是源代码只增加了缩进没有任何改动 ###
window:
style "nvl_window"
has vbox:
spacing gui.nvl_spacing
## 在 vpgrid 或 vbox 中显示对话框。
if gui.nvl_height:
vpgrid:
cols 1
yinitial 1.0
use nvl_dialogue(dialogue)
else:
use nvl_dialogue(dialogue)
## 显示菜单,如果给定的话。如果 config.narrator_menu 设置为 True,则菜单
## 可能显示不正确。
for i in items:
textbutton i.caption:
action i.action
style "nvl_button"
add SideImage() xalign 0.0 yalign 1.0
1/25更新处
(上方是完整代码,以后如果再次更新,也是完整发布,在最下方给出修改处)
[RenPy] 纯文本查看 复制代码 #在第二部分
default phone_avatars = {...}
#修改为
init python:
def update_phone_avatars():
store.phone_avatars = {...}
#在第一部分
# 手机消息显示组件
screen phone_nvltext(dialogue):
zorder 101
# 添加了这一句
$ update_phone_avatars()
原因:
游戏生命周期中,default在初始化时被决定,因此立即翻译函数并不会被执行,或只执行一次
[RenPy] 纯文本查看 复制代码 renpy.translate_string()
所以,这个字典会被锁死在最初的游戏语言,一旦切换语言,字典就会对不上,没有动态化
所以将其注册为函数,每次打开界面时运行,实现动态匹配多语言角色
|