Here are a few classes which can be used to easily manage any items in any kind of collection
(e.g. characters in a list of characters; personal attributes for a person...),
but originally it was for items in inventory, hence the main class name is Inventory.
This is a good example of OOP applied for Ren'Py script.
(I hope it will show newcomers how useful can be OOP, even some exotic methods like making an object callable, iterable, returning a "nonexistent" attribute etc.
For more details about OOP see e.g. a tutorial on magic methods).
Save the code below as "objects_ren.py" file into "game" directory of your Ren'Py project.
Then use it as it's shown in the second code block (an example of "scripts.rpy", that creates an inventory screen etc).
BTW you can play with objects_ren.py file in the interactive Python interpreter, launching it from command line:
python -i objects_ren.py
"Usage Cheat Sheet" see here:
objects_ren.py
Code: Select all
"""renpy
init -5 python:
"""
""" Inventory and items - v.1.2
Usage Cheat Sheet:
default inv = Inventory() # Initialize the collection of items
inv("Apple", "is sweet.") # Add a new item to the inventory
# => inv.Apple
# inv.Apple.name == "Apple"
# inv.Apple.desc == "is sweet."
inv("Egg", "is fresh.", 3) # Add 3 new items to the inventory
# => inv.Egg.count == 3
inv("Piece of cheese", "is stale") # => inv.Piece_of_cheese
inv("Ant", "is sour.", color="Red") # Add a new item with a custom attr
# => inv.Ant.color == "Red"
inv.Bread("Bread loaf", "is good.") # Another syntax to add a new item
# => inv.Bread.name == "Bread loaf"
# Address the item's attributes:
inv.Egg.name # name,
inv.Egg.desc # description,
inv.Egg.count # amount.
inv.Egg.desc = "New phrase" # Update the description with a new string
inv.Egg.count += 1 # Increase the amount of an existing item
inv.Egg.count -= 1 # Decrease the amount of an existing item
inv.Egg.count = 4 # Set a different amount of an existing item
if inv.Egg: # Check if the item exists in the collection
for i in inv: # Iterate items
print(inv.Egg) # Print the contents of an item to console
"[inv.Egg]" # Output the contents of an item in dialog
print(inv) # Print the contents of the inventory
"[inv]" # Output in dialog (multi-line!)
len(inv) # The amount of different types of items
# in the inventory (zero means it's empty).
"""
class DoesNotExist:
""" When an absent item is requested from the inventory, this class
is used. Ex.:
if inv.Alien # False
inv.Alien.count # 0
inv.Alien(args) # create new item "Alien" with args
"""
count = 0
def __init__(self, obj, non_existent_attr):
""" To create a new item, save `inventory` and `item`. """
self.inv = obj
self.nonexistent = non_existent_attr
def __bool__(self):
""" When checked for existence, report False. """
return False
def __setattr__(self, attr, value):
""" Attempting to assign to non-existing item. Raise Error. """
if attr in ("inv", "nonexistent"):
object.__setattr__(self, attr, value)
return
raise AttributeError(f"Item `{self.nonexistent}` doesn't exist."
f" Therefore it can't be assigned an attribute (`{attr}`).")
def __call__(self, *args, **kwargs):
""" Create a new item in Inventory. """
if args or kwargs:
self.inv(*args, **kwargs, own_var_name=self.nonexistent)
class Item:
""" An item in the inventory.
Normally it has `desc` (description).
`count` can be 0, then the item "is not in the inventory".
It has at least `name` and typically is accessed as inv.<name>,
unless `own_var_name` attribuute exists: then it's accessed as
inv.<own_var_name>
(It's useful if the name has spaces or other symbols forbidden
in Python identifiers, like 食パン etc.).
To create a new item, use `inv()` or `inv.<name>()`:
inv('NewItem', 'Description', 123) => inv.NewItem, 123 pcs
inv.Name('This is The Name', 'Description') => inv.Name
"""
def __init__(self, name, desc="", count=1, **kwargs):
"""
Create an item in the inventory.
If additional keyword arguments are present, store them
with the item.
"""
self.name = name
self.desc = desc
self.count = count
for k, v in kwargs.items():
setattr(self, k, v)
def __bool__(self):
""" To check if such item exists in the inventory.
"""
return bool(self.count)
def __str__(self):
""" Return the item's info as a string.
"""
s = ', '.join([f"{k}: {v}" for k, v in vars(self).items() \
if k not in ('own_var_name', 'name')])
if 'own_var_name' in vars(self) and self.own_var_name != self.name:
return f"{self.own_var_name} ({self.name}) = {s}"
return f"{self.name} = {s}"
def __getattr__(self, attr):
""" Called when a requested attribute doesn't exist.
self.own_var_name
(if present) should be the item's name in the inventory.
Otherwise `self.name` is the item's name in the inventory.
"""
if attr == 'own_var_name':
return None
def __call__(self, *args, **kwargs):
""" Trying to call an existing item (to create it?). => Error.
"""
raise NameError(f"'Create item' `{self.own_var_name}` failed: "
"it already exists. Did you mean increase .count?")
class Inventory:
def __str__(self):
""" Report the contents of the inventory.
"""
return '\n'.join([str(v) for v in vars(self).values() if v.count])
def __call__(self, *args, **kwargs):
""" If used without parameters `inv()`, report its contents.
With parameters, add an item to the inventory.
"""
if not (args or kwargs):
return str(self)
# Adding an item to the inventory
# Get the attribute name (inv.<own_var_name>):
if kwargs and 'own_var_name' in kwargs:
n = kwargs['own_var_name']
if not args and 'name' not in kwargs:
kwargs['name'] = n
else:
if args:
n = args[0]
else:
n = kwargs['name']
# In case the name has spaces:
if ' ' in n:
n = n.replace(' ', '_')
kwargs['own_var_name'] = n
# In case the name still can't be an identifier:
if not n.isidentifier():
raise NameError(f"{n} is not a valid identifier.")
# Create and insert the item
setattr(self, n, Item(*args, **kwargs))
def __getattr__(self, item):
""" If an absent attribute is requested, return an instance of
DoesNotExist().
"""
return DoesNotExist(self, item)
def __iter__(self):
""" To iterate items. """
for k, v in vars(self).items():
if v.count:
# Show only items with non-zero amount
yield v
def __len__(self):
i = 0
for item in self:
i += 1
return i
Here's a working example in Ren'Py (thanks to Kaivu for the inventory screen code!).
You can save this code as "script.rpy" or something, and run in Ren'Py to test:
script.rpy
Code: Select all
default inv = Inventory()
default selected_item = None
screen inventory_display_toggle():
zorder 92
frame:
background "#000a"
xalign 0.05
yalign 0.095
textbutton "Inventory":
#hover_sound "click3.mp3"
#activate_sound "click1.wav"
action ToggleScreen("inventory_screen")
screen inventory_screen():
modal True
tag inventory
frame:
xfill True
yfill True
background "#531A"
hbox:
spacing 50 # Space between the two main sections
align (0.5, 0.5)
# Items list section
frame:
background "#000c" # Background color for items list
xminimum 550
xmaximum 550
ymaximum 680
vbox:
spacing 15
text "Items" size 30 align (0.5, 0.5)
viewport:
draggable True
mousewheel True
vbox:
for item in inv:
textbutton item.name:
action SetVariable("selected_item", item)
xalign 0.0
xmaximum 530 # Maximum width for item text
yminimum 30 # Minimum height for each item
# Description section
frame:
background "#000c" # Background color for description area
xminimum 550
xmaximum 550
ymaximum 680
vbox:
spacing 15
text "Description" size 30 align (0.5, 0.5)
vbox:
xfill True
if selected_item:
text selected_item.desc xmaximum 530 line_spacing 1.2 xoffset 5
null height 20
text "Amount: [selected_item.count]" xoffset 5
else:
text "Select an item to see its description." xmaximum 530 line_spacing 1.2 xoffset 5
label start:
python:
quick_menu = False
inv('Water', 'is tasty')
inv('Bread', 'is mouldy', 3)
inv.Sixpack('Six-pack', 'is good for health', 30)
inv.cheese1('Large piece of cheese', 'is fresh')
window hide
scene black
show screen inventory_display_toggle
"[inv()]"
"[inv.Sixpack].\nDo I have some? [not (not inv.Sixpack)]."
"""I have [inv.Water.count] bottles of [inv.Water.name]\n
and [inv.Bread.count] pcs of [inv.Bread.name], and it [inv.Bread.desc]."""
"Total kinds of items: [len(inv)]. The End"