Ren'Py The Best Inventory I Know So Far

Hidden Life Studios

Newbie
Game Developer
May 17, 2019
83
298
First of all, sorry for any error. English is not my first language.
The code I will show and explain you is not created by me. I only completed and improved it.
The creator of this inventory (Milkymalk) explained it and I will also copy-paste some parts of his post because he did really a wonderful job in explaining it.
And now, let's get started!

Unlike Milkymalk I will not do a step by step guide. Instead I will directly go to the completed code.
First of all make sure you understand the difference between Python code, Ren'py script language and Ren'Py screen language before you even attempt to do anything that mixes the three. Which thing this inventory does!
Also, everything that has one or more "#" is a comment and therefore Renpy will ignore that line when executing the code. I will use comments to explaing how the code work.
And, for last, I will not deep dive into the code, explaining every detail. I will only give you everything you need to understand it and be able to modify it. If you wish a deeper explanation, check Milkymalk's post or study python language.

Code:
# First we need to tell to RenPy that this code has to be executed at the start of the game and we do so with "init".
# The -5 after init is to execute this before any other "init" with higher number.
# "python" explain to RenPy that what this init will contain is python code and, therefore, should be executed as so.
init -5 python:
# First, let's declare a class called "Item". Declaring a class let us store group of datas together.
    class Item(object):
# Now, with the method "__init__" (mind the two _ before and after) we specify the names and number of those datas.
# Every method need his first argument to be "self", so, don't eliminate it!
        def __init__(self, name, price, pic):
# And now let's give a real name to this arguments. In this case is the best just to repeat their name.
            self.name = name
            self.price = price
            self.pic = pic

# Let's do a similar thing declaring a new class to store the amount of every item.
# We use a new class instead of adding the amount in the class "Item" to be able to change the amount of items in the inventory without changing the item.
# In fact every item we will add to the class "Item" will be unique for each group of arguments.
# So, there cannot be two items called "ball" with different prices but counted in your inventory as the same.
# To use different prices you have to declare a new class as we are doing with the amount.
    class InvItem(object):
        def __init__(self, item, amount):
            self.item = item
            self.amount = amount

# And, for last but not least, let's declare a class that will be the container of our items, so, the inventory.
    class Container(object):
# We don't need to specify any other argument. We want to store the items in this container without specifing anything else.
        def __init__(self):
# Here we declare a list of data. The two empty [] tell to RenPy that this array is empty at his creation.
            self.inventory = []

# There is now point in creating a class only to store a list. We created a class with a list to be able to use easly other method with that list.
# First, let's add a method that let the program know where a specified item is in the list.
# Every item in the list has an index (an unique number). In order to modify or see that item, the program needs to know what his index is.
        def finditem(self, item):
# "return" will return to the program the index of the specified item.
# "[i.item for i in self.inventory]" will repeat the method for every item in the list.
# If we don't do so, the program will not be able to find the item we specified and return his index.
            return(self.inventory[[i.item for i in self.inventory].index(item)])

# Then, let's add a method that let the program know if there is that item in the list or not.
# The arguments are "self", "item" and "amount". If you notice, "amount" has a =1 attached.
# That means that, if we don't specify an amount for the item we want to check the presence, the default number will be 1.
        def has_item(self, item, amount=1):
# With this double If-Else we first check if there is that item at all in the inventory, then we check if that item has an amount equal or above the one we specified.
            if item in [i.item for i in self.inventory]:
                if self.finditem(item).amount >= amount:
                    return(self.finditem(item).amount)
                else:
                    return(False)
            else:
                return(False)

# At this point, let's add a method that let us add items to the list (the inventory).
        def add_item(self, item, amount=1):
# In short terms, this If-Else will add the amount of item we specified to the item in the inventory or,
# if is a new item, will add it to the inventory and use the amount we specified.
            if item in [i.item for i in self.inventory]:
                self.finditem(item).amount += amount
            else:
                self.inventory.append(InvItem(item, amount))
# In case there is no error, will return "success".
            return('success')

# Similar to the previous method, this let us remove one item from the inventory if we have enough.
        def rem_item(self, item, amount=1):
            if self.has_item(item):
                if self.finditem(item).amount > amount:
                    self.finditem(item).amount -= amount
                    return('more left')
                elif self.finditem(item).amount == amount:
                    self.finditem(item).amount -= amount
                    self.inventory.pop(self.inventory.index(self.finditem(item)))
                    return('gone')
                elif self.finditem(item).amount < amount:
                    return('not enough')
            else:
                return('item not found')
