В прошлом уроке мы попытались сделать популярную игру Крестики-Нолики. Надеюсь, у большинства из вас попытки запуска игры были успешными, возможно у кого-то даже получилось модифицировать и улучшить написанный код (а кому-то это всё покажется детским лепетом). Даже если это не так, старайтесь, пытайтесь, и в конечном итоге у вас всё получится.
В этом уроке мы попытаемся проапгрейдить наши навыки, добавив в копилку реализацию анимации с помощью SDL. Как и прежде, мы будем основываться на предыдущих уроках (но не на TicTacToe, это был отдельный крутой проект). Думаю, пора начинать.
В первую очередь нам нужно создать новый класс для обработки анимации, а в следующем уроке мы создадим класс для работы с Entity (игровыми сущностями). Пожалуйста, имейте в виду, что эти две вещи являются совершенно разными по сути, и хотя я знаю, что они могут быть с легкостью реализованы в одном классе, я не хочу использовать такой подход. Всю критику лучше направить автору серии, хотя мне тоже будут полезны любые отзывы.
Создаем 2 новых файла CAnimation.h и CAnimation.cpp соответствено. В конечном итоге, когда мы будем создавать CEntity класс, он будет наследоваться от CAnimation, но сейчас мы будем тестировать только один объект, который мы создадим чуть позже. И прежде чем начать давайте добавим директиву включения #include «CAnimation.h» в CApp.h (желательно до #include «CEvent.h»).
Let’s make some code!
Открываем CAnimation.h, и добавляем следующие строки:
#ifndef _CANIMATION_H_ #define _CANIMATION_H_ #include <SDL.h> class CAnimation { private: int CurrentFrame; int FrameInc; private: int FrameRate; //Milliseconds long OldTime; public: int MaxFrames; bool Oscillate; public: CAnimation(); void OnAnimate(); public: void SetFrameRate(int Rate); void SetCurrentFrame(int Frame); int GetCurrentFrame(); }; #endif
А в CAnimation.cpp вписываем это:
#include "CAnimation.h" CAnimation::CAnimation() { CurrentFrame = 0; MaxFrames = 0; FrameInc = 1; FrameRate = 100; //Milliseconds OldTime = 0; Oscillate = false; } void CAnimation::OnAnimate() { if(OldTime + FrameRate > SDL_GetTicks()) { return; } OldTime = SDL_GetTicks(); CurrentFrame += FrameInc; if(Oscillate) { if(FrameInc > 0) { if(CurrentFrame >= MaxFrames) { FrameInc = -FrameInc; } }else{ if(CurrentFrame <= 0) { FrameInc = -FrameInc; } } }else{ if(CurrentFrame >= MaxFrames) { CurrentFrame = 0; } } } void CAnimation::SetFrameRate(int Rate) { FrameRate = Rate; } void CAnimation::SetCurrentFrame(int Frame) { if(Frame < 0 || Frame >= MaxFrames) return; CurrentFrame = Frame; } int CAnimation::GetCurrentFrame() { return CurrentFrame; }
Думаю стоит немного рассказать о том что же всё-таки делает этот код. В геймдеве существует один основной элемент анимации, который нам нужно обрабатывать — текущий кадр анимации. Посмотрите на изображение, которое мы будем использовать в этом уроке. Как видите, у нас есть 8 кадров дракончика на одном изображении. Обращаться к текущему кадру мы будем по его порядковому номеру 1,2,… и отрисовывать его.
Помните, во втором уроке, мы создали функцию, чтобы отрисовывать часть изображения? Осталось применить её вкупе с нашим кадром анимации, и вуаля!
Итак, за номер текущего кадра у нас будет отвечать переменная с говорящим названием CurrentFrame. Это текущий кадр анимации, который мы и будем рисовать на экране. Все, за что он отвечает, это хранение порядкового номера части поверхности которую мы будем рисовать на экране. Так что, когда мы будем вызывать нашу функцию рисования, это будет выглядеть примерно так:
CSurface::OnDraw(Surf_Display, Surf_Image, 0, 0, Ezhik.GetCurrentFrame() * 64, 0, 64, 64);
Когда CurrentFrame увеличивается на 1, мы всего лишь смещаемся на 64 пикселя вправо по изображению (размер кадра), и рисуем этот кадр.
Как вы уже догадались, MaxFrames сообщает нам о том, сколько всего кадров анимации у нас имеется. Ну и напоследок нам нужно знать, сколько кадров в секунду у нас отрисовывается, а точнее, как быстро эта анимация будет отображаться (да, да, тот самый пресловутый FPS). Определение частоты отрисовки запрограммировано в методе OnAnimate:
if(OldTime + FrameRate > SDL_GetTicks()) { return; }
Складывая старое значение времени прошедшего с момента работы SDL и желаемой частоты кадров, мы можем сравнить его с тем временем, которое определяет сколько времени уже работает SDL (текущее). Разжую: допустим, мы только запустили нашу программу. SDL_GetTicks возвращает 0, и OldTime естественно тоже равен 0. Примем как данность что желаемая частота кадров составляет 1 кадр в секунду. Т.о. FrameRate = 1000 (миллисекунды). Итак, 0 + 1000 больше 0? Да, т.е. нам надо пропустить этот такт и ждать следующего. Но на следующем такте 0 + 1000 меньше SDL_GetTicks, это должно означать, что 1 секунда прошла. Таким образом, мы увеличиваем значение кадра, а затем сбрасываем значение OldTime на текущее временя, и запускаем проверку сначала. Думаю теперь более ясно как это всё работает.
А вот теперь расскажу про Oscillate и FrameInc. Не то, чтобы я хотел вас запутать, добавив всё это, скорее наоборот, поэтому покажу, для чего это необходимо. В общем и целом, когда Oscillate выставлено в true, класс анимации будет увеличить кадры до достижения максимального количества кадров, а затем уменьшать. Если бы у нас было 10 кадров, к примеру, то мы наблюдали бы что-то вроде этого: 0 1 2 3 4 5 6 7 8 9 8 7 6 5 4 3 2 1 2… (CurrentFrame). Мы проходим весь путь от 0 до 9 а затем спускаемся в обратном порядке. Всё предельно просто! В следующем уроке я покажу интересный способ использования всего этого. Как же это работает? Давайте взглянем на OnAnimate чуть пристальнее.
Про OldTime и его предназначение мы в курсе, а что с остальными кусками кода? Приглядимся к проверке значения Oscillate. Внутри мы просто проверяем достигло ли CurrentFrame максимального числа кадров. Если да, сбрасываем CurrentFrame обратно в 0.
Думаю что задав Oscillate значение true у нас будет более плавное отображение анимации! А вы как считаете? Давайте рассмотрим предназначение FrameInc. Значение FrameInc задаётся равным либо 1 либо -1, в зависимости от того нужно ли нам идти по кадрам в ту или иную сторону (уменьшать или увеличивать). То есть если FrameInc больше 0, мы начинаем инкрементировать кадры, в противном случае применяем декремент. Таким образом мы попеременно достигаем либо 0 либо значения MaxFrames, что, как мне кажется, намного более красиво (по крайней мере нет жёсткого сброса CurrentFrame в 0).
Финишная черта
Теперь соберем всё воедино! Нужно создать оъект класса CAnimation в CApp.h:
CAnimation Anim_Yoshi;
Зададим значение MaxFrames, внутри CApp_OnInit:
Anim_Yoshi.MaxFrames = 8;
Если вам нужна анимация от 0 кадра до 7 и обратно, задаем:
Anim_Yoshi.Oscillate = true;
Заставим анимироваться Йожи внутри CApp_OnLoop:
Anim_Yoshi.OnAnimate();
И естественно, чтобы насладиться анимацией, нужно её посмотреть, добавив в CApp_OnRender:
CSurface::OnDraw(Surf_Display, Surf_Test, 290, 220, Anim_Yoshi.GetCurrentFrame() * 64, 0, 64, 64);
Ну вот и всё! Скомпилируйте наш обновлённый проект и запустите! Не забудьте скачать картинку из этой статьи, потому что в оригинальной кадры в столбик, что, ИМХО, некрасиво смотрится. И ещё пара моментов. Поскольку картинка .PNG, нужно использовать IMG_Load (в предыдущих статьях я писал про эту функцию) и подключить к проекту библиотеку ключом -lSDL_image. Также, попробуйте выключить у Йожи этот раздражающий розовый цвет (как это сделать тоже можно посмотреть в прошлых статьях). Успехов вам, в нелегком труде GameDeveloper’a! Надеюсь эти статьи помогают…
Ссылки на исходный код:
Ссылки на все уроки:
ссылка на оригинал статьи http://habrahabr.ru/post/169197/
Добавить комментарий