== Lesson 11: Hitting Things image::learn/11-hitting-things.png[The finished lesson, 480] Your game from lesson ten works. Shots break rocks, rocks cost lives, and the explosions land in the right places. Watch it closely, though, and it cheats, in both directions. A rock that slides past the tip of a wing takes a life. A shot that goes through the empty sky beside a rock breaks it anyway. Two things in a game touch when the numbers say they do. Nobody looks at the pictures. This lesson makes the numbers agree with what the player sees: it swaps your hand-written test for the engine's own, shrinks the boxes being tested so they fit the things inside them, and gives you a switch that shows you those boxes. The picture above is the game with that switch on. === Where You Are The game asks "do these two touch?" in exactly two places, and both use `overlapping`, the function you typed in lesson nine. One is in `updateRocks`, where a rock meets the ship: [source,lua] ---- if overlapping(shipX, shipY, shipWidth, shipHeight, rock.x, rock.y, rockWidth, rockHeight) then ---- The other is in `updateShots`, where a shot meets a rock: [source,lua] ---- if not gone and overlapping(shot.x, shot.y, shotWidth, shotHeight, rock.x, rock.y, rockWidth, rockHeight) then ---- Everything in this lesson changes those two lines and adds a few small functions to feed them. Nothing else in the game moves. The whole finished script is in `learn/11-hitting-things.singe` if you lose your way. === Read It Once More Lesson nine already did this part: `overlapping` is rectangle collision written out by hand. Read it once more before you throw it away, because it is the whole of the idea: [source,lua] ---- function overlapping(ax, ay, aw, ah, bx, by, bw, bh) if ax + aw <= bx then return false end if bx + bw <= ax then return false end if ay + ah <= by then return false end if by + bh <= ay then return false end return true end ---- The question "are these two touching?" is hard to answer directly and easy to answer backwards. Two rectangles are *not* touching when one of them is entirely to the left of the other, or entirely to the right, or entirely above, or entirely below. There is no fifth way to miss. If none of those four is true, they are touching. Each `if` is one of the four ways to miss. `ax + aw` is the right-hand edge of the first rectangle, so `ax + aw <= bx` reads "the first one ends before the second one starts", which is what "entirely to the left" means in numbers. The same shape three times more, for right, above, and below, with `y` and the heights in place of `x` and the widths, and the only thing left is `true`. Those are the eight numbers any collision test needs: a corner and a size for each of two things. Keep the shape in your head, because you are about to delete it. === The Engine Already Has It You met `collideRects` in lesson seven, where it caught the falling blocks in Dodge. Lesson nine wrote the same test out by hand so you could see inside it. You have seen inside it. Delete `overlapping`, and change the two lines from *Where You Are* to call the engine instead: [source,lua] ---- if collideRects(shipX, shipY, shipWidth, shipHeight, rock.x, rock.y, rockWidth, rockHeight) then ---- [source,lua] ---- if not gone and collideRects(shot.x, shot.y, shotWidth, shotHeight, rock.x, rock.y, rockWidth, rockHeight) then ---- Same eight numbers, in the same order: the first rectangle's corner and size, then the second one's. Run it, and the game plays exactly as it did, which is the point. The only difference is at the very edge. Your `<=` said that two rectangles meeting along an edge are not touching. `collideRects` says they are, and a rectangle with no width or height touches nothing at all. Those awkward cases are where hand-written versions go quietly wrong, and they are one reason to use the engine's. `collideRects` is one of a family of collision tests, and the family is one part of what this book calls the *game kit*: a handful of plain functions for the things nearly every game used to write for itself. Testing whether two things touch is one. Saving a number for next time is another, and it is lesson twelve. Waiting a while, sliding a number smoothly from one value to another, and showing how long each frame took are the rest. The reference gives each part a section of its own: Collide, Save, Timer, Tween, and Stats. The collision tests are the plainest of the lot: plain questions, with no handles to keep and nothing to set up or free. And none of them move anything. `collideRects` tells you that the ship and the rock are in the same place; what to do about it is yours to decide, and everything interesting in a game happens in that decision. === The Picture Is Not the Hit Box Lesson ten promised to explain why the rectangle around a round rock is the wrong shape. The rectangle around the ship is wrong too, and for the same reason. Draw the ship's 32 by 24 rectangle on paper and shade in the ship. It is a triangle with two fins, so there is empty sky in both top corners and between the fins. The rock is round, so there is empty sky in all four corners of its square. A rock that clips the top corner of the ship's rectangle has not hit the ship. It has flown past the wing, and a player who loses a life for it will say the game cheated -- and be right. So the box you test is not the picture you draw. It is smaller, and it sits inside the picture. Add three numbers to the constants at the top of your file: [source,lua] ---- local SHIP_PAD_X = 6 local SHIP_PAD_Y = 5 local ROCK_PAD = 4 ---- The pad is how far in from each edge the real thing starts. Six pixels off each side of the ship and five off the top and bottom leaves a box 20 by 14 in the middle of a 32 by 24 picture, which is about where the hull is. Four off every side of the rock leaves 16 by 16, and that is nearly all rock. Both boxes are a little smaller than the things they stand for, and that is on purpose. A box slightly too small gives the player near misses that feel like skill. A box slightly too big gives them hits that feel like cheating. Players forgive the first and never the second. Rather than write the arithmetic out at every test, write it once for each kind of thing: [source,lua] ---- function rockBox(rock) return rock.x + ROCK_PAD, rock.y + ROCK_PAD, rockWidth - ROCK_PAD * 2, rockHeight - ROCK_PAD * 2 end function shipBox() return shipX + SHIP_PAD_X, shipY + SHIP_PAD_Y, shipWidth - SHIP_PAD_X * 2, shipHeight - SHIP_PAD_Y * 2 end ---- The sizes come from `rockWidth` and `shipWidth` and their friends, the measurements lesson nine took from the pictures, so there is still not one raw pixel size in the playing code. Redraw the ship bigger and the box grows with it. Each of those functions hands back four numbers at once, separated by commas. A function may return as many values as it likes, and the way to catch them is a list of names on the left of the `=`: [source,lua] ---- function shipHitsRock(rock) local sx, sy, sw, sh = shipBox() local rx, ry, rw, rh = rockBox(rock) return collideRects(sx, sy, sw, sh, rx, ry, rw, rh) end ---- Now the test in `updateRocks` reads like the question it is asking: [source,lua] ---- if shipHitsRock(rock) then ---- The padding arithmetic is in exactly two places, and the numbers it uses are at the top of the file, so you can tune how fair the game feels by changing one number rather than by hunting through your tests. Put the new functions in their places in the alphabet, as lesson nine asked: `rockBox` after `onSoundCompleted`, then `shipBox` and `shipHitsRock`. === Firing Back That is the ship's side done. The other collision in the game is the one you fire back with: every shot against every rock, in `updateShots`. It has the same problem the ship had, in a smaller size. A shot is a 4 by 10 rectangle tested against the rock's whole 24 by 24 square, so a shot that only passes through the empty corner beside a rock still breaks it. You could give the shot a padded box of its own. Look at the numbers first, though. Pad a shot four pixels wide and there is almost nothing left, and testing a whole rectangle to find out whether a sliver of light touched something is more machinery than the question deserves. === A Shot Is Almost a Point Test the tip instead. `collidePointRect` asks whether one point falls inside one rectangle -- two numbers for the point, then the rectangle's corner and size: [source,lua] ---- function shotHitsRock(shot, rock) local rx, ry, rw, rh = rockBox(rock) return collidePointRect(shot.x + shotWidth / 2, shot.y, rx, ry, rw, rh) end ---- `shot.y` is the top of the shot and `shot.x + shotWidth / 2` is halfway across it, so the point being tested is the middle of the shot's nose. That is where a player thinks a shot is. The rock's side of the question uses the same `rockBox` the ship does, so a rock is the same size whatever hits it. It goes after `shipHitsRock`, and the test in `updateShots` becomes: [source,lua] ---- if not gone and shotHitsRock(shot, rock) then ---- === Deciding What Happened None of the collision tests do anything. They answer, and your code decides. Here are the two places that decide, with their new questions in them. First `updateRocks`: [source,lua] ---- if shipHitsRock(rock) then local channel = soundPlay(boomClip) newRock(rock) lives = lives - 1 if lives <= 0 then dying = true deathChannel = channel if channel < 0 then endGame() end end end ---- And the loop inside `updateShots`: [source,lua] ---- for _, rock in ipairs(rocks) do if not gone and shotHitsRock(shot, rock) then newRock(rock) score = score + HIT_SCORE gone = true soundPlay(boomClip, 0, 45) end end ---- Everything under the questions is exactly what lesson ten left there. A rock that hits the ship bangs, goes back to the top, and costs a life. A rock that is shot bangs, goes back to the top, and scores. The shot goes too, and the `not gone` stops one shot from breaking two rocks. Change any of that and you change the game, without touching a single collision test, which is the reason the questions and the answers are kept apart. Run it. Fly close past a rock and let it brush a fin: you keep the life. Shoot just beside a rock and the shot sails past. Shoot into the middle of one and it breaks, the same as ever. === Seeing the Boxes The hit boxes are invisible, which makes a mistake in them invisible too. Draw them while you are working. One more constant at the top: [source,lua] ---- local SHOW_BOXES = false ---- Two more functions, which go at the very top of the alphabet: [source,lua] ---- function drawBox(x, y, width, height) overlayBox(x, y, x + width - 1, y + height - 1) end function drawBoxes() colorForeground(0, 255, 0) drawBox(shipBox()) for _, rock in ipairs(rocks) do drawBox(rockBox(rock)) end end ---- And in `onOverlayUpdate`, after the ship is drawn and before the score goes on top: [source,lua] ---- spriteDraw(shipSprite, shipX, shipY) if SHOW_BOXES then drawBoxes() end drawHud() ---- Change `SHOW_BOXES` to `true` and green outlines appear over the artwork, as in the picture at the top of this lesson. Change it back when you are done. A switch like this, one word at the top of a file that turns on a picture of what the program believes, is worth more than any amount of staring. === What Just Happened [source,lua] ---- function rockBox(rock) return rock.x + ROCK_PAD, rock.y + ROCK_PAD, rockWidth - ROCK_PAD * 2, rockHeight - ROCK_PAD * 2 end ---- Four values from one `return`, separated by commas. Lua is happy to hand back as many as you want, and the caller decides how many to keep. When a call like this is the *last* thing inside another call's parentheses, all four are passed along, which is why `drawBox(rockBox(rock))` works and gives `drawBox` its four arguments. Anywhere else in an argument list, only the first value survives. That rule bites exactly once, and it bites at the end of this lesson. [source,lua] ---- return collideRects(sx, sy, sw, sh, rx, ry, rw, rh) ---- `collideRects` wants eight numbers: corner and size, corner and size. It gives back `true` or `false` and changes nothing. Because the answer comes straight back out of the function with `return`, `shipHitsRock(rock)` reads like a question wherever it is used. [source,lua] ---- return collidePointRect(shot.x + shotWidth / 2, shot.y, rx, ry, rw, rh) ---- `collidePointRect` wants six: a point, then a rectangle. The edge counts as inside, so a shot exactly on the boundary hits. Use it whenever one side of the question is small enough that its size does not matter. [source,lua] ---- colorForeground(0, 255, 0) overlayBox(x, y, x + width - 1, y + height - 1) ---- Both of these are from lesson seven. `overlayBox` takes two *corners*, not a corner and a size -- the one place in this lesson where the numbers change shape -- and the `- 1` is because both corners are included. `colorForeground` is a switch that stays flipped, so everything `drawBoxes` draws comes out green. Sprites and `overlayPrint` ignore it, so the rest of the game looks the same. === The Rest of the Family `collideRects` and `collidePointRect` will carry most 2D games. The others are there for the shapes rectangles describe badly, and each is the same kind of plain question: * `collideCircles` and `collidePointCircle`, for anything round. A circle is a center and a radius. * `collideRectCircle`, for a round thing against a square one. * `collidePointPolygon`, for a shape with corners of its own -- a dragon, a continent, an odd-shaped button. You give it a flat list of `x`, `y`, `x`, `y` numbers. * `collideSegments`, for two line segments crossing. If you are honest, your rock is round, and the circle tests fit it better than any box. Padding is the cheap answer and it is good enough for most games, which is why it comes first. The exact answer is one of the things to try below. `collideSegments` matters more than it sounds. Your shot moves six pixels a frame and the rocks are sixteen pixels of hit box, so a shot cannot get past one without being tested inside it. Make the shot fast enough -- forty pixels a frame -- and it jumps from below a rock to above it, never once being in the same place as the rock, and it never hits anything. Drawing the line from where the shot was to where it is now, and asking `collideSegments` whether that line crosses an edge, is the cure. Slow things do not need it. There is one collision callback in Singe, `onCollision`, and it is not for this. It belongs to the 3D physics engine: bodies with mass, falling and bouncing off one another in three dimensions, telling you where they touched and how hard. You will meet it in lesson twenty-four. Nothing on the overlay reports itself, and nothing needs to. Your rocks are in a list, you know how many there are, and asking is cheap. And when you want to know not just *whether* two things met but where the moving one should end up -- sliding along a wall instead of stopping dead inside it -- the collide calls deliberately do not answer that. A library called `bump` is bundled with Singe for exactly that job, and the reference's Included Libraries section says how to reach it. === Try It . *Take the padding out.* Set `ROCK_PAD` to `0` and play for a minute. Then set it to `8` and play again. One of them feels like the game is lying to you. . *Look at what you are testing.* Set `SHOW_BOXES` to `true` and watch the green outlines while a rock passes the ship. Now set `SHIP_PAD_X` to `16`, play, and notice that nothing can hit you any more. Work out why from the arithmetic in `shipBox`. . *Make the shot too fast.* Set `SHOT_SPEED` to `40`. Count how many rocks you hit out of ten. Work out from the numbers why that happens before you change it back. . *Use the whole shot.* Rewrite `shotHitsRock` to use `collideRects` with the shot's full rectangle instead of `collidePointRect` with its nose. Play both and decide which one you prefer; there is no right answer, which is worth knowing. . *Make the rock round.* Give the rock a circle instead of a box: its center is `rock.x + rockWidth / 2` across and `rock.y + rockHeight / 2` down, and a radius of `10` is about right. Use `collideRectCircle` for the ship and `collidePointCircle` for the shot, and draw the circle in `drawBoxes` with `overlayCircle`, which takes a center and a radius. === Break It on Purpose The two boxes are already four values each, so it is tempting to feed them straight in and skip the eight local names: [source,lua] ---- function shipHitsRock(rock) return collideRects(shipBox(), rockBox(rock)) end ---- That looks right and it is wrong. Run it and the game stops before it draws a thing: ---- 186:collideRects: Expected 8 argument(s), got 5. ---- The line is the one you just typed, so yours may not be 186. Five, though. `rockBox(rock)` is the last thing in the parentheses, so all four of its values went in. `shipBox()` is not, so it was cut down to one. One plus four is five, and `collideRects` will not guess at the other three. This error comes from the engine rather than from Lua, so it is shaped like the ones in lessons nine and ten: the number at the front is the line, then the name of the function that refused, then the complaint. Put the eight local names back. === What You Learned * Collision is arithmetic on rectangles, and you can write it yourself. * Two rectangles miss in exactly four ways; if none of them is true, they hit. * `collideRects` takes a corner and a size for each of two rectangles and answers `true` or `false`. It counts edges that only touch as touching. * The collision tests are part of the game kit, plain functions with nothing to set up. Save, Timer, Tween, and Stats are the rest of it. * `collidePointRect` asks whether a point is inside a rectangle, which is the right question for anything small. * The box you test is smaller than the picture you draw, or the game feels unfair. * A function can return several values, and only the last call in an argument list passes all of them on. * The collision tests only answer. What happens next is your code, and keeping the two apart lets you change either one alone. * Drawing your hit boxes on screen turns an invisible bug into a visible one. * `onCollision` is for 3D physics bodies, not for sprites on the overlay. === Next Time The game counts your score and takes your lives, and the moment you quit, the best score anybody ever got is gone. It starts in the middle of the action the instant it loads. And it keeps track of what it is doing with two true-or-false variables, `over` and `dying`, which is exactly the trap lesson seven warned you about. Lesson twelve gives it a title screen, one variable in place of those two, and a high score that is still there tomorrow.