Last active
January 1, 2016 17:39
-
-
Save juaxix/8178134 to your computer and use it in GitHub Desktop.
Hombert Castle Fall
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--# Main | |
-- 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