Randomly spawning physics bodies that are influenced by gravity

Hi,
I’m trying to randomly spawn ellipses at the top of the screen and have them drop, influenced by gravity. I have a physics.body shown with an image on the bottom of the screen that I can drag along an x-axis. When the ellipses fall, I would like to be able to “catch” them. Eventually, I would like to have them spawn faster based on how long you survive (you have to catch all the ellipses). So far I’ve been able to spawn an ellipse at random x values, but for some reason they are not dropping even though I have set gravityScale to 1. Sorry for my ignorance on the topic. I’ve just begun using Codea. I’ve posted my code below and replaced any images with images that come default with Codea in case you wanted to test my code. Warning, it is very basic.

Thanks!




--# Main
-- Basket Catch

-- Use this function to perform your initial setup
function setup()
    print("Hello World!")
    bmaWidth = 100
    bmaHeight = 100
    inTouch = false
    bma = physics.body(POLYGON,
                            vec2(-bmaWidth/2,-bmaHeight/2),
                            vec2(-bmaWidth/2,bmaHeight/2),
                            vec2(bmaWidth/2, bmaHeight/2),
                            vec2(bmaWidth/2, -bmaHeight/2))
    bma.x = WIDTH/2
    bma.y = 100
    bma.gravityScale = 0
    box = DragMe()
    b1 = Ball(100,50,300,color(128,128,128))
    timer = 0
    b = physics.body(CIRCLE,50)
end

-- This function gets called once every frame
function draw()
    -- This sets a dark background color 
    background(40, 40, 50)

    -- This sets the line thickness
    strokeWidth(3)

    -- Do your drawing here
    timer = timer + 1 
    text("Score:0",WIDTH-100,HEIGHT-50)
    box:draw()
    b1:draw()
end

function touched(t)
    box:touched(t)
end
    


--# DragMe
DragMe = class()

function DragMe:init()
    -- you can accept and set parameters here
    self.pos = vec2(bma.x,bma.y)
    self.size = 100
end

function DragMe:draw()
    -- Codea does not automatically call this method
    pushStyle()
    fill(0, 33, 255, 255)
    rectMode(CENTER)
    sprite("Cargo Bot:Crate Blue 1",self.pos.x,self.pos.y,self.size,self.size)
    popStyle()
    
end
function DragMe:hit(point)
    if point.x > (self.pos.x - self.size/2) and
    point.x < (self.pos.x + self.size/2) and
    point.y > (self.pos.y - self.size/2) and
    point.y < (self.pos.y + self.size/2) then
        return true
    end
    
    return false
end
function DragMe:touched(t)
    -- Codea does not automatically call this method
    if self:hit(vec2(t.x,t.y)) and 
        t.state ==  MOVING then
        self.pos = self.pos + vec2(t.deltaX, 0)
    end
end




--# Ball
Ball = class()

function Ball:init()
    -- you can accept and set parameters
    self.pos = vec2(math.random(WIDTH),HEIGHT-100)
    gravityScale = 1
end

function Ball:draw()
    -- Codea does not automatically call this method
    pb = physics.body(CIRCLE,50)
    pb.x = self.pos.x
    pb.y = self.pos.y
    pb.gravityScale = 1
    ellipse(pb.x,pb.y,50)
    fill(0, 46, 255, 255)
end

function Ball:touched(touch)
    -- Codea does not automatically call this method
end

Unless you have a special reason for using physics(such as wanting the circles to bounce around in the bucket) what you are trying to do doesn’t really need a physics engine

You are defining the ball physics body every frame so that will eat up memory and make it crash. Also I don’t think that you are actually moving the physics box, just the image of the crate.

Here is some code to start you off


--# Main
-- Catch2
displayMode(FULLSCREEN)
displayMode(OVERLAY)
-- Use this function to perform your initial setup
function setup()
    physics.gravity(0,-100)--set the gravity for the physics world
    
    w,h=200,50--the width&height of the box
    box=physics.body(POLYGON, vec2(-w/2,-h/2),vec2(-w/2,h/2),vec2(w/2,h/2),vec2(w/2,-h/2))
    --define the box with a type and list of certices
    box.x=WIDTH/2
    box.y=200
    box.type=KINEMATIC--it doesnt react to collisions
    box.linearVelocity=vec2(0,0)--not moving yet
    
    balls={}--table to hold the physics balls
    parameter.action("spawn-ball",function()--button that spawns a ball
        table.insert(balls,
        createBall(math.random(100,WIDTH-100),HEIGHT))--create a ball and add it to our table
    end)
end

-- This function gets called once every frame
function draw()
    -- This sets a dark background color 
    background(40, 40, 50)

    fill(255)
    rectMode(CENTER)
    rect(box.x,box.y,w,h)--draw the box
    
    ellipseMode(RADIUS)
    for i=1,#balls do--draw the balls
        ellipse(balls[i].x,balls[i].y,50,50)
    end
    for i=#balls,1,-1 do
        if balls[i].y<-60 then--is the ball offscreen?
            balls[i]:destroy()--then destroy it and remove it from our table
            table.remove(balls,i)
        end
    end
end

function touched(t)--when you tap the screen
    if t.x<box.x then
        box.linearVelocity=vec2(-200,0)--push the box towards your finger
    else
        box.linearVelocity=vec2(200,0)
    end
    if t.state==ENDED then
        box.linearVelocity=vec2(0,0)--stop it when you stop touching
    end
end

function createBall(x,y)--the function to create a ball
    local ball=physics.body(CIRCLE,50)--define
    ball.x=x
    ball.y=y
    return ball--send the ball's data to put in the table
end

@Coder Thanks! As for timing the ball spawn, how would you set it so that say, every 3 seconds a new ball spawns. I tried setting a timer that increases by 1, but I’m not sure how to get it to spawn EVERY 3 seconds. I used

if timer >= 180 then
b1:draw()
timer = 0
else timer = timer + 1
end

, but the ball only appears for a second as once it is set to 0, it is no longer >= 180

@Staples As @Coder mentioned, this can be done without using gravity. Here’s an example of that.


displayMode(FULLSCREEN)

function setup()
    balls={}
    cnt=200
    limit=180   
    dx=WIDTH/2 
    gameOver=false
    total=0
    fontSize(40)
end

function draw()
    background(40,40,50)
    fill(255)
    if gameOver then
        text("Game over",WIDTH/2,HEIGHT-100)
        text("double tap screen to start again",WIDTH/2,HEIGHT-150)
        return
    end
    cnt=cnt+1
    if cnt>limit then
        if limit<35 then
            limit=35
        else
            limit=limit-5
        end
        cnt=0
        create()
    end  
    rect(dx,200,100,10)
    for a,b in pairs(balls) do
        ellipse(b.x,b.y,30)
        b.y=b.y-5
        if b.x>dx and b.x<dx+100 and b.y>180 and b.y<220 then
            table.remove(balls,a)
            total=total+1
        end
        if b.y<0 then
            gameOver=true
        end
    end
    text("Balls caught     "..total,WIDTH/2,HEIGHT-100)
end

function create()
    table.insert(balls,vec2(math.random(50,WIDTH-50),HEIGHT))
end

function touched(t)
    if t.state==BEGAN and gameOver and t.tapCount==2 then
        restart()
    end
    if dx~=nil then
        dx=dx+t.deltaX 
    end
end

If you are using the code I posted just call createBall() when the timer is up

@Coder oh ok thanks and sorry,but one last thing. How can I create a reaction to a collision between the box and the ball? I’ve changed the box’s width and height so that is is 100x100, added an image to cover the fill, and changed the ball’s size to 25x25. I want it to look as though the ball is falling into the box. So, I would like the ball not to collide upon contact,but fall behind the box and be destroyed once it reached the center of the box(the 50,50 location inside the box). How would I go about doing so?

I have updated the code so that the box and balls are now in classes. I think it now does what you want


--# Main
-- Catch2


