Today I decided to experiment with improved controls. The experiments, um, succeeded oddly.

TL:DR

I tried three ideas for better turning controls:

  1. Drag to left or right of where you first put your finger down to signify left or right.
  2. Touch above/left or right/below the main diagonal to signify left or right.
  3. Touch left or right of a vertical dead zone to signify left or right. (Sort of a rectangular button, if you think about it.)

None of these ideas was better than what we have. The experiments “succeeded” in telling me none of them were very good ideas.

The main lesson is that bigger left-right buttons would be better. A secondary lesson is that we might want to add some parameters for further experiments.

I recommend that you stop reading here unless you truly have nothing useful or fun to do.

The Long Story

The game has become much more fun with the added sounds. I would not have guessed they’d make that much difference, but they do. I hope some of you have been following closely enough to enjoy the thrums and bangs.

But the game is hard to control, especially if the iPad is in your lap. And it’s just about impossible in typing mode, but that’s not surprising. So we need to do something about improving control, but it needs to be something possible.

I had thought that it would be really nice if you could move your left hand fingers in a circle, with the ship turning whichever way you were going. It can’t just track your finger: I think sticking to the original game’s constant rotation speed is more consistent with my objectives.

And recognizing which way you’re circling could be tricky, since you get a zillion touch events while your finger hasn’t moved a bit … and even while it isn’t moving at all. So parsing the shape might be a bit tricky … and I just don’t want to try to solve that geometry problem.

A suggestion from @West on the Codea forum was to treat the point where the touch started as a fixed point and measure from there. I think @West was talking about measuring the arc around that point, but I’m thinking we can do something simple, yet better.

Here’s what I’m thinking:

Touches in the left hand area of the screen will be about rotating the ship. There will be no fixed buttons to press. Instead, when there is no touch, the ship will not rotate. When a touch occurs in the area, the starting location will be remembered. If the finger is to the left of the starting location, the ship turns left (counter-clockwise) and if the finger is to the right, it turns right.

It seems to me that turning will become fairly natural, you just touch and drag even a bit in either direction, and the ship turns. We could imagine making it turn faster the further you pull away from the starting point but, again, the original game had constant rotation speed, so we’ll stick with that for now.

@Dave1707 had a related idea as well, with an example that turned the ship left as long as you were dragging left and right as long as you were dragging right. I’ve run that code and it didn’t seem quite the thing for Asteroids but it may have contributed to my present idea. As usual, any mistakes are Chet’s.

Reviewing Buttons

I’ll begin by reviewing how touches work now. In Main:

function touched(touch)
    if U.attractMode and touch.state == ENDED then U:startGame() end
    if touch.state == ENDED or touch.state == CANCELLED then
        Touches[touch.id] = nil
    else
        Touches[touch.id] = touch
    end
end

This code stores all the touches, keyed by touch id, which is constant the whole time the touch is in effect. The touch disappears from the Touches table on ENDED or CANCELLED.

Touch states are BEGAN, ENDED, CHANGED, or CANCELLED. So a touch in the table will have state BEGAN or CHANGED. That seems like just what we need for our new turning mechanism.

We also know how touches communicate with the game proper: The table U.button contains keys, left, right, fire, go, which are true if the ship should turn, go, or fire. We’ll have to change how that translation is made, but that should be all that we’ll need to do. Now let’s look at the Button class:

local Buttons = {}

function createButtons()
    local dx=50
    local dy=200
    table.insert(Buttons, {x=dx, y=dy, name="left"})
    table.insert(Buttons, {x=dy, y=dx, name="right"})
    table.insert(Buttons, {x=WIDTH-dx, y=dy, name="fire"})
    table.insert(Buttons, {x=WIDTH-dy, y=dx, name = "go"})
end

function checkButtons()
    U.button.left = false
    U.button.right = false
    U.button.go = false
    U.button.fire = false
    for id,touch in pairs(Touches) do
        for i,button in ipairs(Buttons) do
            if touch.pos:dist(vec2(button.x,button.y)) < 50 then
                U.button[button.name]=true
            end
        end
    end
