Created
December 16, 2013 01:36
-
-
Save NathanFlurry/7981135 to your computer and use it in GitHub Desktop.
December 2013 Ludum Dare entry.
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
--# Main | |
displayMode(FULLSCREEN_NO_BUTTONS) | |
function setup() | |
parameter.boolean("rotateCamera",true) | |
lastScore = readLocalData("lastScore",0) | |
highscore = readLocalData("highscore",0) | |
StateMachine:init() | |
StateMachine:registerStates({ | |
start = Start, | |
game = Game | |
}) | |
StateMachine:setState("start") | |
end | |
function draw() | |
background(0, 0, 0, 255) | |
physics.gravity(0,0) | |
font("Didot") | |
fontSize(25) | |
fill(255, 255, 255, 255) | |
StateMachine:draw() | |
end | |
function touched(t) | |
StateMachine:touched(t) | |
end | |
function collide(c) | |
if StateMachine.currentState.player.collide then | |
state.player:collide(c) | |
end | |
end | |
--# Start | |
Start = class() | |
function Start:init() | |
end | |
function Start:draw() | |
pushStyle() | |
if lastScore > 0 then | |
fontSize(20) | |
text("Last Score: "..lastScore,WIDTH/2,HEIGHT/4) | |
text("Highscore: "..highscore,WIDTH/2,HEIGHT/4-fontSize()*2) | |
end | |
fontSize(100) | |
text("SnakeChase",WIDTH/2,HEIGHT/3*2) | |
fontSize(50) | |
text("Touch to start",WIDTH/2,HEIGHT/3) | |
popStyle() | |
end | |
function Start:touched(t) | |
if t.state == ENDED then | |
StateMachine:setState("game") | |
end | |
end | |
--# Game | |
Game = class() | |
function Game:init() | |
self.score = 0 | |
self.scoreTimer = Timer(.1,function() | |
self.score = self.score + #self.player.bodyParts | |
end) | |
self.lives = {} | |
self.addLife = function() | |
table.insert(self.lives,{size = 0}) | |
tween(.2,self.lives[#self.lives],{size = 1},tween.easing.sineOut) | |
end | |
self.subtractLife = function() | |
if #self.lives > 0 then | |
tween(.2,self.lives[#self.lives],{size = 0},tween.easing.sineOut,function() | |
table.remove(self.lives) | |
end) | |
end | |
end | |
for i = 1,5 do | |
self:addLife() | |
end | |
self.player = Player() | |
self.enemies = Enemies() | |
end | |
function Game:draw() | |
self.scoreTimer:update() | |
self.trans = vec2(-self.player.head.x+WIDTH/2,-self.player.head.y+HEIGHT/2) | |
local scoreStr = tostring(self.score) | |
local scoreTable = {} | |
for i = 6,1,-1 do | |
local num = string.sub(scoreStr,-i,-i) | |
if type(tonumber(num)) ~= "number" then | |
num = "0" | |
end | |
table.insert(scoreTable,num) | |
end | |
for i,v in ipairs(scoreTable) do | |
text(v,fontSize()*i*.7,HEIGHT-fontSize()) | |
end | |
local sMStr = tostring(#self.player.bodyParts) | |
local sMTable = {} | |
for i = 1,string.len(#self.player.bodyParts) do | |
local num = string.sub(sMStr,-i,-i) | |
if type(tonumber(num)) ~= "number" then | |
num = "0" | |
end | |
table.insert(sMTable,num) | |
end | |
table.insert(sMTable,"x") | |
for i,v in ipairs(sMTable) do | |
text(v,fontSize()*(#scoreTable-i+1)*.7,HEIGHT-fontSize()*2) | |
end | |
for i,v in ipairs(self.lives) do | |
pushMatrix() | |
pushStyle() | |
tint(255, 255, 255, 255) | |
translate((WIDTH-i*25)+0,HEIGHT-20) | |
scale(v.size) | |
text("♡",0,0) | |
popStyle() | |
popMatrix() | |
end | |
pushMatrix() | |
if rotateCamera then | |
translate(WIDTH/2,HEIGHT/2) | |
rotate(-self.player.head.angle+90) | |
translate(-WIDTH/2,-HEIGHT/2) | |
end | |
translate(self.trans.x,self.trans.y) | |
pushStyle() | |
fill(76, 76, 76, 255) | |
popStyle() | |
self.player:draw() | |
self.enemies:draw() | |
popMatrix() | |
if #self.lives <= 0 then | |
lastScore = self.score | |
saveLocalData("lastScore",self.score) | |
if self.score > highscore then | |
highscore = self.score | |
saveLocalData("highscore",self.score) | |
end | |
-- StateMachine:setState("start") -- UNKNOWN BUG | |
restart() | |
end | |
end | |
function Game:exit() | |
end | |
--# Player | |
Player = class() | |
function Player:init() | |
self.partSize = 15 | |
self.headSize = self.partSize | |
self.rotateSpeed = 250 | |
self.moveSpeed = 150 | |
self.oDistance = self.partSize | |
self.head = physics.body(CIRCLE,self.headSize) | |
self.head.sensor = true | |
self.head.type = KINEMATIC | |
self.head.position = vec2(WIDTH/2,HEIGHT/2) | |
self.color = color(255, 255, 255, 255) | |
self.m = mesh() | |
self.m:setColors(self.color) | |
self.bodyParts = {} | |
for i = 1,1 do | |
self:addBodyPart() | |
end | |
end | |
function Player:draw() | |
self:control() | |
--debugDraw(self.head) | |
for i,v in ipairs(self.bodyParts) do | |
local nextObj = self.bodyParts[i-1] or self.head | |
local ang = math.deg(vec2(0,0):angleBetween((v.position-nextObj.position)))-180 | |
v.angle = ang | |
v.linearVelocity = vec2( | |
math.cos(math.rad(ang)), | |
math.sin(math.rad(ang)))*self.moveSpeed | |
--debugDraw(v) | |
end | |
local snakeWidth = self.partSize | |
local points = {} | |
local capPoints = {} | |
local capRes = 20 | |
for i = 1,capRes do | |
local pos = vec2(math.cos(math.rad((360/capRes)*i)), | |
math.sin(math.rad((360/capRes)*i))) | |
if pos.y >= -capRes then | |
table.insert(capPoints,pos) | |
end | |
end | |
for i = 1,#capPoints do | |
local pos = capPoints[i] | |
pos = pos:rotate(math.rad(self.head.angle-90)) | |
pos = pos * snakeWidth + self.head.position | |
table.insert(points,pos) | |
end | |
for i = 0,#self.bodyParts do | |
local v = self.bodyParts[i] or self.head | |
table.insert( | |
points, | |
vec2( | |
v.x+math.cos(math.rad(v.angle+90))*snakeWidth, | |
v.y+math.sin(math.rad(v.angle+90))*snakeWidth | |
) | |
) | |
end | |
for i = #capPoints,1,-1 do | |
local pos = capPoints[i] | |
local part = self.bodyParts[#self.bodyParts] or self.head | |
pos.x = pos.x | |
pos.y = -pos.y | |
pos = pos:rotate(math.rad(part.angle-90)) | |
pos = pos * snakeWidth + part.position | |
table.insert(points,pos) | |
end | |
for i = #self.bodyParts,0,-1 do | |
local v = self.bodyParts[i] or self.head | |
table.insert( | |
points, | |
vec2( | |
v.x+math.cos(math.rad(v.angle-90))*snakeWidth, | |
v.y+math.sin(math.rad(v.angle-90))*snakeWidth | |
) | |
) | |
end | |
self.m.vertices = triangulate(points) | |
self.m:setColors(self.color) | |
self.m:draw() | |
local eyeSize = 3 | |
pushStyle() | |
pushMatrix() | |
translate(self.head.x,self.head.y) | |
rotate(self.head.angle+90) | |
fill(0, 0, 0, 255) | |
ellipse(-4,2,eyeSize) | |
ellipse(4,2,eyeSize) | |
popMatrix() | |
popStyle() | |
end | |
function Player:control() | |
self.head.angle = self.head.angle+(-Gravity.x*self.rotateSpeed*DeltaTime) | |
self.head.linearVelocity = vec2( | |
math.cos(math.rad(self.head.angle)), | |
math.sin(math.rad(self.head.angle)))*self.moveSpeed | |
end | |
function Player:addBodyPart() | |
local o = physics.body(CIRCLE,self.partSize) | |
o.sensor = true | |
local nextObj; | |
if self.bodyParts[#self.bodyParts] then | |
nextObj = self.bodyParts[#self.bodyParts] | |
else | |
nextObj = self.head | |
end | |
o.x = nextObj.x - math.cos(math.rad(nextObj.angle)) * self.oDistance | |
o.y = nextObj.y - math.sin(math.rad(nextObj.angle)) * self.oDistance | |
o.id = "bodyPart" | |
o.j = physics.joint(DISTANCE,o,nextObj,o.position,nextObj.position) | |
o.j.frequency = 0 | |
table.insert(self.bodyParts,o) | |
end | |
function Player:collide(c) | |
if c.bodyA == self.head or c.bodyB == self.head then | |
local destroyBody; | |
for i,v in ipairs(state.enemies.enemies) do | |
if c.bodyA == v or c.bodyB == v then | |
destroyBody = i | |
end | |
end | |
if destroyBody then | |
self:addBodyPart() | |
state.enemies.enemies[destroyBody]:destroy() | |
table.remove(state.enemies.enemies,destroyBody) | |
end | |
elseif (c.bodyA.id == "bodyPart" and c.bodyB.id ~= "bodyPart") or | |
(c.bodyB.id == "bodyPart" and c.bodyA.id ~= "bodyPart") then | |
local stopLoop = false | |
local i = 1 | |
while not stopLoop do | |
local v = self.bodyParts[i] | |
if c.bodyA == v or c.bodyB == v then | |
stopLoop = true | |
local destroyBody; | |
for i,v in ipairs(state.enemies.enemies) do | |
if c.bodyA == v or c.bodyB == v then | |
destroyBody = i | |
end | |
end | |
if destroyBody then | |
self.color = color(255, 0, 0, 255) | |
tween(1, | |
self.color, | |
{g = 255, b = 255}, | |
{easing = tween.easing.sineOut}) | |
state:subtractLife() | |
state.enemies.enemies[destroyBody]:destroy() | |
table.remove(state.enemies.enemies,destroyBody) | |
local destI = i | |
for i = #self.bodyParts,i,-1 do | |
local v = self.bodyParts[i] | |
v:destroy() | |
table.remove(self.bodyParts,i) | |
end | |
end | |
end | |
i = i+1 | |
if i > #self.bodyParts then | |
stopLoop = true | |
end | |
end | |
end | |
end | |
--# Enemies | |
Enemies = class() | |
function Enemies:init() | |
self.size = 20 | |
self.moveSpeed = 50 | |
self.angleRange = 30 | |
self.turnRange = 100 | |
self.enemies = {} | |
self.enemyTimer = Timer(.2,function() self:addEnemy() end) | |
self.speedUpTimer = Timer(1, | |
function() | |
self.enemyTimer.interval = self.enemyTimer.interval * .95 | |
end) | |
end | |
function Enemies:draw() | |
self.speedUpTimer:update() | |
self.enemyTimer:update() | |
for i = #self.enemies,1,-1 do | |
local v = self.enemies[i] | |
v.angle = v.angle + v.turnAmount * DeltaTime | |
v.linearVelocity = vec2( | |
math.cos(math.rad(v.angle)), | |
math.sin(math.rad(v.angle)))*v.speed | |
--debugDraw(v) | |
pushMatrix() | |
pushStyle() | |
fill(v.fill) | |
translate(v.x,v.y) | |
scale(v.offsetSize) | |
ellipse(0,0,v.radius*2) | |
popMatrix() | |
popStyle() | |
if v.x < state.player.head.x-WIDTH or | |
v.x > state.player.head.x+WIDTH or | |
v.y < state.player.head.y-HEIGHT or | |
v.y > state.player.head.y+HEIGHT then | |
v:destroy() | |
table.remove(self.enemies,i) | |
end | |
end | |
end | |
function Enemies:addEnemy() | |
local e = physics.body(CIRCLE,randomInRange(self.size,10)) | |
local location = math.random(1,4) | |
e.sensor = true | |
local positionRange = vec2(WIDTH,HEIGHT) | |
e.speed = randomInRange(self.moveSpeed,30) | |
if location == 1 then | |
e.position = vec2( | |
state.player.head.x-positionRange.x/2, | |
randomInRange(state.player.head.y,positionRange.y)) | |
e.angle = randomInRange(0,self.angleRange) | |
elseif location == 2 then | |
e.position = vec2( | |
randomInRange(state.player.head.x,positionRange.x), | |
state.player.head.y+positionRange.y/2) | |
e.angle = randomInRange(270,self.angleRange) | |
elseif location == 3 then | |
e.position = vec2( | |
state.player.head.x+positionRange.x/2, | |
randomInRange(state.player.head.y,positionRange.y)) | |
e.angle = randomInRange(180,self.angleRange) | |
elseif location == 4 then | |
e.position = vec2( | |
randomInRange(state.player.head.x,positionRange.x), | |
state.player.head.y-positionRange.y/2) | |
e.angle = randomInRange(90,self.angleRange) | |
end | |
e.turnAmount = (math.random()-.5)*self.turnRange | |
e.fill = color(255) | |
tween(.25,e.fill,{g = 200, b = 200},{loop = "pingpong",easing = tween.easing.sineInOut}) | |
e.offsetSize = 1.1 | |
tween(.4,e,{offsetSize = .9},{loop = "pingpong",easing = tween.easing.sineInOut}) | |
table.insert(self.enemies,e) | |
end | |
--# StateMachine | |
StateMachine = class() | |
function StateMachine:init() | |
self.states = {} | |
self.currentStateIndex = nil | |
self.currentState = nil | |
end | |
function StateMachine:draw() | |
if self.currentState then | |
self.currentState:draw() | |
end | |
end | |
function StateMachine:setState(index) | |
if self.currentState and self.currentState.exit then | |
self.currentState:exit() | |
end | |
if self.states[index] then | |
self.currentStateIndex = index | |
self.currentState = self.states[index]() | |
state = self.currentState | |
else | |
error("Non-existent state") | |
end | |
end | |
function StateMachine:registerStates(states) | |
for i,v in pairs(states) do | |
self.states[i] = v | |
print(i,v) | |
end | |
end | |
function StateMachine:touched(t) | |
if self.currentState and self.currentState.touched then | |
self.currentState:touched(t) | |
end | |
end | |
--# Timer | |
Timer = class() | |
function Timer:init(interval,callback) | |
self.interval = interval | |
self.callback = callback | |
self.time = 0 | |
self.occurances = 0 | |
self.paused = false | |
end | |
function Timer:update() | |
if self.paused ~= true then | |
self.time = self.time + DeltaTime | |
if self.time >= self.interval then | |
self.occurances = self.occurances + 1 | |
self.time = 0 | |
if type(self.callback) == "function" then | |
self.callback(self.occurances) | |
end | |
return true | |
else | |
return false | |
end | |
end | |
end | |
function Timer:reset() | |
self.time = 0 | |
self.occurances = 0 | |
end | |
function Timer:pause() | |
self.paused = true | |
end | |
function Timer:resume() | |
self.paused = false | |
end | |
function Timer:isPaused() | |
return self.paused | |
end | |
function Timer:getTime() | |
return self.time | |
end | |
function Timer:getCount() | |
return self.occurances | |
end | |
--# Functions | |
function randomInRange(n,r) | |
return n+((math.random()-.5)*2*r) | |
end | |
function debugDraw(b) | |
pushStyle() | |
pushMatrix() | |
noFill() | |
translate(b.x,b.y) | |
rotate(b.angle) | |
if b.type == STATIC then | |
stroke(255,255,255,255) | |
elseif b.type == DYNAMIC then | |
stroke(150,255,150,255) | |
elseif b.type == KINEMATIC then | |
stroke(150,150,255,255) | |
end | |
if b.shapeType == POLYGON then | |
strokeWidth(3) | |
local points = b.points | |
for j = 1,#points do | |
local a = points[j] | |
local b = points[(j % #points)+1] | |
line(a.x,a.y,b.x,b.y) | |
end | |
elseif b.shapeType == CHAIN or b.shapeType == EDGE then | |
strokeWidth(3) | |
local points = b.points | |
for j = 1,#points-1 do | |
local a = points[j] | |
local b = points[j+1] | |
line(a.x,a.y,b.x,b.y) | |
end | |
elseif b.shapeType == CIRCLE then | |
strokeWidth(3) | |
line(0,0,b.radius-3,0) | |
ellipse(0,0,b.radius*2) | |
end | |
popStyle() | |
popMatrix() | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment