Изучаем DelphiX (Часть 3)

== Изучаем DelphiX. Часть 3: Крутим спрайты. ==
 "Крутимся не накрутимся:)"
В DelphiX существует маленькая проблема, это разворот спрайтов. Она решается относительно просто, сейчас я объясню, как и что.
Для начала создадим новый проект в Delphi, подстроим его под шаблон (об этом написано в одной из частей цикла). Далее скачиваем мною модернизированный DXSprite.pas (Dxsprite.rar), в папке, где у вас установлен DelphiX есть папочка Source, копируем его туда. В DXSprite.pas я добавил всего одну процедуру Angle это и есть процедура разворота, как она работает можете посмотреть, сами поковырявшись в исходниках. Ну что приступим, для начала создадим простенький пример (в архиве с исходниками в папке 3.1).
Создаём новый класс:
 TPlayerone = class(TImageSprite)
 protected
 procedure DoMove(MoveCount: Integer); override;
end;
Соответственно и процедуру DoMove к этому классу:
procedure TPlayerone.DoMove(MoveCount: Integer);
begin
 inherited DoMove(MoveCount);
 x := x + cos256(Angle) * Speed; // обработчик движения по X
 y := y + sin256(Angle) * Speed; // обработчик движения по Y
 if y >= Form1.DXDraw1.SurfaceHeight - Image.Height then
 y := Form1.DXDraw1.SurfaceHeight - Image.Height;
 if x >= Form1.DXDraw1.SurfaceWidth - Image.Width then
 x := Form1.DXDraw1.SurfaceWidth - Image.Width;
 if y <= 0 then
 y := 1;
 if x <= 0 then
 x := 1;
 begin
 Speed := 0; // когда ничего не делаем, скорость равна 0
 if isLeft in Form1.DXInput1.States then Angle := Angle-5;
 if isRight in Form1.DXInput1.States then Angle := Angle+5;
 if isUp in Form1.DXInput1.States then Speed := 4;
 if isDown in Form1.DXInput1.States then Speed := -4;
 end;
end;
И перед implementation в var добавляем:
var
 Form1: TForm1;
 Speed: Integer; // Это у нас переменная скорости объекта
Не забудь в DXtimer добавить:
procedure TForm1.DXTimer1Timer(Sender: TObject; LagCount: Integer);
begin
 if not DXDraw1.CanDraw then exit;
 DXInput1.Update;
 DXSpriteEngine1.Move(LagCount);
 DXSpriteEngine1.Dead;
 DXDraw1.Surface.Fill(0);
 DXSpriteEngine1.Draw;
 DXDraw1.Flip;
end;
И в процедуре FormCreate создаём наш спрайт:
procedure TForm1.FormCreate(Sender: TObject);
begin
 with TPlayerone.Create(dxSpriteEngine1.Engine) do
 begin
 PixelCheck := True;
 Image := Form1.dxImageList1.Items.Find('krut');
 x := 350;
 y := 250;
 Width := Image.Width;
 Height := Image.Height;
 end;
end;
Далее в DXImageList добавляем спрайт "krut" он у меня выглядит вот так:
И всё, простейший пример с Angle готов.
Теперь давайте усложним наш пример и сделаем так что бы игрок стрелял в ту сторону куда он смотрит, для этого создаём новый класс для патрона. Выглядит он так:
 TPlayerFa = class(TImageSprite)
 protected
 procedure DoMove(MoveCount: Integer); override;
 private
 AngleFa: Integer; // Угол под которым летит пуля
 public
 constructor Create(AParent: TSprite); override;
 destructor Destroy; override;
 end;
Перед implementation добавляется ещё одна переменная Ang:
var
 Form1: TForm1;
 Speed, Ang: Integer;
Она нужна для выноса значения Angle плеера. А зачем нужен вынос, спросишь ты, да всё очень просто, он нужен для патрона, чтобы указывать ему, под каким углом лететь т.е. под каким углом находиться плеер под таким углом и летит патрон.
Маленько модернизируем класс плеера:
 TPlayerone = class(TImageSprite)
 private
 LngPolet: Integer; // мы же не хотим, чтобы наши пули летали кучами
 OldLngPolet: Integer; // а мы сделаем чтобы летали стаями :)
 protected
 procedure DoMove(MoveCount: Integer); override;
 end;
Сразу, чтобы не забыть конструктор и деструктор для патрона:
constructor TPlayerFa.Create(AParent: TSprite);
begin
 inherited Create(AParent);
 Image := Form1.DXImageList1.Items.Find('pul');
 Width := Image.Width;
 Height := Image.Height;
end;
destructor TPlayerFa.Destroy;
begin
 inherited Destroy;
end;
Ну а теперь движение патрона:
procedure TPlayerFa.DoMove(MoveCount: Integer);
begin
 inherited DoMove(MoveCount);
 Angle := AngleFa;
 x := x + cos256(angle) * 7; // цифра 7 здесь скорость патрона и изменять её надо
 y := y + sin256(angle) * 7; // пропорционально
 if X >= 800 then Dead;
 if y >= 600 then Dead;
 if X <= 0 then Dead;
 if y <= 0 then Dead;
 Collision;
end;
Надеюсь, ты помнишь, на прошлых уроках я рассказывал тебе об DXInput, так вот теперь нам понадобятся дополнительные кнопочки. Их значения ты можешь поменять, два раза кликнув на самом компоненте. Так вот, гляди процедуру DoMove у игрока:
procedure TPlayerone.DoMove(MoveCount: Integer);
begin
 inherited DoMove(MoveCount);
 Ang := Angle; // наша переменная для патрона
 x := x + cos256(Angle) * Speed;
 y := y + sin256(Angle) * Speed;
 if y >= Form1.DXDraw1.SurfaceHeight - Image.Height then
 y := Form1.DXDraw1.SurfaceHeight - Image.Height;
 if x >= Form1.DXDraw1.SurfaceWidth - Image.Width then
 x := Form1.DXDraw1.SurfaceWidth - Image.Width;
 if y <= 0 then
 y := 1;
 if x <= 0 then
 x:=1;
 begin
 Speed := 0;
 if isLeft in Form1.DXInput1.States then Angle := Angle-5;
 if isRight in Form1.DXInput1.States then Angle := Angle+5;
 if isUp in Form1.DXInput1.States then Speed := 4;
 if isDown in Form1.DXInput1.States then Speed := -4;
 if isButton1 in Form1.DXInput1.States then
 begin
 if LngPolet - OldLngPolet >= 250 then
 begin
 Inc(LngPolet);
 with TPlayerFa.Create(Engine) do
 begin
 Image := Form1.DXImageList1.Items.Find('pul');
 X := Self.X + cos256(ang) * 50; // здесь 50 расстояние патрона от плеера
 Y := Self.y + sin256(ang) * 50; // они должны быть пропорциональны
 AngleFa := Ang; // передаём угол
 OldLngPolet := LngPolet;
 end;
 end;
 end;
 LngPolet := LngPolet + MoveCount;
 end;
end;
И последнее что осталось это создать спрайт "pul" в DXimageList. Он выглядит у меня вот так:
Что, устал? Ну, расслабься, если хочешь. Дальше мы сделаем следующее: 
# Добавим второго плеера 
# И научимся делать стрэйфы
Для начала установим новую раскладку в DXInput. Для этого два раза кликнем по компоненту DXInput и переходим в закладку Keybord:
Сразу маленький совет: чтобы на одной кнопке у тебя не было 300 действий для каждой метки, назначай только одну кнопку.
Я выбрал такой вариант для первого игрока: 
 Up - Num 8 
 Down - Num 5 
 Left - Num 4 
 Right - Num 6 
 Button1 - Num 0 
 Button2 - Num 7 
 Button3 - Num 9 
