Midpoint displacement

Основная суть алгоритма (который впоследствии плавно перерастет в Diamond square через двумерный Midpoint displacement):

// vector — массив точек, изначально заполнены только первая и последняя
// left, right — индексы первого и последнего элемента
// len = длина изначального отрезка(в пикселах)
// r -  шероховатость. Чем меньше тем более плавная кривая будет в результате.
function midpoint(vector, left, right, len, r) {
    if (right - left < 2)
        return;
    var hl = vector[left]; //высота левой точки
    var hr = vector[right]; //высота правой
    var h = (hl + hr) / 2 + rand(-r * len, +r * len); //считаем высоту
    var index = Math.floor(left + (right - left) / 2); //ищем середину
    vector[index] = h;
    //выполняем алгоритм для получившихся половин
    midpoint(vector, left, index, len / 2, r);
    midpoint(vector, index, right, len / 2, r);
}

ДемоИсходники

Ограничение скорости кадров

Что может работать на вашем компьютере на скорости 60 кадров в секунду, на другом может работать на 200. Так как скорость меняется от компьютера к компьютеру, вам необходимо регулировать фреймрейт (скорость кадров), для того, чтобы игра не работала слишком быстро. Если игра работает слишком быстро, в нее становится невозможно играть. Для предотвращения этого, вам нужно ограничить скорость кадров. Этот урок научит вас этому.
//Кадров в секунду
const int FRAMES_PER_SECOND = 20;

Мы определяем количество кадров в секунду как глобальную константу.

    //Количество отрисованных кадров
    int frame = 0;

    //Ограничивать ли фреймрейт (количество кадров в секунду)
    bool cap = true;

    //Таймер для регулирования фреймрейта
    Timer fps;

В функции main() мы объявляем несколько переменных. Переменная frame хранит количество отрисованных кадров, что важно для определения положения, в котором нужно отрисовать сообщение в этой программе. Так же тут есть переменная cap, которая определяет, хочет ли пользователь ограничивать фреймрейт. Затем мы объявляем объект таймера, который мы используем для ограничения фреймрейта.

    //Генерируем поверхность с сообщением
    message = TTF_RenderText_Solid( font, "Testing Frame Rate", textColor );

Теперь мы рендерим поверхность с сообщением, которая будет двигаться по экрану.

    //Пока пользователь не вышел
    while( quit == false )
    {
        //Запускаем таймер
        fps.start();

Далее мы входим в главный цикл. В начале каждого кадра мы запускаем таймер кадра.

        //Пока есть события для обработки
        while( SDL_PollEvent( &event ) )
        {
            //Если нажали клавишу
            if( event.type == SDL_KEYDOWN )
            {
                //Если нажали ввод
                if( event.key.keysym.sym == SDLK_RETURN )
                {
                    //Switch cap
                    cap = ( !cap );
                }
            }

            //Если пользователь вышел
            else if( event.type == SDL_QUIT )
            {
                //Выйти
                quit = true;
            }
        }

Здесь мы обрабатываем события. По существу, мы включаем/выключаем ограничение фреймрейта, когда пользователь нажимает enter, и выходим из программы при наступлении соответствующего события. Обычно, вам не требуется переменная для включение/выключения ограничения фреймрейта в настоящей игре, но здесь, в нашей программе, она есть, для того чтобы вы могли видеть разницу между регулируемым и нерегулируемым фреймрейтом.

        //Отрисовать фон
        apply_surface( 0, 0, background, screen );

        //Отрисовать сообщение
        apply_surface(
           ( SCREEN_WIDTH - message->w ) / 2,
           ( ( SCREEN_HEIGHT + message->h * 2 ) / FRAMES_PER_SECOND ) *
           ( frame % FRAMES_PER_SECOND ) - message->h,
           message,
           screen
        );

Затем мы отрисовываем фон и сообщение. Не беспокойтесь обо всем этом коде, рисующем сообщение. Это просто более короткий спобоб записи для:

if( frame % FRAMES_PER_SECOND == 0 )
{
//Отрисовать тут
}
if( frame % FRAMES_PER_SECOND == 1 )
{
//Отрисовать тут
}

И т.д.

        //Обновить экран
        if( SDL_Flip( screen ) == -1 )
        {
            return 1;
        }

        //Увеличить количество отрисованных кадров
        frame++;

Далее мы обновляем экран и увеличиваем счетчик кадров. Теперь мы закончили все, что нужно было сделать в этом кадре.

        //Если мы хотим ограничить фреймрейт
        if( ( cap == true ) && ( fps.get_ticks() < 1000 / FRAMES_PER_SECOND ) )
        {
            //Ждем оставшееся время
            SDL_Delay( ( 1000 / FRAMES_PER_SECOND ) - fps.get_ticks() );
        }

В этом месте мы на фактически ограничиваем фреймрейт. Когда мы начали обрабатывать кадр, мы запустили таймер, для того чтобы знать, сколько времени ушло на вывод кадра. Для того, чтобы программа не работала слишком быстро, каждый кадр должен отображаться в течение определенного времени. Так как мы показываем 20 кадров в секунду, каждый кадр должен отображаться не меньше чем 1/20-ую секунды. При фреймрейте 60, каждый кадр должен отображаться не менее 1/60-ой секунды. А так как демо запускается при 20 кадрах в секунду, мы должны тратить 50 миллисекунд (1000 мс / 20 кадров) на кадр. Для контроля над фреймрейтом, для начала мы проверяем, что кадр отображается меньше времени, отпущенного на один кадр. Если оно больше, это означает что нам или уже пора отрисовывать следующий кадр, или мы опаздываем с отрисовкой, таким образом у нас нет времени ждать. Если же оно меньше, мы используем SDL_Delay() засыпая до конца времени, отведенного под текущий кадр. Например, если на отрисовку кадра ушло 20 мс, мы прождем 30 мс. Если ушло 40 мс, прождем 10 мс и т.д.

Скачать исходники и материалы

Caint: рисовалка на канвасе

Простенкая рисовалка (Canvas pAINT) на канвасе: (в новом окне)Чуть позже я собираюсь написать статью о том, как я её писал.

var iframe = document.getElementById("caint"); iframe.onload = function() { var page = document.getElementById("page"); var close = document.createElement("a"); close.innerHTML = "x"; close.href = "javascript://"; close.style.position = "absolute"; close.style.right = "0.5em"; close.style.top = "0.5em"; close.style.color = "white"; var hide = function() { page.style.display = "block"; iframe.style.display = "none"; } close.onclick = hide; window.onpopstate = hide; iframe.contentDocument.body.appendChild(close); } function caintShow() { iframe.src="/public/misc/caint/"; var wp = document.getElementById("wpadminbar"); if(wp) wp.style.display = "none"; document.location.hash = "caint"; page.style.display = "none"; iframe.style.display = "block"; iframe.style.position = "fixed"; iframe.style.top = 0; iframe.style.left = 0; iframe.style.bottom = 0; iframe.style.right = 0; document.body.appendChild(iframe); return false; } document.getElementById("caintShow").onclick = caintShow;

Продвинутый таймер

Вы уже научились тому, как написать простой таймер. В этот раз займемся созданием класса таймера с функциями запуска, остановки и паузы, который вам пригодится при программировании игр.
//Таймер
class Timer
{
    private:
    //Сколько было времени при запуске таймера
    int startTicks;

    //Сколько было времени, когда таймер поставили на паузу
    int pausedTicks;

    //Статус таймера
    bool paused;
    bool started;

    public:
    //Инициализируем переменные
    Timer();

    //Действия с часами
    void start();
    void stop();
    void pause();
    void unpause();

    //Получить время таймера
    int get_ticks();

    //Проверки статуса таймера
    bool is_started();
    bool is_paused();
};

Здесь мы можем наблюдать краткое описание нашего класса. startTicks — точка отсчета времени, а pausedTicks — время, когда таймер поставили на паузу. Конструктор инициализирует переменные, и я вполне уверен, что вы прекрасно представляете что делают start(), stop(), pause() и unpause(). Функция get_ticks() возвращает время таймера в миллисекундах. is_started() проверяет запущен ли таймер, а is_paused() поставлен ли он на паузу.

Timer::Timer()
{
    //Инициализировать переменные
    startTicks = 0;
    pausedTicks = 0;
    paused = false;
    started = false;
}

Здесь в нашем конструкторе, мы инициализируем переменные. Тут особо нечего объяснять.

void Timer::start()
{
    //Запустить таймер
    started = true;

    //Снять с паузы
    paused = false;

    //Запомнить текущее время
    startTicks = SDL_GetTicks();
}

Теперь когда мы запускаем таймер, мы выставляем статус started в true, снимаем его с паузы и делаем время запуска равным текущему времени при помощи SDL_GetTicks().

void Timer::stop()
{
    //Остановить таймер
    started = false;

    //Снять таймер с паузы
    paused = false;
}

Когда мы останавливаем его, мы выставляем статус started в false и, опять-таки, снимаем с паузы.

int Timer::get_ticks()
{
    //Если таймер запущен
    if( started == true )
    {
        //Если таймер на паузе
        if( paused == true )
        {
            //Возвращаем время работы таймера до паузы
            return pausedTicks;
        }
        else
        {
            //Возвращаем текущее время минус время запуска
            return SDL_GetTicks() - startTicks;
        }
    }

    //Таймер не запущен
    return 0;
}

Итак, функция возвращающая значение таймера. Для начала проверяем, запущен ли таймер. Если да, проверяем не стоит ли он на паузе. Если он на паузе, возвращаем время работы до паузы. Мы поговорим о паузах позже. Если таймер не на паузе, вы возвращаем разницу во времени между запуском и текущим моментом времени. Как вы можете заметить, формула такая же, как и в предыдущем уроке. Если таймер не запускался возвращаем ноль.

void Timer::pause()
{
    //Если таймер запущен и еще на паузе
    if( ( started == true ) && ( paused == false ) )
    {
        //Поставить на паузу
        paused = true;

        //Вычислить время работы таймера до паузы
        pausedTicks = SDL_GetTicks() - startTicks;
    }
}

Теперь, когда мы хотим поставить таймер на паузу, для начала мы проверяем запущен ли таймер и не на паузе ли он уже. Если мы можем поставить таймер на паузу, мы меняем статус paused на true и сохраняем время работы таймера в pausedTicks.

void Timer::unpause()
{
    //Если таймер на паузе
    if( paused == true )
    {
        //Снять с паузы
        paused = false;

        //Скинуть время запуска
        startTicks = SDL_GetTicks() - pausedTicks;

        //Обнулить время работы до паузы
        pausedTicks = 0;
    }
}

Когда мы хотим снять таймер с паузы, мы проверяем был ли таймер на паузе до этого. Если был, меняем статус paused на false, и затем изменяем время запуска на текущее минус время работы до паузы. В конце концов обнуляем pausedTicks, впрочем без особой на то причины, по сути лишь потому что мне не нравятся бессвязные переменные.

bool Timer::is_started()
{
    return started;
}

bool Timer::is_paused()
{
    return paused;
}

Функции проверяющие статус таймера. Я вполне уверен что они очевидны.

    //Создаем таймер
    Timer myTimer;

    //Генерируем сообщения для поверхностей
    startStop = TTF_RenderText_Solid(
                    font,
                    "Press S to start or stop the timer",
                    textColor);
    pauseMessage = TTF_RenderText_Solid(
                       font,
                       "Press P to pause or unpause the timer",
                       textColor);

Теперь, в функции main после инициализации и загрузки файлов, мы объявляем объект таймера и рендерим поверхности с сообщениями.

    //Запустить таймер
    myTimer.start();

    //Пока пользователь не захотел выйти
    while( quit == false )
    {

Преждем чем начать главный цикл, запускаем таймер.

        //Пока есть события для обработки
        while( SDL_PollEvent( &event ) )
        {
            //Если нажали клавишу
            if( event.type == SDL_KEYDOWN )
            {
                //Если нажали s
                if( event.key.keysym.sym == SDLK_s )
                {
                    //Если таймер запущен
                    if( myTimer.is_started() == true )
                    {
                        //Остановить таймер
                        myTimer.stop();
                    }
                    else
                    {
                        //Запустить таймер
                        myTimer.start();
                    }
                }

В этом месте мы обрабатываем нажатия клавиш. При нажатии "s", если таймер запущен останавливаем его, а в противном случае запускаем.

                //Если нажали p
                if( event.key.keysym.sym == SDLK_p )
                {
                    //Если таймер на паузе
                    if( myTimer.is_paused() == true )
                    {
                        //Снять с паузы
                        myTimer.unpause();
                    }
                    else
                    {
                        //Поставить на паузу
                        myTimer.pause();
                    }
                }
            }

При нажатии на "p", если таймер на паузе снимаем его с нее, а иначе ставим.

        //Строка для времени таймера
        std::stringstream time;

        //Преобразовываем время таймера в строку
        time << "Timer: " << myTimer.get_ticks() / 1000.f;

        //Рендерим поверхность
        seconds = TTF_RenderText_Solid( font, time.str().c_str(), textColor );

        //Отображаем ее
        apply_surface( ( SCREEN_WIDTH - seconds->w ) / 2, 0, seconds, screen );

        //Освобождаем память
        SDL_FreeSurface( seconds );

        //Обновляем экран
        if( SDL_Flip( screen ) == -1 )
        {
            return 1;
        }
    }

После обработки событий и отрисовки фона и сообщений, мы показываем время таймера на экране Сначала мы создаем строковой поток, затем кладем в него время таймера. Так как мы хотим время в секундах, мы делим значение времени таймера на 1000, постольку поскольку оно хранится в миллисекундах. Дальше мы создаем поверхность из строки со временем. И потом отображаем новую поверхность на экране, после чего освобождаем память из-под нее. Далее обновляем экран и продолжаем главный цикл.

Скачать исходники и материалы