Dungeon 45
Not a political idea, but perhaps it should be. But I digress. Let’s do on-screen controls.
I’d rather do almost anything else, but I think today we should put some on-screen controls and maybe even some information into the system.
I think we’ll just do some arrows, up down left right, you know how it goes. And ever so fancy, maybe we’ll wind up with a Button class. In fact, I’m so sure we want one, I think I’ll start that way.
There will be some cutting, trying, and feeling of the way here. I’ll try to look as silly as possible to help keep you cheered up.
Default Button class:
Button = class()
function Button:init()
end
function Button:draw()
end
function Button:touched(touch)
end
We’ll need a rectangle for the button, and a sprite, and detection of touch. I suspect we want the button to be a repeater, hold down for continued movement. That will be semi-interesting.
I see no point to TDDing this, let’s create some in Main and see what we get. Well, maybe create in GameRunner, it sets everything else up. See, learning already.
function GameRunner:createLevel(count)
self:createRandomRooms(count)
self:connectRooms()
self:convertEdgesToWalls()
local r1 = self.rooms[1]
local rcx,rcy = r1:center()
local tile = self:getTile(vec2(rcx,rcy))
self.player = Player(tile,self)
self.monsters = self:createThings(Monster,5)
for i,monster in ipairs(self.monsters) do
monster:startAllTimers()
end
self.keys = self:createThings(Key,10)
self:createThings(Chest,10)
self.buttons = {}
table.insert(self.buttons, Button("left",100,100, 64,64, asset.builtin.UI.Blue_Slider_Left))
end
The sliders are all slightly different sizes. That may become interesting. Anyway, I’m going to try a 64x64 rectangle. Now I’ll need to call draw on them, in unscaled mode, and of course fillin the blanks in the Button class.
function GameRunner:draw(tiny)
fill(0)
stroke(255)
strokeWidth(1)
for i,row in ipairs(self.tiles) do
for j,tile in ipairs(row) do
tile:draw(tiny)
end
end
self.player:draw(tiny)
self:drawButtons()
end
function GameRunner:drawButtons()
for i,b in ipairs(self.buttons) do
b:draw()
end
end
Now in Button …
function Button:init(name, x,y,w,h, img)
self.name = name
self.x = x
self.y = y
self.w = w
self.h = h
self.img = img
end
function Button:draw()
pushMatrix()
pushStyle()
translate(self.x,self.y)
sprite(self.img, 0,0, self.w, self.h)
popStyle()
popMatrix()
end
I am hopeful that this will draw an arrow somewhere in view. Nothing shows up at all in the zoomed view, which is where I wanted to see it. And in the zoomed out view, it’s visible but tiny:
You can just about see it down there on the lower left.
The issue is that GameRunner doesn’t do the scaling. That’s still in Main:
function draw()
pushMatrix()
if CodeaUnit then showCodeaUnitTests() end
if DisplayToggle then
showKeyboard()
local center = Runner.player:graphicCorner()
focus(center, 1)
else
scale(0.25)
end
Runner:draw(false)
popMatrix()
drawTinyMap()
end
But it should still show up somewhere in the expanded (full scale) mode Ah. it’s the focus
function, which has already translated the screen for us:
function focus(center, zoom)
local LOWX,LOWY = maxScrollValues()
translate(clamp(LOWX, WIDTH/2-center.x, 0), clamp(LOWY, HEIGHT/2-center.y, 0))
end
Let’s finesse this for now by drawing the buttons outside the matrix adjustment in Main.
function draw()
pushMatrix()
if CodeaUnit then showCodeaUnitTests() end
if DisplayToggle then
showKeyboard()
local center = Runner.player:graphicCorner()
focus(center, 1)
else
scale(0.25)
end
Runner:draw(false)
popMatrix()
Runner:drawButtons()
drawTinyMap()
end
That’s close enough to let me set down all four of them in a nearly good layout. It will require adjustment.
self.buttons = {}
table.insert(self.buttons, Button("left",100,200, 64,64, asset.builtin.UI.Blue_Slider_Left))
table.insert(self.buttons, Button("up",200,300, 64,64, asset.builtin.UI.Blue_Slider_Up))
table.insert(self.buttons, Button("right",300,200, 64,64, asset.builtin.UI.Blue_Slider_Right))
table.insert(self.buttons, Button("down",200,100, 64,64, asset.builtin.UI.Blue_Slider_Down))
That’s nearly good. Let’s bring the up down ones inward a bit.
self.buttons = {}
table.insert(self.buttons, Button("left",100,200, 64,64, asset.builtin.UI.Blue_Slider_Left))
table.insert(self.buttons, Button("up",200,250, 64,64, asset.builtin.UI.Blue_Slider_Up))
table.insert(self.buttons, Button("right",300,200, 64,64, asset.builtin.UI.Blue_Slider_Right))
table.insert(self.buttons, Button("down",200,150, 64,64, asset.builtin.UI.Blue_Slider_Down))
That’ll do. Now let’s make them work. Right now, touch looks like this:
function touched(aTouch)
if aTouch.state == ENDED then
DisplayToggle = not DisplayToggle
TouchX = aTouch.pos.x
TouchY = aTouch.pos.y
end
end
We’ll need to disable the display toggle stuff for now, and pass the touch on into GameRunner, who’ll pass it on to the buttons.
function touched(aTouch)
Runner:touched(aTouch)
end
function GameRunner:touched(aTouch)
for i,b in ipairs(self.buttons) do
b:touched(aTouch, self.player)
end
end
The buttons will want to know the player, so they can tell her what to do. I’ve decided not to do auto-repeat, at least not now. Instead, I’ll trigger the buttons on touch beginning. I’ll try this:
function Button:touched(aTouch, player)
if not aTouch.state == BEGAN then return end
local x = aTouch.pos.x
local y = aTouch.pos.y
local ww = self.w/2
local hh = self.h/2
if x > self.x - ww and x < self.x + ww and
y > self.y - hh and y < self.y + hh then
player[self.name]()
end
end
This should check to be sure the touch state is BEGAN, then that the touch is inside this button’s rectangle. (I’m assuming and have set spriteMode(CENTER)
.) If it is, we look up our name on Player as a method and execute it. Our names are left, right, up, down, so I expect a crash looking for left when I touch the left button.
Button:33: attempt to call a nil value (field '?')
stack traceback:
Button:33: in method 'touched'
GameRunner:196: in method 'touched'
Main:80: in function 'touched'
Build those methods:
function Player:left()
self:keyPress("a")
end
function Player:right()
self:keyPress("d")
end
function Player:up()
self:keyPress("w")
end
function Player:down()
self:keyPress("s")
end
I just forward these to the keyPress
function, which knows how to move us already.
I left off the self parameter when I called the looked up function, so this is the button code:
function Button:touched(aTouch, player)
if not aTouch.state == BEGAN then return end
local x = aTouch.pos.x
local y = aTouch.pos.y
local ww = self.w/2
local hh = self.h/2
if x > self.x - ww and x < self.x + ww and
y > self.y - hh and y < self.y + hh then
player[self.name](player)
end
end
This nearly works. For some reason, I am seeing a movement when I touch the button and when I lift. But I’m returning if the touch state isn’t BEGAN. It is as if I get a BEGAN on lifting.
Changing that statement to this:
if aTouch.state ~= BEGAN then return end
Makes it work. Apparently not binds tighter than ==. Let’s look that up. Yes. not
is very high priority, tighter than anything other than ^
. So the code above is what we wanted. We can now drive the princess around with the arrow keys. Let’s remove the showKeyboard
and stick with the buttons. (I have to leave one showKeyboard
in somewhere, or my magic keyboard won’t work. The other iPad will just have to deal with that, and with the console starting out open. Nonetheless we can now play using the arrow keys.
Let’s commit: Player now controlled by Buttons.
That went more easily than I had feared. I’m not sure why I was concerned, I’ve certainly done on-screen buttons in the past. We could add a few more, like one that toggles to the full dungeon view, but for now I’m OK without. I do wonder what will happen when there is a room in the lower left and the player tries to walk under the buttons. The view will be blocked but it should certainly work.
That might be one good reason for a zoom out button, I could search for a layout with a room down there. Or … let’s force one.
That’s not bad, but let’s make the buttons a bit transparent.
That’ll do just fine. Commit: transparent buttons.
I think that’ll do for the morning. We’ll see what comes up tomorrow. See you then?