Last active
September 2, 2015 23:39
-
-
Save reefwing/4ba2e6023c640574ea1f to your computer and use it in GitHub Desktop.
Lunar Lander HD
This file contains hidden or 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
FPS = class() | |
-- Written by Jmv38 | |
-- | |
-- Version: 1.0 - Modified by Reefwing Software | |
-- - Text location bottom right. | |
-- this manages FPS and a progress bar | |
function FPS:init() | |
-- average fps | |
self.val = 60 | |
self.t0 = ElapsedTime | |
-- min fps | |
self.min = 60 | |
self.t1 = ElapsedTime | |
-- progress bar | |
self.frac = 0 | |
self:progressBarInit() | |
end | |
function FPS:draw() | |
local vShift = 0 | |
if self.progressBarActive then vShift = 30 end | |
-- update FPS value with some smoothing | |
local old = self.val | |
local frac = 0.1 | |
-- local t1 = os.clock() | |
local delta = DeltaTime | |
local new = 1/delta or old | |
if new<self.min then self.min=new; self.t1=ElapsedTime+1 end | |
if self.t1<ElapsedTime then self.min=60 end | |
new = old*(1-frac)+ new*frac | |
self.val = new | |
-- write the FPS on the screen | |
pushStyle() | |
fill(208, 208, 208, 255) | |
fontSize(20) | |
font("AmericanTypewriter") | |
rectMode(CENTER) | |
text(math.floor(new).." fps (> "..math.floor(self.min)..")",WIDTH - 70, HEIGHT - 15 - vShift) | |
popStyle() | |
-- draw progress bar | |
self:progressBarDraw() | |
end | |
function FPS:progressBarInit(txt) | |
self.frac = 0 | |
self.txt = txt or "running" | |
self.img = self:progressBarCalcInfoImg(self.txt,WIDTH*0.19,30,"top") | |
self.progressBarActive = false | |
end | |
function FPS:progressBarUpdate(frac) | |
self.frac = frac | |
if frac>0 and frac<1 then self.progressBarActive = true | |
else self.progressBarActive = false end | |
end | |
-- image to show job progress | |
function FPS:progressBarCalcInfoImg(txt,w,h,rot) | |
local w0,h0 | |
pushStyle() pushMatrix() | |
if rot=="left" or rot=="right" | |
then w0,h0 = h,w | |
else w0,h0 = w,h | |
end | |
local img0 = image(w0,h0) | |
setContext(img0) | |
font("AmericanTypewriter-Bold") | |
rectMode(CENTER) | |
textMode(CENTER) | |
strokeWidth(1) | |
background(255, 255, 255, 255) | |
fill(0, 0, 0, 255) | |
stroke(0, 0, 0, 255) | |
fontSize(20) | |
text(txt,w0/2,h0/2) | |
setContext() | |
local img = image(w,h) | |
setContext(img) | |
background(0, 0, 0, 255) | |
spriteMode(CENTER) | |
translate(w/2,h/2) | |
if rot=="left" then rotate(-90) end | |
if rot=="right" then rotate(90) end | |
sprite(img0,0,0) | |
setContext() | |
popStyle() popMatrix() | |
return img | |
end | |
function FPS:progressBarDraw() | |
if self.progressBarActive then | |
local img = self.img | |
pushStyle() | |
spriteMode(CORNER) | |
tint(128, 128, 128, 255) | |
sprite(img, 0, HEIGHT - img.height) | |
tint() | |
tint(255, 255, 255, 255) | |
clip(0,HEIGHT - img.height,self.frac*img.width,img.height) | |
sprite(img, 0, HEIGHT - img.height) | |
clip() | |
popStyle() | |
end | |
end |
This file contains hidden or 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
Level = class() | |
function Level:init(ground, landingZone, landerX, landerY, gravityX, gravityY) | |
-- Initialise Level Data | |
-- default is lunar gravityY = 1.622 m/s^2 cf Earth 9.78 m/s^2 | |
local w, h = WIDTH, 100 | |
local points = {vec2(0,h), vec2(0,0), vec2(w,0), vec2(w,h), | |
vec2(w-40, 80), vec2(w-60, 120), vec2(w-90, 220), | |
vec2(w-120, 180), vec2(w-160, 140), vec2(w-190, 140), | |
vec2(w-210, 190), vec2(w-230, 260), vec2(w-300, 320), | |
vec2(w-320, 360), vec2(w-350, 400), vec2(w-370, 360), | |
vec2(w-400, 360), vec2(w-420, 380), vec2(w-450, 200), | |
vec2(w-470, 180), vec2(w-480, 150), vec2(w-540, 150), | |
vec2(w-580, 200), vec2(w-600, 210), vec2(w-620, 250), | |
vec2(w-640, 220), vec2(w-660, 120), vec2(w-680, 140), | |
vec2(w-705, 185), vec2(w-730, 207), vec2(w-750, 257), | |
vec2(w-780, 280), vec2(w-806, 310), vec2(w-823, 357), | |
vec2(w-840, 379), vec2(w-870, 421), vec2(w-890, 376), | |
vec2(w-905, 350), vec2(w-920, 321), vec2(w-940, 330), | |
vec2(w-950, 315), vec2(w-965, 320)} | |
self.ground = ground or points | |
self.landingZone = landingZone or nil | |
self.landerX = landerX or 150 | |
self.landerY = landerY or (HEIGHT - 22) | |
self.gravityX = gravityX or 0 | |
self.gravityY = gravityY or -1.622 | |
end |
This file contains hidden or 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
-- LunarLander HD | |
-- Reefwing Software (reefwing.com.au) | |
-- | |
-- Version 1.0 | |
-- | |
-- A reprisal of the classic Lunar Lander arcade game, written entirely on the iPad. | |
-- | |
-- Version Control: | |
-- - v1.0 Original release | |
-- | |
-- This demonstration game was built as part of a series of Tutorials on Codea | |
-- and programming with Lua for the iPad. These tutorials may be found at | |
-- www.codeatuts.blogspot.com.au | |
-- | |
-- Define display mode and supported iPad orientations. | |
-- This way we don't forget to handle orientation changes if required. | |
supportedOrientations(LANDSCAPE_ANY) | |
-- Note that orientationChanged(newOrientation) gets called before setup() | |
-- in the runtime. Consequently, you can't use globals defined in setup() in this | |
-- function until after it has run. The boolean setupHasRun keeps track of that. | |
-- | |
-- If mute = true, no sound will be played. | |
local setupHasRun = false | |
local mute = false | |
local abortBurn = false | |
local hardLanding = false | |
-- Main tab locals init | |
local fuel = 750 | |
local fuelLost = 0 | |
local score = 0 | |
local maxLevels = 2 | |
local gameInProgress = false | |
local gameTime = 0 | |
local blinkTime = 0 | |
local messageTime = 0 | |
local explosionTime = 0 | |
local abortBurnTime = 0 | |
local msgSeed = math.random(0, 2) | |
function setup() | |
displayMode(FULLSCREEN) | |
local version = 1.0 | |
saveProjectInfo("Description", "Lunar Lander v"..version) | |
saveProjectInfo("Author", "Reefwing Software") | |
saveProjectInfo("Date", "28th January 2013") | |
saveProjectInfo("Version", version) | |
saveProjectInfo("Comments", "Original Release.") | |
print("Lunar Lander HD v"..version.."\n") | |
-- Frames Per Second (FPS) Class Initialisation | |
fps = FPS() | |
-- Physics Engine Initialisation | |
physicsDraw = PhysicsDebugDraw() | |
-- Define the Game State Machine | |
stateTitle = 0 | |
statePlaying = 1 | |
stateWon = 2 | |
stateLost = 3 | |
gameState = stateTitle | |
-- Init Game Controls | |
leftButton = Button("Left") | |
leftButton.pos = vec2(45, 10) | |
rightButton = Button("Right") | |
rightButton.pos = vec2(195, 10) | |
thrustButton = Button("Thrust") | |
thrustButton.pos = vec2(750, 10) | |
abortButton = Button("Abort") | |
abortButton.pos = vec2(900, 10) | |
abortButton.action = function() abortButtonTapped() end | |
-- Load the demo level (0) for playing as a title screen | |
currentLevelNumber = 0 | |
loadLevel() | |
-- Setup Complete | |
setupHasRun = true | |
end | |
function resetLevel() | |
-- Play lunar module appearance sound. | |
if not mute then | |
sound(SOUND_EXPLODE, 17391) | |
end | |
-- Reset level variables | |
gameTime = 0 | |
abortBurnTime = 0 | |
explosionTime = 0 | |
msgSeed = math.random(0, 2) | |
-- Clean up physics objects before we recreate them. | |
cleanup() | |
ground = nil | |
newGround = nil | |
lunarModule = nil | |
destroyExplosionObjects() | |
-- Reset button states to prevent them jamming on when state changes occur. | |
leftButton.state = "normal" | |
rightButton.state = "normal" | |
thrustButton.state = "normal" | |
abortButton.state = "normal" | |
-- Assign gravity for this level, default is lunar gravity. | |
physics.gravity(level.gravityX, level.gravityY) | |
-- Create the Moon surface and Lunar Lander Module. | |
-- Physic body creation functions are found in the Physics Tab. | |
ground = createGround(level.ground) | |
lunarModule = createCircle(level.landerX, level.landerY, 20) | |
local launchForce = math.random(45, 100) | |
lunarModule:applyForce(vec2(launchForce, 0)) | |
physicsDraw.landerDestroyed = false | |
physicsDraw.drawLanderFlame = false | |
end | |
function loadLevel() | |
-- Load Current Level | |
if (currentLevelNumber == 0) then | |
-- Default is the demo level 0. | |
level = Level() | |
fuel = 750 | |
elseif (currentLevelNumber == 1) then | |
local w, h = WIDTH, 150 | |
local gnd = {vec2(0,h), vec2(0,0), vec2(w,0), vec2(w,h), | |
vec2(w/2, h), vec2(w/2-20, 170), vec2(w/2-40, 220), | |
vec2(w/2-65, 185), vec2(w/2-80, 200), vec2(w/2-90, 180), | |
vec2(w/2-100, h)} | |
local LZ = {vec2(0,0), vec2(w,0)} | |
level = Level(gnd, LZ) | |
fuel = 150 | |
elseif (currentLevelNumber == 2) then | |
local w, h = WIDTH, 150 | |
local gnd = {vec2(0,h), vec2(0,0), vec2(w,0), vec2(w,h), | |
vec2(w-40, 180), vec2(w-60, 220), vec2(w-90, 260), | |
vec2(w-120, 180), vec2(w-160, 170), vec2(w-180,h), | |
vec2(w-350,h), vec2(w-370, 190), vec2(w-390, 180), | |
vec2(w-410, 210), vec2(w-430, h)} | |
local LZ = {vec2(0,0), vec2(w-180,0)} | |
level = Level(gnd, LZ) | |
fuel = 150 | |
else | |
-- Unknown level number - use the default title screen level. | |
level = Level() | |
end | |
-- Reset & Build Current Level. | |
resetLevel() | |
end | |
-- Rendering | |
function drawHUD(score, time, fuel, altitude, dX, dY) | |
local score = score or "0000" | |
local time = time or 0.00 | |
local fuel = fuel or "0000" | |
local altitude = altitude or 0 | |
local dX = dX or 0 | |
local dY = dY or 0 | |
local h = HEIGHT | |
pushStyle() | |
fill(255) | |
font("AmericanTypewriter-Light") | |
if gameState == stateTitle then | |
textMode(CENTER) | |
fontSize(24) | |
text("750 FUEL UNITS PER COIN", WIDTH/2, 450) | |
if blinkTime > 1.0 then | |
text("INSERT COINS", WIDTH/2, 400) | |
if blinkTime > 2.0 then | |
blinkTime = 0.0 | |
end | |
end | |
end | |
textAlign(LEFT) | |
textMode(CORNER) | |
fontSize(32) | |
text("SCORE", 150, h - 100) | |
text("TIME", 150, h - 150) | |
text("FUEL", 150, h - 200) | |
text("ALTITUDE", 650, h - 100) | |
text("DELTA vX", 650, h - 150) | |
text("DELTA vY", 650, h - 200) | |
font("DB LCD Temp") | |
fontSize(28) | |
text(score, 300, h - 98) | |
local dTime = string.format("%.2f", time) | |
text(dTime, 300, h - 148) | |
text(fuel, 300, h - 198) | |
local dAltitude = string.format("%.2f", altitude) | |
text(dAltitude, 850, h - 98) | |
local dDX = string.format("%.2f", math.abs(dX)) | |
text(dDX, 850, h - 148) | |
local dDY = string.format("%.2f", math.abs(dY)) | |
text(dDY, 850, h - 198) | |
-- draw the delta Vx and Vy arrows | |
stroke(255) | |
strokeWidth(2) | |
if dX > 0 then | |
line(980, h - 130, 1000, h - 130) | |
line(1000, h - 130, 995, h - 125) | |
line(1000, h - 130, 995, h - 135) | |
elseif dX < 0 then | |
line(980, h - 130, 1000, h - 130) | |
line(980, h - 130, 985, h - 125) | |
line(980, h - 130, 985, h - 135) | |
end | |
if dY > 0 then | |
line(990, h - 190, 990, h - 170) | |
line(990, h - 170, 985, h - 175) | |
line(990, h - 170, 995, h - 175) | |
elseif dY < 0 then | |
line(990, h - 190, 990, h - 170) | |
line(990, h - 190, 985, h - 185) | |
line(990, h - 190, 995, h - 185) | |
end | |
popStyle() | |
end | |
function draw() | |
-- This sets a black background color | |
background(0, 0, 0) | |
-- Calculate and display FPS. | |
fps:draw() | |
-- Lunar module explosion animation completion handler. | |
if physicsDraw.landerDestroyed then | |
explosionTime = explosionTime + DeltaTime | |
if explosionTime > 5.0 then | |
if fuel == 0 then | |
currentLevelNumber = 0 | |
gameState = stateTitle | |
loadLevel() | |
else | |
resetLevel() | |
end | |
end | |
end | |
-- Game State dependent rendering. | |
if gameState == stateTitle then | |
blinkTime = blinkTime + DeltaTime | |
drawHUD() | |
elseif gameState == statePlaying then | |
gameTime = gameTime + DeltaTime | |
leftButton:draw() | |
rightButton:draw() | |
thrustButton:draw() | |
abortButton:draw() | |
if fuel <= 50 and fuel > 0 then | |
pushStyle() | |
fill(0, 255, 255, 255) | |
text("LOW ON FUEL", WIDTH/2, 50) | |
popStyle() | |
elseif fuel <= 0 then | |
pushStyle() | |
fill(0, 255, 255, 255) | |
text("OUT OF FUEL", WIDTH/2, 50) | |
popStyle() | |
end | |
if leftButton.state == "pressing" then | |
lunarModule.angle = lunarModule.angle + 2 | |
elseif rightButton.state == "pressing" then | |
lunarModule.angle = lunarModule.angle - 2 | |
end | |
if thrustButton.state == "pressing" and fuel > 0 then | |
fuel = math.max(0, fuel - 1) | |
physicsDraw.drawLanderFlame = true | |
lunarModule:applyForce(forceVector()) | |
else | |
physicsDraw.drawLanderFlame = false | |
end | |
if abortBurn then | |
abortBurnTime = abortBurnTime + DeltaTime | |
if abortBurnTime < 1.0 and fuel > 0 then | |
fuel = math.max(0, fuel - 2) | |
physicsDraw.drawLanderFlame = true | |
lunarModule.angle = 0 | |
lunarModule:applyForce(forceVector()) | |
else | |
physicsDraw.drawLanderFlame = false | |
abortBurn = false | |
abortBurnTime = 0 | |
end | |
end | |
drawHUD(score, gameTime, fuel, lunarModule.y, | |
lunarModule.linearVelocity.x, lunarModule.linearVelocity.y) | |
elseif gameState == stateWon then | |
messageTime = messageTime + DeltaTime | |
if messageTime < 5.0 then | |
pushStyle() | |
fill(0, 255, 0, 255) | |
if hardLanding then | |
text("YOU LANDED HARD", WIDTH/2, 450) | |
-- Generate Random Hard Landing Message | |
local str = "COMMUNICATION SYSTEM DESTROYED" | |
if msgSeed == 1 then | |
str = "YOUR TRIP IS ONE WAY" | |
elseif msgSeed == 2 then | |
str = "YOU BREAK IT, YOU BOUGHT IT" | |
end | |
text(str, WIDTH/2, 300) | |
text("15 POINTS", WIDTH/2, 200) | |
else | |
text("CONGRATULATIONS!", WIDTH/2, 450) | |
text("THAT WAS A GREAT LANDING", WIDTH/2, 300) | |
text("50 POINTS", WIDTH/2, 200) | |
end | |
popStyle() | |
else | |
messageTime = 0 | |
print("Current Level Number at start: " .. currentLevelNumber) | |
if currentLevelNumber < maxLevels then | |
currentLevelNumber = currentLevelNumber + 1 | |
gameState = statePlaying | |
else | |
currentLevelNumber = 0 | |
gameState = stateTitle | |
end | |
print("Current Level Number at finish: " .. currentLevelNumber) | |
loadLevel() | |
end | |
elseif gameState == stateLost then | |
messageTime = messageTime + DeltaTime | |
if messageTime < 5.0 then | |
pushStyle() | |
fill(0, 255, 255, 255) | |
text("AUXILARY FUEL TANKS DESTROYED", WIDTH/2, 450) | |
text(fuelLost .. " FUEL UNITS LOST", WIDTH/2, 400) | |
-- Generate Random Death Message | |
local str = "THERE WERE NO SURVIVORS" | |
if msgSeed == 1 then | |
str = "YOU CREATED A 2 MILE CRATER" | |
elseif msgSeed == 2 then | |
str = "YOU JUST DESTROYED A 100 MEGABUCK LANDER" | |
end | |
text(str, WIDTH/2, 300) | |
text("5 POINTS", WIDTH/2, 200) | |
popStyle() | |
else | |
messageTime = 0 | |
if fuel > 0 then -- You get another go. | |
gameState = statePlaying | |
else | |
gameState = stateTitle | |
end | |
end | |
end | |
-- Check whether we need to scroll the background. | |
-- We start scrolling when the lunar module location is | |
-- approaching the left or right edges of the screen. | |
if lunarModule.x > 750 then | |
if newGround == nil then | |
newGround = createGround(ground.points) | |
newGround.x = WIDTH + 1 | |
end | |
ground.x = ground.x - 2 | |
newGround.x = newGround.x - 2 | |
lunarModule.x = math.min(850, lunarModule.x - 2) | |
elseif lunarModule.x < 150 then | |
ground.x = ground.x + 2 | |
lunarModule.x = lunarModule.x + 2 | |
end | |
-- Render Physics Objects unless safely landed. | |
if gameState ~= stateWon then | |
physicsDraw:draw() | |
end | |
end | |
-- Calculate direction of thrust and return vector | |
function forceVector() | |
if abortBurn then | |
return vec2(0, 3) -- apply force straight up | |
else | |
local theta = math.rad(lunarModule.angle) -- convert angle from degrees to radians | |
local magnitude = 2 -- magnitude applied to unit vector | |
local x = -1 * magnitude * math.sin(theta) | |
local y = magnitude * math.cos(theta) | |
return vec2(x, y) -- return force vector | |
end | |
end | |
-- Explosion Handlers | |
function destroyExplosionObjects() | |
box = nil | |
randPoly1 = nil | |
randPoly2 = nil | |
randPoly3 = nil | |
randPoly4 = nil | |
randPoly5 = nil | |
randPoly6 = nil | |
end | |
function createExplosionAt(x, y) | |
if box == nil then | |
box = createBox(x, y, 30, 14) | |
box:applyForce(vec2(50,50)) | |
randPoly1 = createRandPoly(x, y) | |
randPoly1:applyForce(vec2(50,50)) | |
randPoly2 = createRandPoly(x, y) | |
randPoly2:applyForce(vec2(50,50)) | |
randPoly3 = createRandPoly(x, y) | |
randPoly3:applyForce(vec2(50,50)) | |
randPoly4 = createRandPoly(x, y) | |
randPoly4:applyForce(vec2(-50,50)) | |
randPoly5 = createRandPoly(x, y) | |
randPoly5:applyForce(vec2(-50,50)) | |
randPoly6 = createRandPoly(x, y) | |
randPoly6:applyForce(vec2(-50,50)) | |
end | |
end | |
-- Handle Collision Detection | |
function crash(cX, cY) | |
-- Check if landed on a safe (flat) landing zone | |
-- Check if landing velocity is sufficiently low | |
if math.abs(lunarModule.linearVelocity.x) > 8 or | |
math.abs(lunarModule.linearVelocity.y) > 8 then | |
score = score + 5 | |
return true | |
elseif math.abs(lunarModule.linearVelocity.x) > 4 or | |
math.abs(lunarModule.linearVelocity.y) > 4 then | |
score = score + 15 | |
hardLanding = true | |
return false | |
else | |
score = score + 50 | |
fuel = fuel + 50 | |
hardLanding = false | |
return false | |
end | |
end | |
function collide(contact) | |
if not physicsDraw.landerDestroyed then | |
if crash(contact.position.x, contact.position.y) then | |
if not mute then | |
sound(SOUND_EXPLODE, 17403) | |
end | |
createExplosionAt(lunarModule.x, lunarModule.y) | |
physicsDraw.landerDestroyed = true | |
if gameState == statePlaying then | |
gameState = stateLost | |
fuelLost = math.random(100, 250) | |
if fuelLost > fuel then | |
fuelLost = fuel | |
fuel = 0 | |
else | |
fuel = fuel - fuelLost | |
end | |
end | |
else | |
gameState = stateWon | |
end | |
end | |
end | |
function touched(touch) | |
if touch.state == BEGAN then | |
if gameState == stateTitle then | |
gameState = statePlaying | |
currentLevelNumber = 1 | |
loadLevel() | |
elseif gameState == statePlaying then | |
thrustButton:touched(touch) | |
abortButton:touched(touch) | |
leftButton:touched(touch) | |
rightButton:touched(touch) | |
if abortButton.state == "pressing" then | |
abortBurn = true | |
end | |
end | |
elseif touch.state == MOVING then | |
print("touch moving...") | |
elseif touch.state == ENDED then | |
if gameState == statePlaying then | |
leftButton:touched(touch) | |
rightButton:touched(touch) | |
thrustButton:touched(touch) | |
abortButton:touched(touch) | |
end | |
end | |
end | |
-- Game Button Call Back Functions | |
function abortButtonTapped() | |
abortBurn = true | |
end | |
-- Math Utilities | |
function math.round(value) | |
-- math.round function courtesy of Vega. | |
return math.floor(value + 0.5) | |
end | |
This file contains hidden or 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
-- Physics functions courtesy of TLL Physics Lab example | |
-- | |
-- Modified by Reefwing Software | |
-- | |
-- Version 1.0: - createPoly & createGround functions added | |
-- | |
function createCircle(x,y,r) | |
local circle = physics.body(CIRCLE, r) | |
-- enable smooth motion | |
circle.interpolate = true | |
circle.x = x | |
circle.y = y | |
circle.restitution = 0.25 | |
circle.sleepingAllowed = false | |
circle.info = "lunarModule" | |
physicsDraw:addBody(circle) | |
return circle | |
end | |
function createBox(x,y,w,h) | |
-- polygons are defined by a series of points in counter-clockwise order | |
local box = physics.body(POLYGON, vec2(-w/2,h/2), vec2(-w/2,-h/2), | |
vec2(w/2,-h/2), vec2(w/2,h/2)) | |
box.interpolate = true | |
box.x = x | |
box.y = y | |
box.restitutions = 0.25 | |
box.sleepingAllowed = false | |
box.info = "poly" | |
physicsDraw:addBody(box) | |
return box | |
end | |
function createGround(points) | |
local ground = physics.body(POLYGON, unpack(points)) | |
ground.type = STATIC | |
ground.info = "ground" | |
physicsDraw:addBody(ground) | |
return ground | |
end | |
function createPoly(x, y, points) | |
local poly = physics.body(POLYGON, unpack(points)) | |
poly.x = x | |
poly.y = y | |
poly.sleepingAllowed = false | |
poly.restitution = 0.25 | |
poly.info = "poly" | |
physicsDraw:addBody(poly) | |
return poly | |
end | |
function createRandPoly(x,y) | |
local count = math.random(3,10) | |
local r = math.random(1,5) | |
local a = 0 | |
local d = 2 * math.pi / count | |
local points = {} | |
for i = 1,count do | |
local v = vec2(r,0):rotate(a) + vec2(math.random(-10,10), math.random(-10,10)) | |
a = a + d | |
table.insert(points, v) | |
end | |
local poly = physics.body(POLYGON, unpack(points)) | |
poly.x = x | |
poly.y = y | |
poly.sleepingAllowed = false | |
poly.restitution = 0.25 | |
poly.info = "poly" | |
physicsDraw:addBody(poly) | |
return poly | |
end | |
function cleanup() | |
clearOutput() | |
physicsDraw:clear() | |
end |
This file contains hidden or 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
PhysicsDebugDraw = class() | |
-- Modified from physics lab example project | |
function PhysicsDebugDraw:init() | |
self.bodies = {} | |
self.joints = {} | |
self.touchMap = {} | |
self.contacts = {} | |
self.landerDestroyed = false | |
self.drawLanderFlame = false | |
end | |
function PhysicsDebugDraw:addBody(body) | |
table.insert(self.bodies,body) | |
end | |
function PhysicsDebugDraw:addJoint(joint) | |
table.insert(self.joints,joint) | |
end | |
function PhysicsDebugDraw:deleteBody(info) | |
for i,body in ipairs(self.bodies) do | |
if body.info == info then | |
body:destroy() | |
end | |
end | |
end | |
function PhysicsDebugDraw:deleteAllBodiesExceptGround() | |
for i,body in ipairs(self.bodies) do | |
if body.info ~= "ground" then | |
body:destroy() | |
end | |
end | |
end | |
function PhysicsDebugDraw:clear() | |
-- deactivate all bodies | |
for i,body in ipairs(self.bodies) do | |
body:destroy() | |
end | |
for i,joint in ipairs(self.joints) do | |
joint:destroy() | |
end | |
self.bodies = {} | |
self.joints = {} | |
self.contacts = {} | |
self.touchMap = {} | |
end | |
function PhysicsDebugDraw:draw() | |
pushStyle() | |
smooth() | |
strokeWidth(5) | |
stroke(128,0,128) | |
local gain = 2.0 | |
local damp = 0.5 | |
for k,v in pairs(self.touchMap) do | |
local worldAnchor = v.body:getWorldPoint(v.anchor) | |
local touchPoint = v.tp | |
local diff = touchPoint - worldAnchor | |
local vel = v.body:getLinearVelocityFromWorldPoint(worldAnchor) | |
v.body:applyForce( (1/1) * diff * gain - vel * damp, worldAnchor) | |
line(touchPoint.x, touchPoint.y, worldAnchor.x, worldAnchor.y) | |
end | |
stroke(0,255,0,255) | |
strokeWidth(5) | |
for k,joint in pairs(self.joints) do | |
local a = joint.anchorA | |
local b = joint.anchorB | |
line(a.x,a.y,b.x,b.y) | |
end | |
stroke(255,255,255,255) | |
noFill() | |
for i,body in ipairs(self.bodies) do | |
pushMatrix() | |
translate(body.x, body.y) | |
rotate(body.angle) | |
if body.type == STATIC then | |
stroke(255,255,255,255) | |
elseif body.type == DYNAMIC then | |
stroke(150,255,150,255) | |
elseif body.type == KINEMATIC then | |
stroke(150,150,255,255) | |
end | |
if body.shapeType == POLYGON then | |
strokeWidth(2.0) | |
local points = body.points | |
for j = 1,#points do | |
a = points[j] | |
b = points[(j % #points)+1] | |
if body.info == "poly" or (body.info == "ground" and j > 3) then | |
line(a.x, a.y, b.x, b.y) | |
end | |
end | |
elseif body.shapeType == CHAIN or body.shapeType == EDGE then | |
strokeWidth(2.0) | |
local points = body.points | |
for j = 1,#points-1 do | |
a = points[j] | |
b = points[j+1] | |
line(a.x, a.y, b.x, b.y) | |
end | |
elseif body.shapeType == CIRCLE and not self.landerDestroyed then | |
-- draw Lunar Module | |
strokeWidth(2.0) | |
-- draw body | |
line(-5, 4, -10, 10) | |
line(-10, 10, -10, 15) | |
line(-10, 15, -5, 20) | |
line(-5, 20, 5, 20) | |
line(5, 20, 10, 15) | |
line(10, 15, 10, 10) | |
line(10, 10, 5, 4) | |
rect(-15, -10, 30, 14) | |
-- draw engine | |
line(0, -10, -3, -15) | |
line(-3, -15, 3, -15) | |
line(3, -15, 0, -10) | |
-- draw lander leg struts | |
line(-15, -5, -18, -14) | |
line(-10, -10, -18, -14) | |
line(15, -5, 18, -14) | |
line(10, -10, 18, -14) | |
-- draw lander legs | |
line(-18, -14, -18, -20) | |
line(-20, -20, -16, -20) | |
line(18, -14, 18, -20) | |
line(16, -20, 20, -20) | |
if self.drawLanderFlame then | |
pushStyle() | |
stroke(255, 0, 0) | |
strokeWidth(3) | |
line(0, -20, 0, -30) | |
strokeWidth(2) | |
line(-3, -22, -3, -28) | |
line(3, -22, 3, -28) | |
popStyle() | |
end | |
-- DEBUG: Show collision envelope | |
-- ellipse(0,0,body.radius*2) | |
end | |
popMatrix() | |
end | |
stroke(255, 0, 0, 255) | |
fill(255, 0, 0, 255) | |
-- Show contact points. | |
-- Not required for Lunar Lander. | |
-- for k,v in pairs(self.contacts) do | |
-- for m,n in ipairs(v.points) do | |
-- ellipse(n.x, n.y, 10, 10) | |
-- end | |
-- end | |
popStyle() | |
end | |
function PhysicsDebugDraw:touched(touch) | |
local touchPoint = vec2(touch.x, touch.y) | |
if touch.state == BEGAN then | |
for i,body in ipairs(self.bodies) do | |
if body.type == DYNAMIC and body:testPoint(touchPoint) then | |
self.touchMap[touch.id] = {tp = touchPoint, body = body, anchor = body:getLocalPoint(touchPoint)} | |
return true | |
end | |
end | |
elseif touch.state == MOVING and self.touchMap[touch.id] then | |
self.touchMap[touch.id].tp = touchPoint | |
return true | |
elseif touch.state == ENDED and self.touchMap[touch.id] then | |
self.touchMap[touch.id] = nil | |
return true; | |
end | |
return false | |
end | |
function PhysicsDebugDraw:collide(contact) | |
if contact.state == BEGAN then | |
self.contacts[contact.id] = contact | |
if not mute then | |
sound(SOUND_HIT, 2643) | |
end | |
elseif contact.state == MOVING then | |
self.contacts[contact.id] = contact | |
elseif contact.state == ENDED then | |
self.contacts[contact.id] = nil | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment