How do I make a shelf, with multiple random items, each an image button?

Discuss how to use the Ren'Py engine to create visual novels and story-based games. New releases are announced in this section.
Forum rules
This is the right place for Ren'Py help. Please ask one question per thread, use a descriptive subject like 'NotFound error in option.rpy' , and include all the relevant information - especially any relevant code and traceback messages. Use the code tag to format scripts.
Message
Author
Hadriex
Newbie
Posts: 11
Joined: Mon Feb 05, 2024 12:11 am
Contact:

How do I make a shelf, with multiple random items, each an image button?

#1 Post by Hadriex »

So I've been working on a game, figuring out the code, and I finally hit a wall. Specifically with image buttons. I need some help on how to make multiple imagebuttons that are randomized and disappear when interacted with. I should probably explain what I'm going for here.


What I want: A shop where the shelf is a background image and various items are image buttons on screen. The items on the shelf are randomized, so that the potion slot can be any of a dozen or so potions, the weapon rack can have anything from a sword to an axe, etc. etc. You click on the button and the shopkeeper asks you if you want to purchase that item, if you do it vanishes from the shelf and you spend the money.

What I have: An image button with idle, hover, and even a tooltip. When you click on it it calls a label where you can spend money and put it in your inventory. All of this is working perfectly.


So far prying google for answers has yielded how to get multiple screen buttons on screen at once, and a lot of references to how to make maps for some reason. But the two features that I can't seem to find anything for are:

#1 How do I randomize the image buttons? Each part of the shelf should have it's own pool of random image buttons as I, for example, draw multiple potions that could be sitting on that shelf.

#2 How do I make the imagebutton for an item that has been purchased vanish from the shelf without effecting any of the other image buttons present?

Bonus: It might be cool is I had the option to make additional items appear on the shelf. If you say the right thing to the shopkeeper, or purchase a certain combination of items he pulls something out from behind the counter.

Much as I would love some example code (I mean it would save me so much trouble to see working code, probably save me hours of hair pulling frustration) just being able to point me to the right resources would be great.

User avatar
m_from_space
Miko-Class Veteran
Posts: 978
Joined: Sun Feb 21, 2021 3:36 am
Contact:

Re: How do I make a shelf, with multiple random items, each an image button?

#2 Post by m_from_space »

Randomization of things is best done when they're part of a list. Because then you can always call the renpy.random.shuffle(list) function.

So assume we have a list of items. And we want to randomize it inside a screen. Here is the basic idea:

Code: Select all

default shoplist = ["Health Potion", "Mana Potion", "Toxic Potion"]

screen shop():
    # this will be executed when showing or re-showing this screen after you hide it
    default random_shoplist = renpy.random.shuffle(shoplist)
    vbox:
        for item in random_shoplist:
            textbutton item action NullAction()
Now this of course only uses strings as items, that are shown from top to bottom. But it's the same for objects of a class or just other lists you put inside the shoplist. And it's the same for imagebuttons.

You didn't mention what the shop's shelfs looks like. So if we assume you have very specific spaces inside the shelf, you would need to create a list of possible spaces and their positions. Otherwise you could just use a hbox or vbox or a grid or whatever suits you. But let's assume you have random positions on the screen:

Code: Select all

default shelfspaces = [(0, 10), (120, 50), (200, 100), (333, 30)]
default shoplist = ["Health Potion", "Mana Potion", "Toxic Potion"]

screen shop():
    default random_shoplist = renpy.random.shuffle(shoplist)
    for i, item in enumerate(random_shoplist):
        # is there a spot left?
        if i < len(shelfspaces):
            textbutton item action NullAction() pos shelfspaces[i]
#2 How do I make the imagebutton for an item that has been purchased vanish from the shelf without effecting any of the other image buttons present?
Hmm, good question! I think I would create an item class, that has the property "available" and then just set this property to False whenever it's sold and therefore hide the image from the shelf or make it appear differently.

Code: Select all

init python:
    class Item:
        def __init__(self, name, available=True):
            self.name = name
            self.available = available

default manapotion = Item("Mana Potion")
default healthpotion = Item("Health Potion")

default shoplist = [manapotion, healthpotion]

screen shop()
    default random_shoplist = renpy.random.shuffle(shoplist)
    hbox:
        for item in random_shoplist:
            if item.available:
                textbutton item.name action SetField(item, "available", False)
            else:
                # an invisible space / item
                null width 32 height 32
Bonus: It might be cool is I had the option to make additional items appear on the shelf. If you say the right thing to the shopkeeper, or purchase a certain combination of items he pulls something out from behind the counter.
I suggest using a different shoplist for those special items and make it part of the screen as well. Of course if you're not in the shop right now, you can just simply add and remove items from the shoplist as much as you like. And when you're in the shop again, they will appear as long as they are "available".

Hadriex
Newbie
Posts: 11
Joined: Mon Feb 05, 2024 12:11 am
Contact:

Re: How do I make a shelf, with multiple random items, each an image button?

#3 Post by Hadriex »

Thank you very much, I'll get to work with this tomorrow and see what I can do with it :)

Hadriex
Newbie
Posts: 11
Joined: Mon Feb 05, 2024 12:11 am
Contact:

Re: How do I make a shelf, with multiple random items, each an image button?

#4 Post by Hadriex »

So I set to work, step one plug in the code and make sure it works, and then step 2 will be to modify the code to work with image butto... Aw crap. It's not working in step one?

...All right, let's try the simpler code from the first box... It must have something to do with the code I already wrote... Let me make certain nothing could be influencing it... Uh maybe I need to put something into the item variable no wait, it's pulling from shoplist, er, maybe U need to declare item as a variable with all my other variables... Well no, it's already being declared as part of a for loop, uh... Crap it's been 20 years since I've seriously tried to code something and all my knowledge has rusted and leaked out my ears...
All right, Maybe if I do this... Maybe if I do that...

...One hour later...

Argh. All right, I surrender. I'll just post the error log and hope someone understands what it's trying to say.




-----

I'm sorry, but an uncaught exception occurred.

While running game code:
File "game/script.rpy", line 170, in script
call screen shop
File "renpy/common/000statements.rpy", line 609, in execute_call_screen
store._return = renpy.call_screen(name, *args, **kwargs)
File "game/script.rpy", line 73, in execute
screen shop():
File "game/script.rpy", line 73, in execute
screen shop():
File "game/script.rpy", line 76, in execute
vbox:
TypeError: 'NoneType' object is not iterable

-- Full Traceback ------------------------------------------------------------

Full traceback:
File "game/script.rpy", line 170, in script
call screen shop
File "C:\Users\Hadriex\Desktop\Way of the Merchandier\Ren'Py\renpy-8.0.3-sdk\renpy\ast.py", line 2232, in execute
self.call("execute")
File "C:\Users\Hadriex\Desktop\Way of the Merchandier\Ren'Py\renpy-8.0.3-sdk\renpy\ast.py", line 2220, in call
return renpy.statements.call(method, parsed, *args, **kwargs)
File "C:\Users\Hadriex\Desktop\Way of the Merchandier\Ren'Py\renpy-8.0.3-sdk\renpy\statements.py", line 281, in call
return method(parsed, *args, **kwargs)
File "renpy/common/000statements.rpy", line 609, in execute_call_screen
store._return = renpy.call_screen(name, *args, **kwargs)
File "C:\Users\Hadriex\Desktop\Way of the Merchandier\Ren'Py\renpy-8.0.3-sdk\renpy\exports.py", line 3181, in call_screen
rv = renpy.ui.interact(mouse="screen", type="screen", roll_forward=roll_forward)
File "C:\Users\Hadriex\Desktop\Way of the Merchandier\Ren'Py\renpy-8.0.3-sdk\renpy\ui.py", line 299, in interact
rv = renpy.game.interface.interact(roll_forward=roll_forward, **kwargs)
File "C:\Users\Hadriex\Desktop\Way of the Merchandier\Ren'Py\renpy-8.0.3-sdk\renpy\display\core.py", line 3377, in interact
repeat, rv = self.interact_core(preloads=preloads, trans_pause=trans_pause, pause=pause, pause_start=pause_start, pause_modal=pause_modal, **kwargs) # type: ignore
File "C:\Users\Hadriex\Desktop\Way of the Merchandier\Ren'Py\renpy-8.0.3-sdk\renpy\display\core.py", line 3810, in interact_core
root_widget.visit_all(lambda i : i.per_interact())
File "C:\Users\Hadriex\Desktop\Way of the Merchandier\Ren'Py\renpy-8.0.3-sdk\renpy\display\core.py", line 582, in visit_all
d.visit_all(callback, seen)
File "C:\Users\Hadriex\Desktop\Way of the Merchandier\Ren'Py\renpy-8.0.3-sdk\renpy\display\core.py", line 582, in visit_all
d.visit_all(callback, seen)
File "C:\Users\Hadriex\Desktop\Way of the Merchandier\Ren'Py\renpy-8.0.3-sdk\renpy\display\core.py", line 582, in visit_all
d.visit_all(callback, seen)
File "C:\Users\Hadriex\Desktop\Way of the Merchandier\Ren'Py\renpy-8.0.3-sdk\renpy\display\screen.py", line 451, in visit_all
callback(self)
File "C:\Users\Hadriex\Desktop\Way of the Merchandier\Ren'Py\renpy-8.0.3-sdk\renpy\display\core.py", line 3810, in <lambda>
root_widget.visit_all(lambda i : i.per_interact())
File "C:\Users\Hadriex\Desktop\Way of the Merchandier\Ren'Py\renpy-8.0.3-sdk\renpy\display\screen.py", line 462, in per_interact
self.update()
File "C:\Users\Hadriex\Desktop\Way of the Merchandier\Ren'Py\renpy-8.0.3-sdk\renpy\display\screen.py", line 653, in update
self.screen.function(**self.scope)
File "game/script.rpy", line 73, in execute
screen shop():
File "game/script.rpy", line 73, in execute
screen shop():
File "game/script.rpy", line 76, in execute
vbox:
File "C:\Users\Hadriex\Desktop\Way of the Merchandier\Ren'Py\renpy-8.0.3-sdk\renpy\sl2\slast.py", line 1680, in execute
for index, v in enumerate(value): # type: ignore
TypeError: 'NoneType' object is not iterable



