Мир программирования

 


Найти: на:


Меню
Партнеры
Счетчики
Реклама

Часть 9. Простой анимационный класс, обновление экрана, обработка ввода с клавиатуры


Простой анимационный класс

Я хочу создать анимацию с торами, отображая очередной кадр для создания впечатления вращения. Для того, чтобы сделать это как можно легче, я использую объектную ориентацию, добавляя простой анимационный класс. Этот класс хранит позицию спрайта, число кадров в его анимационной последовательности и скорость самой анимации. Это простой класс, но он вносит изменения в код, такие как добавление или удаление спрайтов. Листинг 22 представляет простой анимационный класс.

Листинг 22 Простой анимационный класс,

integer

class

integer
integer
integer
integer

type
TSimpleAnim
private
FFrameInterval
FHumberOf Frames
FLas tT ick Count
FCurrentFrame
protected

function GetCurrentFrame ( TickCount : integer
public
X ' integer ;
X : integer ;

integer
integer
integer
integer ] :

constructor Create ( AFrameInterval
ANumberOf Frames
Ax, Ay

integer

property CurrentFrameC TickCount

read GetCurrentFrame ;
end ;

integer
integer
integer

constructor TSimpleAnim. Create ( AFrameInterval :
ANuiriberOfFrames :
Ax, Ay :
begin

FFrameinterval := AFrameInterval ;
FNumberOf Frames := ANumberOf Frames ;
X := Ax ;
Y := Ay ;
end ;

integer

function TSimpleAnim. GetCurrentFrame ( TickCount
begin

if TickCount - FLastTickCount >= FFrameinterval then begin
FLastTickCount := TickCount ;
inc ( FCurrentFrame ) ;

if FCurrentFrame > FNumberOf Frames then FCurrentFrame :=
end ;

Result := FCurrentFrame ;
end ;

integer

Как видите, конструктор Create принимает параметры, определяющие спрайт, и хранит их в полях объекта. Свойство CurrentFrame сообщает нам, какой кадр спрайта должен быть отображен на экране с учетом текущего значения TickCount.
TickCount фактически представляет собой число миллисекунд, которые истекли с момента запуска Windows. Вы можете получить это число простым вызовом функции GetTickCount. Используемый таймер имеет высокое разрешение, вплоть до
одной миллисекунды. Это намного эффективней, чем использование компонента TTimer, который имеет разрешение 55 миллисекунд; он не может использоваться чаще, чем 18 раз и секунду, и это слишком медленно для применения в играх.
Также TTimer не очень точен и дает неодинаковые результаты.

CurrentFrame применяется функцией GetCurrentFrame. CurrentFrame использует грубый, но простой метод последовательного перехода на следующий кадр, если время в миллисекундах с момента появления последнего кадра больше, чем
промежуток времени, требуемый для демонстрации этого кадра. Когда это происходит, CurrentFrame записывает время в FLastTickCount и возвращается к исходному кадру, если согласно последовательности завершен последний кадр.

Вы добавляете этот код непосредственно в модуль главной формы. Это не компонент, поэтому нет необходимости добавлять его в палитру компонент. Экземпляры создаются в коде только в режиме выполнения. Это делается путем добавления TList, названного Animations, в определение формы. Данный список создается в FormShow, и вы добавляете в нее три анимационных объекта:

Animations := TList .Create ;

Animations.Add( TSimpleAnim.Create( 48, 60, 260, 120 ) ) ;
Animations.Add ( TSimpleAnim. Create( 12, 60, 420, 248 ) ) ;
Animations.Add( TSimpleAnim. Create( 80, 60, 200, 320 ) ) ;

Заметьте, что второй параметр Create - число кадров в анимационной последовательности - равен 60, Что это означает? Хорошо, если вы откроете ALL.BMP и произведете в нем прокрутку, вы увидите все анимационные кадры, размещенные под фоновым изображением. Их всего 60, и они размещены в шести рядах по 10 изображений в каждом ряду. Главное изображение имеет размер 640х480 пикселей, дабы соотиетстиоиать используемому разрешению экрана. Каждый кадр представляет собой 64-пиксельный квадрат, и десять таких кадров точно вписываются в одно полноэкранное пространство.

Обновление экрана

Теперь вы готовы написать код для обновления фонового буфера и осуществления быстрой смены кадров. Чтобы зделать это добавьте метод UpdateDisplay, как показано в листинге 23.

Листинг 23 Обновление экрана с элементами анимации.

procedure TFormI . UpdateDisplay ;
var TickCount : integer ;
ARect : TRect ;
ACurrentFrame : integer ;
i : integer ;
begin

// обновить и сменить поверхности - вначале перенести фон
ARect :== Rect( 0, 0, 640, 480 ) ;
repeat

until MakeltSo( BackBuffer.BitFast( 0, 0, Image, ARect, DDBLTFAST_NOCOLORKEY ) ) ;
// перенести анимации
TickCount := GetTickCount ;

for i :== 0 to Animations-Count - I do begin
with TSimpleAnim( Animations [ i ]) do begin
ACurrentFrame : == CurrentFrame [ TickCount ] ;
ARect :== Bounds ( ( ACurrentFrame mod 10 ) * 64,

( ACurrentFrame div 10) * 64+ 480, 64, 64 ) ;
repeat

until MakeltSo( BackBuffer.BltFast( X, Y, Image, ARect, DDBLTFAST_SRCCOIiORKEY*) ) ;
end ;
end ;
// сейчас смена
repeat

until MakeltSo( PrimarySurf ace. Flip ( NIL, DDFLIP_WAIT ) ) ;
// обновить местоположение тора
Move( XVelocity, YVelocity ) ;
end ;

Первым делом эта процедура переносит фоновое изображение в фоновый буфер. Помните, что верхняя часть ALLBMP является фоном, ARect создается, чтобы определить прямоугольную область части растрового изображения и BItFast. Фун-
кции BItFast - более-менее то же самое, что и Bit, но они нс в состоянии растянуть растровое изображение и не производят отсечения. Вы лишь предоставляете ей исходную поверхность, из которой будет пересылаться изображение, прямоугольник
на этой поверхности, который вы хотите переслать, битовый блок и позицию места назначения, куда необходимо его переслать, Последний параметр сообщает BItFast проигнорировать цветной ключ, который вы установили, потому что это фон,
а не одно из анимационных изображений. Считается, что BItFast приблизительно на 10 процентов быстрее, чем Bit, если она используется в программном продукте, в котором отсутствует акселератор, В противном случае разницы нет.

Теперь проведите битовый перенос для каждой анимации. Получите значение TickCount и затем пройдите в цикле список Anirnations. Получите текущий анимационный кадр и затем подсчитайте позицию этого кадра в ALLBMP. Рассчитайте координату х с помощью операции вычисления остатка по модулю между текущим кадром и числом кадров в одном ряду. Это даст число до 10, что определит индекс кадра в ряду. Это значение умножается на ширину каждого кадра, то есть на 64.

Координата у подсчитывается путем деления номера кадра на 10, что даст сначала номер кадра ц ряду; умножения номера кадра в ряду на высоту каждого ряда, которая равна 64, и добавления 480. Это место, где в ALLBMP начинаются кадры -
сразу же под 640х480 фоновым изображением. Все, что теперь необходимо, - это использовать BItFast снова для переноса кадра в фоновый буфер на этот раз с использованием цветного ключа.

Как только вся анимация будет нарисована, произойдет смена страницы. Тут есть существенный момент, о котором необходимо помнить: смена страницы не происходит, пока не закончится вертикальная развертка, поэтому приложение
находится в замкнутом цикле MakeltSo. Это устанавливает общую для приложения скорость смены кадров в секунду. Нет необходимости контролировать анимацию при помощи таймера. Анимация работает на своей максимальной скорости, используя GetTickCount для определения того, какой анимационный кадр выводить на экран.

Изображение на экране компьютера создается с помощью луча электронов, который начинает падать на экран вверху слева, отражает горизонтальные линии картины одну под другой слева направо. пока не достигните нижнёго правого
угла. Луч затем поворачивается опять в верхний левый угол и все начинается сначала. Скорость с которой происходит такое сканирование, как правило, находится в диапазоне от 60 до 80 Гц, хотя она может варьировать от 50 до 100 Гц и выше с учетом появившихся видеокарт и экранов. Вертикальная ретрассировка" это время, за которое электронный луч устанавливается вновь в верхнее левое положение с готовностью осуществлять очередное сканирование. Изменение
изображения экрана в определенный момент непосредственно перед сканированием даетв результате абсолютно гладкую без мельканий анимацию.


Вы можете заменить в конце дополнительную строку, вызывающую метод обновления позиции тора. Я решил сделать демонстрацию более интересной, предоставив вам возможность передвигать анимационный объект по экрану с помощью клавиатуры. Это легко сделать: позиция хранится в классе TSirnpleAnim. Листинг 24 представляет код метода Move.

Листинг 24 Метод Move передвигает аннимационный объект в пределах экрана.

procedure TForinl.Move( dx, dy : integer ) ;
var i, nFrom, nTo : integer ;
begin
/ / переместить торы

if Assigned ( Animations ) then begin
if MoveOption = 0 then begin
nFrom := 0 ;

nTo : = Animations. Count - I ;
end else begin
nFrom : = MoveOption - I;
nTo := nFrom ;
end ;

for i := nFrom to nTo do begin
with TSimpleAnim ( Animations [i]) do begin
X := X+dx ;
if X < 0 then X := 0 else
if X > 640 - 64 then X := 640 - 64 :
I := I + dy ;
if Y < 0 then Y := 0 else
if Y > 480 - 64 then Y := 480 - 64 ;
end ;
end ;
end ;
end;

Метод использует два параметра, которые представляют расстояние в пикселах для передвижения анимационного объекта относительно каждой из осей. Я добавил к форме еще одно поле, называемое MovedOption. MoveOption принимает положительное значение типа integer: 0 означает, что будут передвигаться все анимационные объекты, и любое другое число разрешает передвижение только определенного анимационного объекта. Определив диапазон использования анимационных объектов (либо все объекты вместе, либо один единственный объект), вы циклически проходите массив Animations, добавляя dx и dy в каждую анимационную позицию. Move также следит за тем, чтобы анимационные объекты не выходили за пределы экрана.

Обработка ввода с клавиатуры

Я модифицировал обработчик OnKeyDown для обработки нажатий клавиш курсора, которые используются для передвижения анимационных объектов по экрану, как представлено в листинге 25.

Листинг 25 Передвижение анимационных объектов с помощью клавиш курсора.

procedure TForml.FormKeyDown (Sender: TObject; var Key: Word;

Shift: TShiftState) ;
var Speed : integer ;

Anoption : integer ;
begin
Speed := 4 ;

if ssShift in Shift then Speed := I ;
case Key of

VK_ESCAPE, VK_F12 : Close ;
VK_LEFT : XVelocity := -Speed ;
VK_RIGHT : XVelocity := Speed ;
VK_UP : YVelocity := -Speed ;
VK_DOWN : YVelocity := Speed ;
VK_SHIFT : begin

if Abs( XVelocity ) > I then XVelocity := XVelocity div 4 ;
if Abs( YVelocity ) > I then YVelocity :== YVelocity div 4 ;

end ;

by1:e( '0' )..byte( '9' ) : begin
Anoption := Key -48 ;

if Anoption <= Animations. Count then MoveOption : = Anoption
end ;
end ;
end;

Если удерживать клавишу Shift, анимационный объект будет передвигаться в четыре раза медленней, для этого необходимо определять значение локальной переменной Speed. Я добавил поля скоростей XVelocity и YVelocity, которые получают значение при нажатии какой-либо из клациш курсора. Клавиша Shift при нажатии снижает скорость, если она еще недостаточно медленная. Клавиши от 0 до 9 определяют значение MoveOption, которое указывает, каким объектам перемещаться.

Заметьте, что наличие мониторинга нажатия определенной клавиши означает, что анимационный объект может по-разному реагировать на различные комбинации клавиш. В этом случае, вы можете передвигать спрайты по диагонали, если вы будете удерживать сразу две клавиши управления курсором. Теперь необходимо ввести обработчик ОпКеу1}рдля учета количества отжатия клавиши. Это представлено в листинге 26.

Листинг 26 Отслеживание отжатых клавиш.

procedure TForml.FormKeyUp (Sender: TObject; var Key: Word;
Shift: TShiftState);

begin
// проверка отпускания клавиши управления курсором

case Key of
VKJLEFT,

VK_RIGHT : XVelocity := 0 ;
VK_UP,

VK_DOWN : YVelocity :== 0 ;
VK_SHIFT : begin
XVelocity :== XVelocity * 4 ;
YVelocity := YVelocity * 4 ;
end ;
end ;
end;

Как вы видите, движение прекращается ь горизонтальном и вертикальном направлениях, если клавиша управления курсором отпущена, и скорость увеличивается в четыре раза, если отпускается клавиша Shift.

Вхождение в цикл сообщений

Теперь, когда главный код у вас в сборе, вы должны найти место для вызова UpdateDisplay. Фактически, вы можете вызывать ее как можно чаще для того, чтобы получить максимально возможную скорость смены кадров. На языке С или даже Pascal, вам необходимо было войти в цикл сообщения и вызывать UpdateDisplay всякий раз при повторном прохождении цикла. Это можно сделать и в Delphi, но это несколько замысловато, ввиду того, что Delphi обладает своим собственным циклом
сообщений. Вы просто должны определенным образом перенести элемент управления в свой собственный цикл.

Вместо этого можно добавить код в события Onidle и OnMessage переменной Application. Вы можете взглянуть на объявления обработчиков этих событий в диалоговой справке Delphi и добавить их в качестве методов в свою форму. Вот как
выглядят эти объявления:

procedure HandleMessage( var Msg : TMsg ;

var Handled : boolean ) ;

procedure ldleHandler( Sender : TObject ;
var Done : boolean ) ;

Они, как и любой другой метод, добавляются в секцию private объявления формы. Применять их легко, главным образом нужно просто вызвать UpdateDisplay. Нужно только учесть один существенный момент - необходимо установить Done в False в IdleHandler, чтобы убедиться, что VCL не отдает управление в ожидании сообщения Windows, что приостановило бы обновление экрана. Это показано в листинге 27.

Листинг 27 Обновление экрана с максимально возможной скоростью.

procedure TForml.HandleMessage( var Msg : TMsg ; var Handled : boolean ) ;

begin
UpdateDisplay ;

end ;
procedure TFormI .ldleHandler( Sender : TObject ; var Done : boolean ) ;

begin
UpdateDisplay ;
Done := false ;
end ;

В конечном итоге добавьте ь форму свойство FlippingEnabled, объявив его, как показано в листинге 28.

Листинг 28 Добавление в форму свойства FlippingEnabled.

type TFormI = class( TForm )

* *

FFlippingEnabled : boolean ;
public

property FlippingEnabied : boolean read FFlippingEnabled write SetFlippingEnabled ;
end ;

[Оглавление]

Опрос

Конкурсы
Реклама

 

Web дизайн: Бурлаков Михаил    

Web программирование: Бурлаков Михаил

Используются технологии uCoz