[SOLVED] Changing drag appearance after dragged onto a droppable

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
jepp21
Regular
Posts: 74
Joined: Fri Feb 10, 2023 3:46 pm
Contact:

[SOLVED] Changing drag appearance after dragged onto a droppable

#1 Post by jepp21 »

Hi all,
I'm creating a deli mini-game and trying to change the way a drag looks after it has been dropped. For example, I want a draggable image of a closed burger bun to change to an open bun image once it has been dropped onto the plate. I tried using

Code: Select all

set_child()
from https://www.renpy.org/doc/html/drag_dro ... .set_child but I'm getting an error. This is the function I am calling whenever a draggable is dragged

Code: Select all

def ingredient_dragged(drags, drop):
        #drop will be the drag object it was dropped onto.
        
        #id not placed on a droppable, do nothing
        if not drop: 
            return

        #currently snaps whenever the drag and drop overlap. 
        if drop: #id the drag was successfully dropped onto something
            drags[0].snap(drop.x,drop.y) #drop onto coordinates of droppable
            drags[0].draggable = False #makes it so image is no longer draggable
            drags[0].set_child("images/deli/bar/sauces.png") #change child to a different image
            return
Here is the traceback:
File "game/My Screens/deli/make.rpy", line 162, in ingredient_dragged
drags[0].set_child("images/deli/bar/sauces.png") #change child to a different image. not working?
AttributeError: 'str' object has no attribute 'per_interact'
Last edited by jepp21 on Mon Mar 27, 2023 3:26 pm, edited 1 time in total.

User avatar
_ticlock_
Miko-Class Veteran
Posts: 910
Joined: Mon Oct 26, 2020 5:41 pm
Contact:

Re: Changing drag appearance after dragged onto a droppable

#2 Post by _ticlock_ »

jepp21 wrote: Wed Mar 08, 2023 1:54 am AttributeError: 'str' object has no attribute 'per_interact'
It is because you need to use displayable as an argument. You can try the following:

Code: Select all

drags[0].set_child(renpy.displayable("images/deli/bar/sauces.png"))
Note: You are setting the child for the drag. It may not work because the drag image would be replaced with the previous one (supplied as drag child in screen language) with the next interaction.

jepp21
Regular
Posts: 74
Joined: Fri Feb 10, 2023 3:46 pm
Contact:

Re: Changing drag appearance after dragged onto a droppable

#3 Post by jepp21 »

_ticlock_ wrote: Wed Mar 08, 2023 1:32 pm
jepp21 wrote: Wed Mar 08, 2023 1:54 am AttributeError: 'str' object has no attribute 'per_interact'
It is because you need to use displayable as an argument. You can try the following:

Code: Select all

drags[0].set_child(renpy.displayable("images/deli/bar/sauces.png"))
Note: You are setting the child for the drag. It may not work because the drag image would be replaced with the previous one (supplied as drag child in screen language) with the next interaction.
Thanks for the reply. I tried your code out and the error disappeared, but the image never changed. I also tried
drags[0].set_child(Transform("images/deli/bar/sauces.png"))
and
drags[0].set_child(Image("images/deli/bar/sauces.png"))
and again, the error disappeared but the image never changed. I also tried using a file that doesn't exist and nothing happened. Maybe ren'py isnt executing this code for some reason? Or is it what you're saying about the next iteration?

jepp21
Regular
Posts: 74
Joined: Fri Feb 10, 2023 3:46 pm
Contact:

Re: Changing drag appearance after dragged onto a droppable

#4 Post by jepp21 »

jepp21 wrote: Wed Mar 08, 2023 2:04 pm
_ticlock_ wrote: Wed Mar 08, 2023 1:32 pm
jepp21 wrote: Wed Mar 08, 2023 1:54 am AttributeError: 'str' object has no attribute 'per_interact'
It is because you need to use displayable as an argument. You can try the following:

Code: Select all

drags[0].set_child(renpy.displayable("images/deli/bar/sauces.png"))
Note: You are setting the child for the drag. It may not work because the drag image would be replaced with the previous one (supplied as drag child in screen language) with the next interaction.
Thanks for the reply. I tried your code out and the error disappeared, but the image never changed. I also tried
drags[0].set_child(Transform("images/deli/bar/sauces.png"))
and
drags[0].set_child(Image("images/deli/bar/sauces.png"))
and again, the error disappeared but the image never changed. I also tried using a file that doesn't exist and nothing happened. Maybe ren'py isnt executing this code for some reason? Or is it what you're saying about the next iteration?
I just did some testing that may be useful. I changed the drag from:

Code: Select all

drag:
            drag_name "test bread"
            child "images/deli/bar/bread.png"
            xpos 500
            ypos 100
            draggable True
            droppable False
            dragged ingredient_dragged
            drag_raise True
to

Code: Select all

drag:
            drag_name "test bread"
            frame:
                xpadding 20
                ypadding 20
                text "Bread"
            xpos 500
            ypos 100
            draggable True
            droppable False
            dragged ingredient_dragged
            drag_raise True
and it worked this way. So it seems like after the function ingredient_dragged is called, it goes back to the screen with the drags and then child "images/deli/bar/bread.png" changes the image back. I still want the drag to be an image instead of text but at least we know where the issue lies

User avatar
_ticlock_
Miko-Class Veteran
Posts: 910
Joined: Mon Oct 26, 2020 5:41 pm
Contact:

Re: Changing drag appearance after dragged onto a droppable

#5 Post by _ticlock_ »

jepp21 wrote: Wed Mar 08, 2023 2:34 pm So it seems like after the function ingredient_dragged is called, it goes back to the screen with the drags and then child "images/deli/bar/bread.png" changes the image back. I still want the drag to be an image instead of text but at least we know where the issue lies
EDIT: I missed that you didn't end the interaction. Ignore this post.



Right. It happens because you set the drag child as a static image. That is what I was trying to say in the "Note".



Here is a simple example with a global variable:

Code: Select all

default drag_child = "images/deli/bar/bread.png"

Code: Select all

drag:
            drag_name "test bread"
            child drag_child

Code: Select all

def ingredient_dragged(drags, drop):
        #drop will be the drag object it was dropped onto.
        
        #id not placed on a droppable, do nothing
        if not drop: 
            return

        #currently snaps whenever the drag and drop overlap. 
        if drop: #id the drag was successfully dropped onto something
            drags[0].snap(drop.x,drop.y) #drop onto coordinates of droppable
            drags[0].draggable = False #makes it so image is no longer draggable
            store.drag_child = "images/deli/bar/sauces.png"
            return True
PS: You can use dictionaries with drag_name as keywords, or class variables, etc., instead of global variables to have cleaner code.

Check this example. It can be a bit complicated due to 2D array but still useful.
Last edited by _ticlock_ on Tue Mar 14, 2023 2:03 pm, edited 4 times in total.

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

Re: Changing drag appearance after dragged onto a droppable

#6 Post by m_from_space »

I think this might be a bug. But it's possible that I don't understand what the intention of this function actually is. Also the reason it raises an exception when you provide a string (which usually works, since Renpy creates simple Displayables by default), is inside the code:

Code: Select all

def set_child(self, d):
        """
        :doc: drag_drop method
        Changes the child of this drag to `d`.
        """

        d.per_interact()
        self.child = renpy.easy.displayable(d)
        renpy.display.render.invalidate(self)
It tries to call "per_interact()" on a string. I think it should be something like:

Code: Select all

def set_child(self, d):
        """
        :doc: drag_drop method
        Changes the child of this drag to `d`.
        """

        self.child = renpy.easy.displayable(d)
        self.child.per_interact()
        renpy.display.render.invalidate(self)
