找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 600|回复: 7

[原创] 物品及物品栏系统及使用教程

[复制链接]
发表于 2026-8-11 11:25:06 | 显示全部楼层 |阅读模式

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

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

×
本帖最后由 甲醇水溶液 于 2026-8-12 10:44 编辑

1.png
许久之前在论坛分享了物品栏的大概思路,但并不完善,且那个物品栏的用途为解谜游戏,而不是传统的“背包”系统。
因此在这分享自己写的框架以及使用教程。
首先,我们需要为物品写一个类:
[RenPy] 纯文本查看 复制代码
init -1 python:
    class Item:
        """
        基础物品类
        :item_id(str) :物品ID,每个物品应有独立且唯一的ID
        :name(str) :物品名称
        :purpose :物品用途,将在物品介绍界面显示
        :description :物品描述,将在物品介绍界面显示
        :type :物品类型,将在物品介绍界面显示
        :quantity :未使用
        :image :在物品栏时的图片路径
        :image_show :物品大图路径
        :func_throw :物品被丢弃时触发的函数,默认为"throw_item"
        :func_use :物品被使用时触发的函数,默认为"use_item"
        """
        def __init__(
            self, item_id, name, description=None,
            purpose="无",
            type="物品",
            image="",
            image_show="",
            func_throw="throw_item",
            func_use="use_item",
            ):
            self.item_id = item_id
            self.name = name
            self.purpose = purpose
            self.description = description
            self.type = type
            self.quantity = 1
            self.image = image
            self.image_show = image_show
            self.func_throw = func_throw
            self.func_use = func_use

可以注意到我添加了一些属性,它们用于在screen展示对应的信息

这里以图中的测试物品为例:
2.png
你可以这样定义一个“测试物品1”:
[RenPy] 纯文本查看 复制代码
testItem1 = Item(
        item_id="test-001",
        name="测试物品1",
        purpose="若系统正常,这个物品只能被使用而不能被丢弃",
        func_throw = None,
        description="如你所见,这是一个测试物品。它唯一的用处就是用来测试物品栏系统是否正常运转。"
    )

item_id用于在物品栏确认物品,毕竟可能存在俩个同名的物品,它用于避免这种情况
接下来,我们为物品栏写一个类,用于存放物品
[RenPy] 纯文本查看 复制代码
class PlayerHand:
        """
        玩家物品栏类
        eg: player_hand = PlayerHand()
        
        :param max_slots: 槽位总数,默认 8
        每个槽位存储格式: [item_obj, count] 或 None(空槽)
        """

        def __init__(self, max_slots=8):
            self.slots = [None] * max_slots
            self.max_slots = max_slots

        def add(self, item, count=1, slot_index=None):
            """
            放入物品。
            :param item: 物品对象
            :param count: 数量,默认 1
            :param slot_index: 指定槽位 0~7;若为None,则自动放入第一个空槽
            :return: True or False
            """
            if not hasattr(item, 'item_id'):
                print("添加失败:传入的对象缺少 item_id")
                return False

            if slot_index is not None:
                if not (0 <= slot_index < self.max_slots):
                    print(f"添加失败:槽位索引 {slot_index} 超出范围 0~{self.max_slots - 1}")
                    return False
                if self.slots[slot_index] is not None:
                    return False  # 该槽位已被占用
                self.slots[slot_index] = [item, count]
                return True
            
            # 自动寻找并堆叠
            for i in range(self.max_slots):
                if self.slots is not None and item.item_id == self.slots[0].item_id:
                    self.slots[1] += count
                    return True

            # 自动寻找第一个空槽
            for i in range(self.max_slots):
                if self.slots is None:
                    self.slots = [item, count]
                    return True

            print("添加失败:物品栏已满")
            return False

        def remove(self, slot_index, count=1):
            """
            从指定槽位移除指定数量的当前物品 
            :return: 
                - 数量足够/刚好用完:返回物品对象
                - 槽位为空 或 数量不够:返回 None
            """
            if not (0 <= slot_index < self.max_slots):
                return None

            slot = self.slots[slot_index]
            if slot is None:
                return None

            item, current = slot
            if current < count:
                return None
            elif current > count:
                slot[1] -= count
                return item
            else:
                self.slots[slot_index] = None
                return item

        def remove_by_item(self, item, count=1):
            """
            按物品对象移除(遍历所有槽位)。
            :return: 成功返回物品对象,失败返回 None
            """
            for i in range(self.max_slots):
                slot = self.slots
                if slot is not None and slot[0].item_id == item.item_id:
                    return self.remove(i, count)
            return None

        def has(self, item):
            """检查任意槽位中是否存在该物品"""
            for i in range(self.max_slots):
                if self.slots is not None and item.item_id == self.slots[0].item_id:
                    return True
            return False

        def get(self, slot_index):
            """
            根据槽位索引取出物品对象(不移除)。
            找不到则返回 None
            """
            if 0 <= slot_index < self.max_slots and self.slots[slot_index] is not None:
                return self.slots[slot_index][0]
            return None

        def find(self, item_id):
            """
            查找物品所在的第一个槽位索引。
            :return: 槽位索引 0~7,找不到返回 -1
            """
            for i, slot in enumerate(self.slots):
                if slot is not None and slot[0].item_id == item_id:
                    return i
            return -1

        def is_full(self):
            """是否所有槽位都已占满"""
            return all(slot is not None for slot in self.slots)

        def is_empty(self):
            """是否所有槽位都是空的"""
            return all(slot is None for slot in self.slots)

        def __len__(self):
            """返回已使用的槽位数"""
            return sum(1 for slot in self.slots if slot is not None)

        def __contains__(self, item_id):
            """eg: 'key' in player_hand"""
            return self.has(item_id)

        def __repr__(self):
            """在控制台输入定义的物品栏名即可查看"""
            lines = []
            for i, slot in enumerate(self.slots):
                if slot is not None:
                    item_obj, count = slot
                    lines.append(f"[{i}] {item_obj.name} x{count}")
                else:
                    lines.append(f"[{i}] (空)")
            return "\n".join(lines)

