找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 448|回复: 6

[原创] RenPyUtil:AdvancedCharacter高级角色对象

[复制链接]
发表于 2023-10-6 22:54:00 | 显示全部楼层 |阅读模式

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

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

×
本帖最后由 ZYKsslm 于 2024-4-25 02:20 编辑

一个在Github上新开的造轮子项目:RenPyUtil

正如标题所述,最近在Github上开了个新项目:RenPyUtil,旨在造更多轮子给Ren'Py开发者以实现更多可能。项目地址:Github仓库
如果无法正常访问Github也没关系,以后更新项目也会同时在论坛发布。

📖 说明
该项目使用 MIT 协议开源,但若使用需要在程序中标明。


RenPyUtil.advanced_character模块源码:


[RenPy] 纯文本查看 复制代码
# 此文件提供了一系列基于Ren'Py的功能类,以供Ren'Py开发者调用
# 作者  ZYKsslm
# 仓库  [url]https://github.com/ZYKsslm/RenPyUtil[/url]
# 声明  该源码使用 MIT 协议开源,但若使用需要在程序中标明作者信息


# 对话组使用的transform
"""renpy
transform emphasize(t, l):
    linear t matrixcolor BrightnessMatrix(l)
"""

"""renpy
init -1 python:
"""


import random
from functools import partial


def threading_task(func):
    """一个装饰器,被装饰的函数将在子线程中运行。
    
    被装饰的函数使用`Ren'Py API`能做到事情非常有限,通常用来调用`renpy.notify()`函数或实时更新变量。

    Arguments:
        func -- 一个函数。
    """        

    def wrapper(*args, **kwargs):
        renpy.invoke_in_thread(func, *args, **kwargs)
        
    return wrapper


class CharacterError(Exception):
    """该类为一个异常类,用于检测角色对象。"""

    errorType = {
        0: "错误地传入了一个ADVCharacter类,请传入一个AdvancedCharacter高级角色类!",
        1: "对象类型错误!",
        2: "该角色对象不在角色组内!",
        3: "该角色对象无图像标签,无法强调!"
    }

    def __init__(self, errorCode):
        super().__init__()
        self.errorCode = errorCode

    def __str__(self):
        return CharacterError.errorType[self.errorCode]


class CharacterTask(object):
    """该类为角色任务类,用于高级角色对象绑定任务。"""        

    def __init__(self, single_use=True, *args, **kwargs):
        
        """初始化一个任务。
        
        Keyword Arguments:
            single_use -- 该任务是否只执行一次。 (default: {True})
        
        Example:
            ```python
            eg_task = CharacterTask("example_task", True,
                health,
                strength=100,
            )

            eg_task.add_func(eg_func1, *args, **kwargs)
            eg_task.add_func(eg_func2, *args, **kwargs)
            ```
        """            

        self.attrs_pattern = {}
        for a in args:
            self.attrs_pattern[a] = None
        self.attrs_pattern.update(kwargs)

        self.single_use = single_use
        self.func_list: list[tuple[str, partial]] = []
        self.func_return = {}

    def add_func(self, func, *args, **kwargs):
        """调用该方法,给任务绑定一个函数。若函数有返回值,则返回值储存在对象的`func_return`属性中。
        
        `func_return`是一个键为函数名,值为函数返回值的字典。
        在子线程中运行的函数无法取得返回值。

        Arguments:
            func -- 一个函数。

        不定参数为函数参数。
        """            

        self.func_list.append((func.__name__, partial(func, *args, **kwargs)))

        self.func_return.update(
            {
                func.__name__: None
            }
        )


class AdvancedCharacter(ADVCharacter):
    """该类继承自ADVCharacter类,在原有的基础上增添了一些新的属性和方法。"""

    def __init__(self, name=None, kind=None, **properties):
        """初始化方法。若实例属性需要被存档保存,则定义对象时请使用`default`语句或Python语句。

        Keyword Arguments:
            name -- 角色名。 (default: {NotSet})
            kind -- 角色类型。 (default: {None})
        """

        if not name:
            name = renpy.character.NotSet

        self.task_list: list[CharacterTask] = []
        self.customized_attr_dict = {}
        super().__init__(name=name, kind=kind, **properties)

    def _emphasize(self, emphasize_callback, t, l):
        """使角色对象支持强调。"""

        if self.image_tag:
            self.display_args["callback"] = partial(emphasize_callback, self, t=t, l=l)
        else:
            raise CharacterError(3)

    def add_task(self, task: CharacterTask):
        """调用该方法,绑定一个角色任务。

        Arguments:
            task -- 一个角色任务。
        """            

        self.task_list.append(task)

    def add_attr(self, *args, **kwargs):
        """调用该方法,给该角色对象创建自定义的一系列属性。

        属性可以无初始值。

        Example:
            character.add_attr(strength, health=5)
            character.add_attr(strength=100, health=100)     
        """

        attrs = {}
        for attr in args:
            attrs[attr] = None

        attrs.update(kwargs)

        for a, v in attrs.items():
            self.set_attr(a, v)

    def set_attr(self, attr, value):
        """调用该方法,修改一个自定义属性的值。若没有该属性则创建一个。

        Arguments:
            attr -- 自定义属性名。
            value -- 要赋予的值。
        """

        setattr(self, attr, value)
        self.customized_attr_dict[attr] = value

    def _check_task(self, attr, value):
        """该方法用于在更新自定义属性值时触发任务。"""

        for task in self.task_list:

            for attr, value in task.attrs_pattern.items():
                if getattr(self, attr) != value:
                    break
                    
            else:
                for i in task.func_list:
                    name, func = i
                    func_return = func()
                
                    if func_return:
                        task.func_return[name] = func_return

                if task.single_use:
                    self.task_list.remove(task)

    def __setattr__(self, attr, value):
        """该方法用于在设置自定义属性值时触发任务。"""

        super().__setattr__(attr, value)
        self._check_task(attr, value)

    def get_customized_attr(self):
        """调用该方法,返回一个键为自定义属性,值为属性值的字典,若无则为空字典。

        Returns:
            一个键为自定义属性,值为属性值的字典。
        """

        return self.customized_attr_dict


class CharacterGroup(object):
    """该类用于管理多个高级角色(AdvancedCharacter)对象。"""

    def __init__(self, *characters: AdvancedCharacter):
        """初始化方法。"""

        self.character_group: set[AdvancedCharacter] = set()
        self.add_characters(*characters)

    @staticmethod
    def  _type_check(obj):
        """检查对象类型。"""        

        if isinstance(obj, AdvancedCharacter):
            return
        elif (not isinstance(obj, AdvancedCharacter)) and (isinstance(obj, ADVCharacter)):
            raise CharacterError(0)
        else:
            raise CharacterError(1)

    def add_characters(self, *characters: AdvancedCharacter):
        """调用该方法,向角色组中添加一个或多个角色对象。"""

        for character in characters:
            CharacterGroup._type_check(character)
            self.character_group.add(character)
         
    def get_random_character(self, rp=True):
        """调用该方法,返回角色组中随机一个角色对象。

        Keyword Arguments:
            rp -- 是否使用`renpy`随机数接口。 (default: {True})
        """        

        choice = renpy.random.choice if rp else random.choice
        
        return choice(self.character_group)

    def del_characters(self, *characters: AdvancedCharacter):
        """调用该方法,删除角色组中的一个或多个角色。"""
        
        for character in characters:
            CharacterGroup._type_check(character)
            self.character_group.remove(character)

    def add_group_attr(self, *args, **kwargs):
        """调用该方法,对角色组中所有角色对象创建自定义的一系列属性。

        Example:
            character_group.add_group_attr(strength, health=5)
            character_group.add_group_attr(strength=100, health=100)
        """

        attrs = {}
        for a in args:
            attrs[a] = None
        attrs.update(kwargs)

        for character in self.character_group:
            character.add_attr(*args, **kwargs)

    def set_group_attr(self, attr, value):
        """调用该方法,更改角色组中所有角色对象的一项自定义属性值。若没有该属性,则创建一个。

        Arguments:
            attr -- 自定义属性名。
            value -- 自定义属性值。
        """

        for character in self.character_group:
            character.set_attr(attr, value)
    
    def set_group_func(self, task: CharacterTask):
        """调用该方法,给所有角色组中的角色对象绑定一个任务。

        Arguments:
            task -- 一个任务。
        """

        for character in self.character_group:
            character.add_task(task)


