Предыдущая тема

Мультимедийная библиотека SFML лечит головную боль не только новичкам в игровой индустрии, но и преподавателям информатики в школе. Основная задача которых состоит в том, чтобы не только научить ребёнка программировать, но и привить любовь к разработке приложений. Используя простые графические объекты и их методы можно за короткое время создать прототип игры или симулятор физико-математических процессов. В этой статье мы рассмотрим разработку аналоговых часов на C++ используя библиотеку SFML.

Инструменты для разработки

  1. Visual Studio c установленным пакетом "Разработка классических приложений на С++".

  2. Мультимедийная библиотека SFML.

  3. Подключаемый файл заголовка SFMLWorldTime.

  4. Шрифты и программный код для самостоятельной разработки или репозиторий.

Подготовительная работа

Подключаем к Visual Studio библиотеку SFML и копируем шаблон кода библиотеки SFML в редактор кода.

#include <SFML/Graphics.hpp>
#include"SFMLWorldTime.h"

using namespace sf;

int main()
{
    // Создаём графическое окно размером 900х900 
    RenderWindow window(VideoMode(900, 900), L"Аналоговые часы", Style::Default);
    
    window.setVerticalSyncEnabled(true);  // Вертикальная синхронизация
    
    SFMLWorldTime etm(50, 50, 4, Color::Yellow); // Объект электронные часы
               
    while (window.isOpen())
    {
        Event event;
        while (window.pollEvent(event))
        {
            if (event.type == Event::Closed)
                window.close();
        }
        
        window.clear(Color::Blue); // Очищаем графическое окно и закрашиваем в синий цвет
        etm.drawTime(window);  // Рисуем электронные часы в графическом окне
        window.display();
    }
    return 0;
}

Добавляем в папку проекта файлы SFMLWorldTime.h и SFMLWorldTime.cpp и папку со шрифтами lib.

Добавляем в проект файлы SFMLWorldTime.h и SFMLWorldTime.cpp.

В код шаблона программы прописываем вызов заголовочного файла SFMLWorldTime.h.

#include"SFMLWorldTime.h"

Создаём объект электронные часы и рисуем их в графическом окне.

SFMLWorldTime etm(50, 50, 4, Color::Yellow);

etm.drawTime(window); 

Часовых дел мастер

#define _USE_MATH_DEFINES
#include <SFML/Graphics.hpp>
#include"SFMLWorldTime.h"
#include<math.h>


using namespace sf;
using namespace std;

int main()
{
    
    RenderWindow window(VideoMode(900, 900), L"Аналоговые часы", Style::Default);
    
    window.setVerticalSyncEnabled(true);

    SFMLWorldTime etm(50, 50, 4, Color::Yellow);

    // Корпус аналоговых часов
    CircleShape circleTime(300.f);
    circleTime.setOrigin(150, 150);
    circleTime.setPosition(303, 303);
    circleTime.setFillColor(Color::Blue);
    circleTime.setOutlineThickness(10);
    circleTime.setOutlineColor(Color::Yellow);

    // Риски аналоговых часов объявление переменных
    CircleShape PointMin;
    PointMin.setFillColor(Color::Yellow);
    float radiusNum = 280; // радиус расположения рисок
    float radiusPoint;
    float CenterClockX = 450;
    float CenterClockY = 450;
    float xPoint, yPoint;

    // Оцифровка циферблата аналоговых часов объявление переменных
    Font fontTime;
    if (!fontTime.loadFromFile("lib/dockerthree.ttf")) return 777;
    Text TimeText;
    TimeText.setFont(fontTime);
    TimeText.setCharacterSize(30);
    TimeText.setFillColor(Color::Yellow);
    float numx, numy;

    // Рисуем стрелки аналоговых часов
    RectangleShape secArrow(Vector2f(2, 280));                //секундная
    InitRect(secArrow, 453, 453, 1, 280, Color::Red);
    RectangleShape minArrow(Vector2f(8, 260));               //минутная
    InitRect(minArrow, 455, 455, 4, 260, Color::Yellow);
    RectangleShape hourArrow(Vector2f(12, 180));             //часовая
    InitRect(hourArrow, 455, 455, 6, 180, Color::Yellow);

               
    while (window.isOpen())
    {
        Event event;
        while (window.pollEvent(event))
        {
            if (event.type == Event::Closed)
                window.close();
        }
        
        window.clear(Color::Blue);
        etm.drawTime(window);     // электронные часы
        window.draw(circleTime);  // корпус аналоговых часов

        // Риски аналоговых часов 
        for (int a = 0; a < 60; a++) {
            if (a % 5 == 0)  radiusPoint = 8; else  radiusPoint = 4;
            xPoint = CenterClockX + radiusNum * cos(-6 * a * (M_PI / 180) + M_PI / 2);
            yPoint = CenterClockY - radiusNum * sin(-6 * a * (M_PI / 180) + M_PI / 2);
            PointMin.setRadius(radiusPoint);
            PointMin.setOrigin(radiusPoint / 2, radiusPoint / 2);
            PointMin.setPosition(xPoint, yPoint);
            window.draw(PointMin);
        }

        // Оцифровка циферблата аналоговых часов
        for (int i = 1; i <= 12; i++)
        {
            numx = CenterClockX + (radiusNum - 30) * cos(-30 * i * (M_PI / 180) + M_PI / 2);
            numy = CenterClockX - (radiusNum - 30) * sin(-30 * i * (M_PI / 180) + M_PI / 2);

            if (i <= 5) TimeText.setPosition(numx - 10, numy - 17);
            else  TimeText.setPosition(numx - 8, numy - 15);
            TimeText.setString(to_string(i));
            window.draw(TimeText);

        }
        secArrow.setRotation(6 * etm.getsec());                         // вращение секундной стрелки 
        minArrow.setRotation(6 * etm.getmin() + etm.getsec() * 0.1);    // вращение минутной стрелки 
        hourArrow.setRotation(30 * etm.gethour() + etm.getmin() * 0.5); // вращение часовой стрелки 

        window.draw(hourArrow); // часовая стрелка
        window.draw(minArrow);  // минутная стрелка
        window.draw(secArrow);  // секундная

        window.display();
    }
    return 0;
}

Рисуем окружность т.е. контур будущих часов

// Блок объявления и установки параметров объектов
    CircleShape circleTime(300.f);
    circleTime.setOrigin(150, 150);
    circleTime.setPosition(303, 303);
    circleTime.setFillColor(Color::Blue);
    circleTime.setOutlineThickness(10);
    circleTime.setOutlineColor(Color::Yellow);

// Блок отрисовки графических объектов
    window.draw(circleTime);

Подключаем макрос математических констант

#define _USE_MATH_DEFINES

Подключаем заголовочный файл математических функций

#include<math.h>

Объявляем объекты и переменные для рисования рисок часов.

Создаём объект круга, который и будет отображать окружность в виде рисок.

    // Блок объявления и установки параметров объектов
    CircleShape PointMin;                  // объект круга
    PointMin.setFillColor(Color::Yellow);  // цвет круга жёлтый

Объявим и инициализируем вещественную переменную значением расстояния от центра часов до расположения рисок.

float radiusNum = 280;

Создадим вещественную переменную размера рисок т.е. радиуса объекта круга PointMin

float radiusPoint;

Обозначим переменными координаты центра окружности

float CenterClockX = 450;
float CenterClockY = 450;

Объявим переменные координат рисок

float xPoint, yPoint;

Рисуем риски в графическом окне

for (int a = 0; a < 60; a++) 
{
            // каждой пятой риски увеличиваем радиус
            if (a % 5 == 0)  radiusPoint = 8; else  radiusPoint = 4;
            // вычисляем координаты риски
            xPoint = CenterClockX + radiusNum * cos(-6 * a * (M_PI / 180) + M_PI / 2);
            yPoint = CenterClockY - radiusNum * sin(-6 * a * (M_PI / 180) + M_PI / 2);
            // задаём координаты и параметры риски т.е. объекту круга  PointMin
            PointMin.setRadius(radiusPoint);
            PointMin.setOrigin(radiusPoint / 2, radiusPoint / 2);
            PointMin.setPosition(xPoint, yPoint);
            // рисуем риску в графическом окне
            window.draw(PointMin);
}

В формуле вычисления координат рисок, используем математическую константу числа Пи - M_PI.

Объявим объекты и переменные для отображения циферблата часов

Загрузим шрифт для отображения цифр, создаём объект шрифта fontTime и загружаем в него шрифт dockerthree.ttf