Для второго игрока: 
 Button4 - T 
 Button5 - G 
 Button6 - F 
 Button7 - H 
 Button8 - E 
 Button9 - R 
 Button10 - Y
Посмотрите на клаву и поймёте раскладку. Ну что, приступаем, для начала перед Implementation добавим ещё две переменные, только уже для второго игрока:
var
 Form1: TForm1;
 speed, speed2, ang, ang2: Integer;
implementation
Теперь соответственно добавляем 2 игрока и модернизируем первого:
 TPlayerOne = class(TImageSprite)
 private
 LngPolet: Integer;
 OldLngPolet: Integer;
 protected
 procedure DoMove(MoveCount: Integer); override;
 // Добавили столкновение
 procedure DoCollision(Sprite: TSprite; var Done: Boolean); override;
 end;
 TPlayerTwo = class(TImageSprite)
 private
 LngPolet: Integer;
 OldLngPolet: Integer;
 protected
 procedure DoMove(MoveCount: Integer); override;
 procedure DoCollision(Sprite: TSprite; var Done: Boolean); override;
 end;
и теперь обрабатываем DoCollision у обоих плееров:
procedure TPlayerOne.DoCollision(Sprite: TSprite; var Done: Boolean);
begin
 if Sprite is TPlayerFa then Dead;
end;
procedure TPlayerTwo.DoCollision(Sprite: TSprite; var Done: Boolean);
begin
 if Sprite is TPlayerFa then Dead;
end;
Процедура DoMove у первого игрока в корне изменяется:
procedure TPlayerOne.DoMove(MoveCount: Integer);
begin
 inherited DoMove(MoveCount);
 Ang := Angle;
 x := x + cos256(Angle) * Speed;
 y := y + sin256(Angle) * Speed;
 if y >= Form1.DXDraw1.SurfaceHeight - Image.Height then
 y := Form1.DXDraw1.SurfaceHeight - Image.Height;
 if x >= Form1.DXDraw1.SurfaceWidth - Image.Width then
 x := Form1.DXDraw1.SurfaceWidth - Image.Width;
 if y <= 0 then
 y := 1;
 if x <= 0 then
 x := 1;
 begin
 Speed := 0;
 if isLeft in Form1.DXInput1.States then Angle := Angle-5;
 if isRight in Form1.DXInput1.States then Angle := Angle+5;
 if isUp in Form1.DXInput1.States then Speed := 4;
 if isDown in Form1.DXInput1.States then Speed := -4;
 if isButton2 in Form1.DXInput1.States then
 begin
 x := x + cos256(Angle + 64) * 3; // это у нас стрейф
 y := y + sin256(Angle + 64) * 3; // 3 - на сколько быстро стрейфиться
 end;
 if isButton3 in Form1.DXInput1.States then
 begin
 x := x + cos256(Angle - 64) * 3; // тоже стрейф, только в
 y := y + sin256(Angle - 64) * 3; // другую сторону
 end;
 if isButton1 in Form1.DXInput1.States then
 begin
 if LngPolet - OldLngPolet >= 250 then
 begin
 Inc(LngPolet);
 with TPlayerFa.Create(Engine) do
 begin
 Image := Form1.DXImageList1.Items.Find('pul');
 X := Self.X + cos256(ang) * 50;
 Y := Self.y + sin256(ang) * 50;
 AngleFa := Ang;
 OldLngPolet := LngPolet;
 end;
 end;
 end;
 LngPolet := LngPolet + MoveCount;
 end;
 Collision;
end;
Аналогично и у второго плеера:
procedure TPlayerTwo.DoMove(MoveCount: Integer);
begin
 inherited DoMove(MoveCount);
 Ang2 := Angle;
 x := x + cos256(Angle) * Speed2;
 y := y + sin256(Angle) * Speed2;
 if y >= Form1.DXDraw1.SurfaceHeight - Image.Height then
 y := Form1.DXDraw1.SurfaceHeight - Image.Height;
 if x >= Form1.DXDraw1.SurfaceWidth - Image.Width then
 x := Form1.DXDraw1.SurfaceWidth - Image.Width;
 if y <= 0 then
 y := 1;
 if x <= 0 then
 x := 1;
 begin
 Speed2 := 0;
 if isButton6 in Form1.DXInput1.States then Angle := Angle-5;
 if isButton7 in Form1.DXInput1.States then Angle := Angle+5;
 if isButton4 in Form1.DXInput1.States then Speed2 := 4;
 if isButton5 in Form1.DXInput1.States then Speed2 := -4;
 if isButton9 in Form1.DXInput1.States then
 begin
 x := x + cos256(Angle - 64) * 3;
 y := y + sin256(Angle - 64) * 3;
 end;
 if isButton10 in Form1.DXInput1.States then
 begin
 x := x + cos256(Angle + 64) * 3;
 y := y + sin256(Angle + 64) * 3;
 end;
 if isButton8 in Form1.DXInput1.States then
 begin
 if LngPolet-OldLngPolet >= 250 then
 begin
 Inc(LngPolet);
 with TPlayerFa.Create(Engine) do
 begin
 Image := Form1.DXImageList1.Items.Find('pul');
 X := Self.X + cos256(Ang2) * 50;
 Y := Self.y + sin256(Ang2) * 50;
 AngleFa := Ang2;
 OldLngPolet := LngPolet;
 end;
 end;
 end;
 LngPolet := LngPolet + MoveCount;
 end;
 Collision;
end;
И последнее в FormCreate добавляем, чтобы второй игрок креатился:
procedure TForm1.FormCreate(Sender: TObject);
begin
 with TPlayerOne.Create(dxSpriteEngine1.Engine) do
 begin
 PixelCheck := True;
 Image := Form1.dxImageList1.Items.Find('krut');
 x := 350;
 y := 250;
 Width := Image.Width;
 Height := Image.Height;
 end;
 with TPlayerTwo.Create(dxSpriteEngine1.Engine) do
 begin
 PixelCheck := True;
 Image := Form1.dxImageList1.Items.Find('krut');
 x := 50;
 y := 250;
 Width := Image.Width;
 Height := Image.Height;
 end;
И пару советов, для дебага можно выводить любую переменную на экран. Для этого в DXTimer добавим следующие строчки:
procedure TForm1.DXTimer1Timer(Sender: TObject; LagCount: Integer);
begin
 if not DXDraw1.CanDraw then Exit;
 DXInput1.Update;
 DXSpriteEngine1.Move(LagCount);
 DXSpriteEngine1.Dead;
 DXDraw1.Surface.Fill(0);
 DXSpriteEngine1.Draw;
 with DXDraw1.Surface.Canvas do
 begin
 Brush.Style := bsClear; // стиль
 Font.Color := clWhite; // цвет текста
 Font.Size := 12; // размер
 TextOut(0, 0, 'FPS: ' + IntToStr(DXTimer1.FrameRate)); // вывод текста
 TextOut(0, 24, 'спрайты: ' + IntToStr(DXSpriteEngine1.Engine.AllCount));
 Release;
 end;
 DXDraw1.Flip;
end;
Здесь я вывожу Fps (кадры в секунду), и количество спрайтов на экране.
Д/З: 
# Сделай анимированные патроны. 
# Реализуй, чтобы вторым игроком управлял не человек, а созданный тобой интеллект. 
# Сделай так, чтобы вёлся счёт фрагов. 
# После смерти любого игрока, чтобы через 5 сек. происходил респаун.
Вот архив всего, что мы сегодня натворили: part3.rar (18kB).
Если у вас возникли какие-то вопросы или проблемы, пишите мне.
Author: Влад Энгельгардт
Source: http://gamedev.ru
ID: 03931
Category:Графика
Category:DirectX
Category:Учебники