Now, the code for the inventory is done. Congrats!
What remains to do is to create the items, the gui of the inventory and learn how to add and remove item from the inventory.
Yes, we will use the two methods we just created but, if you are here, I asume you don't know yet how to do that.
Code:
# Once again let's make this part of the code be executed at the start of the game,
# BUT only AFTER the code of the inventory.
init 0:
# First of all, we need to create an inventory using the class Container.
# We will call this inventory "backpack".
# Yes, you can create more than one inventory without doubling all the previous code.
    $ backpack = Container()

# Let's add money too to our inventory. We will use a simple numeric variable for this.
    $ money = 0

# Now, we will make a list with ALL the items in our game. In this case only two.
# Let's analyze this.
# "ball" is the name of the variable to which the item will be bound.
# "Item()" calls the Item class. In the parhentesis we will need to specify all of the arguments declared if those don't have a default value.
# ""Ball"" is the name. "5" is the price of the item. ""images/ball_icon.png"" is the pic.
# The pic is the directory, the name and the extension of the image that will be used to show the item in the inventory.
# This means that you will not be able to change the image inside the game, like resizing it.
# I suggest you to have all of the items' images of the same size.
    default ball = Item("Ball", 5, "images/ball_icon.png")
    default brick = Item("Brick", 20, "images/brick_icon.png")
And now, the inventory GUI:
Code:
# Now, let's create a screen that will be the GUI of our inventory.
# I hope you already know how screen works, because I will go rapidly explaining this.
# If you already are an expert of screens, please still read this part to be sure to don't make any mistake.

# Let's declare a screen and call it "backpack".
screen backpack:
    # We give it a tag "inventory" in order to replace any other inventory GUI currently open, in case you have more than one inventory.
    tag inventory
    # "zorder" with a numer of "1000" will make sure that this GUI will not be hidden behind something.
    zorder 1000
    # "modal" is the keyword that, if "True", block the user to interact with anything that is not part of the GUI.
    # In this way there is no risk that the user accidentaly click something that is behind it.
    modal True
    # With "add" we specify the directory, the name and the extension of the image that will be the background of our inventory GUI.
    add "images/inventory_bg.png"
    # This horizontal box will show in the inventory how many money the player has.
    # xalign, yalign and spacing values are according to my GUI. Feel free to change them.
    hbox:
        xalign 0.5
        yalign 0.05
        spacing 10
        # This image is a small icon that goes next to the money amount. Just an aestetic thing.
        add "images/inventory_money.png":
            zoom 0.075
        # This too is purely aestethic. If the player has zero money or less, the money amount will be read. If it's not, the value will be green.
        if money > 0:
            text "[money]$" color "#ffffff"
        else:
            text "[money]$" color "#ff0000"

# First, let's specify that the items. Instead at the center of the GUI, we want that will be shown starting to the top left corner ("tl").
    side "tl":
        # "465" and "109" are according to my GUI background. You need to change them for the size of your GUI.
        area (465, 109, 1, 1)
        # This grid will contain all of our items' icons as well as their name and amount.
        # The two value "10" and "100" are the columns and lines of the grid. Change the columns according to your GUI.
        # There is no need to change the lines because, if you wish, you can scroll this inventory with the mousewheel.
        # Something you want ABSOLUTELY to avoid with this inventory is that the player run out of item slots.
        # So, put enough line to store all the items at once.
        # If the player exceed the slots number, RenPy will give an error and the save will not be playable anymore.
        grid 10 100:
            # This is the distance from one item to another.
            spacing 1
            # THIS PART IS EXTREMELY IMPORTANT!!!
            # If you omit this, RenPY will give you an error because not all the slots are filled.
            allow_underfull True
            # And, for last, this "for i..." simply add all of the items in the backpack container to the grid.
            # In this way there's no need to add manually every item to the inventory.
            for i in backpack.inventory:
                # This boxes are to keep locked in place the images, names and counts of all the items.
                # Again, fell free to change the "zoom", "size" and any other properies.
                vbox:
                    hbox:
                        add i.item.pic zoom 0.86
                        text str(i.amount) size 20
                    text i.item.name size 15

    # I sugest you to put, somewhere in this GUI, a button to close the inventory GUI. Otherwise the player will be stuck in here.
We are almost done. To be more precise, the coding part is done. Now you need to learn how to use the methods during the game.
Code:
# In the normal story, you can execute a method as changing a variable. In this way:
    $ backpack.add_item(ball, 1)
# Is the same for every method.
# We use the method "add_item" or "rem_item" and we use it on the container "backpack".
# Also, we want to change the item "ball" and the amount we change is "1"
    $ backpack.rem_item(ball, 1)

# So, if we buy something the code will be:
    menu:
        merchant "Do you want to buy two balls?"
        "Yes":
            if money >= 10:
                $ backpack.add_item(ball, 2)
                $ money -= (ball.price * 2)
                jump shop
            else:
                merchant "You don't have enough money."
                jump shop
        "No":
            jump shop

# Else, if you wish to use it in a screen or similar, here's an example with a button to remove 1 ball:
                imagebutton:
                    idle "images/button.png"
                    action Function(backpack.rem_item, ball)
Your inventory is ready. If you have any error, any suggestion or everything else, feel free to say/ask it here.
This is the best inventory system I know. Is absolutely not perfect but, for the most of you, there will be no need to search a better one.

P.S. I tried to explain this in the simpliest way and, despite I know python, I'm not a real programmer, so, if I use some terms in wrong ways, correct me without put on a tantrum.
 
Last edited:

UncleNanard

I am to music what Kanye West is to adult games.
Game Developer
Jul 1, 2017
1,402
1,418
Hello, thank you for your time and help!

I'm sad, because your thread could have been very helpful to me a few months ago :ROFLMAO:

Programming - Do I really need a complex inventory system? | F95zone

