Analog Clock
Setup
Let's build an analog clock that shows three hands: A second, minute, and hour hand that dynamically update!
Most analog clocks are circles, so we will use the DrawCircle()
function to render one. Let's start with a basic OLC project template:
#define OLC_PGE_APPLICATION
#include "olcPixelGameEngine.h"
// Override base class with your custom functionality
class AnalogClock : public olc::PixelGameEngine
{
public:
AnalogClock()
{
// Name your application
sAppName = "Analog Clock";
}
public:
bool OnUserCreate() override
{
return true;
}
bool OnUserUpdate(float fElapsedTime) override
{
return true;
}
};
int main()
{
AnalogClock demo;
if (demo.Construct(256, 240, 2, 2))
demo.Start();
return 0;
}
Clock Outline
Start by clearing the screen each frame and then drawing a circle with a white outline in the center of the screen. Position defines the center of the circle. Let's make the radius of the circle 100
pixels.
We will do this in OnUserUpdate()
:
bool OnUserUpdate(float fElapsedTime) override
{
Clear(olc::VERY_DARK_GREEN);
DrawCircle(GetScreenSize()/2,100);
return true;
}
Notice:
Usage of
GetScreenSize()
and dividing by 2 here means we don't have to use magic numbers to figure out what the center of the screen is. It returns a vector with the width and height for us.
Clock Numbers
Generating the numbers displayed around the clock will involve a little maths.
We want to generate one number every 30 degrees on the clock. This is because there are 12 numbers and 360/12 == 30
.
We can use the olc::vf2d
class which is a vector class that stores an x
and a y
component. Instead of using it to store cartesian coordinates, let's use it to store polar coordinates.
In the polar coordinate system, points have a magnitude and an angle. We can use these to our advantage to traverse around the clock's outline while placing numbers at each point.
Attempting the Problem
Let's first try to draw some reference dots to get a better idea of what we are doing here.
So for i
in 12 times, we will draw a pixel at angle * i
degrees from the center. Let's use a radius of 80
this time so it doesn't overlap the circle.
bool OnUserUpdate(float fElapsedTime) override
{
Clear(olc::VERY_DARK_GREEN);
DrawCircle(GetScreenSize()/2,100);
for(int i{0};i<12;i++){
olc::vf2d drawVec{80,float(i*30)};
Draw(drawVec);
}
return true;
}
And...This isn't quite right.
What happened?
If we intend to represent a olc::vf2d in the polar coordinate space, we need to convert it back to cartesian coordinates to get a sensible result for the screen. olc::vf2d comes with a built-in function called
cart()
to do this for us.
for(int i{0};i<12;i++){
olc::vf2d drawVec{80,float(i*30)};
Draw(drawVec.cart());
}

Still not quite right, but starting to look a little like something. We have to remember that these are just representations of a grid from the origin of (0,0). Tip:
After converting a polar coordinate to cartesian coordinates, add back in your origin to offset the entire circle.
for(int i{0};i<12;i++){
olc::vf2d drawVec{80,float(i*30)};
Draw(GetScreenSize()/2+drawVec.cart());
}
Surely everything is great now, righ-...?
One more fundamental issue here. Maybe you have caught on by now. The angles are incorrect. C++ deals with angles in radians, not degrees! Therefore we must convert to radians each time we attempt to calculate our angles.
A little revisit to trigonometry class! Conversion from degrees to radians is deg * (π/180)
. Therefore we can write a small utility conversion function.
const float degToRad(const float deg){
using namespace std::numbers;
return deg*(pi/180);
}
bool OnUserUpdate(float fElapsedTime) override
{
Clear(olc::VERY_DARK_GREEN);
DrawCircle(GetScreenSize()/2,100);
for(int i{0};i<12;i++){
olc::vf2d drawVec{80,degToRad(i*30)};
Draw(GetScreenSize()/2+drawVec.cart());
}
return true;
}
Great! Another way to think about this is if π is half a revolution, then dividing it into sixths would also give us the correct result in radians. So alternatively we can do without the extra function and just multiply by π/6
for each number. That would look like this:
bool OnUserUpdate(float fElapsedTime) override
{
Clear(olc::VERY_DARK_GREEN);
DrawCircle(GetScreenSize()/2,100);
for(int i{0};i<12;i++){
using namespace std::numbers;
olc::vf2d drawVec{80,float(i*(pi/6))};
Draw(GetScreenSize()/2+drawVec.cart());
}
return true;
}
A little cleaner, and an alternative solution! Choose the one you like the most. The new result looks good! The angles are now correct.
Rendering the Numbers
Now that we have determined how to draw around the clock via code, we still need to draw the numbers themselves. If we just attempt to draw them, they will all be left-aligned to their corresponding dots.
for(int i{0};i<12;i++){
using namespace std::numbers;
olc::vf2d drawVec{80,float(i*(pi/6))};
const olc::vf2d drawPos{GetScreenSize()/2+drawVec.cart()};
Draw(drawPos);
DrawString(drawPos,std::format("{}",i+1));
}
I've moved the final rendered drawing coordinate to a variable so we can keep the dots around as a reference point. I am also adding 1 to i
so the numbers start at 1, not 0.

