--# Main
-- Bouncing Demos by Ignatz
--These demos show how to bounce a ball round the screen, from very simple to using the physics engine
--You can use them as templates for your own projects
--Just run this, and press the buttons at upper left to see the various demos
--Simple = Demo_1
--Friction = Demo_2
--Physics = Demo_3
--Physics rotation = Demo_4
--How to use the demos in your own projects
--Each demo tab is completely self contained, but is set up as a class to make managing all of them easier.
--To change any of them from a class to a main program, copy the code from the tab you want, into an
--empty project and remove the first line saying Bounce_X =class()
--remove the class prefixes in front of all the function names, so everywhere you (say) see
--Bounce_2:somefunction(), change it to somefunction()
--However, if you do want to use the demos in a class the way they are, you should probably prefix all
--the variables with self. so they aren't global variables.
--The code below manages all the demos and isn't part of them.
function setup()
choices={"Simple","Friction","Physics","Physics Rotation"}
classes={Bounce_1(),Bounce_2(),Bounce_3(),Bounce_4()}
callbacks={bounce1}
for i=1,#choices do
parameter.action(choices[i],function(n) Select(n) end)
end
currSel=""
Select("Simple")
end
function draw()
b:draw()
end
--this function changes between demos, setting up the new choice
function Select(btn)
if string.find(currSel,"Physics")~=nil then b:cleanup() end
--setup new choice
for i=1,#choices do
if btn==choices[i] then
b=classes[i]
b:setup()
currSel=btn
return
end
end
end
--this function only applies to demos which use physics
--it tells the demo which objects collided
function collide(contact)
b:collide(contact)
end
--# Bounce_1
Bounce_1=class()
--[[
A simple bouncing object
The code below makes a red ball and/or an image bounce around the walls endlessly.
It isn't difficult to do, as long as you change direction when you get close to a wall
You will see an image and a red ball bouncing together when you run this. They have
been included to show you how to do both types. Just delete the one you don't want
(the code is all marked to help you).
--]]
function Bounce_1:setup()
x=200 --starting x
y=250 --starting y
dx=3 --the amount x changes
dy=2 --the amount y changes
--if you want to draw a ball, use this ---------
d=60 --diameter of ball
--if you prefer to use an image, use this instead--------
--change the image by pressing on the name in brackets (try to pick a roughly circular one)
img=readImage("Tyrian Remastered:Mine Spiked Huge")
d=(img.width+img.height)/2 --use average of width and height as diameter
output.clear()
print("This is a simple bouncing demo")
print("a ball and an image bounce forever")
print("These are the two main things you will use for bouncing")
print("They bounce together just to keep the code very simple for you to follow")
end
function Bounce_1:draw()
--update x and y positions
x=x+dx
y=y+dy
--set up screen and color
background(200,200,200,255)
--this chunk draws the ball --
pushStyle() --see comment at very bottom
fill(255,0,0,255)
ellipse(x,y,d)
popStyle()
-------------------------------
--this chunk draws an image ---
sprite(img,x,y)
-------------------------------
--change direction if we hit a wall
r=d/2 --how close the centre of the ball can get to a wall before bouncing
if x>WIDTH-r or x<r then dx=-dx end --reverse x direction if we hit left or right wall
if y>HEIGHT-r or y<r then dy=-dy end --reverse y direction if we hit top or bottom wall
end
-- pushStyle and popStyle --
-- in some programming, we plonk an object on the screen, then start setting colour etc
-- However, because Codea is refreshing the screen 60 times a second, nothing on the screen is permanent,
-- and everything is just a drawing.
-- Actually, it's more like real life. If you paint, you select a brush and mix a palette, and then you
-- paint on the canvas. No matter what you paint, your brush size and your colours will stay the same
-- until you change them.
-- Codea works like this too. You set the colour, brush size, whatever, then you draw on the screen. Those
-- settings will stay there for the next thing you draw.
-- Which can make a mess! So Codea provides pushStyle, which stores current settings safely, and popStyle,
-- whch brings them back when you're done messing around, so you don't mess things up for anything else
-- you're drawing. So whenever changing colours, styles, etc, wrap your changes between pushStyle and
-- popStyle.
--# Bounce_2
Bounce_2=class()
--[[
A simple bouncing object that slows down
The code below makes a red ball and/or an image bounce around the walls, slowing down with friction and
the effect of impacts.
Bouncing isn't difficult to do, as long as you change direction when you get close to a wall. Friction
and impacts require trial and error to get them looking realistic, and you can get your object stuck in
a wall if you aren't careful. If objects collide it starts getting hard.
You will see an image and a red ball bouncing together when you run this. Just delete the
one you don't want (the code is all marked to help you).
--]]
function Bounce_2:setup()
x=200 --starting x
y=250 --starting y
dx=3 --the amount x changes
dy=2 --the amount y changes
impact=0.9 --speed is multiplied by this on impact
friction=0.999 --speed is multiplied by this on each draw
stop=2 --stop if speed is below this level, so it doesn't go on forever getting tinier
--if you want to draw a ball, use this ---------
d=60 --diameter of ball
--if you prefer to use an image, use this instead--------
--change the image by pressing on the name in brackets (try to pick a roughly circular one)
img=readImage("Tyrian Remastered:Mine Spiked Huge")
d=(img.width+img.height)/2 --use average of width and height as diameter
output.clear()
print("The ball and image slow down and stop, due to impacts and friction. At this stage, we are managing all of this ourselves.")
end
function Bounce_2:draw()
--update x and y positions and speed
dx=dx*friction
dy=dy*friction
if math.abs(dx)+math.abs(dy)<stop then dx=0 dy=0 end
x=x+dx
y=y+dy
--set up screen and color
background(200,200,200,255)
--this chunk draws the ball --
pushStyle() --see comment at foot of Bounce_1
fill(255,0,0,255)
ellipse(x,y,d)
popStyle()
-------------------------------
--this chunk draws an image ---
sprite(img,x,y)
-------------------------------
--change direction if we hit a wall
r=d/2 --how close the centre of the ball can get to a wall before bouncing
--check for impact, if so, adjust speed for impact and reverse direction
if x>WIDTH-r or x<r then dx=-dx*impact dy=dy*impact end --reverse x direction if we hit left or right wall
if y>HEIGHT-r or y<r then dx=dx*impact dy=-dy*impact end --reverse y direction if we hit top or bottom wall
end
--# Bounce_3
Bounce_3=class()
--[[
Bouncing with physics
The code below makes a red ball and an image bounce around the walls, using the Codea physics engine.
Some of the code is adapted from an example by Dalorbi.
--]]
function Bounce_3:setup()
--create objects
--we'll create a table of them simply to make it easy to destroy them later (see cleanup function at bottom)
--They are unlike circles and images, which only exist if you draw them - instead, physics objects stay
--alive until you actually destroy them, and they will interact with other physics objects
--The added problem is they are invisible, so if you leave any lying around without something
--visible attached, you're likely to collide with them
bodies={}
--first the ball
ball_diam=60 --diameter of ball
--now create the physics object that behaves like a real ball, it will tell us where to draw our ball
--think of it as a GPS navigator that guides us
p_ball = physics.body(CIRCLE,ball_diam/2) --physics bodies use radius not diameter
p_ball.x = math.random(60,250) -- choose a random position between 30 and the screen width minus 30
p_ball.y = math.random(60,250) -- same for the y position
p_ball.gravityScale = 0 -- no gravity - if there was, the ball would fall to the bottom of screen
p_ball.restitution = 1 -- this circle is bouncy
p_ball.friction = 0.1 -- the amount of friction to be applied to the circle
p_ball.linearVelocity = vec2(math.random(400),math.random(400)) --velocity (pixels per second = 60 redraws)
p_ball.info="ball" --so when collisions occur, we can figure out which object was involved
table.insert(bodies,p_ball) --keep track of body so we can destroy it later if we want
--now much the same for the image
img=readImage("Tyrian Remastered:Mine Spiked Huge")
img_diam=(img.width+img.height)/2 --use average of width and height as diameter
--create the physics object
p_img = physics.body(CIRCLE,img_diam/2) --physics bodies use radius not diameter
p_img.x = math.random(60,250) -- choose a random position
p_img.y = math.random(60,250) -- same for the y position
p_img.gravityScale = 0 -- no gravity
p_img.restitution = 0.8 -- this image is not so bouncy
p_img.friction = 0.4 -- the amount of friction to be applied to the image
p_img.linearVelocity = vec2(math.random(400),math.random(400)) -- velocity
p_img.info="mine" --so when collisions occur, we can figure out which object was involved
table.insert(bodies,p_img) --keep track of body so we can destroy it later if we want
--finally the walls
Bounce_3:CreateWalls()
output.clear()
print("The physics engine handles friction, bouncing and collisions")
end
--create the walls - physics objects can only bounce off each other
--the first four parameters are the starting x,y and ending x,y
--the fifth parameter is restitution, or bounciness, from 0=no bounce to 1=very bouncy
function Bounce_3:CreateWalls()
leftWall = Bounce_3:CreateWall(1,1,1,HEIGHT-1,1.0)
rightWall = Bounce_3:CreateWall(WIDTH-1,0,WIDTH-1,HEIGHT-1,0.9)
bottomWall = Bounce_3:CreateWall(1,1,WIDTH-1,1,0.2)
topWall = Bounce_3:CreateWall(1,HEIGHT-1,WIDTH-1,HEIGHT-1,0.7)
end
--this function creates one wall (actually just a line)
function Bounce_3:CreateWall(x,y,x1,y1,r)
local w = physics.body(EDGE,vec2(x,y),vec2(x1,y1)) -- vec2
w.restitution=r --see comment above
table.insert(bodies,w) --keep track of body so we can destroy it later if we want
return w
end
function Bounce_3:draw()
background(200,200,200,255)
--draw the ball --
pushStyle() --see comment at foot of Bounce_1
fill(255,0,0,255)
ellipse(p_ball.x,p_ball.y,ball_diam)
popStyle()
-------------------------------
--draw image ---
sprite(img,p_img.x,p_img.y)
-------------------------------
end
--the function to be called when two physics bodies collide
--we are told which bodies (including walls etc) have collided
--contactA and contactB are the two bodies
function Bounce_3:collide(contact)
if contact.state == BEGAN then
if contact.bodyA.info=="ball" or contact.bodyB.info=="ball" then sound(SOUND_JUMP, 50) end
if contact.bodyA.info=="mine" or contact.bodyB.info=="mine" then sound(SOUND_EXPLODE, 50) end
end
end
--destroy all bodies when we are done, we have to keep track of the, ourselves, hence the table
function Bounce_3:cleanup()
for i,bb in ipairs(bodies) do
bb:destroy()
end
end
--# Bounce_4
Bounce_4=class()
--[[
Bouncing - and rotating - with physics
The code below makes a red rectangle and an image bounce around the walls, using the Codea physics engine.
Both of them rotate as they bounce.
--]]
function Bounce_4:setup()
--create objects
--we'll create a table of them simply to make it easy to destroy them later (see cleanup function at bottom)
--They are unlike circles and images, which only exist if you draw them - instead, physics objects stay
--alive until you actually destroy them, and they will interact with other physics objects
--The added problem is they are invisible, so if you leave any lying around without something
--visible attached, you're likely to collide with them
bodies={}
--first the rectangle
rect_width=100 --size of rectangle
rect_height=50
--now create the physics object that behaves like a real block, it will tell us where to draw our rectangle
--think of it as a GPS navigator that guides us
p_rect = physics.body(POLYGON,
vec2(-rect_width / 2, -rect_height / 2),
vec2(-rect_width / 2, rect_height / 2),
vec2(rect_width / 2, rect_height / 2),
vec2(rect_width / 2, -rect_height / 2)
)
p_rect.x = math.random(60,250) -- choose a random position between 30 and the screen width minus 30
p_rect.y = math.random(60,250) -- same for the y position
p_rect.angle=math.random(0,360) -- and rotation
p_rect.gravityScale = 0 -- no gravity - if there was, the ball would fall to the bottom of screen
p_rect.restitution = 1 -- this circle is bouncy
p_rect.friction = 0.1 -- the amount of friction to be applied to the circle
p_rect.linearVelocity = vec2(100+math.random(400),100+math.random(400)) --velocity (pixels per second = 60 redraws)
table.insert(bodies,p_rect)
--p_rect.linearDamping = 0.2
--p_rect.angularDamping = 0.2
p_rect.info="rect" --so when collisions occur, we can figure out which object was involved
--now much the same for the image
img=readImage("Tyrian Remastered:Mine Spiked Huge")
img_diam=(img.width+img.height)/2 --use average of width and height as diameter
--create the physics object
p_img = physics.body(CIRCLE,img_diam/2) --physics bodies use radius not diameter
p_img.x = math.random(60,250) -- choose a random position
p_img.y = math.random(60,250) -- same for the y position
p_img.angle=math.random(0,360)
p_img.gravityScale = 0 -- no gravity
p_img.restitution = 0.8 -- this image is not so bouncy
p_img.friction = 0.4 -- the amount of friction to be applied to the image
p_img.linearVelocity = vec2(math.random(400),math.random(400)) -- velocity
p_img.info="mine" --so when collisions occur, we can figure out which object was involved
table.insert(bodies,p_img)
--p_img.linearDamping = 0.9
--p_img.angularDamping = 0.9
--finally the walls
Bounce_4:CreateWalls()
output.clear()
print("Watch how the rectangle and")
print("image rotate as they bounce")
end
--create the walls - physics objects can only bounce off each other
--the first four parameters are the starting x,y and ending x,y
--the fifth parameter is restitution, or bounciness, from 0=no bounce to 1=very bouncy
function Bounce_4:CreateWalls()
leftWall = Bounce_3:CreateWall(1,1,1,HEIGHT-1,1.0)
rightWall = Bounce_3:CreateWall(WIDTH-1,0,WIDTH-1,HEIGHT-1,0.9)
bottomWall = Bounce_3:CreateWall(1,1,WIDTH-1,1,0.2)
topWall = Bounce_3:CreateWall(1,HEIGHT-1,WIDTH-1,HEIGHT-1,0.7)
end
--this function creates one wall (actually just a line)
function Bounce_4:CreateWall(x,y,x1,y1,r)
local w = physics.body(EDGE,vec2(x,y),vec2(x1,y1)) -- vec2
w.restitution=r --see comment above
table.insert(bodies,w) --keep track of body so we can destroy it later if we want
return w
end
function Bounce_4:draw()
background(200,200,200,255)
--draw the rectangle --
pushStyle() --see comment at foot of Bounce_1
fill(255,0,0,255)
--see explanation of next few lines at bottom below
pushMatrix()
translate(p_rect.x, p_rect.y)
rotate(p_rect.angle)
rect(-rect_width / 2, -rect_height / 2, rect_width, rect_height)
popMatrix()
popStyle()
-------------------------------
--draw image ---
--see explanation of next few lines at bottom below
pushMatrix()
translate(p_img.x, p_img.y)
rotate(p_img.angle)
sprite(img,0,0)
popMatrix()
-------------------------------
end
--the function to be called when two physics bodies collide
--we are told which bodies (including walls etc) have collided
--contactA and contactB are the two bodies
function Bounce_4:collide(contact)
if contact.state == BEGAN then
if contact.bodyA.info=="rect" or contact.bodyB.info=="rect" then sound(SOUND_JUMP, 50) end
if contact.bodyA.info=="mine" or contact.bodyB.info=="mine" then sound(SOUND_EXPLODE, 50) end
end
end
function Bounce_4:cleanup()
for i,bb in ipairs(bodies) do
bb:destroy()
end
end
--Drawing rotating physics objects
--Suppose you had to draw a little shape all over a piece of paper, at different angles
--The hard way is to keep the paper as is, and start having to lean all oer the place to draw sideways over
--her, and upside down over there
--The sensible approach is to swivel and move the paper so that you can draw each shape the right way up,
--right in front of you. And Codea finds that the best way, too. Let's look at an example from above.
--[[
pushMatrix() --before we go swivelling the paper, let's remember how it was, so we can put it back later
translate(x, y) -- move the place we want draw under our pen, so that the point x,y is now treated as 0,0
-- this means if you now draw at 40,30, Codea will actually draw at x+40,y+30
rotate(p_rect.angle) --rotate our whole screen by whatever angle our physics object is at
-- this is like turning your paper round so you can draw the image the right way up
rect(-rect_width / 2, -rect_height / 2, rect_width, rect_height) --draw our rectangle
--note it is drawn the right way up, but Codea has the screen at an angle, so the final
--result will be an angled rectangle
--note also the centre of the rectangle is at 0,0, because we've moved to the exact
--spot where we want to draw it. So the first two parameters x and y are set to a
--point half of the width to the left, and half of the height below.
popMatrix() --put everything back the way it was
--]]