I think we are using almost the same system. The only thing I would improve is a page system in the inventory so that there are not too many items displayed at the same time (but I don't know how to do that yet :p)
 

Hidden Life Studios

Newbie
Game Developer
May 17, 2019
83
298
Hello, thank you for your time and help!

I'm sad, because your thread could have been very helpful to me a few months ago :ROFLMAO:

Programming - Do I really need a complex inventory system? | F95zone

I think we are using almost the same system. The only thing I would improve is a page system in the inventory so that there are not too many items displayed at the same time (but I don't know how to do that yet :p)
I have some ideas in mind about how can you do pages.
The simpliest way is to have more more GUI. Every GUI has two buttons to switch GUI. Every GUI has his own inventory.
The problem with this is that isn't easy to change easily position of an item from a page to another. But, if your pages are categories of items, this issue is no more.
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Respected User
Donor
Jun 10, 2017
10,145
14,830
The only thing I would improve is a page system in the inventory so that there are not too many items displayed at the same time (but I don't know how to do that yet :p)
Globally speaking, and relying on a generic inventory system instead of this one, it works like this:
Python:
screen whatever():

    # Current page of the inventory
    default currentPage = 0

    vbox:
        # 25 items by page.
        grid 5 5:
            # Index of the items on the current page
            for idx in range( ( currentPage * 25 ) + 0, ( currentPage * 25 ) + 25 ):

                # get the item at the idx rank. Should return None if no items
               $  item = inventory.item( idx )
   
                # If there's an item, display it
                if item:
                    vbox:
                        add item.image
                        text "[item.number] unit(s)"
                        text "[item.name]"
                # else let the cell blank
                else:
                    null width 1

        # If it's not the first page
        if currentPage > 0:
            # Show the button going to the previous page
            textbutton "prev":
                # Select the previous page
                action SetScreenVariable( "currentPage", currentPage - 1 )
        else:
            # Left empty if it's the first page
            null height 20

        # If it's not the last page
        if ( currentPage * 25 ) + 25 < inventory.numberItems:
            # Show the button going to the previous page
            textbutton "next":
                # Select the next page
                action SetScreenVariable( "currentPage", currentPage + 1 )
        else:
            # Left empty if it's the first page
            null height 20

        textbutton "close":
            action Return()

Edit: Typo in the code
 
Last edited:
  • Like
Reactions: Hidden Life Studios

KiaAzad

Member
Feb 27, 2019
276
205
I'm very biased, but in my opinion, the best inventory system should be one of mine, and thanks to my princess trainer being open source, you can get your hands on it for free, if you're willing to strip it down to it's bare bones.
I don't consider myself the best programmer, but I've been modifying and improving it for a long time, it's been battle tested and works like a clock.
 

lobotomist

Active Member
Sep 4, 2017
777
597
I'm very biased, but in my opinion, the best inventory system should be one of mine, and thanks to my princess trainer being open source, you can get your hands on it for free, if you're willing to strip it down to it's bare bones.
I don't consider myself the best programmer, but I've been modifying and improving it for a long time, it's been battle tested and works like a clock.
have you considered selling your patreon minigames on itch?
 

KiaAzad

Member
Feb 27, 2019
276
205
have you considered selling your patreon minigames on itch?
I've been thinking about that for a while, but I'm not sure how I should price them, and I had technical problems uploading images to itch that I've been trying to fix for few months.
My itch page is mostly free pixel art at the moment.
 
  • Thinking Face
Reactions: lobotomist

Ikatikei

Newbie
Aug 1, 2018
81
45
Your code saved me a lot, thank you. But for some reason, when I load the game , and open the backpack screen again the item on the screen disappears. It seems to happens only when you close the game.

Edit: Solve it. I changed:

#$ brick = Item("Brick", 20, "images/slot.png")
to:
#default brick = Item("Brick", 20, "images/slot.png")

It's seems to be working properly now.
 
Last edited:

Hidden Life Studios

Newbie
Game Developer
May 17, 2019
83
298
Your code saved me a lot, thank you. But for some reason, when I load the game , and open the backpack screen again the item on the screen disappears. It seems to happens only when you close the game.

Edit: Solve it. I changed:

#$ brick = Item("Brick", 20, "images/slot.png")
to:
#default brick = Item("Brick", 20, "images/slot.png")

It's seems to be working properly now.
Thank you. I added your fix to the main code.
 
  • Like
Reactions: Ikatikei

Guy43

New Member
Nov 10, 2017
3
2
So I haven't used and am not familiar with RenPy but typically you don't want your inventory to actually hold the data of the items it has. Instead, you want it to hold references that you can use to look up the item when it is used/displayed. The reason being (and again, not familiar with RenPy specifically) is that if you need to change your items class then it could cause older saves to break or for a specific item to become outdated as it doesn't use the new values in your update automatically.

Python:
ITEMS = {
    "sword" = item(name="sword", price=10, pic="sword.png"),
    "shield" = item(name="shield", price=8, pic="shield.png")
}

# For InvItem, self.item would be a string. I'm changing it to id though since it's a reference now.
class InvItem(object):
    def __init__(self, idname, amount):
        self.id = idname
        self.amount = amount
# Example: InvItem("shield", 1)

# You would then reference these items in the inventory by storing the string "sword" or "shield".
# For the code in the OP displaying each item:
for i in backpack.inventory:
    item = ITEMS[i.id]
    vbox:
        hbox:
            add item.pic zoom 0.86
            text str(i.amount) size 20
        text item.name size 15
Now you know where all your item data is always stored and used in your game (within your dictionary ITEMS) and it is easy to change values.
 
Jan 18, 2021
98
139
Globally speaking, and relying on a generic inventory system instead of this one, it works like this:
Python:
screen whatever():

    # Current page of the inventory
    default currentPage = 0

    vbox:
        # 25 items by page.
        grid 5 5:
            # Index of the items on the current page
            for idx in range( ( currentPage * 25 ) + 0, ( currentPage * 25 ) + 25 ):

                # get the item at the idx rank. Should return None if no items
                item = inventory.item( idx )
   
                # If there's an item, display it
                if item:
                    vbox:
                        add item.image
                        text "[item.number] unit(s)"
                        text "[item.name]"
                # else let the cell blank
                else:
                    null width 1

        # If it's not the first page
        if currentPage > 0:
            # Show the button going to the previous page
            textbutton "prev":
                # Select the previous page
                action SetScreenVariable( "currentPage", currentPage - 1 )
        else:
            # Left empty if it's the first page
            null height 20

        # If it's not the last page
        if ( currentPage * 25 ) + 25 < inventory.numberItems:
            # Show the button going to the previous page
            textbutton "next":
                # Select the next page
                action SetScreenVariable( "currentPage", currentPage + 1 )
        else:
            # Left empty if it's the first page
            null height 20

        textbutton "close":
            action Return()
So i'm relatively new to Ren'py and i'm still learning how to do the inventory stuff, so apologies if it's something stupid that I am missing...
I'm trying to get a "grid" style inventory setup for a game I am working on, so I opted to try out the code above.
When I try to use it I get an error on the " item = inventory.item( idx )" line: 1693339615651.png
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Respected User
Donor
Jun 10, 2017
10,145
14,830
When I try to use it I get an error on the " item = inventory.item( idx )" line:
Oops, my bad. There's a leading "$" missing here. It should be $ item = inventory.item( idx ).

I'll edit the code to fix this, and looks if there's other obvious typos.
 
Jan 18, 2021
98
139
Kay-o, that helped with the initial issue. To be honest I feel dumb as it seems like a simple fix...
However, now i'm getting a different error:
1693436304526.png

I had to change it to "items", since that's what the items in the array are called in my code.