Последние записи
- TChromium (CEF3), сохранение изображений
- Как в Delphi XE обнулить таймер?
- Изменить цвет шрифта TextBox на форме
- Ресайз PNG без потери прозрачности
- Вывод на печать графического файла
- Взаимодействие через командную строку
- Перенести программу из Delphi в Lazarus
- Определить текущую ОС
- Автоматическая смена языка (раскладки клавиатуры)
- Сравнение языков на массивах. Часть 2
Интенсив по Python: Работа с API и фреймворками 24-26 ИЮНЯ 2022. Знаете Python, но хотите расширить свои навыки?
Slurm подготовили для вас особенный продукт! Оставить заявку по ссылке - https://slurm.club/3MeqNEk
Online-курс Java с оплатой после трудоустройства. Каждый выпускник получает предложение о работе
И зарплату на 30% выше ожидаемой, подробнее на сайте академии, ссылка - ttps://clck.ru/fCrQw
23rd
Июл
Как сделать Фоновое изображение в ListView?
Stilet:
Могу предложить такое:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, Grids, StdCtrls, ComCtrls;type
TForm1 = class(TForm)
ListView1: TListView;
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure ListView1CustomDrawItem(Sender: TCustomListView;
Item: TListItem; State: TCustomDrawState; var DefaultDraw: Boolean);
private
{ Private declarations }
public
{ Public declarations }
end;var b:TBitmap;
Form1: TForm1;implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
var e,i:integer;
begin
b:=TBitmap.Create;
b.LoadFromFile(’D:\stasya_ru_2_big.bmp’);
for i:=1 to 100 do begin
with ListView1.Items.Add do begin
Caption:=TimeToStr(now);
for e:=1 to 100 do SubItems.Add(TimeToStr(now));
end;
end;
end;procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
b:=nil;
end;procedure TForm1.ListView1CustomDrawItem(Sender: TCustomListView;
Item: TListItem; State: TCustomDrawState; var DefaultDraw: Boolean);
const c=3;
var ARect:TRect;
begin
DefaultDraw:=false;
ARect:=Item.DisplayRect(drBounds);
BitBlt(Sender.Canvas.Handle,ARect.Left+c,ARect.Top+c,ARect.Right-ARect.Left-c,
ARect.Bottom-ARect.Top-c,b.Canvas.Handle,ARect.Left,ARect.Top,SRCCOPY);
end;end.
14th
Июл
Gif анимация в delphi
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, GifImg, ExtCtrls;
type
TForm30 = class(TForm)
Image1: TImage;
Timer1: TTimer;
procedure Timer1Timer(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
Gif: TGifImage;
public
{ Public declarations }
end;
var
Form30: TForm30;
implementation
{$R *.dfm}
procedure TForm30.FormCreate(Sender: TObject);
begin
Gif := TGifImage.Create;
Gif.LoadFromFile(’C:\Xom04.gif’);
Gif.Animate := True;
Gif.AnimateLoop := glEnabled;
end;
procedure TForm30.Timer1Timer(Sender: TObject);
begin
Image1.Picture.Assign(Gif);
end;
end.
10th
Июл
Как работать с графикой на канве в среде Дельфи. Урок 7-8
Вот мы и подошли к заключительной серии наших уроков по графике в среде DELPHI. Сегодня рассмотрим практическое применение модуля <LoadObjectToBufferMod>, из нашего прошлого материала, на примере работы с движущимися графическими объектами…
Продолжение. Начало цикла смотрите в первом выпуске журнала…
Как работать с графикой на канве в среде Дельфи. Урок 7—8
Владимир Дегтярь
DeKot degvv@mail.ru
Проект с использованием дополнительного универсального модуля. Урок 7
Создайте и сохраните аналогично предыдущим урокам новый проект <Lesson 4>. В разделе uses введем модуль и в разделе var переменные для фона и звездолета ‘ship1 . Сам модуль должен находится в папке проекта (см. листинг 1):
Interface ЛИСТИНГ-1
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs,LoadObjectToBufferMod, ExtCtrls;
var
Form1: TForm1;
xf,yf: integer; // координаты вывода общего буфера на форму
dyf: integer; // приращение изменения координаты yf по вертикали
xS1,yS1: integer; // координаты звездолета 'ship1'
dxS1,dyS1: integer; // приращение координат 'ship1' по гориз. и вертик.
xR,yR: integer; // координаты звездолетов 'ship4' ...'ship7'
dyR: integer; // приращение координат 'ship2 - 5'
N_kadrS1: byte; // номер изображения спрайта 'ship1'
implementation
В процедуре OnCreate() формы проведем инициализацию буферов и введем начальные данные для переменных (см. листинг 2):
procedure TForm1.FormCreate(Sender: TObject); ЛИСТИНГ-2
begin
// инициализация и загрузка битовых образов буфера фона и общего буфера
InitFon(1,2,'data/star1.bmp');
LoadFon(0,0,'data/star1.bmp');
LoadFon(0,540,'data/star2.bmp');
InitBuff;
// начальные значения переменных
xS1:= 250; yS1:= 1006;
dxS1:= 5; dyS1:= 2;
xf:= 0; yf:= -540; dyf:= 2;
end;
Процедура InitFon() вызывается из модуля ‘1‘ – количество используемых файлов фона по ширине, ‘2‘ – по высоте, ‘data/star1.bmp’ – по этому файлу определяем размеры основного буфера фона BufFon. Далее в процедурах loadFon() загружаем все файлы фона в BufFon. InitBuff() – инициализация буфера Buffer и загрузка в него фона. Движение графических объектов организовано в обработчике таймера (см. листинг 3):
procedure TForm1.Timer1Timer(Sender: TObject); ЛИСТИНГ-3
begin
// вывод движения 'ship1'
N_kadrS1:= InitSprite('data/ship1.bmp',2,1,1,N_kadrS1);
LoadBuff(xS1-dxS1,yS1+dyS1,xS1,yS1,0);
Form1.Canvas.Draw(xf,yf,Buffer);
yf:=yf+dyf; // приращения координат для движ. фона
if yf >= 0 then // если половина фона "прошла" через окно формы
begin
yf:= -540; yS1:= 1006; // возврат к начальным координатам
FreeBuff; // "перезагрузка" фона
end;
yS1:= yS1 - dyS1; // приращение для 'ship1',обратное приращению фона
end;
Функция InitSprite() возвращает номер следующего кадра (следующий рисунок спрайта ‘ship1’), который при следующем такте таймера опять передается в эту же функцию. В процедуре LoadBuff() предыдущее изображение спрайта перекрывается областью TRect фона и затем выводится новое изображение спрайта.
Когда весь фон проходит через окно формы в процедуре FreeBuff() фон снова перерисовывается в Buffer и возвращаются начальные координаты вывода Buffer на форму. Движение звездолета по горизонтали организовано в обработчике нажатия клавиш (см. листинг 4):
procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; ЛИСТИНГ-4
Shift: TShiftState);
begin
case Key of
VK_Left: begin // клавиша "стрелка" - влево
if xS1 <= 0 then EXIT; { если звездолет у левого края формы
xS1 не изменяется}
dxS1:= -5; // движение звездолета влево
xS1:= xS1 + dxS1;
end;
VK_Right: begin // клавиша "стрелка" - вправо
if xS1 >= 630 then EXIT; { если звездолет у правого края формы
xS1 не изменяется}
dxS1:= 5; // движение звездолета вправо
xS1:= xS1 + dxS1;
end;
end;
end;
Запустите данный проект. Получим приложение аналогичное проекту <Lesson 3>. Сравнив коды этих проектов, заметно преимущество применения модуля. Сам проект приведен в папке <Lesson 4_1> (см. ресурсы к статье). Мы же продолжим создание проекта <Lesson 4> далее, введя еще один движущийся
графический объект (звездолет из файлов ‘ship4’ … ‘ship7’). Эти файлы имеют разный размер и разное количество рисунков).
В разделе var введем переменные для нового графического объекта:
var
N_kadrR: byte; // номер изображения спрайта 'ship4' ... 'ship7'
ns: byte = 5; // номер файла спрайтов
Nspr: byte = 2; // кол-во изображений в строке файла спрайтов
Добавим начальные значения новых этих переменных:
xs1 := 250; ys1 := 1006; // начальные значения переменных-
dxs1:= 4; dys1:= 2;
xf := 0; yf := -540; dyf:= 2;
xr := 50; yr:= 450;
dyr:= 3;
Randomize;
В обработчике таймера для нового объекта повторно вызовем методы вывода изображений спрайтов (см. листинг 6):
// вывод движения ‘ship4-ship7’ ЛИСТИНГ-6
N_kadrR:= InitSprite(‘data/ship’ + inttostr(ns) + ‘.bmp’, Nspr, 1, 1, N, N_kadR);
LoadBuf(xR, yR – dyR, xR, yR, 0);
Form1.canvas.draw(xf, yf, Buffer);
yR:= yR + dyR; // приращение для ‘ship4-ship7’
if yR >= yS1 + 100 then // проход ‘ship4-ship7’ через окно формы
begin
xR:= random(600);
yR:= yS1 – 600;
ns:= random(4) + 4;
if ns = 7 then Nspr:= 4
else Nspr:= 2
end
end
Здесь, в функции InitSprite() переменная Nspr означает количество изображений в файле спрайтов и определяется по значению ns – номера файла нового объекта, который в свою очередь определяется функцией random() после каждого прохождения новым графическим объектом окна формы. Причем, движение ‘ship1’ аналогично предыдущим примерам в процедуре FormKeyDown(). Полностью проект приведен в папке <Lesson 4> (см. ресурсы к статье).
Развитие проекта Lesson 4. Урок 8
Продолжим наш проект <Lesson 4>, превратив его в небольшой «шутер» или попросту в «стрелялку». Добавим также возможность стрельбы ракетами для ‘ship1’ по встречным звездолетам и эффект взрыва при попадании. Соответствующие графические файлы ракеты (bullet) и взрыва (explo) находятся в папке <data>. Сделаем это новым проектом с использованием проекта <Lesson 4>.
Итак, приступим… Создайте новую папку и присвойте ей имя <Lesson 5>. Скопируйте в нее все файлы
проекта <Lesson 4> и затем откройте проект в Delphi. Переименуем проект – File => Save Project as … и в диалоговом окне “Введите имя файла” – введите Lesson5 и сохраните проект под новым именем. Теперь снова откройте проект <Lesson 5> и добавьте переменные для новых объектов:
xb, yb: integer; // координаты ракеты
dyb: integer; // приращение координат ракеты
n_kadrb: byte; // номер изображения ракеты
n_kadre: byte; // номер изображения взрыва
vistrel,flag_s: boolean;
Флаги vistrel и flag_S типа Boolean необходимы для управления выводом изображения при выстреле ракетой. Случайный выбор звездолетов ‘ship4’ … ‘ship7’ выделим в отдельную процедуру, так как выбор в программе осуществляется несколько раз после прохождения звездолетом нижней границы окна формы, после попадания ракеты, а значит и уничтожения звездолета (см. листинг 7):
procedure randomship; ЛИСТИНГ-7
begin
xr:= random(600);
yr:= ys1 – 600;
ns:= random(4) + 4;
if ns = 7 then nspr:= 4
else nspr:= 2
end;
В процедуре FormKeyDown() добавим обработку клавиши ”пробел” – пуск ракеты (см. листинг 8):
vk_space: // пробел – выстрел ЛИСТИНГ-8
if not vistrel then begin
xb:= xs1 + 22;
yb:= ys1 – 30;
dyb:= 20;
vistrel:= true
end;
В обработчике таймера выводим движение ракеты при произведенном пуске – флаг (см. листинг 9):
if yr >= ys1 + 100 then ЛИСТИНГ-9
randomShip; // проход ship4-7 через окно формы
if (vistrel) and (not flag_s) then begin // если произведен выстрел, то
// вывод изображения ракеты
n_kadrb:= initSprite(‘data/bullet.bmp’, 1, 1, 1, n_kadrb);
loadBuff(xb, yb + dyb, yb, 0);
yb:= yb – dyb;
if yb <= ys1 – 510 then begin // ракета вышла за пределы окна формы
vistrel:= false;
freeBuff // ПЕРЕЗАГРУЗКА фона
end
end;
и также вводим “эффект взрыва” при попадании ракеты в цель (см. листинг 10):
if (yb <= yr) and ЛИСТИНГ-10
(xb >= xr) and
(xb <= xr+100) and
(vistrel) then begin
flag_s:= true;
dyb:= 0;
n_kadre:= initSprite(‘data/explo.bmp’, 8, 1, 1, n_kadre);
loadBuff(xb-54, yb + 2, xb-54, 0);
form1.canvas.draw(0, yf, buffer);
if n_kadre = 0 then begin // конец эффекта взрыва
vistrel:= false;
flag_s:= false;
freeBuff;
randomShip
end
end;
И собственно то, чего добивались. Скомпилируем и запустим наш проект (см. рисунок):
Заключение
На этом мы закончим рассмотрение простейших приемов работы с движущимися графическими объектами. Предложу еще самостоятельно разобрать метод вывода на канву текстовой графики. Добавьте в код последнего приложения счетчики количества пусков ракет и попаданий в цель (например: count1, count2) и используя свойства Font(Size,Style,Color) и метод TextOut(X,Y, IntToStr(count..) введите статистику в приложении. Будем считать это домашним заданием…
Рассматриваемые в данной статье проекты полностью приведены в ресурсах к статье на http://www.programmersforum.ru в разделе «Журнал клуба программистов. Четвертый выпуск» (папка Lesson5*).
* Комментарий автора
Перед запуском в среде Дельфи скопируйте в папку с проектом папку data с графическими файлами.
Статья из четвертого выпуска журнала “ПРОграммист”.
Скачать этот номер можно по ссылке.
Ознакомиться со всеми номерами журнала.
Обсудить на форуме — Как работать с графикой на канве в среде Дельфи. Урок 7-8
2nd
Июл
Анимация в WEB на JavaScript
Здравствуйте! В этой статье я хотел бы рассказать о создании анимации на веб-страницах с использованием скриптового языка программирования JavaScript, далее JS. Статья ориентирована на новичков.
Анимация на WEB страницах
Алексей Шульга
by Levsha100 http://www.programmersforum.ru/member.php?u=19268
По правде говоря я не являюсь поклоником JS да и вэб-программирования тоже, но родина сказала «Надо!» Я немного отошел от темы нашего сайта, ибо работы над ним еще ведутся и это, видимо, будет еще долго. Поэтому было решено написать цикл статей по JS и различным интересным трюкам.
Шаблон
Для начала нам необходимо создать заготовку- то место, куда мы будем внедрять скрипты, Вот она:
<html>
<head>
<title>Тестовая страничка</title>
</head>
<body>
</body>
</html>
Это обыкновенная HTML-страничка, я сохранил ее под именем Test.html .
Встраивание JS-скрипта
Итак, шаблон у нас есть, и теперь мы можем смело пробовать оживлять страничку с помощью скриптов, добавить JS-скрипт можно двумя основными способами- добавлением его непосредственно в веб-страницу, либо вынести его в отдельный файл, для наглядности я выбрал первый способ. Добавим скрипт в секцию body, после чего она примет вид:
<body>
<script>
//Тут находится JS-код
</script>
</body>
Для проверки можно заменить за комментированную строку такой строкой:
alert(”Hello, world” );
Этот код выведет [модальное] окно с текстом “Hello, world”.
Добавление объектов
Прежде чем мы займемся анимацией нужно что бы было то, что будет анимироваться, поэтому займемся созданием анимируемых объектов- для примера возьмем квадратные DIV-вы со стороной 10 пикселей. Прежде чем создавать собственно объекты для них необходимо задать стиль, это делается с помощью CSS. Пример:
<head>
<title>Тестовая страничка</title>
<style type=”text/css”>
.obj1style{position:absolute; width:10; height:10; background-color: #00FF00}
</style>
</head>
Наиболее важным свойством является свойство position и именно его значение absolute -оно позволяет размещать объект, к которому применен данный стиль, в любой точке веб-страницы. Свойства width и height, как нетрудно догадаться, отвечают за высоты и ширину объекта соответственно. Цвет фона объекта- зеленый.
Теперь займемся собственно добавлением объектов, для этого необходимо заменить строку:
//Тут находится JS-код
на строки:
var obj_count=10; //Сколько у нас будет объектов
for (i=1;i<=obj_count;i++){ //Цикл от 1 до количества создаваемых объектов
document.write(’<div style=”left:’);
document.write(i*20+50);
document.write(’;top:50;”MsoBodyTextIndent” style=”margin-right: -2.75pt; text-indent: 0cm;”>
document.write(i);
document.write(’”>’+'</div>’);
//вот то самое место, именно тут мы вписываем в страницу 10 DIV-ов с классом obj1style идентификаторами с именами от obj1-1 до obj1-10. Все элементы располагаются на одной высоте, отступ от левой стороны страницы высчитывается по формуле i * 20 +50, где i-это номер элемента[1..10].
}
Вместо данного кода мы могли бы написать что-то вроде:
<div style=”left:70; top:50;” id=”obj1-1″></div>
…
<div style=”left:250; top:50;” id=”obj1-10″></div>
Но это неэффективно и некрасиво да и лень мне писать так много.
I like to move it или анимируем!
Самое интересное. Итак, для начала зададимся целью, что необходимо сделать. Я решил начать с простого – объекты будут летать в неком сосуде и при ударении об стенку они будут отскакивать
* Комментарий автора
кстати данную модель можно было бы назвать “Модель идеального газа”, ибо данная анимация действительно в некой степени моделирует поведение частиц идеального газа.
Для начала после строки:
var obj_count=10;
добавим следующий код:
var VX= new Array(obj_count);
var VY= new Array(obj_count);
document.write(’<div style=”position:absolute; left:45; top:40; width:300; height:300; background:#bbbbbb;” id=”Area”></div>’);
Первые две строки создают два массива длиной 10 элементов, которые хранят значение скорости для каждого объекта. Третья же строка размещает на страничке “сосуд”, в котором будут летать наши “молекулы”, тут, в принципе, я думаю все знакомо и просто. Далее после строк начальной инициализации:
document.write(’;top:50;” id=”obj1-’);
document.write(i);
document.write(’”>’+'</div>’);
добавляем код, который будет задавать случайную скорость для каждого объекта
VX=Math.round(Math.random()*6)-3; if(VX==0){VX=1;}
VY=Math.round(Math.random()*6)-3; if(VY==0){VY=1;}
Условия “if” нужны для того что бы не было нулевых скоростей, то есть что бы небыло неподвижных объектов. Теперь перейдем к самому сердцу нашего аниматора- функции Timer она периодически ( с интервалом 50мс вызывает функцию Animate, но о ней позже.
Функция выглядит так:
function Timer()
{
setTimeout( function(){Animate();setTimeout(arguments.callee, 50);}, 0);
}
Эта функция должна вызываться(стартовать) при загрузке страницы для того что бы обеспечить такую возможность мы подправим тег body, итоговый вид после изменений показан ниже:
<body onload=”Timer()”>
Вроде разобрались, пора браться за последнюю в данном уроке и самую массивную функцию – функцию Animate, вот полный ее текст:
function Animate()
{
for(i=1;i<=obj_count;i++){
document.getElementById(”obj1-”+i).style.left=parseInt(document.getElementById(”obj1-”+i).style.left)+VX;
document.getElementById(”obj1-”+i).style.top=parseInt(document.getElementById(”obj1-”+i).style.top)+VY;
if( parseInt(document.getElementById(”obj1-”+i).style.left) <= parseInt(document.getElementById(”Area”).style.left) ){
document.getElementById(”obj1-”+i).style.left=document.getElementById(”Area”).style.left;
VX*=-1;
}
if( parseInt(document.getElementById(”obj1-”+i).style.left)+10 >= parseInt(document.getElementById(”Area”).style.left)+parseInt(document.getElementById(”Area”).style.width) ){
document.getElementById(”obj1-”+i).style.left=parseInt(document.getElementById(”Area”).style.left)+parseInt(document.getElementById(”Area”).style.width)-10;
VX*=-1;
}
if( parseInt(document.getElementById(”obj1-”+i).style.top) <= parseInt(document.getElementById(”Area”).style.top) ){
document.getElementById(”obj1-”+i).style.top=document.getElementById(”Area”).style.top;
VY*=-1;
}
if( parseInt(document.getElementById(”obj1-”+i).style.top)+10 >= parseInt(document.getElementById(”Area”).style.top)+parseInt(document.getElementById(”Area”).style.height) ){
document.getElementById(”obj1-”+i).style.top=parseInt(document.getElementById(”Area”).style.top)+parseInt(document.getElementById(”Area”).style.height)-10;
VY*=-1;
}
}
}
Как она работает? Очень просто! В цикле мы пробегаем по всем элементам, при этом выполняем следующие действия:
1. Прибавляем к X координате объекта его горизонтальную скорость, которую берем из массива VX.
2. Прибавляем к Y координате объекта его вертикальную скорость, которую берем из массива VY.
3. Проверяем, не вышел ли левый край объекта за левый край сосуда, если вышел, то ставим объект на границу и инвертируем скорость. Т.е. если летел, к примеру, шарик и он вылетел за левую границу, то мы возвращаем шар на край сосуда и пинаем его в обратную сторону, так, что модуль его горизонтальной скорости не меняться- меняется только знак
4. Аналогично, только мы проверяем вылет за правую границу.
5. В принципе тоже самое, только здесь все происходить в вертикальной плоскости- мы определяем вылет за верхнюю границу и если таковой факт был инвертируем вертикальную составляющую скорости.
6. Аналогично действию №5, за исключением того, что проверка происходит на вылет за нижнюю грань.
Смело копируем функцию перед функцией Timer() и пробуем запустить нашу маленькую демонстрацию (см. рисунок).
Заключение
В этой статье я попытался объяснить базовые принципы анимации с помощью JavaScript. Если Вам что-то непонятно, то не расстраивайтесь в следующих уроках курса я на более интересных и практичных примерах покажу как делать практичные вещи, которые будет не стыдно поставить на свой сайт а пока экспериментируйте! Также хотелось бы написать статью о оптимизации кода, ибо наш вариант далеко не идеален, поэтому ждите.
Если возникнут какие-либо вопросы то обращайтесь на форум www.programmersforum.ru, либо пишите на почтовый ящик редакции. Я или кто-либо другой попытаемся Вам помочь.
Статья из четвертого выпуска журнала “ПРОграммист”.
Скачать этот номер можно по ссылке.
Ознакомиться со всеми номерами журнала.
23rd
Июн
GAMEDEV на Delphi. Делаем змейку
Вэтой статье я подробно рассмотрю создание игры «Змейка» с использованием ООП (объектно-ориентированного программирования). Делать игру мы будем средствами GDI на Delphi 7 (хотя должна подойти и любая другая версия). Для полного осознания того, о чем говорится в статье желательно иметь хоть какой-нибудь опыт программирования, знать что такое Сanvas и TBitmap, что такое классы и в чем заключаются их особенности.
Вадим Буренков
Вообще, при создании любой игры можно выделить три момента:
- Создание идеи, примерный план реализации. При желании разработчиком пишется «диздок» (дизайн-документ — это документ, который описывает то, что вы хотите создать).
- Алгоритмизация. То есть мы представляем план строения игры, какие типы данных будут использоваться. Обдумываются алгоритмы работы и взаимодействия элементов игры.
- Все выше мы записываем в виде кода, то есть программируем.
В принципе, игра не использует наследования, поэтому можно было обойтись и без классов, но мне захотелось сделать так.
Краткий экскурс…
При создании любой игры нужно придерживаться определенного строения кода.
Каждая игра имеет следующие секции:
- инициализация (тут происходит настройка окна для рендера (вывода изображения), загрузка всех игровых ресурсов и уровня)
- обработка (всех игровых объектов, их взаимодействие друг с другом. Эта секция в отличие от инициализации и деинициализации выполняется постоянно, причем скорость выполнения измеряется в миллисекундах и обычно** равна 10-100 мс)
- деинициализация (выполняется при завершении работы приложения и служит для очищения занятой приложением памяти)
Обратите внимание! Компонент TTimer имеет низкую точность. Ставить на нем низкие значения (менее 30 мс) не имеет смысла.
Сделаем основу для игры
Откройте Delphi, создайте новое приложение. Сразу хочу заметить, что именам всех переменных, окон, файлов, и.т.п. надо давать осмысленные имена. Конечно, кто-то скажет что и так запомнит что такое Form1 и Label2, но когда пишется проект не на 10 а на 10000 строк и используется не одна, а пятнадцать форм… Также не надо мелочиться на комментарии, поскольку смысл определенного кода через месяц простоя не поймет и тот, кто его написал, не говоря уже о том – что вам может понадобиться сторонняя помощь.
Назовем форму MainForm, модуль GameMain, а проект SnakeProject. Поскольку эта игра простая и небольшая, то я буду использовать один модуль, но в остальных случаях рекомендуется использовать свой модуль для отдельной игровой части. Создадим два события формы: OnCreate и OnDestroy которые и будут инициализатором и деинициализатором игры. За обработку игры будет отвечать компонент-таймер TTimer (поместите его на форму из вкладки System, назовем его MainTimer), а именно его событие – OnTimer.
Я не буду делать игру очень гибкой в плане игрового поля (его размеры нельзя будет менять. Поле будет 16х12 клеток, но о них будет написано ниже, основные параметры будут константами. Игровое же поле 640х480 по центру экрана без возможности перемещения (без синей рамки). Объявим константы:
SCR_WIDTH=640;
SCR_HEIGHT=480;
Можно не выставлять параметры окна в среде разработки, а написать их в коде (событие OnCreate):
MainForm.Width := SCR_WIDTH;
MainForm.Height:= SCR_HEIGHT;
// параметры формы
MainForm.BorderStyle:= bsNone; // без рамки
MainForm.Position := poDesktopCenter; // по центру экрана
Теперь заставим работать таймер (изначально параметр таймера должен быть enable = false):
MainTimer.Enabled:= true;
Создадим и добавим в событие OnTimer две процедуры (пока пустые) – Main_Update() и Main_Draw(). Для чего это? Мы ведь можем писать весь код напрямую в таймер. Поясню. Например, если мы захотим сделать в игре меню, то должны быть отдельные процедуры обработки/рисования для меню и игры и выполняться они будут в зависимости от того, где игрок находится:
if Scene = sMenu then Menu_Draw;
if Scene = sGame then Main_Draw;
Отрисовка
В этой статье я не буду делать меню, но все же. Итак, мы получили некую заготовку игры. Теперь надо правильно настроить рендер. Canvas отличается простотой, но его недостаток – медленная скорость. Если просто выводить спрайты (изображения) на форму, то будет видно – что они выводятся не сразу, а один за другим и появляется эффект мерцания изображения. Чтобы избежать этого – нужно выводить спрайты не на экран, а в буфер памяти. После того как финальное изображение будет построено, можно вывести его на форму. После вывода буфер можно очистить и он будет готов для построения следующего кадра.
Теперь реализуем данную систему. Объявим переменную – буфер scr_Buffer как Tbitmap. Перед использованием, буфер нужно проинициализировать и установить размеры:
scr_Buffer:= TBitmap.Create;
scr_Buffer.Width := SCR_WIDTH;
scr_buffer.Height:= SCR_HEIGHT;
И в событии Main_Draw() напишем код отрисовки буфера (все рисование должно происходить до этих строчек):
scr_Buffer.Canvas.Rectangle(0, 0, SCR_WIDTH, SCR_HEIGHT); // очищаем буфер ***
Тут же можем проверить, как наша заготовка справляется с рисованием. Загрузим фон для игры в переменную BackImage:
BackImage:= Tbitmap.Create;
BackImage.LoadFromFile(‘fon.bmp’);
// отрисовка
scr_Buffer.Canvas.Draw(0 ,0, BackImage);
// очищение
BackImage.Free;
Нажимаем клавишу <F9> и любуемся на результат (см. рис.1):
Рис. 1. Вывод фоновой картинки игры
Концепция игры. Долгосрочная стратегия
Итак, мы сделали «скелет» игры, на который будем насаживать игровой код. Обдумаем, что же будет представлять из себя игра? Есть змейка, она ползет по уровню, маневрирует между препятствиями и ест все съедобное на ее пути. Но змея не должна есть стены и себя, иначе она погибнет. Из выше написанного подобия «диздока» можно выделить два класса, которые мы будем использовать в игре: змея (назовем ее TSnake) и остальные объекты (TGameObject). Объекты будут иметь переменную, которая определяет их тип: либо это еда, либо препятствие. Каждый объект имеет координату. Поле будет поделено на клетки, это позволит легко определять столкновения (если координаты двух объектов совпадают, то они столкнулись). Размер каждой клетки будет определяться константой CAGE_SIZE = 40. Зная координату клетки можно получить координату на экране, умножив ее на размер клетки. Это нам понадобится при рисовании****. Эти вспомогательные функции делают следующие:
begin
result:= x*CAGE_SIZE
end;
Function G_Y(Y:integer):integer;
begin
result:= Y*CAGE_SIZE
end;
Программируем поведение змейки
Обдумаем, как же будет работать змейка. Каждая деталь змейки – это отдельный элемент со своими координатами. Когда змейка движется – первый элемент (голова) занимает новую позицию, а второй элемент занимает позицию предыдущего до перемещения (там, где раньше была голова), третий позицию второго и.т.д. Это самый логичный вариант, который приходит в голову. Но тогда надо сохранять координаты, где было тело змейки. Для упрощения кода есть более легкий способ – двигать змейку с хвоста. Перейдем к созданию класса змейки:
TSnake=class
Sprite:TBitmap; // спрайт составной части змейки
len:integer; // длина змейки (из какого кол-ва частей состоит змейка)
BodyPos: array of vect2D; // динамический (!) массив с координатами частей змейки
Speed:vect2D; // скорость змейки (сколько клеток она проходит за ход)
MoveTo:(left,right,up,down); // показатель направления движения
Constructor Create(spr_name:string;pos:vect2D;size:integer); // создание змейки со спрайтом,
// позицией и длиной
Procedure SetSize(size:integer);// изменение размера
Procedure Drive; // управление змейкой
Procedure Update; // один ход змейки
Procedure Draw; // отрисовка змейки
Destructor Free; // удаление змейки
end;
Как и в любой игре, в этой нельзя обойтись без математики. Тип «Vect2D» облегчает нам задачу хранения координат и взаимодействия с ним:
vect2D = record
X,Y: integer;
end;
Function v2(x,y:integer):vect2D; // создание
begin
result.X:= X;
result.Y:= Y
end;
Function v2ident(v1,v2:vect2D):boolean; // проверка одинаковых координат
begin
result:= false;
if (v1.x=v2.x) and (v1.y=v2.y) then result:=true
end;
Function v2add(v1,v2:vect2D):vect2D; // сложение координат
begin
result.X:= v1.x+v2.x;
result.Y:= v1.y+v2.y
end;
И вот код всех процедур класса:
Constructor TSnake.Create(spr_name:string;pos:vect2D;size:integer);
Begin
Sprite:= TBitmap.create; // загружаем спрайт
Sprite.LoadFromFile(spr_name);
Sprite.Transparent:= true;
SetSize(size); // установка длины
BodyPos[0]:= pos; // установка положения
MoveTo:= left; // изначальное направление
end;
// установка длины массива частей змейки:
Procedure TSnake.SetSize(size:integer);
begin
len:=size;
SetLength(BodyPos, len)
end;
// вот тут самое сложенное и интересное. Относительно MoveTo устанавливается скорость змейки
// и происходит сдвиг змейки в сторону движения
procedure TSnake.Update;
var i: integer;
begin
if MoveTo=Up then Speed:=v2( 0,-1);
if MoveTo=Down then Speed:=v2( 0, 1);
if MoveTo=Left then Speed:=v2(-1, 0);
if MoveTo=right then Speed:=v2( 1, 0);
// двигаем хвост (downto означает, что мы идет от большего к меньшему)
for i:= len-1 downto 1 do
BodyPos[i]:= BodyPos[i-1];
// двигаем первый элемент (голову) прибавляя скорость
BodyPos[0]:= v2add(BodyPos[0], Speed);
// проверка столкновения головы змейки с хвостом
for i:=1 to len-1 do if v2Ident(BodyPos[0],BodyPos[i]) then TryAgain;
end;
TryAgain() – процедура вызываемая при проигрыше. К ней вернемся позже. В следующей процедуре мы управляем змейкой, задавая ее направление движения через MoveTo(). Это отдельная от Tsnake.Update процедура, поскольку Update() будет вызываться реже таймера, чтобы контролировать скорость движения змейки. При этом, ловить нажатые клавиши управления надо постоянно. Определение нажатых клавиш происходит через функцию Key_Press():
var
keys: TKeyboardState;
begin
result:=false;
GetKeyboardState(keys);
If (keys[key] = 128) or (keys[key] = 129) then result:= true
end;
Коды клавиш можно определить специальной программой [2]. Те, что нам понадобятся, я занес в константы:
KEY_UP= 38;
KEY_DOWN= 40;
KEY_LEFT= 37;
KEY_RIGHT= 39;
KEY_ESC= 27;
Поскольку змейка может только поворачивать влево и вправо, а не может изменять направления движения мгновенно на 180 градусов, введены проверки типа v2Ident(v2(BodyPos[0].x,BodyPos[0].y-1),BodyPos[1])=false, которые не позволяют ей этого сделать:
begin
if Key_Press(KEY_UP) then if v2Ident(v2(BodyPos[0].x,BodyPos[0].y-1),BodyPos[1])=false then MoveTo:=Up;
if Key_Press(KEY_DOWN) then if v2Ident(v2(BodyPos[0].x,BodyPos[0].y+1),BodyPos[1])=false then MoveTo:=Down;
if Key_Press(KEY_LEFT) then if v2Ident(v2(BodyPos[0].x-1,BodyPos[0].y),BodyPos[1])=false then MoveTo:=Left;
if Key_Press(KEY_RIGHT) then if v2Ident(v2(BodyPos[0].x+1,BodyPos[0].y),BodyPos[1])=false then MoveTo:=Right;
end;
И две последние процедуры. Отрисовка отобразит все части змейки по координатам (голова отрисовывается два раза для наглядности):
var i: integer;
begin
for i:=0 to len-1 do if not v2Ident(BodyPos[i],v2(0,0)) then begin
// не отрисовываются части с нулевыми координатами,
// так как их имеют новые части змейки до движения
if i=0 then scr_Buffer.Canvas.draw(G_X(BodyPos[i].x)+Speed.x*5,G_Y(BodyPos[i].y)+Speed.y*5,sprite);
scr_Buffer.Canvas.draw(G_X(BodyPos[i].x),G_Y(BodyPos[i].y),sprite);
end
end;
// при удалении змейки очищается спрайт
destructor TSnake.Free;
begin
Sprite.Free;
end;
Рис. 2. Отображение нашей змейки
Объекты в игре. Не дадим змейке «помереть с голоду»
Теперь необходимо добавить объекты. Они будут двух типов:
TObjType=(oWall,oFood); // стены и еда
type
TgameObject = class
ObjType:TObjType; // тип объекта
Sprite:TBitmap; // спрайт
Pos:vect2D; // положение
Constructor Create(spr_name:string;oType:TObjType;p:vect2D); // создание
procedure Update(Snake:TSnake); // обновление (проверка столкновения со змеей)
procedure Draw; // отрисовка
destructor Free; // удаление
end;
Тут ничего сложного нет. Многие процедуры аналогичны змейке:
begin
Sprite:=TBitmap.create;
Sprite.LoadFromFile(spr_name);
Sprite.Transparent:=true;
pos:= p;
ObjType:= oType
end;
Procedure TGameObject.Update(Snake:TSnake);
begin
if v2Ident(Snake.BodyPos[0],pos) then begin // если змея столкнулась с объектом
if ObjType = oWall then TryAgain;
if ObjType = oFood then begin Snake.SetSize(Snake.len+1);
pos:= RandomFieldPos
end;
// увеличиваем размер змеи, перемещаем еду в другое место
// что такое RandomFieldPos см. ниже
end;
end;
Procedure TGameObject.Draw;
begin
scr_Buffer.Canvas.Draw(G_X(pos.x),G_Y(pos.y),sprite)
end;
Destructor TGameObject.Free;
begin
Sprite.Free
end;
Используем классы в игре
Итак, компоненты игры написаны. Теперь их надо создать, добавить обработку и отрисовку в Main_Update() и Main_Draw(). Вот полный код инициализации змейки и стен. Объявим следующие переменные:
Wall: array of TGameObject; // все стены игры
WallNum:integer; // их кол-во
Food:TGameObject; // еда для змейки
И добавим в инициализацию следующий код:
MySnake:= TSnake.Create(‘snake.bmp’,v2(7,7),2);
// теперь создам еду. А на ужин у нас аппетитненький пингвинчик
Food:=TGameObject.Create(‘food.bmp’,oFood,RandomFieldPos);
Чтобы не вводить координату каждой стенки уровня, так как загрузчика уровней нет, я просчитал создание всех стен в операторе for:
SetLength(Wall,WallNum);
for i:=0 to 15 do Wall[i] := TGameObject.Create(‘wall.bmp’, oWall, v2(i,0)); // верхняя
for i:=16 to 31 do Wall[i]:= TGameObject.Create(‘wall.bmp’, oWall, v2(i-16,11)); // нижняя
for i:=32 to 42 do Wall[i]:= TGameObject.Create(‘wall.bmp’, oWall, v2(0,i-32)); // левая
for i:=43 to 53 do Wall[i]:= TGameObject.Create(‘wall.bmp’, oWall, v2(15,i-43)); // правая
Выше я писал о функции RandomFieldPos(), которая возвращает случайную координату на поле, где нет стен. В OnCreate() надо поставить randomize() для инициализации генератора случайных чисел:
begin
result:=v2(random(13)+1,random(9)+1); // я просчитал допустимые значения
// по X от 1 до 14, по Y от 1 до 10
// тут ничего сложного нет.
end;
Собираем запчасти
Теперь надо добавить нашу змейку, стены и пингвинчика в обработку и отрисовку. Поскольку скорость движения змейки надо ограничить, мы заводим переменную-счетчик WaitTime. Она считает до 5 и выполняет процедуру движения и сбрасывает себя на 0. В итоге, MySnake.Update() срабатывает в 5 раз реже таймера. Ошибкой многих начинающих разработчиков является использование большого количества таймеров, что сильно усложняет код. Чтобы из игры можно было нормально выйти, сделаем условие нажатия на клавишу <ESCAPE>:
var i: integer;
begin
MySnake.Drive;
for i:=0 to WallNum-1 do wall[i].Update(MySnake);
Food.Update(MySnake);
if WaitTime>5 then begin MySnake.Update; WaitTime:= 0; end else inc(WaitTime);
if Key_Press(KEY_ESC) then Application.Terminate
end;
procedure Main_Draw;
var i: integer;
begin
scr_Buffer.Canvas.Draw(0, 0, BackImage);
MySnake.Draw;
for i:=0 to WallNum-1 do Wall[i].Draw;
Food.Draw;
MainForm.Canvas.Draw(0, 0, scr_Buffer);
scr_Buffer.Canvas.Rectangle(0, 0, SCR_WIDTH, SCR_HEIGHT);
end;
Напишем «правильное очищение» всех ресурсов игры:
var i: integer;
begin
scr_Buffer.Free;
BackImage.Free;
MySnake.Free;
for i:=0 to wallnum-1 do wall[i].Free
end;
После чего остается процедура «проигрыша» TryAgain(). В ней сбрасывается длина змейки, а сама она перемещается на стартовую позицию:
begin
MySnake.SetSize(0);
MySnake.SetSize(2);
MySnake.BodyPos[0]:= v2(7,7);
MySnake.MoveTo:= left;
Food.Pos:=RandomFieldPos
end;
Размер устанавливается два раза: первый для того, чтобы сбросить координаты частей змейки на нулевые значения, второй для установки начальной длины. И вот результат (см. рис.3):
Рис. 3. Результат наших трудов. Любуемся игрой
Заключение
Теперь можно запустить игру и наслаждаться результатом. Посмотрев на финальный код можно увидеть, что разработка простой игры не требует каких-либо особых усилий и знаний. Мной эта игра для статьи была написана за 2-3 часа. Весь исходный код проекта, а также текстуры, используемые для создания игры можно найти на http://www.programmersforum.ru в разделе «Журнал клуба программистов. Второй выпуск».
Обсудить на форуме – GAMEDEV на Delphi. Делаем змейку
Статья из второго выпуска журнала «ПРОграммист».
15th
Июн
3D НЕБО в DELPHI
Здравствуйте уважаемые программисты, я пишу 3D движок и столкнулся с проблемой, как делать небо?
Если можно, то скажите как рисуется небо в авиа симуляторах?
Метод CubeMap, берешь 6 картиночек CubeMap, грузишь их при создании сцены и обрабатываешь типа:
Код:
procedure DrawSkyBox; begin glColor3f(0.3,0.3,0.3); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
glPushMatrix; glTranslatef(Scene1.Player.x,Scene1.Player.y,Scene1.Player.z);
glBindTexture(GL_TEXTURE_2D,Tex[SB_BACK]); glBegin(GL_QUADS); glTexCoord2f(1, 0); glVertex3f(-SkyRad, -SkyRad, SkyRad); glTexCoord2f(0, 0); glVertex3f(SkyRad, -SkyRad, SkyRad); glTexCoord2f(0, 1); glVertex3f(SkyRad, SkyRad, SkyRad); glTexCoord2f(1, 1); glVertex3f(-SkyRad, SkyRad, SkyRad); glEnd;
glBindTexture(GL_TEXTURE_2D,Tex[SB_FRONT]); glPushMatrix; glRotatef(180, 0, 1, 0); glBegin(GL_QUADS); glTexCoord2f(1, 0); glVertex3f(-SkyRad, -SkyRad, SkyRad); glTexCoord2f(0, 0); glVertex3f(SkyRad, -SkyRad, SkyRad); glTexCoord2f(0, 1); glVertex3f(SkyRad, SkyRad, SkyRad); glTexCoord2f(1, 1); glVertex3f(-SkyRad, SkyRad, SkyRad); glEnd; glPopMatrix;
glBindTexture(GL_TEXTURE_2D,Tex[SB_LEFT]); glPushMatrix; glRotatef(-90, 0, 1, 0); glBegin(GL_QUADS); glTexCoord2f(1, 0); glVertex3f(-SkyRad, -SkyRad, SkyRad); glTexCoord2f(0, 0); glVertex3f(SkyRad, -SkyRad, SkyRad); glTexCoord2f(0, 1); glVertex3f(SkyRad, SkyRad, SkyRad); glTexCoord2f(1, 1); glVertex3f(-SkyRad, SkyRad, SkyRad); glEnd; glPopMatrix;
glBindTexture(GL_TEXTURE_2D,Tex[SB_RIGHT]); glPushMatrix; glRotatef(90, 0, 1, 0); glBegin(GL_QUADS); glTexCoord2f(1, 0); glVertex3f(-SkyRad, -SkyRad, SkyRad); glTexCoord2f(0, 0); glVertex3f(SkyRad, -SkyRad, SkyRad); glTexCoord2f(0, 1); glVertex3f(SkyRad, SkyRad, SkyRad); glTexCoord2f(1, 1); glVertex3f(-SkyRad, SkyRad, SkyRad); glEnd; glPopMatrix;
glBindTexture(GL_TEXTURE_2D,Tex[SB_TOP]); glPushMatrix; glRotatef(-90, 1, 0, 0); glBegin(GL_QUADS); glTexCoord2f(0, 0); glVertex3f(-SkyRad, -SkyRad, SkyRad); glTexCoord2f(0, 1); glVertex3f(SkyRad, -SkyRad, SkyRad); glTexCoord2f(1, 1); glVertex3f(SkyRad, SkyRad, SkyRad); glTexCoord2f(1, 0); glVertex3f(-SkyRad, SkyRad, SkyRad); glEnd; glPopMatrix;
glBindTexture(GL_TEXTURE_2D,Tex[SB_BOTTOM]); glPushMatrix; glRotatef(90, 1, 0, 0); glBegin(GL_QUADS); glTexCoord2f(1, 1); glVertex3f(-SkyRad, -SkyRad, SkyRad); glTexCoord2f(1, 0); glVertex3f(SkyRad, -SkyRad, SkyRad); glTexCoord2f(0, 0); glVertex3f(SkyRad, SkyRad, SkyRad); glTexCoord2f(0, 1); glVertex3f(-SkyRad, SkyRad, SkyRad); glEnd; glPopMatrix; glPopMatrix; end;
end.
А т.н. окружение сцены можно делать просто скроллом картинок по небу, можно добавить 3Д анимацию. много можно чего…
14th
Июн
Анимация на ассемблере.
Смотрим идущего человечка.
code segment
ASSUME CS:code, DS:DATA_SEG, ES:NOTHING, SS:STACK_SEG
start:
MOV AX,DATA_SEG
MOV DS,AX
MOV AX,STACK_SEG
MOV SS,AX
MOV SP, OFFSET TOP_OF_STACK
MOV AX,13h
INT 10h
MOV AX,0A000h
MOV ES,AX
CLD
MOV DI,320*12
MOV BH, 48
MOVE_MASK:
MOV AL, NOM_KADR
CMP AL,0
JNZ KADR2
MOV NOM_KADR, 1
MOV SI, OFFSET H1
JMP SHOW_KADR
KADR2:
MOV NOM_KADR, 0
MOV SI, OFFSET H2
SHOW_KADR:
PUSH DI
MOV BL,13
DRAW_H:
MOV CX,12
REP MOVSB
ADD DI,320-12
DEC BL
JNZ DRAW_H
POP DI
INC DI
READ_KEY:
MOV AH,08h
INT 21h
cmp al,0 ; Проверка на расш.коды
JZ READ_KEY
cmp al, 'q'
JNZ MOVE_MASK
MOV AX,3
INT 10h
MOV AH,4Ch
INT 21h
DATA_SEG SEGMENT
H1 DB 0,0,0,0,0,9,9,9,9,0,0,0
DB 0,0,0,0,0,9,9,9,9,0,0,0
DB 0,0,0,0,0,9,9,0,0,0,0,0
DB 0,0,0,9,9,9,9,9,9,0,0,0
DB 0,0,0,9,9,9,9,9,9,0,0,0
DB 0,9,9,0,0,9,9,0,0,9,9,0
DB 0,9,9,0,0,9,9,0,0,9,9,0
DB 0,0,0,0,0,9,9,0,0,0,0,0
DB 0,0,0,0,0,9,9,0,0,0,0,0
DB 0,0,0,9,9,0,0,9,9,0,0,0
DB 0,0,0,9,9,0,0,9,9,0,0,0
DB 0,9,9,0,0,0,0,0,0,9,9,0
DB 0,9,9,0,0,0,0,0,0,9,9,0
H2 DB 0,0,0,0,0,9,9,9,9,0,0,0
DB 0,0,0,0,0,9,9,9,9,0,0,0
DB 0,0,0,0,0,9,9,0,0,0,0,0
DB 0,0,0,9,9,9,9,9,9,0,0,0
DB 0,0,0,9,9,9,9,9,9,0,0,0
DB 0,9,9,0,0,9,9,0,0,9,9,0
DB 0,9,9,0,0,9,9,0,0,9,9,0
DB 0,0,0,0,0,9,9,0,0,0,0,0
DB 0,0,0,0,0,9,9,9,9,0,0,0
DB 0,0,0,9,9,0,0,9,9,0,0,0
DB 0,0,0,9,9,0,0,9,9,0,0,0
DB 0,9,9,0,0,0,0,9,9,0,0,0
DB 0,9,9,0,0,0,0,0,0,0,0,0
NOM_KADR DB 0
DATA_SEG ENDS
STACK_SEG SEGMENT
DB 64 DUP('STACK')
TOP_OF_STACK DW ?
STACK_SEG ENDS
code ends
end start
end
30th
Май
Как работать с графикой на канве в среде Дельфи
Здравствуйте, уважаемые читатели. Как и обещал, сегодня с вами мы подробно рассмотрим процедуры работы с графическими объектами, вынесенными в отдельный модуль, позволяющий использовать универсальные методы для создания движущихся изображений, находящихся в файлах, обычно в виде спрайтов…
Продолжение. Начало цикла смотрите в первом и втором выпусках журнала…
Владимир Дегтярь
DeKot degvv@mail.ru
Создание проекта с несколькими движущимися объектами. Урок 5
Создадим новый проект <Lesson 3> аналогично предыдущим. Введем в него новые движущиеся графические объекты (в папке <data> добавлено еще четыре звездолета. Размер каждого спрайта 100х80 pix. Новые звездолеты будут появляться по случайному закону (используем функцию Randomize) и двигаться будут сверху вниз. Вывод фона и основного звездолета ’ship1′ осуществляется также как и в предыдущем проекте <Lesson 2>. Для новых объектов вводим дополнительно BufShipR и BufPicR. Также объявим новые переменные – координаты вывода новых звездолетов и приращения этих координат.
Одновременно у нас будут отображаться основной звездолет ’ship 1′ и два из ’ship 2′ – ’ship 5′, выбираемые по случайному закону (см. листинг 1):
ЛИСТИНГ 1
Var
Form1: TForm1;
BufFon,BufFonDop,Buffer: TBitMap;
BufShip1,BufShipR: TBitMap; // буферы спрайтов
BufPicS1,BufPicR: TBitMap; // буферы изображений одного спрайта
xf,yf: integer; // координаты вывода общего буфера на форму
dyf: integer; // приращение изменения координаты yf по вертикали
xS1,yS1: integer; // координаты звездолета 'ship1'
dxS1,dyS1: integer; // приращение координат 'ship1' по гориз. и вертик.
xR1,yR1,xR2,yR2: integer; // координаты звездолетов 'ship2 - ship5'
dyR1,dxR2,dyR2: integer; // приращение координат 'ship2 - 5'
ns,nr1: byte; // номер спрайта и выбор ship2 - ship5
nr2: byte = 3;
implementation
В процедуре OnCreate формы проведем инициализацию буферов и введем начальные данные для переменных. Процедура DrawShipR(i,j: byte) для вывода новых объектов (’ship2′ – ’ship5′) имеет два
параметра: i (изменение номера рисунка в файле спрайтов) и j (переменная для номера файла спрайтов). Т.к. выбор файла спрайта происходит по передаваемому параметру j, то инициализация буфера BufShipR и загрузка в него файла спрайтов находится в процедуре DrawShipR(i,j: byte) (см. листинг 2):
ЛИСТИНГ 2
procedure DrawShip1(i: byte);
begin
// загрузка одного спрайта в буфер рисунка
BufPicS1.Canvas.CopyRect(bounds(0,0,BufPicS1.Width,
BufPicS1.Height),
BufShip1.Canvas,
bounds(i * 66,0,
BufPicS1.Width,
BufPicS1.Height));
BufPicS1.Transparent:= true; // зададим прозрачность фона рисунка спрайта
BufPicS1.TransparentColor:= BufPicS1.Canvas.Pixels[1,1];
end;
Все движения организованы в обработчике таймера. Звездолеты ’ship2′ — ’ship5′ выводятся в Buffer в координаты xR1, yR1 и xR2, yR2 вне видимого окна формы выше. В каждом такте таймера происходит приращение координат для одного dyR1 по вертикали, для другого dxR2 и dyR2 – по горизонтали и вертикали. После того, как объекты выходят за пределы видимого окна формы внизу, вызываются методы random( ) для задания новых координат xR1, xR2 и номера файла спрайта (nr1, nr2). Координаты yR1, yR2 привязаны к координате yS1 , так как ’ship1′ неподвижен в координатах окна формы. Функция
random ( 4 ) возвращает числа в дипазоне 0 .. 3, а файлы спрайтов встречных звездолетов имеют номера 2 .. 5. Поэтому в процедуре загрузки спрайтов BufShipR.LoadFromFile(’data/ship’ + IntToStr(j+2) + ‘.bmp’) номер загружаемого файла определяется как IntToStr(j + 2)… В остальном процедуры обработчиков таймера и нажатия клавиш не отличаются от проекта <Lesson 2>.
Использование универсального модуля для работы с графикой. Урок 6
Если рассмотреть внимательно код программы в проекте <Lesson 3>, можно заметить, что многие методы часто повторяются для разных графических объектов (создание буферов, загрузка изображений из файлов, копирование и т.п.). При этом для упрощения, я сознательно применил файлы спрайтов одинакового размера и с равным количеством рисунков в файлах. А если файлов спрайтов будет не пять, а больше и если количество рисунков в каждом файле будет разным? Придется значительно увеличивать код для
каждого вида спрайтов. Следовательно, необходимо оптимизировать код программы. Выход здесь в написании методов обработки объектов, не зависящих от количества объектов и применимых для разных изображений.
Даная задача реализована в отдельном модуле <LoadObjectToBufferMod>, позволяющий использовать универсальные методы для создания движущихся графических объектов (находящихся в файлах, обычно в виде спрайтов), имеющих различный размер и разное количество изображений отдельных рисунков.
Модуль находится в папке <Lesson 4> (см. ресурсы к статье). Принцип организации модуля следующий:
- вся работа с графическими объектами проводится через битовые образы TBitMap и области
копирования битовых образов TRect
- для работы с фоном используются процедуры InitFon (инициализация) и LoadFon (загрузка фона из
файлов)
- функция InitSprite предназначена для инициализации и загрузки рисунков спрайтов
- для вывода фона и изображений спрайтов использован общий буфер типа TBitMap
- в процедуре InitBuff происходит инициализация общего буфера, а в процедуре FreeBuff ”переустановка”,
т.е. уничтожение общего буфера и создание снова, но уже без изображений спрайтов
- в процедуре LoadBuff происходит наложение изображений спрайтов на фон
Подробно работа модуля показана ниже…
Применение модуля LoadObjectToBufferMod
1. procedure InitFon(nw, nh: byte; FileName: string)
Создаем дополнительный и основной буферы фона:
BufFonD:= TBitmap.Create;
BufFon := TBitmap.Create;
Далее загружаем рисунок одного из фонов в дополнительный буфер:
BufFonD.LoadFromFile(FileName) или
LoadFromResourceName(hinstance.filename);
По загруженному рисунку получаем размер одного рисунка фона (см. рисунок 1):
Причем, размер буфера фона определяем как:
WF:= nw * WFD;
HF:= nh * HFD;
2. procedure LoadFon(xf, yf: integer; FileName: string)
Загружаем все рисунки фонов в буфер фона через дополнительный буфер (см. рисунок 2):
BufFonD.LoadFromFile(FileName) или
LoadFromResourceName(hinstance.filename);
3. procedure initBuffer
Создаем основной буфер (Buffer) через который выводим спрайты на форму:
Buffer:= TBitmap.Create;
Размер основного буфера устанавливаем равным размеру буфера фона WF и HF. Загружаем в основной буфер весь фон (cм. рисунок 3):
Buffer.Canvas.Draw(0, 0, BufFon);
4. procedure FreeBuffer
Процедура уничтожаем основной буфер Buffer. Применяется когда необходимо убрать какой-либо спрайт с формы:
Buffer.Free;
Восстанавливаем-же основной буфер с фоном так:
InitBuffer;
Спрайты, которые должны оставаться на форме, следует перерисовать по новому (см. процедуру InitStprite)…
5. procedure InitStprite(SpriteName: string; N_goriz, N_vertic, N_stroka, N_kadr: byte): byte
Cоздаем буфер массива спрайтов и загружаем туда файл спрайтов (см. рисунок 4):
BufSprite:= TBitmap.Create; BufSprite.LoadFromFile(SpriteName) или LoadFromResourceName(hinstance.spritename);
Создаем буфер рисунка (одного спрайта):
BufPic:= TBitmap.Create;
Далее определяем размеры буферов массива спрайтов и буфера рисунка, а также области (типа TRoot) загрузки рисунка. Загружаем спрайт в буфер рисунка (см. рисунок 5):
BufPic.Canvas.CopyRect(RectPic.BufSprite.Canvas, rectSprite);
Задаем прозрачность рисунку:
BufPic.Transparent:= true;
Для вывода следующего спрайта функция возвращает (правильнее сказать – функция принимает значение = Result) значение следующего номера спрайта N_kadr:
Result:= N_kadr;
Уничтожаем буфер массива спрайтов
BufSprite.Free;
6. procedure LoadBuffer(xf, yf, xs, ys, bs: integer)
В этой процедуре на Buffer выводится участок фона с координатами ранее выведенного спрайта, а затем очередное положение спрайта. Определяем область фона и область дополнительного буфера (см. рисунок 6):
Выводим участок фона в буфер, т.е. затираем спрайт фоном (см. рисунок 7):
Выводим очередной спрайт в дополнительный буфер Buffer (см. рисунок 8):
Buffer.Canvas.StretchDraw(Bounds(xs, ys, WP + bs, HP + bs), BufPic);
Уничтожаем буфер рисунка:
BufPic.Free;
Далее в программе выводим дополнительный буфер Buffer на форму методом Draw:
Form1.Canvas.Draw(x, y, Buffer);
Битовые образы фона (BufFon и BufFonD инициализируются (создаются – Create) в программе проекта всего один раз при инициализации программы (обычно вызовом процедуры InitFon в событиях OnCreate или OnActivate). Методы InitBuff, FreeBuff, InitSprite, LoadBuf в программе вызываются неоднократно*. Соответственно и объекты Buffer, BufSprite, BufPic создаются многократно. Поэтому после окончания действия каждого из методов происходит уничтожение битовых образов Buffer, BufSprite, BufPic методом Free.
Комментарий автора.
Загрузку фона можно производить из n – количества файлов, c одинаковым размером не более 1024 х
1024. Для этого вызывать процедуру LoadFon n -раз для разных файлов FonName и изменяя
координаты xf и yf.
Модуль можно применять и для простых объектов (один рисунок в файле SpriteName). Присвойте
переменным N_goriz и N_vertic значения = 1. Если размер спрайта не изменяется, присвойте
переменной bs значение = 0.
Можно загружать рисунки из файлов .jpg. Для этого вместо TBitMap применять класс TJpegImage и в
разделах uses LoadObjectToBufferMod и uses Unit1 добавить модуль Jpeg.
Как работать с модулем?
Для этого необходимо выполнить следующие действия:
1. В процедуре FormActivate (можно в FormCreate) инициализируем буфер фона. Вызываем procedure InitFon(nw,nh: byte; FonName: string) с одним из файлов фонов:
. n раз вызываем procedure LoadFon(xf,yf: integer; FonName: string), последовательно прикрепляя рисунки фонов как бы друг к другу
. инициализируем дополнительный буфер Buffer, вызвав procedure InitBuff. Он получит размер равный сумме размеров всех файлов фонов.
2. Для вывода необходимых спрайтов в нужном месте программы вызываем function InitSprite(SpriteName: string; N_goriz,N_vertic,N_stroka, N_kadr: byte). Функция возвращает очередной номер спрайта для последующего вывода очередного спрайта. Этот номер (N_kadr) необходимо передавать в функцию при каждом ее вызове. Причем, функцию можно использовать для вывода нескольких спрайтов, не забывая передавать ей значение N_kadr для каждых спрайтов.
Далее, вызвав процедуру LoadBuff(xf,yf,xs,ys,bs: integer), выводим спрайт на канву дополнительного буфера поверх фона. Окончательный вывод дополнительного буфера на канву формы производим методом Draw().
Заключение
Рассматриваемые в данной статье проекты полностью приведены в виде ресурсов в теме «Журнал клуба программистов. Третий выпуск» или непосредственно в архиве с журналом (папка Lesson3). Продолжение наших уроков смотрите в следующем выпуске журнала «ПРОграммист»…
Комментарий автора.
Перед запуском в среде Дельфи скопируйте в папку с проектом папку data с графическими файлами.
Это статья из третьего номера журнала “ПРОграммист”.
Скачать его можно по ссылке.
Ознакомиться со всеми номерами журнала.
Обсудить на форуме — Как работать с графикой на канве в среде Дельфи
24th
Май
Все стили линий canvas
psSolid Сплошная линия
psDash Штриховая линия
psDot Пунктирная линия
psDashDot Штрих-пунктирная линия
psDashDotDot Линия, чередующая штрих и два пунктира
psClear Отсутствие линии
psInsideFrame Сплошная линия, но при Width > 1 допускающая цвета, отличные от палитры Windows
17th
Май
Движение бильярдного шара
Простенькая наработка для имитации движения бильярдного шара.
uses crt,graph;
var gd,gm:integer;
dx,dy:integer;
x1,y1,radius:integer;
begin
gd:=detect;
initgraph(gd,gm,' ');
setcolor(green);
rectangle(10,10,610,460);
x1:=50;y1:=200;
radius:=10;
dx:=3;dy:=2;
setcolor(yellow);
circle(x1,y1,radius);
repeat
setcolor(0);
circle(x1,y1,radius);
if x1>610 then dx:=-dx;
if x1<10 then dx:=-dx;
if y1>470 then dy:=-dy;
if y1<10 then dy:=-dy;
x1:=x1+dx;y1:=y1+dy;
setcolor(yellow);
circle(x1,y1,radius);
delay(10000);
until (x1-10<=10) and (y1+10>=47) or
(x1-10<=10) and (y1-10<=10) or
(x1+10>=610) and (y1-10<=10) or
(x1+10>=610) and (y1+10>=470);
readln;
closegraph;
end.
Облако меток
css реестр ассемблер timer SaveToFile ShellExecute программы массив советы word MySQL SQL ListView pos random компоненты дата LoadFromFile form база данных сеть html php RichEdit indy строки Win Api tstringlist Image мысли макросы Edit ListBox office C/C++ memo графика StringGrid canvas поиск файл Pascal форма Файлы интернет Microsoft Office Excel excel winapi журнал ПРОграммист DelphiКупить рекламу на сайте за 1000 руб
пишите сюда - alarforum@yandex.ru
Да и по любым другим вопросам пишите на почту
пеллетные котлы
Пеллетный котел Emtas
Наши форумы по программированию:
- Форум Web программирование (веб)
- Delphi форумы
- Форумы C (Си)
- Форум .NET Frameworks (точка нет фреймворки)
- Форум Java (джава)
- Форум низкоуровневое программирование
- Форум VBA (вба)
- Форум OpenGL
- Форум DirectX
- Форум CAD проектирование
- Форум по операционным системам
- Форум Software (Софт)
- Форум Hardware (Компьютерное железо)