Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@juaxix
Last active January 1, 2016 17:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save juaxix/8178134 to your computer and use it in GitHub Desktop.
Save juaxix/8178134 to your computer and use it in GitHub Desktop.
Hombert Castle Fall
--# Main
-- hombert Castle Fall
displayMode(FULLSCREEN)
supportedOrientations(LANDSCAPE_ANY)
-- Use this function to perform your initial setup
function setup()
w = WIDTH/1024
h = HEIGHT/768
useMusic = true
useSound = true
game = Game()
end
-- This function gets called once every frame
function draw()
-- This sets a dark background color
background(40, 40, 50)
--physics.gravity(Gravity)
-- This sets the line thickness
strokeWidth(5)
-- game drawing here
game:draw()
-- show fps in black, in the right bottom space of the screen
fill(0)
fontSize(32)
text(math.ceil(1/DeltaTime),WIDTH-20,10)
end
function touched(touch)
game:touched(touch)
end
function collide(contact)
game:collide(contact)
end
--# Game
-- All the game classes of the game
Game = class()
MENU = 0
GAME = 1
function Game:init()
maxLevels = 3
self.nLevel= 1
self.player= Player()
self.sky = Sky (self.nLevel)
self.levels= Levels(self.nLevel,self.player)
self.hud = HUD (self.player)
self.screen= vec2(0,0)
self:changeState(MENU)
end
function Game:musicPlay()
if not useMusic then return end
local nMusic = self.state
if nMusic == MENU then
music("A Hero's Quest:Tavern & Inn",true,0.0)
tween(3, music, {volume=0.6})
elseif nMusic == GAME then
tween(1, music,{volume = 0}, tween.easing.linear, function()
music("A Hero's Quest:Exploration",true,0.0)
tween(3, music, {volume=0.6})
end)
end
end
function Game:draw()
if self.state == MENU then
self.hud:drawMenu()
elseif self.state == GAME then
self.screen.x = WIDTH/2 - self.player.ball.x
self.screen.y = HEIGHT/2- self.player.ball.y
pushMatrix()
translate(self.screen.x,self.screen.y)
self.sky:draw()
self.levels:draw()
popMatrix()
self.hud:draw()
end
end
function Game:newGame()
self.nLevel = 1
self.player.nRedGems = 0
self.player.nBlueGems = 0
self.player.nGreenGems= 0
self:changeState(GAME)
end
function Game:continueGame()
self.levels:readLocalData()
self:changeState(GAME)
end
function Game:changeState(newState)
tween.stopAll()
if newState == GAME then
self.levels:initLevel(self.nLevel)
self.state = GAME
self:musicPlay()
elseif newState == MENU then
self.state = MENU
self:musicPlay()
end
end
function Game:touched(touch)
self.hud :touched(touch)
end
function Game:collide(contact)
self.player:collide(contact)
end
--# Sky
-- Class to draw backgrounds
Sky = class()
function Sky:init(nLevel)
self.imgs = {
readImage("castleFall:bg"),
readImage("castleFall:bg_castle")
--space for clouds here
}
for i=1,3 do
table.insert(
self.imgs, readImage("castleFall:cloud"..i)
)
end
table.insert(self.imgs, readImage("castleFall:castleCenter") )
table.insert(self.imgs,readImage("castleFall:dirtCenter"))
self.sky = self:loadSky(nLevel)
end
function Sky:loadSky(nLevel)
local l = { }
--if nLevel == 1 then
-- sky bg
l = {
{i=1,
pos=vec2(WIDTH/2,HEIGHT/2),
sx=WIDTH*10, sy=HEIGHT*10}
}
-- castle bg
local n = (math.floor(WIDTH/70*w))
local m = (math.floor(HEIGHT/70*h))
for i=0,n do
for j=0,m do
table.insert(
l,
{ i=math.random(6,7),
pos=vec2(i*70*w,-300-j*70*h),
sx=70*w,sy=70*h
}
)
end
end
-- clouds with animations
for i=1,2 do
table.insert(
l,{
i=math.random(3,5),
pos=vec2((100+100*i)*w,
(HEIGHT-100*h)-i*math.random(10,100)*h)
}
)
l[#l].sx = self.imgs[l[#l].i].width*w
l[#l].sy = self.imgs[l[#l].i].height*h
l[#l].tw = tween(
math.random(4,6)+i*4, l[#l].pos, {x = WIDTH-(100*w) },
{easing=tween.easing.linear,
loop =tween.loop.pingpong}
)
end
--end
return l
end
function Sky:draw()
for i,s in ipairs(self.sky) do
sprite(
self.imgs[s.i],
s.pos.x, s.pos.y,
s.sx, s.sy
)
end
end
--# Levels
-- Stores generated data from levels
Levels= class()
WALL = 2
PICKABLE= 3
DOOR = 4
KEY = 5
FIRE = 6
CHKPOINT= 7
ENEMY = 8
function Levels:init(nLevel,hPlayer)
-- load walls images
self.w_imgs = {
readImage("castleFall:castleCenter_rounded"),
readImage("castleFall:grassCenter"),
readImage("castleFall:brickWall"),
readImage("castleFall:castleCenter")
}
-- fires images
self.f_imgs = {
readImage("castleFall:fireball")
}
-- pickable images (gems, coins)
self.p_imgs = {
readImage("castleFall:gemRed"),
readImage("castleFall:gemBlue"),
readImage("castleFall:gemGreen"),
readImage("castleFall:coinGold")
}
-- doors images
self.d_imgs ={
readImage("castleFall:door_closedMid"),
readImage("castleFall:door_openMid")
}
-- keys images (open doors)
self.k_imgs = {
readImage("castleFall:keyYellow")
}
-- checkpoint images
self.c_imgs = {
readImage("castleFall:flagBlueHanging"),
readImage("castleFall:flagBlue")
}
-- enemies images
self.e_imgs = {
readImage("Platformer Art:Battor Flap 1"),
readImage("Platformer Art:Battor Flap 2"),
readImage("Platformer Art:Battor Dead")
}
self.player = hPlayer
end
function Levels:initLevel(nLevel)
-- load game level data
self:loadWalls (nLevel)
self:loadFires (nLevel)
self:loadTexts (nLevel)
self:loadShaders (nLevel)
self:loadCheckPoints(nLevel)
self:loadPickables (nLevel)
self:loadDoors (nLevel)
self:loadEnemies (nLevel)
self:loadKeys (nLevel)
self.player:initPlayer()
game.hud:initHUD()
self.chkpos = self.player.ball.position
collectgarbage()
end
function Levels:loadDoors(nLevel)
self.doors = {}
if nLevel == 1 then
local exitDoor = {
body = self:createDoor(
WIDTH/2,-HEIGHT-35*h,
w*70, h*70
), img = 1
}
table.insert(self.doors,exitDoor)
elseif nLevel >1 then
table.insert(self.doors, {
body = self:createDoor(
WIDTH/2+30*w,-266*h,
w*70, h*70
),
img = 1
})
end
end
function Levels:createDoor(x,y,width,height)
local door = physics.body(POLYGON,
vec2(-width/2,height/2),
vec2(-width/2,-height/2),
vec2(width/2,-height/2),
vec2(width/2,height/2)
)
door.x = x
door.y = y
door.type = STATIC
door.info = {w=width,h=height,t=DOOR}
door.active = true
door.sensor = true
door.mask = {PLAYER}
door.categories = {DOOR}
return door
end
function Levels:createKey(x,y,width,height)
local key = physics.body(POLYGON,
vec2(-width/2,height/2),
vec2(-width/2,-height/2),
vec2(width/2,-height/2),
vec2(width/2,height/2)
)
key.x = x
key.y = y
key.type = STATIC
key.info = {w=width,h=height,t=KEY}
key.active = true
key.sensor = true
key.mask = {PLAYER}
key.categories = {KEY}
return key
end
function Levels:loadKeys(nLevel)
self.keys = {}
if nLevel == 1 then
local exitKey = {
body = self:createKey(
WIDTH-166*w,-HEIGHT-30*h,
w*70, h*70
), img = 1
}
table.insert(self.keys,exitKey)
elseif nLevel >1 then
table.insert(
self.keys,
{
body = self:createKey(
WIDTH/2,-100*h,
w*70, h*70
),
img = 1
}
)
end
end
function Levels:loadShaders(nLevel)
self.shaders = {}
if nLevel == 1 then
local m = mesh()
m.shader = shader("Effects:Electricity")
m:addRect(WIDTH/2, HEIGHT/2, 300*w,300*h)
m.shader.resolution = vec2(322,322)
m.shader.time = ElapsedTime
table.insert(self.shaders, {
m = m,
pos = vec2(WIDTH/2, HEIGHT/2),
sx = 300*w,sy=300*h,
mode= ADDITIVE
})
elseif nLevel == 3 then
local m = mesh()
m.shader = shader(
[[
uniform mat4 modelViewProjection;
attribute vec4 position;
attribute vec4 color;
attribute vec2 texCoord;
varying lowp vec4 vColor;
varying highp vec2 vTexCoord;
void main()
{
vColor = color;
vTexCoord = texCoord;
gl_Position = modelViewProjection * position;
}
]],
[[
uniform lowp vec2 resolution;
uniform highp float time;
uniform lowp sampler2D texture;
varying highp vec2 vTextCoord;
precision highp float;
float heartbeat(float p){
float res = fract(p)*2.-1.;
res = cos(time*2.-p*10.*3.)*min(res,1.-2.*res);
return res;
}
void main(void){
vec2 r = vec2(time*600.,400.);
//vec2 p = (gl_FragCoord.xy/resolution.xy)-vec2(0.1,0.5);
vec2 p = (gl_FragCoord.xy/r.xy)-vec2(0.5,0.5);
float c= 0.0;
float f= heartbeat(p.x*2.+time*0.25);
float d= (p.y*4.-f)*10.;
c=.25/abs(pow(d,.5));
c=clamp(c,0.,1.);
gl_FragColor = vec4(vec3(c*c+c,0.,0.), 0.8);
}
]]
)
m.shader.time = ElapsedTime
--m.shader.resolution = vec2(300.0,300.0)
local r = m:addRect(WIDTH/2, HEIGHT/2, WIDTH,HEIGHT)
table.insert(self.shaders, {
m = m,
pos = vec2(WIDTH/2,HEIGHT/2),
sx = WIDTH,sy=HEIGHT,
mode= MULTIPLY,follow= true,r=r
})
m:setColors(color(255, 255, 255, 255))
m.texture = readImage("Cargo Bot:Background Fade")
end
end
function Levels:loadTexts(nLevel)
self.texts = self:getTexts(nLevel)
for i,t in ipairs(self.texts) do
if t.duration then
if t.target then
if t.target.opacity then
t.tw = tween(t.duration,t.color,{a=t.target.opacity})
end
end
end
end
end
function Levels:getWallsLevel(nLevel)
if nLevel == 1 then
return
{
{body = {WIDTH/2, HEIGHT/2,70*w,70*h},img = 1},
{body = {-70*w,HEIGHT/2, 70*w,12*70*h},img=2},
{body = {-70*w,HEIGHT/2-12*70*h, 70*w,12*70*h},img=2},
{body = {WIDTH/2-70*w,(70/2)*h, WIDTH+70*w,70*h},img=2},
{body = {WIDTH+177*w,(70/2)*h, 70*w,12*70*h},img=2},
{body = {WIDTH/2, (-HEIGHT-100*h), WIDTH+70*w*2, 70*h},img=3},
{body = {WIDTH/2+212*w,-(10*70/2)*h, WIDTH,70*h},img=2},
{body = {WIDTH+100*w,-HEIGHT, 70*w,12*70*h},img=2},
}
elseif nLevel >1 then
return {
{body = {WIDTH/2-70*w,70*h, WIDTH+70*w,70*h},img=3},
{body = {WIDTH/2+70*w,-166*h, WIDTH+30*w,70*h},img=2},
{body = {-100*w, HEIGHT/2, 70*w*2, HEIGHT*2},img=2},
{body = {WIDTH+100*w, HEIGHT/2, 70*w, HEIGHT*2},img=2},
{body = {WIDTH/2+70*w,-166*h, WIDTH+30*w,70*h},img=4},
{body = {WIDTH/2+23*w,-350*h, WIDTH+100*w,70*h},img=4}
}
end
end
function Levels:getPickables(nLevel)
if nLevel == 1 then
return {
{body = {0, -(7*70/2)*h,70*w,70*h,(70/4)*h},img = 1},
{body = {WIDTH/2+70*w*2, HEIGHT/2+60*h, 70*w, 70*h, (70/4)*h},img = 2},
{body = {WIDTH/2+70*w*4, 160*h, 70*w, 70*h, (70/4)*h},img = 2},
{body = {WIDTH/2+70*w*6, -HEIGHT+60*h, 70*w, 70*h, (70/4)*h},img = 3}
}
elseif nLevel == 2 then
return {
{body = {WIDTH/1.3,123*h,70*w,70*h,(70/4)*h},img = math.random(1,3)},
{body = {WIDTH/1.3,-123*h,70*w,70*h,(70/4)*h},img = math.random(1,3)},
}
elseif nLevel == 3 then
return {
{body = {WIDTH/1.3,123*h,70*w,70*h,(70/4)*h},img = math.random(1,3)},
{body = {WIDTH/1.3,-123*h,70*w,70*h,(70/4)*h},img = math.random(1,3)},
}
elseif nLevel == 4 then
return {
{body = {WIDTH/1.3,123*h,70*w,70*h,(70/4)*h},img = math.random(1,3)},
{body = {WIDTH/1.3,-123*h,70*w,70*h,(70/4)*h},img = math.random(1,3)},
}
end
end
function Levels:getTexts(nLevel)
if nLevel == 1 then
return {
{text="LEVEL 1: Who is Hombert?", duration = 7 ,pos=vec2(WIDTH/2, HEIGHT/1.3),
fontSize=40*h, color=color(233, 21, 21, 255),target={opacity=0}
},
{text="Hombert is lost again...",color=color(98, 226, 39, 0),
pos=vec2(WIDTH/2, HEIGHT/2-60*h),duration=4,target={opacity=255}
},
{
text="He suffers memory loss...",color=color(98, 226, 39, 0),
pos=vec2(WIDTH/2+70*w*4, 160*h),duration=6,target={opacity=255}
},
{text="Checkpoint",color=color(127),pos=vec2(WIDTH+70*w,-200*h)
},
{
text="Lucky him, you'll help picking gems",color=color(226, 38, 64, 255),
pos=vec2(166*w, -66*h)
},
{text="I feel you are a good guide, opening mind and doors",
color=color(75, 76, 42, 255),
pos=vec2(WIDTH/2-55*w*4, -HEIGHT+60*h)
},
{text="Remember, Gems avoid oblivion",pos=vec2(WIDTH/2+70*w*6, -HEIGHT+60*h)
}
}
elseif nLevel == 2 then
return {
{text="Level 2: Hombert memory game",color=color(0), pos=vec2(WIDTH/2, 444*h),
duration = 7, fontSize=40*h, target={opacity=0}
},
{text="Tap the flies, memory garbage to be transmuted", pos= vec2(-10*w, 222*h),
color=color(157, 26, 26, 255)
}
}
elseif nLevel == 3 then
return {
{text="Level 3: Hombert shadows",color=color(0), pos=vec2(WIDTH/2, 444*h),
duration = 7, fontSize=40*h, target={opacity=0}}
}
elseif nLevel == 4 then
return {}
end
end
function Levels:getFires(nLevel)
if nLevel < 4 then
return {}
elseif nLevel == 4 then
return {
{i=1,x0=WIDTH/2, y0=-HEIGHT-77*h,
x1=WIDTH/2+10*w,y1=-HEIGHT-177*h}
}
end
end
function Levels:getEnemies(nLevel)
if nLevel == 1 then
return {
} --- no enemies in the first level
elseif nLevel == 2 or nLevel == 3 then
return {
{
img=1,body={ 10*w, 111*h},
tw={duration = 2,target={x=232*w,y=130*h}},
},
{
img = 1, body={ WIDTH/2+200*w,-99*h}
},
{
img=1,body={ 10*w, -100*h},
tw={duration = 2,target={x=100*w,y=-250*h}},
}
}
elseif nLevel == 4 then
return {}
end
end
function Levels:loadCheckPoints(nLevel)
if nLevel == 1 then
self.checkPoints =
{
{body = self:createCheckPoint(
WIDTH+100*w,-280*h,
w*70, h*70
), img = 1}
}
elseif nLevel>1 then
self.checkPoints =
{
{body = self:createCheckPoint(
WIDTH+30*w,-100*h,
w*70, h*70
), img = 1}
}
end
end
function Levels:loadEnemies(nLevel)
self.enemies = self:getEnemies(nLevel)
for i,e in ipairs(self.enemies) do
e.body = self:createEnemy(
e.body[1],e.body[2],
self.e_imgs[e.img].width*w,
self.e_imgs[e.img].height*h
)
if e.tw then
local targets = {}
for j = e.body.x, e.tw.target.x, e.tw.target.x/(10*w) do
table.insert(targets, {x=j,y=e.body.y+w*math.sin(j*4)*20+60*w})
end
table.insert(targets,e.target)
e.tw = tween.path(
e.tw.duration,e.body,targets,
{easing=tween.easing.quadInOut,loop=tween.loop.pingpong}
)
end
end
end
function Levels:createEnemy(x,y,width,height)
local enemy = physics.body(POLYGON,
vec2(-width/2,height/2),
vec2(-width/2,-height/2),
vec2(width/2,-height/2),
vec2(width/2,height/2)
)
enemy.x = x
enemy.y = y
enemy.sensor = true
enemy.type = STATIC
enemy.mask = {PLAYER}
enemy.info = {w=width,h=height,t=ENEMY}
enemy.restitution = 0.25
enemy.categories = {ENEMY}
return enemy
end
function Levels:createCheckPoint(x,y,width,height)
local chkp = physics.body(POLYGON,
vec2(-width/2,height/2),
vec2(-width/2,-height/2),
vec2(width/2,-height/2),
vec2(width/2,height/2)
)
chkp.x = x
chkp.y = y
chkp.sensor = true
chkp.type = STATIC
chkp.mask = {PLAYER}
chkp.info = {w=width,h=height,t=CHKPOINT}
chkp.categories = {CHKPOINT}
return chkp
end
function Levels:createWall(x,y,width,height)
-- polygons are defined by a series of points in counter-clockwise order
local wall = physics.body(POLYGON,
vec2(-width/2,height/2),
vec2(-width/2,-height/2),
vec2(width/2,-height/2),
vec2(width/2,height/2)
)
--wall.interpolate = true
wall.x = x
wall.y = y
wall.restitution = 0.25
wall.sleepingAllowed = false
wall.type = STATIC
wall.info = {w=width,h=height,t=WALL}
wall.active = true
wall.type = STATIC
wall.mask = {PLAYER,ENEMY}
wall.categories = {WALL}
--wall.friction = 10
return wall
end
function Levels:loadWalls(nLevel)
self.walls = self:getWallsLevel(nLevel)
-- create physics world
for i,wall in ipairs(self.walls) do
if wall.body then
wall.body = self:createWall(
wall.body[1],wall.body[2],
wall.body[3],wall.body[4]
)
end
end
end
function Levels:createFire(x,y,width,height,r)
local fire = physics.body(CIRCLE, r)
fire.interpolate = true
fire.x = x
fire.y = y
fire.type = STATIC
fire.sensor = true
fire.info = {w=width,h=height,t=FIRE}
fire.mask = {PLAYER}
fire.categories = {FIRE}
return fire
end
function Levels:loadFires(nLevel)
local tfires = self:getFires(nLevel)
self.fires = {}
for i,fire in ipairs(tfires) do
table.insert(self.fires, {
img = self.f_imgs[fire.i],
body = self:createFire(
fire.x0, fire.y0,
self.f_imgs[fire.i].width*w,
self.f_imgs[fire.i].height*h,
30*h
)
})
self.fires[#self.fires].tw = tween(
4, self.fires[#self.fires].body, {x=fire.x1,y=fire.y1},
{easing=tween.easing.elasticOutIn,
loop =tween.loop.pingpong}
)
end
end
function Levels:createPickable(x,y,width,height,r,i)
local pickable = physics.body(CIRCLE, r)
pickable.interpolate = false
pickable.x = x
pickable.y = y
pickable.type = STATIC
pickable.sensor = true
pickable.info = {w=width,h=height,t=PICKABLE,i=i}
pickable.mask = {PLAYER}
pickable.categories = {PICKABLE}
return pickable
end
function Levels:loadPickables(nLevel)
self.pickables = self:getPickables(nLevel)
for i,p in ipairs(self.pickables) do
p.body = self:createPickable(p.body[1],p.body[2],p.body[3],p.body[4],p.body[5],p.img)
end
end
function Levels:drawShaders(playerpos,drawdist)
for j,sha in ipairs(self.shaders) do
if sha.pos:dist(playerpos)<drawdist then
if sha.follow then
sha.pos = self.player.ball.position
sha.m:setRect(sha.r,sha.pos.x,sha.pos.y,sha.sx,sha.sy)
end
blendMode(sha.mode)
sha.m.shader.time = ElapsedTime
sha.m:draw()
end
end
blendMode(NORMAL)
end
function Levels:drawTexts(playerpos,drawdistance)
textAlign(CENTER)
font("Futura-CondensedMedium")
for j,t in ipairs(self.texts) do
local pos
if t.pos then
pos = t.pos
else
pos = vec2(WIDTH/2, HEIGHT/2)
end
-- check player position before draw
if pos:dist(playerpos)<drawdistance and t.text then
if t.fontSize then
fontSize(t.fontSize)
else
fontSize(30)
end
if t.color then
fill(t.color)
else
fill(255)
end
text(t.text, pos.x, pos.y)
end
end
end
function Levels:drawPicks(playerpos,drawdistance)
for j,pick in ipairs(self.pickables) do
if pick.body.info.picked then
pushMatrix()
translate(0,0)
end
-- check all fields (can be deleted in the middle of a for)
if pick and pick.body and pick.body.position and
pick.body.position:dist(playerpos)<drawdistance then
sprite(
self.p_imgs[pick.img], pick.body.x, pick.body.y,
pick.body.info.w, pick.body.info.h
)
end
if pick.body.info.picked then
popMatrix()
end
end
end
function Levels:drawChkPoints(playerpos,drawdist)
self:drawSprites(self.checkPoints,self.c_imgs, playerpos, drawdist)
end
function Levels:drawWalls(playerpos, drawdist)
self:drawSprites(self.walls,self.w_imgs, playerpos, drawdist)
end
function Levels:drawFires(playerpos,drawdist)
self:drawSprites(self.fires,self.f_imgs, playerpos, drawdist)
end
function Levels:drawDoors(playerpos,drawdist)
self:drawSprites(self.doors,self.d_imgs, playerpos, drawdist)
end
function Levels:drawEnemies(playerpos,drawdist)
for j,e in ipairs(self.enemies) do
if e and e.body and e.body.position and
e.body.position:dist(playerpos)<drawdist
then
if e.alpha then
tint(255,e.alpha)
end
if e.body.sensor then -- animate fly
e.img = 1 + math.fmod(math.ceil(ElapsedTime*2),2)
end
sprite(
self.e_imgs[e.img],e.body.x,e.body.y,
e.body.info.w, e.body.info.h
)
noTint()
end
end
end
function Levels:drawKeys(playerpos, drawdist)
self:drawSprites(self.keys,self.k_imgs, playerpos, drawdist)
end
function Levels:drawSprites(tData, tSprites, vPlayerpos, nDrawdist)
for j,d in ipairs(tData) do
if d and d.body and d.body.position and
d.body.position:dist(vPlayerpos)<nDrawdist
then
sprite(
tSprites[math.ceil(d.img)], d.body.x,d.body.y,
d.body.info.w, d.body.info.h
)
end
end
end
function Levels:draw()
local ppos = self.player.ball.position
local drawDist = HEIGHT/1.2
self:drawWalls (ppos,drawDist*2)
self:drawChkPoints(ppos,drawDist)
self:drawDoors (ppos,drawDist)
self:drawFires (ppos,drawDist)
self:drawShaders (ppos,drawDist)
self:drawTexts (ppos,drawDist)
self.player:draw ()
self:drawEnemies (ppos,drawDist*2)
self:drawKeys (ppos,drawDist*2)
self:drawPicks (ppos,drawDist)
end
-- events
function Levels:picked(pickableBody)
pickableBody.info.picked = true
-- check which one is it
for i,p in ipairs(self.pickables) do
if p.body == pickableBody then
local hudo = nil
if p.body.info.i == 1 then
hudo = game.hud.redGem
self.player.nRedGems = self.player.nRedGems + 1
elseif p.body.info.i == 2 then
hudo = game.hud.blueGem
self.player.nBlueGems = self.player.nBlueGems + 1
elseif p.body.info.i == 3 then
hudo = game.hud.greenGem
self.player.nGreenGems = self.player.nGreenGems + 1
end
if hudo then
if useSound then sound("A Hero's Quest:Pick Up") end
game.hud:showGemsCounter(hudo)
tween(3, p.body, {x=WIDTH-166*w,y = hudo.pos.y},tween.easing.backInOut )
tween.delay(5,function(b)
self:removeFromTable(b, self.pickables)
game.hud:hideGemsCounter(hudo)
end,pickableBody)
end
return
end
end
end
function Levels:checked(checkedBody)
checkedBody.info.picked = true
-- check which one is it
for i,p in ipairs(self.checkPoints) do
if p.body == checkedBody then
p.img = 2
p.body.active = false
if useSound then
sound("A Hero's Quest:Fuse2")
end
-- save current check point
self.chkpos = p.body.position
end
end
end
function Levels:playerGotDamage(enemyBody)
if useSound then
sound("A Hero's Quest:Hurt "..math.random(1,3))
end
self.player:stop(vec2(0,0))
self.player.ball.active = false
self.player.nState = 5
tween(3,self.player.ball,
{x=self.chkpos.x,y=self.chkpos.y},
tween.easing.elasticOut,
function()
self.player.ball.active = true
self.player.nState = 1
end
)
end
function Levels:gotKey(keyBody)
keyBody.info.picked = true
-- check which one is it
for i,p in ipairs(self.keys) do
if p.body == keyBody then
if useSound then
sound("A Hero's Quest:Defensive Cast 1")
end
tween(3, p.body.info, {w=0,h=0},tween.easing.bounceInOut,
function(b)
self:removeFromTable(b, self.keys)
end,
keyBody)
game.hud.keyState = 2
return
end
end
end
function Levels:openDoor(doorBody)
doorBody.info.opened = true
-- check which one is it
for i,d in ipairs(self.doors) do
if d.body == doorBody then
if useSound then
sound("A Hero's Quest:Door Open")
end
d.img = 2
tween(3, d.body.info, {w=0,h=0},tween.easing.bounceInOut,
function(b)
-- level change now
self:loadNextLevel()
end
)
tween(1, self.player.ball, {x=d.body.x})
tween(2, self.player.ball.info,{w=0,h=0},tween.easing.bounceOut)
game.hud.keyState = 2
-- freeze player
self.player:stop(vec2(0,0))
self.player.ball.active = false
return
end
end
end
function Levels:loadNextLevel()
if useSound then
sound("A Hero's Quest:Level Up")
end
if game.nLevel == maxLevels then
game:changeState ( MENU )
return
end
game.nLevel = game.nLevel + 1
-- save level progress now
self:saveLocalData()
-- destroy all things
for i,d in ipairs(self.doors) do
d.body:destroy()
end
for i,p in ipairs(self.pickables) do
p.body:destroy()
end
for i,k in ipairs(self.keys) do
k.body:destroy()
end
for i,f in ipairs(self.fires) do
f.body:destroy()
end
for i,w in ipairs(self.walls) do
w.body:destroy()
end
for i,e in ipairs(self.enemies) do
if e.tw then
tween.stop(e.tw)
end
e.body:destroy()
end
game.sky.skies = {}
-- init new level now
self:initLevel(game.nLevel)
game.hud:initHUD()
-- reactivate player
self.player.ball.active = true
end
function Levels:removeFromTable(body, tab)
for i,e in ipairs(tab) do
if e.body == body then
if body then
body:destroy()
end
table.remove(tab,i)
return
end
end
end
function Levels:checkEnemyHit(vTouch)
for i,e in ipairs(self.enemies) do
if e and e.body and e.body.position and e.body.sensor and
e.body:testPoint(vTouch)
then
if e.tw then
tween.stop(e.tw )
end
e.tw = nil
e.body.sensor = false
e.body.type = DYNAMIC
e.body.mask = {WALL}
e.img = 3
if useSound then
sound("A Hero's Quest:Hit Monster "..math.random(1,3))
end
e.alpha = 255
tween(4,e,{alpha=1},tween.easing.bounceInOut,function(b)
self:removeFromTable(b,self.enemies)
end,e.body)
local pimg = math.random(1,3)
table.insert(self.pickables,{img=pimg,
body = self:createPickable(
e.body.x,e.body.y+2,70*w,70*h,(70/4)*h, pimg
) }
)
return
end
end
end
function Levels:touched(touch)
if touch.state == ENDED then
self:checkEnemyHit(vec2(touch.x,touch.y)-game.screen)
end
end
function Levels:saveLocalData()
-- save current level progress
saveLocalData("nLevel",game.nLevel)
saveLocalData("nRedGems",self.player.nRedGems)
saveLocalData("nBlueGems",self.player.nBlueGems)
saveLocalData("nGreenGems",self.player.nGreenGems)
end
function Levels:readLocalData()
self.player.nRedGems = readLocalData("nRedGems")
self.player.nBlueGems = readLocalData("nBlueGems")
self.player.nGreenGems = readLocalData("nGreenGems")
end
--# Player
-- The Player model, view (and controller functions)
Player = class()
PLAYER = 1
function Player:init()
self.ball = physics.body(CIRCLE, 40*w)
self.imgs = {
readImage("castleFall:p1_front"),
readImage("castleFall:p1_stand"),
readImage("castleFall:p1_jump"),
readImage("castleFall:p1_duck"),
readImage("castleFall:p1_hurt")
}
-- add walk images
for i=1,9 do
table.insert(self.imgs,
readImage("castleFall:p1_walk0"..i)
)
end
self.nRedGems = 0
self.nBlueGems = 0
self.nGreenGems = 0
self.ball.active = false
end
-- initializate player data structures
function Player:initPlayer()
self.nState = 1
self.direction = 0
self.ball.linearDamping = 0
self.ball.sleepingAllowed = false
self.ball.interpolate= true
self.ball.x = WIDTH/2
self.ball.y = HEIGHT/2+166*h
self.ball.restitution = 0
self.ball.mass = 1.91
--self.ball.mask = {} -- ALL!
self.ball.categories = {PLAYER}
self.ball.friction = 0
self.ball.info = {
t = PLAYER,
w = self.imgs[self.nState].width*w,
h = self.imgs[self.nState].height*h
}
self.walkAnim = nil
self.walkID = nil
self.jumping = false
self.ball.active= true
self.playerLastPos = {}
end
function Player:update()
if self.walkID~=nil then
self.nState = math.floor(self.walkAnim.nstate)
end
-- limit velocity
self.ball.linearVelocity.x = math.min(self.ball.linearVelocity.x,100)
end
function Player:drawTrail()
local ball = self.ball
noTint()
if ball.linearVelocity:len()<4 then return end
if #self.playerLastPos<2 or
self.playerLastPos[#self.playerLastPos]:dist(ball.position)>6
then
table.insert(self.playerLastPos, ball.position)
if #self.playerLastPos>11 then
table.remove(self.playerLastPos, 1)
end
end
blendMode(ADDITIVE)
local l = nil
for k,p in ipairs(self.playerLastPos) do
if l then
local a = math.fmod(k,11)
strokeWidth(a)
stroke(233-(a*11), 166-k-2)
line(p.x,p.y, l.x, l.y)
end
l = p
end
blendMode(NORMAL)
end
function Player:draw()
self:update()
self:drawTrail()
-- fix for scale (cant use a math.fmod :\ )
local d = self.direction
if d == 0 then d = 1 end
sprite(
self.imgs[self.nState],
self.ball.x,
self.ball.y+((self.imgs[self.nState].height)/2)-self.ball.radius,
self.ball.info.w*d, self.ball.info.h
)
-- draw body:
--ellipse(self.ball.x,self.ball.y,self.ball.radius*2)
end
function Player:jump(axis)
if self.jumping or not self.ball.active then return end
local d = self.direction
-- wait for the char to hit the ground
self:stop(self.ball.linearVelocity)
self.direction = d
self.jumping = true
self.nState = 3
if useSound then
sound("A Hero's Quest:Swing 3")
end
self.ball:applyForce(
vec2(
1000*self.direction*axis.x*DeltaTime,
40000*axis.y*DeltaTime
),
self.ball.worldCenter
)
end
function Player:walk(axis)
if self.jumping or not self.ball.active then return end
local x = self.direction
if axis.x > 0 then
self.direction = 1
else
self.direction = -1
end
if self.direction ~= x then
x = self.direction
self:stop(vec2(0,0))
self.direction = x
end
-- move player with a force:
self.ball:applyForce(
vec2(2000*axis.x*DeltaTime,0),
self.ball.worldCenter
)
if self.walkID == nil then
self.nState = 6
self.walkAnim = {nstate = 6}
self.walkID = tween(
1/2, self.walkAnim, {nstate=12},
{easing=tween.easing.linear,
loop =tween.loop.pingpong}
)
end
end
function Player:stop(axis)
if not self.ball.active then return end
if self.walkID ~= nil then
tween.stop(self.walkID)
self.walkID = nil
end
self.ball.linearVelocity = axis
self.ball.angularVelocity= 0
self.ball.linearDamping = 0
self.direction = 0
self.nState = 1
end
function Player:touched(touch)
end
function Player:collide(contact)
if not contact.bodyA or not contact.bodyB or
not contact.bodyA.info or not contact.bodyB.info
then
-- unhandled collision
return
end
if (contact.bodyA.info.t == WALL and contact.bodyB.info.t == PLAYER) or
(contact.bodyB.info.t == WALL and contact.bodyA.info.t == PLAYER )
then
if contact.state == BEGAN then
self.jumping = false
if self.direction ~= 0 then
self:walk(vec2(self.direction,0))
else
self:stop(vec2(0,0))
end
if useSound then
sound("A Hero's Quest:Drop")
end
elseif contact.state == ENDED and math.abs(self.ball.linearVelocity.y) > 1 then
self.jumping = true
end
elseif contact.bodyA.info.t == PICKABLE and contact.bodyB.info.t == PLAYER then
if contact.state == BEGAN and not contact.bodyA.info.picked then
game.levels:picked(contact.bodyA)
end
elseif contact.bodyA.info.t == KEY and contact.bodyB.info.t == PLAYER then
if contact.state == BEGAN and not contact.bodyA.info.picked then
game.levels:gotKey(contact.bodyA)
end
elseif contact.bodyA.info.t == DOOR and contact.bodyB.info.t == PLAYER then
if contact.state == BEGAN and not contact.bodyA.info.opened and
game.hud.keyState == 2
then
game.levels:openDoor(contact.bodyA)
end
elseif contact.bodyA.info.t == PLAYER and contact.bodyB.info.t == PICKABLE then
if contact.state == BEGAN and not contact.bodyB.info.picked then
game.levels:picked(contact.bodyB)
end
elseif contact.bodyA.info.t == PLAYER and contact.bodyB.info.t == CHKPOINT then
if contact.state == BEGAN and not contact.bodyB.info.checked then
game.levels:checked(contact.bodyB)
end
elseif contact.bodyB.info.t == PLAYER and contact.bodyA.info.t == CHKPOINT then
if contact.state == BEGAN and not contact.bodyA.info.checked then
game.levels:checked(contact.bodyA)
end
elseif (contact.bodyA.info.t == PLAYER and contact.bodyB.info.t == ENEMY) or
(contact.bodyB.info.t == PLAYER and contact.bodyA.info.t == ENEMY)
then
if contact.state == BEGAN then
if contact.bodyB.info.t == ENEMY then
game.levels:playerGotDamage(contact.bodyB)
else
game.levels:playerGotDamage(contact.bodyA)
end
end
end
end
--# HUD
-- Generate the HUD for the user to control the game
HUD = class()
function HUD:init(player)
self.player = player
self.controls = {
btnJump = {
imgs = { readImage("castleFall:buttonBlue"),
readImage("castleFall:buttonBlue_pressed")
},
state=1,pos=vec2(WIDTH-100*w,50*h),sx=70*w*2,sy=70*h*2,
tid = 0
}
}
self.controller = VirtualStick {
moved = function(v)
self.player:walk(v)
end,
released = function(v)
self.player:stop(vec2(0,0))
end
}
self.controller:activate(
{x0=0,x1=0.5,y0=0,y1=0.25,name="move Hombert"}
)
-- castle key hud
self.k_imgs = {
readImage("castleFall:hud_keyYellow_disabled"),
readImage("castleFall:hud_keyYellow")
}
self.keyposx = 46*w
self.keyposy = HEIGHT - 43*h
self.keysx = 44*w
self.keysy = 40*h
self.keyState = 1
-- gems collect display
self.redGem = {
icon = readImage("castleFall:hud_gem_red"),
pos = vec2(WIDTH-166*w, HEIGHT-66*h),
xpos = vec2(WIDTH-(127)*w, HEIGHT-66*h)
}
self.blueGem = {
icon = readImage("castleFall:hud_gem_blue"),
pos = vec2(WIDTH-166*w, HEIGHT-123*h),
xpos = vec2(WIDTH-(127)*w, HEIGHT-123*h)
}
self.greenGem = {
icon = readImage("castleFall:hud_gem_green"),
pos = vec2(WIDTH-166*w, HEIGHT-180*h),
xpos = vec2(WIDTH-(127)*w, HEIGHT-180*h)
}
self.gemsx = 46*w
self.gemsy = 36*h
self.hudximg = readImage("castleFall:hud_x")
self.hudxsizx= 30*w
self.hudxsizy= 28*h
-- initialize numbers
self.numimgs = {}
for i=0,9 do
self.numimgs[string.format("%d",i)..""] = {icon = readImage("castleFall:hud_"..i)}
local n = self.numimgs[string.format("%d",i)..""]
n.sx = n.icon.width*w
n.sy = n.icon.height*h
end
end
-- init HUD and presentation (animations)
function HUD:initHUD()
self:showGemsCounter(self.redGem)
self:showGemsCounter(self.blueGem)
self:showGemsCounter(self.greenGem)
tween.delay(2.0, function() self:hideGemsCounter(self.redGem ) end)
tween.delay(2.2, function() self:hideGemsCounter(self.blueGem ) end)
tween.delay(2.4, function() self:hideGemsCounter(self.greenGem) end)
self.keyState = 1
self.keysx = 0
self.keysy = 0
tween(4, self, {keysx=44*w,keysy=40*h}, tween.easing.bounceInOut)
end
function HUD:drawKey()
sprite(
self.k_imgs[self.keyState],
self.keyposx,self.keyposy,
self.keysx, self.keysy
)
end
function HUD:drawBtnJump()
pushMatrix()
translate(self.controls.btnJump.pos.x, self.controls.btnJump.pos.y)
--rotate(90)
sprite(
self.controls.btnJump.imgs[math.floor(self.controls.btnJump.state)],
0,0,
self.controls.btnJump.sx, self.controls.btnJump.sy
)
popMatrix()
end
function HUD:drawGemNumbers(sgems,position)
for i=0, string.len(sgems)-1 do
local strip = string.sub(sgems,i,i+1)
if strip~="" then
local img = self.numimgs[string.format("%d",strip)..""]
if img then
sprite(
img.icon,
position.x + (i+1)*(img.sx),
position.y,
img.sx, img.sy
)
end
end
end
end
function HUD:drawPickedGems()
-- red
sprite(
self.redGem.icon, self.redGem.pos.x, self.redGem.pos.y,
self.gemsx, self.gemsy
)
sprite(
self.hudximg, self.redGem.xpos.x, self.redGem.xpos.y,
self.hudxsizx, self.hudxsizy
)
self:drawGemNumbers(self.player.nRedGems.."",self.redGem.xpos)
-- blue
sprite(
self.blueGem.icon, self.blueGem.pos.x, self.blueGem.pos.y,
self.gemsx, self.gemsy
)
sprite(
self.hudximg, self.blueGem.xpos.x, self.blueGem.xpos.y,
self.hudxsizx, self.hudxsizy
)
self:drawGemNumbers(self.player.nBlueGems.."",self.blueGem.xpos)
-- green
sprite(
self.greenGem.icon, self.greenGem.pos.x, self.greenGem.pos.y,
self.gemsx, self.gemsy
)
sprite(
self.hudximg, self.greenGem.xpos.x, self.greenGem.xpos.y,
self.hudxsizx, self.hudxsizy
)
self:drawGemNumbers(self.player.nGreenGems.."",self.greenGem.xpos)
end
function HUD:hideGemsCounter(tGem)
if useSound then
tween.delay(1,function()
sound("A Hero's Quest:Swing "..math.random(1,4))
end)
end
if tGem.tw1 then
tween.stop(tGem.tw1)
tween.stop(tGem.tw2)
end
tGem.tw1 = tween(2, tGem.pos, {x = WIDTH+self.gemsx}, tween.easing.elasticInOut)
tGem.tw2 = tween(2, tGem.xpos,{x = WIDTH+self.gemsx}, tween.easing.elasticInOut)
end
function HUD:showGemsCounter(tGem)
if tGem.tw1 then
tween.stop(tGem.tw1)
tween.stop(tGem.tw2)
end
tGem.tw1 = tween(1, tGem.pos, {x = WIDTH-166*w}, tween.easing.quadOut)
tGem.tw2 = tween(1, tGem.xpos,{x = WIDTH-127*w}, tween.easing.quadOut)
end
function HUD:drawMenu()
local dt = math.sin(ElapsedTime)
background(120, 196, 190, 255)
local m = mesh()
m.shader = shader("Effects:Ripple")
m.texture= readImage("Cargo Bot:Background Fade")
m.shader.time = ElapsedTime
m.shader.freq = 1/10
m:addRect(WIDTH/2, HEIGHT/2, WIDTH,HEIGHT)
blendMode(MULTIPLY)
m:draw()
blendMode(NORMAL)
sprite("castleFall:cloud1", 100*w*dt+30*w, HEIGHT/2)
sprite("castleFall:cloud1", WIDTH-(60*dt)-60*w, HEIGHT/2-30*h)
sprite("castleFall:p1_walk09", WIDTH/2, HEIGHT/2+(70*h+(dt*32)*h))
pushStyle()
textAlign(CENTER)
font("AcademyEngravedLetPlain")
fontSize(50*w)
fill(53,10+ math.floor( dt*100), math.random(0,48), 255)
text("Hombert Castle Fall", WIDTH/2, HEIGHT/2+200*h)
fontSize(33*w)
--textAlign(LEFT)
fill(208, 64, 32, 255)
text("New game", WIDTH/2-30*w, HEIGHT/2-23*h)
text("Continue game", WIDTH/2-30*w, HEIGHT/2-79*h)
popStyle()
end
function HUD:draw()
self:drawKey()
self:drawPickedGems()
self:drawBtnJump()
self.controller:draw()
end
function HUD:menuTouched(touch)
if touch.state == ENDED then
if pointInsideRect(WIDTH/2-30*w, HEIGHT/2-23*h, 200*w,40*h, touch.x,touch.y) then
if useSound then
sound("A Hero's Quest:Arrow Shoot 1")
end
game:newGame()
elseif pointInsideRect(
WIDTH/2-30*w, HEIGHT/2-79*h,400*w,40*h, touch.x,touch.y
) then
if useSound then
sound("A Hero's Quest:Arrow Shoot 2")
end
game:continueGame()
end
end
end
function HUD:touched(touch)
if game.state == MENU then
self:menuTouched(touch)
elseif game.state == GAME then
self:inGameTouch(touch)
end
end
function HUD:inGameTouch(touch)
self.controller:touched(touch)
local b = self.controls.btnJump
if touch.state == BEGAN and pointInsideRect(b.pos.x,b.pos.y,b.sx,b.sy,touch.x,touch.y) then
if b.state == 1 then
b.tid = touch.id
b.state = 2
self.player:jump(vec2(0,1))
tween.delay(0.2,function()
if self.controls.btnJump.state == 2 then
self.controls.btnJump.state = 2.1
elseif self.controls.btnJump.state == 2.2 then
self.controls.btnJump.state = 1
end
end)
end
elseif touch.state == ENDED and touch.id == b.tid then
if b.state == 2 then
b.state = 2.2 --waiting for btn reactivation
elseif b.state == 2.1 then -- wait done
b.state = 1
end
elseif not self.controller:check(touch) then
game.levels:touched(touch)
end
end
function pointInsideRect(x,y,sx,sy,tx,ty)
local sx = sx / 2
local sy = sy / 2
return
tx >= (x-sx) and tx <= (x+sx) and
ty <= (y+sy) and ty >= (y-sy)
end
--# Controller
Controller = class()
function Controller:activate(input)
local x0 = input.x0 or 0
local x1 = input.x1 or 1
local y0 = input.y0 or 0
local y1 = input.y1 or 1
self.cx = (x0+x1)/2 *WIDTH
self.cy = (y0+y1)/2 *HEIGHT
self.wx = (x1-x0)/2 *WIDTH
self.wy = (y1-y0)/2 *HEIGHT
self.name = input.name
end
function Controller:demo(timeout)
if ElapsedTime<timeout then
pushStyle() pushMatrix()
strokeWidth(1)
rectMode(RADIUS)
fill(0, 180, 255, 123)
rect(self.cx,self.cy,self.wx,self.wy)
fill(32, 230, 13, 255)
fontSize(20)
text("Touch here to",self.cx,self.cy+20)
text(self.name,self.cx,self.cy - 20)
popStyle() popMatrix()
end
end
function Controller:check(touch)
local goodZone = false
if math.abs((touch.x-self.cx))<self.wx
and math.abs((touch.y-self.cy))<self.wy
then
goodZone = true
end
return goodZone
end
function Controller:draw()
-- nothing
end
-- Utility functions
function touchPos(t)
return vec2(t.x, t.y)
end
function clamp(x, min, max)
return math.max(min, math.min(max, x))
end
function clampAbs(x, maxAbs)
return clamp(x, -maxAbs, maxAbs)
end
function clampLen(vec, maxLen)
if vec == vec2(0,0) then
return vec
else
return vec:normalize() * math.min(vec:len(), maxLen)
end
end
-- projects v onto the direction represented by the given unit vector
function project(v, unit)
return v:dot(unit)
end
function sign(x)
if x == 0 then
return 0
elseif x < 0 then
return -1
elseif x > 0 then
return 1
else
return x -- x is NaN
end
end
function doNothing()
end
--# Controller_VirtualStick
VirtualStick = class(Controller)
function VirtualStick:init(args)
self.radius = args.radius or 100
self.deadZoneRadius = args.deadZoneRadius or 25
self.releasedCallback = args.released or doNothing
self.steerCallback = args.moved or doNothing
self.pressedCallback = args.pressed or doNothing
-- pre-draw sprites
self.base = self:createBase()
self.stick = self:createStick()
end
function VirtualStick:createBase()
local base = image(self.radius*2+6,self.radius*2+6)
pushStyle() pushMatrix()
ellipseMode(RADIUS)
strokeWidth(1)
stroke(255, 255, 255, 255)
noFill()
setContext(base)
background(0, 0, 0, 0)
ellipse(base.width/2, base.height/2, self.radius, self.radius)
ellipse(base.width/2, base.height/2, self.deadZoneRadius, self.deadZoneRadius)
setContext()
popMatrix() popStyle()
return base
end
function VirtualStick:createStick()
local base = image(56,56)
pushStyle() pushMatrix()
ellipseMode(RADIUS)
strokeWidth(1)
stroke(255, 255, 255, 255)
noFill()
setContext(base)
background(0, 0, 0, 0)
ellipse(base.width/2, base.height/2, 0.75*25, 25)
setContext()
popMatrix() popStyle()
return base
end
function VirtualStick:touched(t)
local pos = touchPos(t)
local goodZone = self:check(t)
if t.state == BEGAN and self.touchId == nil and goodZone then
self.touchId = t.id
self.touchStart = pos
self.stickOffset = vec2(0, 0)
self.pressedCallback()
elseif t.id == self.touchId then
if t.state == MOVING then
self.stickOffset = clampLen(pos - self.touchStart, self.radius)
self.steerCallback(self:vector())
elseif t.state == ENDED or t.state == CANCELLED then
self:reset()
self.releasedCallback()
end
end
end
function VirtualStick:vector()
local stickRange = self.radius - self.deadZoneRadius
local stickAmount = math.max(self.stickOffset:len() - self.deadZoneRadius, 0)
local stickDirection = self.stickOffset:normalize()
return stickDirection * (stickAmount/stickRange)
end
function VirtualStick:reset()
self.touchId = nil
self.touchStart = nil
self.stickOffset = nil
end
function VirtualStick:draw()
if self.name ~= nil then self:demo(10) end
if self.touchId ~= nil then
sprite(self.base,self.touchStart.x, self.touchStart.y)
sprite(self.stick,
self.touchStart.x+self.stickOffset.x,
self.touchStart.y+self.stickOffset.y)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment