43. Learning from popping balloons
This post is based on a forum question, and discusses how to manage physics objects, especially how to destroy them. I’ve done it in the form of interactive debugging, showing how to fix problems and improve the result one step at a time. And, as always, there are interesting things to learn.
It was a simple enough question. A user wanted to make a very simple game, touching the screen to fire bullets from left to right, to pop coloured balloons that floated up the screen.
My bullets keep disappearing!
However, on writing code to fire bullets (and before making any balloons), the first problem was that each time he touched the screen, it created a new bullet, and the old one disappeared, so there was only ever one bullet. Oh, and they were falling to the ground rather quickly.
So here was the initial code. Can you see the problem?
function setup() bullet = physics.body(CIRCLE,10) end function touched(touch) x = touch.x y = touch.y if touch.state == BEGAN then --create bullet bullet.x,bullet.y = x,y bullet.linearVelocity = vec2(1000,0) bullet.restitution = 0 bullet.type = DYNAMIC end end function draw() background(0, 0, 0, 255) --draw bullets pushStyle() fill(255, 255, 255, 255) ellipse(bullet.x,bullet.y,10) popStyle() end
The problem of falling toward the ground is a gravity thing. Either you need more speed, or less gravity, but in a simple game, maybe it’s just easier to turn gravity off. So we put this line in setup
physics.gravity(0,0)
The next problem is that there is only one bullet defined in setup. When you touch the screen, the touched function resets the x,y position etc for the bullet, but there is still only one bullet.
So we need to create a table to hold more than one bullet, and the draw function needs to draw them all. And, if they leave the screen, we need to delete them.
First, we need to add this in setup
bullets={}
Then we need to add one line to the touched function, near the end
function touched(touch) x = touch.x y = touch.y if touch.state == BEGAN then --create bullet bullet.x,bullet.y = x,y bullet.linearVelocity = vec2(1000,0) bullet.restitution = 0 bullet.type = DYNAMIC table.insert(bullets,bullet) -- NEW end end
and some code in draw
--ellipse(bullet.x,bullet.y,10) --previous code for i,b in pairs(bullets) do --draw all bullets ellipse(b.x,b.y,10) end
Hold on, that doesn’t solve the problem. We still only have one bullet at a time, which gets replaced when you touch the screen.
The problem is that when we redefine the bullet in the touched function, and then add it to the bullets table, we are adding the original bullet. Remember that for anything more complicated than a string or a number, like a table, a class, or a physics object, if you put it into a variable (like bullet), all that is stored is the memory address (or ‘pointer’) which tells Code where to find the object details. So when you add bullet to the table over and over, you are adding the same memory address to the table each time. and all of them point to the original bullet.
Clearly, we need to create a new bullet each time we touch the screen. So we’ll delete the bullet we created in setup, and do it all in touched, like so.
function touched(touch) x = touch.x y = touch.y if touch.state == BEGAN then --create bullet local bullet = physics.body(CIRCLE,10) -- NEW bullet.x,bullet.y = x,y bullet.linearVelocity = vec2(1000,0) bullet.restitution = 0 bullet.type = DYNAMIC table.insert(bullets,bullet) end end
Note that we make the bullet local, so that a new one is created each time.
OK, so now we have multiple bullets. The code so far is here, if you want to see.
And if you’re wondering why we don’t need to calculate new x,y positions for the bullets, that’s the magic of physics objects. You give them an initial velocity, and the physics engine automatically updates x,y all the time for you. The only problem is that physics objects are invisible, which is why you need to draw a little circle exactly on top of the invisible physics object, so the user can see it.
But we forgot something. We need to delete the bullets when they leave the screen. So we’ll modify draw…
for i,b in pairs(bullets) do -- i is the index (position in the table), b is the bullet if b.x>WIDTH or b.y>HEIGHT then --if outside screen.. table.remove(bullets,i) --remove the bullet from the table else ellipse(b.x,b.y,10) end end
That seems to be working ok.
Adding balloons
The user managed this nicely, with a little class. Note the use of “or” to set default values where parameters are not provided.
Ballons = class() function Ballons:init(x,y,r,c,lv) self.radius = r or math.random(25,50) self.ballon = physics.body(CIRCLE,self.radius) self.ballon.x = x or math.random(WIDTH/2,WIDTH) self.ballon.y = y or math.random(-HEIGHT/2,-self.radius) self.ballon.linearVelocity = lv or vec2(0,math.random(100,150)) self.ballon.colr = c or color(math.random(255),math.random(255),math.random(255),math.random(100,150)) end
As an aside, the balloons are a nice pastel shade simply through using a low value for alpha, the last colour element, which makes them somewhat transparent, too.
We need to use this class to create some balloons. The user did this in setup.
ball = {} for i = 1,5 do ball[i] = Ballons() end
and drew the balloons in draw. I won’t show that code, as it is very simple. The final result is here. The balloons look quite good, and the bullets bang into them, but don’t pop them yet.
Popping balloons
So now we come to the tricky part. We want to pop a balloon when a bullet hits it, and also make the bullet disappear.
There is a collide function built into Codea, that triggers every time two physics objects touch. It gives you the two objects as bodyA and bodyB. Now, how will we know what type of objects these are, and which ones they are, so we know what to delete?
The user tried using the info property of physics objects, to store information about type of object, so he could get the information directly from bodyA and bodyB, but this is quite tricky. In the end, I think a brute force approach is as good as any, like so
function collide(contact) for i,b in pairs(ball) do if b.ballon==contact.bodyA or b.ballon==contact.bodyB then for j,bb in pairs(bullets) do if bb==contact.bodyA or bb==contact.bodyB then -- DELETE BALL, BULLET end end end end end
So the code compares all the balls against bodyA and bodyB, and if there is a match, it compares all the bullets against bodyA and bodyB. If that matches too, then clearly a bullet has hit a balloon, and we must delete them both. We know exactly which balloon and bullet to delete, too.
But this is NOT so easy.
Deleting physics objects
To delete a bullet or balloon, we need to delete the physics objects themselves. The Codea documentation tells you that this is a two step process
-- destroy should be used before -- setting a body to nil circle = physics.body(CIRCLE, 100) circle:destroy() circle = nil
…and our objects are also in tables, so we need to think that through.
You would imagine you can delete the physics objects in the collide function above, but if you do, you will find Codea crashes or hangs. It literally breaks. This is because until you leave the collide function, Codea hasn’t finished with bodyA and bodyB, and destroying either of them totally confuses things. (Details here, if you are interested).
So how can we delete the balloon and bullet? There is really only one place to do it, in the draw function. So we need a way of telling the draw function that there is stuff to delete.
I chose to do it by putting this line in collide
collision={i,j} --store index number of balloon and bullet
i is the index of the colliding balloon, ie it is ball[i], and j is the index of the colliding bullet.
Then, in draw, I put this…
if collision~=nil then --ignore if not set --two step process, destroy physics object, then remove from table --ball first ball[collision[1]].ballon:destroy() table.remove(ball,collision[1]) --bullet bullets[collision[2]]:destroy() table.remove(bullets,collision[2]) collision=nil --so we don't try to do the same thing twice! end
note collision[1] and collision[2] are simply the “i” and “j” from the collide function, telling us which balloon and bullet to delete.
Oh, and I realised we weren’t deleting the physics objects for the bullets that flew off the screen, so I added a line in for that.
Note – if we had multiple balloons or bullets to delete, we would need a table to which we could add as many as we wanted, then the draw function could loop through them.
The final code is here, complete with a nice popping sound added by the user.
All of this just shows that quite innocent and simple sounding questions can lead you down quite interesting paths…