[SOLVED] how to make a draggable item snap back to its original position ?

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.
Post Reply
Message
Author
half1-2moon
Newbie
Posts: 7
Joined: Fri Mar 01, 2024 5:49 pm
Contact:

[SOLVED] how to make a draggable item snap back to its original position ?

#1 Post by half1-2moon »

hello, how to make a draggable item snap back to its original position, when its on the wrong droppable item.
so i have four items that are draggable, they are 4 balls. and i have two boxes. and i have the function like this: ( i am not a programmer, i follow some tutorials and i learn)

Code: Select all

def dragged_func(dragged_items, dropped_on):
      if dropped_on is not None:
         if dragged_items[0].drag_name == "ball_1" and dropped_on.drag_name == "rightbox":
            #dragged_items[0].snap(dropped_on.x, dropped_on.y, 0.5)
            my_draggroup.remove(ball_1_drag)
            renpy.play("/audio/correct.mp3")
         elif dropped_on.drag_name == "rightbox":
            renpy.play("/audio/incorrect.mp3")
            dragged_items[0].snap(0.75, 0.25)
the balls do return to (0.75, 0.25) but i need each one to return to their initial allignment when its their wrong box. also how i add ball_2 to the first condition.
just if anyone has a suggestion or a tutorial on this i can watch or follow i appreciate it, i know its a whole language i need to learn. thanks.
Last edited by half1-2moon on Fri Mar 29, 2024 11:44 am, edited 1 time in total.

jeffster
Veteran
Posts: 409
Joined: Wed Feb 03, 2021 9:55 pm
Contact:

Re: how to make a draggable item snap back to its original position ?

#2 Post by jeffster »

Let us analyze this function step by step, so you will understand everything it does.

Code: Select all

    def dragged_func(dragged_items, dropped_on):
        if dropped_on is not None:
            if dragged_items[0].drag_name == "ball_1" and dropped_on.drag_name == "rightbox":
                #dragged_items[0].snap(dropped_on.x, dropped_on.y, 0.5)
                my_draggroup.remove(ball_1_drag)
                renpy.play("/audio/correct.mp3")
            elif dropped_on.drag_name == "rightbox":
                renpy.play("/audio/incorrect.mp3")
                dragged_items[0].snap(0.75, 0.25)
First line is a standard template explained here:
https://renpy.org/doc/html/drag_drop.html#Drag

When you create "Drag" object, you set dragged parameter, assigning it the function you want to run when this "drag" is dropped.

The documentation says:

A callback (or list of callbacks) that is called when the Drag has been dragged. It is called with two arguments. The first is a list of Drags that are being dragged. The second is either a Drag that is being dropped onto, or None of a drop did not occur. If the callback returns a value other than None, that value is returned as the result of the interaction.

Even if in your program you only drag one ball, it's generally possible to drag several objects at once (if they are kinda linked).

Therefore instead of one item (the ball) you have to use a list as the 1st parameter of this callback:

Code: Select all

    # Instead of
    # def dragged_func(ball, dropped_on):
    # use this:
    def dragged_func(dragged_items, dropped_on):
dragged_items here will be a list with one element (the ball that was dragged there), and you address this ball as

Code: Select all

dragged_items[0]
Here ...[0] means "the first item of the list" (i.e. at index == 0).

The second line is checking if the item was dropped on another item, or somewhere else:

Code: Select all

        if dropped_on is not None:
If you want the ball to return back when not dropped into the right box, then you can use this construction:

Code: Select all

    def dragged_func(dragged_items, dropped_on):
        if dropped_on and dropped_on.drag_name == "rightbox":
            # Correct. Do what you need here
            # ...
            # and return from the function:
            return

        # Otherwise (wrong box or not in a box at all) - Incorrect.
        # Snap it back.
Do you understand this construction?
The second line checks if the drag (the ball) was dropped onto a drag:

Code: Select all

if dropped_on
and that the target drag's name is "rightbox":

Code: Select all

and dropped_on.drag_name == "rightbox":
If that's clear, let's move on. For correct dropping we have this code:

Code: Select all

                dragged_items[0].snap(dropped_on.x, dropped_on.y, 0.5)
                my_draggroup.remove(ball_1_drag)
                renpy.play("/audio/correct.mp3")
The first of those lines adjusts the dropped item at the box it goes to.
Otherwise, as you drop the ball not precisely at the center, it would remain at some inaccurate position.

Of course, if you want several balls to be arranged in the box, then you would need to adjust their positions. For example, if the box is horizontal and have space for 4 balls,

