Skip to content

Instantly share code, notes, and snippets.

@reefwing
Last active September 2, 2015 23:39
Show Gist options
  • Save reefwing/4ba2e6023c640574ea1f to your computer and use it in GitHub Desktop.
Save reefwing/4ba2e6023c640574ea1f to your computer and use it in GitHub Desktop.
Lunar Lander HD
Button = class()
function Button: init(displayName)
-- displayName: Is the text displayed on your button. The button will scale up and down to fit the
-- text.
-- pos: Defines the x and y - coordinates of the button using a vector.
-- size: Is a vector which contains the width and height of the button, which is set by the
-- display name text, and is used to determine if a button has been hit.
-- action: Is the function that you want called when the button is tapped.
-- color: Is the color of the button fill.
self.displayName = displayName
self.pos = vec2(0,0)
self.size = vec2(80,80)
self.action = nil
self.visible = true
self.state = "normal"
self.colour = color(236, 176, 56, 255) -- Orange colour from original arcade machine
end
function Button:draw()
if self.visible then
pushStyle()
fill(self.colour)
rect(self.pos.x, self.pos.y + 20, self.size.x, self.size.y)
stroke(0)
strokeWidth(2)
ellipseMode(CENTER)
ellipse(self.pos.x + self.size.x/2, self.pos.y + 20 + self.size.y/2, self.size.x * 0.75)
ellipse(self.pos.x + self.size.x/2, self.pos.y + 20 + self.size.y/2, self.size.x * 0.65)
ellipse(self.pos.x + self.size.x/2, self.pos.y + 20 + self.size.y/2, self.size.x * 0.55)
ellipse(self.pos.x + self.size.x/2, self.pos.y + 20 + self.size.y/2, self.size.x * 0.45)
ellipse(self.pos.x + self.size.x/2, self.pos.y + 20 + self.size.y/2, self.size.x * 0.35)
ellipse(self.pos.x + self.size.x/2, self.pos.y + 20 + self.size.y/2, self.size.x * 0.25)
fill(0)
ellipse(self.pos.x + self.size.x/2, self.pos.y + 20 + self.size.y/2, self.size.x * 0.15)
textMode(CENTER)
font("AmericanTypewriter")
fontSize(22)
fill(255)
text(self.displayName, self.pos.x + self.size.x/2, self.pos.y)
popStyle()
end
end
function Button:touched(touch)
if self.visible then
if touch.x >= self.pos.x and touch.x <= self.pos.x + self.size.x
and touch.y >= self.pos.y and touch.y <= self.pos.y + self.size.y then
if touch.state == BEGAN then
self.state = "pressing"
end
if touch.state == ENDED then
if self.state == "pressing" then
self.state = "normal"
end
if self.action then
self.action()
end
end
else
self.state = "normal"
end
end
end
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
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
-- 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
-- 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
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