What obvious thing am I just failing to see?

User avatar
m_from_space
Miko-Class Veteran
Posts: 978
Joined: Sun Feb 21, 2021 3:36 am
Contact:

Re: How do I make a shelf, with multiple random items, each an image button?

#5 Post by m_from_space »

Hadriex wrote: Tue Feb 06, 2024 11:38 am ...One hour later...

Argh. All right, I surrender. I'll just post the error log and hope someone understands what it's trying to say.
Oh god, I just tested it and was seriously confused, but the answer is simple and I forgot about it at the moment. I am sorry I have wasted so much time. The renpy.random.shuffle() function does not return a list (which I assumed), but just shuffles an existing one. It's a bit... inconvenient.

So the error occurs, because when a function does not return something, it will return None and that is what we saved inside "random_shoplist". And you cannot iterate through None.

So let's make our own function that is able to return a shuffled list.

Code: Select all

init python:
    def getShuffledList(l):
        # better not shuffle the original list, so make a copy (because in Python everything is a reference)
        new_list = l.copy()
        renpy.random.shuffle(new_list)
        return new_list

# our shop now accepts an argument, so we can pass different lists of items
screen shop(supply):
    default random_shoplist = getShuffledList(supply)
    ...
    
label start:
    show screen shop(shoplist)
    ...
P.S. If in doubt, just ask the Renpy documentation about the function: https://www.renpy.org/doc/html/other.html#renpy-random

Hadriex
Newbie
Posts: 11
Joined: Mon Feb 05, 2024 12:11 am
Contact:

Re: How do I make a shelf, with multiple random items, each an image button?

#6 Post by Hadriex »

Oh no worries, you're helping immensely. My stubborness is my own problem not yours.

I think I understand how to use the random shuffle now. I'm a little stuck on is how to put the image buttons into a list.

I like how slick this line you wrote is: default shelfspaces = [(0, 10), (120, 50), (200, 100), (333, 30)]
But that assumes every slot can have the same inventory. Unfortunately I'm going to be need to be able to give different slots their own lists to shuffle.

I think I can communicate better with an example:


'''
screen Shop1:
imagebutton:
xalign 0.2
yalign 0.4
idle "items/potion1.png"
hover "items/potion1glow.png"
action Call("buypotion1")
tooltip "It's a test item."

imagebutton:
xalign 0.4
yalign 0.4
idle "items/potion2.png"
hover "items/potion2glow.png"
action Call("buypotion2")
tooltip "It's another imagebutton test."

imagebutton:
xalign 0.7
yalign 0.7
idle "items/sword1.png"
hover "items/sword1glow.png"
action Call("buysword")
tooltip "Weapon rack imagebutton test."

$ tooltip = GetTooltip()

if tooltip:

nearrect:
focus "tooltip"
prefer_top False

frame:
xalign 0.5
text tooltip
'''