Font fontTime;
if (!fontTime.loadFromFile("lib/dockerthree.ttf")) return 777;

Создаём объект текста и задаём ему параметры: шрифта, размера и цвета.

    Text TimeText;
    TimeText.setFont(fontTime);
    TimeText.setCharacterSize(30);
    TimeText.setFillColor(Color::Yellow);

Объявляем переменные координат текстового объекта, для отображения цифр.

float numx, numy;

Рисуем циферблат в графическом окне

for (int i = 1; i <= 12; i++)
        {
            // вычисляем координаты для текстового объекта с цифрами
            numx = CenterClockX + (radiusNum - 30) * cos(-30 * i * (M_PI / 180) + M_PI / 2);
            numy = CenterClockX - (radiusNum - 30) * sin(-30 * i * (M_PI / 180) + M_PI / 2);
            // Корректируем координаты расположения цифр 
            if (i <= 5) TimeText.setPosition(numx - 10, numy - 17);
            else  TimeText.setPosition(numx - 8, numy - 15);
            // заносим значение цифры в текстовый объект
            TimeText.setString(to_string(i));
            // рисуем текстовый объект
            window.draw(TimeText);

        }

Объявляем объекты для стрелок часов и настраиваем их.

    RectangleShape secArrow(Vector2f(2, 280));               // Секундная стрелка
    InitRect(secArrow, 453, 453, 1, 280, Color::Red);
    RectangleShape minArrow(Vector2f(8, 260));               // Минутная стрелка
    InitRect(minArrow, 455, 455, 4, 260, Color::Yellow);
    RectangleShape hourArrow(Vector2f(12, 180));             // Часовая стрелка
    InitRect(hourArrow, 455, 455, 6, 180, Color::Yellow);

Графическое отображение стрелок базируются на объекте формы прямоугольника RectangleShape. Настраиваем объекты с помощью функции InitRect (), которая находится в заголовочном файле SFMLWorldTime.h.

Рисуем стрелки часов

// Изменение угла положения прямоугольников т.е. стрелок
secArrow.setRotation(6 * etm.getsec());                         // Вращение секундной стрелки 
minArrow.setRotation(6 * etm.getmin() + etm.getsec() * 0.1);    // Вращение минутной стрелки 
hourArrow.setRotation(30 * etm.gethour() + etm.getmin() * 0.5); // Вращение часовой стрелки 

// Рисование стрелок в графическом окне
window.draw(hourArrow); // часовая стрелка
window.draw(minArrow);  // минутная стрелка
window.draw(secArrow);  // секундная

Метод setRotation вращает объект вокруг заданной оси, согласно установленному углу поворота в параметрах метода.

Рассчитаем угол поворота стрелок.

Секундная стрелка повернется на угол равный 360 °/60 сек = 6 ° (вся окружность 360 ° по ней распределяем 60 секунд) и умножаем на текущее значение времени секунд.

Минутная стрелка за это время повернется на 360 °/60 мин = 6 ° умноженных на текущее значение времени минут и добавляем 6 °/60сек=0.1 ° умноженный на количество пройденного времени секунд.

Часовая стрелка за это время повернётся на 360°/12 часов = 30 ° умноженных на текущее время часов и добавляем 30 °/60мин=0,5° умноженным на пройденное время минут.

Более подробную инструкцию вы можете получить посмотрев видео "Аналоговые часы SFML C++".

Клонировать репозиторий

Предыдущая тема

Комментарии (4)


  1. wellhause
    22.12.2022 21:12

    Хорошая статья, +1


  1. Alex_v99
    23.12.2022 14:26

    А можно дурацкий вопрос?..

    С интересом читаю статьи из этого цикла, но до сих пор не понял, чем SFML лучше чем тот же GDI+. Или его преимущества появятся в следующих статьях - в более сложных случаях?


    1. JordanCpp
      23.12.2022 18:17

      SFML это кроссплатформенный продукт, GDI+ нет. Это серьезный недостаток. Когда автор осилит CMake, пользователи с других ос, смогут нормально собирать примеры из статей.


  1. JordanCpp
    23.12.2022 22:19

    Извиняюсь за дезинформацию, ссылка на репозиторий, в конце статьи. При первом чтении пропустил её. Cmake на месте.