找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 2743|回复: 3

[原创] 手机聊天系统教程

[复制链接]
发表于 2022-7-5 02:06:49 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

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

×
本帖最后由 blackpineapple 于 2022-7-5 14:24 编辑

效果演示



代码:

本教程主要讲如何实现通讯录部分,短信演出部分请看另外一篇。通讯录部分是由通讯录类,负责控制主角的联系人。联系人类,储存联系人相关的信息。通讯录的现实部分,这三部分构成的。本教程的最后附加了再script里需要添加的代码。



通讯录类:

通讯录类主要负责增加/删除联系人,以及获得该联系人当前的信息脚本的label名字。
[RenPy] 纯文本查看 复制代码
## Contact类:这个类负责通讯录的后端逻辑。
init -1 python:
    class Contact(object):
        def __init__(self, main_character, npcs=[]):
            ## 主角的名字,因为主角在发短信的屏幕和别人的显示不同,这里记录下主角的名字。
            self.mc = main_character

            ## 用字典储存联系人,键是人名字的字符串,值是NPC对象。
            self.contacts = dict()

            ## 是否在nvl mode启用这个phone的屏幕。
            ## 如果不在游戏里使用nvl,可以直接改自带的nvl screen
            self.enable_phone_mode = True

            ## 初始化主角的通讯录,添加NPC到通讯录中。
            for npc in npcs:
                self.add(npc)

        ## 添加一个联系人到通讯录。参数是一个NPC对象。
        def add(self, npc):
            self.contacts[npc.name] = npc

        ## 从通讯录删掉一个联系人。参数是一个字符串,npc的名字。
        def remove(self, name):
            del self.contacts[name]

        ## 通过NPC名字的字符串获得NPC对应的头像,用于聊天对话显示。
        def get_icon(self, name):
            if name == self.mc:
                return self.mc.img

            return self.contacts[name].img

        ## 获得和联系人短信脚本的label的名字,是一个字符串。
        def get_message(self, name):
            return self.contacts[name].get_message()

联系人类

主要用来储存联系人的相关信息。
[RenPy] 纯文本查看 复制代码
init -1 python:
    class NPC(object):
        def __init__(self, name, bio="", texting_default="texting_default",
             friendship=0):

            ## NPC的名字
            self.name = name

            ## NPC的图标头像,可以修改下面这个去符合你自己的需求。
            self.img = name + "_icon.png"

            ## NPC的签名,在通讯录中显示的一句话。
            self.bio = bio

            ## 主角和NPC的友好度。通讯录会按照友好度排序显示。
            self.friendship = friendship

            ## 点击联系人,有3种信息显示。
            ## 主角给联系人发信息的演出,联系人并不会提示有新信息。
            ## 主角收到了联系人发的信息,会有信息提示。
            ## 缺省信息演出,如果系统中并没有前两种信息,但是玩家点了联系人,就会演出一个
            ## 缺省的信息对话。
            self.texting_send = []
            self.texting_recieved = []
            self.texting_default = texting_default

        ## 判断是否有新信息。
        def has_new_message(self):
            return len(self.texting_send) > 0

        ## 联系人列表的排列顺序,如果有新消息就排列在前面,除此之外按照友好度排列。
        ## 假设没有友好度会达到10000
        def sort_score(self):
            score = 10000 if self.has_new_message() else 0
            return self.friendship + score

        ## 获得和联系人的信息的label的名字。
        def get_message(self):
            if self.texting_send:
                return self.texting_send[0]

            if self.texting_recieved:
                return self.texting_recieved[0]

            return self.texting_default

        ## 删掉已经演出过的短信剧本。
        def pop_message(self):
            if self.texting_send:
                self.texting_send.pop(0)

            if self.texting_recieved:
                self.texting_recieved.pop(0)

主界面代码:

包含一个手机按钮,可以呼出通讯录界面。
[RenPy] 纯文本查看 复制代码
screen main:
    zorder 1
    modal True

    hbox:
        add "naruto.png"

        imagebutton:
            ycenter 0.1
            xalign 0.7
            idle "phone.png"
            hover im.MatrixColor("phone.png", im.matrix.brightness(0.1))
            focus_mask True
            action [Hide('main'), Jump('contact')]

通讯录界面

[RenPy] 纯文本查看 复制代码
screen contact:
    zorder 1
    modal True

    default contacts = contact.contacts.values()
    ## 按照友好度排列联系人。
    python:
        contacts = sorted(contacts, key=lambda x: x.sort_score(), reverse=True)

    button:
        xysize (1280, 720)
        action [Hide('contact'), Return()]

    frame at phone_appear:
        background "phone_background.png"
        foreground "phone_foreground.png"
        xalign 0.3

        ysize 600
        xsize 350

        vpgrid:
            yoffset 120
            xalign 0.5
            cols 1
            draggable True
            mousewheel True
            ysize 500

            for idx, npc in enumerate(contacts):
                button:
                    action [Hide('contact'), Jump(
                        contact.get_message(npc.name))]
                    hover_sound "audio/click.mp3"
                    vbox:
                        spacing 10
                        hbox:
                            spacing 10
                            add npc.img
                            vbox:
                                hbox:
                                    spacing 10
                                    text npc.name:
                                        color "#f59a61"
                                        hover_color "#61aef5"
                                        size 15
                                    if npc.has_new_message():
                                        text "(New)":
                                            color "#f41161"
                                            hover_color "#eb6d99"
                                            size 15
                                text npc.bio:
                                    color "#000"
                                    size 15
                        if idx != len(contacts) - 1:
                            add Solid("#f59a61", xsize=300, ysize=3)

transform phone_appear:
    xcenter 0.3
    yalign 0.5

    on show:
        yoffset 720
        easein_back 1.0 yoffset -50

script
[RenPy] 纯文本查看 复制代码
## 很重要,没有下面的4行,NVL会没有办法清理掉。
init python:
    config.empty_window = nvl_show_core
    config.window_hide_transition = dissolve
    config.window_show_transition = dissolve

define n_nvl = Character("naruto", kind=nvl,callback=Phone_SendSound)
define e_nvl = Character("iruka", kind=nvl, callback=Phone_ReceiveSound)

default NARUTO = NPC('naruto')
default IRUKA = NPC('iruka', "今天也要好好学习。", friendship=5)
default NAVI = NPC('navi', "通讯记录刷新中", friendship=1)
default KAKASHI = NPC('kakashi', "周刊征稿,请联系我。", friendship=2)
default C_1 = NPC('c1', "自我介绍")
default C_2 = NPC('c2', "关于我的关键字")
default C_3 = NPC('c3', "随便写写")
default C_4 = NPC('c4', "这个是我的网站")
default C_5 = NPC('c5', "小猫咪能有什么坏心眼")

## 初始化通讯录
default contact = Contact(NARUTO, [IRUKA, NAVI, KAKASHI, C_1, C_2, C_3, C_4, C_5])

#Skip the main menu 跳过主菜单
label main_menu:
    return

label start:
    ## 增加一条IRUKA的信息,label的名字是script_1
    python:
        IRUKA.texting_send.append('script_1')
    scene bg with dissolve
    pause 1.0
    jump main
    return

## 有手机按钮的界面
label main:
    show screen main
    pause
    jump main
    return

## 通讯录界面
label contact:
    show screen contact
    pause
    jump contact
    return

label script_1:
    ## 因为运行到了这个地方,删掉这条信息。否则会重复演出此条信息。
    $ IRUKA.pop_message()
    n_nvl "这是一个关于手机短信的演示视频。"
    n_nvl "实在不知道写点什么文案比较好。"
    e_nvl "哦"
    n_nvl "下面测试一个图片发送的效果。"
    e_nvl "{image=picture.png}"
    n_nvl "而且还可以发表情。"
    e_nvl "{image=emoji/slightly_happy.png}"
    n_nvl "也可以发一段非常长的发言。不如这一条。但是我真的不会写小作文,不如就复制粘贴达到一百字好了,但是这样真的非常敷衍诶。或者可以去找一个小作文生成器来帮忙,能不能找个文案帮帮忙?"
    e_nvl "请不要在意文案的具体内容。"

    ## NVl的选择枝的写法。
    menu(nvl=True):
        "这是选项1":
            n_nvl "选择了选项1"
            e_nvl "是的"
        "这是选项2":
            n_nvl "选择了选项2"
            e_nvl "是的"
        "这是选项3":
            n_nvl "选择了选项3"
            e_nvl "是的"


    n_nvl "这是之后一行了。"

    ## 清理掉nvl的演出。
    nvl clear
    window hide
    jump contact
    return


短信演出

本代码是二改自renpy官方论坛lemmasoft的帖子
https://lemmasoft.renai.us/forums/viewtopic.php?f=51&t=62837%20

手机短信的演出其实就是基于NVL 模式的改写。NVL model的详细教程,请参考官方文档(https://doc.renpy.cn/zh-CN/nvl_mode.html)。
打开你自己的工程或者新建一个工程,找到screens 这个文件,搜索nvl会找到下面的代码。

[RenPy] 纯文本查看 复制代码

## NVL 模式屏幕 ####################################################################
##
## 此屏幕用于 NVL 模式的对话和菜单。
##
## [url=https://www.renpy.org/doc/html/screen_special.html#nvl]https://www.renpy.org/doc/html/screen_special.html#nvl[/url]


screen nvl(dialogue, items=None):

    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)

        ## 如果给定,则显示“menu”。 如果“config.narrator_menu”设置为“True”,
        ## 则“menu”可能显示不正确,如前述。
        for i in items:

            textbutton i.caption:
                action i.action
                style "nvl_button"

    add SideImage() xalign 0.0 yalign 1.0


screen nvl_dialogue(dialogue):

    for d in dialogue:

        window:
            id d.window_id

            fixed:
                yfit gui.nvl_height is None

                if d.who is not None:

                    text d.who:
                        id d.who_id

                text d.what:
                    id d.what_id

这个代码就是Ren'Py 自带的NVL模式的实现,如果想做什么类似于NVL的效果,就可以基于这个屏幕去修改。请对比这个界面的写法和修改后的写法。

对于我们这个聊天的显示要做如下修改

[RenPy] 纯文本查看 复制代码
screen nvl(dialogue, items=None):

    if contact.enable_phone_mode:
        use phone_dialogue(dialogue, items)

增加一个判断,如果是手机模式,就用手机的界面。contact是教程1中通讯录的对象。

手机短信的主要屏幕和效果

[RenPy] 纯文本查看 复制代码
## 用回调函数来增加收/发信息的音效。
init -1 python:
    def Phone_ReceiveSound(event, interact=True, **kwargs):
        if event == "show_done":
            renpy.sound.play("audio/ReceiveText.ogg")
    def Phone_SendSound(event, interact=True, **kwargs):
        if event == "show_done":
            renpy.sound.play("audio/SendText.ogg")

## 消息出现的动态效果。因为可能是收也可能是发
## 用direction来决定出现的方向。
transform message_appear(direction):
    alpha 0.0
    xoffset 50 * direction
    parallel:
        ease 0.5 alpha 1.0
    parallel:
        easein_back 0.5 xoffset 0

screen phone_dialogue(dialogue, items=None):
    frame:
        background "phone_background.png"
        foreground "phone_foreground.png"

        ysize 600
        xsize 350

        xcenter 0.3
        yalign 0.5

        viewport:
            yfill True
            xfill True
            ysize 550
            yoffset 80
            draggable True
            mousewheel True
            yinitial 1.0

            vbox:
                spacing 10
                xfill True

                null height 20
                use nvl_phonetext(dialogue, items)
                null height 20

                ## 负责显示选项的部分
                for i in items:
                    button:
                        action i.action
                        xalign 0.15
                        xysize (265,52)
                        background "choice_bg.png"
                        hover_background "choice_bg_hover.png"
                        hover_sound "audio/click.mp3"
                        text i.caption:
                            align (0.5,0.5)
                            text_align 0.5
                            size 15
                            color "#000"

screen nvl_phonetext(dialogue, items=None):
    style_prefix None

    ## dialogue 是对话,items是负责选项的部分。
    for d in dialogue:
        ## 如果是旁白,这样显示。
        if d.who == None:
            text d.what:
                xpos -230
                xsize 250
                text_align 0.5
                color "#000"
                italic True
                size 15
                slow_cps False
                id d.what_id
                if d.current:
                    at transform:
                        alpha 0.0
                        yoffset -50

                        parallel:
                            ease 0.5 alpha 1.0
                            easein_back 0.5 yoffset 0
        else:
            hbox:
                xpos 18
                spacing 10

                ## 判断现在说话的人是不是主角,因为主角的显示方式和其他人不同。
                ## 比如主角头像的方向和其他说话人不同。
                if d.who == contact.mc.name:
                    $ message_frame = "send_base.png"
                    $ direction = 1

                    ## 因为主角是先显示对话框再显示头像,所以这里用下面这个参数。
                    box_reverse True
                else:
                    $ message_frame = "text_base.png"
                    $ direction = -1

                $ message_icon = d.who + "_icon.png"

                ## 有选择枝的时候,items不为空,但是current为True,会重新加载
                ## 最后一句对话。为了避免重新加载,需要判断是否有选择枝。
                add message_icon:
                    if d.current and not items:
                        at transform:
                            zoom 0.0
                            ease_back 0.5 zoom 1.0

                frame:
                    yalign 1.0
                    padding (10,10)
                    background Frame(message_frame, 12,12,12,12)
                    xsize 210

                    if d.current and not items:
                        at message_appear(direction)

                    text d.what:
                        pos (0,0)
                        xsize 190
                        size 15
                        slow_cps False
                        color "#000"
                        id d.what_id






评分

参与人数 1活力 +300 干货 +3 收起 理由
被诅咒的章鱼 + 300 + 3 楼主辛苦了!

查看全部评分

 楼主| 发表于 2022-10-9 01:52:26 | 显示全部楼层
Chaos♪ 发表于 2022-10-8 00:38
大佬 可否实现通讯录里的不同npc有独立的消息存储(不清除nvl记录),不然会出现和A的对话出现在和B对话的 ...

都可以做,但是要重新设计显示和储存逻辑。
我做可能也要1周,不过我现在没这个整块时间。

你可以通过自学python和界面语言来实现这个功能。
回复 支持 1 抱歉 0

使用道具 举报

发表于 2022-10-8 00:38:15 | 显示全部楼层
大佬 可否实现通讯录里的不同npc有独立的消息存储(不清除nvl记录),不然会出现和A的对话出现在和B对话的上面(共用一个容器的感觉)
另外在前面基础上 超出屏幕的nvl信息好像就销毁了(滑动翻页也翻不回来),这部分有办法解决么
以及如下设置了滑动翻页后viewport区域就只有滑动没办法点击了,有没有办法可以点击和滑动并存
            draggable True
            mousewheel True
问题有点多 希望不要嫌烦
万分感谢!!!!!
回复 支持 抱歉

使用道具 举报

 楼主| 发表于 2023-8-31 06:25:51 | 显示全部楼层
Chaos♪ 发表于 2022-10-8 00:38
大佬 可否实现通讯录里的不同npc有独立的消息存储(不清除nvl记录),不然会出现和A的对话出现在和B对话的 ...

独立消息存储,可以实现,但是需要比较完备的编程基本知识。

滑动其实会有问题,就是信息演出会重新演出一遍。

又点又滑动可能需要用vpgrid,而不是现在这种viewport。

这个基本是比较难写的,如果入门,建议简化需求。

回复 支持 抱歉

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|RenPy中文空间 ( 苏ICP备17067825号|苏公网安备 32092302000068号 )

GMT+8, 2024-3-29 17:54 , Processed in 0.033105 second(s), 14 queries , File On.

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表