Code: Select all

1st ball goes to x == 0.0 (i.e. at 0% of width)
2nd ball goes to x == 0.25 (i.e. at 25% of width)
3rd ball goes to x == 0.5 (i.e. at 50% of width)
4th ball goes to x == 0.75 (i.e. at 75% of width)
To do that, let's imagine that your variable that counts correctly placed balls is called balls_done. And for simplicity let's say the box horizontal size is 100 pixels. Then we can do:

Code: Select all

                dragged_items[0].snap(
                    dropped_on.x + 25 * balls_done,
                    dropped_on.y,
                    0.5)
That way correctly dropped balls would be arranged in a precise line:

Code: Select all

1st ball: dropped_on.x
2nd ball: dropped_on.x + 25
3rd ball: dropped_on.x + 50
4th ball: dropped_on.x + 75
The next two lines

Code: Select all

            my_draggroup.remove(ball_1_drag)
            renpy.play("/audio/correct.mp3")
remove the dragged ball from draggables and play the sound "correct".

As we have several balls, instead of "ball_1_drag" we should use the current draggable: "dragged_items[0]".

Here's our function now:

Code: Select all

    def dragged_func(dragged_items, dropped_on):
        if dropped_on and dropped_on.drag_name == "rightbox":
            # Correct
            dragged_items[0].snap(
                dropped_on.x + 25 * balls_done,
                dropped_on.y,
                0.5)
            my_draggroup.remove(dragged_items[0])
            renpy.play("/audio/correct.mp3")
            return

        # Incorrect (snap the ball back)
        renpy.play("/audio/incorrect.mp3")
        dragged_items[0].snap(0.75, 0.25)