As you can see, the numbers start from 1 to 12. But they also start from the right. In C++, 0 radians is pointing to the right and going clockwise, as we are now witnessing here. Let's first fix the degrees issue. We have to start by having 12 pointing to the top instead of the 2 o' clock position, which means bringing the rotation back by 60 degrees, or π/3
radians.
olc::vf2d drawVec{80,float(i*(pi/6)-pi/3)};
Much better.
Next we focus on centering the text on each dot. One trick I like to use for centering an image or text is the rotated version of the functions. So instead of DrawString()
I am going to use DrawRotatedStringDecal()
. Using a decal version of the string drawing routine is inconsequential here.
for(int i{0};i<12;i++){
using namespace std::numbers;
olc::vf2d drawVec{80,float(i*(pi/6)-pi/3)};
const olc::vf2d drawPos{GetScreenSize()/2+drawVec.cart()};
Draw(drawPos);
const std::string text{std::format("{}",i+1)};
const olc::vf2d textNumbSize{GetTextSize(text)};
DrawRotatedStringDecal(drawPos,text,0.f,textNumbSize/2);
}
So this is a little bit involved. First I separated the text
argument since we need it twice. To center text we need to get the size of the entire text and then divide it by 2.
Once I have that I can plug it in for the center
argument of DrawRotatedStringDecal()
to create a nice little trick of centering text without too much mucking with the drawing position itself.
The result looks like this:
Very nice. Just to make it look a little cooler, I'm going to make the numbers be a grey color and push the numbers back a little bit so they are farther to the edges of the clock. I think I will keep the dots there, they seem to make good decorations.
for(int i{0};i<12;i++){
using namespace std::numbers;
//Draw decorative dots
olc::vf2d drawVec{60,float(i*(pi/6)-pi/3)};
const olc::vf2d drawPos{GetScreenSize()/2+drawVec.cart()};
Draw(drawPos);
//Draw clock numbers
olc::vf2d numberVec{drawVec};
numberVec.x=90; //Adjusted further towards the edge of the clock.
const olc::vf2d numberDrawPos{GetScreenSize()/2+numberVec.cart()};
const std::string text{std::format("{}",i+1)};
const olc::vf2d textNumbSize{GetTextSize(text)};
DrawRotatedStringDecal(numberDrawPos,text,0.f,textNumbSize/2,olc::GREY);
}
Tidied up the code with some separation, comments, and moved the dots slightly further back while adding olc::GREY
as the coloring for the clock numbers.
Getting Current Time
The next step is getting the system time. C++ provides us with a system clock in std::chrono::system_clock::now()
. Unfortunately there are very few ways to extract number of seconds from this form. The way we will do it is by converting it to a classical time_t
structure (a value holding the number of seconds since the epoch) to where we can breaks things down into a tm
structure, giving us exactly the number of hours, minutes, and seconds this time contains.
using namespace std::chrono;
std::time_t time{system_clock::to_time_t(system_clock::now())};
std::tm*localTime{std::localtime(&time)};
The code is a little cryptic (working with the chrono
library tends to be a challenge). With the local time now converted to a std::tm*
structure, we have direct access to the data we need now through the tm_hour
, tm_min
, and tm_sec
members. Let's display them on the clock formatted appropriately.
const std::string timeText{std::format("{:02}:{:02}:{:02}",localTime->tm_hour,localTime->tm_min,localTime->tm_sec)};
const olc::vf2d timeTextSize{GetTextSize(timeText)};
DrawStringDecal(GetScreenSize()/2+olc::vi2d{0,15}-timeTextSize/2,timeText);
Again we do some centering of the text so it appears proper.
NOTE:
Using the
->
operator to access members for a pointer structure.

