I am really tired of this game. I think I understand why. Is there anything I can do about it?

I can feel myself wriggling on the hook. I want to be done with this project. There are interesting things to try, and problems that might be challenging, but that turn out to be fairly straightforward, because our design is really pretty decent.

We continue to learn our same old lessons, that we do better when we have good tests, and that we do better when our code is well-organized. But it’s not fun. And I think I know why:

We’re at a point in the development where, to make a real game, a fun game, we’d have to do masses of non-programming work. We’d need better art, and lots of it. We’d need to lay out dungeons with interesting things in them, things to do, things to discover. We’d probably need things to buy and sell, things to equip and use.

Most of these things–but not all–are mostly outside the realm of programming, and make up a lot of the necessary but tedious work that turns a game like this one into something worth playing. And most of it is work that I’m not qualified to do, and from which it would be hard to draw the kinds of lessons I’m trying to draw here.

That’s probably why I let the chrome squirrel of the Making App get my attention: new problems to solve. However, at least as I see it now, there aren’t many ideas of reasonable value for such a thing in the Codea world. Maybe something will come to mind.

Take the Doors, Please

It took a minute to consider, but identifying all the cells where doors might be appropriate was just a few lines of code. But how might we represent a door, now that we know where one might go. Take a look at a room or two:

room1

I can fairly quickly patch in a door image here, to demonstrate an issue:

local DoorSprite = AdjustedSprite(asset.documents.Dropbox.ddoor_closed, p512)

function Tile:drawLargeSprite(center)
    -- we have to draw something: we don't clear background. Maybe we should.
    local sp = self:getSprite(self:pos(), false)
    pushMatrix()
    pushStyle()
    textMode(CENTER)
    translate(center.x,center.y)
    if not self.currentlyVisible then tint(0) end
    if self:canBeDoor() then 
        sp = DoorSprite
        rotate(90)
    end
    sp:draw()
    popStyle()
    popMatrix()
end

I rotated the scene 90 degrees to give us this picture:

room3

What we can easily see here is that the doors need to be rotated depending on whether they are east-west or north-south doors. But do they also need to be rotated depending on where the player is?

room player right

room player left

That second picture might look better with the door facing away from the player, like this:

room player left right

I’d say that, ideally, doors should always point their round part away from the player. That’s certainly possible: we have the ability to get the direction and distance from any tile to the player.

Will it be difficult? I doubt it very much. Will we learn something new? I doubt it very much.

Is it necessary to make a “better product”? Almost certainly yes. Necessary for my purpose here, and yours? I seriously doubt it.

Will I do it? Almost certainly yes. It’s somewhat interesting, and it will improve the look of the game. However …

The fundamental look of the game, we’re nearly stuck with. We could get a new tileset, perhaps, but we’re stuck in this 2D plus a little bit model.

I suppose an interesting challenge might be to convert the game to use 3d voxels and carve out a 3d dungeon space. Or maybe at least to go to an isometric kind of design:

isometric map

It would be “interesting” to try to convert this game to use a map of that kind. Looking at the big area in the foreground, it looks like we could probably map our rectangular tiles to the open area, and when there were things on the “tiles”, we have the ability to deal with that.

But as we look in the northwest direction … we see a rise in level, and that there are adjacent areas that are at different vertical levels. That would be a challenge for our current model … or would it? Does our model even care about the vertical dimension? Would it be hard to retrofit, if it was important?

A Pivot is Always Interesting

I tried a pivot in the Asteroids game, where suddenly the powers that be demanded more modern graphics. It was scary for a few minutes, and then we just did it.

Suppose our product people were to look at the game we have here and decide, well, we can’t make it interesting enough in this 2d-like style, and we need you to make it isometric. Get new tiles and get to it!

Is there a matrix transformation that would change our existing 2d coordinates (tile center) into an isometric view? I don’t recall it offhand, but I’d bet a lot that there is one. If there is, our 2d logic would result in an isometric view.

There’s always the chance, of course, that the current architecture just can’t support anything that weird. I’m pretty sure that if the product folks were to ask us to convert this game into a three-dimensional flying space game like a cross between Doom and Space Invaders and Angry Birds, I’d be saying we’d have to start over.

Well, What?

Yes, well. I think I’ll do the doors facing away from the player, just to prove that I can. And I think we’ll find that it’s weird when you walk through them.

Let’s see. We need to know whether the door is at a North-South entrance or East-West. That’ll require more information in the tile. Then once we have the door aligned vertically or horizontally, we can rotate it 180 if the player is on this side or that.

Can we know the path direction?

Well, we could:

function Dungeon:horizontalCorridor(fromX, toX, y)
    local prev,curr
    local increment = 1
    if fromX > toX then
        increment = -1
    end
    prev = self:privateGetTileXY(fromX,y)
    for x = fromX,toX,increment do
        curr = self:privateGetTileXY(x,y)
        curr = self:setHallAndDoors(prev,curr)
        prev = curr
    end
end

function Dungeon:setHallAndDoors(prevTile,currTile)
    local pos = currTile:pos()
    if not currTile:isRoom() then
        currTile = Tile:hall(pos.x,pos.y,currTile.runner)
        self:defineTile(currTile)
        if prevTile:isRoom() then
            currTile:setCanBeDoor()
        end
    else -- curr is room, may need to set prev
        if prevTile:isHall() then
            prevTile:setCanBeDoor()
        end
    end
    return currTile
end

function Dungeon:verticalCorridor(fromY, toY, x)
    local prev,curr
    local increment = 1
    if fromY>toY then
        increment = -1
    end
    prev = self:privateGetTileXY(x,fromY)
    for y = fromY, toY, increment do
        curr = self:privateGetTileXY(x,y)
        curr = self:setHallAndDoors(prev,curr)
        prev = curr
    end
end

We can pass the horizontal or vertical info into our set method:

function Dungeon:horizontalCorridor(fromX, toX, y)
    local prev,curr
    local increment = 1
    if fromX > toX then
        increment = -1
    end
    prev = self:privateGetTileXY(fromX,y)
    for x = fromX,toX,increment do
        curr = self:privateGetTileXY(x,y)
        curr = self:setHallAndDoors(prev,curr, "EW")
        prev = curr
    end
end

function Dungeon:setHallAndDoors(prevTile,currTile, dir)
    local pos = currTile:pos()
    if not currTile:isRoom() then
        currTile = Tile:hall(pos.x,pos.y,currTile.runner)
        self:defineTile(currTile)
        if prevTile:isRoom() then
            currTile:setCanBeDoor(dir)
        end
    else -- curr is room, may need to set prev
        if prevTile:isHall() then
            prevTile:setCanBeDoor(dir)
        end
    end
    return currTile
end

function Dungeon:verticalCorridor(fromY, toY, x)
    local prev,curr
    local increment = 1
    if fromY>toY then
        increment = -1
    end
    prev = self:privateGetTileXY(x,fromY)
    for y = fromY, toY, increment do
        curr = self:privateGetTileXY(x,y)
        curr = self:setHallAndDoors(prev,curr, "NS")
        prev = curr
    end
end

Then set it into the tile:

function Tile:setCanBeDoor(direction)
    self.door = true
    self.doorDirection = direction
end

Then it’s a simple matter to display it rotated to a canonical form:

function Tile:drawLargeSprite(center)
    -- we have to draw something: we don't clear background. Maybe we should.
    local sp = self:getSprite(self:pos(), false)
    pushMatrix()
    pushStyle()
    textMode(CENTER)
    translate(center.x,center.y)
    if not self.currentlyVisible then tint(0) end
    if self:canBeDoor() then 
        sp = DoorSprite
        if self.doorDirection == "EW" then
            rotate(90)
        end
    end
    sp:draw()
    popStyle()
    popMatrix()
end

And it looks like this:

rd1

rd2

rd3

Hey! That’s not half bad. I’m not sure we’ll want the doors to flip away from the player after all.

I’m going to declare this to be a feature. Commit: All door cells now display a door. Player can just walk through.

Let’s sum up.

Summary

See what I mean? No matter what we think of to do, a dozen or twenty lines later and there it is. Our objects, imperfect though they may be, have their responsibilities pretty sensibly divided and changes go just about where you think they might.

Now, of course there’s more to do with doors. We probably don’t really want doors at every room entrance and exit. We probably do want doors to require keys. We certainly need to improve the graphics a bit: did you notice that the doors clip vertically?

We want doors to be able to be closed or open. We might want to see a little animation when they open. We might want our cool levers to control certain doors. Maybe each lever setting in the main room opens some doors and closes others. That could be fun.

Doors are presently a graphical trick. Probably we really want a door to be a contents item: that’s how most things we interact with are done.

But all that is quite doable.

Not deadly boring: at least they look pretty cool. So I’m happier than when I started. But still … this game is getting boring.

What would you prefer to see me do next? Features here? Some new thing? Start writing about society or Scrum? Let me know.

See you next time!


D2.zip