Created
May 11, 2012 12:17
-
-
Save peteroyle/2659283 to your computer and use it in GitHub Desktop.
Codea Achievements Framework
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
--# PointSizeHelper | |
function pointsScaled(width) | |
if WIDTH == 1024 then | |
pointRatio = WIDTH / 10000 | |
else | |
pointRatio = WIDTH / 5500 | |
end | |
return width * pointRatio | |
end | |
--# RoundRect | |
function roundRect(x, y, w, h, rad) | |
pushStyle() | |
insetPos = vec2(x+rad,y+rad) | |
insetSize = vec2(w-2*rad,h-2*rad) | |
rectMode(CORNER) | |
rect(insetPos.x,y,insetSize.x,h) | |
r,g,b,a = fill() | |
stroke(r,g,b,a) | |
if rad > 0 then | |
smooth() | |
lineCapMode(ROUND) | |
strokeWidth(rad*2) | |
--line(insetPos.x, insetPos.y, | |
--insetPos.x + insetSize.x, insetPos.y) | |
line(insetPos.x, insetPos.y, | |
insetPos.x, insetPos.y + insetSize.y) | |
--line(insetPos.x, insetPos.y + insetSize.y, | |
--insetPos.x + insetSize.x, insetPos.y + insetSize.y) | |
line(insetPos.x + insetSize.x, insetPos.y, | |
insetPos.x + insetSize.x, insetPos.y + insetSize.y) | |
end | |
popStyle() | |
end | |
--# Frame | |
Frame = class() | |
-- Frame | |
-- ver. 1.0 | |
-- a simple rectangle for holding controls. | |
-- ==================== | |
function Frame:init(x1, y1, x2, y2) | |
self.x1 = x1 | |
self.x2 = x2 | |
self.y1 = y1 | |
self.y2 = y2 | |
self.w = x2-x1 | |
self.h = y2-y1 | |
self.rad = 5 | |
end | |
function Frame:inset(dx, dy) | |
self.x1 = self.x1 + dx | |
self.x2 = self.x2 - dx | |
self.y1 = self.y1 + dy | |
self.y2 = self.y2 - dy | |
end | |
function Frame:offset(dx, dy) | |
self.x1 = self.x1 + dx | |
self.x2 = self.x2 + dx | |
self.y1 = self.y1 + dy | |
self.y2 = self.y2 + dy | |
end | |
function Frame:draw() | |
--pushStyle() | |
--rectMode(CORNERS) | |
roundRect(self.x1, self.y1, self.w, self.h, self.rad) | |
--popStyle() | |
end | |
function Frame:gloss(baseclr) | |
local i, t, r, g, b, y | |
pushStyle() | |
if baseclr == nil then baseclr = color(194, 194, 194, 255) end | |
fill(baseclr) | |
rectMode(CORNERS) | |
roundRect(self.x1, self.y1, self.w, self.h, self.rad) | |
r = baseclr.r | |
g = baseclr.g | |
b = baseclr.b | |
for i = 1 , self:height() / 2 do | |
r = r - 1 | |
g = g - 1 | |
b = b - 1 | |
stroke(r, g, b, 255) | |
y = (self.y1 + self.y2) / 2 | |
line(self.x1, y + i, self.x2, y + i) | |
line(self.x1, y - i, self.x2, y - i) | |
end | |
popStyle() | |
end | |
function Frame:shade(base, step) | |
pushStyle() | |
strokeWidth(1) | |
for y = self.y1, self.y2 do | |
i = self.y2 - y | |
stroke(base - i * step, base - i * step, base - i * step, 255) | |
line(self.x1, y, self.x2, y) | |
end | |
popStyle() | |
end | |
function Frame:touched(touch) | |
if touch.x >= self.x1 and touch.x <= self.x2 then | |
if touch.y >= self.y1 and touch.y <= self.y2 then | |
return true | |
end | |
end | |
return false | |
end | |
function Frame:ptIn(x, y) | |
if x >= self.x1 and x <= self.x2 then | |
if y >= self.y1 and y <= self.y2 then | |
return true | |
end | |
end | |
return false | |
end | |
function Frame:width() | |
return self.x2 - self.x1 | |
end | |
function Frame:height() | |
return self.y2 - self.y1 | |
end | |
function Frame:midx() | |
return (self.x1 + self.x2) / 2 | |
end | |
function Frame:midy() | |
return (self.y1 + self.y2) / 2 | |
end | |
function Frame:setMidx(mx) | |
self.x1 = mx - self.w/2 | |
self.x2 = mx + self.w/2 | |
end | |
function Frame:setMidy(my) | |
self.y1 = my - self.h/2 | |
self.y2 = my + self.h/2 | |
end | |
--# TextButton | |
TextButton = class() | |
-- TextButton | |
-- ver. 1.0 | |
-- a control for displaying a simple button | |
-- ==================== | |
function TextButton:init(x, y, s, fontS, clr, txtClr) | |
pushStyle() | |
font("Futura-Medium") | |
textMode(CENTER) | |
if fontS == nil then | |
fontS = 14 | |
end | |
self.x = x | |
self.y = y | |
self.fontS = fontS | |
self:setText(s) | |
self.clr = clr | |
if self.clr == nil then | |
self.clr = color(62, 62, 62, 255) | |
end | |
self.txtClr = txtClr | |
if self.txtClr == nil then | |
self.txtClr = color(255, 255, 255, 255) | |
end | |
popStyle() | |
end | |
function TextButton:updateWidth() | |
pushStyle() | |
fontSize(self.fontS * 1.2) | |
local w, h = textSize(self.text) | |
w = w + 15 | |
h = h + 6 | |
self.frame = Frame(self.x, self.y, self.x+w, self.y+h) | |
self.w = w | |
self.h = h | |
popStyle() | |
end | |
function TextButton:draw() | |
pushStyle() | |
stroke(0, 0, 0, 255) | |
font("Arial-BoldMT") | |
textMode(CENTER) | |
fontSize(self.fontS) | |
fill(self.clr) | |
self.frame:gloss(self.clr, 1) | |
--self.frame:draw()i | |
fill(self.txtClr) | |
text(self.text, self.frame:midx(), self.frame:midy()) | |
popStyle() | |
end | |
function TextButton:touched(touch) | |
return self.frame:touched(touch) | |
end | |
function TextButton:setText(txt) | |
if txt ~= self.txt then | |
self.text = txt | |
self:updateWidth() | |
end | |
end | |
--# DialogAB | |
DialogAB = class() | |
function DialogAB:init(frameDims, txt, txtSize, btnAConf, btnBConf, screenCol, bgCol, txtCol, | |
btnBgCol, btnTxtCol) | |
self.frameDims = frameDims | |
self.width = frameDims.x | |
self.height = frameDims.y | |
self.txt = txt | |
self.txtSize = txtSize | |
self.menuPos = vec2(self.frameDims.x/2, self.frameDims.y/2) | |
self.menuDims = vec2(self.frameDims.x*0.5, self.frameDims.y*0.5) | |
self.btnAConf = btnAConf | |
self.btnBConf = btnBConf | |
self.btnPoints = pointsScaled(self.frameDims.x*0.4) | |
local quitBtnOfsY = self.menuDims.y * 0.4 | |
self.quitBtn = TextButton(0, self.menuPos.y-quitBtnOfsY, | |
btnAConf.text, self.btnPoints, btnBgCol, btnTxtCol) | |
if self.btnBConf ~= nil then | |
self.resumeBtn = TextButton(0, self.menuPos.y-quitBtnOfsY, | |
btnBConf.text, self.btnPoints, btnBgCol, btnTxtCol) | |
local maxW = math.max(self.quitBtn.frame.w, self.resumeBtn.frame.w) | |
self.quitBtn.frame.w = maxW | |
self.resumeBtn.frame.w = maxW | |
self.quitBtn.frame:setMidx(self.menuPos.x - self.menuDims.x/4) | |
self.resumeBtn.frame:setMidx(self.menuPos.x + self.menuDims.x/4) | |
else | |
self.quitBtn.frame:setMidx(self.menuPos.x) | |
end | |
self:initColors(screenCol, bgCol, txtCol, btnBgCol, btnTxtCol) | |
end | |
function DialogAB:isActive() | |
return self.pausedState == 1 | |
end | |
function DialogAB:setActive(act) | |
if act == true then | |
self.pausedState = 1 | |
else | |
self.pausedState = 0 | |
end | |
end | |
function DialogAB:draw() | |
if self.pausedState == 1 then | |
self:drawPauseMenu() | |
end | |
end | |
function DialogAB:drawPauseMenu() | |
pushStyle() | |
fill(self.screenCol) | |
rect(-2, -2, self.frameDims.x+3, self.frameDims.y+3) | |
fill(self.bgCol) | |
roundRect(self.menuPos.x-self.menuDims.x/2, self.menuPos.y-self.menuDims.y/2, | |
self.menuDims.x, self.menuDims.y, self.menuDims.x*0.05) | |
fontSize(self.txtSize) | |
textMode(CENTER) | |
fill(self.txtCol) | |
textWrapWidth(self.menuPos.x*0.8) | |
text(self.txt, self.menuPos.x, self.menuPos.y+self.menuDims.y*0.25) | |
self.quitBtn:draw() | |
if self.resumeBtn then | |
self.resumeBtn:draw() | |
end | |
popStyle() | |
end | |
function DialogAB:touched(t) | |
if self.quitBtn:touched(t) and t.state == ENDED then | |
self:setActive(false) | |
if self.btnAConf.action ~= nil then | |
self.btnAConf:action() | |
end | |
end | |
if self.resumeBtn and self.resumeBtn:touched(t) and t.state == ENDED then | |
self:setActive(false) | |
if self.btnBConf.action ~= nil then | |
self.btnBConf:action() | |
end | |
end | |
end | |
function DialogAB:initColors(screenCol, bgCol, txtCol, btnBgCol, btnTxtCol) | |
self.screenCol = screenCol | |
if self.screenCol == nil then | |
self.screenCol = color(20, 20, 20, 200) | |
end | |
self.bgCol = bgCol | |
if self.bgCol == nil then | |
self.bgCol = color(255, 207, 0, 255) | |
end | |
self.txtCol = txtCol | |
if self.txtCol == nil then | |
self.txtCol = color(255, 0, 0, 255) | |
end | |
self.btnBgcol = btnBgCol | |
if self.btnBgCol == nil then | |
self.btnBgCol = color(72, 72, 72, 255) | |
end | |
self.btnTxtCol = btnTxtCol | |
if selfBtnTxtCol == nil then | |
self.btnTxtCol = color(255, 255, 255, 255) | |
end | |
end | |
--# Trophy | |
-- was HintFreeZone | |
Trophy = class() | |
TROPHY_PREFIX="TROPHY_" | |
function Trophy:init(frameDims, name, description, col, xPos, yPos, wid) | |
self.frameDims = frameDims | |
self.name = name | |
self.description = description | |
self.col = col | |
local colDif = 1.1 | |
self.colBorder = color(col.r*colDif, col.g*colDif, col.b*colDif, col.a) | |
self.x = frameDims.x * xPos | |
self.y = frameDims.y * yPos | |
self.w = frameDims.x * wid | |
self.r = self.w/2 | |
self.points = pointsScaled(self.w*1.3) | |
self.animated=animated | |
self.explainDlg = DialogAB(frameDims, self.name .. " Achievement:\n\n"..self.description, | |
pointsScaled(frameDims.x*0.2), {text="OK"}, nil, nil, | |
self.col, color(255, 255, 255, 255), | |
color(255, 255, 255, 255), color(0, 0, 0, 255)) | |
self.roundFrill = math.random()>0.95 | |
self.completed = readLocalData(TROPHY_PREFIX..self.name) | |
end | |
function Trophy:setAchieved(ach) | |
if self.completed ~= ach then | |
saveLocalData(TROPHY_PREFIX..self.name, ach) | |
self.completed = ach | |
self.explainDlg:setActive(ach) | |
self:resetDisplay() | |
end | |
end | |
function Trophy:resetDisplay() | |
self.img = nil | |
self.imgFX = nil | |
self:setDisplayOptions() | |
end | |
-- here is a good place to customise the appearence of the trophies in their different states | |
function Trophy:setDisplayOptions() | |
if self.explainDlg:isActive() then | |
-- drop GLOW | |
self.blur = color(255, 255, 255, 77) | |
-- slight transparency for jitter effect | |
self.transp =color(255, 255, 255, 74) | |
elseif self.completed then | |
-- drop SHADDOW | |
self.blur = color(127, 127, 127, 54) | |
-- no transparency for readability | |
self.transp =color(255, 255, 255, 255) | |
else | |
-- drop GLOW | |
self.blur = color(255, 255, 255, 21) | |
-- high transparency for ghost effect | |
self.transp = color(68, 68, 68, 15) | |
end | |
end | |
function Trophy:drawDialog() | |
if self.explainDlg:isActive() then | |
self.explainDlg:draw() | |
pushStyle() | |
pushMatrix() | |
fontSize(pointsScaled(self.frameDims.x*0.6)) | |
translate(self.frameDims.x/2, self.frameDims.y/2) | |
rotate(40) | |
if self.completed then | |
else | |
fill(255, 0, 0, 70) | |
text("[ incomplete ]", 0,0) | |
end | |
popMatrix() | |
popStyle() | |
-- now draw the badge over the top of the modal shade | |
self:draw() | |
end | |
end | |
function Trophy:draw() | |
self:setDisplayOptions() | |
self:initImg() | |
if self.explainDlg:isActive() then | |
self:drawWithFX(self.x, self.y) | |
else | |
if self.imgFX == nil then | |
local imgw = self.w*1.5 | |
self.imgFX = image(imgw, imgw) | |
setContext(self.imgFX) | |
self:drawWithFX(imgw/2, imgw/2) | |
setContext() | |
end | |
sprite(self.imgFX, self.x, self.y) | |
end | |
end | |
function Trophy:drawWithFX(x, y) | |
pushStyle() | |
if self.blur then | |
tint(self.blur) | |
local maxOffset = self.w*0.03 | |
local offsetx=0 | |
local offsety=0 | |
for i=0, 5 do | |
offsetx=math.random()*maxOffset*2-maxOffset | |
offsety=math.random()*maxOffset*2-maxOffset | |
sprite(self.img,x+offsetx,y+offsety) | |
end | |
end | |
tint(self.transp) | |
sprite(self.img,x,y) | |
popStyle() | |
end | |
function Trophy:initImg() | |
if self.img == nil then | |
local imgw = self.w*1.5 | |
self.img = image(imgw, imgw) | |
setContext(self.img) | |
self:drawSticker(imgw) | |
setContext() | |
end | |
end | |
function Trophy:drawSticker(imgw) | |
pushStyle() | |
pushMatrix() | |
ellipseMode(CENTER) | |
rectMode(CENTER) | |
translate(imgw/2, imgw/2) | |
fill(209, 70, 54, 255) | |
fill(53, 90, 208, 255) | |
fill(self.colBorder) | |
local wfact = (math.random()*0.1) | |
for a=1, 36 do | |
if self.roundFrill then | |
ellipse(0, self.w/2, self.w*0.1) | |
else | |
rect(0, self.w/2, self.w*(0.2+wfact), self.w*0.05) | |
end | |
rotate(10) | |
end | |
fill(196, 63, 51, 255) | |
fill(51, 75, 195, 255) | |
fill(self.col) | |
ellipse(0, 0, self.w) | |
rotate(15) | |
textWrapWidth(self.w*0.85) | |
textAlign(CENTER) | |
fontSize(self.points) | |
fill(229, 214, 214, 255) | |
text(self.name, 0, self.w*0.05) | |
popMatrix() | |
popStyle() | |
end | |
function Trophy:touchedTrophy(t) | |
if t.state==BEGAN then | |
local xdist = math.abs(t.x-self.x) | |
local ydist = math.abs(t.y-self.y) | |
local tdistSqr = xdist*xdist+ydist*ydist | |
if tdistSqr <= self.r*self.r then | |
self.explainDlg:setActive(true) --self.explainDlg:isActive()==false) | |
end | |
self:setDisplayOptions() | |
end | |
end | |
function Trophy:touchedDialog(t) | |
if self.explainDlg:isActive() then | |
self.explainDlg:touched(t) | |
end | |
end | |
--# TrophyCabinet | |
-- holds the collection of possible achievements, and can render and control them all | |
TrophyCabinet = class() | |
function TrophyCabinet:init() | |
local frameDims = vec2(WIDTH, HEIGHT) | |
-- customise your trophies/achievements here | |
self.trophies = {minimalist=Trophy(frameDims, "Minimalist", | |
"Make your first painting!", | |
color(111, 130, 226, 255), 0.93, 0.05, 0.2), | |
apprentice=Trophy(frameDims, "Apprentice", | |
"Spend longer than 10 seconds on a single painting", | |
color(156, 106, 223, 255), 0.08, 0.15, 0.2), | |
greenThumb=Trophy(frameDims, "Green Thumb", | |
"Paint using any shade of green", | |
color(105, 223, 106, 255), 0.08, 0.5, 0.2), | |
master=Trophy(frameDims, "Master", | |
"Spend longer than 20 seconds on a single painting", | |
color(124, 122, 196, 255), 0.6, 0.05, 0.2)} | |
end | |
function TrophyCabinet:draw() | |
for name,trophy in pairs(self.trophies) do | |
trophy:draw() | |
end | |
for name,trophy in pairs(self.trophies) do | |
trophy:drawDialog() | |
end | |
end | |
function TrophyCabinet:touched(touch) | |
if self:isDialogActive() then | |
for name,trophy in pairs(self.trophies) do | |
trophy:touchedDialog(touch) | |
end | |
else | |
for name,trophy in pairs(self.trophies) do | |
trophy:touchedTrophy(touch) | |
end | |
end | |
end | |
function TrophyCabinet:isDialogActive(touch) | |
for name,trophy in pairs(self.trophies) do | |
if trophy.explainDlg:isActive() then | |
return true | |
end | |
end | |
return false | |
end | |
-- handy for debugging/testing | |
function TrophyCabinet:resetAll() | |
for name,trophy in pairs(self.trophies) do | |
trophy:setAchieved(false) | |
end | |
end | |
--# Menu | |
Menu = class() | |
function Menu:init() | |
self.start = TextButton(WIDTH/2, HEIGHT/2, "Start", 100) | |
self.reset = TextButton(WIDTH*0.1, HEIGHT*0.9, "RESET ACHIEVEMENTS", 20) | |
self.trophyCabinet = TrophyCabinet() | |
end | |
function Menu:draw() | |
if self.main then | |
-- draw game | |
self.main:draw() | |
else | |
-- draw menu | |
fill(215, 231, 230, 255) | |
rect(0,0,WIDTH, HEIGHT) | |
self.start:draw() | |
self.reset:draw() | |
self.trophyCabinet:draw() | |
end | |
end | |
function Menu:touched(touch) | |
if self.main then | |
-- main game crontrol | |
self.main:touched(touch) | |
else | |
-- menu control | |
self.trophyCabinet:touched(touch) | |
if touch.state == BEGAN then | |
if self.start:touched(touch) then | |
self:startGame() | |
elseif self.reset:touched(touch) then | |
self.trophyCabinet:resetAll() | |
end | |
end | |
end | |
end | |
function Menu:startGame() | |
self.main = Main() | |
self.main:setupGame(self, self.trophyCabinet) | |
end | |
function Menu:endGame() | |
self.main = nil | |
end | |
--# Main | |
displayMode(FULLSCREEN) | |
function setup() | |
menu = Menu() | |
end | |
-- This function gets called once every frame | |
function draw() | |
menu:draw() | |
end | |
function touched(t) | |
menu:touched(t) | |
end | |
Main = class() | |
function Main:init() | |
self.touches={} | |
self.gameBoard=nil | |
end | |
-- After you create a Main() instance, call this method once for each player | |
function Main:setupGame(menu, trophyCabinet) | |
self.gameBoard = GameBoard(menu, trophyCabinet) | |
end | |
-- render each player's game board | |
function Main:draw() | |
fill(0, 0, 0, 255) | |
rect(0,0,WIDTH+1,HEIGHT+1) | |
self.gameBoard:draw() | |
end | |
-- handle multitouch, passing each touch to each player's game board | |
function Main:touched(touch) | |
self.touches[touch.id] = touch | |
for j,t in pairs(self.touches) do | |
self.gameBoard:touched(t) | |
end | |
if touch.state == ENDED then | |
self.touches[touch.id] = nil | |
end | |
end | |
--# GameBoard | |
GameBoard = class() | |
function GameBoard:init(mainMenu, trophyCabinet) | |
self.mainMenu = mainMenu | |
self.trophyCabinet = trophyCabinet | |
self.startTime = ElapsedTime | |
self.circles={} | |
self.color = color(math.random()*255, math.random()*255, math.random()*255, 255) | |
-- the green thumb achievement | |
if self.color.g > self.color.r and self.color.g > self.color.b then | |
self.trophyCabinet.trophies.greenThumb:setAchieved(true) | |
end | |
self.quitBtn = TextButton(WIDTH*0.1, HEIGHT*0.1, "QUIT", pointsScaled(WIDTH*0.3)) | |
end | |
-- time based achievements | |
function GameBoard:checkTimeAchievements() | |
self.trophyCabinet.trophies.minimalist:setAchieved(true) | |
if ElapsedTime >= self.startTime + 20 then | |
self.trophyCabinet.trophies.master:setAchieved(true) | |
elseif ElapsedTime >= self.startTime + 10 then | |
self.trophyCabinet.trophies.apprentice:setAchieved(true) | |
end | |
end | |
function GameBoard:draw() | |
self:checkTimeAchievements() | |
pushStyle() | |
pushMatrix() | |
fill(self.color) | |
for i,v in ipairs(self.circles) do | |
ellipse(v.x, v.y, 200, 200) | |
end | |
fontSize(200) | |
text("Paint!", WIDTH*0.5, HEIGHT*0.3) | |
self.quitBtn:draw() | |
popMatrix() | |
popStyle() | |
end | |
function GameBoard:isTouchInsideBoard(t) | |
return t.x >= 0 and t.x <= WIDTH and | |
t.y >= 0 and t.y <= HEIGHT | |
end | |
function GameBoard:touched(t) | |
if self:isTouchInsideBoard(t) then | |
table.insert(self.circles, vec2(t.x,t.y)) | |
end | |
if t.state==BEGAN and self.quitBtn:touched(t) then | |
self.mainMenu:endGame() | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment