Some topic

Here’s a recent list of things we might do, with the ones we’ve done removed and a couple added:

  • Buttons
    • Hard to see
    • Hard to keep finger on
    • Bad position
    • Put in corners
  • one more fragment
  • fragments stick to sun?
  • hyperspace
  • sun / gravity (has gravity, does not look good)
  • star field
  • Ships bigger?
  • Limit game size to stay off keys
  • Hit count / damage amount
  • settings console
  • Information display (bullets left, score, …)
  • Asteroids (frogger, etc)
  • Game recycle
  • Player ID

Let’s look at the buttons. They are all in a line at the edge of the screen. My fingers are not all in a line. I’m thinking we should have a setup that looks like this:

IMAGE FROM PAPER

We sketched this layout and it lets you rest your hands and fingers fairly comfortably. Now to figure out how to do it. We think it should be easy enough to test insideButton instead of insideCircle. Getting them created will be a bit interesting. Today, things look like this:

function Ship:init(shipNumber)
    self.name = string.format("Ship %d", shipNumber)
    self.missileLoad = Ship.MaxMissiles
    self.timeLastFired = -Ship.TicksBetweenMissiles
    self.shipNumber = shipNumber
    self.hitpoints = Ship.MaxDamage
    self.damageDealt = 5
    
    self.pos = self:initialPosition()
    self.heading = self:initialHeading()
    self.vel = vec2(0,0)
    self.controls = { 
        left   = self:controlButton(HEIGHT/5),
        right  = self:controlButton(2*HEIGHT/5),
        thrust = self:controlButton(3*HEIGHT/5),
        fire   = self:controlButton(4*HEIGHT/5)
    }
    U:addObject(self)
end

function Ship:controlButton(offset)
    if self.shipNumber == 1 then
        return Button(WIDTH-85, offset)
    else
        return Button(85, HEIGHT-offset)
    end
end

-- HAVE YOU PUSHED TO GITHUB TODAY?

Button = class()

function Button:init(x, y)
    self.name = string.format("Button %f %f", x, y)
    self.pos = vec2(x,y)
    self.radius = 50
    self.pressed = false
    self.capturedID = nil
    self.cannotCollide = true
    U:addTouched(self)
    U:addObject(self)
end

function Button:draw()
    pushStyle()
    pushMatrix()
    ellipseMode(RADIUS)
    translate(self.pos:unpack())
    fill(self:fillColor())
    ellipse(0,0, self.radius)
    popMatrix()
    popStyle()
end

function Button:fillColor()
    if (self.pressed) then
        return color(0, 255, 0, 255)
    else
        return color(255,0,0,26)
    end
end

function Button:touched(touch) 
    self.capturedID = self:getCapture(touch)
    self.pressed = self:getPressed(touch)
end

function Button:getCapture(touch) 
    if touch.state == BEGAN and self:insideCircle(touch) and not self.capturedID then
        return touch.id 
    elseif touch.state == ENDED and self.capturedID == touch.id then
        return nil
    else
        return self.capturedID
    end
end

function Button:getPressed(touch)
    if not self.capturedID then
        return false
    elseif self.capturedID ~= touch.id then
        return self.pressed
    else 
        return self:insideCircle(touch)
    end
end

function Button:insideCircle(touch)
    return self.pos:dist(vec2(touch.x, touch.y)) <= self.radius
end

function Button:move()
    -- don't
end

The calls to create the Button are providing a calculated center based on the ship number. The Button tests for being touched with insideCircle, which will need to be changed to deal with the rectangles we have in mind.

Our careful calculations yesterday tell us that dimensions of 170x100 or 100x170 are about what we need. Let’s see what we can do.

Tozier asks whether we should create a new RectangularButton as opposed to modifying Button. Upon discussion we decide to try making the existing buttons rectangular, then moving them more or less last. We’ll modify Button rather than build a new class or subclass. After all, the old version will be in Git.

First, let’s draw squares in place:

function Button:draw()
    pushStyle()
    pushMatrix()
    rectMode(RADIUS)
    translate(self.pos:unpack())
    fill(self:fillColor())
    rect(0,0, self.radius)
    popMatrix()
    popStyle()
end

IMAGE HERE

That was easy, we just changed ellipseMode to rectMode and ellipse to rect. (Note the sketches of the new buttons in the lower part of the picture. Those are just drawn with a couple of lines in Main. Pay no attention to that.)

Let’s try making them other than square:

IMAGE HERE

function Button:draw()
    pushStyle()
    pushMatrix()
    rectMode(RADIUS)
    translate(self.pos:unpack())
    fill(self:fillColor())
    rect(0,0, self.radius, self.radius*1.7)
    popMatrix()
    popStyle()
end

