|
|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
本帖最后由 甲醇水溶液 于 2026-8-11 11:23 编辑
许久之前在论坛分享了物品栏的大概思路,但并不完善,且那个物品栏的用途为解谜游戏,而不是传统的“背包”系统。
因此在这分享自己写的框架以及使用教程。
首先,我们需要为物品写一个类:
[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展示对应的信息
这里以图中的测试物品为例:
你可以这样定义一个“测试物品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[i] is not None and item.item_id == self.slots[0].item_id:[/i]
[i] self.slots[1] += count[/i]
[i] return True[/i]
[i] # 自动寻找第一个空槽[/i]
[i] for i in range(self.max_slots):[/i]
[i] if self.slots is None:[/i]
[i] self.slots = [item, count][/i]
[i] return True[/i]
[i] print("添加失败:物品栏已满")[/i]
[i] return False[/i]
[i] def remove(self, slot_index, count=1):[/i]
[i] """[/i]
[i] 从指定槽位移除指定数量的当前物品 [/i]
[i] :return: [/i]
[i] - 数量足够/刚好用完:返回物品对象[/i]
[i] - 槽位为空 或 数量不够:返回 None[/i]
[i] """[/i]
[i] if not (0 <= slot_index < self.max_slots):[/i]
[i] return None[/i]
[i] slot = self.slots[slot_index][/i]
[i] if slot is None:[/i]
[i] return None[/i]
[i] item, current = slot[/i]
[i] if current < count:[/i]
[i] return None[/i]
[i] elif current > count:[/i]
[i] slot[1] -= count[/i]
[i] return item[/i]
[i] else:[/i]
[i] self.slots[slot_index] = None[/i]
[i] return item[/i]
[i] def remove_by_item(self, item, count=1):[/i]
[i] """[/i]
[i] 按物品对象移除(遍历所有槽位)。[/i]
[i] :return: 成功返回物品对象,失败返回 None[/i]
[i] """[/i]
[i] for i in range(self.max_slots):[/i]
[i] slot = self.slots[/i]
[i] if slot is not None and slot[0].item_id == item.item_id:[/i]
[i] return self.remove(i, count)[/i]
[i] return None[/i]
[i] def has(self, item):[/i]
[i] """检查任意槽位中是否存在该物品"""[/i]
[i] for i in range(self.max_slots):[/i]
[i] if self.slots is not None and item.item_id == self.slots[0].item_id:[/i]
[i] return True[/i]
[i] return False[/i]
[i] def get(self, slot_index):[/i]
[i] """[/i]
[i] 根据槽位索引取出物品对象(不移除)。[/i]
[i] 找不到则返回 None[/i]
[i] """[/i]
[i] if 0 <= slot_index < self.max_slots and self.slots[slot_index] is not None:[/i]
[i] return self.slots[slot_index][0][/i]
[i] return None[/i]
[i] def find(self, item_id):[/i]
[i] """[/i]
[i] 查找物品所在的第一个槽位索引。[/i]
[i] :return: 槽位索引 0~7,找不到返回 -1[/i]
[i] """[/i]
[i] for i, slot in enumerate(self.slots):[/i]
[i] if slot is not None and slot[0].item_id == item_id:[/i]
[i] return i[/i]
[i] return -1[/i]
[i] def is_full(self):[/i]
[i] """是否所有槽位都已占满"""[/i]
[i] return all(slot is not None for slot in self.slots)[/i]
[i] def is_empty(self):[/i]
[i] """是否所有槽位都是空的"""[/i]
[i] return all(slot is None for slot in self.slots)[/i]
[i] def __len__(self):[/i]
[i] """返回已使用的槽位数"""[/i]
[i] return sum(1 for slot in self.slots if slot is not None)[/i]
[i] def __contains__(self, item_id):[/i]
[i] """eg: 'key' in player_hand"""[/i]
[i] return self.has(item_id)[/i]
[i] def __repr__(self):[/i]
[i] """在控制台输入定义的物品栏名即可查看"""[/i]
[i] lines = [][/i]
[i] for i, slot in enumerate(self.slots):[/i]
[i] if slot is not None:[/i]
[i] item_obj, count = slot[/i]
[i] lines.append(f"[{i}] {item_obj.name} x{count}")[/i]
[i] else:[/i]
[i] lines.append(f"[{i}] (空)")[/i]
[i] return "\n".join(lines)
并不是所有的函数都将被使用,有一些函数因为需要适配我自己的游戏而被添加了进去
这里举几个简单的例子:
首先,我们定义一个物品栏
[RenPy] 纯文本查看 复制代码 smallball_hand = PlayerHand()
然后,我们在游戏里按[Shift+O]打开renpy的控制台,输入定义的物品栏名称,若先前的操作正确,你将看到以下信息:
物品栏默认为八槽,当然,可以自行修改将上限提高,把它变成一个背包。个人认为八槽物品栏最为美观。
物品栏的槽位都是空的,让我们使用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[/i]
[i]screen bag_screen(hand=smallball_hand):[/i]
[i] zorder 50[/i]
[i] frame:[/i]
[i] xalign 0.5[/i]
[i] ypos 0.8[/i]
[i] xysize(0.8,0.2)[/i]
[i] background "#971e1e"[/i]
[i] # 物品网格[/i]
[i] grid 8 1:[/i]
[i] spacing 25[/i]
[i] xalign 0.5[/i]
[i] yalign 0.5[/i]
[i] for i in range(8):[/i]
[i] $ slot = hand.slots if i < len(hand.slots) else None[/i]
[i] button:[/i]
[i] xsize 150[/i]
[i] ysize 150[/i]
[i] if bag_selected == i:[/i]
[i] background Frame("#fff306b0", 10, 10)[/i]
[i] else:[/i]
[i] background Frame("#493e3f", 10, 10)[/i]
[i] hovered [[/i]
[i] SetVariable("bag_selected",i),[/i]
[i] ][/i]
[i] text str(i+1):[/i]
[i] align (1.0, 1.0)[/i]
[i] offset (-2, -2)[/i]
[i] size 25[/i]
[i] color "#fff"[/i]
[i] outlines [(1, "#000")][/i]
[i] if slot is not None:[/i]
[i] add slot[0].image:[/i]
[i] align (0.5, 0.5)[/i]
[i] at transform:[/i]
[i] zoom 0.8[/i]
[i] on hover:[/i]
[i] easein 0.5 zoom 1.0[/i]
[i] on idle:[/i]
[i] easein 0.5 zoom 0.8[/i]
[i] action [[/i]
[i] NullAction(),[/i]
[i] ]
如果你没有在物品栏里看到物品,可能是没有给物品定义对应的image路径。
建议在类中为物品定义默认的图片路径以方便测试
如何在screen里实现物品的“使用”和“丢弃”:
我不会在此给出完整的screen代码,因为涉及到其它不相关的代码逻辑,因此我会着重讲相关逻辑的部分
也许你已经注意到了,在物品类中有俩个属性func_throw和func_use。它们的默认值为use_item和throw_item
它们为函数名,当物品被使用/丢弃时将触发对应的函数
[RenPy] 纯文本查看 复制代码 init -1 python:[/i]
[i] def use_item(item,user):[/i]
[i] if item is None:[/i]
[i] return False[/i]
[i] renpy.play("audio/sound/up.wav")[/i]
[i] return True[/i]
[i] def throw_item(item,user):[/i]
[i] if item is None:[/i]
[i] return False[/i]
[i] renpy.play("audio/sound/down.wav")[/i]
[i] return True[/i]
[i] def use_Handitem(hand,slot_index,user=None):[/i]
[i] renpy.notify(f"使用")[/i]
[i] slot = hand.slots[slot_index][/i]
[i] if slot is None:[/i]
[i] renpy.play("audio/sound/worse.wav")[/i]
[i] renpy.notify(f"> 你不能使用一个不存在的东西")[/i]
[i] return[/i]
[i] item, count = slot[/i]
[i] if item.func_use is None:[/i]
[i] renpy.play("audio/sound/worse.wav")[/i]
[i] renpy.notify(f"> [{item.name}]不能在这使用")[/i]
[i] #该物品不能使用[/i]
[i] return[/i]
[i] func = getattr(renpy.store, item.func_use, None)[/i]
[i] if func is None:[/i]
[i] renpy.notify(f"> 错误:找不到函数 {item.func_use}")[/i]
[i] return[/i]
[i] # 调用函数[/i]
[i] consumed = func(item, user)[/i]
[i] renpy.notify(f"{consumed}")[/i]
[i] # 如果返回 True,减少数量或移除[/i]
[i] if consumed == True:[/i]
[i] renpy.notify(f"{item.name} 使用成功")[/i]
[i] hand.remove(slot_index, 1)[/i]
[i] return[/i]
[i] return[/i]
[i] def throw_Handitem(hand,slot_index,user=None):[/i]
[i] slot = hand.slots[slot_index][/i]
[i] if slot is None:[/i]
[i] renpy.play("audio/sound/worse.wav")[/i]
[i] renpy.notify(f"> 这里已经一无所有了")[/i]
[i] return[/i]
[i] item, count = slot[/i]
[i] if item.func_throw is None:[/i]
[i] renpy.play("audio/sound/worse.wav")[/i]
[i] renpy.notify(f"> [{item.name}]不能被丢弃")[/i]
[i] #该物品不能使用[/i]
[i] return[/i]
[i] func = getattr(renpy.store, item.func_throw, None)[/i]
[i] if func is None:[/i]
[i] renpy.notify(f"> 错误:找不到函数 {item.func_throw}")[/i]
[i] return[/i]
[i] # 调用函数[/i]
[i] consumed = func(item, user)[/i]
[i] renpy.notify(f"{consumed}")[/i]
[i] # 如果返回 True,减少数量或移除[/i]
[i] if consumed == True:[/i]
[i] renpy.notify(f"{item.name} 被丢弃")[/i]
[i] hand.remove(slot_index, 1)[/i]
[i] return[/i]
[i] 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="如你所见,这是一个测试物品。它唯一的用处就是用来测试物品栏系统是否正常运转。"
)
最后,在编写物品界面的时候,也许应该考虑一个“不存在的物品”。我在写相关函数的时候考虑到了这种情况,但我尚未开始测试。
|
|