How do I destroy an object?

I'm new to Lua and Playdate dev, so please excuse my ignorance. I'm trying to make an Arkanoid clone and I'm having trouble determining when to destroy a brick. I have the colliders all set up and the ball bounces off the brick just fine but I have no idea how to make the brick "go away". Currently I have the following which isn't working at all - specifically it seems like the collisions aren't registering at the position I gave (the center of the brick):

	if (brick ~= nil) then
		local _, _, _, length = brick:checkCollisions(brick.transform.x, brick.transform.y)

		if (length > 0) then
			brick = nil
			print("removed brick")
		end
	end

Objects in Lua are destroyed by the garbage collector once there are no more references to them in scope. So all you have to do is set any variable or table entry referring to the object to nil, or (assuming it's not a global and there are no other references) just wait for the scope the object was declared in to exit.

In the case of sprites, the system keeps a reference to each sprite that's been add()ed to the display list. So make sure that you call brick:remove() before setting brick = nil and it will get cleaned up the next time garbage collection runs. The GC frequency is determined by the runtime, but you can control it somewhat.

That all makes sense but I think I still have something wrong.

Here's my brick.lua file:

import "CoreLibs/sprites"
import "CoreLibs/graphics"

local gfx <const> = playdate.graphics

class("brick").extends(gfx.sprite)

function brick:init(x, y)
    self.transform = {
        x = x,
        y = y,
        width = 40,
        height = 16
    }

    self:moveTo(self.transform.x, self.transform.y)
    self:setImage(gfx.image.new("images/brick-1"))
    self:setCollideRect(0, 0, self:getSize())
    self:setTag(1) -- bricks tag
    self:add()
end

and then my main.lua file:

import "ball"
import "paddle"
import "brick"

local ball = ball()
local paddle = paddle()
local brick = brick(20, 8)

local gfx <const> = playdate.graphics

local function loadGame()
	playdate.display.setRefreshRate(60) -- Sets framerate to 60 fps
	math.randomseed(playdate.getSecondsSinceEpoch()) -- seed for math.random
	gfx.setBackgroundColor(gfx.kColorBlack)
end

local function updateGame()
	ball:update()
	paddle:update()
end

local function drawGame()
	gfx.clear() -- Clears the screen
	ball:draw()
	paddle:draw()
end

loadGame()

function playdate.update()
	updateGame()
	drawGame()

	if (brick ~= nil) then
		local _, _, _, length = brick:checkCollisions(brick.transform.x, brick.transform.y)

		if (length > 0) then
			brick:remove()
			brick = nil
			print("removed brick")
		end
	end
end

Does anything stand out as wrong?

It looks like you're missing a call to the superclass initializer. Try adding this as the first line of brick:init():

brick.super.init(self)

You should also set your sprite's image before calling moveTo(). This ensures the sprite's size is updated to match the image; otherwise the size will be zero. (You can also set it manually via self.setSize() or just by assigning self.width and self.height.)

OK I've made those changes without any luck :smiling_face_with_tear:

Any other ideas?

What’s going wrong exactly?

Try ball:checkCollisions(brick.transform.x, brick.transform.y) instead.

brick:checkCollisions won’t return a collision for the location of brick because sprites don’t collide with themselves.

1 Like

assuming it's not a global

Memory for globals can be freed too by setting the global to nil, right?

Yes, sorry if I phrased that poorly. I meant that globals are different because they never go out of scope.

Well, my print statement isn't getting triggered so that makes me think the collision isn't even working. I can see the ball collide with the brick and bounce off of it though. I'm only checking the length of the collisions array because the only thing that can collide with the brick is the ball.

I tried changing it to ball:checkCollisions, but then it thinks the ball has collided immediately and the brick is instantly removed. They start at different positions so I'm not sure how that could happen.

Great! That’s what checkCollisions does. It gives a list of the collisions that occur for any given x and y without moving the sprite. You've given it the x and y of the brick, so it returns a collision with the brick. And then you've asked it to remove brick if it detects a collision. So it’s working!

What you probably want to do is ball:moveWithCollisions each frame. That way, the ball will move, but only return a collision if it actually collides.

So however you were moving the ball before, replace that with something like:

actualX, actualY, collisions, length = ball:moveWithCollisions(newX, newY)

Then paste in the bit that removes the bricks that ball collides with:

for i = 1, length do
       collisions[i].other:remove()
       print("removed brick") 
       -- and when you have a score variable:
       -- score += 1
end

... and then it still won't work the way you want, because now it'll destroy any sprite it contacts, which might include walls and your paddle!

There are a number of ways to deal with this. Your code above only has one brick, called brick, so you could do:

for i = 1, length do
     if collisions[i].other = brick then
          collisions[i].other:remove()
          print("removed brick") 
          -- and when you have a score variable:
          -- score += 1
     end
end

But that's going to get tricky when you have dozens of bricks. I tend to just give all my sprites a type. I'd add self.type = "brick" to brick:init and self.type = "paddle" to paddle:init and self.type = "ball" to ball.init. More experienced people probably have better ways.

But then you can do:

for i = 1, length do
     if collisions[i].other.type = "brick" then
          collisions[i].other:remove()
          print("removed brick") 
          -- and when you have a score variable:
          -- score += 1
     end
end

Apologies in advance for any errors!

1 Like

I'll give this all a shot and report back!

1 Like

its-working-anakin-skywalker

1 Like

playdate-20220730-085925

3 Likes

Nice work!

Looks like you need to add the ability to aim the ball though, otherwise it’ll bounce around forever at 45 degree angles.

IIRC from Arkanoid on the Mac Classic (the last Arkanoid I really spent time with), the paddle x-axis movement is key. So maybe instead of just flipping the ball’s y velocity when it collides with the paddle you could:

  • Calculate old ball speed math.sqrt(velocity x * velocity x +velocity y * velocity y)
  • Ball velocity y *= -1
  • Ball velocity x += (paddle velocity x * 0.1)
  • Calculate new ball speed as above
  • Scale velocity x and y by (old ball speed / new ball speed) to keep the ball moving at a steady speed.
1 Like

Yep, I'm planning on doing that! I'm also going to store the level layouts in files or something so I don't have to manually load each brick.

1 Like