Last active
March 18, 2023 17:48
-
-
Save dermotbalson/6509753 to your computer and use it in GitHub Desktop.
Pinball
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--# Notes | |
--this project creates a simple pinball game | |
--instead of flippers, it uses a spherical controller to bounce the ball | |
--and the physics engine to manage all the collisions | |
--To run the final step, you will need all the images in this zipfile | |
--https://www.dropbox.com/s/uv3t6dawvcsh7d6/Pinball.zip | |
--You need to unzip them and copy them into the Dropbox folder you have linked to Codea, then | |
--go into Codea's dropbox library, and press the sync button tp download them | |
--HOW TO USE THIS PROJECT | |
--There are a number of tabs at the top. Press on a tab to see its code. | |
--Work through the tabs from left to right, to see the project develop | |
--You can run the code in any of the project tabs, by | |
--1. running the program, and | |
--2. selecting the tab number using the controls at the upper left of the screen | |
--Unfortunately, this project uses the full screen, so, to select another tab, you need to press the | |
--little arrow icon at top left of the screen, to show the controls. Then you can press the icon again | |
--to make it full screen again | |
--The program will remember your selection after that. | |
--This enables you to work with one tab at a time, make changes and see the effects by running the program | |
--# Main | |
--Main | |
--This code manages which Code tab is run | |
--it remembers your last choice, and if you select a different one, it runs that instead | |
local tabs = {} | |
local fnames = {"setup","draw","touched","collide","orientationChanged","close","restart","keyboard","cleanup"} | |
local fns = {} | |
local tabDesc={} | |
function setup() | |
for k,v in ipairs(fnames) do --store addresses of key event functions | |
fns[v] = _G[v] | |
end | |
LastCode=readProjectData("Code") or 1 --load stored tab number | |
parameter.integer("Choose_a_tab",1,#tabs,LastCode,ShowList) --tab selector | |
parameter.action("Run selected tab", RunCode) | |
RunCode() | |
end | |
function ShowList() | |
output.clear() | |
for i=1,#tabs do | |
print(i,tabDesc[i]) | |
end | |
end | |
--these two functions do all the tab switching magic (thanks to Andrew_Stacey) | |
function localise(n,d) | |
if d then tabDesc[n]=d end | |
local t= {} | |
setmetatable(t,{__index = _G}) | |
--setfenv(2,t) | |
tabs[n] = t | |
return t | |
end | |
--change tabs | |
function RunCode() | |
output.clear() | |
saveProjectData("Code",Choose_a_tab) | |
cleanup() | |
local t = tabs[Choose_a_tab] | |
for k,v in ipairs(fnames) do | |
if t[v] then _G[v] = t[v] else _G[v] = fns[v] end -- overwrite with the new code | |
end | |
setup() | |
end | |
--default empty function to avoid errors for tabs that don't have it | |
function cleanup() | |
end | |
--# Step_1 | |
if localise then _ENV=localise(1,"Build controller") end --you can DELETE this if you copy this code to another project | |
--we'll start with the controller | |
--from the beginning, we'll force the iPad to be held long side up | |
--we'll also use the full screen | |
--(how do you select a code tab to use if you can't see the tab selector?) | |
--(Answer-press the little left arrow at top of screen while the program runs) | |
--(this toggles full screen on and off) | |
--I was going to only show the top half of the ball controller, but it is quite small | |
--and difficult to reach, so I'm showing the whole ball | |
--I can still create the illusion of a semi circle by drawing a solid block of colour along the bottom | |
--I'd like to try varying the shape of the controller, maybe squashing it, but I need to put a physics | |
--object behind it to handle bouncing, and physics can only do circular objects, so circular it will be | |
--I've also drawn the ball we'll bounce around the screen | |
displayMode(FULLSCREEN) | |
supportedOrientations(PORTRAIT) | |
function setup() | |
controlColour=color(217, 181, 58, 255) | |
controlSize=150 | |
blockColour=color(121, 112, 40, 255) | |
blockHeight=50 | |
ballColour=color(172, 172, 165, 255) | |
ballSize=40 | |
end | |
function draw() | |
background(0) --black background for now | |
--ball controller | |
pushStyle() | |
fill(controlColour) | |
ellipse(WIDTH/2,blockHeight,controlSize) | |
--colour block along bottom | |
fill(blockColour) | |
rect(0,0,WIDTH,blockHeight) | |
--ball | |
fill(ballColour) | |
ellipse(WIDTH/2,300,ballSize) | |
popStyle() | |
end | |
--# Step_2 | |
if localise then _ENV = localise(2,"Split code into functions") end --you can DELETE this if you copy this code to another project | |
--Having designed my controller, I move all the code into functions to keep it separate and clean | |
--This also makes it easy for you to change them, if you prefer a different colour or shape | |
--it's very important to compartmentalise code once you have it working | |
--it makes it much easier to debug problems, than if you just have one big mess of code | |
displayMode(FULLSCREEN) | |
supportedOrientations(PORTRAIT) | |
function setup() | |
setupScreen() | |
end | |
function setupScreen() | |
controlColour=color(217, 181, 58, 255) | |
controlSize=150 | |
controlX=WIDTH/2 --initial position | |
blockColour=color(121, 112, 40, 255) | |
blockHeight=50 | |
ballColour=color(172, 172, 165, 255) | |
ballSize=40 | |
ballPos=vec2(WIDTH/2,300) | |
end | |
function draw() | |
background(0) | |
drawTable() | |
end | |
--this function will draw all the objects on the table | |
function drawTable() | |
pushStyle() | |
--ball controller | |
fill(controlColour) | |
ellipse(controlX,blockHeight,controlSize) | |
--colour block along bottom | |
fill(blockColour) | |
rect(0,0,WIDTH,blockHeight) | |
--ball | |
fill(ballColour) | |
ellipse(ballPos.x,ballPos.y,ballSize) | |
popStyle() | |
end | |
--# Step_3 | |
if localise then _ENV = localise(3,"Add physics objects") end --you can DELETE this if you copy this code to another project | |
--now we'll add the basic physics objects | |
--walls around three sides, but not the bottom | |
--one for the ball controller too | |
--and one for the ball that will bounce around | |
--there is one tricky problem caused by all the different tabs in this project | |
--if you choose a different tab, we need to carefully remove all the physics objects from the current tab first | |
--otherwise they will live on and cause problems | |
--the easiest way to do this is to keep a list of them in a table, so we can just loop theough the table | |
--and delete them. So that's what the table called bodies is for. | |
displayMode(FULLSCREEN) | |
supportedOrientations(PORTRAIT) | |
function setup() | |
setupScreen() | |
end | |
function setupScreen() | |
bodies={} --table to hold physics bodies | |
controlColour=color(217, 181, 58, 255) | |
controlSize=150 | |
controlX=WIDTH/2 | |
blockColour=color(121, 112, 40, 255) | |
blockHeight=50 | |
ballColour=color(172, 172, 165, 255) | |
ballSize=40 | |
ballPos=vec2(WIDTH/2,300) | |
--physics bodies | |
controlBody=physics.body(CIRCLE,controlSize/2) | |
controlBody.type=STATIC --prevents controller moving when ball hits it | |
--(or from falling through floor due to gravity) | |
controlBody.x=controlX --starting position | |
controlBody.y=blockHeight | |
controlBody.linearVelocity=vec2(0,0) --prevent it moving when ball hits it | |
controlBody.restitution=3 --bounciness | |
table.insert(bodies,controlBody) | |
ballBody=physics.body(CIRCLE,ballSize/2) | |
ballBody.x=ballPos.x --starting position | |
ballBody.y=ballPos.y | |
ballBody.restitution=.8 --bounciness | |
ballBody.gravityScale=0.2 --gravity as fraction of normal | |
--(table is tilted but not vertical, so gravity is reduced) | |
table.insert(bodies,ballBody) | |
walls={} --we'll keep the walls in a little table | |
walls[1]=physics.body(EDGE,vec2(0,0),vec2(0,HEIGHT)) --left | |
walls[2]=physics.body(EDGE,vec2(0,HEIGHT),vec2(WIDTH,HEIGHT)) --top | |
walls[3]=physics.body(EDGE,vec2(WIDTH,0),vec2(WIDTH,HEIGHT)) --right | |
for i=1,3 do | |
walls[i].restitution=0.9 --bounciness | |
table.insert(bodies,walls[i]) --put bodies in our list | |
end | |
end | |
function draw() | |
background(0) | |
drawTable() | |
end | |
function drawTable() | |
pushStyle() | |
--ball controller | |
fill(controlColour) | |
--note now we use the physics object position | |
--it's important to do this so if something goes wrong, you can at least see where the physics object is | |
ellipse(controlBody.x,controlBody.y,controlSize) | |
--ball | |
fill(ballColour) | |
ellipse(ballBody.x,ballBody.y,ballSize) --note now we use the physics object position | |
popStyle() | |
--colour block along bottom | |
fill(blockColour) | |
rect(0,0,WIDTH,blockHeight) | |
end | |
--destroy physics bodies before going to run a different tab | |
function cleanup() | |
for i,b in pairs(bodies) do | |
b:destroy() | |
end | |
bodies=nil | |
controlBody:destroy() | |
controlBody=nil | |
ballBody:destroy() | |
ballBody=nil | |
for i=1,#walls do | |
walls[i]:destroy() | |
end | |
walls=nil | |
end | |
--# Step_4 | |
if localise then _ENV = localise(4,"Make controller work") end --you can DELETE this if you copy this code to another project | |
--now let's move the controller and try to keep the ball in play | |
--we need to include a touched function, see near bottom | |
displayMode(FULLSCREEN) | |
supportedOrientations(PORTRAIT) | |
function setup() | |
setupScreen() | |
end | |
function setupScreen() | |
bodies={} --table to hold physics bodies | |
controlColour=color(217, 181, 58, 255) | |
controlSize=150 | |
controlX=WIDTH/2 | |
blockColour=color(121, 112, 40, 255) | |
blockHeight=50 | |
ballColour=color(172, 172, 165, 255) | |
ballSize=40 | |
ballPos=vec2(WIDTH/2,300) | |
--physics bodies | |
controlBody=physics.body(CIRCLE,controlSize/2) | |
controlBody.type=STATIC --prevents controller moving when ball hits it | |
--(or from falling through floor due to gravity) | |
controlBody.x=controlX --starting position | |
controlBody.y=blockHeight | |
controlBody.linearVelocity=vec2(0,0) --prevent it moving when ball hits it | |
controlBody.restitution=1.3 --bounciness --PLAY WITH DIFFERENT VALUES FOR THIS (if >1, speeds up ball) | |
table.insert(bodies,controlBody) | |
ballBody=physics.body(CIRCLE,ballSize/2) | |
ballBody.x=ballPos.x --starting position | |
ballBody.y=ballPos.y | |
ballBody.restitution=.8 --bounciness | |
ballBody.gravityScale=0.5 --gravity as fraction of normal | |
--(table is tilted but not vertical, so gravity is reduced) | |
table.insert(bodies,ballBody) | |
walls={} --we'll keep the walls in a little table | |
walls[1]=physics.body(EDGE,vec2(0,0),vec2(0,HEIGHT)) --left | |
walls[2]=physics.body(EDGE,vec2(0,HEIGHT),vec2(WIDTH,HEIGHT)) --top | |
walls[3]=physics.body(EDGE,vec2(WIDTH,0),vec2(WIDTH,HEIGHT)) --right | |
for i=1,3 do | |
walls[i].restitution=0.9 --bounciness | |
table.insert(bodies,walls[i]) --put bodies in our list | |
end | |
end | |
function draw() | |
background(0) | |
drawTable() | |
end | |
function drawTable() | |
pushStyle() | |
--ball controller | |
fill(controlColour) | |
--note now we use the physics object position | |
--it's important to do this so if something goes wrong, you can at least see where the physics object is | |
ellipse(controlBody.x,controlBody.y,controlSize) | |
--ball | |
fill(ballColour) | |
ellipse(ballBody.x,ballBody.y,ballSize) --note now we use the physics object position | |
popStyle() | |
--colour block along bottom | |
fill(blockColour) | |
rect(0,0,WIDTH,blockHeight) | |
end | |
--trap touches on the controller | |
function touched(touch) | |
--this is a slider control so we are only interested in sliding movements | |
--the touch.state has three values that tells us about the touch | |
--BEGAN = touch just started | |
--MOVING = touch still in progress <-- this is the one we want | |
--ENDED = touch has ended | |
if touch.state==MOVING then | |
--we want to trap touches on the visible controller, which is a semi circle | |
--the easiest way is to test if | |
--(a) the touch is above the controller centre, and | |
--(b) the touch is within controlSize/2 of the controller centre | |
if touch.y>blockHeight and vec2(touch.x,touch.y):dist(vec2(controlBody.x,controlBody.y))<controlSize/2 then | |
controlBody.x=touch.x --that's all we need! | |
end | |
end | |
end | |
--destroy physics bodies before going to run a different tab | |
function cleanup() | |
for i,b in pairs(bodies) do | |
b:destroy() | |
end | |
bodies=nil | |
controlBody:destroy() | |
controlBody=nil | |
ballBody:destroy() | |
ballBody=nil | |
for i=1,#walls do | |
walls[i]:destroy() | |
end | |
walls=nil | |
end | |
--# Step_5 | |
if localise then _ENV = localise(5,"Add 3 planets") end --you can DELETE this if you copy this code to another project | |
--now we can add some more stuff on the screen | |
--I'm going to use a space theme with planets - this is convenient because they are circular, which is easiest | |
--I'll add three planets to start with | |
displayMode(FULLSCREEN) | |
supportedOrientations(PORTRAIT) | |
function setup() | |
setupScreen() | |
end | |
function setupScreen() | |
bodies={} --table to hold physics bodies | |
--controller | |
controlColour=color(217, 181, 58, 255) | |
controlSize=150 | |
controlX=WIDTH/2 | |
blockColour=color(121, 112, 40, 255) | |
blockHeight=50 | |
--ball | |
ballColour=color(172, 172, 165, 255) | |
ballSize=40 | |
ballPos=vec2(WIDTH/2,300) | |
--objects - there are going to be quite a few, so let's put them in a table | |
--within this table, each planet will have its own table with various properties | |
objects={} | |
objects[1]={colour=color(169, 126, 78, 255),size=20,x=150,y=300,points=100} --Mercury | |
objects[2]={colour=color(178, 168, 108, 255),size=50,x=250,y=400,points=60} --Venus | |
objects[3]={colour=color(105, 219, 217, 255),size=50,x=350,y=500,points=60} --Earth | |
--physics bodies | |
controlBody=physics.body(CIRCLE,controlSize/2) | |
controlBody.type=STATIC | |
controlBody.x=controlX | |
controlBody.y=blockHeight | |
controlBody.linearVelocity=vec2(0,0) | |
controlBody.restitution=1.3 | |
table.insert(bodies,controlBody) | |
ballBody=physics.body(CIRCLE,ballSize/2) | |
ballBody.x=ballPos.x | |
ballBody.y=ballPos.y | |
ballBody.restitution=.8 | |
ballBody.gravityScale=0.5 --gravity as fraction of normal | |
table.insert(bodies,ballBody) | |
--now you'll see one good reason for putting all our planets in a table | |
--one loop creates all their physics bodies | |
for i=1,#objects do | |
b=physics.body(CIRCLE,objects[i].size/2) -- size=diam, but we need radius | |
b.x=objects[i].x | |
b.y=objects[i].y | |
b.type=STATIC --planets don't move | |
b.info=i --identifier, so when we hit them, we can tell which one it was | |
objects[i].body=b --so we can keep track of them | |
table.insert(bodies,b) --for cleanup at the end | |
end | |
walls={} --we'll keep the walls in a little table | |
walls[1]=physics.body(EDGE,vec2(0,0),vec2(0,HEIGHT)) --left | |
walls[2]=physics.body(EDGE,vec2(0,HEIGHT),vec2(WIDTH,HEIGHT)) --top | |
walls[3]=physics.body(EDGE,vec2(WIDTH,0),vec2(WIDTH,HEIGHT)) --right | |
for i=1,3 do | |
walls[i].restitution=0.9 | |
table.insert(bodies,walls[i]) | |
end | |
end | |
function draw() | |
background(0) | |
drawTable() | |
end | |
function drawTable() | |
pushStyle() | |
--objects first | |
for i=1,#objects do | |
fill(objects[i].colour) | |
ellipse(objects[i].x,objects[i].y,objects[i].size) | |
end | |
--ball controller | |
fill(controlColour) | |
ellipse(controlBody.x,controlBody.y,controlSize) | |
--ball | |
fill(ballColour) | |
ellipse(ballBody.x,ballBody.y,ballSize) --note now we use the physics object position | |
popStyle() | |
--colour block along bottom | |
fill(blockColour) | |
rect(0,0,WIDTH,blockHeight) | |
end | |
--trap touches on the controller | |
function touched(touch) | |
if touch.state==MOVING then | |
if touch.y>blockHeight and vec2(touch.x,touch.y):dist(vec2(controlBody.x,controlBody.y))<controlSize/2 then | |
controlBody.x=touch.x --that's all we need! | |
end | |
end | |
end | |
--destroy physics bodies before going to run a different tab | |
function cleanup() | |
for i,b in pairs(bodies) do | |
b:destroy() | |
end | |
bodies=nil | |
controlBody:destroy() | |
controlBody=nil | |
ballBody:destroy() | |
ballBody=nil | |
for i=1,#walls do | |
walls[i]:destroy() | |
end | |
walls=nil | |
end | |
--# Step_6 | |
if localise then _ENV = localise(6,"Add more planets, get them moving") end --you can DELETE this if you copy this code to another project | |
--I'm going to add all the planets | |
--I'm getting a bit ambitious now, but I'd like to try getting the planets moving in a kind of orbit | |
--I'll start by just getting them to move from left to right repeatedly | |
displayMode(FULLSCREEN) | |
supportedOrientations(PORTRAIT) | |
function setup() | |
setupScreen() | |
end | |
function setupScreen() | |
bodies={} --table to hold physics bodies | |
--controller | |
controlColour=color(217, 181, 58, 255) | |
controlSize=150 | |
controlX=WIDTH/2 | |
blockColour=color(121, 112, 40, 255) | |
blockHeight=50 | |
--ball | |
ballColour=color(232, 46, 206, 255) --CHANGED THIS TO STAND OUT MORE | |
ballSize=40 | |
ballPos=vec2(WIDTH/2,300) | |
--objects - there are going to be quite a few, so let's put them in a table | |
--within this table, each planet will have its own table with various properties | |
--I'll include an x value but not set it yet, we'll do that underneath | |
--I'll define the y value as a proportion of screen height, and multiply by HEIGHT underneath | |
--this means it will work on different size screens, better than defining specific pixel positions | |
objects={} | |
objects[1]={colour=color(169, 126, 78, 255),size=20,x=0,y=.2,points=100,speed=200} --Mercury | |
objects[2]={colour=color(178, 168, 108, 255),size=50,x=0,y=.25,points=60,speed=140} --Venus | |
objects[3]={colour=color(105, 219, 217, 255),size=50,x=0,y=.33,points=60,speed=120} --Earth | |
objects[4]={colour=color(220, 89, 41, 255),size=40,x=0,y=.4,points=70,speed=100} --Mars | |
objects[5]={colour=color(218, 146, 105, 255),size=125,x=0,y=.55,points=30,speed=70} --Jupiter | |
objects[6]={colour=color(218, 215, 105, 255),size=90,x=0,y=.7,points=60,speed=50} --Saturn | |
objects[7]={colour=color(116, 145, 188, 255),size=70,x=0,y=.8,points=70,speed=30} --Uranus | |
objects[8]={colour=color(46, 58, 167, 255),size=70,x=0,y=.9,points=80,speed=20} --Neptune | |
--set the x value randomly | |
for i=1,#objects do | |
objects[i].x=math.random(objects[i].size,WIDTH-objects[i].size) | |
objects[i].y=objects[i].y*HEIGHT | |
--randomly reverse direction of movement (make planet move left, with 50% probability) | |
if math.random()<.5 then | |
objects[i].speed=-objects[i].speed | |
end | |
end | |
--physics bodies | |
controlBody=physics.body(CIRCLE,controlSize/2) | |
controlBody.type=STATIC | |
controlBody.x=controlX | |
controlBody.y=blockHeight | |
controlBody.linearVelocity=vec2(0,0) | |
controlBody.restitution=1.7 --INCEASED THIS because it is difficult to get past inner planets | |
table.insert(bodies,controlBody) | |
ballBody=physics.body(CIRCLE,ballSize/2) | |
ballBody.x=ballPos.x | |
ballBody.y=ballPos.y | |
ballBody.restitution=.8 | |
ballBody.gravityScale=0.5 --gravity as fraction of normal | |
table.insert(bodies,ballBody) | |
for i=1,#objects do | |
b=physics.body(CIRCLE,objects[i].size/2) -- size=diam, but we need radius | |
b.x=objects[i].x | |
b.y=objects[i].y | |
b.type=STATIC --planets don't move | |
b.info=i --identifier, so when we hit them, we can tell which one it was | |
objects[i].body=b --so we can keep track of them | |
table.insert(bodies,b) --for cleanup at the end | |
end | |
walls={} --we'll keep the walls in a little table | |
walls[1]=physics.body(EDGE,vec2(0,0),vec2(0,HEIGHT)) --left | |
walls[2]=physics.body(EDGE,vec2(0,HEIGHT),vec2(WIDTH,HEIGHT)) --top | |
walls[3]=physics.body(EDGE,vec2(WIDTH,0),vec2(WIDTH,HEIGHT)) --right | |
for i=1,3 do | |
walls[i].restitution=0.9 | |
table.insert(bodies,walls[i]) | |
end | |
end | |
function draw() | |
background(0) | |
drawTable() | |
end | |
function drawTable() | |
pushStyle() | |
--objects first | |
for i=1,#objects do | |
--move them, reverse direction if they get to the edge | |
objects[i].x=objects[i].x+objects[i].speed/60 | |
if objects[i].x<objects[i].size/2 then | |
objects[i].speed=-objects[i].speed | |
elseif objects[i].x>WIDTH-objects[i].size/2 then | |
objects[i].speed=-objects[i].speed | |
end | |
--adjust position of physics objects to match | |
objects[i].body.x=objects[i].x | |
--draw object | |
fill(objects[i].colour) | |
ellipse(objects[i].x,objects[i].y,objects[i].size) | |
end | |
--ball controller | |
fill(controlColour) | |
ellipse(controlBody.x,controlBody.y,controlSize) | |
--ball | |
fill(ballColour) | |
ellipse(ballBody.x,ballBody.y,ballSize) --note now we use the physics object position | |
popStyle() | |
--colour block along bottom | |
fill(blockColour) | |
rect(0,0,WIDTH,blockHeight) | |
end | |
--trap touches on the controller | |
function touched(touch) | |
if touch.state==MOVING then | |
if touch.y>blockHeight and vec2(touch.x,touch.y):dist(vec2(controlBody.x,controlBody.y))<controlSize/2 then | |
controlBody.x=touch.x --that's all we need! | |
end | |
end | |
end | |
function cleanup() | |
for i,b in pairs(bodies) do | |
b:destroy() | |
end | |
bodies=nil | |
controlBody:destroy() | |
controlBody=nil | |
ballBody:destroy() | |
ballBody=nil | |
for i=1,#walls do | |
walls[i]:destroy() | |
end | |
walls=nil | |
end | |
--# Step_7 | |
if localise then _ENV = localise(7,"Scoring") end --you can DELETE this if you copy this code to another project | |
--Now the scoring | |
--I changed one planet, made Mars score negative, just to add some spice | |
--to score, we need to identify when a collision occurs, and get the right score | |
--see the collide function at bottom (this is provided by Codea to help us) | |
displayMode(FULLSCREEN) | |
supportedOrientations(PORTRAIT) | |
function setup() | |
setupScreen() | |
score=0 --NEW we can start scoring, now | |
end | |
function setupScreen() | |
bodies={} --table to hold physics bodies | |
--controller | |
controlColour=color(217, 181, 58, 255) | |
controlSize=150 | |
controlX=WIDTH/2 | |
blockColour=color(121, 112, 40, 255) | |
blockHeight=50 | |
--ball | |
ballColour=color(232, 46, 206, 255) --CHANGED THIS TO STAND OUT MORE | |
ballSize=40 | |
ballPos=vec2(WIDTH/2,300) | |
objects={} | |
objects[1]={colour=color(169, 126, 78, 255),size=20,x=0,y=.2,points=100,speed=200} --Mercury | |
objects[2]={colour=color(178, 168, 108, 255),size=50,x=0,y=.25,points=60,speed=140} --Venus | |
objects[3]={colour=color(105, 219, 217, 255),size=50,x=0,y=.33,points=60,speed=120} --Earth | |
objects[4]={colour=color(220, 89, 41, 255),size=40,x=0,y=.4,points=-100,speed=100} --Mars | |
objects[5]={colour=color(218, 146, 105, 255),size=125,x=0,y=.55,points=30,speed=70} --Jupiter | |
objects[6]={colour=color(218, 215, 105, 255),size=90,x=0,y=.7,points=60,speed=50} --Saturn | |
objects[7]={colour=color(116, 145, 188, 255),size=70,x=0,y=.8,points=70,speed=30} --Uranus | |
objects[8]={colour=color(46, 58, 167, 255),size=70,x=0,y=.9,points=80,speed=20} --Neptune | |
--set the x value randomly | |
for i=1,#objects do | |
objects[i].x=math.random(objects[i].size,WIDTH-objects[i].size) | |
objects[i].y=objects[i].y*HEIGHT | |
--randomly reverse direction of movement (make planet move left, with 50% probability) | |
if math.random()<.5 then | |
objects[i].speed=-objects[i].speed | |
end | |
end | |
--physics bodies | |
controlBody=physics.body(CIRCLE,controlSize/2) | |
controlBody.type=STATIC | |
controlBody.x=controlX | |
controlBody.y=blockHeight | |
controlBody.linearVelocity=vec2(0,0) | |
controlBody.restitution=1.7 --INCEASED THIS because it is difficult to get past inner planets | |
table.insert(bodies,controlBody) | |
ballBody=physics.body(CIRCLE,ballSize/2) | |
ballBody.x=ballPos.x | |
ballBody.y=ballPos.y | |
ballBody.restitution=.8 | |
ballBody.gravityScale=0.5 --gravity as fraction of normal | |
table.insert(bodies,ballBody) | |
for i=1,#objects do | |
b=physics.body(CIRCLE,objects[i].size/2) -- size=diam, but we need radius | |
b.x=objects[i].x | |
b.y=objects[i].y | |
b.type=STATIC --planets don't move | |
b.info=i --identifier, so when we hit them, we can tell which one it was - see collide function at bottom | |
objects[i].body=b --so we can keep track of them | |
table.insert(bodies,b) --for cleanup at the end | |
end | |
walls={} --we'll keep the walls in a little table | |
walls[1]=physics.body(EDGE,vec2(0,0),vec2(0,HEIGHT)) --left | |
walls[2]=physics.body(EDGE,vec2(0,HEIGHT),vec2(WIDTH,HEIGHT)) --top | |
walls[3]=physics.body(EDGE,vec2(WIDTH,0),vec2(WIDTH,HEIGHT)) --right | |
for i=1,3 do | |
walls[i].restitution=0.9 | |
table.insert(bodies,walls[i]) | |
end | |
end | |
function draw() | |
background(0) | |
drawTable() | |
drawStats() --NEW | |
end | |
function drawTable() | |
pushStyle() | |
--objects first | |
for i=1,#objects do | |
--move them, reverse direction if they get to the edge | |
objects[i].x=objects[i].x+objects[i].speed/60 | |
if objects[i].x<objects[i].size/2 then | |
objects[i].speed=-objects[i].speed | |
elseif objects[i].x>WIDTH-objects[i].size/2 then | |
objects[i].speed=-objects[i].speed | |
end | |
--adjust position of physics objects to match | |
objects[i].body.x=objects[i].x | |
--draw object | |
fill(objects[i].colour) | |
ellipse(objects[i].x,objects[i].y,objects[i].size) | |
end | |
--ball controller | |
fill(controlColour) | |
ellipse(controlBody.x,controlBody.y,controlSize) | |
--ball | |
fill(ballColour) | |
ellipse(ballBody.x,ballBody.y,ballSize) --note now we use the physics object position | |
popStyle() | |
--colour block along bottom | |
fill(blockColour) | |
rect(0,0,WIDTH,blockHeight) | |
end | |
function drawStats() --NEW for now it will just show the score | |
pushStyle() | |
fontSize(24) | |
font("AmericanTypewriter-Bold") | |
fill(229, 25, 25, 150) | |
textMode(CORNER) | |
text("Score: "..score,70,HEIGHT-50) | |
popStyle() | |
end | |
--trap touches on the controller | |
function touched(touch) | |
if touch.state==MOVING then | |
if touch.y>blockHeight and vec2(touch.x,touch.y):dist(vec2(controlBody.x,controlBody.y))<controlSize/2 then | |
controlBody.x=touch.x --that's all we need! | |
end | |
end | |
end | |
--trap collisions | |
function collide(contact) | |
--just trap the BEGAN state (when the collision starts) | |
if contact.state==BEGAN then | |
--the contact object has information about the two objects colliding stored in bodyA and bodyB | |
--remember we stored the number of each planet in the info property of its physics object | |
--so if bodyA is a planet, then bodyA.info will be the number of the planet (same for bodyB) | |
if contact.bodyA.info~=nil then | |
score=score+objects[contact.bodyA.info].points | |
elseif contact.bodyB.info~=nil then | |
score=score+objects[contact.bodyB.info].points | |
end | |
end | |
end | |
function cleanup() | |
for i,b in pairs(bodies) do | |
b:destroy() | |
end | |
bodies=nil | |
controlBody:destroy() | |
controlBody=nil | |
ballBody:destroy() | |
ballBody=nil | |
for i=1,#walls do | |
walls[i]:destroy() | |
end | |
walls=nil | |
end | |
--# Step_8 | |
if localise then _ENV = localise(8,"Add circular orbits") end --you can DELETE this if you copy this code to another project | |
--we want the planets follow an orbit around the central controller | |
--We know the radius of the orbit (distance from the central controller) and speed in pixels per second | |
--But we need to calculate the position on the circular orbit, at each redraw | |
--We will need to use the angle to calculate x and y position. If we know the angle A, then | |
-- x=radius * cos(A) | |
-- y=radius * sin(A) | |
--We can calculate how much the angle changes with each redraw, because we know the speed, and we can | |
--calculate the circumference of the orbit = 2 * pi * radius | |
--Therefore the change in angle per second = 360 * speed/(2 * pi * radius) | |
--and we divide by 60 to get the change in angle, per redraw = 360 * speed/(2 * pi * radius) / 60 | |
--Angles are a bit strange in Codea, because rotation is anti-clockwise. So we will need to rotate the angle on | |
--each side of 90 degrees. | |
--We can stop a planet going off the screen in two ways. One is to check if the x or y value has left the screen, | |
--and if it has, we reverse direction. That is quite simple. | |
--The other way is to calculate the angle at which the planet hits the screen edge, and reverse direction when | |
--we reach that angle on either side. | |
--I will use the first method, because it's way simpler. | |
--Main code changes are marked with ****** below | |
displayMode(FULLSCREEN) | |
supportedOrientations(PORTRAIT) | |
function setup() | |
setupScreen() | |
score=0 | |
end | |
function setupScreen() | |
bodies={} --table to hold physics bodies | |
--controller | |
controlColour=color(217, 181, 58, 255) | |
controlSize=150 | |
controlX=WIDTH/2 | |
blockColour=color(121, 112, 40, 255) | |
blockHeight=50 | |
--ball | |
ballColour=color(232, 46, 206, 255) --CHANGED THIS TO STAND OUT MORE | |
ballSize=40 | |
ballPos=vec2(WIDTH/2,300) | |
objects={} | |
objects[1]={colour=color(169, 126, 78, 255),size=20,x=0,y=.2,points=100,speed=200} --Mercury | |
objects[2]={colour=color(178, 168, 108, 255),size=50,x=0,y=.25,points=60,speed=140} --Venus | |
objects[3]={colour=color(105, 219, 217, 255),size=50,x=0,y=.33,points=60,speed=120} --Earth | |
objects[4]={colour=color(220, 89, 41, 255),size=40,x=0,y=.4,points=-100,speed=100} --Mars | |
objects[5]={colour=color(218, 146, 105, 255),size=125,x=0,y=.55,points=30,speed=70} --Jupiter | |
objects[6]={colour=color(218, 215, 105, 255),size=90,x=0,y=.7,points=60,speed=50} --Saturn | |
objects[7]={colour=color(116, 145, 188, 255),size=70,x=0,y=.8,points=70,speed=30} --Uranus | |
objects[8]={colour=color(46, 58, 167, 255),size=70,x=0,y=.9,points=80,speed=20} --Neptune | |
--our orbit calculations | |
for i=1,#objects do | |
local P=objects[i] --just to make the code easier to read while we're doing this ****** | |
P.radius=P.y*HEIGHT - blockHeight --radius of orbit ****** | |
local circum=2*math.pi*P.radius --circumference of orbit ****** | |
P.da=P.speed/circum*360/60 --change in angle per redraw ****** | |
if math.random()<.5 then P.da=-P.da end --make it negative half the time, randomly ****** | |
P.a=0 --starting position ****** | |
P.x=controlX --****** | |
P.y=blockHeight+P.radius --****** | |
end | |
--physics bodies | |
controlBody=physics.body(CIRCLE,controlSize/2) | |
controlBody.type=STATIC | |
controlBody.x=controlX | |
controlBody.y=blockHeight | |
controlBody.linearVelocity=vec2(0,0) | |
controlBody.restitution=1.7 --INCEASED THIS because it is difficult to get past inner planets | |
table.insert(bodies,controlBody) | |
ballBody=physics.body(CIRCLE,ballSize/2) | |
ballBody.x=ballPos.x | |
ballBody.y=ballPos.y | |
ballBody.restitution=.8 | |
ballBody.gravityScale=0.5 --gravity as fraction of normal | |
table.insert(bodies,ballBody) | |
for i=1,#objects do | |
b=physics.body(CIRCLE,objects[i].size/2) -- size=diam, but we need radius | |
b.x=objects[i].x | |
b.y=objects[i].y | |
b.type=STATIC --planets don't move | |
b.info=i --identifier, so when we hit them, we can tell which one it was - see collide function at bottom | |
objects[i].body=b --so we can keep track of them | |
table.insert(bodies,b) --for cleanup at the end | |
end | |
walls={} --we'll keep the walls in a little table | |
walls[1]=physics.body(EDGE,vec2(0,0),vec2(0,HEIGHT)) --left | |
walls[2]=physics.body(EDGE,vec2(0,HEIGHT),vec2(WIDTH,HEIGHT)) --top | |
walls[3]=physics.body(EDGE,vec2(WIDTH,0),vec2(WIDTH,HEIGHT)) --right | |
for i=1,3 do | |
walls[i].restitution=0.9 | |
table.insert(bodies,walls[i]) | |
end | |
end | |
function draw() | |
background(0) | |
drawTable() | |
drawStats() --NEW | |
end | |
function drawTable() | |
pushStyle() | |
--objects first | |
for i=1,#objects do | |
local P=objects[i] --just to make the code easier to read while we're doing this ****** | |
--move them, reverse direction if they get to the edge | |
P.x=controlX+P.radius*math.sin(math.rad(P.a)) --****** | |
if P.x<P.size/2 or P.x>WIDTH-P.size/2 then P.da=-P.da end --****** | |
P.y=blockHeight+P.radius*math.cos(math.rad(P.a)) --****** | |
if P.y<blockHeight+P.size/2 then P.da=-P.da end --****** | |
--adjust position of physics objects to match | |
P.body.x=P.x | |
P.body.y=P.y | |
P.a=P.a+P.da --adjust angle ****** | |
--draw object | |
fill(P.colour) | |
ellipse(P.x,P.y,P.size) | |
end | |
--ball controller | |
fill(controlColour) | |
ellipse(controlBody.x,controlBody.y,controlSize) | |
--ball | |
fill(ballColour) | |
ellipse(ballBody.x,ballBody.y,ballSize) --note now we use the physics object position | |
popStyle() | |
--colour block along bottom | |
fill(blockColour) | |
rect(0,0,WIDTH,blockHeight) | |
end | |
function drawStats() --NEW for now it will just show the score | |
pushStyle() | |
fontSize(24) | |
font("AmericanTypewriter-Bold") | |
fill(229, 25, 25, 150) | |
textMode(CORNER) | |
text("Score: "..score,70,HEIGHT-50) | |
popStyle() | |
end | |
--trap touches on the controller | |
function touched(touch) | |
if touch.state==MOVING then | |
if touch.y>blockHeight and vec2(touch.x,touch.y):dist(vec2(controlBody.x,controlBody.y))<controlSize/2 then | |
controlBody.x=touch.x --that's all we need! | |
end | |
end | |
end | |
--trap collisions | |
function collide(contact) | |
--just trap the BEGAN state (when the collision starts) | |
if contact.state==BEGAN then | |
--the contact object has information about the two objects colliding stored in bodyA and bodyB | |
--remember we stored the number of each planet in the info property of its physics object | |
--so if bodyA is a planet, then bodyA.info will be the number of the planet (same for bodyB) | |
if contact.bodyA.info~=nil then | |
score=score+objects[contact.bodyA.info].points | |
elseif contact.bodyB.info~=nil then | |
score=score+objects[contact.bodyB.info].points | |
end | |
end | |
end | |
--destroy physics bodies before going to run a different tab | |
function cleanup() | |
for i,b in pairs(bodies) do | |
b:destroy() | |
end | |
bodies=nil | |
controlBody:destroy() | |
controlBody=nil | |
ballBody:destroy() | |
ballBody=nil | |
for i=1,#walls do | |
walls[i]:destroy() | |
end | |
walls=nil | |
end | |
--# Step_9 | |
if localise then _ENV = localise(9,"Add start/end screens") end --you can DELETE this if you copy this code to another project | |
--let's give it a proper start screen, and a win and lose screen, and high score | |
--we need a variable that tells us which "state" we are in | |
--it can be a good idea to define variables for each state because it makes the code clearer, like so | |
--here are the state definitions, we are always in one of these states | |
STARTUP=1 --program has just started, show instructions etc | |
PLAYING=2 | |
FINISHED=3 --show finished screen, offer to replay | |
--key changes below are commented and marked with ****** | |
displayMode(FULLSCREEN) | |
supportedOrientations(PORTRAIT) | |
function setup() | |
state=STARTUP --***** | |
end | |
function StartGame() ---start playing, set up ******* | |
state=PLAYING | |
score=0 | |
endMessage=nil | |
highScore=readLocalData("highScore") or 0 --get high score from where we stored it ****** | |
if bodies~=nil then cleanup() end --destroy everything if we've been playing already | |
bodies={} --table to hold physics bodies | |
--controller | |
controlColour=color(217, 181, 58, 255) | |
controlSize=150 | |
controlX=WIDTH/2 | |
blockColour=color(121, 112, 40, 255) | |
blockHeight=50 | |
--ball | |
ballColour=color(232, 46, 206, 255) | |
ballSize=40 | |
ballPos=vec2(WIDTH/2,300) | |
objects={} | |
objects[1]={colour=color(169, 126, 78, 255),size=20,x=0,y=.2,points=100,speed=200} --Mercury | |
objects[2]={colour=color(178, 168, 108, 255),size=50,x=0,y=.25,points=60,speed=140} --Venus | |
objects[3]={colour=color(105, 219, 217, 255),size=50,x=0,y=.33,points=60,speed=120} --Earth | |
objects[4]={colour=color(220, 89, 41, 255),size=40,x=0,y=.4,points=-100,speed=100} --Mars | |
objects[5]={colour=color(218, 146, 105, 255),size=125,x=0,y=.55,points=30,speed=70} --Jupiter | |
objects[6]={colour=color(218, 215, 105, 255),size=90,x=0,y=.7,points=60,speed=50} --Saturn | |
objects[7]={colour=color(116, 145, 188, 255),size=70,x=0,y=.8,points=70,speed=30} --Uranus | |
objects[8]={colour=color(46, 58, 167, 255),size=70,x=0,y=.9,points=80,speed=20} --Neptune | |
--our orbit calculations | |
for i=1,#objects do | |
local P=objects[i] --just to make the code easier to read while we're doing this | |
P.radius=P.y*HEIGHT - blockHeight --radius of orbit | |
local circum=2*math.pi*P.radius --circumference of orbit | |
P.da=P.speed/circum*360/60 --change in angle per redraw | |
if math.random()<.5 then P.da=-P.da end --make it negative half the time, randomly | |
P.a=0 --starting position | |
P.x=controlX | |
P.y=blockHeight+P.radius | |
end | |
--physics bodies | |
controlBody=physics.body(CIRCLE,controlSize/2) | |
controlBody.type=STATIC | |
controlBody.x=controlX | |
controlBody.y=blockHeight | |
controlBody.linearVelocity=vec2(0,0) | |
controlBody.restitution=1.7 --INCEASED THIS because it is difficult to get past inner planets | |
table.insert(bodies,controlBody) | |
ballBody=physics.body(CIRCLE,ballSize/2) | |
ballBody.x=ballPos.x | |
ballBody.y=ballPos.y | |
ballBody.restitution=.8 | |
ballBody.gravityScale=0.5 --gravity as fraction of normal | |
table.insert(bodies,ballBody) | |
for i=1,#objects do | |
b=physics.body(CIRCLE,objects[i].size/2) -- size=diam, but we need radius | |
b.x=objects[i].x | |
b.y=objects[i].y | |
b.type=STATIC --planets don't move | |
b.info=i --identifier, so when we hit them, we can tell which one it was - see collide function at bottom | |
objects[i].body=b --so we can keep track of them | |
table.insert(bodies,b) --for cleanup at the end | |
end | |
walls={} --we'll keep the walls in a little table | |
walls[1]=physics.body(EDGE,vec2(0,0),vec2(0,HEIGHT)) --left | |
walls[2]=physics.body(EDGE,vec2(0,HEIGHT),vec2(WIDTH,HEIGHT)) --top | |
walls[3]=physics.body(EDGE,vec2(WIDTH,0),vec2(WIDTH,HEIGHT)) --right | |
for i=1,3 do | |
walls[i].restitution=0.9 | |
table.insert(bodies,walls[i]) | |
end | |
end | |
function draw() | |
background(0) | |
if state==STARTUP then --show instructions when starting up ***** | |
showMessage("Move the central controller with your finger\n".. | |
"to keep the spaceship hitting planets\n(except for Mars, it counts against your score)") -- ******** | |
elseif state==FINISHED then --******* | |
showMessage(endMessage) --********* | |
else | |
drawTable() | |
drawStats() | |
end | |
end | |
--use consistent messaging style | |
function showMessage(m) --******* | |
pushStyle() | |
fill(255) | |
font("Copperplate-Bold") | |
fontSize(48) | |
text("Cosmic Pinball",WIDTH/2,HEIGHT*.75) | |
fontSize(24) | |
font("GillSans-Light") | |
textWrapWidth(500) | |
text(m,WIDTH/2,HEIGHT*.65) | |
sprite("Dropbox:Sun",WIDTH/2,HEIGHT*.4) | |
text("Touch the screen to start",WIDTH/2,HEIGHT*.2) | |
popStyle() | |
end | |
function drawTable() | |
pushStyle() | |
--objects first | |
for i=1,#objects do | |
local P=objects[i] --just to make the code easier to read while we're doing this | |
--move them, reverse direction if they get to the edge | |
P.x=controlX+P.radius*math.sin(math.rad(P.a)) | |
if P.x<P.size/2 or P.x>WIDTH-P.size/2 then P.da=-P.da end | |
P.y=blockHeight+P.radius*math.cos(math.rad(P.a)) | |
if P.y<blockHeight+P.size/2 then P.da=-P.da end | |
--adjust position of physics objects to match | |
P.body.x=P.x | |
P.body.y=P.y | |
P.a=P.a+P.da --adjust angle | |
--draw object | |
fill(P.colour) | |
ellipse(P.x,P.y,P.size) | |
end | |
--ball controller | |
fill(controlColour) | |
ellipse(controlBody.x,controlBody.y,controlSize) | |
--ball | |
fill(ballColour) | |
ellipse(ballBody.x,ballBody.y,ballSize) --note now we use the physics object position | |
if ballBody.y<blockHeight then -- END OF GAME! ******* | |
state=FINISHED | |
if score<500 then endMessage="Better luck next time" | |
elseif score<1000 then endMessage="Good effort" | |
elseif score<1500 then endMessage="Excellent!" | |
else endMessage="Out of this world!" end | |
endMessage=endMessage.."\n\nScore: "..score | |
if score>highScore then | |
saveLocalData("highScore",score) --save high score | |
endMessage=endMessage.."\n\nHigh score!" | |
end | |
end | |
popStyle() | |
--colour block along bottom | |
fill(blockColour) | |
rect(0,0,WIDTH,blockHeight) | |
end | |
function drawStats() --NEW for now it will just show the score | |
pushStyle() | |
fontSize(24) | |
font("AmericanTypewriter-Bold") | |
fill(229, 25, 25, 150) | |
textMode(CORNER) | |
text("Score: "..score,70,HEIGHT-50) | |
text("High Score: "..highScore,70,HEIGHT-80) ---***** | |
popStyle() | |
end | |
--trap touches on the controller | |
function touched(touch) | |
if touch.state==BEGAN and state~=PLAYING then | |
StartGame() | |
elseif touch.state==MOVING then | |
if state==PLAYING then --****** same code in here as before | |
if touch.y>blockHeight and | |
vec2(touch.x,touch.y):dist(vec2(controlBody.x,controlBody.y))<controlSize/2 then | |
controlBody.x=touch.x --that's all we need! | |
end | |
end | |
end | |
end | |
--trap collisions | |
function collide(contact) | |
--just trap the BEGAN state (when the collision starts) | |
if contact.state==BEGAN then | |
--the contact object has information about the two objects colliding stored in bodyA and bodyB | |
--remember we stored the number of each planet in the info property of its physics object | |
--so if bodyA is a planet, then bodyA.info will be the number of the planet (same for bodyB) | |
if contact.bodyA.info~=nil then | |
score=score+objects[contact.bodyA.info].points | |
elseif contact.bodyB.info~=nil then | |
score=score+objects[contact.bodyB.info].points | |
end | |
end | |
end | |
--destroy physics bodies before going to run a different tab | |
function cleanup() | |
for i,b in pairs(bodies) do | |
b:destroy() | |
end | |
bodies=nil | |
controlBody:destroy() | |
controlBody=nil | |
ballBody:destroy() | |
ballBody=nil | |
for i=1,#walls do | |
walls[i]:destroy() | |
end | |
walls=nil | |
end | |
--# Step10 | |
if localise then _ENV = localise(10,"Use planet images") end --you can DELETE this if you copy this code to another project | |
--Now we can work on the pretty stuff | |
--I've imported a picture for each planet, the controller (sun) and the ball (spaceship), plus background | |
--I also changed the bounciness of each planet - watch out for Mars! | |
STARTUP=1 --program has just started, show instructions etc | |
PLAYING=2 | |
FINISHED=3 --show finished screen, offer to replay | |
displayMode(FULLSCREEN) | |
supportedOrientations(PORTRAIT) | |
function setup() | |
state=STARTUP | |
end | |
function StartGame() ---start playing, set up | |
state=PLAYING | |
score=0 | |
endMessage=nil | |
highScore=readLocalData("highScore") or 0 --get high score from where we stored it | |
if bodies~=nil then cleanup() end --destroy everything if we've been playing already | |
backImg=readImage("Dropbox:galaxy") | |
bodies={} --table to hold physics bodies | |
--controller | |
controlColour=color(217, 181, 58, 255) | |
controlSize=150 | |
controlX=WIDTH/2 | |
controlImg=readImage("Dropbox:Sun") | |
blockColour=color(121, 112, 40, 255) | |
blockHeight=50 | |
--ball | |
ballColour=color(232, 46, 206, 255) --CHANGED THIS TO STAND OUT MORE | |
ballSize=40 | |
ballPos=vec2(WIDTH/2,300) | |
ballImg=readImage("Space Art:Red Ship") | |
--r = restitution (bounciness) | |
objects={} | |
objects[1]={colour=color(169, 126, 78, 255),size=20,x=0,y=.2,points=100,speed=200,img="Mercury",r=1} | |
objects[2]={colour=color(178, 168, 108, 255),size=50,x=0,y=.25,points=60,speed=140,img="Venus",r=.9} | |
objects[3]={colour=color(105, 219, 217, 255),size=50,x=0,y=.33,points=60,speed=120,img="Earth",r=.9} | |
objects[4]={colour=color(220, 89, 41, 255),size=40,x=0,y=.4,points=-100,speed=100,img="Mars",r=3} | |
objects[5]={colour=color(218, 146, 105, 255),size=125,x=0,y=.55,points=30,speed=70,img="Jupiter",r=.5} | |
objects[6]={colour=color(218, 215, 105, 255),size=90,x=0,y=.7,points=100,speed=50,img="Saturn",r=.8} | |
objects[7]={colour=color(116, 145, 188, 255),size=70,x=0,y=.8,points=150,speed=30,img="Uranus",r=.8} | |
objects[8]={colour=color(46, 58, 167, 255),size=70,x=0,y=.9,points=200,speed=20,img="Neptune",r=.8} | |
--our orbit calculations | |
for i=1,#objects do | |
local P=objects[i] --just to make the code easier to read while we're doing this | |
P.radius=P.y*HEIGHT - blockHeight --radius of orbit | |
local circum=2*math.pi*P.radius --circumference of orbit | |
P.da=P.speed/circum*360/60 --change in angle per redraw | |
if math.random()<.5 then P.da=-P.da end --make it negative half the time, randomly | |
P.a=0 --starting position | |
P.x=controlX | |
P.y=blockHeight+P.radius | |
--get images | |
objects[i].pic=readImage("Dropbox:"..objects[i].img) | |
end | |
--physics bodies | |
controlBody=physics.body(CIRCLE,controlSize/2) | |
controlBody.type=STATIC | |
controlBody.x=controlX | |
controlBody.y=blockHeight | |
controlBody.linearVelocity=vec2(0,0) | |
controlBody.restitution=1.7 --INCEASED THIS because it is difficult to get past inner planets | |
table.insert(bodies,controlBody) | |
ballBody=physics.body(CIRCLE,ballSize/2) | |
ballBody.x=ballPos.x | |
ballBody.y=ballPos.y | |
ballBody.restitution=.8 | |
ballBody.gravityScale=0.5 --gravity as fraction of normal | |
table.insert(bodies,ballBody) | |
for i=1,#objects do | |
b=physics.body(CIRCLE,objects[i].size/2) -- size=diam, but we need radius | |
b.x=objects[i].x | |
b.y=objects[i].y | |
b.type=STATIC --planets don't move | |
b.restitution=objects[i].r | |
b.info=i --identifier, so when we hit them, we can tell which one it was - see collide function at bottom | |
objects[i].body=b --so we can keep track of them | |
table.insert(bodies,b) --for cleanup at the end | |
end | |
walls={} --we'll keep the walls in a little table | |
walls[1]=physics.body(EDGE,vec2(0,0),vec2(0,HEIGHT)) --left | |
walls[2]=physics.body(EDGE,vec2(0,HEIGHT),vec2(WIDTH,HEIGHT)) --top | |
walls[3]=physics.body(EDGE,vec2(WIDTH,0),vec2(WIDTH,HEIGHT)) --right | |
for i=1,3 do | |
walls[i].restitution=0.9 | |
table.insert(bodies,walls[i]) | |
end | |
end | |
function draw() | |
background(0) | |
if state==STARTUP then --show instructions when starting up | |
showMessage("Move the central controller with your finger\n".. | |
"to keep the spaceship hitting planets\n(except for Mars, it counts against your score)") -- ******** | |
elseif state==FINISHED then | |
showMessage(endMessage) | |
else | |
sprite(backImg,WIDTH/2,HEIGHT/2,WIDTH,HEIGHT) | |
drawTable() | |
drawStats() | |
end | |
end | |
--use consistent messaging style | |
function showMessage(m) --******* | |
pushStyle() | |
fill(255) | |
font("Copperplate-Bold") | |
fontSize(48) | |
text("Cosmic Pinball",WIDTH/2,HEIGHT*.75) | |
fontSize(24) | |
font("GillSans-Light") | |
textWrapWidth(500) | |
text(m,WIDTH/2,HEIGHT*.65) | |
sprite("Dropbox:Sun",WIDTH/2,HEIGHT*.4) | |
text("Touch the screen to start",WIDTH/2,HEIGHT*.2) | |
popStyle() | |
end | |
function drawTable() | |
pushStyle() | |
--objects first | |
for i=1,#objects do | |
local P=objects[i] --just to make the code easier to read while we're doing this | |
--move them, reverse direction if they get to the edge | |
P.x=controlX+P.radius*math.sin(math.rad(P.a)) | |
if P.x<P.size/2 or P.x>WIDTH-P.size/2 then P.da=-P.da end | |
P.y=blockHeight+P.radius*math.cos(math.rad(P.a)) | |
if P.y<blockHeight+P.size/2 then P.da=-P.da end | |
--adjust position of physics objects to match | |
P.body.x=P.x | |
P.body.y=P.y | |
P.a=P.a+P.da --adjust angle | |
--draw object | |
fill(P.colour) | |
sprite(P.pic,P.x,P.y,P.size) | |
--ellipse(objects[i].x,objects[i].y,objects[i].size) | |
end | |
--ball controller | |
fill(controlColour) | |
sprite(controlImg,controlBody.x,controlBody.y,controlSize) | |
--ellipse(controlBody.x,controlBody.y,controlSize) | |
--ball | |
fill(ballColour) | |
sprite(ballImg,ballBody.x,ballBody.y,ballSize) | |
--ellipse(ballBody.x,ballBody.y,ballSize) --note now we use the physics object position | |
if ballBody.y<blockHeight then -- END OF GAME! ******* | |
state=FINISHED | |
if score<500 then endMessage="Better luck next time" | |
elseif score<1000 then endMessage="Good effort" | |
elseif score<1500 then endMessage="Excellent!" | |
else endMessage="Out of this world!" end | |
endMessage=endMessage.."\n\nScore: "..score | |
if score>highScore then | |
saveLocalData("highScore",score) --save high score | |
endMessage=endMessage.."\n\nHigh score!" | |
end | |
end | |
popStyle() | |
--colour block along bottom | |
fill(blockColour) | |
rect(0,0,WIDTH,blockHeight) | |
end | |
function drawStats() --NEW for now it will just show the score | |
pushStyle() | |
fontSize(24) | |
font("AmericanTypewriter-Bold") | |
fill(255) | |
textMode(CORNER) | |
text("Score: "..score,70,HEIGHT-50) | |
text("High Score: "..highScore,70,HEIGHT-80) | |
popStyle() | |
end | |
--trap touches on the controller | |
function touched(touch) | |
if touch.state==BEGAN and state~=PLAYING then | |
StartGame() | |
elseif touch.state==MOVING then | |
if state==PLAYING then --****** same code in here as before | |
if touch.y>blockHeight and | |
vec2(touch.x,touch.y):dist(vec2(controlBody.x,controlBody.y))<controlSize/2 then | |
controlBody.x=touch.x --that's all we need! | |
end | |
end | |
end | |
end | |
--trap collisions | |
function collide(contact) | |
--just trap the BEGAN state (when the collision starts) | |
if contact.state==BEGAN then | |
--the contact object has information about the two objects colliding stored in bodyA and bodyB | |
--remember we stored the number of each planet in the info property of its physics object | |
--so if bodyA is a planet, then bodyA.info will be the number of the planet (same for bodyB) | |
if contact.bodyA.info~=nil then | |
score=score+objects[contact.bodyA.info].points | |
elseif contact.bodyB.info~=nil then | |
score=score+objects[contact.bodyB.info].points | |
end | |
end | |
end | |
--destroy physics bodies before going to run a different tab | |
function cleanup() | |
for i,b in pairs(bodies) do | |
b:destroy() | |
end | |
bodies=nil | |
controlBody:destroy() | |
controlBody=nil | |
ballBody:destroy() | |
ballBody=nil | |
for i=1,#walls do | |
walls[i]:destroy() | |
end | |
walls=nil | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment