Главная » Статьи » Программирование » Delphi, Pascal, ObjectPascal » Создание базового движка для игры. Часть 2. Анимация, Столкновения и воспроизведения музыки
|
Создание базового движка для игры. Часть 2. Анимация, Столкновения и воспроизведения музыки
[ Поделиться ]
[ Спасибо! ]
|
Давайте начнем с анимации. Откройте модуль uGraphics. В type добавьте следующую строчку:
Code
TAnimationMode = (amNone,amLoop,amPlayOnce);
это наш тип анимации: amNone значит нет анимации, amLoop анимация проигрывается бсконечно, amPlayOnce анимация проигрывается один раз... Теперь нам надо изменить класс TSprite добавив новые перменные и функции, таким образом класс TSprite будет выглядеть следующим образом:
Code
TSprite = class //наш спрайт =)
public
Position: TCoordinates; //координаты спрайта (X,Y)
Tag: Integer; //номер спрайта
Name: String; //имя спрайта
Destroy: Boolean; //если Destroy = true то спрайт рисоваться не будет
FramesCount: Integer; //число кадров
FrameID: Integer; //номер проигрываемого кадра
Frames: array[0..999] of TImage; //все кадры
Interval: Integer; //интервал между показами
CurrentFrame: TImage; //проигрываемый кадр
AnimationMode: TAnimationMode; //тип анимации
Other: TSprite; //столкнувшийся объект
function AddFrame: TImage;
procedure Update;
constructor Create; //конструктор класса
end;
Обратите внимание, теперь у нас нету переменной Iamge, следовательно нам нужно переписать конструктор класса TSprite и функцию обновления класса TViewer.
Code
//конструктор класса TSprite
constructor TSprite.Create; //конструктор спрайта
begin
inherited Create; //создаем спрайт
Frames[0]:= TImage.Create(nil); //создаем первый кадр
Frames[0].Visible:= false; //делаем его невилимым
Frames[0].AutoSize:= true; //узнаем размер кадра
CurrentFrame:= Frames[0]; //проигрываемый кадр равен нулевому кадру
FramesCount:= 1; //всего 1 кадр
FrameID:= 0; //номер проигрываемого кадра равен нулю
Interval:= 3; //интервал (задержка) вопроизведения анимации
end;
//функция обновления класса TViewer
procedure TViewer.Update; //обновление изображения
Var
i: Integer; //перменная i понадобится нам для цмкла for
begin
BackBuffer.Canvas.CopyRect(Rect(0,0,Width,Height),Canvas,Rect(0,0,Width,Height)); //копируем с основного экрана на задний буффер
BackBuffer.Canvas.Brush.Color:= $000000; //выбираем цвет для заливки
BackBuffer.Canvas.FillRect(Rect(0,0,Width,Height)); //заливаем экран
for i:= 0 to Scene.SpritesCount - 1 do //цикл для каждого спрайта на сцене
if Scene.Sprites[i].Destroy then //если объект не уничтожен
//то рисуем его в зависимости от положения камеры
BackBuffer.Canvas.Draw((Scene.Sprites[i].Position.X - (Scene.Sprites[i].Frames[0].Width div 2)) -
(Scene.Camera.Position.X - (Scene.Camera.Width div 2)),
(Scene.Sprites[i].Position.Y - (Scene.Sprites[i].Frames[0].Height div 2)) -
(Scene.Camera.Position.Y - (Scene.Camera.Height div 2)),
Scene.Sprites[i].CurrentFrame.Picture.Graphic);
Canvas.CopyRect(Rect(0,0,Width,Height),BackBuffer.Canvas,Rect(0,0,Width,Height)); //копируем все на основной экран
end;
думаю здесь все понятно, теперь опишем новые функции и процедуры для класса TSprite:
Code
function TSprite.AddFrame: TImage; //функция добавления кадра
begin
Frames[FramesCount]:= TImage.Create(nil); //создаем новый кадр
Frames[FramesCount].AutoSize:= true; //узнаем размер кадра
Result:= Frames[FramesCount]; //результат наш новый кадр
FramesCount:= FramesCount + 1; //число кадров стало на один больше
end;
procedure TSprite.Update; //обновление спрайта
begin
case AnimationMode of
amNone: //если нет анимации, то
begin
CurrentFrame:= Frames[0]; //текущий кадр равен нулевому кадру
FrameID:= 0; //номер кадра равен нулю
end;
amLoop: //если цикличная, то
begin
FrameID:= FrameID + 1; //текущей номер кадра больше на один
CurrentFrame:= Frames[FrameID div Interval]; //текущий кадр равен кдру с номером FrameID c задержкой Interval
//если кадр больше или равен чеслу кадров с задержкой Interval, то
if FrameID >= (FramesCount - 1) * Interval then FrameID:= 0; //текущей номер равен нулю
end;
amPlayOnce: //если надо проиграть один раз, то
if FrameID < (FramesCount - 1) * Interval then //проверяем, меньше ли значение FrameID
begin //и если меньше, то
FrameID:= FrameID + 1; //текущей номер кадра больше на один
CurrentFrame:= Frames[FrameID div Interval]; //текущий кадр равен кдру с номером FrameID c задержкой Interval
end;
end;
end;
Далее нам нужно создать процедуру Update в классе TScene:
Code
procedure TScene.Update; //процедура обновления сцены
Var
i: Integer;
begin
for i:= 0 to SpritesCount - 1 do
Sprites[i].Update; //обновляем каждый спрайт
end;
с анимацией кажись разобрались, теперь давайте проверим ее в деле Для начала подготовьте картинки для анимации, у меня это будет 16 картинок составляющих анимацию... Откройте модуль uMain и в обработчике событий удалите код добавляющий спрайты на сцену (если они у вас есть :)) и вставьте данный код:
Code
with GameViewer.Scene.AddSprite do //добавляем новый спрайт
begin //и выбираем его для изменения параметров
AnimationMode:= amLoop; //анимация цикличная
Frames[0].Picture.LoadFromFile('Data\Images\1.png'); //загружаем первый кадр
Frames[0].Transparent:= true; //устанавливаем прозрачность
for i:= 1 to 16 do //цикл для загрузки 16'ти кадров
with AddFrame do //добавляем новый кадр
begin
Picture.LoadFromFile('Data\Images\' + IntToStr(i) + '.png'); //загружаем картинку
Transparent:= true; //устанавливаем прозрачность
end;
end;
так же не забудьте в таймер вписать следующую строчку:
Code
GameScene.Update;
иначе анимация не будет воспроизводиться Я советую вам использовать формат png для ваших игр, т.к. картинка в формате png весит не много, и так же грузится из фала альфа текстура! скачать компонент для загрузки png картинок можно здесь: http://ifolder.ru/9783150
Если сейчас запустить приложение, то вы увидите как все работает =) наша картинка анимированная ура!!
Пора приступить к столкновениям создайте новый модуль и назовите его uCollisions я приведу полный код модуля:
Code
unit uCollisions;
interface
uses
uGraphics;
//функции обработки столкновений
function IsCollision(Scene: TScene; Obj1: TSprite; Tag: Integer): Boolean; overload;
function IsCollision(Scene: TScene; Obj1: TSprite; Obj2: TSprite): Boolean; overload;
implementation
//*Collisions*//
function IsCollision(Scene: TScene; Obj1: TSprite; Tag: Integer): Boolean;
Var
i: Integer;
Obj2: TSprite;
begin
Result:= False; //изначально мы результат равен False
//тоесть мы не счем не столкнулись
for i:= 0 to Scene.SpritesCount - 1 do //проверяем каждый объект
if Scene.Sprites[i].Tag = Tag then //если tag совпадает
if (not Obj1.Destroy) and (not Scene.Sprites[i].Destroy) then //и если объекты не уничтожен
begin
//то проверяем на столкновения
Obj2:= Scene.Sprites[i];
if (((Obj1.Position.X + Obj1.CurrentFrame.Width / 2) >= (Obj2.Position.X - Obj2.CurrentFrame.Width / 2)) and
((Obj1.Position.X - Obj1.CurrentFrame.Width / 2) <= (Obj2.Position.X + Obj2.CurrentFrame.Width / 2))) and
(((Obj1.Position.Y + Obj1.CurrentFrame.Height / 2) >= (Obj2.Position.Y - Obj2.CurrentFrame.Height / 2)) and
((Obj1.Position.Y - Obj1.CurrentFrame.Height / 2) <= (Obj2.Position.Y + Obj2.CurrentFrame.Height / 2))) then
begin
//и если столкнулся
Obj1.Other:= Obj2; //выбираем Other объект
Result:= True; //результат True
end;
end;
end;
function IsCollision(Scene: TScene; Obj1: TSprite; Obj2: TSprite): Boolean;
begin
Result:= False;//изначально мы результат равен False
//тоесть мы не счем не столкнулись
if (not Obj1.Destroy) and (not Obj2.Destroy) then // если объекты не уничтожен
//то, проверяем столкнулись ли объекты
if (((Obj1.Position.X + Obj1.CurrentFrame.Width / 2) >= (Obj2.Position.X - Obj2.CurrentFrame.Width / 2)) and
((Obj1.Position.X - Obj1.CurrentFrame.Width / 2) <= (Obj2.Position.X + Obj2.CurrentFrame.Width / 2))) and
(((Obj1.Position.Y + Obj1.CurrentFrame.Height / 2) >= (Obj2.Position.Y - Obj2.CurrentFrame.Height / 2)) and
((Obj1.Position.Y - Obj1.CurrentFrame.Height / 2) <= (Obj2.Position.Y + Obj2.CurrentFrame.Height / 2))) then
begin
//и если столкнулся
Obj1.Other:= Obj2; //выбираем Other объект
Result:= True; //результат True
end;
end;
end.
Теперь проверим столкновения в действии, ну для начала создайте два объекта и у второго поставьте значение Tag равное одному.
Code
with GameViewer.Scene.AddSprite do //добавляем новый спрайт
begin //и выбираем его для изменения параметров
AnimationMode:= amLoop; //анимация цикличная
Frames[0].Picture.LoadFromFile('Data\Images\1.bmp'); //загружаем первый кадр
Frames[0].Transparent:= true; //устанавливаем прозрачность
end;
with GameViewer.Scene.AddSprite do //добавляем новый спрайт
begin //и выбираем его для изменения параметров
AnimationMode:= amLoop; //анимация цикличная
Frames[0].Picture.LoadFromFile('Data\Images\1.gif'); //загружаем первый кадр
Frames[0].Transparent:= true; //устанавливаем прозрачность
Position.X:= 100;
Tag:= 1;
for i:= 1 to 16 do //цикл для загрузки 16'ти кадров
with AddFrame do //добавляем новый кадр
begin
Picture.LoadFromFile('Data\Images\' + IntToStr(i) + '.gif'); //загружаем картинку
Transparent:= true; //устанавливаем прозрачность
end;
end;
GameScene.Camera.CameraObject:= GameScene.Sprites[0]; //привязываем камеру к объекту
далее измените обработчик события OnTimer следующим образом:
Code
GameViewer.Update; //обналвяем основной экран для рисования
GameCamera.Update; //обналвяем камеру
GameScene.Update; //обналвяем сцену
//перемещаем спрайт в зависимомти от нажатой клавиши
if OnKey(VK_LEFT) then GameScene.Sprites[0].Position.X:= GameScene.Sprites[0].Position.X - 4;
if OnKey(VK_RIGHT) then GameScene.Sprites[0].Position.X:= GameScene.Sprites[0].Position.X + 4;
if OnKey(VK_UP) then GameScene.Sprites[0].Position.Y:= GameScene.Sprites[0].Position.Y - 4;
if OnKey(VK_DOWN) then GameScene.Sprites[0].Position.Y:= GameScene.Sprites[0].Position.Y + 4;
if isCollision(GameScene,GameScene.Sprites[0],GameScene.Sprites[1]) then GameScene.Sprites[0].Other.Destroy:= true;
запустите приложение. Теперь при столкновении со вторым объектом, то второй объект умрет но это как то не интересно без музыки и звуков, давайте напишем модуль специальный для этих целей! Для начала скачайте следующий файл: http://ifolder.ru/9784983, это библиотека Bass она нам поможет со звуком. Файл Bass.pas и Bass.dll должны всегда быть в папке с игрой. создайте новый модуль и назовите его uAudio
Code
unit uAudio;
interface
uses
Windows, Bass;
//процедуры для работы со звуком
procedure InitSongs(Handle: Hwnd);
procedure FreeSongs(Stream: HStream);
procedure PlaySong(Stream: HStream; FileName: String; Loop: Boolean);
implementation
//*Song*//
procedure InitSongs(Handle: Hwnd); //инициализация
begin
BASS_Init(-1,44100,0,Handle,0); //инициализируем Bass
BASS_Start(); //и запускаем его
end;
procedure FreeSongs(Stream: HStream); //освобождение памяти
begin
BASS_Stop(); //Останавливаем Bass
BASS_ChannelStop(Stream); //останавливаем каннал
BASS_StreamFree(Stream); //и освобаждаем его
BASS_Free(); //освобождаем Bass
end;
procedure PlaySong(Stream: HStream; FileName: String; Loop: Boolean); //функция проигрывания
begin
if not Loop then // если не Loop (не повторяется)
Stream:= BASS_StreamCreateFile(False,PChar(FileName),0,0,0) //то воспроизводим звук/музыку без цикла
else //иначе
Stream:= BASS_StreamCreateFile(False,PChar(FileName),0,0,BASS_SAMPLE_LOOP); //воспроизводим звук/музыку с циклом
BASS_ChannelPlay(Stream,True); //проигрываем звук
end;
end.
Добавьте в обработчик события главной формы OnCreate следующюю строчку кода:
Code
InitSongs(Handle); //инициализируем Bass
PlaySong(AudioStream,'Data\Audio\world.ogg',true); //включаем музыку
и в обработчик OnDestroy:
Code
FreeSongs(AudioStream); //Освобождаем bass
ВНИМАНИЕ! эти две строчки (не считая PlaySong) должны всегда присутвовать в коде, иначе может выскочить много ошибок. Если вы хотите остановить музыку и загрузить новую то пишите следующее:
Code
FreeSongs(AudioStream); //Освобождаем bass
InitSongs(Handle); //инициализируем Bass
и после этого кода можете снова загружать мелодию...
Теперь обработчике событий OnTimer измените следующим образом:
Code
GameViewer.Update; //обналвяем основной экран для рисования
GameCamera.Update; //обналвяем камеру
GameScene.Update; //обналвяем сцену
//перемещаем спрайт в зависимомти от нажатой клавиши
if OnKey(VK_LEFT) then GameScene.Sprites[0].Position.X:= GameScene.Sprites[0].Position.X - 4;
if OnKey(VK_RIGHT) then GameScene.Sprites[0].Position.X:= GameScene.Sprites[0].Position.X + 4;
if OnKey(VK_UP) then GameScene.Sprites[0].Position.Y:= GameScene.Sprites[0].Position.Y - 4;
if OnKey(VK_DOWN) then GameScene.Sprites[0].Position.Y:= GameScene.Sprites[0].Position.Y + 4;
if isCollision(GameScene,GameScene.Sprites[0],GameScene.Sprites[1]) then
begin
GameScene.Sprites[0].Other.Destroy:= true; //уничтожаем объект с которым мы столкнулись
PlaySong(AudioStream,'Data\Audio\beep.wav',false); //и воспроизводим звук
end;
Перед запуском закинте все ресурсы в папку здесь у меня world.ogg мелодия которая вопроизводится при запуске игры, второй это звук beep.wav он воспроизводится при столкновении...
Ну вообщем то и все, привожу полный код того что получилось (решил еще и коментарии написать :))
Авторские права принадлежат Каыблину Андрею (Xakep) (с) 2008 год
Категория: Delphi, Pascal, ObjectPascal |
Просмотров: 4102 |
Добавил: ДядяВолк (11.08.2010)
| Рейтинг: 0.0/0
Источник: http://quadrathell.cn.ua | Автор: Михаил Христосенко | |
HTML ссылка на материал: BB ссылка на материал: |
Всего комментариев: 0 | |