Skip to content

Instantly share code, notes, and snippets.

@peteroyle
Created May 11, 2012 12:17
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 peteroyle/2659283 to your computer and use it in GitHub Desktop.
Save peteroyle/2659283 to your computer and use it in GitHub Desktop.
Codea Achievements Framework
--# 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