-- Use this function to perform your initial setup
function setup()
    physics.gravity(0,-100)--set the gravity for the physics world
    box=Box(WIDTH/2,200)
    balls={}--table to hold the physics balls
    parameter.action("spawn-ball",function()--button that spawns a ball
        table.insert(balls, Ball(math.random(100,WIDTH-100),HEIGHT,30,#balls+1))
    end)
end

-- This function gets called once every frame
function draw()
    -- This sets a dark background color 
    background(0, 0, 0, 255)
    
    ellipseMode(RADIUS)
    for i=1,#balls do--draw the balls
        balls[i]:draw()
    end
    box:draw()
end

function touched(t)--when you tap the screen
    box:touched(t)
end

function collide(c)
    if c.state==BEGAN and 
    (c.bodyA.info=="ball" or c.bodyB.info=="ball") and 
    (c.bodyA.info=="box" or c.bodyB.info=="box") then 
        if c.bodyA=="ball" then
            balls[c.bodyA.num]:hit()
        else
            balls[c.bodyB.num]:hit()
        end
    end
end






--# Ball
Ball = class()

function Ball:init(x,y,r,n)
    self.body=physics.body(CIRCLE,r)
    self.body.x=x
    self.body.y=y
    self.body.radius=r
    self.body.info="ball"
    self.body.num=n
end

function Ball:draw()
    fill(255)
    ellipseMode(RADIUS)
    if self.body then
        ellipse(self.body.x,self.body.y,self.body.radius,self.body.radius)
        fill(0)
        text(self.body.num,self.body.x,self.body.y)
    else
        fill(255,0,0)
        ellipse(self.info.x,self.info.y,self.info.r,self.info.r)
    end
end

function Ball:hit()
    self.info={x=self.body.x,y=self.body.y,r=self.body.radius}
    self.body:destroy()
    self.body=nil
    tween(0.5,self.info,{x=box.body.x,y=box.body.y,r=0})
end



--# Box
Box = class()

function Box:init(x,y)  
    local w,h=200,50
    self.vertices = 
{vec2(-w/2,-h/2),vec2(-w/2,h/2),vec2(w/2,h/2),vec2(w/2,-h/2)}
    self.body = physics.body(POLYGON,unpack(self.vertices))
    self.body.x = x
    self.body.y = y
    self.body.type=KINEMATIC
    self.body.angle = 0
    self.body.linearVelocity=vec2(0,0)
    self.body.info="box"
    self.w,self.h=w,h
end

function Box:draw()
    fill(255)
    rectMode(CENTER)
    rect(self.body.x,self.body.y,self.w,self.h)
end

function Box:touched(t)
    if t.x>self.body.x then
        self.body.linearVelocity=vec2(200,0)
    else 
        self.body.linearVelocity=vec2(-200,0)
    end
    if t.state==ENDED then
        self.body.linearVelocity=vec2(0,0)
    end
end


@Coder I thought about that, similar to the app 100 balls on the App Store, but then if you you cannot score over around 20 in the game because the balls will pile up.

@Coder @dave1707 Thanks for all the help, I’ve figured it out now!

@Staples Here’s another version.


displayMode(FULLSCREEN)
supportedOrientations(PORTRAIT_ANY)
    
function setup()
    dx=0
    ball={}
    bucket=physics.body(CHAIN,false,vec2(-100,0),
            vec2(-50,-100),vec2(50,-100),vec2(100,0)) 
    bucket.x,bucket.y=WIDTH/2,300
    bucket.type=KINEMATIC
    time=3
end

function create()
    b=physics.body(CIRCLE,10)
    b.x=math.random(40,WIDTH-40)
    b.y=HEIGHT   
    table.insert(ball,b)
end

function draw()
    background(40,40,50)
    fill(255,0,0)
    strokeWidth(4)
    line(bucket.x-100,bucket.y,bucket.x-50,bucket.y-100) 
    line(bucket.x-50,bucket.y-100,bucket.x+50,bucket.y-100)  
    line(bucket.x+50,bucket.y-100,bucket.x+100,bucket.y) 
    strokeWidth(0)
    stroke(255)
    for z=#ball,1,-1 do
        ellipse(ball[z].x,ball[z].y,20)  
        if ball[z].y<0 then
            ball[z]:destroy()
            table.remove(ball,z)
        end
    end
    time=time+DeltaTime
    if time>2 then
        create()
        time=0
    end
end

function touched(t)
    dx=dx+t.deltaX
    bucket.linearVelocity=vec2(dx,0)
end

Wow that’s compact

@dave1707 I’ve been working on a game using some of the code above, but recently I’ve decided to change the main idea of the “basket” to be similar to the code you posted just above and make the balls physics bodies. I was just wondering if there was any smoother way of moving the basket. Using the code above, the basket moves quite slowly and the movement is delayed. I am guessing this is because you are changing the linear velocity to move towards your touch rather than always have the basket’s x value located at “dx”. I have tried altering the code to make basket.x = dx and also tried changing the chain to a group of edges which I’m guessing is the exact same as a chain because there was no difference. Can you explain if there is a simple way to make ellipses act as physics bodies on lines(as if they are kinematic edges) without making the balls or the basket physics bodies or if there is a way to make the basket’s movement as a physics body more similar to its movement as a sprite?

P.S. Sorry for the long question and if it is confusing.

@Staples Change the touched() routine above to this.

function touched(t)
    dx=(t.x-bucket.x)*5
    bucket.linearVelocity=vec2(dx,0)
end

@dave1707 That was a lot simpler than I expected! Thanks, that’s exactly what I was going for!