class SpeakingGroup(CharacterGroup):
    """该类继承自CharacterGroup类,用于管理角色发言组。"""

    def __init__(self, *characters: AdvancedCharacter, t=0.15, l=-0.3):
        """初始化方法。
        
        Arguments:
            t -- 转变的时长 (default: {0.15})
            l -- 变暗的明度。 (default: {-1})
        """
        
        self.t = t
        self.l = l

        self.character_group: set[AdvancedCharacter] = set()
        self.add_characters(*characters)

    def add_characters(self, *characters: AdvancedCharacter):
        for character in characters:
            CharacterGroup._type_check(character)
            character._emphasize(self.emphasize, self.t, self.l)
            self.character_group.add(character)

    def emphasize(self, character: AdvancedCharacter, event, t=0.15, l=-0.3, **kwargs):
        """该方法用于定义角色对象时作为回调函数使用。该方法可创建一个对话组,对话组中一个角色说话时,其他角色将变暗。
        """            

        if not event == "begin":
            return

        if character not in self.character_group:
            self.add_characters(character)
        
        image = renpy.get_say_image_tag()
        if renpy.showing(character.image_tag):
            renpy.show(
                image, 
                at_list=[emphasize(t, 0)]
            )
        
        for speaker in self.character_group:
            if speaker != character and renpy.showing(speaker.image_tag):
                renpy.show(
                    speaker.image_tag, 
                    at_list=[emphasize(t, l)]
                )

评分

参与人数 1活力 +300 干货 +3 收起 理由
被诅咒的章鱼 + 300 + 3 感谢分享!

查看全部评分

 楼主| 发表于 2023-10-9 23:24:42 | 显示全部楼层
本帖最后由 ZYKsslm 于 2023-10-9 23:38 编辑

请重新查看源码,本次编辑修复了一些常规BUG并完善了一些功能,示范代码也相应的做了修改。
回复 支持 1 抱歉 0

使用道具 举报

发表于 2023-10-7 01:18:15 | 显示全部楼层
支持一下 但是还是提醒你 Ren'py 运行机制比较特别,支撑它的底层是一个非标准的Python集合,很多功能是无法做到落地的
可以做到语义上的强化,但是很难做到功能上的真正强化,相对比较好的解决方法就是 针对不同的平台做单独兼容。写三份代码。
回复 支持 抱歉

使用道具 举报

 楼主| 发表于 2023-10-7 22:07:03 | 显示全部楼层
Furau 发表于 2023-10-7 01:18
支持一下 但是还是提醒你 Ren'py 运行机制比较特别,支撑它的底层是一个非标准的Python集合,很多功能是无 ...

谢谢。我的目的就是多造点轮子,给开发者节省时间,提高开发效率。只要有人把累活干了,就不用别人再累一遍了
回复 支持 抱歉

使用道具 举报

 楼主| 发表于 2023-10-7 22:07:54 | 显示全部楼层
代码可能不完善,如果有BUG请及时回帖提醒
回复 支持 抱歉

使用道具 举报

 楼主| 发表于 2023-10-7 22:22:56 | 显示全部楼层
本帖最后由 ZYKsslm 于 2024-4-25 02:20 编辑

这里有一个使用示范:


[RenPy] 纯文本查看 复制代码
init python:
 
 
    # 一个任务函数
    def love_event(speaker, name):
 
        speaker(f"{name}, I love you.")
        recieve = renpy.input("So, your answer is......")
 
        return recieve

    # 使用threading_task装饰的函数将在子线程中运行
    @threading_task
    def thread_event():
        renpy.notify("Messages")
 
 
# 使用default语句定义高级角色对象
default e = AdvancedCharacter("艾琳", what_color="#FF8C00", who_color="#00CED1")
 
 
# 游戏在此开始。
 
label start:
 
    python:
        # 高级角色增添属性
        e.add_attr(love_point=50)
        e.add_attr(thread=False)
        e.add_attr(strength=100, health=40)
 
    # 输出角色所有的自定义属性及其值
    e "[e.customized_attr_dict!q]"
 
    python:
 
        # 创建一个角色任务
        love_task = CharacterTask(single_use=True, # single_use参数若为True则该任务为一次性任务
            love_point=100,
            health=50,
        )

        thread_task = CharacterTask(False, thread=True)

        # 绑定任务函数
        love_task.add_func(love_event, e, name="ZYKsslm")
        thread_task.add_func(thread_event)

        # 绑定角色任务
        e.add_task(love_task)
        e.add_task(thread_task)
 
        e.love_point += 50
        e.health += 10

        e.thread = True
 
        # 获取任务函数返回值
        recieve = love_task.func_return["love_event"]
    
    if recieve:
        e "Your answer is '[recieve!q]'"
 
    return

回复 支持 抱歉

使用道具 举报

发表于 2023-10-8 09:09:30 | 显示全部楼层
个人看法:
1. 仅作者的设计思路和编码规范就值得很多人学习和参考。
2. 高级角色类大概是给游戏添加RPG元素的通用方案,包括且不限于角色好感度系统和简单战斗等。

总之,楼主加油~
回复 支持 抱歉

使用道具 举报

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

本版积分规则

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

GMT+8, 2024-4-29 18:09 , Processed in 0.060705 second(s), 14 queries , File On.

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

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