并不是所有的函数都将被使用,有一些函数因为需要适配我自己的游戏而被添加了进去
这里举几个简单的例子:
首先,我们定义一个物品栏
[RenPy] 纯文本查看 复制代码
smallball_hand = PlayerHand()

然后,我们在游戏里按[Shift+O]打开renpy的控制台,输入定义的物品栏名称,若先前的操作正确,你将看到以下信息:
3.png
物品栏默认为八槽,当然,可以自行修改将上限提高,把它变成一个背包。个人认为八槽物品栏最为美观。
物品栏的槽位都是空的,让我们使用add函数为物品栏添加刚刚定义的[测试物品1]
[RenPy] 纯文本查看 复制代码
smallball_hand.add(testItem1,5,4)

在控制台输入这个语句,将在物品栏的[4]号槽位添加5个测试物品1
如果你想要在物品栏移除特定的物品,可以这样做:
[RenPy] 纯文本查看 复制代码
smallball_hand.remove_by_item(testItem1,4)

这个语句将移除4个[测试物品1],
需要注意的是,如果移除的数量超过物品的当前数量,并不会将其全部移除,而是返回None。
remove_by_item运用于玩家消耗物品的情景,假设玩家需要消耗5个金币才能开门却只有4个金币,应该拒绝移除物品,而不是消耗全部的4个金币。
下面是一个物品栏界面(Screen)的例子:
[RenPy] 纯文本查看 复制代码
default bag_selected = 0
screen bag_screen(hand=smallball_hand):
    zorder 50
    frame:
        xalign 0.5
        ypos 0.8
        xysize(0.8,0.2)
        background "#971e1e"
        # 物品网格
        grid 8 1:
            spacing 25
            xalign 0.5
            yalign 0.5
            for i in range(8):
                $ slot = hand.slots if i < len(hand.slots) else None
                button:
                    xsize 150
                    ysize 150
                    if bag_selected == i:
                        background Frame("#fff306b0", 10, 10)
                    else:
                        background Frame("#493e3f", 10, 10)
                    hovered [
                        SetVariable("bag_selected",i),
                    ]

                    text str(i+1):
                        align (1.0, 1.0)
                        offset (-2, -2)
                        size 25
                        color "#fff"
                        outlines [(1, "#000")]

                    if slot is not None:
                        add slot[0].image:
                            align (0.5, 0.5)
                            at transform:
                                zoom 0.8
                                on hover:
                                    easein 0.5 zoom 1.0
                                on idle:
                                    easein 0.5 zoom 0.8
                    action [
                        NullAction(),
                    ]

如果你没有在物品栏里看到物品,可能是没有给物品定义对应的image路径。
建议在类中为物品定义默认的图片路径以方便测试
如何在screen里实现物品的“使用”和“丢弃”:
4.png
我不会在此给出完整的screen代码,因为涉及到其它不相关的代码逻辑,因此我会着重讲相关逻辑的部分
也许你已经注意到了,在物品类中有俩个属性func_throwfunc_use。它们的默认值为use_itemthrow_item
它们为函数名,当物品被使用/丢弃时将触发对应的函数
[RenPy] 纯文本查看 复制代码
init -1 python:
    def use_item(item,user):
        if item is None:
            return False
        renpy.play("audio/sound/up.wav")
        return True
    def throw_item(item,user):
        if item is None:
            return False
        renpy.play("audio/sound/down.wav")
        return True


    def use_Handitem(hand,slot_index,user=None):
        renpy.notify(f"使用")
        slot = hand.slots[slot_index]
        if slot is None:
            renpy.play("audio/sound/worse.wav")
            renpy.notify(f"> 你不能使用一个不存在的东西")
            return

        item, count = slot

        if item.func_use is None:
            renpy.play("audio/sound/worse.wav")
            renpy.notify(f"> [{item.name}]不能在这使用")
            #该物品不能使用
            return

        func = getattr(renpy.store, item.func_use, None)

        if func is None:
            renpy.notify(f"> 错误:找不到函数 {item.func_use}")
            return

        # 调用函数
        consumed = func(item, user)
        renpy.notify(f"{consumed}")
        # 如果返回 True,减少数量或移除
        if consumed == True:
            renpy.notify(f"{item.name} 使用成功")
            hand.remove(slot_index, 1)
            return
        return

    def throw_Handitem(hand,slot_index,user=None):

        slot = hand.slots[slot_index]
        if slot is None:
            renpy.play("audio/sound/worse.wav")
            renpy.notify(f"> 这里已经一无所有了")
            return

        item, count = slot

        if item.func_throw is None:
            renpy.play("audio/sound/worse.wav")
            renpy.notify(f"> [{item.name}]不能被丢弃")
            #该物品不能使用
            return

        func = getattr(renpy.store, item.func_throw, None)

        if func is None:
            renpy.notify(f"> 错误:找不到函数 {item.func_throw}")
            return

        # 调用函数
        consumed = func(item, user)
        renpy.notify(f"{consumed}")
        # 如果返回 True,减少数量或移除
        if consumed == True:
            renpy.notify(f"{item.name} 被丢弃")
            hand.remove(slot_index, 1)
            return
        return


我在函数里使用了renpy.play用于播放音效,使用时应将路径改为你的音效路径或直接注释
renpy.notify用于在左上角显示一个消息提示。
下面是一个“使用”“丢弃”按钮的示例:
[RenPy] 纯文本查看 复制代码
textbutton "使用":
    hovered [SetVariable("bag_selected",-1),Play("sound","audio/sound/btn.wav")]
    if bag_selected == -1:
        text_color "#fff306f8"
    action [
        Function(use_Handitem, hand=hand, slot_index=curItemSlot)
    ]
textbutton "丢弃":
    hovered [SetVariable("bag_selected",-2),Play("sound","audio/sound/btn.wav")]
    if bag_selected == -2:
        text_color "#fff306f8"
    action [
        Function(throw_Handitem, hand=hand, slot_index=curItemSlot)
    ]

curItemSlot为对应物品的物品槽序号
也许你注意到了先前定义的[测试物品1]不能被丢弃。
如果你想要一个物品不能被使用/丢弃,应该在定义时把对应的func_use/func_throw设为None

[RenPy] 纯文本查看 复制代码
    testItem1 = Item(
        item_id="test-001",
        name="测试物品1",
        purpose="若系统正常,这个物品只能被使用而不能被丢弃",
        func_throw = None,
        description="如你所见,这是一个测试物品。它唯一的用处就是用来测试物品栏系统是否正常运转。"
    )

    testItem2 = Item(
        item_id="test-002",
        name="测试物品2",
        purpose="若系统正常,这个物品只能被丢弃而不能被使用",
        func_use = None,
        description="如你所见,这是一个测试物品。它唯一的用处就是用来测试物品栏系统是否正常运转。"
    )


最后,在编写物品界面的时候,也许应该考虑一个“不存在的物品”。我在写相关函数的时候考虑到了这种情况,但我尚未开始测试。



评分

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

查看全部评分

 楼主| 发表于 2026-8-11 11:25:07 | 显示全部楼层
(你不能使用一个不存在的物品)
5.png
回复 支持 抱歉

使用道具 举报

 楼主| 发表于 2026-8-11 11:29:59 | 显示全部楼层
本帖最后由 甲醇水溶液 于 2026-8-11 11:31 编辑

不知道为什么代码在帖子编辑后会有奇怪的 i .....
回复 支持 抱歉

使用道具 举报

发表于 2026-8-11 17:21:04 来自手机 | 显示全部楼层
本帖最后由 BuErShen 于 2026-8-11 17:25 编辑
甲醇水溶液 发表于 2026-8-11 11:29
不知道为什么代码在帖子编辑后会有奇怪的 i .....

复制粘贴时本身没有【i】【/i】?(这里用【】替代)
在 Discuz! X 帖子中【i】【/i】标签的作用是将文字设置为斜体。
回复 支持 抱歉

使用道具 举报

 楼主| 发表于 2026-8-12 10:46:53 | 显示全部楼层
BuErShen 发表于 2026-8-11 17:21
复制粘贴时本身没有【i】【/i】?(这里用【】替代)
在 Discuz! X 帖子中【i】【/i】标签的作用是将文字 ...

是的,我在编辑的时候没有取消文字的斜体。现在代码应该不会有i了
回复 支持 抱歉

使用道具 举报

发表于 2026-8-14 09:51:44 | 显示全部楼层
一个建议:
func_use/func_throw 改为callback式的函数或行为列表,根据不同场景和需要append或remove列表中的元素,更灵活。
回复 支持 抱歉

使用道具 举报

发表于 2026-8-16 19:43:39 | 显示全部楼层
我顶!
回复

使用道具 举报

发表于 2026-8-22 20:12:17 | 显示全部楼层
回复

使用道具 举报

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

本版积分规则

小黑屋|手机版|RenPy中文空间 ( 苏ICP备17067825号 )

GMT+8, 2026-9-6 16:54 , Processed in 0.027608 second(s), 10 queries , Redis On.

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

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