(Note: I don't quite know what this function actually does.)

Also the child is not changed after this for whatever reason, maybe because per_interact() is called on a local argument, instead of the child.

If @Ocelot or other pros on here think that might be true, I would create an issue on github.

jepp21
Regular
Posts: 74
Joined: Fri Feb 10, 2023 3:46 pm
Contact:

Re: Changing drag appearance after dragged onto a droppable

#7 Post by jepp21 »

_ticlock_ wrote: Wed Mar 08, 2023 3:04 pm
jepp21 wrote: Wed Mar 08, 2023 2:34 pm So it seems like after the function ingredient_dragged is called, it goes back to the screen with the drags and then child "images/deli/bar/bread.png" changes the image back. I still want the drag to be an image instead of text but at least we know where the issue lies
Right. It happens because you set the drag child as a static image. That is what I was trying to say in the "Note".

Here is a simple example with a global variable:

Code: Select all

default drag_child = "images/deli/bar/bread.png"

Code: Select all

drag:
            drag_name "test bread"
            child drag_child

Code: Select all

def ingredient_dragged(drags, drop):
        #drop will be the drag object it was dropped onto.
        
        #id not placed on a droppable, do nothing
        if not drop: 
            return


        #currently snaps whenever the drag and drop overlap. 
        if drop: #id the drag was successfully dropped onto something
            drags[0].snap(drop.x,drop.y) #drop onto coordinates of droppable
            drags[0].draggable = False #makes it so image is no longer draggable
            drags[0].set_child(renpy.displayable("images/deli/bar/sauces.png"))
            store.drag_child = "images/deli/bar/sauces.png"
            return
PS: You can use dictionaries with drag_name as keywords, or class variables, etc., instead of global variables to have cleaner code.

Check this example. It can be a bit complicated due to 2D array but still useful.
Okay, this makes sense. However, I tried your example and for some reason it still didn't work. I checked the console and drag_child was not altered to the new file name. I'll check out the tutorial when I have more time, but would you happen to know why your example may not have worked?

User avatar
_ticlock_
Miko-Class Veteran
Posts: 910
Joined: Mon Oct 26, 2020 5:41 pm
Contact:

Re: Changing drag appearance after dragged onto a droppable

#8 Post by _ticlock_ »

jepp21 wrote: Wed Mar 08, 2023 4:39 pm Okay, this makes sense. However, I tried your example and for some reason it still didn't work. I checked the console and drag_child was not altered to the new file name. I'll check out the tutorial when I have more time, but would you happen to know why your example may not have worked?
I am sorry. I did not pay attention. I missed that you didn't end the interaction.
m_from_space wrote: Wed Mar 08, 2023 3:27 pm
I agree, I don't see a reason to have per_interact() before self.child = renpy.easy.displayable(d).
Although, I don't think it is related to why the image is not changed. I'll have a look at it a bit later.

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

Re: Changing drag appearance after dragged onto a droppable

#9 Post by m_from_space »

jepp21 wrote: Wed Mar 08, 2023 4:39 pm Okay, this makes sense. However, I tried your example and for some reason it still didn't work.
While I still think set_child() has a bug, the suggestion by @_ticlock_ works if you insert renpy.restart_interaction() after changing the drag variable. And you don't have to use set_child() anymore at all.

Code: Select all

if drop: #id the drag was successfully dropped onto something
            drags[0].snap(drop.x,drop.y) #drop onto coordinates of droppable
            drags[0].draggable = False #makes it so image is no longer draggable
            store.drag_child = "images/deli/bar/sauces.png"
            renpy.restart_interaction()
            return

jepp21
Regular
Posts: 74
Joined: Fri Feb 10, 2023 3:46 pm
Contact:

Re: Changing drag appearance after dragged onto a droppable

#10 Post by jepp21 »

m_from_space wrote: Wed Mar 08, 2023 5:05 pm
jepp21 wrote: Wed Mar 08, 2023 4:39 pm Okay, this makes sense. However, I tried your example and for some reason it still didn't work.
While I still think set_child() has a bug, the suggestion by @_ticlock_ works if you insert renpy.restart_interaction() after changing the drag variable. And you don't have to use set_child() anymore at all.

Code: Select all

if drop: #id the drag was successfully dropped onto something
            drags[0].snap(drop.x,drop.y) #drop onto coordinates of droppable
            drags[0].draggable = False #makes it so image is no longer draggable
            store.drag_child = "images/deli/bar/sauces.png"
            renpy.restart_interaction()
            return

Thanks for the reply. This worked, but it yielded an issue with other parts of my function. The image does change, but now the draggable is set to True again, so the user can move the object again when I want it to remain locked in place.

User avatar
Alex
Lemma-Class Veteran
Posts: 3094
Joined: Fri Dec 11, 2009 5:25 pm
Contact:

Re: Changing drag appearance after dragged onto a droppable

#11 Post by Alex »

jepp21 wrote: Wed Mar 08, 2023 5:13 pm ... The image does change, but now the draggable is set to True again, so the user can move the object again when I want it to remain locked in place.
So, you've made a drag's child be a variable - go farther, make all the drag's properties be variables...))

This might help - viewtopic.php?f=8&t=62944#p545717

jepp21
Regular
Posts: 74
Joined: Fri Feb 10, 2023 3:46 pm
Contact:

Re: Changing drag appearance after dragged onto a droppable

#12 Post by jepp21 »

Alex wrote: Wed Mar 08, 2023 6:56 pm
jepp21 wrote: Wed Mar 08, 2023 5:13 pm ... The image does change, but now the draggable is set to True again, so the user can move the object again when I want it to remain locked in place.
So, you've made a drag's child be a variable - go farther, make all the drag's properties be variables...))

This might help - viewtopic.php?f=8&t=62944#p545717
Thanks, I took a look at your example, but the variables weren't updating as expected. Here's what I tried:

Code: Select all

default ingredients_draggable = {
    "test bread": True,
    }

Code: Select all

def ingredient_dragged(drags, drop):
        global drag_child, ingredients_draggable
        #drop will be the drag object it was dropped onto
        
        #if not placed on droppable, snap back to original pos
        if not drop: 
            return

        #currently snaps whenever the drag and drop overlap. can mess w/ if statement to change this
        if drop: #if the drag was successfully dropped onto something
            drags[0].snap(drop.x,drop.y) #drop onto coordinates of droppable
            store.drag_child = "images/deli/bar/sauces.png" #change image
            store.ingredients_draggable["[drags[0]]"] = False #sets item from dictionary draggable to false
            renpy.restart_interaction() #changes child but restart interaction undoes the previous lines
            return

Code: Select all

drag:
            drag_name "test bread"
            child drag_child
            xpos 500
            ypos 100
            draggable ["test bread"]
            droppable False
            dragged ingredient_dragged
            drag_raise True
The image is again changing as expected but I can still drag it. The console says that the key value is still set to True

User avatar
_ticlock_
Miko-Class Veteran
Posts: 910
Joined: Mon Oct 26, 2020 5:41 pm
Contact:

Re: Changing drag appearance after dragged onto a droppable

#13 Post by _ticlock_ »

m_from_space wrote: Wed Mar 08, 2023 5:05 pm While I still think set_child() has a bug
I agree. There is a bug with setting child.

Code: Select all

def render(self, width, height, st, at):

        child = self.style.child
        if child is None:
            child = self.child
The render method use `self.style.child` instead of `self.child` if child property was provided with drag. Method set_child() sets `self.child`.

As a result, the displayable can't be changed with `set_child()` method if child property was provided with drag.
m_from_space wrote: Wed Mar 08, 2023 3:27 pm I would create an issue on github.
Go ahead.

User avatar
_ticlock_
Miko-Class Veteran
Posts: 910
Joined: Mon Oct 26, 2020 5:41 pm
Contact:

Re: Changing drag appearance after dragged onto a droppable

#14 Post by _ticlock_ »

jepp21 wrote: Wed Mar 08, 2023 11:47 pm The image is again changing as expected but I can still drag it. The console says that the key value is still set to True
If you use renpy.restart_interaction() you need to use variables for all properties that you want to change. Something like this:

Code: Select all

default drag_dict = {
    "child": "images/deli/bar/bread.png",
    "x": 500,
    "y": 100,
    "draggable": True
    }

Code: Select all

drag:
            drag_name "test bread"
            child drag_dict["child"]
            xpos drag_dict["x"]
            ypos drag_dict["y"]
            draggable drag_dict["draggable"]
            ...

Code: Select all

def ingredient_dragged(drags, drop):
        #drop will be the drag object it was dropped onto
        
        #if not placed on droppable, snap back to original pos
        if not drop: 
            return

        #currently snaps whenever the drag and drop overlap. can mess w/ if statement to change this
        if drop: #if the drag was successfully dropped onto something
            drags[0].snap(drop.x,drop.y) #drop onto coordinates of droppable
            store.drag_dict["child"] = "images/deli/bar/sauces.png" 
            store.drag_dict["draggable"] = False
            store.drag_dict["x"] = drags[0].x
            store.drag_dict["y"] = drags[0].y
            renpy.restart_interaction() #changes child but restart interaction undoes the previous lines
            return
Last edited by _ticlock_ on Thu Mar 09, 2023 10:56 am, edited 1 time in total.

jepp21
Regular
Posts: 74
Joined: Fri Feb 10, 2023 3:46 pm
Contact:

Re: Changing drag appearance after dragged onto a droppable

#15 Post by jepp21 »

_ticlock_ wrote: Thu Mar 09, 2023 12:14 am
jepp21 wrote: Wed Mar 08, 2023 11:47 pm The image is again changing as expected but I can still drag it. The console says that the key value is still set to True
If you use renpy.restart_interaction() you need to use variables for all properties that you want to change. Something like this:

Code: Select all

default drag_dict = {
    "child": "images/deli/bar/bread.png",
    "x": 500,
    "y": 100,
    "draggable": True
    }

Code: Select all

drag:
            drag_name "test bread"
            child drag_dict["child"]
            xpos drag_dict["x"]
            ypos drag_dict["y"]
            draggable drag_dict["draggable"]
            ...

Code: Select all

def ingredient_dragged(drags, drop):
        global drag_child, ingredients_draggable
        #drop will be the drag object it was dropped onto
        
        #if not placed on droppable, snap back to original pos
        if not drop: 
            return

        #currently snaps whenever the drag and drop overlap. can mess w/ if statement to change this
        if drop: #if the drag was successfully dropped onto something
            drags[0].snap(drop.x,drop.y) #drop onto coordinates of droppable
            store.drag_dict["child"] = "images/deli/bar/sauces.png" 
            store.drag_dict["draggable"] = False
            store.drag_dict["x"] = drags[0].x
            store.drag_dict["y"] = drags[0].y
            renpy.restart_interaction() #changes child but restart interaction undoes the previous lines
            return
Thanks! This worked as expected. I've been wondering how to go about adding a few other drags. I'm hoping I won't have to create new dictionaries and ingredient_dragged functions for every individual drag.

Post Reply

Who is online

Users browsing this forum: Google [Bot]