Inventory Tutorial

In this tutorial we will make a drag and drop inventory like this.






1 Setting up the class



    Let's begin by making an Inventory class

from ursina import * class Inventory(Entity): def __init__(self): super().__init__() if __name__ == '__main__': app = Ursina() inventory = Inventory() app.run()




2 Adding graphics



    However, if we run the code, we'll see that there's nothing visible.
    Let's parent it to the ui and set the model to 'quad', an included model.

from ursina import * class Inventory(Entity): def __init__(self): super().__init__( parent = camera.ui, model = 'quad' ) if __name__ == '__main__': app = Ursina() inventory = Inventory() app.run()

    If we look at it now, we see it's a white square in the middle of the screen.
    Let's set it to a nicer shape. We also want (0,0) to be in the upper left corner
    because it makes it easier to add items later. Let's also give it a texture and a color.
    If we have both, the color value will tint the texture.

    Now, this is nice and all, but wouldn't it be nice to show a grid as well?
    There are multiple way to do that, but in this case, we'll simply make the texture repeat
    by setting texture_scale to (5,6). That'll nicely fit the size of our inventory.

from ursina import * class Inventory(Entity): def __init__(self): super().__init__( parent = camera.ui, model = 'quad', scale = (.5, .8), origin = (-.5, .5), position = (-.3,.4), texture = 'white_cube', texture_scale = (5,8), color = color.dark_gray ) if __name__ == '__main__': app = Ursina() inventory = Inventory() app.run()




3 Adding placeholder items



    Let's try to add some items to the inventory.

from ursina import * class Inventory(Entity): def __init__(self): super().__init__( parent = camera.ui, model = 'quad', scale = (.5, .8), origin = (-.5, .5), position = (-.3,.4), texture = 'white_cube', texture_scale = (5,8), color = color.dark_gray ) if __name__ == '__main__': app = Ursina() inventory = Inventory() item = Button(parent=inventory, color=color.red, position=(0,0)) item = Button(parent=inventory, color=color.green, position=(2,0)) app.run()

    Well, that didn't go as planned. The items cover the entire inventory and
    the second item is way off to the left.
    Let's fix this by making another object to put the items under.
    Scale the object to the size of an item.

    They also don't fit the grid. Fix that by setting origin to the upper left
    corner, (-.5,.5).

from ursina import * class Inventory(Entity): def __init__(self): super().__init__( parent = camera.ui, model = 'quad', scale = (.5, .8), origin = (-.5, .5), position = (-.3,.4), texture = 'white_cube', texture_scale = (5,8), color = color.dark_gray ) self.item_parent = Entity(parent=self, scale=(1/5,1/8)) if __name__ == '__main__': app = Ursina() inventory = Inventory() item = Button(parent=inventory.item_parent, origin=(-.5,.5), color=color.red, position=(0,0)) item = Button(parent=inventory.item_parent, origin=(-.5,.5), color=color.green, position=(2,0)) app.run()




4 Adding items



    We've added some items manually to make sure they get the right scale and position,
    but we should make an append() function so it's easy to add items.

    Let's start by making a function called 'append()' and make it spawn an item
    when we send it a string. We'll also set the button's text to the string we
    receive so we can differentiate them.
    Let's give them a random color too, why not.

    Lastly, let's call inventory.append('test item') a couple of times to make sure it works.


from ursina import * class Inventory(Entity): def __init__(self): super().__init__( parent = camera.ui, model = 'quad', scale = (.5, .8), origin = (-.5, .5), position = (-.3,.4), texture = 'white_cube', texture_scale = (5,8), color = color.dark_gray ) self.item_parent = Entity(parent=self, scale=(1/5,1/8)) def append(self, item): Button( parent = inventory.item_parent, model = 'quad', origin = (-.5,.5), color = color.random_color(), z = -.1 ) if __name__ == '__main__': app = Ursina() inventory = Inventory() inventory.append('test item') inventory.append('test item') app.run()




5 Find a free slot in the inventory



    The items gets added, but they overlap. We need to find the first open slot in the inventory
    and place the item there. We can to this by checking each grid position and see if any
    if the items occupy that position already.

from ursina import * class Inventory(Entity): def __init__(self): super().__init__( parent = camera.ui, model = 'quad', scale = (.5, .8), origin = (-.5, .5), position = (-.3,.4), texture = 'white_cube', texture_scale = (5,8), color = color.dark_gray ) self.item_parent = Entity(parent=self, scale=(1/5,1/8)) def find_free_spot(self): taken_spots = [(int(e.x), int(e.y)) for e in self.item_parent.children] for y in range(8): for x in range(5): if not (x,-y) in taken_spots: return (x,-y) def append(self, item): Button( parent = inventory.item_parent, model = 'quad', origin = (-.5,.5), color = color.random_color(), position = self.find_free_spot(), z = -.1 ) if __name__ == '__main__': app = Ursina() inventory = Inventory() for i in range(7): inventory.append('test item') app.run()




6 Add random item button



    Make a button to add a random item to the inventory. This is not part of the inventory itself,
    but it's useful in order to test it.
    Assign the button's 'on_click' to a function, and it will call that function when we click it.
    button.on_click = inventory

    Let's make an tuple with those and make the button choose a random item from the tuple
    using random.choice(items)

if __name__ == '__main__': app = Ursina() inventory = Inventory() def add_item(): inventory.append(random.choice(('bag', 'bow_arrow', 'gem', 'orb', 'sword'))) for i in range(7): add_item() add_item_button = Button( scale = (.1,.1), x = -.5, color = color.lime.tint(-.25), text = '+', tooltip = Tooltip('Add random item'), on_click = add_item ) app.run()




7 Adding textures and hover text



    To add textures, just set texture=item and it will try to find a
    texture with that name. It searches first in the project's assets and then
    in the assets included with ursina. I've included some icons for the purpose
    if this tutorial, but feel free to add your own.

    Let's also remove the random color and instead show the item's name when we hover it with the mouse.
    If a button has 'tooltip' set to something, it will show it when we hover the Button.

def append(self, item): icon = Button( parent = inventory.item_parent, model = 'quad', texture = item, color = color.white, origin = (-.5,.5), position = self.find_free_spot(), z = -.1, ) name = item.replace('_', ' ').title() icon.tooltip = Tooltip(name) icon.tooltip.background.color = color.hsv(0,0,0,.8)




8 Drag n' drop



    We can't move the items! That's not fun.
    However, that's easy to change. Just spawn a Draggable instead of Button.
    Draggable inherits from Button, so the tooltips will still work!

def append(self, item): icon = Draggable( parent = inventory.item_parent, model = 'quad', texture = item, color = color.white, origin = (-.5,.5), position = self.find_free_spot(), z = -.1, ) name = item.replace('_', ' ').title() icon.tooltip = Tooltip(name) icon.tooltip.background.color = color.hsv(0,0,0,.8)




9 Snap to grid on_drop



    The items can be moved now, but they don't follow the grid.
    Let's round the position when we drop it.
    Draggable's will automatically call on_drag() and on_drop() if has them.

    round the position on drop

def append(self, item): icon = Draggable( parent = inventory.item_parent, model = 'quad', texture = item, color = color.white, origin = (-.5,.5), position = self.find_free_spot(), z = -.1, ) name = item.replace('_', ' ').title() icon.tooltip = Tooltip(name) icon.tooltip.background.color = color.hsv(0,0,0,.8) def drop(): icon.x = int(icon.x) icon.y = int(icon.y) icon.drop = drop




10 Swap items



    Add a drag function to remember the start position as org_pos.


def drag(): icon.org_pos = (icon.x, icon.y) def drop(): icon.x = int(icon.x) icon.y = int(icon.y) '''if the spot is taken, swap positions''' for c in self.children: if c == icon: continue if c.x == icon.x and c.y == icon.y: print('swap positions') c.position = icon.org_pos icon.drag = drag icon.drop = drop




11 Stay inside the inventory, please



    We shouldn't be able to drop the items outside of the inventory.

def drop(): icon.x = int(icon.x) icon.y = int(icon.y) '''if outside, return to original position''' if icon.x < 0 or icon.x >= 1 or icon.y > 0 or icon.y <= -1: icon.position = (icon.org_pos) return '''if the spot is taken, swap positions''' for c in self.children: if c == icon: continue if c.x == icon.x and c.y == icon.y: print('swap positions') c.position = icon.org_pos icon.drop = drop




12 Bugfix: Make the dragged items render on top



    All the items are at the same depth, so it hard to say how they will overlap.
    It feels natural that the item we're currently dragging stays on top,
    so we'll move it back a bit when we drag it and forward when we drop it.

def drag(): icon.org_pos = (icon.x, icon.y) icon.z -= .01 # ensure the dragged item overlaps the rest def drop(): icon.x = int(icon.x) icon.y = int(icon.y) icon.z += .01 '''if outside, return to original position''' if icon.x < 0 or icon.x >= 1 or icon.y > 0 or icon.y <= -1: icon.position = (icon.org_pos) return '''if the spot is taken, swap positions''' for c in self.children: if c == icon: continue if c.x == icon.x and c.y == icon.y: print('swap positions') c.position = icon.org_pos icon.drag = drag icon.drop = drop