Help with Drag and Drop Inventory System

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
Counter Arts
Miko-Class Veteran
Posts: 649
Joined: Fri Dec 16, 2005 5:21 pm
Completed: Fading Hearts, Infinite Game Works
Projects: Don't Save the World
Organization: Sakura River
Location: Canada
Contact:

Help with Drag and Drop Inventory System

#1 Post by Counter Arts »

So yeah. Can anyone help me with this?

Here is most of a test case of an inventory system. However I can't seem to get rid of this bug where one of the items swaps places with the one that's currently being dragged.

Code: Select all

# You can place the script of your game in this file.

# Declare images below this line, using the image statement.
# eg. image eileen happy = "eileen_happy.png"

# Declare characters used by this game.
define e = Character('Eileen', color="#c8ffc8")

init python:

    import inspect

    def testDrop(target, sourceDrop):
        print "Target: {0}\n Source: {1}".format(target.drag_name, sourceDrop[0].drag_name)

    def returnDrop(xPos,yPos):

        def innerReturnDrop(sourceDrop, target ):
            if target == None:
                return None
            sourceDrop[0].snap(xPos, yPos, delay=10)
            print "Target: {0}\n Source: {1}".format(target.drag_name, sourceDrop[0].drag_name)
            renpy.restart_interaction
        return innerReturnDrop


    def resetIndexLocation(sourceWidget, newList, originalItem, newBoxX, newBoxY):
        num = newList.index(originalItem)
        xLocation = newBoxX + (num % 2) * (54 + 60)
        yLocation = newBoxY + (num // 2) * 54
        sourceWidget.snap(xLocation, yLocation)


    def giveItem(sourceList, theItem, originalX, originalY):

        def innerGiveItem(sourceDrop, target):
            global inventory1, inventory2
            if target == None:

                sourceDrop[0].snap(originalX, originalY)
            else:
                if target.drag_name == "Inv1":
                    if sourceList == inventory1:
                        sourceDrop[0].snap(originalX, originalY)
                    else:
                        sourceList.remove(theItem)
                        inventory1.append(theItem)
                        #resetIndexLocation(sourceDrop[0],inventory1, theItem, 25, 305)

                elif target.drag_name == "Inv2":
                    if sourceList == inventory2:
                        sourceDrop[0].snap(originalX, originalY)
                    else:
                        sourceList.remove(theItem)
                        inventory2.append(theItem)
                        #resetIndexLocation(sourceDrop[0],inventory2, theItem,355, 305)
                else:
                    pass
                    #sourceDrop[0].snap(originalX, originalY)
            renpy.restart_interaction()
        return innerGiveItem



transform testTransform:

    on show:
        alpha 0.0
        linear 1.0 alpha 1.0
    on hide:
        alpha 1.0
        linear 1.0 alpha 0.0

screen innerTestScreen(viewId):

    side "c r":
        viewport id viewId:
            has vbox
            for i in xrange(1,30):
                text "hi {0}".format(i)

        vbar:
            value YScrollValue(viewId)

screen testScreen:
    $ print "inventory1 = {0}".format(inventory1)
    $ print "inventory2 = {0}\n".format(inventory2)
    textbutton "restart Interaction":
        xalign 1.0
        yalign 1.0

        action Return()

    frame:
        area (0,0, 200, 200)
        at testTransform
        use innerTestScreen("viewportA")

    frame:
        area (250, 0, 450, 200)
        at testTransform
        use innerTestScreen("viewportB")

    draggroup:
        drag:
            drag_name "Inv1"
            child Frame("dark-window-normal.png", 10, 10)
            draggable False
            xpos 20 ypos 300 xsize 200 ysize 300

        drag:
            drag_name "Inv2"
            child Frame("dark-window-normal.png", 10, 10)
            draggable False
            xpos 350 ypos 300 xsize 200 ysize 300

        $ startX, startY = (25, 305)
        for num,i in enumerate(inventory1):
            $ xLocation = startX + (num % 2) * (54 + 60)
            $ yLocation = startY + (num // 2) * 54
            drag id "{0}-{1}".format(num,i):
                child Fixed(Image("book.png"), Text("Book {0}".format(i), yalign=0.5, xpos= 1.0, xoffset=4, xsize=40, xanchor=0.0, size=12), xsize=50, ysize=50)
                droppable False
                dragged giveItem(inventory1, i, xLocation, yLocation)
                xpos xLocation ypos yLocation

        $ startX, startY = (355, 305)
        for num,i in enumerate(inventory2):
            $ xLocation = startX + (num % 2) * (54 + 60)
            $ yLocation = startY + (num // 2) * 54
            drag id "{0}-{1}".format(num,i):
                child Fixed(Image("book.png"), Text("Book {0}".format(i), yalign=0.5, xpos= 1.0, xoffset=4, xsize=40, xanchor=0.0, size=12), xsize=50, ysize=50)
                droppable False
                dragged giveItem(inventory2, i, xLocation, yLocation)
                xpos xLocation ypos yLocation







# The game starts here.
label start:

    python:
        inventory1 = ["a", "c", "d", "m", "fark"]
        inventory2 = ["b"]


    e "You've created a new Ren'Py game."

    call screen testScreen

    e "Once you add a story, pictures, and music, you can release it to the world!"

    return

Attachments
Test.zip
(609.35 KiB) Downloaded 61 times
Fading Hearts is RELEASED
http://www.sakurariver.ca

User avatar
orz
Regular
Posts: 126
Joined: Tue Apr 21, 2015 10:19 pm
Contact:

Re: Help with Drag and Drop Inventory System

#2 Post by orz »

I'm a bit baffled it's even half working.

Here's an immediate problem I noticed:

Code: Select all

dragged giveItem(inventory2, i, xLocation, yLocation)
This probably isn't doing what you think it's doing. Instead of firing off that specific code when it's dragged... when the screen is run, that code is being evaluated immediately. It's just due to how Renpy is.

So, what it's expecting is a location of a function, not a function call itself. That is, it's wanting giveItem, not giveItem(inventory2, [...]) etc etc. This is, of course, a slight problem because you want to pass along more parameters than the two it automatically does (which is the list of drags being dragged, and whatever it might be dropped upon).

So, what you can do is have something evaluate that'll just return a function's location, or anonymously bind it with a lambda. For option A, just use renpy.curry like such:

Code: Select all

init python:
    curried_giveItem = renpy.curry(giveItem)

### and wherever your drag is...
drag:
    dragged curried_giveItem(inventory2, i, xLocation, yLocation) ## so this, when it evaluates, will give back the giveItem function,
## which is *then* used by dragged
Or with lambda, which I'm still not that great at using...
I tried typing up an example but I realized I'm still not that sure how to use it! Oh well.

Counter Arts
Miko-Class Veteran
Posts: 649
Joined: Fri Dec 16, 2005 5:21 pm
Completed: Fading Hearts, Infinite Game Works
Projects: Don't Save the World
Organization: Sakura River
Location: Canada
Contact:

Re: Help with Drag and Drop Inventory System

#3 Post by Counter Arts »

@orz

About that... I was just abusing closures which kinda does the same thing. In the outer function you define the inner function. Then you return the inner function which is a callable.

Unless I am mistaken... maybe I should use the renpy.curry or the python functools.
Fading Hearts is RELEASED
http://www.sakurariver.ca

User avatar
orz
Regular
Posts: 126
Joined: Tue Apr 21, 2015 10:19 pm
Contact:

Re: Help with Drag and Drop Inventory System

#4 Post by orz »

About that... I was just abusing closures which kinda does the same thing.
Aha, so you were. I missed that in my first read-through.

Well then. I'll have to run it myself (which involves editing out the graphics).

User avatar
orz
Regular
Posts: 126
Joined: Tue Apr 21, 2015 10:19 pm
Contact:

Re: Help with Drag and Drop Inventory System

#5 Post by orz »

I pared it down and ran through it a number of times (using 'watch inventory1' and 'watch inventory2' in console was most useful), and my best guess, and this is a guess, is that because you're building the positions from a list going to the same positions, when the interaction is restarted, Renpy is erroneously believing that the wrong displayable is the one you were using and is attempting to reuse it there. This has to do with Displayable Reuse, which is under Screen Optimization in the documentation.

For example, if you were dragging the drag associated with the third index over to the other inventory, it gets removed from the third index and added to the other list just fine. Then Renpy, when it redraws the screen comes to the "new" third index which has the same (starting) position as the "old" third index, it thinks it's the same third index and places it right back under your cursor.

How to fix it? I'm not sure, off hand.

Counter Arts
Miko-Class Veteran
Posts: 649
Joined: Fri Dec 16, 2005 5:21 pm
Completed: Fading Hearts, Infinite Game Works
Projects: Don't Save the World
Organization: Sakura River
Location: Canada
Contact:

Re: Help with Drag and Drop Inventory System

#6 Post by Counter Arts »

@orz

You are not going to like the fix. It's based off an old trick to break ties for sorting algorithms that required no ties.

You ready?

Code: Select all

xoffset (renpy.random.random() * 0.0001)
Fading Hearts is RELEASED
http://www.sakurariver.ca

User avatar
orz
Regular
Posts: 126
Joined: Tue Apr 21, 2015 10:19 pm
Contact:

Re: Help with Drag and Drop Inventory System

#7 Post by orz »

On the contrary, I like it.

I have a thing for coding abominations.

With that said, isn't there a very, very, very small chance you could get the same random() two restarted interactions in a row and still get that error? At that point, wouldn't it just be better to basically do:

Code: Select all

if time_to_offset:
    xoffset 0.00001
$ time_to_offset = not time_to_offset
?

Counter Arts
Miko-Class Veteran
Posts: 649
Joined: Fri Dec 16, 2005 5:21 pm
Completed: Fading Hearts, Infinite Game Works
Projects: Don't Save the World
Organization: Sakura River
Location: Canada
Contact:

Re: Help with Drag and Drop Inventory System

#8 Post by Counter Arts »

Would that work if there were more than two elements to tie-break?
Fading Hearts is RELEASED
http://www.sakurariver.ca

User avatar
orz
Regular
Posts: 126
Joined: Tue Apr 21, 2015 10:19 pm
Contact:

Re: Help with Drag and Drop Inventory System

#9 Post by orz »

Code: Select all

##outside the screen
init python:
    time_to_offset = False ## or True, doesn't matter

## then, in your screen...

for loops in whatever:
    drag:
        id etcstuff
        if time_to_offset:
             xoffset 0.0001

## do the same with your second for loop

## at the end,
$time_to_offset = not time_to_offset

## and if you want to look at it, add at the end of the screen:
text "[time_to_offset]" pos (0.5,0.2)

Counter Arts
Miko-Class Veteran
Posts: 649
Joined: Fri Dec 16, 2005 5:21 pm
Completed: Fading Hearts, Infinite Game Works
Projects: Don't Save the World
Organization: Sakura River
Location: Canada
Contact:

Re: Help with Drag and Drop Inventory System

#10 Post by Counter Arts »

So yeah... turns out the previous said solution can cause a memory and savefile leak. Here is another way.

Code: Select all

define e = Character('Eileen', color="#c8ffc8")

init python:

    import inspect

    def testDrop(target, sourceDrop):
        print "Target: {0}\n Source: {1}".format(target.drag_name, sourceDrop[0].drag_name)

    def returnDrop(xPos,yPos):

        def innerReturnDrop(sourceDrop, target ):
            if target == None:
                return None
            sourceDrop[0].snap(xPos, yPos, delay=10)
            print "Target: {0}\n Source: {1}".format(target.drag_name, sourceDrop[0].drag_name)
            renpy.restart_interaction
        return innerReturnDrop


    def resetIndexLocation(sourceWidget, newList, originalItem, newBoxX, newBoxY, columns=2, itemWidth=114, itemHeight=54, delay=0.5, padding=4):
        print "reset Index called"
        num = newList.index(originalItem)
        xLocation = newBoxX + (num % columns) * (itemWidth +padding) + padding
        yLocation = newBoxY + (num // columns) * (itemHeight +padding) + padding
        sourceWidget.style.xpos = xLocation
        sourceWidget.style.ypos = yLocation
        sourceWidget.snap(xLocation, yLocation, delay)

    def resetAllIndexLocations(currentDrags, allInventoryDefs):
        for i in currentDrags:
            for j in allInventoryDefs:
                if i.dataItem in j[0]:

                    resetIndexLocation(i, j[0], i.dataItem,j[1][0], j[1][1] )


    def giveItem(sourceList, theItem, originalX, originalY):

        def innerGiveItem(sourceDrop, target):
            global inventory1, inventory2
            if target == None:

                sourceDrop[0].snap(originalX, originalY)
            else:
                if target.drag_name == "Inv1":
                    if sourceList == inventory1:
                        sourceDrop[0].snap(originalX, originalY)
                    else:
                        sourceList.remove(theItem)
                        inventory1.append(theItem)
                        #resetIndexLocation(sourceDrop[0],inventory1, theItem, 25, 305)

                elif target.drag_name == "Inv2":
                    if sourceList == inventory2:
                        sourceDrop[0].snap(originalX, originalY)
                    else:
                        sourceList.remove(theItem)
                        inventory2.append(theItem)
                        #resetIndexLocation(sourceDrop[0],inventory2, theItem,355, 305)
                elif target.drag_name.startswith("item"):
                    inventoryId, dragItem = target.drag_name.split(" ")[1].split("-")
                    targetInventory = inventory1
                    if inventoryId == "2":
                        targetInventory = inventory2
                    theIndex = targetInventory.index(dragItem)
                    sourceList.remove(theItem)
                    targetInventory.insert(theIndex, theItem)
                    print "droped onto {0}".format(dragItem)
                else:
                    pass
                    #sourceDrop[0].snap(originalX, originalY)
            renpy.restart_interaction()
        return innerGiveItem

    def updateInventory(sources, target):
        if target == None:
            return
        print sources[0].dataItem

        resultInventory = None
        sourceDrag = sources[0]


        for i in sourceDrag.dataInventories:
            sourceInventory = None
            if sourceDrag.dataItem in i[0]:
                sourceInventory = i[0]
                sourceInventory.remove(sourceDrag.dataItem)
        for i in sourceDrag.dataInventories:
            if target.inventory is i[0]:
                target.inventory.append(sourceDrag.dataItem)

        for i in sourceDrag.allDrags:
            for j in sourceDrag.dataInventories:
                if i.dataItem in j[0]:

                    resetIndexLocation(i, j[0], i.dataItem,j[1][0], j[1][1] )
                    break

    def generateInventoryDrags(inventorysDefs, itemGen, itemDemensions):
        currentDrags = []
        currentDragGroup = DragGroup(id="inventoryDragGroup", xpos=0, ypos=0, xsize=1.0, ysize=1.0)

        for currentInventory, startLocation, width, height, border, background, itemCol in inventorysDefs:
            print "CI: {0}, startLocation: {1}, width: {2}, height: {3}, border: {4}, background: {5}, itemCol:{6}".format( currentInventory, startLocation, width, height, border, background, itemCol)
            inventoryDrag = Drag(background,xsize=width, ysize=height, xpos=startLocation[0], ypos=startLocation[1],  draggable=False, droppable=True)
            currentDragGroup.add(inventoryDrag)
            inventoryDrag.inventory = currentInventory
            for i in currentInventory:
                dragContents = itemGen(i)
                currentDrag = Drag(dragContents, dragged=updateInventory, drag_name=i, droppable=False)
                currentDrag.dataItem = i
                currentDrag.dataInventories = inventorysDefs
                currentDrag.allDrags = currentDrags
                currentDrags.append(currentDrag)
                currentDragGroup.add(currentDrag)
                resetIndexLocation(currentDrag, currentInventory, currentDrag.dataItem, startLocation[0], startLocation[1] )

        for i in currentDrags:
            for j in i.dataInventories:
                if i.dataItem in j[0]:
                    print "i:{0}, j:{1}".format(i,j)
                    resetIndexLocation(i, j[0], i.dataItem, j[1][0], j[1][1] )
                    break

        return currentDrags, currentDragGroup

    def generateIcon(i):
        return Fixed(Image("book.png"), Text("Book {0}".format(i), yalign=0.5, xpos= 1.0, xoffset=4, xsize=40, xanchor=0.0, size=12), xsize=50, ysize=50)







    inventoryStartLocations = ((25, 305),(355, 305))


label initInventoryDefs:

    python:
        inventoryA = ["ca", "mo"]
        inventoryB = ["kee","mars"]
        iDefs = [
            (inventoryA, (25, 305),300, 300, 4, Frame("dark-window-normal.png", 10, 10), 2),
            (inventoryB, (355, 305), 300, 300, 4,Frame("dark-window-normal.png", 10, 10), 2)
            ]

    return



transform testTransform:

    on show:
        alpha 0.0
        linear 1.0 alpha 1.0
    on hide:
        alpha 1.0
        linear 1.0 alpha 0.0

screen innerTestScreen(viewId):

    side "c r":
        viewport id viewId:
            has vbox
            for i in xrange(1,30):
                text "hi {0}".format(i)

        vbar:
            value YScrollValue(viewId)

screen testScreenTwo:
    #add DragGroup(Drag(Null()))
    default iDrags = generateInventoryDrags(iDefs, generateIcon, (100, 55) )
    add iDrags[1]
    $ resetAllIndexLocations(iDrags[0],iDefs)

screen testScreen:
    textbutton "restart Interaction":
        xalign 1.0
        yalign 1.0

        action Return()

    frame:
        area (0,0, 200, 200)
        at testTransform
        use innerTestScreen("viewportA")

    frame:
        area (250, 0, 450, 200)
        at testTransform
        use innerTestScreen("viewportB")


    draggroup id "testDragGroup":
        drag:
            drag_name "Inv1"
            child Frame("dark-window-normal.png", 10, 10)
            draggable False
            xpos 20 ypos 300 xsize 200 ysize 300

        drag:
            drag_name "Inv2"
            child Frame("dark-window-normal.png", 10, 10)
            draggable False
            xpos 350 ypos 300 xsize 200 ysize 300

        $ print "widget: {0}".format(renpy.get_widget("testScreen", "testDragGroup"))

        for i in masterInventory:
            $ startLocations = inventoryStartLocations[0]
            $ theIndex = 0
            $ targetInventory = inventory1
            $ inventoryName = "1"
            if i in inventory2:
                $ targetInventory = inventory2
                $ startLocations = inventoryStartLocations[1]
                $ theIndex = inventory2.index(i)
                $ inventoryName = "2"
            else:
                $ theIndex = inventory1.index(i)
            $ startX = startLocations[0]
            $ startY = startLocations[1]
            $ xLocation = startX + (theIndex % 2) * (54 + 60)
            $ yLocation = startY + (theIndex // 2) * 54
            drag id ("sa_{0}".format(i)):
                drag_name "item {0}-{1}".format(inventoryName,i)
                child Fixed(Image("book.png"), Text("Book {0}".format(i), yalign=0.5, xpos= 1.0, xoffset=4, xsize=40, xanchor=0.0, size=12), xsize=50, ysize=50)

                dragged giveItem(targetInventory, i, xLocation, yLocation)
                xpos xLocation ypos yLocation
                #xoffset (id(i) * 0.000000001)


        #
        # $ startX, startY = (25, 305)
        # for num,i in enumerate(inventory1):
        #     $ xLocation = startX + (num % 2) * (54 + 60)
        #     $ yLocation = startY + (num // 2) * 54
        #     drag id "{1}".format(num,id(i)):
        #         child Fixed(Image("book.png"), Text("Book {0}".format(i), yalign=0.5, xpos= 1.0, xoffset=4, xsize=40, xanchor=0.0, size=12), xsize=50, ysize=50)
        #         droppable False
        #         dragged giveItem(inventory1, i, xLocation, yLocation)
        #         xpos xLocation ypos yLocation
        #
        # $ startX, startY = (355, 305)
        # for num,i in enumerate(inventory2):
        #     $ xLocation = startX + (num % 2) * (54 + 60)
        #     $ yLocation = startY + (num // 2) * 54
        #     drag id "{1}".format(num,id(i)):
        #         child Fixed(Image("book.png"), Text("Book {0}".format(i), yalign=0.5, xpos= 1.0, xoffset=4, xsize=40, xanchor=0.0, size=12), xsize=50, ysize=50)
        #         droppable False
        #         dragged giveItem(inventory2, i, xLocation, yLocation)
        #         xpos xLocation ypos yLocation








# The game starts here.
label start:

    call initInventoryDefs

    e "You've created a new Ren'Py game."

    call screen testScreenTwo

    e "Once you add a story, pictures, and music, you can release it to the world!"

    return
Fading Hearts is RELEASED
http://www.sakurariver.ca

User avatar
orz
Regular
Posts: 126
Joined: Tue Apr 21, 2015 10:19 pm
Contact:

Re: Help with Drag and Drop Inventory System

#11 Post by orz »

I'll have to take a look at the code later, but...
So yeah... turns out the previous said solution can cause a memory and savefile leak. Here is another way.
...toggling like that can cause a leak? How?

Post Reply

Who is online

Users browsing this forum: Ahrefs [Bot]