|
|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
本帖最后由 甲醇水溶液 于 2026-8-12 10:44 编辑
许久之前在论坛分享了物品栏的大概思路,但并不完善,且那个物品栏的用途为解谜游戏,而不是传统的“背包”系统。
因此在这分享自己写的框架以及使用教程。
首先,我们需要为物品写一个类:
[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 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的控制台,输入定义的物品栏名称,若先前的操作正确,你将看到以下信息:
物品栏默认为八槽,当然,可以自行修改将上限提高,把它变成一个背包。个人认为八槽物品栏最为美观。
物品栏的槽位都是空的,让我们使用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里实现物品的“使用”和“丢弃”:
我不会在此给出完整的screen代码,因为涉及到其它不相关的代码逻辑,因此我会着重讲相关逻辑的部分
也许你已经注意到了,在物品类中有俩个属性func_throw和func_use。它们的默认值为use_item和throw_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="如你所见,这是一个测试物品。它唯一的用处就是用来测试物品栏系统是否正常运转。"
)
最后,在编写物品界面的时候,也许应该考虑一个“不存在的物品”。我在写相关函数的时候考虑到了这种情况,但我尚未开始测试。
|
评分
-
查看全部评分
|