Hello, loves! It’s Friday afternoon.
Hello, loves! It’s Saturday morning.
Hello, loves! It’s Sunday morning.
Hello, loves! It’s Monday morning.

Yes, that’s right, I’ve just thrown away three days of writing about the attempts I made to get the scrolling and zooming to work as I wanted. The basic idea is that as Dot gets close to the edge of the world, the display should always show where she is, but should not scroll the world edge way onto the screen. Instead there should be a small margin of black. Basically we want to show as much of the dungeon as we can, while keeping Dot as close to the center as we can.

I have written a few hundred lines of text about this, and have over 250 lines of test code, and frankly, the problem was kicking my tail. I’ve decided that none of that text is worth showing you, it’s just me bashing my head against the computer. We might look at some of the tests, and we might write some more.

Part of my problem was that Arcade’s Camera2D writeup and my brain were incompatible, and I kept finding information that might work but didn’t quite give me what I wanted, so I’d try to mash it to be what I wanted, and it would get so nasty that I’d throw it away, and find something else, rinse, repeat.

I finally came up with a way of thinking of the problem that I liked. At any given zoom factor, since we try to place Dot in the center of the screen, if we’re not near the edge, there will be an equal number of tiles to her left and to her right:

|___|___|___|_D_|___|___|___|

Now the leftmost and bottom-most tile is at (0,0), and the rightmost topmost is at (55,55) (in general (max_x-1, max_y-1)). So if we could answer a couple of questions, we could fix things up. Given that we have centered the display on Dot …

  1. If the left- or bottom-most coordinate is less than zero, we need to move the display away from zero.
  2. If the right- or top-most coordinate is greater than the max, we need to move Dot away from the max

After a lot of reading and testing of Camera2D methods, I found that the information needed is easy to get.

If you ask the camera for bottom_left, you get the x and y coordinates, in world space, of the lower left corner of the view. In our terms, that’s the cell in lower left. Similarly for top_right.

Now you would think, wouldn’t you, that given that Camera2D also has methods bottom and top, that those would give the same values, one for x and one for y. But no. Those values are not adjusted by camera position, for reasons. It took me longer than it should have to come to grips with that and stumble on the ones that work. Prior to that, I was using a more obscure method, unproject, and trying to write tests with it, and never got anything that I liked.

So yesterday, I just decided #@$! it, and coded this, of which I am not proud, but it does work:

    def scroll_dungeon_cam(self, cell):
        px, py = self.clamped_position(cell)
        self.dungeon_cam.position = (px, py)

    def clamped_position(self, cell):
        cam = self.dungeon_cam
        old_pos = cam.position
        px, py = cell.position
        cam.position = (px, py)

        dx, dy = cam.bottom_left
        if dx < 0:
            px -= dx + 1
        if dy < 0:
            py -= dy + 1

        x, y =  cam.top_right
        dx = x - self.max_x
        dy = y - self.max_y
        if dx > 0:
            px -= dx
        if dy > 0:
            py -= dy
        cam.position = old_pos
        return px, py

That’s not quite as nasty as it may look but it is close. We need to save and restore the old cam.position, because we the camera to calculate what it will do when and if someone else moves the center. Our job here is to compute it, not set it. We could change that, and maybe we will, but at the time I was doing this, I wanted to be safe, and something broke when I didn’t have that save in there.

Remember, as simple as this is, for some reason, I was struggling horribly. It was an awful sight, I assure you.

Anyway all we’re doing here is to get the bottom_left and if x or y have gone negative, move the proposed point of camera focus away (dx and dy are negative and are subtracted).

Then if top_right x or y has gone too high, we adjust the camera focus to the left.

You may be wondering about the +1 in the low case, and the lack of a +1 in the high case. There is at least one reason for that, which is that 0 is in range, while max_x and max_y are +1 outside the range: we can never step there.

I say at least one reason. I’m thinking in terms of integers here, which cell is displayed, but in fact at arbitrary zoom levels, the dx and dy contain fractions. I’ve tried various ceil and floor calls, and other tricks to get these two bits of code to look more similar than they do, and all I can say is that I have found a lot of things that don’t work, but this code does exactly what I had in mind, whether I can fully explain it or not.

Here are two pictures at arbitrary zoom to prove it:

map with dot in lower left corner

map with dot in upper right corner

Summary

The code above could be made more clear, and I’ll work on that, in an article or not as the fancy strikes me. Possibly in making it more clear, some of the ad-hocery will be cleared up. As it stands, it’s a house of cards and all I can do is hold my breath and back away very slowly.

But it works. There’s not much I can do with code that doesn’t work, but with a working base, there’s at least a chance. Quite likely some tests based on this code will let us figure out better what is going on.

If you read these articles, you know that I make no claims to perfection, but that I like to move the code toward perfection, by making it better. And you know that I want always to understand the code. And you know that I like to have decent tests, at least whenever it’s practical. There are over 170 tests for this program so don’t go wagging your finger at me for the ones I’ve skipped. I’m doin’ my best over here.

And sometimes my best isn’t very good. I really feel that I interfered with the canine in this situation. But, in the absence of clear thinking, brute force and refusal to give up resulted, finally, in a decent model of what to do and some code that does it, if not elegantly, at least effectively.

Soon, I can get back to the actual task, which is the inventory pane. Sheesh!

See you next time!