I noticed I am repeating some code again so I am going to condense the centered text rendering code into its own function.
template<typename T>
void DrawCenteredStringDecal(const olc::v2d_generic<T>&pos,const std::string&text,const olc::Pixel&col=olc::WHITE,const olc::v2d_generic<T>&scale={1,1}){
const olc::v2d_generic<T>textSize{GetTextSize(text)};
DrawRotatedStringDecal(pos,text,0.f,textSize/2,col,scale);
}
bool OnUserUpdate(float fElapsedTime) override
{
Clear(olc::VERY_DARK_GREEN);
using namespace std::chrono;
std::time_t time{system_clock::to_time_t(system_clock::now())};
std::tm*localTime{std::localtime(&time)};
DrawCenteredStringDecal(GetScreenSize()/2+olc::vi2d{0,15},std::format("{:02}:{:02}:{:02}",localTime->tm_hour,localTime->tm_min,localTime->tm_sec));
DrawCircle(GetScreenSize()/2,100);
for(int i{0};i<12;i++){
using namespace std::numbers;
//Draw decorative dots
olc::vf2d drawVec{60,float(i*(pi/6)-pi/3)};
Draw(GetScreenSize()/2+drawVec.cart());
//Draw clock numbers
olc::vf2d numberVec{drawVec};
numberVec.x=90; //Adjusted further towards the edge of the clock.
DrawCenteredStringDecal(GetScreenSize()/2+numberVec.cart(),std::format("{}",i+1),olc::GREY);
}
return true;
}
Creating a template function for DrawCenteredStringDecal
allows it to be compatible with olc::vi2d
and olc::vf2d
types together, which just reduces error clutter. Very unnecessary in all honesty, just keeps the error window clean.
We can see the lines of code being condensed with both center text functions reduced to one line. Cleaner code for the upcoming last section of this project.
Clock Hands
It's finally time to render the hands of the clock! Let's start with the second hand. Just like in the previous step, we grabbed the seconds via localTime->tm_sec
. This determines which direction we will point it in. It can be in one of sixty positions, and a full revolution in radians is 2π
. So (2*π)/60
gives us how much to rotate per second.
Let's draw a red line pointing to the correct location on the clock now.
bool OnUserUpdate(float fElapsedTime) override
{
Clear(olc::VERY_DARK_GREEN);
using namespace std::chrono;
using namespace std::numbers;
std::time_t time{system_clock::to_time_t(system_clock::now())};
std::tm*localTime{std::localtime(&time)};
DrawCenteredStringDecal(GetScreenSize()/2+olc::vi2d{0,15},std::format("{:02}:{:02}:{:02}",localTime->tm_hour,localTime->tm_min,localTime->tm_sec));
DrawCircle(GetScreenSize()/2,100);
FillCircle(GetScreenSize()/2,6,olc::BLACK);
const float secondsHandAngle{float(localTime->tm_sec*((2*pi)/60))};
olc::vf2d secondsHandVec{85,secondsHandAngle};
DrawLine(GetScreenSize()/2,GetScreenSize()/2+secondsHandVec.cart(),olc::RED);
FillCircle(GetScreenSize()/2,4,olc::DARK_GREY);
for(int i{0};i<12;i++){
//Draw decorative dots
olc::vf2d drawVec{60,float(i*(pi/6)-pi/3)};
Draw(GetScreenSize()/2+drawVec.cart());
//Draw clock numbers
olc::vf2d numberVec{drawVec};
numberVec.x=90; //Adjusted further towards the edge of the clock.
DrawCenteredStringDecal(GetScreenSize()/2+numberVec.cart(),std::format("{}",i+1),olc::GREY);
}
return true;
}
A little bit of code rearranging and adding a few circles in the center to make it look more like an analog clock. So far so good!