This is my test code. It displays three simultaneous image buttons. I know how to shuffle a list, but how do I make a list of image buttons? In this example I have a weapon rack that should only have weapons, while the shelf may contain potions.

philat
Eileen-Class Veteran
Posts: 1912
Joined: Wed Dec 04, 2013 12:33 pm
Contact:

Re: How do I make a shelf, with multiple random items, each an image button?

#7 Post by philat »

From what you've described of your system, I would say there's little need to shuffle lists (that's a good randomizer if you need non-repeating exhaustive random, but if not, you can get by just as easily with random ints).

Disclaimer: The following was fly typed and could contain errors, but it's meant to be a general pointer more than a concrete working example.

Code: Select all

init python:
    class Item():
        def __init__(self, name, image, tooltip): # I imagine you'll want to create a data structure for your items anyway
            self.name = name
            self.image = image
            self.tooltip = tooltip

default potion1 = Item("Small Health Potion", "potion1", "Potion 1 tooltip")
default potion2 = Item("Large Health Potion", "potion2", "Potion 2 tooltip")
default sword1 = Item("Short Sword", "sword1", "Sword 1 tooltip")
default sword2 = Item("Long Sword", "sword2", "Sword 2 tooltip")

default potion_list = [potion1, potion2]
default sword_list = [sword1, sword2]

screen shop():
    default potion_idx = renpy.random.randint(0, 1) # adjust for list size -- you can also use len(potion_list) but I was lazy since there are only two
    default sword_idx = renpy.random.randint(0,1)
    default random_potion = potion_list[potion_idx]
    default random_sword = sword_list[sword_idx]

    vbox:
        imagebutton:
            idle "items/{}.png".format(random_potion.image)
            hover "items/{}glow.png".format(random_potion.image)
            tooltip random_potion.tooltip
            action NullAction()

        imagebutton:
            idle "items/{}.png".format(random_sword.image)
            hover "items/{}glow.png".format(random_sword.image)
            tooltip random_sword.tooltip
            action NullAction()


Hadriex
Newbie
Posts: 11
Joined: Mon Feb 05, 2024 12:11 am
Contact:

Re: How do I make a shelf, with multiple random items, each an image button?

#8 Post by Hadriex »

This is excellent it worked almost perfectly.

I had to remove the line 'vbox:' I'm not entirely sure what a vbox is but it was pushing all the images into a vertical stack, once I removed that line and added xalign and yalign to the image buttons, and the logic to display tooltips, everything became perfect.

Thank you so much.

If I may ask one or two more questions.

What would be the easiest way to remove an image button for an item after it's been purchased?

And may I add null items to a list so there is a chance the shelf is empty?

philat
Eileen-Class Veteran
Posts: 1912
Joined: Wed Dec 04, 2013 12:33 pm
Contact:

Re: How do I make a shelf, with multiple random items, each an image button?

#9 Post by philat »

That's what vboxes do - they automatically stack buttons, which is useful for testing when I don't want to faff around with button locations, just to check that they're there and working.

There are a lot of ways to approach what you want, but the simplest is just using a variable to show or not show the button.

Code: Select all

default potion_list = [potion1, potion2, None]

screen shop():
    default potion_idx = renpy.random.randint(0, 2)
    default random_potion = potion_list[potion_idx]

    default button1 = True

    if button1 and random_potion != None:
        imagebutton:
            idle "items/{}.png".format(random_potion.image)
            hover "items/{}glow.png"format(random_potion.image)
            action [NullAction(), SetScreenVariable("button1", False)] # this sets the variable button1 to False on click. In reality, you probably want to have extra logic around that, but this is meant as a starting point, not a final solution.

Hadriex
Newbie
Posts: 11
Joined: Mon Feb 05, 2024 12:11 am
Contact:

Re: How do I make a shelf, with multiple random items, each an image button?

#10 Post by Hadriex »

Ah I'm not familiar with vboxes. I see them a lot in renpy code samples but things seem to work without them so I wasn't sure what they did exactly.

I'll try that. I'm wracking my brain to think if there's anything else the shop might need that I haven't asked about but I'm coming up blank. I think this covers everything I needed.

Hadriex
Newbie
Posts: 11
Joined: Mon Feb 05, 2024 12:11 am
Contact:

Re: How do I make a shelf, with multiple random items, each an image button?

#11 Post by Hadriex »

I finally had time to work on it again, and am baffled. The if/then statement isn't working. I have the call for this screen in a loop and, well, here the relevant bit of code. I've been fiddling with it for awhile trying to simplify and isolate the problem.

---

default potion_idx = renpy.random.randint(0, 1) # adjust for list size -- you can also use len(potion_list)
default sword_idx = renpy.random.randint(0,1)
default random_potion = potion_list[potion_idx]
default random_sword = sword_list[sword_idx]
default button1 = 0
default leaveshop = 0

screen shop():


if button1 == 0:

imagebutton:
xalign 0.8
yalign 0.2
idle "items/{}.png".format(random_potion.image)
hover "items/{}glow.png".format(random_potion.image)
tooltip random_potion.tooltip
action [Call("purchasepot"), SetVariable("button1", 1)] #purchasepot is a label that checks which potion and charges money for it.

imagebutton:
xalign 0.4
yalign 1.0
idle "items/{}.png".format(random_sword.image)
hover "items/{}glow.png".format(random_sword.image)
tooltip random_sword.tooltip
action NullAction()

---

The result is an imagebutton that I click on, it displays the placeholder text in the 'purchasepot' label as it should. but the button does not disappear.

So if when I declare my variable I set it as '1' In other words "default button1 = 1", the button will never appear in the first place. So the if/then statement does it's job.

However, so long as the button appears in the first place (so long as I declare the variable button1 to be 0) no matter what method I try to increment the variable button1 or set it to 1, the button will remain visible and can be interacted with.

What is happening here?

philat
Eileen-Class Veteran
Posts: 1912
Joined: Wed Dec 04, 2013 12:33 pm
Contact:

Re: How do I make a shelf, with multiple random items, each an image button?

#12 Post by philat »

https://www.renpy.org/doc/html/screens.html beginning with the paragraph that starts "A screen has a scope associated with it, giving values to some variables..."

Hadriex
Newbie
Posts: 11
Joined: Mon Feb 05, 2024 12:11 am
Contact:

Re: How do I make a shelf, with multiple random items, each an image button?

#13 Post by Hadriex »

Ah, screen actions cannot increment global variables. Thank you.

User avatar
m_from_space
Miko-Class Veteran
Posts: 978
Joined: Sun Feb 21, 2021 3:36 am
Contact:

Re: How do I make a shelf, with multiple random items, each an image button?

#14 Post by m_from_space »

Hadriex wrote: Tue Feb 13, 2024 4:21 am Ah, screen actions cannot increment global variables. Thank you.
Not true. Screen actions can alter any variable.

Incrementing a global variable? Here you go:

Code: Select all

action SetVariable("myvariable", myvariable + 1)

# or (since Renpy 8.2)

action IncrementVariable("myvariable")
https://www.renpy.org/doc/html/screen_actions.html
However, so long as the button appears in the first place (so long as I declare the variable button1 to be 0) no matter what method I try to increment the variable button1 or set it to 1, the button will remain visible and can be interacted with.
The reason is, that your screen action list uses a call. A Call or a Jump should always be the last of a list of screen actions. Otherwise the other actions are not executed. By the way, just use a boolean (True or False) for anything visible or not etc.

Wrong:

Code: Select all

action [ Call("something"), SetVariable("button1", False) ]
Right:

Code: Select all

action [ SetVariable("button1", False), Call("something") ]

Hadriex
Newbie
Posts: 11
Joined: Mon Feb 05, 2024 12:11 am
Contact:

Re: How do I make a shelf, with multiple random items, each an image button?

#15 Post by Hadriex »

Oh. The link was to a thing on scope and it said that the variable would reset to it's original value at arbitrary times, or something like that. Maybe I misread it.

My workaround had just been to increment the variable in the label that was called. I figured, eh, probably for the best, the label checks to see if you have enough money and would make more sense as the spot to decide if the button should vanish anyway.

IncrementVariable was actually one of the many things I attempted. Never even occurred to me to move the call function to the end. Lol. Call and jump interrupt all future actions in a line, if I think of it as a goto I should be able to remember that in the future.

Post Reply

Who is online

Users browsing this forum: Google [Bot], Majestic-12 [Bot]