Hello, loves!

Let’s see if we can sort out the scaling issue with the inventory display. Tout le monde déteste l’IA.

The scaling issue is this: to get the inventory pictures to display in the inventory panel, we have to scale them up:

class DungeonView:
        ...
        with self.cameras.inventory_cam.activate():
            cam = self.cameras.inventory_cam
            w = cam.width
            h = cam.height
            x = w / 2
            y = h / 2
            cam.position = (x,y)
            rectangle = arcade.rect.XYWH(x,y,w, h)
            arcade.draw_rect_filled(rectangle, color=arcade.color.DARK_SLATE_GRAY)
            sprite_y = h - 64
            for item in self.dungeon.player_inventory:
                sprite = self.keyed_sprites.sprite_at(item)
                sprite.scale = params.scale_texture(sprite.textures[-1], item.scale * 64)
                sprite.position = x,sprite_y
                sprite.visible = True
                arcade.draw_sprite(sprite)
                sprite_y -= 64

Setting the scale works, in that the display looks right. But we shouldn’t have to do it, since the items display just fine when they’re in the dungeon. It came to me, when I was describing this problem yesterday, that because the map screen’s zoom factor is generally 64, the native size of the items is very small. So it “should” be the case that if we scale the inventory_cam at 64, we could stop changing the scale of the items displayed.

So we’ll try that. We’ll have to change the locations where we display them as well, of course. I expect to have to play with the numbers to get it right. My belief, from reading the documentation, is that cam.width, height, and position are all in world coordinates. I’m not sure how that affects things, but we’ll find out.

I’ll begin with an experiment, scaling the cam, setting its position to zero, and positioning the images all at zero. Like this:

        with self.cameras.inventory_cam.activate():
            cam = self.cameras.inventory_cam
            cam.zoom = 64
            w = cam.width
            h = cam.height
            x = w / 2
            y = h / 2
            cam.position = (x,y)
            cam.position = (0, 0)
            rectangle = arcade.rect.XYWH(x,y,w, h)
            arcade.draw_rect_filled(rectangle, color=arcade.color.DARK_SLATE_GRAY)
            sprite_y = h - 64
            for item in self.dungeon.player_inventory:
                sprite = self.keyed_sprites.sprite_at(item)
                # sprite.scale = params.scale_texture(sprite.textures[-1], item.scale * 64)
                # sprite.position = x,sprite_y
                sprite.position = (0,0)
                sprite.visible = True
                arcade.draw_sprite(sprite)
                sprite_y -= 64

I just set zoom, bashed in a couple of (0,0), removed the scaling. Let’s see what happens.

inventory panel only filled in top quarter, torch in proper scale at center

So that’s nearly good. Since the rectangle is still being draw with the old (x,y) that probably accounts for the offset of the allegedly dark slate grey bar. And the torch looks to be the right size and centered. Let’s try using the x and y from the little calculation at the top.

Same picture but the panel is now filled properly. Let’s think about positioning them vertically now. We were stepping by 64 before. Does that mean we can step by just one now?

        with self.cameras.inventory_cam.activate():
            cam = self.cameras.inventory_cam
            cam.zoom = 64
            w = cam.width
            h = cam.height
            x = w / 2
            y = h / 2
            cam.position = (x,y)
            rectangle = arcade.rect.XYWH(x,y,w, h)
            arcade.draw_rect_filled(rectangle, color=arcade.color.DARK_SLATE_GRAY)
            sprite_y = h - 1
            for item in self.dungeon.player_inventory:
                sprite = self.keyed_sprites.sprite_at(item)
                sprite.position = x,sprite_y
                sprite.visible = True
                arcade.draw_sprite(sprite)
                sprite_y -= 1

inventory panel properly filled, single column of items down center

Perfect! Don’t you just love it when a plan comes together?

Let’s commit this and then refactor a bit. Commit: rescale inventory cam, no need to rescale sprites.

This drawing method is way too long:

    def on_draw(self):
        self.clear()
        player_cell = self.dungeon.player_cell
        if player_cell:
            self.cameras.scroll_dungeon_cam(self.dungeon.player_cell)
        with self.cameras.dungeon_cam.activate():
            self.keyed_sprites.draw()
            self.draw_adventurer()
            self.draw_flood()
        with self.cameras.inventory_cam.activate():
            cam = self.cameras.inventory_cam
            cam.zoom = 64
            w = cam.width
            h = cam.height
            x = w / 2
            y = h / 2
            cam.position = (x,y)
            rectangle = arcade.rect.XYWH(x,y,w, h)
            arcade.draw_rect_filled(rectangle, color=arcade.color.DARK_SLATE_GRAY)
            sprite_y = h - 1
            for item in self.dungeon.player_inventory:
                sprite = self.keyed_sprites.sprite_at(item)
                sprite.position = x,sprite_y
                sprite.visible = True
                arcade.draw_sprite(sprite)
                sprite_y -= 1

Let’s extract a few methods here:

    def on_draw(self):
        self.clear()
        self.draw_dungeon_pane()
        self.draw_inventory_pane()

    def draw_dungeon_pane(self):
        player_cell = self.dungeon.player_cell
        if player_cell:
            self.cameras.scroll_dungeon_cam(self.dungeon.player_cell)
        with self.cameras.dungeon_cam.activate():
            self.keyed_sprites.draw()
            self.draw_adventurer()
            self.draw_flood()

    def draw_inventory_pane(self):
        with self.cameras.inventory_cam.activate():
            cam = self.cameras.inventory_cam
            cam.zoom = 64
            w = cam.width
            h = cam.height
            x = w / 2
            y = h / 2
            cam.position = (x, y)
            rectangle = arcade.rect.XYWH(x, y, w, h)
            arcade.draw_rect_filled(rectangle, color=arcade.color.DARK_SLATE_GRAY)
            sprite_y = h - 1
            for item in self.dungeon.player_inventory:
                sprite = self.keyed_sprites.sprite_at(item)
                sprite.position = x, sprite_y
                sprite.visible = True
                arcade.draw_sprite(sprite)
                sprite_y -= 1

Now that draw_inventory_pane is isolated, it’s easy to see that the first few lines are unchanging. Let’s move those to Cameras, where this camera is set up.

We’ll do it in small steps. First this:

    def draw_inventory_pane(self):
        with self.cameras.inventory_cam.activate() as cam:
            cam.zoom = 64
            w = cam.width
            h = cam.height
            x = w / 2
            y = h / 2
            cam.position = (x, y)

            x, _ = cam.position
            rectangle = arcade.rect.LRBT(0, cam.width, 0, cam.height)
            arcade.draw_rect_filled(rectangle, color=arcade.color.DARK_SLATE_GRAY)
            sprite_y = cam.height - 1
            for item in self.dungeon.player_inventory:
                sprite = self.keyed_sprites.sprite_at(item)
                sprite.position = x, sprite_y
                sprite.visible = True
                arcade.draw_sprite(sprite)
                sprite_y -= 1

Note that the with-as statement can provide the cam. I had assumed long ago that that would work but had not tried it until now. I changed the rectangle from XYWH to LRBT (left right bottom top) for convenience. My plan is to move the statements above the break over into Cameras. I believe that all the statement below the break only read from the cam when they need to, so we can move the code above over to Cameras now:

class Cameras:
    def __init__(self, view, max_x, max_y, zoom):
        self.view = view
        self.max_x = max_x
        self.max_y = max_y
        width = self.max_x*cell_size
        height = self.max_y*cell_size
        viewport = arcade.rect.LRBT(0,width,0,height)
        self.dungeon_cam = arcade.Camera2D(viewport=viewport)
        inventory_port = arcade.rect.LRBT(width, width+8*cell_size, 0, height)
        cam = self.inventory_cam = arcade.Camera2D(viewport=inventory_port)
        cam.zoom = 64
        w = cam.width
        h = cam.height
        x = w / 2
        y = h / 2
        cam.position = (x, y)

class DungeonView:
    def draw_inventory_pane(self):
        with self.cameras.inventory_cam.activate() as cam:
            x, _ = cam.position
            rectangle = arcade.rect.LRBT(0, cam.width, 0, cam.height)
            arcade.draw_rect_filled(rectangle, color=arcade.color.DARK_SLATE_GRAY)
            sprite_y = cam.height - 1
            for item in self.dungeon.player_inventory:
                sprite = self.keyed_sprites.sprite_at(item)
                sprite.position = x, sprite_y
                sprite.visible = True
                arcade.draw_sprite(sprite)
                sprite_y -= 1

That init is way too messy, but it’s the simplest move I could think of to make. Everything works. Let’s be committing: refactoring inventory camera logic.

Here’s a nice idea:

    def draw_inventory_pane(self):
        with self.cameras.inventory_cam.activate() as cam:
            x, _ = cam.position
            rectangle = arcade.rect.LRBT(0, cam.width, 0, cam.height)
            arcade.draw_rect_filled(rectangle, color=arcade.color.DARK_SLATE_GRAY)
            for dy, item in enumerate(self.dungeon.player_inventory):
                sprite = self.keyed_sprites.sprite_at(item)
                sprite.position = x, cam.height - dy - 1
                sprite.visible = True
                arcade.draw_sprite(sprite)

Using enumerate we can avoid the explicit y offset and its incrementing.

I think we’ll let this ride. Do a final commit and sum up.

Summary

In a series of small steps, we’ve moved from an experiment where we slammed down a rectangle and a sprite, to a rather well-factored additional camera handling the inventory display. The code is located in roughly the right objects, with the initialization of the inventory camera in Cameras, and its use in a separate method in DungeonView.

I think we should look at DungeonView and see whether we’d do well to separate out our Cameras using the Section object. I’m not sure about that: Section is a pretty weak object, seemingly providing little value beyond isolation of the code. That’s not zero value, but with small methods, I’m not sure it adds much. We’ll think about it.

We’ll find a lot more to do with the inventory, and we need to have Buzz accept the flower from Dot, not just admire it as he apparently does now, since he gives Dot the honeycomb but doesn’t take the flower.

Sometimes people just don’t want the flower. Sometimes you have to let them walk away. — Amanda Palmer

So. A decent morning’s improvement. See you next time!