Now let’s make touching work: right now we’re still just checking for inside the radius. I think to do this we’ll save some member variables for width and height … which we’ll later change to make the boxes vertical or horizontal as our “design” asks.

We begin by intention:

function Button:insideMe(touch)
    local x = touch.x
    local y = touch.y
    local insideX = x >= myLeft and x <= myRight
    local insideY = y >= myBottom and y <= myTop
    return insideX and insideY
end

This isn’t complete of course but I wanted to share our thinking.

Tozier reports being easily confused by the lack of parentheses. I propose to wait and see how it turns out but it’s a valid point: we must cater to the easily confused insofar as possible.

function Button:insideMe(touch)
    local x = touch.x
    local y = touch.y
    local myLeft   = self.pos.x - self.width
    local myRight  = self.pos.x + self.width
    local myBottom = self.pos.y - self.height
    local myTop    = self.pos.y + self.height
    local insideX = x >= myLeft and x <= myRight
    local insideY = y >= myBottom and y <= myTop
    return insideX and insideY
end

-- with also:

function Button:init(x, y)
    self.name = string.format("Button %f %f", x, y)
    self.pos = vec2(x,y)
    self.width = 50
    self.height = self.width*1.7
    self.pressed = false
    self.capturedID = nil
    self.cannotCollide = true
    U:addTouched(self)
    U:addObject(self)
end

This works sooner than I had anticipated. Let’s push the code … and done.

It’s worth mentioning that this went better than we thought, and differently from our plan. We had talked about using the rectangle mode where you mention x1, y1, x2, y2, or xLowerLeft, yLowerLeft, width, height, and here we are using xCenter, yCenter, and the half-width and half-height. We slid into this implementation when we decided to “do the simplest thing that could possibly work” and draw the squares where the circles used to be. Very interesting. Is this better or worse than plan?

There may be a tiny bit more calculation in insideMe right now. If so, we can surely factor it out. Otherwise it feels quite nice.

We are left with putting the buttons where they belong, along the edges of the screen, and making the outer ones go in landscape and the inner ones portrait. (We confuse ourselves with those words, so I’ll leave them in in case we get in trouble later.)

Right now, the position of the buttons belongs to the Ship, so let’s let Ship deal with position and orientation of our new rectangles. Then we might want to revisit whether the objects are correctly partitioned.

-- Ship

    self.controls = { 
        left   = self:controlButton(HEIGHT/5),
        right  = self:controlButton(2*HEIGHT/5),
        thrust = self:controlButton(3*HEIGHT/5),
        fire   = self:controlButton(4*HEIGHT/5)
    }
    U:addObject(self)
end

function Ship:controlButton(offset)
    if self.shipNumber == 1 then
        return Button(WIDTH-85, offset)
    else
        return Button(85, HEIGHT-offset)
    end
end

This code is returning the position. Let’s put the half-width and half-height right in the constructor as well:

    self.controls = { 
        left   = self:controlButton(85, HEIGHT-50),
        right  = self:controlButton(50, HEIGHT- (100+85)),
        thrust = self:controlButton(50, 100+85),
        fire   = self:controlButton(85, 50)
    }
    U:addObject(self)
end

function Ship:controlButton(x,y)
    if self.shipNumber == 1 then
        return Button(x,y)
    else
        return Button(WIDTH-x,HEIGHT-y)
    end
end

We just ball-peened in these numbers, and we get buttons where they belong, although the two that have their long side parallel to the long side of the screen are not yet “turned”. We discuss whether to rotate them, or just to provide the other two values. I lean toward the latter because it’s simple and we figured out these coordinates manually: there’s no real math going on here.

    self.controls = { 
        left   = self:controlButton(85, HEIGHT-50,  85, 50),
        right  = self:controlButton(50, HEIGHT-185, 50, 85),
        thrust = self:controlButton(50, 185,        50, 85),
        fire   = self:controlButton(85, 50,         85, 50)
    }
    U:addObject(self)
end

function Ship:controlButton(x,y, w2, h2)
    if self.shipNumber == 1 then
        return Button(x,y, w2, h2)
    else
        return Button(WIDTH-x,HEIGHT-y, w2, h2)
    end
end

IMAGE HERE

I would like to see the edges of the buttons. Let’s see if we can make the line white.

function Button:draw()
    pushStyle()
    pushMatrix()
    rectMode(RADIUS)
    translate(self.pos:unpack())
    strokeWidth(1)
    stroke(255)
    fill(self:fillColor())
    rect(0,0, self.width, self.height)
    popMatrix()
    popStyle()
end

IMAGE HERE

Looks good, and the buttons work. The ships fly and shoot and generally cavort freely like space sheep. Time to push code … and done.

This has been a good day. Let’s stop before we screw this up. See you next time!