Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Randomly spawning physics bodies that are influenced by gravity

edited June 2014 in General Posts: 142

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
Tagged:

• Posts: 425

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

• Posts: 425

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.

• Posts: 425

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
• edited June 2014 Posts: 142

@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

• Posts: 7,810

@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
• Posts: 425

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

• Posts: 142

@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?

• edited June 2014 Posts: 425

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
• edited June 2014 Posts: 142

@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.

• edited June 2014 Posts: 142

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

• Posts: 7,810

@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
• Posts: 425

Wow that's compact

• edited June 2014 Posts: 142

@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.

• Posts: 7,810

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

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

• edited June 2014 Posts: 142

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