Последние записи
- Как в Delphi XE обнулить таймер?
- Изменить цвет шрифта TextBox на форме
- Ресайз PNG без потери прозрачности
- Вывод на печать графического файла
- Взаимодействие через командную строку
- Перенести программу из Delphi в Lazarus
- Определить текущую ОС
- Автоматическая смена языка (раскладки клавиатуры)
- Сравнение языков на массивах. Часть 2
- wprintf как напечатать кириллицу
Интенсив по Python: Работа с API и фреймворками 24-26 ИЮНЯ 2022. Знаете Python, но хотите расширить свои навыки?
Slurm подготовили для вас особенный продукт! Оставить заявку по ссылке - https://slurm.club/3MeqNEk
Online-курс Java с оплатой после трудоустройства. Каждый выпускник получает предложение о работе
И зарплату на 30% выше ожидаемой, подробнее на сайте академии, ссылка - ttps://clck.ru/fCrQw
26th
Июн
Как работать с графикой на канве в среде Дельфи. Урок 3-4
Posted by Chas under Журнал, Статьи
Одним из основных методов получения эффекта движущегося объекта является принцип уничтожения объекта на текущем месте и вывод его на новом с заданным приращением координат dX и dY. Однако прямолинейное применение такого метода на практике приводит к неприятному эффекту “мерцания” объекта во время движения. Причина этого в том, что при проведении этих операций непосредственно на канве объекта, где происходит движение, мы получаем двойную перерисовку участка канвы. Такая двойная смена цвета пикселей и является причиной. Сказанное наглядно видно на рисунке (см. рис.1), во время каждого такта движения происходит два процесса рисования: сначала старое изображение «затирается» фоном, а затем рисуется новое изображение движущегося объекта на новом месте…
Владимир Дегтярь
Урок 3
Создание движущихся объектов (еще немного теории)
Рис. 1. Демонстрация эффекта «мерцания»
Избежать данной проблемы можно, объединив обе операции «стирания» изображения и вывода нового в одну. Суть этого состоит в том, чтобы на канве объекта, невидимого на экране, вывести стирающий участок фона, затем вывести изображение движущегося объекта на новом месте и уже после этих операций вывести полученную комбинацию изображения на видимую часть канвы экрана. Для этого создаем дополнительный объект со свойством канвы (лучше всего подходит дополнительный буфер типа TBitMap), на котором и проводим предварительные операции. Размер такого дополнительного буфера определяется в зависимости от величины смещения движущегося объекта.
Возможны два варианта: когда старое и новое положение объекта имеют общую область (перекрывают друг друга) и не перекрывают (см. рис.2):
Рис. 2. Перекрытие объектов
Размер буфера в первом случае, как видно из рисунка 2 по каждой из координат равен размеру объекта плюс двойное приращение координат по соответствующим координатам (берется абсолютная величина). Теперь достаточно вывести в буфер область фона, соответствующего текущему положению объекта (старое положение) и наложить поверху изображение объекта, разместив его в буфере со смещением dX и dY (опять же по абсолютной величине) относительно нулевых координат буфера. После вывода изображения всего буфера на экран изображение объекта исчезнет и появится в новом положении.
Во втором случае, когда старое и новое изображения не имеют общей области также можно применить указанный метод. Но при этом, судя по рисунку 3, значительно увеличивается размер дополнительного буфера:
Рис. 3. Случай отсутствия перекрытия изображений
Можно уменьшить размер дополнительного буфера, применив немного другой подход (см. рис.4):
Рис. 4. Принцип уменьшения буфера
Здесь размер дополнительного буфера значительно меньше, чем на рисунке 3, но вывод изображения
объекта происходит не в центр буфера, а в один из четырех секторов. Перед заполнением буфера необходимо определить величину и знак следующего приращения dx и dy. Затем заполнить буфер фоном текущего положения объекта и вывести изображение объекта с учетом следующего приращения координат:
Если
dx > 0 и dy <= 0, то bx = dx и by = 0;
dx <= 0 и dy > 0 , то bx = 0 и by = dy;
dx <= 0 и dy <= 0, то bx = 0 и by = 0;
Значения bx и by присваиваются , естественно по абсолютным величинам dx и dy. Вот код программы для первого случая (см. листинг 1):
ЛИСТИНГ-1
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs;
type
TForm1 = class(TForm)
procedure FormActivate(Sender: TObject);
procedure FormPaint(Sender: TObject);
procedure FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
private
end;
var
Form1: TForm1;
{ битовые образы рисунка, фона и дополнит. буфера, соответственно }
Pic, Fon, Buf : TBitMap;
// области вывода фона
RectFon, RectBuf : TRect;
// текущие координаты
x,y: integer;
// приращения координат
dx, dy: integer;
// размеры дополнит.рисунка и буфера
W,H,WB,HB: integer;
implementation
{$R *.dfm}
procedure TForm1.FormActivate(Sender: TObject);
begin // инициализация TBitMap и TRect
// загружаем рисунок фона
Fon:= TBitMap.Create;
Fon.LoadFromFile(‘fon.bmp’);
{ загружаем рисунок объекта, который будет двигаться}
Pic:= TBitMap.Create;
Pic.LoadFromFile(‘picture.bmp’);
// задаем прозрачность объекту
Pic.Transparent:= true;
Pic.TransparentColor:= Pic.Canvas.Pixels[1,1];
{ определяем и устанавливаем размеры дополнит. буфера }
W:= Pic.Width;
H:= Pic.Height;
Buf:= TBitMap.Create;
Buf.Width:= W + 2*abs(dx);
Buf.Height:= H + 2*abs(dy);
WB:= Buf.Width;
HB:= Buf.Height;
// назначаем соответствие палитр цветов
Buf.Palette:= Fon.Palette;
Buf.Canvas.CopyMode:= cmSrcCopy;
{ определяем область в дополнит. Буфере для загрузки участка фона }
RectBuf:= Bounds(0,0,WB,HB);
// инициализация координат
x:=300; y:=150;
end;
procedure TForm1.FormPaint(Sender: TObject);
begin // выводим на экран фон и объект
Form1.Canvas.Draw(0,0,Fon);
Form1.Canvas.Draw(x,y,Pic);
end;
{ перемещение объекта на один шаг происходит после каждого нажатия
соответствующей клавиши}
procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
case Key of
37: begin dx:= -3; dy:= 0; end;
38: begin dx:= 0; dy:= -3; end;
39: begin dx:= 3; dy:= 0; end;
40: begin dx:= 0; dy:= 3; end;
end;
{ определяем область фона, которую надо запомнить }
RectFon:= Bounds(x+dx,y+dy,WB,HB);
// запомним фон в буфере
Buf.Canvas.CopyRect(RectBuf,Fon.Canvas,RectFon);
// наложим в буфере на фон рисунок с прозрачным фоном
Buf.Canvas.Draw(abs(dx),abs(dy),Pic);
// выводим на форму в новой позиции
x:=x+dx; y:=y+dy;
Form1.Canvas.Draw(x,y,Buf);
end;
end.
А это для второго случая (см. листинг 2):
ЛИСТИНГ-2
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs;
type
TForm1 = class(TForm)
procedure FormActivate(Sender: TObject);
procedure FormPaint(Sender: TObject);
procedure FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
private
end;
var
Form1: TForm1;
{ битовые образы рисунка, фона и дополнит.буфера, соответственно }
Pic, Fon, Buf : TBitMap;
// области вывода фона
RectFon ,RectBuf : TRect;
// текущие координаты
x,y: integer;
// приращения координат
dx, dy: integer;
{ смещение изображения объекта
в дополнительном буфере }
bx, by: integer;
// размеры дополнит.рисунка и буфера
W,H,WB,HB: integer;
implementation
{$R *.dfm}
procedure TForm1.FormActivate(Sender: TObject);
begin // инициализация TBitMap и TRect
// загружаем рисунок фона
Fon:= TBitMap.Create;
Fon.LoadFromFile(‘fon.bmp’);
{ загружаем рисунок объекта, который будет двигаться}
Pic:= TBitMap.Create;
Pic.LoadFromFile(‘picture.bmp’);
// задаем прозрачность объекту
Pic.Transparent:= true;
Pic.TransparentColor:= Pic.Canvas.Pixels[1,1];
{ определяем и устанавливаем размеры дополнит. буфера }
W:= Pic.Width;
H:= Pic.Height;
Buf:= TBitMap.Create;
Buf.Width:= W + abs(dx);
Buf.Height:= H + abs(dy);
WB:= Buf.Width;
HB:= Buf.Height;
// назначаем соответствие палитр цветов
Buf.Palette:= Fon.Palette;
Buf.Canvas.CopyMode:= cmSrcCopy;
{ определяем область в дополнит. Буфере для загрузки участка фона }
RectBuf:= Bounds(0,0,WB,HB);
// инициализация координат
x:=300; y:=150;
end;
procedure TForm1.FormPaint(Sender: TObject);
begin // выводим на экран фон и объект
Form1.Canvas.Draw(0,0,Fon);
Form1.Canvas.Draw(x,y,Pic);
end;
{ перемещение объекта на один шаг происходит после каждого нажатия
соответствующей клавиши}
procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
case Key of
37: begin dx:= -3; dy:= 0; end;
38: begin dx:= 0; dy:= -3; end;
39: begin dx:= 3; dy:= 0; end;
40: begin dx:= 0; dy:= 3; end;
end;
if (dx>0) and (dy>0) then begin bx:=abs(dx); by:=abs(dy); end;
if (dx>0) and (dy<=0) then begin bx:=abs(dx); by:=0; end;
if (dx<=0) and (dy>0) then begin bx:=0; by:=abs(dy); end;
if (dx<=0) and (dy<=0) then begin bx:=0; by:=0; end;
{ определяем область фона, которую
надо запомнить }
RectFon:= Bounds(x+dx,y+dy,WB,HB);
// запомним фон в буфере
Buf.Canvas.CopyRect(RectBuf,Fon.Canvas,RectFon);
// наложим в буфере на фон рисунок с прозрачным фоном
Buf.Canvas.Draw(bx,by,Pic);
// выводим на форму в новой позиции
x:=x+dx; y:=y+dy;
Form1.Canvas.Draw(x,y,Buf);
end;
end.
В принципе обе эти программы можно объединить, введя процедуру проверки на совпадение старой и новой областей изображения. Но это уже другая тема.
В данных примерах подразумевалось, что движущийся объект состоит всего из одного изображения ‘picture.bmp’. Но на практике, обычно таких изображений несколько. Наиболее часто изображения хранятся в файле в виде набора спрайтов. Вот здесь есть один небольшой подводный камень. Обычно выбор необходимого спрайта производится процедурой CopyRect() из буфера типа TBitMap, куда предварительно загружены изображения всех спрайтов. Но, при таком методе вывода изображения в дополнительный буфер с участком фона нельзя присвоить изображению свойство прозрачности.
Для обхода этого препятствия следует ввести еще один дополнительный буфер типа TBitMap, куда методом CopyRect() загрузить изображение нужного спрайта, а затем уже, присвоив свойство прозрачности, методом Canvas.Draw вывести изображение на канву дополнительного буфера с участком фона.
Следует отметить, что для создания «эффекта» движения можно оставить изображение графического объекта неподвижным, а двигать сам фон. При этом размер фона должен быть больше размера окна формы. Также можно применять одновременно или поочередно сдвиг объекта и сдвиг фона.
Урок 4
Проект с движением звездолета.
Аналогично Уроку 2 создадим новый проект и сохраним его под именем Lesson2. Параметры формы Form1 также установим аналогично Уроку 2, изменив только заголовок Form1 на «Урок по графике № 2».
Для получения движения объекта применим метод, когда сам объект (звездолет) неподвижен на форме, а “двигается” сам фон. Фон создадим в два раза больше по высоте размера окна формы. Для этого используем файлы ‘star1.bmp’ и ‘star2.bmp’. Нам также понадобится еще один дополнительный буфер BufFonDop. Кроме этого введем еще переменные – координаты вывода на форму фона и звездолета, а также приращения к координатам (см. листинг 3 ):
ЛИСТИНГ-3
Form1: TForm1;
BufFon,BufFonDop,BufSpr,BufPic,Buffer: TBitMap;
xS1,yS1: integer; // координаты звездолета ‘ship1’
xf,yf: integer; // координаты вывода общего буфера на форму
dyf: integer = 2; // приращение изменения координаты yf по вертикали
dxS1,dyS1: integer; // приращение координат звездолета по гориз. и вертик.
ns: byte; // номер спрайта
{Здесь-же вводим начальные данные для переменных}
xs1:= 250; ys1:= 1006; // начальные значения переменных-
dxs1:= 4; dys1:= 2;
xf:= 0; yf:= -540; dyf:= 2;
{ Инициализацию буферов проведем в процедуре OnCreate() формы }
procedure TForm1.FormCreate(Sender: TObject);
begin
BufFon:= TBitMap.Create;
BufFonDop:= TBitMap.Create;
BufSpr:= TBitMap.Create;
BufPic:= TBitMap.Create;
Buffer:= TBitMap.Create;
BufFonDop.LoadFromFile(‘data/star1.bmp’); // загрузка 1-го рисунка фона из файла
BufSpr.LoadFromFile(‘data/ship1.bmp’); // загрузка спрайтов из файла
BufFon.Width:= BufFonDop.Width; // размеры общего буфера фона
BufFon.Height:= (BufFonDop.Height) * 2; { по высоте = двум Height одного
рисунка фона}
BufFon.Canvas.Draw(0,0,BufFonDop); // заносим 1-й рисунок фона
BufFonDop.LoadFromFile(‘data/star2.bmp’); // загрузка 2-го рисунка фона из файла
BufFon.Canvas.Draw(0,540,BufFonDop); // заносим 2-й рисунок фона
Buffer.Width:= BufFon.Width; // размеры общего буфера = размерам буфера фона
Buffer.Height:= BufFon.Height;
BufPic.Width:= round((BufSpr.Width) / 2); // размеры буфера рисунка одного спрайта
BufPic.Height:= BufSpr.Height;
В процедуре DrawShip1(i) добавлено условие прозрачности фона рисунка звездолета (см. листинг 4):
ЛИСТИНГ-4
begin
BufPic.Canvas.CopyRect(bounds(0, 0, BufPic.Width, BufPic.Height),
BufSpr.Canvas,bounds( i * 66, 0, BufPic.Width,
BufPic.Height));
BufPic.transparent:= true;
BufPic.transparentcolor:= BufPic.canvas.pixels[1, 1]
end;
«Эффект» движения организован в обработчике таймера. В каждом такте таймера 50 мсек:
- смещаем координату вывода общего буфера yf на dyf вниз (+ 2)
- координату yS1 вывода звездолета смещаем на dyS1 вверх (- 2). В этом случае изображение звездолета будет неизменным в координатах формы, а двигаться будет только фон
- в общий буфер (Buffer) выводим фон (при этом «уничтожается» изображение звездолета на старом месте)
- выводим в общий буфер на фон новое изображение звездолета со смещением dyS1
- выводим общий буфер на форму
В процедуре DrawShip1(ns) через переменную ns, принимающую попеременно значения 0 или 1, выводятся поочередно спрайты изображения звездолета (см. листинг 5):
ЛИСТИНГ-5
begin
ns:= ns + 1; if ns > 1 then ns:= 0; // смена спрайтов
DrawShip1(ns);
yf:= yf + dyf; if yf > 0 then begin yf:= -540; yS1:= 1006; end; // приращение yf
yS1:= yS1 — dyS1; // приращениие звездолета обратно приращению фона
Buffer.Canvas.Draw(0,0,BufFon); // выводим фон в общий буфер
Buffer.Canvas.Draw(xS1,yS1,BufPic); // выводим рисунок спрайта поверх фона
Form1.Canvas.Draw(xf,yf,Buffer); // выводим все на форму
end;
//Движение звездолета по горизонтали в пределах окна формы организовано
//в обработчике нажатия клавиши OnKeyDown
procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
case Key of
37: begin // клавиша "стрелка" — влево
if xS1 <= 0 then EXIT; { если звездолет у левого края формы
xS1 не изменяется}
xS1:= xS1 — dxS1; // движение звездолета влево
end;
39: begin // клавиша "стрелка" — вправо
if xS1 >= 630 then EXIT; { если звездолет у правого края формы
xS1 не изменяется}
xS1:= xS1 + dxS1; // движение звездолета вправо
end;
end;
end;
Запустим проект на компиляцию и взглянем на результаты нашего труда (см. рис.5):
Рис. 5. Движение звездолета по-горизонтали
Заключение:
В следующих уроках добавим еще несколько движущихся объектов в проект.
Рассматриваемые в данной статье проекты полностью приведены в ресурсах к статье на http://www.programmersforum.ru в разделе «Журнал клуба программистов. Второй выпуск» (папка Lesson2*).
Обсудить на форуме – Как работать с графикой на канве в среде Дельфи. Урок 3-4
Статья из второго выпуска журнала «ПРОграммист».
Случайные статьи
Купить рекламу на сайте за 1000 руб
пишите сюда - alarforum@yandex.ru
Да и по любым другим вопросам пишите на почту
пеллетные котлы
Пеллетный котел Emtas
Наши форумы по программированию:
- Форум Web программирование (веб)
- Delphi форумы
- Форумы C (Си)
- Форум .NET Frameworks (точка нет фреймворки)
- Форум Java (джава)
- Форум низкоуровневое программирование
- Форум VBA (вба)
- Форум OpenGL
- Форум DirectX
- Форум CAD проектирование
- Форум по операционным системам
- Форум Software (Софт)
- Форум Hardware (Компьютерное железо)