(There could be another line at the end, "return". But in Python you can omit "return" at the end of the function block. It's there implicitly).

Now you only need to correct this last line: instead of snap(0.75, 0.25) put the original coordinates of the ball that was dragged.

I don't know how to do that in the simplest way (depending on the data structures of your program), but one of the ways is to keep original coordinates of the balls in some list or dict.

For example, if drag ball_1_drag has name like "ball_1", and so on:

Code: Select all

define balls_pos = {
    "ball_1": (1200, 200),
    "ball_2": (1300, 200),
    "ball_3": (1400, 200),
    "ball_4": (1500, 200),
    }
Meaning: we have set balls positions at
1st ball: x == 1200, y == 200
2nd ball: x == 1300, y == 200
...and so on.

Then we can write that last line like this:

Code: Select all

        dragged_items[0].snap(*balls_pos[dragged_items[0].drag_name])
If you are wondering what does the star mean in *balls_pos[dragged_items[0].drag_name] - it's just "unpacking" of a tuple (x, y) into 2 separate values. See
https://docs.python.org/3/tutorial/datastructures.html

Of course, for this to work, every ball "drag" should have their name set like "ball_1" and so on. Something like this:

Code: Select all

        drag:
            drag_name "ball_1"
            pos balls_pos["ball_1"]

        #...

        drag:
            drag_name "ball_2"
            pos balls_pos["ball_2"]

        # and so on
PS. (Edited to correct a few errors). Finally we have:

Code: Select all

# balls_done (correctly dragged):
default balls_done = 0

# The original positions for the balls:
define balls_pos = {
    "ball_1": (1200, 200),
    "ball_2": (1300, 200),
    "ball_3": (1400, 200),
    "ball_4": (1500, 200),
    }

#
# ...and in the screen, balls are defined like
#
        drag:
            drag_name "ball_1"
            pos balls_pos["ball_1"]

        #...

        drag:
            drag_name "ball_2"
            pos balls_pos["ball_2"]

        #...

# The function called when a ball is dropped:
init python:
    def dragged_func(dragged_items, dropped_on):
        if dropped_on and dropped_on.drag_name == "rightbox":
            # Correct
            dragged_items[0].snap(
                dropped_on.x + 25 * balls_done,
                dropped_on.y,
                0.5)
            my_draggroup.remove(dragged_items[0])
            renpy.play("/audio/correct.mp3")
            store.balls_done += 1
            return

        # Incorrect (snap the ball back)
        renpy.play("/audio/incorrect.mp3")
        dragged_items[0].snap(*balls_pos[dragged_items[0].drag_name])
PPS. Why I use "store.balls_done", not just "balls_done" - because "balls_done" would be treated as a local variable, inside the function, and changing it wouldn't affect anything outside the function. (And BTW it would be undefined).
Why we use "store." namespace in Ren'Py:
https://renpy.org/doc/html/python.html# ... -the-store

half1-2moon
Newbie
Posts: 7
Joined: Fri Mar 01, 2024 5:49 pm
Contact:

Re: how to make a draggable item snap back to its original position ?

#3 Post by half1-2moon »

thank you so much jeffster for taking the time i appreciate it.
i did understand more about the function as you explained it like that. what i changed in my code is that i added define balls_pos and dragged_items[0].snap(*balls_pos[dragged_items[0].drag_name]) to my incorrect condition, so my code looks like this now :

Code: Select all

    def dragged_func(dragged_items, dropped_on):
        if dropped_on is not None:
            if dragged_items[0].drag_name == "ball_1" and dropped_on.drag_name == "redbox":
            # Correct
                my_draggroup.remove(ball_1_drag)
                renpy.play("/audio/correct.mp3")
            # Incorrect
                        elif dropped_on.drag_name == "redbox":
                		renpy.play("/audio/incorrect.mp3")
                		dragged_items[0].snap(*balls_pos[dragged_items[0].drag_name])
            return
define balls_pos = {
    "ball_1": (0.5, 0.60),
    "ball_2": (0.25, 0.60),
    "ball_3": (0.5, 0.85),
    "ball_4": (0.25, 0.85),
    "ball_5": (0.75, 0.85),
    }
its a start, as i certainly need to read again your reply and check the documentation. to understand the rest. thank you.
what i want to achieve is a function that can manage the following :
-having 5 balls on the screen with different colors.
-having 3 boxes, each box has a color.
-dragging and droopping a ball onto its correct box should disappear.
-dragging and dropping a ball onto the wrong box should snap it back to its initial positition.
i will post here if i manage to.

jeffster
Veteran
Posts: 409
Joined: Wed Feb 03, 2021 9:55 pm
Contact:

Re: how to make a draggable item snap back to its original position ?

#4 Post by jeffster »

half1-2moon wrote: Thu Mar 14, 2024 11:01 pm

Code: Select all

    def dragged_func(dragged_items, dropped_on):
        if dropped_on is not None:
            if dragged_items[0].drag_name == "ball_1" and dropped_on.drag_name == "redbox":
            # Correct
                my_draggroup.remove(ball_1_drag)
                renpy.play("/audio/correct.mp3")
            # Incorrect
                        elif dropped_on.drag_name == "redbox":
                		renpy.play("/audio/incorrect.mp3")
                		dragged_items[0].snap(*balls_pos[dragged_items[0].drag_name])
-dragging and dropping a ball onto its correct box should disappear.
Note that both in Python and Ren'Py indentation is a meaningful part of syntax.
E.g. "if" and related "elif" must be indented with the same exact amount of spaces:

Code: Select all

            if dragged_items[0].drag_name == "ball_1" and dropped_on.drag_name == "redbox":
                # Correct
                my_draggroup.remove(ball_1_drag)
                renpy.play("/audio/correct.mp3")

            elif dropped_on.drag_name == "redbox":
                # Incorrect
                renpy.play("/audio/incorrect.mp3")
                dragged_items[0].snap(*balls_pos[dragged_items[0].drag_name])
To check if a ball color corresponds with the box color, you can keep the balls colors in a dict like that "balls_pos" that we used for positions:

Code: Select all

default colors = {
    "ball_1": "red",
    "ball_2": "blue",
    "ball_3": "green",
    "ball_4": "red",
    "ball_5": "blue",
    }
Or to set the ball color dynamically, we can:

Code: Select all

python:
    colors["ball_4"] = "green"
Then to compare:

Code: Select all

            if colors[dragged_items[0].drag_name] == dropped_on.drag_name:
                # Correct
                my_draggroup.remove(dragged_items[0])
                renpy.play("/audio/correct.mp3")
                return

            # Incorrect
            renpy.play("/audio/incorrect.mp3")
            dragged_items[0].snap(*balls_pos[dragged_items[0].drag_name])
meaning it's a "correct" drop if "colors" of the ball is the same string (e.g. "green") as the name of the box dropped on:

Meaning

colors["ball_4"] == "green"
AND
dropped_on.drag_name == "green"

=> "correct". Otherwise "incorrect"...

half1-2moon
Newbie
Posts: 7
Joined: Fri Mar 01, 2024 5:49 pm
Contact:

Re: how to make a draggable item snap back to its original position ?

#5 Post by half1-2moon »

thank you, i corrected the indentation and defined colors for the balls like this, but renpy is giving me an invalid syntax for :
default colors = {

Code: Select all

# The function called when a ball is dropped:
init python:
    # Check if a ball color corresponds with the box color
    if colors[dragged_items[0].drag_name] == dropped_on.drag_name:
        # Correct
        my_draggroup.remove(dragged_items[0])
        renpy.play("/audio/correct.mp3")
    else:
        # Incorrect
        renpy.play("/audio/incorrect.mp3")
        dragged_items[0].snap(*balls_pos[dragged_items[0].drag_name])
    
    # Define colors for balls
    default colors = {
        "ball_1": "red",
        "ball_2": "yellow",
        "ball_3": "red",
        "ball_4": "yellow",
        "ball_5": "green",
    }

    # Define positions for balls
    balls_pos = {
        "ball_1": (0.5, 0.60),
        "ball_2": (0.25, 0.60),
        "ball_3": (0.5, 0.85),
        "ball_4": (0.25, 0.85),
        "ball_5": (0.75, 0.85),
    }


this worked for me, except the balls snap back not exactly to their positions, but shifted a bit.

Code: Select all

def dragged_func(dragged_items, dropped_on):
        if dropped_on is not None:
            if (dragged_items[0].drag_name == "ball_1" or dragged_items[0].drag_name == "ball_3") and dropped_on.drag_name == "redbox":
            # Correct
                my_draggroup.remove(dragged_items[0])
                renpy.play("/audio/correct.mp3")
            elif (dragged_items[0].drag_name == "ball_2" or dragged_items[0].drag_name == "ball_4") and dropped_on.drag_name == "yellowbox":
            # Correct
                my_draggroup.remove(dragged_items[0])
                renpy.play("/audio/correct.mp3")
            elif dragged_items[0].drag_name == "ball_5" and dropped_on.drag_name == "greenbox":
            # Correct
                my_draggroup.remove(dragged_items[0])
                renpy.play("/audio/correct.mp3")
            # incorrect
            else:
                renpy.play("/audio/incorrect.mp3")
                dragged_items[0].snap(*balls_pos[dragged_items[0].drag_name])
            return
            
                # Define positions for balls
    	balls_pos = {
        	"ball_1": (0.5, 0.60),
        	"ball_2": (0.25, 0.60),
        	"ball_3": (0.5, 0.85),
        	"ball_4": (0.25, 0.85),
        	"ball_5": (0.75, 0.85),
    		}

jeffster
Veteran
Posts: 409
Joined: Wed Feb 03, 2021 9:55 pm
Contact:

Re: how to make a draggable item snap back to its original position ?

#6 Post by jeffster »

half1-2moon wrote: Fri Mar 15, 2024 8:21 am thank you, i corrected the indentation and defined colors for the balls like this, but renpy is giving me an invalid syntax for :
default colors = {
"default" and "define" are Ren'Py statements, not Python.
They should be outside of "python:" block.
It's pretty important to understand how to use them, regarding values that could change in the game or not:
https://renpy.org/doc/html/python.html
this worked for me, except the balls snap back not exactly to their positions, but shifted a bit.
It could be related to padding in the screen you use:
https://renpy.org/doc/html/style_proper ... properties
or maybe margins:
https://renpy.org/doc/html/style_proper ... properties

By default, there can be some padding and margins in elements like "frame" etc. You can try to explicitly set them to (0, 0), to avoid shifted positions.

Something like:

Code: Select all

screen balls_boxes():
    frame:
        padding (0, 0)
        margin (0, 0)
        #...

half1-2moon
Newbie
Posts: 7
Joined: Fri Mar 01, 2024 5:49 pm
Contact:

Re: how to make a draggable item snap back to its original position ?

#7 Post by half1-2moon »

i've put them outside the python block and had this error

Code: Select all

line 21: expected statement.
    balls_pos = {
              ^
        "ball_1": (0.5, 0.60),
        "ball_2": (0.25, 0.60),
        "ball_3": (0.5, 0.85),
        "ball_4": (0.25, 0.85),
        "ball_5": (0.75, 0.85),
        }

Code: Select all

# Define colors for balls
default colors = {
    "ball_1": "red",
    "ball_2": "yellow",
    "ball_3": "red",
    "ball_4": "yellow",
    "ball_5": "green",
    }

# Define positions for balls
balls_pos = {
    "ball_1": (0.5, 0.60),
    "ball_2": (0.25, 0.60),
    "ball_3": (0.5, 0.85),
    "ball_4": (0.25, 0.85),
    "ball_5": (0.75, 0.85),
    }

# The function called when a ball is dropped:
init python:
    def dragged_func(dragged_items, dropped_on):
    # Check if a ball color corresponds with the box color
    if colors[dragged_items[0].drag_name] == dropped_on.drag_name:
        # Correct
        my_draggroup.remove(dragged_items[0])
        renpy.play("/audio/correct.mp3")
    else:
        # Incorrect
        renpy.play("/audio/incorrect.mp3")
        dragged_items[0].snap(*balls_pos[dragged_items[0].drag_name])
        return
and for the shifted positions of the balls after being snapped, i dont use any frames and i dont have or paddings or margins.

jeffster
Veteran
Posts: 409
Joined: Wed Feb 03, 2021 9:55 pm
Contact:

Re: how to make a draggable item snap back to its original position ?

#8 Post by jeffster »

half1-2moon wrote: Fri Mar 15, 2024 6:00 pm i've put them outside the python block and had this error

Code: Select all

line 21: expected statement.
    balls_pos = {
              ^
        "ball_1": (0.5, 0.60),
        "ball_2": (0.25, 0.60),
        "ball_3": (0.5, 0.85),
        "ball_4": (0.25, 0.85),
        "ball_5": (0.75, 0.85),
        }
Please read my previous post.

Code: Select all

# The function called when a ball is dropped:
init python:
    def dragged_func(dragged_items, dropped_on):
    # Check if a ball color corresponds with the box color
    if colors[dragged_items[0].drag_name] == dropped_on.drag_name:
        # Correct
        my_draggroup.remove(dragged_items[0])
        renpy.play("/audio/correct.mp3")
    else:
        # Incorrect
        renpy.play("/audio/incorrect.mp3")
        dragged_items[0].snap(*balls_pos[dragged_items[0].drag_name])
        return
Please read about indentation.
and for the shifted positions of the balls after being snapped, i dont use any frames and i dont have or paddings or margins.
There's probably some difference between the way balls are displayed in the screen and their positions when snapped.

To investigate the current screen, styles etc., use "Shift+I Style Inspecting":
https://renpy.org/doc/html/developer_to ... inspecting

half1-2moon
Newbie
Posts: 7
Joined: Fri Mar 01, 2024 5:49 pm
Contact:

Re: how to make a draggable item snap back to its original position ?

#9 Post by half1-2moon »

hello. yes i corrected the default statement for balls_pos. and corrected the indetation and its working. thank you alot jeffster.
as for the position of the balls being shifted after the snap i have started a new project, changed the images, i have no margins and no padding on my screens or styles, the balls are displayed using align, like this :
default ball_5_drag = Drag(d = "ball_5.jpeg", drag_name = "ball_5", drag_raise = True, align = (0.75, 0.85))
and investigating using shift+I doesnt give me anything about a frame of a style or margins or other, i have no idea why it shifts.

jeffster
Veteran
Posts: 409
Joined: Wed Feb 03, 2021 9:55 pm
Contact:

Re: how to make a draggable item snap back to its original position ?

#10 Post by jeffster »

half1-2moon wrote: Sat Mar 16, 2024 1:12 pm hello. yes i corrected the default statement for balls_pos. and corrected the indetation and its working. thank you alot jeffster.
as for the position of the balls being shifted after the snap i have started a new project, changed the images, i have no margins and no padding on my screens or styles, the balls are displayed using align, like this :
default ball_5_drag = Drag(d = "ball_5.jpeg", drag_name = "ball_5", drag_raise = True, align = (0.75, 0.85))
and investigating using shift+I doesnt give me anything about a frame of a style or margins or other, i have no idea why it shifts.
Ah, here it is: "align" sets both position and anchor.

An explanation:
https://feniksdev.com/renpy-position-pr ... set/#Align
Docs:
https://renpy.org/doc/html/style_proper ... properties

I guess if you set the Drag with "pos" instead of "align", they won't shift when snapping back.

half1-2moon
Newbie
Posts: 7
Joined: Fri Mar 01, 2024 5:49 pm
Contact:

Re: how to make a draggable item snap back to its original position ?

#11 Post by half1-2moon »

it was exactly that, setting the drags with pos instead of align fixed it, and finally all my code is working. thanks a lot, for the documentation too.

Post Reply

Who is online

Users browsing this forum: Ahrefs [Bot], Bing [Bot]