end

There’s also a button drawing function, but we can worry about that later.

So presently, the checkButtons code looks to see if the touch is inside the control circle, and if so just sets the appropriate U.button flag. We’ll need to subvert, um, improve how that works.

I’ll begin by removing the two left-right buttons from the list, leaving only the go and fire ones. (I have changes in mind for those as well, but one thing at a time. Then, separately from the button-checking loop, I’ll record some info about turning.

It seems to me that I can safely store my info back into U.button, or I can have some more local variables here in the Button tab. I guess I’ll go with the latter for now.

Here’s the first edit. I’m going to comment out the left and right buttons, just because I’m old, and then call a new function before the loop to deal with left-right:

function createButtons()
    local dx=50
    local dy=200
    --table.insert(Buttons, {x=dx, y=dy, name="left"})
    --table.insert(Buttons, {x=dy, y=dx, name="right"})
    table.insert(Buttons, {x=WIDTH-dx, y=dy, name="fire"})
    table.insert(Buttons, {x=WIDTH-dy, y=dx, name = "go"})
end

function checkButtons()
    U.button.left = false
    U.button.right = false
    U.button.go = false
    U.button.fire = false
    checkLeftRight()
    for id,touch in pairs(Touches) do
        for i,button in ipairs(Buttons) do
            if touch.pos:dist(vec2(button.x,button.y)) < 50 then
                U.button[button.name]=true
            end
        end
    end
end

There we are, all done except for the small detail of writing checkLeftRight. What should it do? Well …

It should check each touch to see if it is in the region of interest, the lower left corner of the screen. If not, move on. (This much thinking is telling me that since I have a loop over touches already, the checkLeftRight can be inside the existing loop. We’ll deal with that in a moment. We’re thinking here.)

If the touch is of interest and the state is BEGAN, we’ll save the coordinates in a convenient local variable.

If the state is CHANGED, we’ll compare the coordinate with our saved one and if it is to the left, we’ll turn left, and if right, turn right.

I see only one issue: what if we do not get the BEGAN state? Could it happen that two events will come into touched for the same id, the second one being CHANGED, so that we never see BEGAN and therefore never set the turn location to the new starting value?

I suspect it could happen. We’ll have to find out. For now, let’s assume the best, expecting the worst.

local Buttons = {}

function createButtons()
    local dx=50
    local dy=200
    --table.insert(Buttons, {x=dx, y=dy, name="left"})
    --table.insert(Buttons, {x=dy, y=dx, name="right"})
    table.insert(Buttons, {x=WIDTH-dx, y=dy, name="fire"})
    table.insert(Buttons, {x=WIDTH-dy, y=dx, name = "go"})
end

function checkButtons()
    U.button.left = false
    U.button.right = false
    U.button.go = false
    U.button.fire = false
    for id,touch in pairs(Touches) do
        checkLeftRight(touch)
        for i,button in ipairs(Buttons) do
            if touch.pos:dist(vec2(button.x,button.y)) < 50 then
                U.button[button.name]=true
            end
        end
    end
end

local TurnStart

function checkLeftRight(touch)
    local x = touch.pos.x
    if x > 250 or touch.pos.y > 250 then return end
    if touch.state == BEGAN then
        TurnStart = x
    else
        if x < TurnStart then U.button.left = true
        elseif x > TurnStart then U.button.right = true
        end
    end
end

Full disclosure: I wrote else if above, not elseif. That confused the compiler and me for a bit.

This seems reasonable. I took a random guess at the overall limits of 250. I’m going to run this and see what happens.

My hopes are immediately dashed. I get an error at the comparison of x with TurnStart, the latter not initialized. That makes me think that I didn’t get the BEGAN. Bummer. What to do? Well, I see no way this code could fail (yeah, right) so let’s print the state out and see what is happening.

function checkLeftRight(touch)
    local x = touch.pos.x
    if x > 250 or touch.pos.y > 250 then return end
    if touch.state == BEGAN then
        print("BEGAN")
        TurnStart = x
    else
        print("not BEGAN ", touch.state)
        if x < TurnStart then 
            U.button.left = true
        elseif x > TurnStart then 
            U.button.right = true
        end
    end
end

Wouldn’t you just know … it never happened again. The turning works just as I’d planned. You can even slide your finger back in the other direction and when you pass where you put it down, the ship turns the other way, just as you’d expect. It’s a bit fiddly right around zero, so probably we need to leave a dead zone in the middle.

Be that as it may, I think we now know that it’s possible to get a CHANGED with no BEGAN, and we’d better deal with it somehow.

Long-term, we could improve what’s stored in our table of touches. For now, I see a couple of options. One is just to ensure that TurnStart is never nil. We could initialize it to some reasonable value and trust that the user’s finger will figure it out. We could pass all touches down to Button. We could check the time since last seeing a touch in this area and clear TurnStart so an old value won’t be used, combined with a default to initialize to the first location we see coming in.

I even looked up the Apple information on touch, and learned something interesting. Touch recognizes “tapCount”, in case you double- or triple-tap the screen. Apple isn’t providing an equivalent to Codea’s BEGAN/ENDED information, so that’s happening somewhere inside Codea. Those two facts together make me wonder whether Codea might sometimes not even see a BEGAN event.

It’s a puzzlement, at least for this scheme. But it mostly works, so let’s just ensure that TurnStart is never nil:

local TurnStart = 100

When our luck is in, which it almost always is, this will be overridden by the BEGAN event. When it’s not, the ship will probably start turning but the user should quickly recover.

Now let’s give ourselves a bit of a dead zone around TurnStart, maybe 20 pixels for a start:

function checkLeftRight(touch)
    local x = touch.pos.x
    if x > 250 or touch.pos.y > 250 then return end
    if touch.state == BEGAN then
        TurnStart = x
    else
        if x < TurnStart - 20 then 
            U.button.left = true
        elseif x > TurnStart + 20 then 
            U.button.right = true
        end
    end
end

This is definitely hackery, but we’re experimenting here. If the idea works we’ll clean it up and package it nicely. (Unless we don’t. These promises are easily forgotten in the press of time and other good ideas. Beware when you find yourself saying you’ll do something later.) Anyway to testing.

The dead zone is too large. I’ll try 10.

OK, this is nearly good. Touch and drag left a bit and hold and it continually turns left. Touch, drag, lift, and you get a small turn. Touch, drag, drag back and you get a small turn.

It’s still not as good as the original, which turned while you held down a discrete button and stopped as soon as you lifted. And it’s not as good as the original here … IF you could avoid missing the buttons.

I’m going to save this version, then check out the current one and try it again. That’s super easy with Working Copy, just commit this version under “touch and drag experiment”, then long-press last night’s final version and select Check Out. It also offers “Cherrypick”. I wonder what that is. Maybe later.

I tried running the latest version using the controls differently. I dragged my thumb back and forth between the left and right buttons, instead of using two fingers, one for each. And it works rather nicely. It’s a bit too each to slide out of the hot zones entirely, though with the thumb, your fingers behind the pad give you a pretty solid position. With the pad laid down, I think it’d be harder to do, as you’d probably want to use a finger rather than thumb.

But what if the buttons were something like this:

cornerbuttons

If your finger is on or near the diagonal, nothing happens. To the left of it, or above it, and you turn left. Right or below, turn right. This might work. I wonder how we can easily decide which side of the diagonal you’re on. Hmm. Vectors, rotations, LaPlace transforms …

Slope! The slope of the diagonal is 1, a larger slope is to the left/above, a smaller is right/below. But what about the dead zone? How do we handle that?

Yes, well. The distance from (x,y) to the diagonal is of course1 just

|x - y|/sqrt(2)

We don’t care about the square root of two, so we can ignore that and just check for distance larger than whatever dead zone works. So therefore … I’ll do something similar to what I did last time:

function checkButtons()
    U.button.left = false
    U.button.right = false
    U.button.go = false
    U.button.fire = false
    for id,touch in pairs(Touches) do
        checkLeftRight(touch)
        for i,button in ipairs(Buttons) do
            if touch.pos:dist(vec2(button.x,button.y)) < 50 then
                U.button[button.name]=true
            end
        end
    end
end

local tooClose = 30

function checkLeftRight(touch)
    local dist = math.abs(touch.pos.x - touch.pos.y)
    if dist < tooClose then return end
    local slope = touch.pos.y/touch.pos.x
    if slope > 1 then U.button.left = true
    else U.button.right = true
    end
end

Trying this tells me that I’ve been too clever. It works exactly as intended, but my fingers do not. I think that either a vertical division between left and right, or a horizontal one, would work better. Moving diagonally doesn’t feel natural, and neither right-left nor up-down works as expected, because the closer you are to an edge the less ability you have to get to the opposite turning direction.

I’ll try a division like I had before, but without the motion logic. (Yes, it would have been more efficient to have started from there. In fact, it still might be. I’ll revert these changes and check out this morning’s experiment and work from it.

Back to “press and drag for turns experiment”

Here’s our code now:

local TurnStart = 100
local deadZone = 10

function checkLeftRight(touch)
    local x = touch.pos.x
    if x > 250 or touch.pos.y > 250 then return end
    if touch.state == BEGAN then
        TurnStart = x
    else
        if x < TurnStart - deadZone then 
            U.button.left = true
        elseif x > TurnStart + deadZone then 
            U.button.right = true
        end
    end
end

I’ll strip this down to:

function checkLeftRight(touch)
    local x = touch.pos.x
    if x > 250 or touch.pos.y > 250 then return end
    -- good idea goes here
end

A little experimenting with my finger and thumb on the screen suggests that a good breakpoint between left and right might be at x = 100. The gap, let’s try 10 on either side. So:

local LRCenter = 100
local LRGap = 10

function checkLeftRight(touch)
    local x = touch.pos.x
    if x > 250 or touch.pos.y > 250 then return end
    if x < LRCenter - LRGap then U.button.left = true
    elseif x > LRCenter + LRGap then U.button.right = true
    end
end

This works exactly as I intended, subject only to some possible adjustment to the position and width of the turn areas, and probably some graphical indication of where they are.

The scheme can be used with a single finger or thumb sliding back and forth, or with one or two fingers touching. So it is no worse than before.

Nor should we expect it to be. All we’ve really accomplished here is to change the shape of the turn buttons, in a sort of odd way.

This is such a debacle that I think I should quash this article. But that’s not in the spirit of what we do here.

Instead, I’ll again revert to yesterday, and decide that these experiments succeeded in telling me that I don’t have a better idea yet.

Summing Up

We’ve tried three experiments for improving the left-right behavior of the game’s human factors. None of them did what I had hoped, namely make me squeal with joy over how nice the new way was.

I learned that given that we have to have on-screen buttons, and for now, that’s our limitation, the best we can probably do is to make slight changes in their positions on the screen, and make them larger. I think I’ve also learned that I do want a gap between them, where the finger could rest with nothing happening.

Some would say that these experiments “failed”, and I can dig it. Certainly they didn’t produce what I’d hoped for. That’s how ideas go, they’re not all good.

Now I happen to recall that @Dave1707 positioned the buttons vertically, left above right, in a mod that he made. Depending how you position your hand, that could be better than left to right.

It may be that I should put in some parameters to allow some adjustments of the buttons, at least for further experiments.

For now, the learning is that we’ve not come up with the best of all possible turning ideas.

No code this time, nothing happened. I was not here. I did not say this.


  1. “Of course”: Phrase meaning, roughly, “a lot of math and a certain amount of internet searching has caused me to believe this fairly strongly”.