You might notice an error with the image above. The seconds value is at 10 seconds but the hand is pointing at the "5" position on the clock, indicating 25 seconds. So it is off by 3 numbers, or 90 degrees. Once again, 0 radians starts to the right, so we have to subtract by π/2
to make our hands point 90 degrees counter-clockwise.
const float secondsHandAngle{float(localTime->tm_sec*2*pi/60-pi/2)};
I removed the parenthesis as the C++ operator precedence indicates that the *
,/
, and %
operators all have the same precedence level, and have an associativity of "left-to-right". So we can read all these chained operators in a row from left-to-right and interpret them the same way we intended. Just be careful with this.
Hour and Minute Hands
For the hour and minute hands, we will do similar. We divide the segments into 60 again for the minutes, and 24 for the hours. Since the clock is a 12-hour clock we have to modulus the 24 as well. localTime->tm_hour%12
will mean when it is 12 the result will be 0
, and for 13 the result will be 1
, etc. Which is the desirable behavior.
Tip:
Use the
%
operator when you have a looping value. This operator will always return the remainder of the division of the operation, which is useful for looping timers, array indices, etc.
const float hourHandAngle{float(localTime->tm_hour%12*2*pi/12-pi/2)};
olc::vf2d hourHandVec{60,hourHandAngle};
DrawLine(GetScreenSize()/2,GetScreenSize()/2+hourHandVec.cart(),olc::BLACK);
const float minutesHandAngle{float(localTime->tm_min*2*pi/60-pi/2)};
olc::vf2d minutesHandVec{85,minutesHandAngle};
DrawLine(GetScreenSize()/2,GetScreenSize()/2+minutesHandVec.cart(),olc::BLACK);
const float secondsHandAngle{float(localTime->tm_sec*2*pi/60-pi/2)};
olc::vf2d secondsHandVec{85,secondsHandAngle};
DrawLine(GetScreenSize()/2,GetScreenSize()/2+secondsHandVec.cart(),olc::RED);
Similar concept to the seconds hand, drawing a black line and a shorter black line for the minute and hour hand respectively.
I am going to remove the digital display of the clock now since one is supposed to assume the time based on the positions of the hands. The final result!
Analog Clock (Advanced)
While this analog clock we made is functional, there are some subtleties to an actual analog clock that we are overlooking.
As the seconds hand goes around, the minutes and hours hand actually slowly rotate as well such that they are pointing in the middle or towards the next number as the seconds hand gets closer and closer to the 12 o' clock position again.
In order to fix this, we will instead adjust the angle based on the total number of seconds that have passed. We can figure out the total number of seconds passed in the day using the formula hours*3600+minutes*60+seconds
. Then using this information, use fmod()
to calculate and loop the hands around the clock as necessary.
Start with the minute hand: Every 3600 seconds (one hour) it makes one revolution. Since one revolution is 2π
, it makes the formula be 2*pi*fmod(totalTime,3600)/3600
. This results in a full rotation of the minute hand around the clock every hour.
const float totalTime{float(localTime->tm_hour*3600+localTime->tm_min*60+localTime->tm_sec)};
const float minutesHandAngle{float(2*pi*fmod(totalTime,3600)/3600)};
olc::vf2d minutesHandVec{85,float(minutesHandAngle-pi/2)};
DrawLine(GetScreenSize()/2,GetScreenSize()/2+minutesHandVec.cart(),olc::BLACK);
Revising the minutesHandAngle
formula now gives us a minute hand that slowly moves across over time, better indicating the current time and matching the behavior of a real analog clock!

Let's make the seconds hand also use the same totalTime
value for the sake of consistency. And make the hour hand work proper using the same logic.
const float totalTime{float(localTime->tm_hour*3600+localTime->tm_min*60+localTime->tm_sec)};
const float hourHandAngle{float(2*pi*fmod(totalTime,43200)/43200)};
olc::vf2d hourHandVec{60,float(hourHandAngle-pi/2)};
DrawLine(GetScreenSize()/2,GetScreenSize()/2+hourHandVec.cart(),olc::BLACK);
const float minutesHandAngle{float(2*pi*fmod(totalTime,3600)/3600)};
olc::vf2d minutesHandVec{85,float(minutesHandAngle-pi/2)};
DrawLine(GetScreenSize()/2,GetScreenSize()/2+minutesHandVec.cart(),olc::BLACK);
const float secondsHandAngle{float(2*pi*fmod(totalTime,60)/60)};
olc::vf2d secondsHandVec{85,float(secondsHandAngle-pi/2)};
DrawLine(GetScreenSize()/2,GetScreenSize()/2+secondsHandVec.cart(),olc::RED);
Using 43200
seconds to represent a full revolution of the hour hand (half a day) and 60
seconds for a full revolution of the seconds hand, we end up drawing the hands as if they move like an actual analog clock over time.
Extra Guide Tick Marks
It would be nice to be able to tell a bit more easily where the minute hand is. So I am going to add some guide lines in between the white dots. Going back to the for loop involving the decorative dots, let's populate it with 60 dots now. And every 5th dot will be larger and a different color.
void DrawClockHands(){
using namespace std::chrono;
std::time_t time{system_clock::to_time_t(system_clock::now())};
std::tm*localTime{std::localtime(&time)};
DrawCircle(GetScreenSize()/2,100);
FillCircle(GetScreenSize()/2,6,olc::BLACK);
const float totalTime{float(localTime->tm_hour*3600+localTime->tm_min*60+localTime->tm_sec)};
const auto DrawHand=[&](const float handLength,const float secondsPerRevolution,const olc::Pixel col){
using namespace std::numbers;
const float angle{float(2*pi*fmod(totalTime,secondsPerRevolution)/secondsPerRevolution)};
olc::vf2d handVec{handLength,float(angle-pi/2)};
DrawLine(GetScreenSize()/2,GetScreenSize()/2+handVec.cart(),col);
};
DrawHand(60,43200,olc::BLACK);
DrawHand(85,3600,olc::BLACK);
DrawHand(85,60,olc::RED);
FillCircle(GetScreenSize()/2,4,olc::DARK_GREY);
}
bool OnUserUpdate(float fElapsedTime) override
{
Clear(olc::VERY_DARK_GREEN);
using namespace std::numbers;
for(int i{0};i<60;i++){
//Draw decorative dots
olc::vf2d drawVec{60,float(i*(pi/30)-pi/3)};
olc::Pixel lineCol{olc::DARK_GREY};
if(i%5==0){
lineCol=olc::WHITE;
drawVec.x-=2;
olc::vf2d lineVec2{drawVec.x+4,drawVec.y};
DrawLine(GetScreenSize()/2+drawVec.cart(),GetScreenSize()/2+lineVec2.cart(),lineCol);
}else Draw(GetScreenSize()/2+drawVec.cart(),lineCol);
}
DrawClockHands();
for(int i{0};i<12;i++){
//Draw clock numbers
olc::vf2d numberVec{90,float(i*(pi/6)-pi/3)};
DrawCenteredStringDecal(GetScreenSize()/2+numberVec.cart(),std::format("{}",i+1),olc::GREY);
}
return true;
}
I have now separated the clock hand drawing into its own function to slim down the update function. I separated the decorative dots loop from the clock numbers loop since the dots now requires 60 iterations while the clock numbers only require the 12. It's clearer to separate the loops since they have different functions.
I wanted to make it so it drew a line that was slightly longer in both directions when the tick mark aligns with a number.
You will also notice that I was able to reduce the drawing of each hand down by its common factors to produce a small lambda function called DrawHand()
where you specify the radius of the dial, how many seconds pass for each revolution and a hand color.
It all looks like this:
"Ticking" Jump Back Motion
When the second hand jumps forward, it has a slight jump back as it locks into place. It's very subtle but also a feature in analog clocks that I like. Let's try to recreate that subtle detail.
Start by adding a new variable to track the amount of time it takes before the second hand "jumps back". We'll make it 0.02 seconds for now (very subtle). We also need a variable tracking what the amount of seconds that have passed was the previous frame. If it ever differs, we set the secondHandBounceTimer
to 0.02
and it starts decreasing by GetElapsedTime()
(the amount of time in seconds that have passed for this rendering frame).
One way we would be able to trigger the "tick back" motion is by modifying the DrawHand()
lambda inside DrawClockHands()
to have a float parameter passed in with an "additional rotation" amount.
First update the DrawHand()
lambda with the new parameter and angle offset:
const auto DrawHand=[&](const float handLength,const float secondsPerRevolution,const olc::Pixel col,const float angleOffset=0.f){
using namespace std::numbers;
const float angle{float(2*pi*fmod(totalTime,secondsPerRevolution)/secondsPerRevolution+angleOffset)};
olc::vf2d handVec{handLength,float(angle-pi/2)};
DrawLine(GetScreenSize()/2,GetScreenSize()/2+handVec.cart(),col);
};
Add two new variables into the game class:
public:
bool OnUserCreate() override
{
return true;
}
float secondHandBounceTimer{0.f};
size_t lastSecond{};
Update the last second value and the bounce timer if it changes.
if(lastSecond!=localTime->tm_sec){
secondHandBounceTimer=0.02f;
lastSecond=localTime->tm_sec;
}
In OnUserUpdate()
we have to tick down the bounce timer. We use std::max
as it's equivalent to if(secondHandBounceTimer>0)secondHandBounceTimer-=fElapsedTime; secondHandBounceTimer=std::max(secondHandBounceTimer,0.f);
secondHandBounceTimer=std::max(secondHandBounceTimer-fElapsedTime,0.f);
Finally, apply the bounce timer to the DrawHand()
call for the second hand.
DrawHand(85,60,olc::RED,secondHandBounceTimer);
Screenshots don't do it justice, but look at the results for yourself and it is a pretty convincing behavior!
Rendering on a Sprite
One of the final things we could do with this is use the clock on some kind of texture. Imagine you have a wall clock image and wanted to display a dynamic clock inside the wall clock itself.