I’m trying to creat a timer that looks like a circle with the center punched out. As the counter ticks down I’d like the circle to diminish in size. Example full timer would be a 360 degree circle half would be 180. I have an idea I’m working on but my FPS are terrible. The idea is creating a circle using Sin and Cos and for every degree I draw a line. Is there a better more efficient way of doing this?
--# Main
function setup()
displayMode(FULLSCREEN)
t = TimerButton({x = x,y = y,size = 150,counterWidth = 15,angleSize = .5})
b = TimerButton()
end
function draw()
background(127, 127, 127, 255)
t:draw()
b:draw()
end
--# TimerButton
TimerButton = class()
function TimerButton:init(tbl)
-- you can accept and set parameters here
if not tbl then tbl = {} end
self.x = tbl.x or WIDTH/2
self.y = tbl.y or HEIGHT/2
self.startColor = tbl.startColor or color(26, 255, 0, 255)
self.midColor = tbl.midColor or color(254, 255, 0, 255)
self.endColor = tbl.endColor or color(255, 0, 0, 255)
self.x1 = 0
self.y1 = 0
self.size = tbl.size or 100
self.counterWidth = tbl.counterWidth or 25
self.angle = 0
self.angleSize = tbl.angleSize or 0.5
self.loc = {}
while self.angle < 360 do
local t = {}
t.x = self.size * math.cos(math.rad(self.angle))
t.y = self.size * math.sin(math.rad(self.angle))
t.x1 = t.x +(self.counterWidth * math.cos(math.rad(self.angle)))
t.y1 = t.y + (self.counterWidth * math.sin(math.rad(self.angle)))
self.angle = self.angle + self.angleSize
table.insert(self.loc,t)
end
end
function TimerButton:draw()
-- Codea does not automatically call this method
pushMatrix()
translate(self.x,self.y)
stroke(self.startColor)
strokeWidth(4)
for i = 1,#self.loc do
line(self.loc[i].x,self.loc[i].y,self.loc[i].x1,self.loc[i].y1)
end
popMatrix()
end
function TimerButton:touched(touch)
-- Codea does not automatically call this method
end
function setup()
parameter.integer("time",1,60,30)
st=false
a=0
str="Tap screen to start timer"
end
function draw()
background(40,40,50)
stroke(255)
strokeWidth(2)
for z=a,360,.2 do
x=math.sin(math.rad(z))*WIDTH/2
y=math.cos(math.rad(z))*WIDTH/2
line(WIDTH/2,HEIGHT/2,WIDTH/2+x,HEIGHT/2+y)
end
fill(255, 0, 0, 255)
text(str,WIDTH/2,HEIGHT/2)
if st then
t1=os.date("*t")
sec=t1.sec
if t2~=sec then
t2=sec
a=a+360/time
end
if a>=360 then
st=false
str="Time expired"
end
end
end
function touched(t)
if t.state==BEGAN then
st=true
str=""
t1=os.date("*t")
t2=t1.sec
end
end
Thanks @dave1707 but I’m still getting about 5 fps. Same problem I was having with my test. I feel its the fact that we are drawing to many lines each draw.
@Chipis I didn’t look at the FPS, so I didn’t realize it was so low. I guess the way I was doing it doesn’t work that well. I’ll have to try something else.
Here’s something I made a little while ago for my game. The color tweens from green to red as time passes and it beeps more and more frequently, but those thigs could easily be removed, just like size changing could easily be added. Here it is:
--# Main
-- Timer
-- Use this function to perform your initial setup
function setup()
parameter.watch("1/DeltaTime")
t = Timer(15)
end
-- This function gets called once every frame
function draw()
-- This sets a dark background color
background(255, 255, 255, 255)
-- This sets the line thickness
strokeWidth(5)
-- Do your drawing here
t:draw()
end
function touched(touch)
if touch.state == ENDED then
if touch.tapCount == 1 then
if t.timing == nil then
t:start()
elseif not t.paused then
t:pause()
else
t:resume()
end
else
t:restart()
end
end
end
--# Timer
Timer = class()
function Timer:init(t, cb, x, y, s)
self.time = t or 5
self.callback = cb or function() end
self.x = x or WIDTH / 2
self.y = y or HEIGHT / 2
self.size = s or HEIGHT * 3/4
self.timing = nil
self.beeping = nil
self.amnt = 0
self.beepTime = 10
self.paused = false
self.tMesh = mesh()
self.tMesh.vertices = triangulate({vec2(-self.size / 2, -self.size / 2),
vec2(-self.size / 2, self.size / 2),
vec2(self.size / 2, self.size / 2),
vec2(self.size / 2, -self.size / 2)})
self.tMesh.shader = shader("Patterns:Arc")
self.tMesh.shader.a1 = math.pi
self.tMesh.shader.a2 = math.pi
self.tMesh.shader.size = .45
self.tMesh.shader.color = vec4(0, 1, 0, 1)
self.tMesh.texCoords = triangulate({vec2(0,0),vec2(0,1),vec2(1,1),vec2(1,0)})
end
function Timer:beep()
sound("Game Sounds One:Menu Select")
self.beepTime = self.beepTime + 2
self.beeping = tween.delay(self.time / self.beepTime, function() self:beep() end)
end
function Timer:start()
self.amnt = 0
self.beepTime = 10
if self.timing == nil then
self.timing = tween(self.time, self, { amnt = 1 }, tween.easing.linear, self.callback)
end
if self.beeping == nil then
self.beeping = tween.delay(self.time / self.beepTime, function() self:beep() end)
end
end
function Timer:pause()
if self.timing ~= nil then
tween.stop(self.timing)
end
if self.beeping ~= nil then
tween.stop(self.beeping)
end
self.paused = true
end
function Timer:resume()
if self.timing ~= nil then
tween.play(self.timing)
end
if self.beeping ~= nil then
tween.stop(self.beeping)
end
self.paused = false
end
function Timer:stop()
if self.timing ~= nil then
tween.stop(self.timing)
self.timing = nil
end
if self.beeping ~= nil then
tween.stop(self.beeping)
self.beeping = nil
end
end
function Timer:restart()
self:stop()
self:start()
end
function Timer:draw()
-- Update timer
self.tMesh.shader.color = vec4(1 * self.amnt, 1 - (1 * self.amnt), 0, 1)
self.tMesh.shader.a2 = -self.amnt * (math.pi * 2) + math.pi
-- Draw timer
pushMatrix()
translate(self.x, self.y)
local t
if self.timing then t = math.ceil(self.timing.time - self.timing.running) else t = self.time end
fontSize(self.size*2/3) fill(255 * self.amnt, 255 - (255 * self.amnt), 0, 255)
-- text(t)
rotate(270.1)
self.tMesh:draw()
popMatrix()
end
function Timer:touched(touch)
-- Codea does not automatically call this method
end
@Chipis Here’s another version that has a high FPS.
function setup()
parameter.integer("time",1,60,30)
str="Tap screen to start timer"
a=0
st=false
xx()
end
function xx()
tab={}
for z=a,360 do
x=math.sin(math.rad(z))*WIDTH/2
y=math.cos(math.rad(z))*WIDTH/2
if z>a then
table.insert(tab,vec2(WIDTH/2,HEIGHT/2))
table.insert(tab,vec2(WIDTH/2+x,HEIGHT/2+y))
table.insert(tab,vec2(WIDTH/2+x1,HEIGHT/2+y1))
end
x1=x
y1=y
end
m=mesh()
m.vertices=tab
m:setColors(255,0,0)
end
function draw()
background(40, 40, 50)
m.draw(m)
t1=os.date("*t")
if st then
sec=t1.sec
if t2~=sec then
t2=sec
a=a+360/time
xx()
if a>=360 then
str="Time expired"
end
end
end
fill(40,40,50)
ellipse(WIDTH/2,HEIGHT/2,WIDTH-50)
fill(255)
text(string.format("%d",1/DeltaTime),WIDTH/2,HEIGHT-50)
text(str,WIDTH/2,HEIGHT/2)
end
function touched(t)
if t.state==BEGAN then
st=true
str=""
xx()
t1=os.date("*t")
t2=t1.sec
end
end
Here is my attempt with the arc shader. I can not figure out how to correctly increment it smoothly or accurately. Maybe someone brighter then I could fix it. It is working great in concept and it is fast.
--# Main
--Visual Timer
supportedOrientations(LANDSCAPE_ANY)
function setup()
t=Timer(20)
t1 = Timer(5,100,100,{width=5,radius=50})
t2 = Timer(10,WIDTH-100,HEIGHT-100,{width=10,radius=50})
t:start()
t1:start()
t2:start()
parameter.watch("time")
end
function draw()
background(255, 255, 255, 255)
time = t.time
t:draw()
t1:draw()
t2:draw()
--arc(cx, cy, radius, startAngle, endAngle)
end
function touched(t)
end
--# Shader
Shader = {}
Shader.vertex = [[
//
// Vertex shader: Arc
//
uniform mat4 modelViewProjection;
attribute vec4 position;
attribute vec2 texCoord;
varying highp vec2 vTexCoord;
void main() {
vTexCoord = texCoord;
gl_Position = modelViewProjection * position;
}
]]
Shader.fragment = [[
//
// Fragment shader: Arc
//
precision highp float;
uniform float size;
uniform float a1;
uniform float a2;
uniform vec4 color;
varying vec2 vTexCoord;
void main() {
vec4 col = vec4(0.0);
vec2 r = vTexCoord - vec2(0.5);
float d = length(r);
if (d > size && d < 0.5) {
float a = atan(r.y, r.x);
if (a2 > a1) {
if (a > a1 && a < a2) {
col = color;
}
} else {
if (a > a1 || a < a2) {
col = color;
}
}
}
gl_FragColor = col;
}
]]
--# Timer
Timer = class()
function Timer:init(time,x,y,tbl)
-- you can accept and set parameters here
local tbl = tbl or {}
self.paused = true
self.startTime = 0
self.tickTime = 0
self.time = time or 60
self.increment = math.rad(360/ (self.time *100))
self.radius = tbl.radius or 200
self.width = tbl.width or 25
--Colors
self.startColor = tbl.startColor or color(0, 255, 0, 255)
self.midColor = tbl.midColor or color(255, 255, 0, 255)
self.endColor = tbl.endColor or color(255, 0, 0, 255)
self.x = x or WIDTH/2
self.y = y or HEIGHT/2
self.m = mesh()
self.m:addRect(0,0, self.radius * 2, self.radius * 2)
self.m.shader = shader(Shader.vertex,Shader.fragment)
self.m.shader.size = (1 - self.width/self.radius) * 0.5
self.m.shader.a1 = math.rad(180)
self.m.shader.a2 = math.rad(180)
end
function Timer:draw()
-- Codea does not automatically call this method
if not self.paused then
local t = tonumber(string.format("%.2f",ElapsedTime))
if t > self.startTime +1 then
self.startTime = t
self.time = self.time - 1
end
if t > self.tickTime then
self.tickTime = t
self.m.shader.a2 = self.m.shader.a2 - self.increment
end
end
pushMatrix()
translate(self.x,self.y)
local clr
if math.deg(self.m.shader.a2) > 0 then
clr = self.startColor
elseif math.deg(self.m.shader.a2) <=0 and math.deg(self.m.shader.a2) > -90 then
clr = self.midColor
else
clr = self.endColor
end
self.m.shader.color = clr
self.m:draw()
popMatrix()
end
function Timer:touched(touch)
-- Codea does not automatically call this method
end
function Timer:start()
self.startTime = ElapsedTime
self.paused = false
end
@Chipis - it would be much easier to use a timer where the circle shrank from the outside toward the middle (by just drawing a smaller and smaller circle). I believe it’s usually best to work to the strengths of your program (ie stick to what it can do easily, where possible).
Otherwise, this is a huge amount of work to do, just for a tiny part of your game.
Thank you guys for the posts, I now have a better idea where I’m going with this timer. Don’t go too far, I’m sure I’ll have more questions in the near future.