-
-
Save AntonioCiolino/a9f5189a0fd8a999d924 to your computer and use it in GitHub Desktop.
Cofender Release v1.0.2 -Cofender - a defender clone written with Codea
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
Cofender Tab Order Version: 1.0.2 | |
------------------------------ | |
This file should not be included in the Codea project. | |
#AnimatedDraw | |
#Attacker | |
#Draw | |
#Explode | |
#Bullet | |
#Firework | |
#Particle | |
#Saucer | |
#FlashScore | |
#Guy | |
#Invader | |
#MineLayer | |
#ScreenEnd | |
#ScreenGame | |
#ScreenLevelComplete | |
#ScreenOptions | |
#ScreenPaused | |
#ScreenShipExplode | |
#ScreenSplash | |
#ScreenTitle | |
#Shields | |
#Ship | |
#Helpers | |
#Main | |
#Spawner | |
#Star |
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
---- | |
AnimatedDraw = class() | |
--animated draw class. | |
--a simple animated loop class | |
--Currently only allows for a horizontal version of a sprite. | |
-- In this version, we do not allow Y-axis alteration. | |
--we are doing simple timer tests for animations, no fancy Timer class. | |
function AnimatedDraw:init(params) | |
--base error checking. | |
assert(params.img, "AnimatedDraw items must have an image to cycle.") | |
self.img = params.img | |
self.fps = params.fps or 5 -- fps | |
self.maxframe = params.frames or 5 --number of frames in the strip | |
self.maxrows = params.rows or 1 --number of rows in this sheet | |
self.size = params.size or vec2(16,16) -- size of animation image | |
self.direction = params.direction or 1 --1, 0, -1...determines move direction | |
self.mesh = mesh() | |
self.mesh.texture=self.img --once set, this isn't let go...we'd have to reset it | |
self.pos = params.pos or vec2(0,0) -- position of the item | |
self.move = params.move or vec2(0,0) --when flipping frames what is the offset | |
--optimization variables. | |
self.curframe = 0 --Current draw frame | |
self.row = params.row or 1 --which row of the altas? | |
self.lastDrawTime = ElapsedTime --last time a NEW frame was drawn. | |
self.diffTime = 1/ self.fps --the difference in time to wait for new frames | |
end | |
-- This assumes that all frames in an image passed are horizontal and are all supposed to | |
--be drawn. if we want to jump around or index frames, we don't so that in this version. | |
function AnimatedDraw:draw() | |
--generate current frame | |
if ElapsedTime > self.lastDrawTime+self.diffTime then | |
self.curframe = self.curframe + 1 | |
if self.curframe >= self.maxframe then self.curframe=0 end --restart | |
self.lastDrawTime=ElapsedTime | |
end | |
-- Codea does not automatically call this method | |
self.mesh:clear() | |
--if we want to move the position of the item, do it here. | |
self.pos.x = self.pos.x + (self.move.x * self.direction) | |
self.pos.y = self.pos.y + (self.move.y * self.direction) | |
--create the mesh and texture position | |
local obj=self.mesh:addRect(self.pos.x,self.pos.y,self.size.x,self.size.y) | |
local obj2=self.mesh:addRect(self.pos.x+10000,self.pos.y,self.size.x,self.size.y) | |
local cols= self.curframe | |
self.mesh:setRectTex(obj, | |
self.curframe / self.maxframe, --current col position | |
self.row/self.maxrows, | |
1/self.maxframe, --ratio of cols | |
1/self.maxrows) --ratio of rows | |
self.mesh:setRectTex(obj2, | |
self.curframe / self.maxframe, --current col position | |
self.row/self.maxrows, | |
1/self.maxframe, --ratio of cols | |
1/self.maxrows) --ratio of rows | |
--draw the mesh! | |
self.mesh:draw() | |
end | |
function AnimatedDraw:touched(touch) | |
-- Codea does not automatically call this method | |
end | |
function AnimatedDraw:ResetTexture(img) | |
self.mesh = mesh() | |
self.mesh.texture=img --once set, this isn't let go...we'd have to reset it | |
end |
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
---- | |
Attacker = class() | |
--class used to attack the player. Invader and others should subclass/extend this. | |
function Attacker:init(params) | |
assert (params.name, "Enemies must have a name for indexing") | |
self.name = params.name | |
self.pos = params.pos or vec2(0,0) | |
self.speed = params.speed or 1 --movement speed | |
self.direction = 1 --or -1 | |
self.looking= params.looking or true --set to false when we acquired | |
self.isMutant = false --set to true if we are able to mutate | |
self.FireSpeed = params.FireSpeed or 7 --how fast is our bullet? | |
self.myHuman = nil --only if we are holding someone... | |
self.maxCycles= params.cycles or 120 --frames before enemy can shoot again. | |
self.cycles = params.cycles or 120 | |
self.img=params.img | |
self.width = self.img.width | |
self.height = self.img.height | |
self.points = params.points or 150 | |
self.impactDamage = params.impactDamage or 10 --imapct damage, not bullet damage | |
self.damage = params.damage or 1 --bullet damage | |
self.speed=self.speed+wave/10 --wave delta increase on movement speed. | |
self.health = 1 --all things have simple health. Spawner is an exception | |
self.hudColor = params.hudColor or color(213, 193, 12, 255) | |
self.oldDelta=vec2(0,0) | |
--pass drawparams for animation | |
self.drawparams = params.drawparams or | |
{img = self.img, fps= 1, size = vec2(self.img.width,self.img.height), pos = self.pos, | |
move=vec2(0, 0), self.direction , frames = 1, rows = 1, row=0} | |
self.d = AnimatedDraw(self.drawparams) | |
end | |
function Attacker:Heartbeat() | |
self:Move() | |
self:Shoot() | |
self:CheckForImpact() | |
self:draw() | |
end | |
function Attacker:Move() | |
local delta = (worldpos + player.screenpos) - self.pos | |
if math.abs(delta.x)>WORLDSIZE/2 then delta=delta *-1 end -- faster the other way... | |
delta = delta + self.oldDelta*self.speed -- | |
delta = delta:normalize() | |
self.pos.x = self.pos.x + (delta.x) * self.speed | |
self.pos.y = self.pos.y + (delta.y) * self.speed | |
self.pos = CorrectPosition(self.pos) | |
self.oldDelta=delta | |
end | |
function Attacker:draw() | |
--sprite(self.img, self.pos.x-worldpos.x, self.pos.y) | |
--sprite(self.img, 10000+self.pos.x-worldpos.x, self.pos.y) | |
if (debug==true) then | |
--text(tostring(self.pos), self.pos.x-worldpos.x, self.pos.y) | |
end | |
self.d.pos = vec2(self.pos.x-worldpos.x, self.pos.y) | |
self.d:draw() | |
--hud | |
pushMatrix() | |
stroke(self.hudColor) | |
ellipse((self.pos.x)/ratio+HUD_LEFT, self.pos.y/ratioy+(TOP), 2,2) | |
popMatrix() | |
end | |
function Attacker:touched(touch) | |
-- Codea does not automatically call this method | |
end | |
function Attacker:Shoot() | |
local target=nil | |
self.cycles = self.cycles - 1 | |
if self.cycles < 1 then | |
--call only one or else you get homing bullets! | |
target = worldpos + player.screenpos | |
if self.predictive == true then | |
target = worldpos + player.screenpos | |
target.x =target.x +steerpos.x*10 | |
target=CorrectPosition(target) | |
-- 18 is half ahip max speed. hack!! 10 is 1/6 sec | |
end | |
local vector = target - self.pos | |
vector = vector:normalize() | |
local dist = self.pos:dist(player.screenpos + worldpos) | |
if (dist< 1200 and dist > 20) then --don't fire if TOO close! Stray bullets! | |
self.cycles = self.maxCycles --again. should make a CONST | |
sound(DATA, "ZgNAJwBDE1xPITlPiEBdPW8lCD8AAAAARQBifzRAOFQiGloU") | |
b = Bullet({ | |
pos = vec2(self.pos.x, self.pos.y), | |
name = "Bullet"..tostring(math.random(1000)), | |
vector = vector , damage = self.damage, | |
impactDamage = self.impactDamage, | |
speed = self.FireSpeed | |
}) | |
table.insert(bullets, b) | |
end | |
end | |
end | |
function Attacker:CheckForImpact() | |
local rbx = self.pos.x - player.width | |
local rax = self.pos.x + player.width | |
local rby = self.pos.y - player.height | |
local ray = self.pos.y + player.height | |
if (rbx < player.screenpos.x+worldpos.x and rax > player.screenpos.x+worldpos.x and | |
rby < player.screenpos.y and ray > player.screenpos.y) then | |
player:IsHit(self.impactDamage) | |
self:IsHit(player.shields) --player damages item too! | |
self.points=math.ceil(math.ceil(self.points/100)*25) -- if player bumps, reduce points! | |
RemoveEnemy(self.name) | |
end | |
end | |
function Attacker:IsHit(damage) | |
--shot | |
self.health = self.health - damage | |
sound(SOUND_HIT, 8780) | |
sound(DATA, "ZgNADgJBQkFHQ0BAAAAAAMK++z4tmeg9NABAf0JAQEAVO0BB", .2) | |
end |
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
---- | |
-- anything fired is a bullet...anything shot | |
Bullet = class() | |
function Bullet:init(params) | |
assert(params.name, "All bullets must have a name") | |
self.name = params.name | |
self.pos = params.pos | |
self.speed = params.speed or 10 | |
self.damage = params.damage or 1 -- | |
-- self.owner = params.owner --who fired? not neeeded | |
self.cycles = params.cycles or 60 --num frames to keep bullet | |
self.vector = params.vector | |
self.img = params.img or readImage("Tyrian Remastered:Bullet Fire A") | |
end | |
function Bullet:Heartbeat() | |
self:Move() | |
self:CheckForHit() | |
self:draw() | |
end | |
function Bullet:Move() | |
self.cycles = self.cycles - 1 | |
self.pos.x = self.pos.x + (self.vector.x ) * self.speed | |
self.pos.y = self.pos.y + (self.vector.y ) * self.speed | |
if self.cycles < 1 or (self.pos.y >TOP-50 or self.pos.y <1) then | |
RemoveBullet(self.name) | |
else | |
self.pos = CorrectPosition(self.pos) | |
end | |
end | |
function Bullet:draw() | |
-- ac if its a mine use a different drawing style? | |
sprite(self.img, self.pos.x-worldpos.x, self.pos.y) | |
-- draw bullet on hud | |
pushMatrix() | |
if (string.find(self.name, "Player") ) then | |
stroke(110, 56, 68, 255) | |
else | |
stroke(216, 216, 216, 255) | |
end | |
ellipse(self.pos.x/ratio+HUD_LEFT, self.pos.y/ratioy+TOP, 2,2) | |
popMatrix() | |
end | |
function Bullet:CheckForHit() | |
local rbx = self.pos.x - player.width -- before | |
local rax = self.pos.x + player.width -- after yes, bad naming convention! | |
local rby = self.pos.y - player.height | |
local ray = self.pos.y + player.height | |
if (rbx < (player.screenpos.x+worldpos.x) and rax > (player.screenpos.x+worldpos.x) and | |
(rby < player.screenpos.y and ray > player.screenpos.y)) then | |
if not (string.find(self.name, "Player") ) then -- fired from enemy | |
player:IsHit(self.damage) | |
RemoveBullet(self.name) | |
end | |
end | |
--see if a player bullet hit anything! | |
if (string.find(self.name, "Player") ) then | |
for k,v in pairs(enemies) do | |
local minx = v.pos.x - v.width | |
local maxx = v.pos.x + v.width | |
local miny = v.pos.y - v.height | |
local maxy = v.pos.y + v.height | |
if (minx < self.pos.x and maxx > self.pos.x and | |
miny < self.pos.y and maxy > self.pos.y) then | |
v:IsHit(1) -- bullets do 1 pt damage. | |
if v.health < 1 then RemoveEnemy(v.name) end --should this be in each class? | |
RemoveBullet(self.name) | |
end | |
-- see if player hit a guy with a bullet. | |
for k,v in pairs(guys) do | |
local minx = v.pos.x - v.width | |
local maxx = v.pos.x + v.width | |
local miny = v.pos.y - v.height | |
local maxy = v.pos.y + v.height | |
if (minx < self.pos.x and maxx > self.pos.x and | |
miny < self.pos.y and maxy > self.pos.y) then | |
v:IsHit(self.shields) --i shot a guy. Big ouch. | |
RemoveBullet(self.name) | |
end | |
end | |
end | |
end | |
end |
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
---- | |
------------------------------------------------------------------------------- | |
-- Default options. | |
-- @class table | |
-- @name default_options | |
-- @field yield_frequency Determines how many iterations of a loop should be | |
-- executed before yielding. Set to -1 to never yield in a loop. | |
-- @field arrow_head_ratio Ratio of arrow head compared to its length. | |
-- @field arrow_head_angle Determines how pointy arrow is in radians. | |
------------------------------------------------------------------------------- | |
local default_options = { | |
yield_frequency = 15, | |
arrow_head_ratio = 0.4, | |
arrow_head_angle = 0.25 | |
} | |
------------------------------------------------------------------------------ | |
-- Set yield frequency. | |
-- @see default_options | |
------------------------------------------------------------------------------- | |
function SetYieldFreq (freq) | |
default_options.yield_frequency = freq | |
return true | |
end | |
------------------------------------------------------------------------------ | |
-- Set arrow head ratio. | |
-- @see default_options | |
------------------------------------------------------------------------------- | |
function SetArrowHeadRatio (ratio) | |
default_options.arrow_head_ratio = ratio | |
return true | |
end | |
------------------------------------------------------------------------------ | |
-- Set arrow head angle. | |
-- @see default_options | |
------------------------------------------------------------------------------- | |
function SetArrowHeadAngle (angle) | |
default_options.arrow_head_angle = angle | |
return true | |
end | |
------------------------------------------------------------------------------- | |
--- AC: draw on the canvas, codea style | |
------------------------------------------------------------------------------- | |
function WhiteboardDraw(x1,y1,x2,y2) | |
pushStyle() | |
stroke(255, 6, 0, 255) | |
strokeWidth(40) | |
line(x1,y1,x2,y2) | |
popStyle() | |
end | |
------------------------------------------------------------------------------- | |
--- Draw an arrow pointing at a provided point. | |
-- @param x1 x-coord of arrow head. | |
-- @param y1 y-coord of arrow head. | |
-- @param length length of arrow from head to tail. | |
-- @param bearing bearing arrow should point. | |
-- @usage draw.ArrowAt4f(0.0,0.0,10.0,math.pi*0.25) | |
-- @see default_options | |
-- @see ArrowAt3v, ArrowFrom3v, ArrowFrom4f | |
------------------------------------------------------------------------------- | |
function ArrowAt4f (x1, y1, length, bearing) | |
local head_ratio = default_options.arrow_head_ratio | |
local head_angle = default_options.arrow_head_angle | |
--arrow body | |
local angle = math.pi + bearing | |
local x2 = x1 + (math.sin(angle) * length) | |
local y2 = y1 + (math.cos(angle) * length) | |
WhiteboardDraw(x1, y1, x2, y2) | |
--arrow head left | |
angle = (math.pi * (1.0 - head_angle)) + bearing | |
x2 = x1 + (math.sin(angle) * length * head_ratio) | |
y2 = y1 + (math.cos(angle) * length * head_ratio) | |
WhiteboardDraw(x1, y1, x2, y2) | |
--arrow head right | |
angle = (math.pi * (1.0 + head_angle)) + bearing | |
x2 = x1 + (math.sin(angle) * length * head_ratio) | |
y2 = y1 + (math.cos(angle) * length * head_ratio) | |
WhiteboardDraw(x1, y1, x2, y2) | |
return true | |
end | |
------------------------------------------------------------------------------- | |
--- Draw an arrow pointing at a provided point. | |
-- @param xy1 table with the x-y coords for arrow head | |
-- @param length length of arrow from head to tail. | |
-- @param bearing bearing arrow should point. | |
-- @usage draw.ArrowAt3v({0.0,0.0},10.0,math.pi*0.25) | |
-- @see default_options | |
-- @see ArrowAt4f, ArrowFrom3v, ArrowFrom4f | |
------------------------------------------------------------------------------- | |
function ArrowAt3v (xy1, length, bearing) | |
ArrowAt4f (xy1[1], xy1[2], length, bearing) | |
return true | |
end | |
------------------------------------------------------------------------------- | |
--- Draw an arrow pointing from a provided point. | |
-- @param x1 x-coord of arrow tail. | |
-- @param y1 y-coord of arrow tail. | |
-- @param length length of arrow from head to tail. | |
-- @param bearing bearing arrow should point. | |
-- @usage draw.ArrowFrom4f(15.0,0.0,10.0,math.pi*0.56) | |
-- @see default_options | |
-- @see ArrowAt3v, ArrowAt4f, ArrowFrom3v | |
------------------------------------------------------------------------------- | |
function ArrowFrom4f (x1, y1, length, bearing) | |
local head_ratio = default_options.arrow_head_ratio | |
local head_angle = default_options.arrow_head_angle | |
--arrow body | |
local angle = bearing | |
local x2 = x1 + (math.sin(angle) * length) | |
local y2 = y1 + (math.cos(angle) * length) | |
WhiteboardDraw(x1, y1, x2, y2) | |
--arrow head left | |
x1 = x2 | |
y1 = y2 | |
angle = bearing - (head_angle * math.pi) | |
x2 = x1 - (math.sin(angle) * length * head_ratio) | |
y2 = y1 - (math.cos(angle) * length * head_ratio) | |
WhiteboardDraw(x1, y1, x2, y2) | |
--arrow head right | |
angle = bearing + (head_angle * math.pi) | |
x2 = x1 - (math.sin(angle) * length * head_ratio) | |
y2 = y1 - (math.cos(angle) * length * head_ratio) | |
WhiteboardDraw(x1, y1, x2, y2) | |
return true | |
end | |
------------------------------------------------------------------------------- | |
--- Draw an arrow pointing from a provided point. | |
-- @param xy1 x-y coord for arrow tail | |
-- @param length length of arrow from head to tail. | |
-- @param bearing bearing arrow should point. | |
-- @usage draw.ArrowFrom3v({15.0,0.0},10.0,math.pi*0.56) | |
-- @see default_options | |
-- @see ArrowAt3v, ArrowAt4f, ArrowFrom4f | |
------------------------------------------------------------------------------- | |
function ArrowFrom3v (xy1, length, bearing) | |
ArrowFrom4f (xy1[1], xy1[2], length, bearing) | |
return true | |
end | |
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
---- | |
-- subset of the old school explode code form forums | |
NUMSTARS = 50 | |
COLOURS = { | |
color(255, 0, 0, 125), | |
color(255, 227, 0, 125), | |
color(99, 255, 0, 125), | |
color(0, 140, 255, 125), | |
color(238, 0, 255, 125), | |
color(255, 156, 0, 125), | |
color(0, 255, 189, 125), | |
color(255, 0, 146, 125) | |
} | |
-- number of particles of each firework | |
Firework_Parts=150 | |
Life=30 | |
Life_Variation=100 | |
Air_Resistance=0.1 | |
PartSize=15 | |
PartSize_Variation=50 | |
Velocity=50 | |
Velocity_Variation=100 | |
Color_Variation=50 | |
Explode = class() | |
function Explode:init() | |
self.fireworks={} | |
end | |
function Explode:draw() | |
-- draw fireworks | |
for k,fw in pairs(self.fireworks) do | |
if fw:isDead() then | |
self.fireworks[k] = nil | |
else | |
pushStyle() | |
fw:draw() | |
popStyle() | |
end | |
end | |
end | |
function Explode:createFirework(x,y,color) | |
--local pos = self:freePos(self.fireworks) | |
self.fireworks[1] = Firework(x,y,color) | |
end | |
function Explode:freePos(list) | |
return self:freePosRecursive(list,1) | |
end | |
function Explode:freePosRecursive(list,n) | |
if list[n] == nil then | |
return n | |
else | |
return self:freePosRecursive(list,n+1) | |
end | |
end |
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
---- | |
Firework = class() | |
function Firework:init(x,y,colour) | |
-- you can accept and set parameters here | |
self.p = {} | |
self.numParticles = Firework_Parts | |
for i=1,self.numParticles do | |
local psize = genNumber(PartSize,PartSize_Variation) | |
local v = vec2(math.random(-100,100),math.random(-100,100)) | |
v = v:normalize() | |
v = v * genNumber(Velocity,Velocity_Variation) | |
local c = color(genNumber(colour.r,Color_Variation), | |
genNumber(colour.g,Color_Variation), | |
genNumber(colour.b,Color_Variation), | |
colour.a | |
) | |
self.p[i] = Particle(x, | |
y, | |
psize, | |
genNumber(Life,Life_Variation), | |
c, | |
v) | |
--self.p[i].sprite = Sprite:GetImage() | |
end | |
end | |
function Firework:draw() | |
local resistance = 1/(Air_Resistance + 1) | |
local g = vec2(Gravity.x,Gravity.y) | |
for i=1,self.numParticles do | |
local p = self.p[i] | |
p.x = p.x + p.v.x | |
p.y = p.y + p.v.y | |
p.v = p.v + g | |
p.v = p.v * resistance | |
local size = math.random(PartSize) * (p.lifeLeft/p.life) | |
p.width = size | |
p.height = size | |
p:draw() | |
end | |
end | |
function Firework:isDead() | |
for i=1,self.numParticles do | |
p = self.p[i] | |
if p.lifeLeft ~= 0 and | |
(p.x>0 and p.x<WIDTH and p.y>0 and p.y<HEIGHT)then | |
return false | |
end | |
end | |
return true | |
end |
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
---- | |
FlashScore = class() | |
-- used to display a score on the screen. builds ina delay for x secs. | |
function FlashScore:init(params) | |
-- you can accept and set parameters here | |
self.time = params.time or 5 --seconds | |
self.text = params.text or "..." -- display text | |
self.pos = params.pos -- where in the world to show it. | |
self.startTime = nil | |
self:start() -- auto start | |
end | |
function FlashScore:start() | |
self.startTime = ElapsedTime | |
end | |
-- false means its done and should be purged. | |
function FlashScore:active() | |
if self.startTime == nil then return false end | |
return ElapsedTime - self.startTime < self.time | |
end | |
function FlashScore:draw() | |
if self.startTime == nil then return end | |
if ElapsedTime - self.startTime < self.time then | |
pushStyle() | |
font("MyriadPro-Bold") | |
fontSize(16) | |
r=math.random(255) | |
g=math.random(255) | |
b=math.random(255) | |
fill(r,g,b,255) | |
text(self.text, self.pos.x, self.pos.y) | |
popStyle() | |
end | |
end | |
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
---- | |
-- a guy. points/goal. | |
Guy = class() | |
function Guy:init(param) | |
assert(param.name, "All guys must have a name") | |
self.pos = param.pos | |
self.name = param.name | |
self.fall=1 | |
self.isGrabbed=false --did an alien get me? | |
self.onShip = false --am I attached to the ship? | |
self.img = imgGuy | |
self.width = self.img.width | |
self.height = self.img.height | |
self.points = 0 | |
self.movement = (math.random(10) -5) / 10 --determined how fast and how far. | |
end | |
function Guy:Heartbeat() | |
self:Move() | |
self:draw() | |
end | |
function Guy:Move() | |
if (self.isGrabbed == true or self.onShip==true) then return end -- owner will move it | |
if self.pos.y > 15 then | |
self.fall=self.fall*1.003 | |
self.pos.y=self.pos.y - self.fall | |
else --we are at the ground. | |
if self.fall > 2 then | |
-- die, fell too fast | |
RemoveGuy(self.name) | |
else | |
self.fall = 1 --should fix this logic | |
self.pos.x = self.pos.x - self.movement | |
--AC: HACK!!! stop invaders from getting near "end" of world (0/10000) | |
if self.pos.x < 15 or self.pos.x > WORLDSIZE - 15 then | |
self.pos.x = self.pos.x - (-1 * (self.movement* 15)) --"jump" if bad to begin with | |
self.movement = -1 *self.movement --turn around | |
end | |
self.pos = CorrectPosition(self.pos) | |
end | |
end | |
end | |
function Guy:draw() | |
sprite(self.img, self.pos.x-worldpos.x, self.pos.y) | |
end | |
function Guy:IsHit() | |
RemoveGuy(self.name) | |
end | |
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
---- | |
--helpers | |
demo = false --demo mode - occurs if menu is up with nothing going on. | |
archive = false --set to false when compiling for tests | |
release = false --if we are Building XCode for the iPad | |
debug = true | |
HUD_HEIGHT =75 | |
TOP = HEIGHT - HUD_HEIGHT | |
HUD_WIDTH = WIDTH/2 | |
--if (release==true) then | |
if ContentScaleFactor==1 then | |
HUD_LEFT = HUD_WIDTH/2.0 --NON-RETINA? | |
else | |
HUD_LEFT = HUD_WIDTH/1.2 | |
end | |
WORLDSIZE = 10000 +WIDTH | |
WORLDSCREENSIZE = WORLDSIZE - WIDTH --so the world rendered | |
DEMODELAY = 10 --seconds before a demo starts | |
--used in several classes to draw on the HUD directly. | |
ratio = WORLDSIZE / HUD_WIDTH | |
ratioy = TOP / HUD_HEIGHT | |
function StartNewGame() | |
enemies={} -- all enemies | |
bonuses={} -- shields currently | |
guys = {} | |
bullets = {} --all bullets. players have bullet names of "player" | |
score = 0 | |
hyperspace = false | |
lives=3 | |
nextLife = 10000 | |
wave = 1 -- -what level are we on? | |
subwave=0 --as we advance... | |
saucerCount = 1 | |
--VirtualStick variables | |
dx=0; dy=0 --stick's delta x/y | |
steerpos = vec2(0,0) --redndant? | |
steer = vec2(0,0) --redundant? | |
speed = 15 -- pixels per second | |
worldpos = vec2(200, 0) --screen position - NOT THE SAME AS SHIP POSITION! | |
player=Ship() --ship has it's own screen offset so we can "turn around" | |
explode = Explode() -- any explosions we need | |
fs=FlashScore({time=5, text="", pos=vec2(100,300)}) -- as we need onscreen scores | |
startPause=ElapsedTime | |
GetHighScores() | |
end | |
function Demo(mode) | |
screenTime =ElapsedTime | |
if mode==true then | |
lives = 3 | |
demo = true | |
StartNewGame() | |
wave = math.random(3) + 10 --show lots of stuff | |
CreateGuys() --force some guys... | |
CreateEnemies(wave) | |
game = ScreenGame() | |
player=Ship() | |
player.shields = 250 | |
lastBullet = 0 --important! we don't fire otherwise! | |
GAMESTATE = GAMESTATE_PLAYING -- start the demo now | |
else | |
demo= false | |
ScreenTitle:init() | |
GAMESTATE = GAMESTATE_MENU | |
StartNewGame() | |
end | |
end | |
function CreateGuys() | |
guys = {} --clear any existing guys. | |
for x=1, 10 do | |
g = Guy({pos = vec2(math.random(WORLDSCREENSIZE), 25), name="Guy"..x} ) | |
table.insert(guys, g) | |
end | |
hyperspace=false | |
end | |
-- now we have waves of enemies so les on the screen at once... | |
function CreateEnemies(wave) | |
--1/5/2013: to try to manage this better, moved CreateGuys spawning here. | |
if math.ceil((wave-1)/5) == (wave-1)/5 and | |
subwave==math.floor(wave / 3) then --subwave code from ScreenGame | |
CreateGuys() | |
end | |
bonuses={} --clear any that were left around | |
--every 5th wave a spawner is sent... | |
--points will double every hit, so the base here is misleading... | |
--we reduced damage because the ship hits too hard! | |
if math.ceil((wave-1)/5) == (wave-1)/5 and wave > 1 then | |
local i = Spawner({pos=vec2(5000, TOP-75), | |
img = imgSpawner, name="Spawner"..math.random(1000), cycles=10, points=25, | |
damage = 10, impactDamage = 5000, speed = 2 + math.random(1,wave/5), | |
FireSpeed = 10, health = math.ceil(4 + wave / 5), spawnCycle=120, | |
hudColor = color(255, 0, 0, 255), | |
drawparams = anim[SPAWNER] | |
}) | |
table.insert(enemies, i) | |
else | |
--do the regular enemy list | |
num = 8 + wave | |
for x=1,num do | |
local i = Invader({pos=vec2(math.random(WORLDSCREENSIZE), TOP-50), | |
img = imgAlien, name="Invader"..x, cycles=240, points=150, | |
damage = 1, impactDamage = 5, speed = math.random() + wave/20, | |
drawparams = anim[3], | |
FireSpeed = 7}) | |
--only mutants in space! | |
if hyperspace==true then i:Mutate() end | |
table.insert(enemies, i) | |
end | |
if wave > 5 then | |
for x=1, ((wave-5)/4)+1 do | |
local i = Star({pos=vec2(math.random(WORLDSCREENSIZE), TOP-50), img = imgStar, | |
name="Star"..x, speed = .2, cycles=9999, points=1000, looking=false, | |
damage = 1, impactDamage = 20, hudColor = color(155, 26, 139, 255), | |
drawparams = anim[5], | |
}) | |
table.insert(enemies, i) | |
end | |
for x=1, (wave-5)/3+1 do | |
local i = MineLayer({pos=vec2(math.random(WORLDSCREENSIZE), TOP-50), | |
img = imgMineLayer, | |
name="MineLayer"..x, speed = .6, cycles=480, points=250, looking=false, | |
damage = 5, impactDamage = 10, | |
drawparams = anim[7], | |
}) | |
table.insert(enemies, i) | |
end | |
end | |
saucerCount = 1 --reset JIC | |
if wave > 0 then | |
for x=1,(math.ceil(wave/5)) do | |
Saucer:Spawn(true) --spawn for a level. do not use for singles. | |
end | |
end | |
--all enemies created | |
end | |
--shields - in a seperate table now. rendered seperate from enemies | |
--consider making a new function? | |
local sv = 1 --every 5th wave, make shields stronger. | |
if math.ceil((wave-1)/5) == (wave-1)/5 then sv = 2 end | |
for x=1,(math.ceil(wave/4)+1) do | |
local i=Shields({pos=vec2(math.random(WORLDSIZE-1000)+250, math.random(TOP-50)+25), | |
img = imgShields, | |
name="Shields"..x, speed = .02, cycles=0, points=250, looking=false, | |
damage = 0, impactDamage = 0, shieldvalue=50*sv, | |
hudColor = color(11, 246, 2, 255), | |
drawparams = anim[10], | |
}) | |
--this is a 69,64 sized object...size on drawparams scaled it down... | |
table.insert(bonuses, i) | |
end | |
end | |
function LoadAssets() | |
if (release ~= true) then | |
assetPrefix="Dropbox:" | |
else | |
assetPrefix ="CofenderGfx2:" | |
end | |
imgAlien = readImage(assetPrefix.."alien") | |
imgMutant = readImage(assetPrefix.."mutant") | |
imgMutantSanta = readImage(assetPrefix.."mutantII") -- special | |
imgGuy = readImage(assetPrefix.."guy") | |
imgShip = readImage(assetPrefix.."shipstill") | |
imgThrust = readImage(assetPrefix.."shipmove") | |
imgStar = readImage(assetPrefix.."star") | |
imgStarling = readImage(assetPrefix.."starling") | |
imgMineLayer = readImage(assetPrefix.."minelayer") | |
imgSaucer = readImage(assetPrefix.."saucer") | |
imgBomb = readImage(assetPrefix.."bomb") | |
imgShields=readImage(assetPrefix .. "ShieldBall2") --this one is altased, next isnt. | |
imgShieldDeflect=readImage(assetPrefix .. "ShieldBall") --render a shield | |
imgSpawner = readImage(assetPrefix.."Spawner") | |
--for title/scoring screen | |
assets ={ imgShip, imgGuy, imgAlien, imgMutant, imgStar, imgStarling, | |
imgMineLayer, imgSaucer,imgSpawner,imgShields,} | |
SHIP=1 GUY=2 ALIEN=3 MUTANT=4 STAR=5 STARLING=6 | |
MINELAYER=7 SAUCER=8 SPAWNER=9 SHIELDS=10 | |
-- 11 is the special mutant. not rendered on title screeen, just reuse mutant. same anim | |
anim = { | |
nil, | |
nil, | |
{img=assets[ALIEN], fps=10, size = vec2(30,24), frames = 5, rows = 1, row=0}, | |
{img=assets[MUTANT], fps=10, size = vec2(30,24), frames = 5, rows = 1, row=0}, | |
{img=assets[STAR], fps=10, size = vec2(22,22), frames = 5, rows = 1, row=0}, | |
{img=assets[STARLING], fps=10, size = vec2(20,10), frames = 5, rows = 1, row=0}, | |
{img=assets[MINELAYER], fps=10, size = vec2(24,18), frames = 5, rows = 1, row=0}, | |
{img=assets[SAUCER], fps=10, size = vec2(30,16), frames = 5, rows = 1, row=0}, | |
{img=assets[SPAWNER], fps=10, size = vec2(81,20), frames = 3, rows = 1, row=0}, | |
{img=assets[SHIELDS], fps=10, size = vec2(32,32), frames = 5, rows = 1, row=0}, | |
} | |
end | |
--if we go past the end of the world (10000-WIDTH, NOT 10000!) then wrap around | |
--non viusally. | |
function CorrectPosition(pos) | |
if pos.x>WORLDSIZE-1 then pos.x=0 end | |
if pos.x< 0 then pos.x=WORLDSCREENSIZE - 1 end | |
if pos.y > TOP-12 then pos.y = TOP-12 end | |
if pos.y < 10 then pos.y = 10 end | |
return pos | |
end | |
--Sloppy drawing - we aren't pushing or popping styles. | |
function DrawHUD() | |
rectMode(CORNER) | |
noFill() | |
noSmooth() | |
stroke(29, 38, 232, 255) | |
-- lineCapMode(SQUARE) | |
strokeWidth(1) | |
cur=vec2(pts[#pts-1]/HUD_HEIGHT, HUD_HEIGHT) | |
rect(HUD_LEFT, TOP, HUD_WIDTH, HUD_HEIGHT) | |
print(HUD_LEFT) | |
print(TOP) | |
print(HUD_WIDTH) | |
print(HUD_HEIGHT) | |
print("===") | |
if (hyperspace == false) then | |
--draw the world in the HUD | |
stroke(168, 168, 168, 255) | |
strokeWidth(1) | |
for x=1,#pts,2 do | |
cur=vec2(pts[x], pts[x+1]) | |
if cur.x > WORLDSIZE then cur.x=WORLDSIZE end | |
line(prev.x/ratio+HUD_LEFT, prev.y/ratio+(TOP), | |
cur.x/ratio+(HUD_LEFT),cur.y/ratio+(TOP)) | |
prev= cur | |
end | |
end | |
--draw the screen bracket | |
stroke(206, 33, 33, 255) | |
strokeWidth(4) | |
--ratio/scale | |
local l = (worldpos.x / WORLDSIZE) * HUD_WIDTH | |
local bar = ratio | |
--fix bar sizes | |
line(HUD_LEFT+l, TOP+2, HUD_LEFT+l+ratio, TOP+2) | |
line(HUD_LEFT+l, TOP + HUD_HEIGHT-2, HUD_LEFT+l+ratio, TOP+ HUD_HEIGHT-2) | |
--draw ship on HUD - consider consolidating all of these draws into the helper. | |
stroke(250, 211, 13, 255) | |
ellipse((player.screenpos.x+worldpos.x)/ratio+HUD_LEFT, player.screenpos.y/ratioy+TOP, 2,2) | |
if player.shields==nil then player.shields=0 end | |
--draw shield bar | |
stroke(115, 81, 38, 255) | |
fill(0, 0, 0, 255) | |
rect(HUD_LEFT -180, TOP + 20, 500 / 3, 15) | |
fill(172, 31, 31, 255) | |
rect(HUD_LEFT - 180, TOP + 20, player.shields / 3, 15) | |
--draw lives left | |
sprite(imgShip, HUD_LEFT+HUD_WIDTH+player.width/2, HEIGHT-(player.height) ) | |
font("ArialRoundedMTBold") | |
fontSize(8) | |
text(math.floor(player.shields), HUD_LEFT - 100, TOP+10) --shields left | |
fontSize(20) | |
text("shields", 75, TOP+28) | |
text("x" .. lives, HUD_LEFT+HUD_WIDTH+player.width+8, HEIGHT-(player.height) ) | |
-- bomb icon goes here | |
sprite(imgBomb, HUD_LEFT+HUD_WIDTH+player.width/2, | |
HEIGHT-player.height-imgBomb.height ) | |
text("x" .. player.bombs, | |
HUD_LEFT+HUD_WIDTH+player.width+8, | |
HEIGHT-player.height-imgBomb.height ) | |
text("score", 900, HEIGHT-15) | |
text(score, 900,HEIGHT-35) | |
text("wave", 60, HEIGHT-15) | |
text(wave, 100, HEIGHT-15) | |
if DEBUG then | |
text(math.floor(waveTime), 20,HEIGHT-15) | |
end | |
popStyle() | |
end | |
function LostLife() | |
lives=lives-1 | |
bullets={} --clear all bullets! | |
if lives <1 then | |
ScreenEnd:init() | |
GAMESTATE=GAMESTATE_ENDED | |
else | |
ShipActive=false | |
--explode parameters | |
life=600 | |
explode:createFirework(player.screenpos.x,player.screenpos.y,color(0,0,255,255)) | |
player.shields= player.STARTSHIELDS | |
player.screenpos.y = WIDTH/2 --move to "center" again. | |
--and somewhere else - starlings are bad news! | |
worldpos.x = math.random(WORLDSCREENSIZE) | |
CorrectPosition(player.screenpos) | |
CorrectPosition(worldpos) | |
for k,v in pairs(guys) do | |
if v.onShip==true then | |
v:IsHit(1) --he MIGHT survive :P | |
else | |
v.pos.y = 5 --move all guys to ground | |
v.fall=1 | |
v.isGrabbed=false | |
v.onShip = false | |
--AC If an enemy has a guy, he will still have them when we come back... | |
--but that's OK. | |
end | |
end | |
GAMESTATE=GAMESTATE_SHIPEXPLODE | |
end | |
end | |
function RemoveGuy(name) | |
for k,v in pairs(guys) do | |
if v.name == name then | |
--if an invader was trying to get him, mark the invader as free again | |
for kk,vv in pairs(enemies) do | |
if (vv.myHuman ~= nil) then | |
if (vv.myHuman.name == v.name) then | |
vv.myHuman = nil | |
vv.looking = true | |
end | |
end | |
end | |
local n = math.random(#COLOURS) | |
explode:createFirework(v.pos.x-worldpos.x,v.pos.y,COLOURS[n]) | |
table.remove(guys, k) | |
-- if all guys are gone, we go into hyperspace | |
if (#guys == 0) then HyperSpace() end | |
return | |
end | |
end | |
end | |
function RemoveBullet(name) | |
for k,v in pairs(bullets) do | |
if v.name == name then | |
table.remove(bullets, k) | |
return | |
end | |
end | |
end | |
--Removes actors from the enemies table. | |
function RemoveEnemy(name) | |
for k,v in pairs(enemies) do | |
if v.name == name then | |
if v.health < 1 then | |
if v.myHuman ~= nil then v.myHuman.isGrabbed = false end | |
AddScore(v.points, v.pos-worldpos) | |
table.remove(enemies, k) | |
return | |
end | |
end | |
end | |
end | |
--if we hit a bonus (shields so far) remove them from the other table. | |
function RemoveBonus(name) | |
for k,v in pairs(bonuses) do | |
if v.name == name then | |
if v.points > 0 then --only show value if it's worth something! | |
AddScore(v.points, v.pos-worldpos) | |
end | |
table.remove(bonuses, k) | |
return | |
end | |
end | |
end | |
--determine if we made enough points for out next life. | |
function AddScore(points, position) | |
if position ~= nil then | |
fs=FlashScore({time=2, text=points, pos=vec2(position.x, position.y)}) | |
fs:start() | |
table.insert(rewards, fs) | |
end | |
--redundant math here.HACKY!!!..should store a "next ship" value globally. | |
curLives = math.ceil(score/nextLife) | |
score = score + points | |
newLives = math.ceil(score/nextLife) | |
if newLives > curLives and curLives > 0 then | |
lives = lives + 1 | |
player.bombs = player.bombs + 1 | |
end | |
end | |
function SummarizeLevel() | |
--if we have sublevels, load before we close the level | |
if subwave>0 then | |
CreateEnemies(wave) | |
subwave = subwave - 1 | |
return | |
end | |
--the "pause" screen is responsible for rewards | |
startPause = ElapsedTime | |
wave = wave + 1 -- AVOID INFINITE WAVE LOOP | |
sound(DATA, "ZgFANw45VmUwI0F/xAy+PhWSYj8l7Ds/LgBSf0VdKj8/WDt/", .7) | |
--give bonus points | |
AddScore( (#guys) * 100 , nil) | |
--player.shields = Ship.STARTSHIELDS --allow player to retain their shielding. | |
GAMESTATE = GAMESTATE_LEVELCOMPLETE | |
bullets={} | |
subwave=math.floor(wave / 3) | |
end | |
function HyperSpace() | |
hyperspace = true | |
for k,v in pairs(enemies) do | |
if string.find(v.name , "Invader") and v.isMutant == false then | |
v:Mutate() | |
end | |
end | |
end | |
function ShowHighScores() | |
local size = 36 | |
GetHighScores() | |
if #highscores < 1 then ResetHighScores() GetHighScores() end | |
pushStyle() | |
font("ArialRoundedMTBold") | |
fontSize(size) | |
for x=1,5 do | |
if sorted[highscores[x].score] == nil then sorted[highscores[x].score]="***" end | |
text(highscores[x].score, 400, 425 - (x*size)) | |
text(sorted[highscores[x].score], 550, 425 - (x*size)) | |
end | |
popStyle() | |
end | |
function SaveHighScores() | |
h=JSON:encode(highscores) | |
saveLocalData("Cofender",h) | |
end | |
function GetHighScores() | |
h=readLocalData("Cofender") | |
if h == nil then | |
ResetHighScores() | |
end | |
highscores=JSON:decode(h) | |
sorted = {} | |
for k,v in pairs(highscores) do sorted[v.score] = v.player end | |
table.sort(highscores, function (a,b) return a.score > b.score end) | |
end | |
function ResetHighScores() | |
highscores = { | |
{score=0, player="AC"}, | |
{score=1000, player="AC"}, | |
{score=1250, player="AC"}, | |
{score=1500, player="AC"}, | |
{score=2000, player="AC"}, | |
} | |
SaveHighScores() | |
end | |
--to override stick drawing! works, "self" below is VirtualStick... | |
function VirtualStick:draw() | |
if self.touchId ~= nil then | |
pushStyle() | |
ellipseMode(RADIUS) | |
strokeWidth(1) | |
stroke(48, 44, 44, 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) | |
fill(63, 66, 166, 255) | |
ellipse(0, 0, 25, 25) | |
popMatrix() | |
popStyle() | |
end | |
end | |
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
---- | |
-- a creature to steal guys. turns into mutants. | |
Invader = class(Attacker) | |
function Invader:init(params) | |
Attacker.init(self,params) | |
end | |
function Invader:Heartbeat() | |
if (self.isMutant == false) then | |
self:GetHuman() | |
self:CanPromote() | |
end | |
Attacker.Heartbeat(self) | |
end | |
function Invader:Move() | |
if (self.isMutant == true) then | |
-- do mutatny stuff - make them track to player position | |
local dx = (math.random() - .5) * 3 | |
local dy = (math.random() - .5) * 3 | |
local delta = (worldpos + player.screenpos) - self.pos | |
if math.abs(delta.x)>WORLDSIZE/2 then delta=delta *-1 end -- faster the other way... | |
delta = delta + self.oldDelta*self.speed -- | |
delta = delta:normalize() | |
self.pos.x = self.pos.x + (delta.x + dx) * self.speed | |
self.pos.y = self.pos.y + (delta.y + dy) * self.speed | |
self.pos = CorrectPosition(self.pos) | |
return | |
end | |
if (self.looking == true) then | |
if self.pos.y < 35 then | |
self.pos.x=self.pos.x-self.speed | |
else | |
self.pos.y=self.pos.y-self.speed --seek a guy | |
end | |
else | |
self.pos.y=self.pos.y+self.speed --try to steal a guy | |
end | |
if (self.myHuman ~= nil) then -- and move the stolen guy | |
self.myHuman.pos.x = self.pos.x --try to center. HACK!! | |
self.myHuman.pos.y = self.pos.y - self.img.height | |
end | |
self.pos = CorrectPosition(self.pos) | |
end | |
function Invader:GetHuman() | |
if (self.looking == true and self.myHuman == nil) then | |
--try to target/find a human | |
for k,v in pairs(guys) do | |
rbx = self.pos.x - 30 | |
rax = self.pos.x + 30 | |
rby = self.pos.y - 30 | |
ray = self.pos.y + 30 | |
if (rbx < v.pos.x and rax > v.pos.x and rby < v.pos.y and ray > v.pos.y) then | |
if v.isGrabbed == false then -- cause sometimes two invaders grab the same guy! | |
--grab the guy! | |
self.direction = -1 -- go up | |
sound(SOUND_POWERUP, 37637) | |
self.myHuman = v --so we know which guy is converted | |
self.looking = false | |
v.isGrabbed = true | |
end | |
end | |
end | |
end | |
end | |
function Invader:CanPromote() | |
if self.pos.y > TOP - 20 then | |
if self.myHuman ~= nil then | |
self:Mutate() | |
RemoveGuy(self.myHuman.name) | |
self.myHuman = nil | |
else | |
self.looking = true | |
self.direction = 1 | |
end | |
end | |
end | |
function Invader:Mutate() | |
sound(SOUND_HIT, 39749) | |
self.isMutant = true | |
self.looking = false | |
self.points = 300 | |
self.speed = self.speed * 1.5 | |
self.img = imgMutant | |
self.FireSpeed = self.FireSpeed*1.5 | |
self.maxCycles = 60 | |
self.cycles = self.maxCycles | |
if math.random(10) < 2 then | |
self.img = imgMutantSanta --special mutant = stronger, faster | |
self.points = self.points * 1.5 | |
self.speed = self.speed * 1.5 --again...on purpose | |
self.maxCycles= 20 --faster shooting too | |
self.cycles = self.maxCycles | |
self.FireSpeed = self.FireSpeed *1.5 | |
self.damage = 100 -- why? seems high | |
self.predictive=true | |
end | |
self.d:ResetTexture(self.img) | |
end | |
--no need to override draw as we are not dong custom drawing for these guys! | |
--Can safely remove. | |
--function Invader:draw() | |
-- Attacker.draw(self) | |
--end | |
function Invader:touched(touch) | |
Attacker.touched(touch.self) | |
end | |
function Invader:Shoot() | |
self.cycles = self.cycles - 1 | |
if self.cycles < 1 then | |
--call only one or else you get homing bullets! | |
local vector = (worldpos + player.screenpos) - self.pos | |
vector = vector:normalize() | |
local dist = self.pos:dist(player.screenpos + worldpos) | |
if (dist< 1200 and dist > 10) then --don't fire if TOO close! Stray bullets! | |
self.cycles = self.maxCycles --again. should make a CONST | |
if self.isMutant then | |
sound(DATA, "ZgNAJABAOmY8P15PLZloPm8lCD8AAAAARQBiWSFAOFRJDVol") | |
else | |
sound(DATA, "ZgNAJwBDE1xPITlPiEBdPW8lCD8AAAAARQBifzRAOFQiGloU") | |
end | |
b = Bullet({ | |
pos = vec2(self.pos.x, self.pos.y), | |
name = "Bullet"..tostring(math.random(1000)), | |
vector = vector , damage = self.damage, | |
impactDamage = self.impactDamage, speed = self.FireSpeed, | |
cycles = self.maxCycles+60 | |
}) | |
-- ac: bullet life was assoc to two things. need a bullet life variable. | |
table.insert(bullets, b) | |
end | |
end | |
end | |
function Invader:IsHit(damage) | |
if self.myHuman ~= nil then | |
self.myHuman.isGrabbed = false | |
self.myHuman.onShip =false | |
self.myHuman.fall= 1.001 | |
end | |
--after special checks run this to remove otherwise it's too soon. | |
Attacker.IsHit(self, damage) | |
end |
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
VERSION = "1.0.1" | |
PROJECTNAME = "Cofender" | |
---- | |
-- Cofender | |
supportedOrientations(LANDSCAPE_LEFT) | |
displayMode(FULLSCREEN) | |
--external references: AutoGist, Cider, Controllers and Utils. | |
--personally, I have TOO MUCH STUFF in Main :) this is a simple game heartbeat system. | |
function setup() | |
--create an instance of AutoGist | |
--Add this to the project that you would like to backup | |
--@param ProjectName -- must match your project use PROJECTNAME at the top | |
--@param Description of your project | |
--@param version number. I like using a global VERSION at the top. | |
--@param [true|false] true will add an update checker to the gist | |
--If Build is true version number is ignored and a Private gist is created for builds | |
--If Build is false or nil the Version checks for a Release gist update | |
VERSION = "1.0.2" | |
PROJECTNAME = "Cofender" | |
BUILD=false | |
autoGist = AutoGist(PROJECTNAME,"Cofender - a defender clone written with Codea",VERSION,false) | |
autoGist:backup(false) | |
highscores = {} --pull these from a param or a network service | |
-- this is the world map. | |
pts={0,20,1000,20,1200,30, | |
1500,60,1800,120,2500,20,2700,60, | |
4000,300,5500,60,7000,20,7200,40,7400,20,7500,30, | |
8000,60,8500,100,8800,60,9000,20, 11000,20} | |
print.redirect.off() | |
-- define consts | |
GAMESTATE_MENU = 1 | |
GAMESTATE_PLAYING = 2 | |
GAMESTATE_SHIPEXPLODE = 3 | |
GAMESTATE_LEVELCOMPLETE=5 | |
GAMESTATE_ENDED = 4 | |
GAMESTATE_SPLASH =6 | |
GAMESTATE_OPTIONS =7 | |
GAMESTATE_PAUSED = 8 | |
GAMESTATES = { ScreenTitle, ScreenGame, ScreenShipExplode, ScreenEnd, | |
ScreenLevelComplete, ScreenSplash, ScreenOptions , ScreenPaused } | |
ScreenSplash:init() | |
GAMESTATE = GAMESTATE_SPLASH | |
if (archive) then | |
displayMode(STANDARD) | |
saveCode("Cofender") | |
end | |
end | |
-- This function gets called once every frame | |
function draw() | |
-- This sets a dark background color | |
--if not retain then | |
background(0, 0, 0, 255) | |
-- end | |
--state machine | |
GAMESTATES[GAMESTATE]:draw() | |
end | |
function touched(touch) | |
--state machine | |
GAMESTATES[GAMESTATE]:touched(touch) | |
end | |
--to send keys to proper screen for processing | |
function keyboard(key) | |
GAMESTATES[GAMESTATE]:keyboard(key) | |
end | |
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
---- | |
-- lays mines / bullets. | |
MineLayer = class(Attacker) | |
function MineLayer:init(params) | |
Attacker.init(self,params) | |
end | |
function MineLayer:Shoot() | |
--don't shoot anything! | |
end | |
--lays down mines (bullets) wherever it goes. | |
function MineLayer:Move() | |
Attacker.Move(self) --call base move class | |
--we "shoot" mines | |
self.cycles = self.cycles - 1 | |
if self.cycles < 1 then | |
b = Bullet({ | |
pos = vec2(self.pos.x, self.pos.y), | |
name = "Mine"..tostring(math.random(1000)), | |
vector = vec2(0,0) , damage = 100, cycles = 250, | |
img=readImage("Tyrian Remastered:Mine Spiked") | |
}) | |
table.insert(bullets, b) | |
self.cycles = 125 --self.MaxCycles | |
end | |
end |
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
---- | |
Particle = class() | |
Particle.DEFAULT_OPACITY = 125 | |
Particle.DEFAULT_ANGLE = 0 | |
Particle.DEFAULT_MASS = 1 | |
function Particle:init(posx,posy,size,life,colour, | |
velocity,mass,angle,sprite) | |
-- position | |
self.x = posx | |
self.y = posy | |
self.ox = -5 | |
self.oy = 0 | |
-- size | |
self.width = size | |
self.height = size | |
-- color | |
if colour == nil then | |
self.color = color(255, 255, 255, 255) | |
else | |
self.color = colour | |
end | |
-- velocity | |
self.v = velocity | |
-- life | |
self.life = life | |
self.lifeLeft= life | |
-- sprite | |
self.sprite = sprite | |
-- mass | |
if mass == nil then | |
self.mass = DEFAULT_MASS | |
else | |
self.mass = mass | |
end | |
-- angle | |
if angle == nil then | |
self.angle = self.DEFAULT_ANGLE | |
else | |
self.angle = angle | |
end | |
end | |
function Particle:draw() | |
if self.lifeLeft > 0 then | |
self.lifeLeft = self.lifeLeft - 1 | |
end | |
if self.lifeLeft ~= 0 then | |
if self.sprite == nil then | |
fill(self.color) | |
ellipse(self.x,self.y,self.width,self.height) | |
else | |
pushMatrix() | |
translate(self.x,self.y) | |
rotate(self.angle) | |
tint(self.color) | |
sprite(self.sprite,0,0,self.width,self.height) | |
popMatrix() | |
end | |
end | |
end | |
function genNumber(number,variation) | |
ret = variation*0.01*number | |
ret = number + math.random(-ret,ret) | |
return ret | |
end |
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
---- | |
-- the incomplete saucer class. | |
Saucer = class(Attacker) | |
function Saucer:init(params) | |
Attacker.init(self,params) | |
self.spawn = params.spawn or false | |
end | |
function Saucer:Move() | |
--dont hit, just "annoy" | |
local delta = (worldpos + player.screenpos) - self.pos | |
if math.abs(delta.x)>WORLDSIZE/2 then delta=delta *-1 end -- faster the other way... | |
local sv=delta.x/(WIDTH) | |
sv=1-math.abs(sv) | |
--delta.x=delta.x - math.random(240)-120 | |
delta.y = delta.y - 60 -- go underneath | |
delta = delta + self.oldDelta*self.speed | |
delta = delta:normalize() | |
self.pos.x = self.pos.x + (delta.x) * self.speed | |
self.pos.y = self.pos.y + (delta.y) * self.speed | |
self.pos = CorrectPosition(self.pos) | |
if sv >.1 then | |
sound(DATA, "ZgBAd2tLSjVmRmoCAAAAADaUVz0AAAAAAAB0fz9AQBZAX2RG", sv) | |
end | |
end | |
function Saucer:CheckForImpact() | |
local rbx = self.pos.x - player.width | |
local rax = self.pos.x + player.width | |
local rby = self.pos.y - player.height | |
local ray = self.pos.y + player.height | |
if (rbx < player.screenpos.x+worldpos.x and rax > player.screenpos.x+worldpos.x and | |
rby < player.screenpos.y and ray > player.screenpos.y) then | |
player:IsHit(self.impactDamage) --he's on us! | |
--make two new respawnable saucers since you rammed this one...and discuorage that style | |
local drawparams = {img = imgSaucer, fps= 10, size = vec2(31,16), | |
pos = self.pos, move=vec2(0, 0), | |
self.direction , frames = 5, rows = 1, row=0} | |
for x=1,2 do | |
local i = Saucer({pos=vec2(math.random(WORLDSIZE), TOP-50), img = imgSaucer, | |
name="Saucer"..math.random(1000), speed = (wave/3)+4, cycles=90, points=200, | |
looking=false, | |
spawn=true, damage= 35, impactDamage = 50, FireSpeed = 15, | |
drawparams=drawparams | |
}) | |
table.insert(enemies, i) | |
end | |
self:IsHit(player.shields) --player damages item too! | |
self.points=0 -- if player bumps, no points! | |
--or Saucer.IsHit(self, player.shields) | |
RemoveEnemy(self.name) | |
end | |
end | |
--Global static for other functions (ScreenGame) - used to spawn a level of saucers. | |
-- set respawn to true for a saucer that spawns others on impact. | |
function Saucer:Spawn(respawn) | |
local k,v | |
local isLast = 0 | |
for k,v in pairs(enemies) do | |
if (string.find(v.name, "Saucer") ) then isLast = isLast + 1 end | |
end | |
if isLast < 2 then | |
--spawn | |
for x=1,saucerCount do | |
local x = math.random(5000) | |
local y = math.random(TOP-50) | |
local newPos = vec2(x,y) | |
--AC: will make the points for a rammed saucer less...and make them slower | |
--but deliver MUCH MORE damage. | |
local drawparams = {img = imgSaucer, fps= 10, size = vec2(31,16), | |
pos = self.pos, move=vec2(0, 0), | |
self.direction , frames = 5, rows = 1, row=0} | |
local i = Saucer({pos=newPos, img = imgSaucer, | |
name="Saucer"..x, speed = (wave/3)+4, cycles=90, points=200, looking=false, | |
damage= 15, impactDamage =25, spawn = respawn, FireSpeed = 15, | |
drawparams = drawparams | |
}) | |
table.insert(enemies, i) | |
end | |
if (saucerCount < wave) then | |
saucerCount = saucerCount + 1 -- add only if all saucers are gone | |
end | |
end | |
end |
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
---- | |
ScreenEnd = class() | |
function ScreenEnd:init() | |
endController = TapAction { | |
pressed = function(v) | |
for k,v in pairs(UI) do | |
v:touched(CurrentTouch) | |
end | |
end, | |
released = function(v) | |
for k,v in pairs(UI) do | |
v:touched(CurrentTouch) | |
end | |
if bucket < 1 or bucket > 5 then | |
demo=false | |
StartNewGame() | |
GAMESTATE=GAMESTATE_MENU | |
end | |
end | |
} | |
UI= {} --table of controls to render. | |
SetupUI() | |
endController:activate() | |
--check high scores | |
GetHighScores() | |
bucket=0 | |
--not sure this is"right", but it's working... | |
for k=5,1,-1 do | |
if (score > highscores[k].score) then | |
bucket = k | |
end | |
end | |
end | |
function ScreenEnd:draw() | |
font("AmericanTypewriter-Bold") | |
fontSize(28) | |
fill(255, 255, 255, 255) | |
text("Game Over", WIDTH/2, HEIGHT/2) | |
if (demo==true) then | |
ScreenTitle:init() | |
GAMESTATE=GAMESTATE_MENU | |
end | |
if bucket > 0 and bucket < 6 then | |
if (Frame ~= nil) then | |
--we have controls and its library | |
for k,v in pairs(UI) do | |
v:draw() | |
end | |
else | |
--force entry and exit | |
bucket = 0 --just in case? | |
WriteHighScore("YOU") | |
end | |
end | |
if (#explode.fireworks) then explode:draw() end | |
end | |
function ScreenEnd:touched(touch) | |
end | |
--lay out controls for high score entry | |
function SetupUI() | |
if (Frame ~= nil) then | |
textBox1000 = TextBox('', 410, 590, 610, 650, textBox1000_Clicked) | |
textBox1000.fontSize = 48 | |
textBox1000.font ="Courier" | |
textBox1000.background=color(75, 75, 75, 255) | |
textBox1000.foreground=color(256, 161, 78, 256) | |
textBox1000.highlight=color(166, 166, 166, 255) | |
button1001 = TextButton('Save Score', 450, 530, 550, 580, button1001_Clicked) | |
button1001.background=color(119, 158, 199, 255) | |
button1001.highlight=color(64, 192, 66, 255) | |
table.insert(UI, textBox1000) | |
table.insert(UI, button1001) | |
end | |
end | |
function textBox1000_Clicked() | |
--This is if the box is tapped | |
textBox1000.text = string.sub(textBox1000.text, 1,6) | |
end | |
function button1001_Clicked() | |
WriteHighScore(textBox1000.text) | |
end | |
--redundant? Maybe. but I needed to write the score in the draw loop in case cidercontrols | |
--were not presen | |
--AC: Found bug. If score is a TIE, we don't actually erase the old one...and we | |
--appear to ovewrite it (but I'm sure that a sorted[] rendering bug | |
function WriteHighScore(name) | |
name = string.sub(name, 1,6) -- arbitrary 6 | |
table.insert(highscores, bucket, {score= score, player=name}) | |
hideKeyboard() | |
SaveHighScores() | |
GetHighScores() -- this will cause a re-sort, which we need for run #2 | |
-- reset to new game - will show High Scores. | |
demo=false | |
StartNewGame() | |
screenPause = 0 | |
screenTime = ElapsedTime | |
ScreenTitle:init() | |
GAMESTATE=GAMESTATE_MENU | |
end | |
function ScreenEnd:keyboard(key) | |
if CCActiveTextBox then | |
if (key == RETURN) then | |
textBox1000_Clicked() -- filter to 6 | |
-- post the score | |
button1001_Clicked() | |
else | |
CCActiveTextBox:acceptKey(key) | |
end | |
end | |
end | |
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
---- | |
ScreenGame = class() | |
startPause = 0 --when a wave ends, we start a paused timer | |
waveTime = 0 | |
lastBullet = 0 --last time the ship fred a demo bullet | |
nextSaucer = 30 --first one shows, then 10 secs for each group after. | |
rewards={} -- array of fs objects | |
-- make radius via options. | |
function ScreenGame:init() | |
vsController = VirtualStick { | |
radius = 95, deadZoneRadius = 10, | |
moved = function(v) | |
if (demo==true) then | |
Demo(false) | |
else | |
steer = v | |
thrust=true | |
end | |
end, | |
released = function(v) steer = vec2(0,0) thrust=false end | |
} | |
tapController = TapAction { | |
pressed = function(v) | |
end, | |
released = function(v) | |
if (demo==true) then | |
Demo(false) | |
end | |
--if we are in the game do stuff otherwise ignore | |
if GAMESTATE==GAMESTATE_PLAYING then | |
--fire bullet | |
local vector | |
if player.direction == 1 then vector=vec2(-1,0) end | |
if player.direction == -1 then vector=vec2(1,0) end | |
worldpos.y = 0 --always! Otherwise bullet offsets go bad! | |
b = Bullet({ pos = player.screenpos+worldpos, | |
name = "Player"..tostring(math.random(1000)), vector=vector, | |
img = readImage("Tyrian Remastered:Bullet Fire A") , cycles=30} ) | |
b.speed = 45 | |
table.insert(bullets,b) | |
sound(SOUND_SHOOT, 16568) | |
elseif GAMESTATE == GAMESTATE_LEVELCOMPLETE then | |
if (ElapsedTime - startPause > 5) then --gimme a 5 sec pause | |
CreateEnemies(wave) | |
subwave=math.floor(wave / 3) | |
GAMESTATE=GAMESTATE_PLAYING | |
waveTime = 0 | |
startPause = 0 --reset the pause "timer"/counter | |
end | |
elseif GAMESTATE == GAMESTATE_PAUSED then | |
GAMESTATE=GAMESTATE_PLAYING | |
end | |
end | |
} | |
bomb = TapAction { | |
released = function(v) | |
-- make a dead zone buffer | |
if GAMESTATE == GAMESTATE_PLAYING then | |
pb=40 | |
if v.y>HEIGHT - pb and v.x < pb then GAMESTATE = GAMESTATE_PAUSED return end | |
if v.y > HEIGHT/2+125 then player:Bomb() end | |
end | |
end | |
} | |
jump = TapAction { | |
pressed=function(v) retain=true end, | |
released = function(v) | |
retain=false | |
if GAMESTATE == GAMESTATE_PLAYING then | |
if v.y > HEIGHT/2+125 then player:HyperJump() end | |
end | |
end | |
} | |
controller = SplitScreen { | |
left = vsController, | |
right = tapController | |
} | |
buttoncontroller = SplitScreen { | |
left = bomb, | |
right = jump | |
} | |
mastercontroller =SplitScreen { | |
top = buttoncontroller, | |
bottom = controller | |
} | |
--activate the split screen controller | |
mastercontroller:activate() | |
--start the game after everything is initiailized | |
GAMESTATE = GAMESTATE_PLAYING | |
startTime = ElapsedTime ---start the clock! | |
subwave=math.floor(wave / 3) | |
CreateEnemies(wave) | |
--game initalized sound goes here | |
if (#guys==0) then HyperSpace() else hyperspace=false end | |
end | |
function ScreenGame:draw() | |
if GAMESTATE==GAMESTATE_PAUSED then return end | |
waveTime = ElapsedTime - startTime | |
controller:draw() | |
prev=vec2(0,20) | |
DrawHUD() | |
--draw time elapsed - DEBUG | |
if (debug==true) then text(waveTime, 50, HEIGHT-15) end | |
if demo == true then | |
if (#guys==0) then | |
CreateGuys() | |
CreateEnemies(wave) | |
end | |
-- thrust the ship and fire | |
if lastBullet == nil then lastBullet=0 end | |
--if (math.random(100) > 90) then player.direction=player.direction *-1 end | |
if math.ceil(waveTime*2) > lastBullet then | |
steer = vec2(-player.direction, (math.random(100)-50)/100 ) | |
b = Bullet({ pos = player.screenpos+worldpos, | |
name = "Player"..tostring(math.random(10000)), | |
vector=vec2(-player.direction,0), | |
img = readImage("Tyrian Remastered:Bullet Fire A") , cycles=30} ) | |
b.speed = 45 | |
table.insert(bullets,b) | |
lastBullet = math.ceil(waveTime*2) + math.random(10)/10 | |
sound(SOUND_SHOOT, 16568) | |
end | |
thrust=true --always for demo | |
end | |
if (#guys==0) then HyperSpace() else hyperspace=false end | |
if (hyperspace == false) then | |
--draw the world | |
strokeWidth(2) | |
for x=1,#pts,2 do | |
cur=vec2(pts[x], pts[x+1]) | |
line(prev.x-worldpos.x, prev.y,cur.x-worldpos.x, cur.y) | |
prev= cur | |
end | |
end | |
stroke(29, 38, 232, 255) | |
strokeWidth(9) | |
line(0,TOP-4,WIDTH+9,TOP-4) | |
player:Heartbeat() | |
self:touched(CurrentTouch) | |
--if the game is running and the ship isn't destroyed, cycle the entities | |
if (ShipActive) then | |
-- these guys have to be their own class...they have logic | |
--render enemies | |
for k,v in pairs(enemies) do | |
v:Heartbeat() | |
end | |
--render bullets | |
for k,v in pairs(bullets) do | |
v:Heartbeat() | |
end | |
--render rewards | |
for k,v in pairs(bonuses) do | |
v:Heartbeat() | |
end | |
end | |
-- ALWAYS show the explosion and guys | |
for k,v in pairs(guys) do | |
v:Heartbeat() | |
end | |
if (#explode.fireworks) then explode:draw() end | |
--ac hack! see if we are an inactive ship AND we are out of fireworks. | |
if (#explode.fireworks == 0 and ShipActive==false) then | |
ShipActive=true -- reactivate and start new ship. | |
end | |
--if there is an fs, show it. | |
for k,v in pairs(rewards) do | |
v:draw() | |
end | |
--it's been a while...let's encorage the player to finish! | |
--AC: decided to only span one - respawnable saucer. | |
--if we have sublevels, load before we close the level | |
if (waveTime > nextSaucer) then | |
if subwave>0 then | |
CreateEnemies(wave) | |
subwave = subwave - 1 | |
else | |
Saucer:Spawn(true) | |
end | |
startTime = ElapsedTime | |
waveTime = 0 | |
--nextSaucer = 30+wave*1.5 | |
end | |
if (#enemies<1) then | |
SummarizeLevel() | |
end | |
end | |
function ScreenGame:touched(touch) | |
--steerpos = steerpos + steer*speed*DeltaTime | |
steerpos = steer * 18 | |
--move world | |
-- if we are moving, correct orientation of shot | |
if steer.x < 0 and player.direction == -1 then | |
--steerpos = vec2(0, steer.y) | |
player.direction = 1 | |
end | |
if steer.x > 0 and player.direction == 1 then | |
--steerpos = vec2(0, steer.y) | |
player.direction = -1 | |
end | |
worldpos.x=worldpos.x + steerpos.x --player.direction | |
--finally correct for ship movement | |
-- logic needs rethinking... | |
if player.direction==1 then | |
if player.screenpos.x < WIDTH-400 then | |
if thrust == false then player.screenpos.x = player.screenpos.x + 30 end | |
else | |
player.screenpos.x = player.screenpos.x + steerpos.x | |
end | |
end | |
if player.direction==-1 then | |
if player.screenpos.x >= 400 then | |
if thrust==false then player.screenpos.x = player.screenpos.x - 30 end | |
else | |
player.screenpos.x = player.screenpos.x + steerpos.x | |
end | |
end | |
player.screenpos.y = player.screenpos.y + steerpos.y | |
if worldpos.x > WORLDSCREENSIZE then worldpos.x = 1 end | |
if worldpos.x < 1 then worldpos.x = WORLDSCREENSIZE-1 end | |
worldpos = CorrectPosition(worldpos) | |
player.screenpos = CorrectPosition(player.screenpos) | |
--slow down forward momentum | |
--if (thrust == false) then steerpos.x=steerpos.x * .99 end | |
if steerpos.x > 35 then steerpos.x = 35 end | |
if steerpos.y > 10 then steerpos.y = 10 end | |
end |
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
---- | |
ScreenLevelComplete = class() | |
function ScreenLevelComplete:init() | |
end | |
function ScreenLevelComplete:draw() | |
if (demo==true) then | |
GAMESTATE=GAMESTATE_MENU | |
end | |
DrawHUD() | |
waveTime = 0 | |
stroke(255, 255, 255, 255) | |
local w = 0 | |
local h = 0 | |
-- wave got incremented already on the way in. | |
text("Wave " .. wave -1 .. " completed", WIDTH/2, HEIGHT/2) | |
--render bonus icons | |
if (#guys) then | |
for k,v in pairs(guys) do | |
w = v.width + 5 | |
h = v.height | |
local left = WIDTH/2 + (k*w) - 80 --AC: 80 HAACK!!! | |
sprite(v.img, left, HEIGHT/2 - h - 5) --5 px buffer | |
--we really need a ResetGuy() function. | |
v.onShip = false | |
v.isGrabbed = false | |
v.pos.y = 5 | |
end | |
--only show this if we have guys. Hyperspace has no bonus. | |
text("x 100", WIDTH/2 + (#guys * w),HEIGHT/2 - h - 5) --5 px buffer | |
end | |
if (ElapsedTime - startPause > 5) then --gimme a 5 sec pause | |
CreateEnemies(wave) | |
GAMESTATE=GAMESTATE_PLAYING | |
waveTime = 0 | |
startPause = 0 --reset the pause "timer"/counter | |
end | |
end | |
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
---- | |
--to be used later radius osma good use here... | |
ScreenOptions = class() | |
function ScreenOptions:init() | |
endController = TapAction { | |
pressed = function(v) | |
for k,v in pairs(UI) do | |
v:touched(CurrentTouch) | |
end | |
end, | |
released = function(v) | |
for k,v in pairs(UI) do | |
v:touched(CurrentTouch) | |
end | |
end | |
} | |
UI= {} --table of controls to render. | |
SetupOptionsUI() | |
endController:activate() | |
end | |
function ScreenOptions:draw() | |
font("AmericanTypewriter-Bold") | |
fontSize(28) | |
fill(255, 255, 255, 255) | |
text("Options", WIDTH/2, HEIGHT/2) | |
for k,v in pairs(UI) do | |
v:draw() | |
end | |
end | |
function ScreenOptions:touched(touch) | |
end | |
--lay out controls for high score entry | |
function SetupOptionsUI() | |
if (Frame ~= nil) then | |
textBox1001 = TextBox('', 410, 590, 610, 650, textBox1001_Clicked) | |
textBox1001.fontSize = 48 | |
textBox1001.font ="Courier" | |
textBox1001.background=color(75, 75, 75, 255) | |
textBox1001.foreground=color(256, 161, 78, 256) | |
textBox1001.highlight=color(166, 166, 166, 255) | |
button1002 = TextButton('Return', 450, 530, 550, 580, button1002_Clicked) | |
button1002.background=color(119, 158, 199, 255) | |
button1002.highlight=color(64, 192, 66, 255) | |
table.insert(UI, textBox1001) | |
table.insert(UI, button1002) | |
end | |
end | |
function button1002_Clicked() | |
if textBox1001.text ~= nil then | |
wave = math.ceil(tonumber(textBox1001.text)) | |
end | |
hideKeyboard() | |
player:Respawn() | |
player.shields=500 | |
game = ScreenGame() | |
GAMESTATE = GAMESTATE_PLAYING | |
end | |
function textBox1001_Clicked() | |
--This is if the box is tapped | |
textBox1001.text = string.sub(textBox1001.text, 1,6) | |
end | |
function ScreenOptions:keyboard(key) | |
if CCActiveTextBox then | |
CCActiveTextBox:acceptKey(key) | |
--AC: HACK!!! | |
if (key == RETURN) then | |
textBox1001_Clicked() -- filter to 6 | |
button1002_Clicked() | |
end | |
end | |
end | |
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
---- | |
ScreenPaused = class() | |
function ScreenPaused:init(x) | |
end | |
function ScreenPaused:draw() | |
font("AmericanTypewriter-Bold") | |
fontSize(28) | |
fill(255, 255, 255, 255) | |
text("Paused", WIDTH/2, HEIGHT/2) | |
end | |
-- fire button is still working...not sure why. use it! | |
function ScreenPaused:touched(touch) | |
GAMESTATE=GAMESTATE_PLAYING | |
end |
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
---- | |
ScreenShipExplode = class() | |
function ScreenShipExplode:init() | |
end | |
function ScreenShipExplode:draw() | |
if self.startTime == nil then self.startTime = ElapsedTime end | |
if ElapsedTime - self.startTime < 1.5 then | |
font("AmericanTypewriter-Bold") | |
fontSize(28) | |
text("SHIELDS FAILED - SHIP LOST", WIDTH/2, HEIGHT/2) | |
if (#explode.fireworks) then explode:draw() end | |
else | |
self.startTime = nil | |
player:Respawn() --sets some basic vars in the ship | |
GAMESTATE=GAMESTATE_PLAYING | |
end | |
end | |
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
---- | |
ScreenSplash = class() | |
function ScreenSplash:init() | |
-- you can accept and set parameters here | |
self.source ={ 1 } | |
self.dest ={255 } | |
tween(250, self.source, self.dest, 'linear', finish) -- | |
self.start=ElapsedTime | |
LoadAssets() | |
StartNewGame() | |
splashController = TapAction { | |
pressed = function(v) | |
if v.y < 200 then | |
openURL("http://UpTwoLate.blogspot.com") | |
else | |
ScreenOptions:init() --when we get there, be working! | |
GAMESTATE=GAMESTATE_OPTIONS | |
--finish() | |
end | |
end, | |
released = function(v) | |
end | |
} | |
splashController:activate() | |
end | |
function ScreenSplash:draw() | |
-- This sets a dark background color | |
if self.source==nil then return end -- hack!!! | |
background(0, 0, 0, 255) | |
-- This sets the line thickness | |
strokeWidth(5) | |
-- Do your drawing here | |
ArrowAt4f(325, HEIGHT-40,HEIGHT/2 - 60,math.pi*0) | |
ArrowAt4f(652, HEIGHT-40,HEIGHT/2 - 60,math.pi*0) | |
fontSize(384) | |
fill(50, 137, 31, 255) | |
text("L 8", WIDTH/2,HEIGHT/2-192) | |
fontSize(36) | |
local clr=self.source[1] | |
fill(clr,clr,clr,255) | |
text("Up 2 Late Productions presents", WIDTH/2, HEIGHT/2 - 20) | |
tween.update(ElapsedTime-self.start) | |
end | |
function finish() | |
ScreenTitle:init() | |
GAMESTATE = GAMESTATE_MENU | |
end |
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
---- | |
ScreenTitle = class() | |
function ScreenTitle:init() | |
screenTime = ElapsedTime | |
titleController = TapAction { | |
pressed = function(v) | |
--see if we touched our button? HACK! | |
--[[ if v.x < 50 then | |
ScreenOptions:init() | |
GAMESTATE=GAMESTATE_OPTIONS | |
end ]] | |
end, | |
released = function(v) | |
StartNewGame() -- ac: hack! reloads alot of stuff! | |
player:Respawn() | |
game = ScreenGame() | |
end | |
} | |
titleController:activate() | |
--setup sound - could use a wav instead...for now, cheesy sound effect layering. | |
sound(DATA, "ZgBAJgA7eARXIklozNLQvRkigzx4M90+QABqfUJBQDxwTA5a",.2) | |
sound(DATA, "ZgBADwA/Nxd1fy4TbR+OvO0fWj/AuCY9TABZfz9AQDw3YHxi",.4) | |
sound(DATA, "ZgBAFwBxXUJAKi0R2QYBvgAAgD8EfQW/YQAjRkVoP1BqV1cm",.7) | |
self:MakeActors() | |
end | |
function ScreenTitle:draw() | |
pushStyle() | |
font("AmericanTypewriter-Bold") | |
fontSize(48) | |
r=math.random(255) | |
g=math.random(255) | |
b=math.random(255) | |
fill( r,g,b,255) | |
text("COFENDER", WIDTH/2, HEIGHT/2 + 150) | |
ShowHighScores() | |
fontSize(28) | |
fill(168, 172, 174, 255) | |
text("Touch to play", WIDTH/2, 100) | |
-- add score and text stuff to describe objects? | |
font("Copperplate") | |
fontSize(24) | |
for k,v in pairs(enemies) do | |
v:draw() | |
-- sprite(assets[vx], vx*spacer, HEIGHT-75) | |
text(v.points, v.pos.x, v.pos.y-50) | |
end | |
popStyle() | |
--set up a demo mode - this code should be moved elsewhere, I think | |
if (ElapsedTime - screenTime) > DEMODELAY then | |
Demo(true) | |
end | |
end | |
function ScreenTitle:touched(touch) | |
end | |
function ScreenTitle:MakeActors() | |
-- | |
enemies={} | |
--make one of each to display on the title screen | |
scores = { "YOU", "Defend" ,150,300, 500, 250, 200, 250, "Spawn", "Shields",} | |
spacer = 90 | |
worldpos = vec2(0,0) | |
for x=1,#assets do | |
local actor = Attacker({pos=vec2(x*spacer, HEIGHT-75), | |
img = assets[x], name="Actor"..x, points=scores[x], | |
drawparams = anim[x]}) | |
table.insert(enemies, actor) | |
end | |
end |
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
---- | |
Shields = class(Attacker) | |
function Shields:init(params) | |
Attacker.init(self,params) | |
assert(params.name, "All Shields must have a name") | |
self.pos = params.pos | |
self.name = params.name | |
self.img = params.img | |
self.points = params.points or 0 | |
self.movement = (math.random(10) -5) / 10 --determines how fast and how far. | |
self.shieldvalue=params.shieldvalue or 10 | |
self.speed=params.speed or 0 | |
end | |
function Shields:Heartbeat() | |
self:Move() | |
self:CheckForImpact() | |
self:draw() | |
end | |
function Shields:Move() | |
self.pos.x = self.pos.x - self.speed | |
--self.pos.y = self.pos.y + self.speed --later? | |
self.pos = CorrectPosition(self.pos) | |
self.shieldvalue = self.shieldvalue - .05 --shields fade with time | |
if self.shieldvalue < 1 then | |
self.points = 0 --poof! | |
RemoveBonus(self.name) | |
end | |
end | |
--we shot it? So we get the bonus. | |
--however, bullets only check to see if we hit enemies (today) so this code | |
--is not hit. | |
function Shields:IsHit(damage) | |
RemoveBonus(self.name) | |
end | |
function Shields:CheckForImpact() | |
local rbx = self.pos.x - player.width | |
local rax = self.pos.x + player.width | |
local rby = self.pos.y - player.height | |
local ray = self.pos.y + player.height | |
if (rbx < player.screenpos.x+worldpos.x and rax > player.screenpos.x+worldpos.x and | |
rby < player.screenpos.y and ray > player.screenpos.y) then | |
player.shields = player.shields +self.shieldvalue | |
if player.shields > player.MAXSHIELDS then | |
player.shields=player.MAXSHIELDS | |
end | |
RemoveBonus(self.name) | |
end | |
end | |
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
---- | |
-- the player's ship. | |
SHIELDCOLOR = { | |
color(255, 0, 0, 255), | |
color(205, 195, 118, 255), | |
color(39, 182, 20, 255) | |
} | |
Ship = class() | |
function Ship:init(params) | |
self.STARTSHIELDS=10 | |
self.MAXSHIELDS=500 | |
--the ship position is on the screen. The ship has real-world coords, but they | |
--are additional to the "global" position. | |
self.screenpos = vec2(400,HEIGHT/2) --offset from global | |
self.direction = -1 --go "foward" | |
self.img=imgShip | |
self.width = self.img.width | |
self.height = self.img.height | |
ShipActive=true | |
self.lastSoundTime = ElapsedTime | |
self.shields = self.STARTSHIELDS | |
self.bombs = 3 | |
self.immuneTime = ElapsedTime --set to nil after 2 secs | |
self.immuneFlag = true --setting something to nil during draw fails, so i used this flag. | |
self.immuneLength = 3 --seconds | |
end | |
function Ship:draw() | |
pushStyle() | |
if self.immuneFlag then | |
local tm = ElapsedTime - self.immuneTime | |
local tintratio = 255 / self.immuneLength --should use the tween library? | |
tint(tm*tintratio, tm*tintratio, tm*tintratio, 255) --just got here | |
if (tm > self.immuneLength) then self.immuneFlag = false end | |
else | |
tint(255, 255, 255, 255) | |
end | |
--ship rotation code - translate! | |
pushMatrix() | |
translate(self.screenpos.x, self.screenpos.y) | |
if (self.direction == -1) then rotate(180,0,1,0) end | |
--if (self.direction == 1) then rotate(0,0,1,0) end | |
if (thrust) then | |
self.img = imgThrust | |
else | |
self.img= imgShip | |
end | |
sprite(self.img, 0,0) | |
popMatrix() | |
popStyle() | |
end | |
function Ship:Heartbeat() | |
self:draw() | |
self:CheckForGuys() | |
self:MoveGuys() | |
self:DropGuys() | |
self:PlaySounds() | |
end | |
--used to play thrust sound every tench of a second. avoids buffer overfilling. | |
function Ship:PlaySounds() | |
if ElapsedTime - self.lastSoundTime > .1 and thrust == true then | |
sound(DATA, "ZgNADgJBQkFHQ0BAAAAAAMK++z4tmeg9NABAf0JAQEAVO0BB", 0.3) | |
self.lastSoundTime = ElapsedTime | |
end | |
end | |
function Ship:CheckForGuys() | |
--see if we "caught" anyone | |
for k,v in pairs(guys) do | |
if (v.pos.y > 15 and v.onShip == false and v.isGrabbed == false) then | |
local minx = v.pos.x - v.width - self.width/2 | |
local maxx = v.pos.x + v.width + self.width /2 | |
local miny = v.pos.y - v.height - self.height/2 | |
local maxy = v.pos.y + v.height + self.height /2 | |
if (minx < self.screenpos.x+worldpos.x and maxx > self.screenpos.x+worldpos.x and | |
miny < self.screenpos.y+worldpos.y and maxy > self.screenpos.y+worldpos.y) then | |
--i got him! | |
--v.isGrabbed=false | |
v.onShip = true | |
-- if we make an difficulty setting, play with this idea more | |
player.shields = player.shields + 1 | |
sound(DATA, "ZgFAHgBdT0YyNBkYI575PJ2r3j4l7Ls+UgAqdkcqLEoRYnRU") | |
AddScore (500, self.screenpos) | |
end | |
end | |
end | |
end | |
function Ship:MoveGuys() | |
for k,v in pairs(guys) do | |
if (v.onShip == true) then | |
v.pos.y=self.screenpos.y+worldpos.y-self.height | |
v.pos.x=self.screenpos.x+worldpos.x | |
--flying guys increase / repair shields | |
if self.shields <self.MAXSHIELDS then | |
self.shields = self.shields + .05 | |
--AC: Idea: allow a guy to "fall off" if we are repairing the ship | |
--FAIL: we end up with LOTS of bonus points as it's easy to recatch the guy.. | |
--if math.random(100) < 50 then | |
-- sound(SOUND_JUMP, 6826) -- fix later | |
-- v.pos.y = v.pos.y - 40 | |
-- v.pos.x = v.pos.x - 75*player.direction | |
-- v.onShip = false | |
--end | |
end | |
end | |
end | |
end | |
function Ship:DropGuys() | |
for k,v in pairs(guys) do | |
if self.screenpos.y < 25 and v.onShip == true then | |
v.onShip = false | |
v.isGrabbed=false | |
AddScore (500, self.screenpos) --looping! | |
sound(SOUND_RANDOM, 34174) | |
end | |
end | |
end | |
function Ship:touched(touch) | |
--ac: make this the bomb feature later? Or something special? unused. | |
--self.bomb = self.bomb + 1 --free bomb! | |
Ship:Bomb() | |
end | |
--called whenever the ship is created/spawned. | |
function Ship:Respawn() | |
self.immuneFlag = true | |
self.immuneTime = ElapsedTime --for the timer | |
sound(DATA, "ZgBAJQpSRkRCVzdAAAAAAG/rFD+IQN09JABRX1hSQEAZVUBI") | |
end | |
function Ship:IsHit(dmg) | |
if self.immuneFlag == true then return end --ignore any damage. | |
--blow up...if we have specials, like shields, decrement them | |
--and show shields graphics... that makes the game Stargate, though. | |
self.shields = self.shields - dmg | |
if self.shields< 0 then | |
local n = math.random(#COLOURS) | |
explode:createFirework(self.screenpos.x,self.screenpos.y,COLOURS[n]) | |
LostLife() | |
else | |
--shields holding, captain! | |
pushStyle() | |
tint(color(255,255,255,255)) | |
stroke(147, 125, 27, 255) | |
local s = math.ceil(self.shields/(self.MAXSHIELDS/3)) | |
if s== 0 then s=1 end --hack!!! | |
clr = SHIELDCOLOR[s] | |
fill(clr) | |
strokeWidth(2) | |
ellipse(self.screenpos.x, self.screenpos.y, self.width*3,self.height * 3) | |
pushStyle() | |
tint(clr) | |
sprite(imgShieldDeflect, self.screenpos.x, self.screenpos.y, self.width*3, self.height*3) | |
popStyle() | |
end | |
sound(SOUND_EXPLODE, 44445) | |
end | |
function Ship:HyperJump() | |
retain=true | |
--any jump has a risk of death | |
if math.random() < .1 then | |
self:IsHit(9999) --guaranteed dead | |
else | |
--jump there...objects may already be in the place you go to, though! | |
-- this explode is solely visual! | |
explode:createFirework(self.screenpos.x,self.screenpos.y,color(0,0,255,255)) | |
worldpos.x = math.random(9000) | |
--worldpos.y = 0 --NEVER ALTER WORLDPOS.Y! leave it at zero! | |
self.screenpos.y = math.random(HEIGHT-HUD_HEIGHT) | |
CorrectPosition(worldpos) | |
CorrectPosition(self.screenpos) | |
sound(DATA, "ZgNADwAqNlQ3eH9PAAAAAJs2Oz/77lc/UABhRVoZCFRGH1oR") | |
end | |
end | |
function Ship:Bomb() | |
-- see if we have any | |
if self.bombs <1 then return end | |
-- get worldpos extents | |
local min =worldpos.x -- making a copy otherwise its by reference...? | |
local max = min + WIDTH | |
toRemove = {} | |
sound(DATA, "ZgNADwAqNlQ3eH9PAAAAAJs2Oz/77lc/UABhRVoZCFRGH1oR",.7) | |
for k,v in pairs(enemies) do | |
if v.pos.x > min and v.pos.x < max then | |
v:IsHit(self.shields) --smart bombs do 100 damage?! | |
table.insert(toRemove, v) | |
end | |
end | |
--optimize - make a single explosion | |
sound(DATA, "ZgNAPgAwDks+a39AAAAAAA2+VD+dq14+fwBAf0BSFEI0R0Ap") | |
explode:createFirework(self.screenpos.x, self.screenpos.y, color(243, 20, 20, 255)) | |
self.bombs = self.bombs - 1 | |
--AC: Moved this to the bottom as inlie it seemed to be failing to operte correctly. | |
--Note that this smart bomb will eliminate ANYTHING, so special enemies are "just gone" | |
--when this is used. | |
for k,v in pairs(toRemove) do | |
if v.health < 1 then RemoveEnemy(v.name) end | |
end | |
end |
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
---- | |
-- Spawner lays enemy mutants. only appears every 5th wave. | |
Spawner = class(Attacker) | |
function Spawner:init(params) | |
Attacker.init(self,params) | |
self.health=params.health or 2 -- times i have to be hit | |
self.spawnCycle = params.spawnCycle or 20 | |
self.maxSpawnCycle = self.spawnCycle | |
self.predictive=true | |
end | |
function Spawner:Move() | |
self.pos.x = self.pos.x + self.speed | |
--we "spwan" ccasionally | |
self.spawnCycle = self.spawnCycle - 1 | |
if self.spawnCycle < 1 then | |
self:CreateSpawn(wave/5) | |
self.spawnCycle = self.maxSpawnCycle | |
end | |
self.pos = CorrectPosition(self.pos) | |
end | |
function Spawner:IsHit(damage) | |
if self.health > 0 then | |
self.health = self.health - damage --1 | |
--some special sound here | |
sound(DATA, "ZgBAVRc9HhlAGWVdAAAAAEEE6j6pabM+ZQAzfFQ9QX9EUjwB") | |
self.points = self.points *2 | |
self.FireSpeed = self.FireSpeed + 4 | |
AddScore(self.points, self.pos-worldpos) | |
--create a shield, too! | |
local i=Shields({pos=vec2(self.pos.x-math.random(50), self.pos.y-math.random(50)), | |
img = imgShields, | |
name="Shields"..math.random(9999), speed = .7, cycles=0, points=25, | |
looking=false, | |
damage = 0, impactDamage = 0, shieldvalue=50, | |
hudColor = color(11, 246, 2, 255), drawparams =anim[10] | |
}) | |
--this is a 69,64 sized object...size=on drawparams scaled it down... | |
table.insert(bonuses, i) | |
end | |
end | |
function Spawner:CheckForImpact() | |
local rbx = self.pos.x - player.width | |
local rax = self.pos.x + player.width | |
local rby = self.pos.y - player.height | |
local ray = self.pos.y + player.height | |
if (rbx < player.screenpos.x+worldpos.x and rax > player.screenpos.x+worldpos.x and | |
rby < player.screenpos.y and ray > player.screenpos.y) then | |
player:IsHit(self.impactDamage) | |
--self:IsHit() --we DO NOT allow the player to impact the ship, instant death | |
end | |
end | |
function Spawner:CreateSpawn(num) | |
if (#enemies > 50) then --AC: 100 is arbitrary and untested | |
self:Shoot() --get extra shooting turn! | |
self:Shoot() --get extra shooting turn #2! | |
return | |
end --we have too many to spawn! | |
for x=1,num do | |
local i = Invader({ | |
pos=vec2(self.pos.x + math.random(250)-125, self.pos.y + math.random(250) - 125), | |
img = imgAlien, name="Invader"..x..math.random(9999999), cycles=240, points=0, | |
damage = 1, impactDamage = 5, speed = math.random() + wave/20, | |
FireSpeed = 7, drawparams=anim[MUTANT] | |
}) | |
i:Mutate() | |
table.insert(enemies, i) | |
end | |
end |
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
---- | |
-- the star, which when shot opens up into starlings. | |
Star = class(Attacker) | |
function Star:Shoot() | |
-- | |
end | |
--spawn if hit ever. | |
--Stars have 1 health so this code fires when hit, won't repeat. | |
function Star:IsHit(damage) | |
Attacker.IsHit(self,damage) | |
if self.health < 1 then | |
self:CreateStarlings(10) | |
end | |
end | |
-- make the starlings. | |
function Star:CreateStarlings(num) | |
for x=1,num do | |
i = Attacker({ | |
pos=vec2(self.pos.x + math.random(80)-40, self.pos.y + math.random(80) - 40), | |
img = imgStarling, name="Starling"..x, | |
speed = math.random (2)+6, | |
cycles=50+math.random(2,50), | |
points=500, looking=false, impactDamage = 5, | |
FireSpeed = 20, | |
drawparams = anim[STARLING], | |
}) | |
table.insert(enemies, i) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment