Skip to content

Instantly share code, notes, and snippets.

Created July 24, 2012 01:17
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save anonymous/3167310 to your computer and use it in GitHub Desktop.
Save anonymous/3167310 to your computer and use it in GitHub Desktop.
Slammer Fall Codea Example
--# Emitter
Emitter = class()
function Emitter:init(args)
self.particleMesh = mesh()
self.particleMesh.texture = args.tex
self.minLife = args.minLife or 1
self.maxLife = args.maxLife or 1
self.spread = args.spread or 360
self.angle = args.angle or 0
self.minSpeed = args.minSpeed or 10
self.maxSpeed = args.maxSpeed or 50
self.minSize = args.minSize or 10
self.maxSize = args.maxSize or 15
self.growth = args.growth or 1
self.startColor = args.startColor or color(255, 255, 255, 255)
self.endColor = args.endColor or color(0, 0, 0, 0)
self.streak = args.streak or false
self.streakMult = args.streakMult or 2
self.accel = args.accel or vec2(0,0)
self.rAccel = args.rAccel or vec2(0,0)
self.particles = {}
self.pCount = 0
for i = 1,100 do
table.insert(self.particles, Particle())
end
end
function Emitter:emit(pos,count)
for i = 1,#self.particles do
local p = self.particles[i]
if p.dead then
self.pCount = math.max(i, self.pCount)
p.dead = false
p.pos = pos
p.life = math.random(self.minLife, self.maxLife)
p.size = math.random(self.minSize, self.maxSize)
p.maxLife = p.life
p.vel = vec2(0, math.random(self.minSpeed, self.maxSpeed))
p.vel = p.vel:rotate(math.rad(self.angle + math.random(-self.spread/2, self.spread/2)))
count = count - 1
if count == 0 then return end
end
end
end
function Emitter:draw()
-- update
self.particleMesh:clear()
for i = 1,self.pCount do
local p = self.particles[i]
if not p.dead then
p.prevPos = p.pos
p.pos = p.pos + p.vel * DeltaTime
p.vel = p.vel + (self.accel + vec2(math.random(-self.rAccel.x, self.rAccel.x), math.random(-self.rAccel.y, self.rAccel.y))) * DeltaTime
p.life = math.max(0, p.life - DeltaTime)
p.size = p.size + DeltaTime * self.growth
if p.life == 0 then p.dead = true end
local interp = p.life / p.maxLife
p.col.r = interp * self.startColor.r + (1-interp) * self.endColor.r
p.col.g = interp * self.startColor.g + (1-interp) * self.endColor.g
p.col.b = interp * self.startColor.b + (1-interp) * self.endColor.b
p.col.a = interp * self.startColor.a + (1-interp) * self.endColor.a
local ind = self.particleMesh:addRect(p.pos.x, p.pos.y, p.size, p.size)
self.particleMesh:setRectColor(ind, p.col)
if self.streak then
local dir = (p.pos - p.prevPos)
local len = dir:len()
local pos = (p.pos + p.prevPos) * 0.5
local ang = math.atan2(dir.y, dir.x)
self.particleMesh:setRect(ind, pos.x, pos.y, p.size * self.streakMult, p.size, ang)
end
end
end
self.particleMesh:draw()
end
Particle = class()
function Particle:init()
self.pos = vec2(0,0)
self.prevPos = vec2(0,0)
self.vel = vec2(0,0)
self.life = 0
self.maxLife = 0
self.dead = true
self.col = color(0, 0, 0, 255)
self.size = 0
end
--# SlammerPod
SlammerPod = class()
function SlammerPod:init(x,y)
self.name = "pod"
self.health = 1
self.chassis = createBox(x, y, 60, 80)
self.chassis.gravityScale = 1
self.chassis.density = 3
self.chassis.fixedRotation = true
self.chassis.info = self
self.rock = createCircle(self.chassis.x,self.chassis.y - 150, 35)
self.rock.density = 0.75
self.rock.info = self
owners[self.rock] = self
self.rock.angularDamping = 0.1
self.rope = physics.joint(ROPE, self.chassis, self.rock, self.chassis.position, self.rock.position, 150)
debugDraw:addJoint(self.rope)
self.links = {}
local lc = 10
local len = (self.chassis.y - self.rock.y) / lc
local p = self.chassis.position - vec2(0, len/2)
local prev = self.chassis
for i = 1,lc do
local link = createBox(p.x, p.y, 5, len)
link.mask = {10}
local j = physics.joint(REVOLUTE, prev, link, p + vec2(0, len/2))
prev = link
debugDraw:addJoint(j)
table.insert(self.links, link)
p.y = p.y - len
end
local j = physics.joint(REVOLUTE, prev, self.rock, self.rock.position)
debugDraw:addJoint(j)
self.stick = VirtualStick({
moved = function(v) self:stickMoved(v) end,
released = function(v) self:stickReleased(v) end,
radius = 80,
deadZoneRadius = 10,
})
self.stick:activate()
self.dir = vec2(0,0)
self.anim = 0.0
--sprite("Cargo Bot:Smoke Particle")
self.sparks = Emitter({tex = "Tyrian Remastered:Bullet Fire A",
minSize = 5,
maxSize = 5,
minSpeed = 100,
maxSpeed = 150,
accel = vec2(0,-1000),
rAccel = vec2(1500,1500),
streak = true,
streakMult = 3})
self.smoke = Emitter({tex = "Cargo Bot:Smoke Particle",
minSize = 5,
maxSize = 25,
minSpeed = 0,
maxSpeed = 0,
accel = vec2(0,1000),
rAccel = vec2(1500,1500),
})
end
function SlammerPod:stickMoved(v)
self.dir = v
end
function SlammerPod:stickReleased(v)
self.dir = vec2(0,0)
end
function SlammerPod:draw()
self.anim = self.anim + DeltaTime
local gain = 250 * self.chassis.mass
local damp = 0.75
local vel = self.chassis.linearVelocity
self.chassis:applyForce(gain * self.dir - damp * vel)
self.chassis.linearDamping = vel:len() / 50
pushMatrix()
translate(self.chassis.x, self.chassis.y)
rotate(self.chassis.angle)
sprite("Tyrian Remastered:Part H", 0, 0, 60)
pushMatrix()
rotate(self.anim * 100)
sprite("Tyrian Remastered:Orb 2", 0,0,30)
popMatrix()
popMatrix()
pushMatrix()
translate(self.rock.x, self.rock.y)
rotate(self.rock.angle)
sprite("Tyrian Remastered:Mine Spiked Huge", 0, 0, self.rock.radius * 2.5)
popMatrix()
for k,v in ipairs(self.links) do
pushMatrix()
translate(v.x,v.y)
rotate(v.angle)
sprite("Tyrian Remastered:Ring", 0,0,15,25)
popMatrix()
end
self.sparks:draw()
--self.()stick:draw()
end
function SlammerPod:hit(other, contact, first)
if contact.state == BEGAN then
local imp = math.max(0, (contact.normalImpulse-25) / 25)
if imp > 0.0 then
self.sparks:emit(contact.position, 10)
sound(SOUND_HIT, 47051, imp)
if other and other.damage then other:damage(imp) end
end
end
end
--# Worm
Worm = class()
function Worm:init(x,y,t)
self.name = "worm"
-- you can accept and set parameters here
self.body = createCircle(x,y,35)
self.body.info = self
self.body.gravityScale = 0
self.target = t
self.dir = vec2(0,0)
self.health = 1
self.power = 1
self.stun = 0
self.base = physics.body(CIRCLE, 35)
self.base.fixedRotation = true
self.base.gravityScale = 0
self.base.position = self.body.position
self.base.mask = {15}
self.servo = physics.joint(REVOLUTE, self.base, self.body, self.base.position)
self.servo.enableMotor = true
self.servo.maxMotorTorque = 1000
self.motorSpeed = 0
end
function Worm:draw()
self.dir = (self.target.chassis.position - self.body.position)
self.dir = self.dir:normalize()
-- Codea does not automatically call this method
local gain = 29.400 * self.body.mass * self.power
local damp = 0.75 * self.power
local vel = self.body.linearVelocity
self.body:applyForce(gain * self.dir - damp * vel)
self.body.linearDamping = vel:len() / 100 * self.power
self.body.gravityScale = 1-self.power
local tAng = math.atan2(self.dir.y, self.dir.x) + math.pi/2
self.servo.motorSpeed = (tAng - math.rad(self.servo.jointAngle)) * 100
--if self.power == 0
pushMatrix()
translate(self.body.x, self.body.y)
rotate(self.body.angle)
sprite("Tyrian Remastered:Organic Claw", 0,-14,60)
sprite("Tyrian Remastered:Part N")
popMatrix()
fill(120, 30, 30, 255)
rect(self.body.x,self.body.y+50,50,10)
fill(255, 0, 0, 255)
rect(self.body.x,self.body.y+50,50*self.health,10)
end
function Worm:damage(dmg)
self.health = math.max(0, self.health - dmg)
if self.health == 0 then
self.power = 0
self.servo.enableMotor = false
sound(SOUND_RANDOM, 41402, 1)
end
end
WormPart = class()
function WormPart:init(x,y,r,worm,parent)
self.body = physics.body(CIRCLE, r)
self.body.x = x
self.body.y = y
self.worm = worm
self.health = 1
end
--# Main
supportedOrientations(LANDSCAPE_ANY)
--displayMode(FULLSCREEN)
physics.resume()
-- Use this function to perform your initial setup
function setup()
debugDraw = PhysicsDebugDraw()
-- test classes
-- to add your own, make sure to define setup() and cleanup()
-- in addition to draw() and touched()
tests = {Test1()}
iparameter("test_number", 1, #tests)
iparameter("use_accelerometer", 0, 1)
currentTestIndex = 1
currentTest = nil
setTest(currentTestIndex)
defaultGravity = physics.gravity()
end
function setTest(t)
if currentTest then
if currentTest.cleanup then
currentTest:cleanup()
end
cleanup()
end
currentTestIndex = t
currentTest = tests[t]
currentTest:setup()
end
function nextTest()
local t = currentTestIndex + 1
if t > #tests then
t = 1
end
setTest(t)
end
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
debugDraw: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
debugDraw:addBody(box)
return box
end
function createGround()
local ground = physics.body(POLYGON, vec2(0,20), vec2(0,0), vec2(WIDTH,0), vec2(WIDTH,20))
ground.type = STATIC
debugDraw:addBody(ground)
return ground
end
function cleanup()
debugDraw:clear()
end
-- This function gets called once every frame
function draw()
-- This sets the background color to black
background(0, 0, 0)
if test_number ~= currentTestIndex then
setTest(test_number)
end
currentTest:draw()
--debugDraw:draw()
local str = string.format("Test %d - %s", currentTestIndex, currentTest.title)
text(str, WIDTH/2, HEIGHT - 20)
if use_accelerometer == 1 then
physics.gravity(Gravity)
else
--physics.gravity(defaultGravity)
end
end
function touched(touch)
if debugDraw:touched(touch) == false then
currentTest:touched(touch)
end
end
function collide(contact)
--debugDraw:collide(contact)
if currentTest.collide then
currentTest:collide(contact)
end
end
--# Test1
Test1 = class()
function Test1:init()
self.title = "SlammerFall"
end
function Test1:setup()
POD = 0
WORM = 1
owners = {}
self.levelWidth = 1024*4
self.levelHeight = 768*2
self.stars = mesh()
self.stars.texture = "SpaceCute:Star"
self.stars2 = mesh()
self.stars2.texture = "SpaceCute:Star"
for i = 1,200 do
local size = math.random(5,15)
self.stars:addRect(math.random(0,self.levelWidth), math.random(0,self.levelHeight), size,size)
self.stars2:addRect(math.random(0,self.levelWidth*2), math.random(0,self.levelHeight*2), size,size)
end
self.pod = SlammerPod(self.levelWidth/2, HEIGHT/2)
self.worm1 = Worm(self.levelWidth, HEIGHT, self.pod)
self.worm1.name = "worm1"
self.worm2 = Worm(self.levelWidth/2, HEIGHT, self.pod)
self.worm2.name = "worm2"
self.worm3 = Worm(9, HEIGHT, self.pod)
self.worm3.name = "worm3"
self.camPos = self.pod.chassis.position
self.landMesh = mesh()
local points = {vec2(0,0)}
for x = 0,self.levelWidth,50 do
table.insert(points, vec2(x,50 + math.random(0, 25)))
end
table.insert(points, vec2(self.levelWidth, 0))
self.landMesh.vertices = triangulate(points)
self.landMesh:setColors(128, 128, 128)
self.land = physics.body(CHAIN, true, unpack(points))
self.land.type = STATIC
self.land.restitution = 0.3
self.leftBarrier = physics.body(EDGE, vec2(0,0), vec2(0,self.levelHeight))
self.rightBarrier = physics.body(EDGE, vec2(self.levelWidth,0), vec2(self.levelWidth,self.levelHeight))
self.topBarrier = physics.body(EDGE, vec2(0,self.levelHeight), vec2(self.levelWidth,self.levelHeight))
end
function Test1:draw()
-- Codea does not automatically call this method
pushMatrix()
local interp = 0.95
self.camPos = self.camPos * interp + self.pod.chassis.position * (1-interp)
self.camPos.x = math.max(self.camPos.x, WIDTH/2)
self.camPos.x = math.min(self.camPos.x, self.levelWidth - WIDTH/2)
self.camPos.y = math.max(self.camPos.y, HEIGHT/2)
self.camPos.y = math.min(self.camPos.y, self.levelHeight - HEIGHT/2)
pushMatrix()
translate(-self.camPos.x/2 + WIDTH/2, -self.camPos.y/2 + HEIGHT/2)
self.stars2:draw()
popMatrix()
translate(-self.camPos.x + WIDTH/2, -self.camPos.y + HEIGHT/2)
self.stars:draw()
self.landMesh:draw()
self.pod:draw()
self.worm1:draw()
self.worm2:draw()
self.worm3:draw()
popMatrix()
end
function Test1:collide(contact)
if contact.state ~= BEGAN then return end
local infoA = contact.bodyA.info
local infoB = contact.bodyB.info
if infoA and infoA.hit then
infoA:hit(infoB, contact, true)
end
if infoB and infoB.hit then
infoB:hit(infoA, contact, false)
end
end
--# PhysicsDebugDraw
PhysicsDebugDraw = class()
function PhysicsDebugDraw:init()
self.bodies = {}
self.joints = {}
self.touchMap = {}
self.contacts = {}
end
function PhysicsDebugDraw:addBody(body)
table.insert(self.bodies,body)
end
function PhysicsDebugDraw:addJoint(joint)
table.insert(self.joints,joint)
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(5.0)
local points = body.points
for j = 1,#points do
a = points[j]
b = points[(j % #points)+1]
line(a.x, a.y, b.x, b.y)
end
elseif body.shapeType == CHAIN or body.shapeType == EDGE then
strokeWidth(5.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 then
strokeWidth(5.0)
line(0,0,body.radius-3,0)
strokeWidth(2.5)
ellipse(0,0,body.radius*2)
end
popMatrix()
end
stroke(255, 0, 0, 255)
fill(255, 0, 0, 255)
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
elseif contact.state == MOVING then
self.contacts[contact.id] = contact
elseif contact.state == ENDED then
self.contacts[contact.id] = nil
end
end
--# Controller
-- Base class for controllers
--
-- Controllers translate touch events into callbacks to functions
-- that do something in the app. (Model/View/Controller style).
--
-- Controllers can draw a representation of their current state on
-- the screen, but you can choose not to.
--
-- A controller can be installed as the global handler for touch
-- events by calling its activate() method
Controller = class()
function Controller:activate()
touched = function(t)
self:touched(t)
end
end
function Controller:draw()
-- nothing
end
-- Utility functions
function touchPos(t)
return vec2(t.x, t.y)
end
function clamp(x, min, max)
return math.max(min, math.min(max, x))
end
function clampAbs(x, maxAbs)
return clamp(x, -maxAbs, maxAbs)
end
function clampLen(vec, maxLen)
return vec:normalize() * math.min(vec:len(), maxLen)
end
-- projects v onto the direction represented by the given unit vector
function project(v, unit)
return v:dot(unit)
end
function sign(x)
if x == 0 then
return 0
elseif x < 0 then
return -1
elseif x > 0 then
return 1
else
return x -- x is NaN
end
end
function doNothing()
end
--# VirtualStick
-- A virtual analogue joystick with a dead-zone at the center,
-- which activates wherever the user touches their finger
--
-- Arguments:
-- radius - radius of the stick (default = 100)
-- deadZoneRadius - radius of the stick's dead zone (default = 25)
-- moved(v) - Called when the stick is moved
-- v : vec2 - in the range vec2(-1,-1) and vec2(1,1)
-- pressed() - Called when the user starts using the stick (optional)
-- released() - Called when the user releases the stick (optional)
VirtualStick = class(Controller)
function VirtualStick:init(args)
self.radius = args.radius or 100
self.deadZoneRadius = args.deadZoneRadius or 25
self.releasedCallback = args.released or doNothing
self.steerCallback = args.moved or doNothing
self.pressedCallback = args.pressed or doNothing
end
function VirtualStick:touched(t)
local pos = touchPos(t)
if t.state == BEGAN and self.touchId == nil then
self.touchId = t.id
self.touchStart = pos
self.stickOffset = vec2(0, 0)
self.pressedCallback()
elseif t.id == self.touchId then
if t.state == MOVING then
self.stickOffset = clampLen(pos - self.touchStart, self.radius)
self.steerCallback(self:vector())
elseif t.state == ENDED then
self:reset()
self.releasedCallback()
end
end
end
function VirtualStick:vector()
local stickRange = self.radius - self.deadZoneRadius
local stickAmount = math.max(self.stickOffset:len() - self.deadZoneRadius, 0)
local stickDirection = self.stickOffset:normalize()
return stickDirection * (stickAmount/stickRange)
end
function VirtualStick:reset()
self.touchId = nil
self.touchStart = nil
self.stickOffset = nil
end
function VirtualStick:draw()
if self.touchId ~= nil then
pushStyle()
ellipseMode(RADIUS)
strokeWidth(1)
stroke(255, 255, 255, 255)
noFill()
pushMatrix()
translate(self.touchStart.x, self.touchStart.y)
ellipse(0, 0, self.radius, self.radius)
ellipse(0, 0, self.deadZoneRadius, self.deadZoneRadius)
translate(self.stickOffset.x, self.stickOffset.y)
ellipse(0, 0, 25, 25)
popMatrix()
popStyle()
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment