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
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:
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:
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