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

Материал из DRKB


Изучаем 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" он у меня выглядит вот так:

Dx 20604a.jpg


И всё, простейший пример с 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. Он выглядит у меня вот так:

Dx 20604b.jpg


Что, устал? Ну, расслабься, если хочешь. Дальше мы сделаем следующее:

  1. Добавим второго плеера
  2. И научимся делать стрэйфы

Для начала установим новую раскладку в DXInput. Для этого два раза кликнем по компоненту DXInput и переходим в закладку Keybord:

Dx 20529m.jpg


Сразу маленький совет: чтобы на одной кнопке у тебя не было 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 (кадры в секунду), и количество спрайтов на экране.

Д/З:

  1. Сделай анимированные патроны.
  2. Реализуй, чтобы вторым игроком управлял не человек, а созданный тобой интеллект.
  3. Сделай так, чтобы вёлся счёт фрагов.
  4. После смерти любого игрока, чтобы через 5 сек. происходил респаун.

Вот архив всего, что мы сегодня натворили: part3.rar (18kB).

Если у вас возникли какие-то вопросы или проблемы, пишите мне.


Author: Влад Энгельгардт
Source: http://gamedev.ru
ID: 03931