Delphi World - Изучаем DelphiX - Часть 6 - Мультиплеер
Delphi World - это проект, являющийся сборником статей и малодокументированных возможностей  по программированию в среде Delphi. Здесь вы найдёте работы по следующим категориям: delphi, delfi, borland, bds, дельфи, делфи, дэльфи, дэлфи, programming, example, программирование, исходные коды, code, исходники, source, sources, сорцы, сорсы, soft, programs, программы, and, how, delphiworld, базы данных, графика, игры, интернет, сети, компоненты, классы, мультимедиа, ос, железо, программа, интерфейс, рабочий стол, синтаксис, технологии, файловая система...
Изучаем DelphiX - Часть 6 - Мультиплеер

Оформил: XVeL
Автор: Влад Энгельгардт
WEB-сайт: http://gamedev.ru

"SINGLE-хорошо, а
MULTIPLAYER - лучше"

Здравствуйте дорогие мои читатели. Сегодня мы будем говорить о мультиплеере. Точнее об его создании в DelphiX. В DelphiX это реализуется с помощью компонента:

DXPlay. Многие, кто пытался что-то сделать на DXPlay, сталкивался с такой проблемой: что если вызывать диалог с DXPlay то получаем непонятный текст:

Устранить ее очень легко. Дело в том, что из-за "НЕРУССКОГО" происхождения программист Hiroyuki Hori в DXPlayFm.dfm в некоторых местах поставил шрифт: "Terminal", а нам нужен "MS Sans Serif". Поэтому то и всё так исковеркано. Я исправил эту проблему вот так:

И выкладываю исправленный мною модуль DXPlayFm.dfm, и более того, он русифицирован. Так что пользуйтесь на здоровье :) DXPlayFm.rar.

Сегодня мы напишем простенькую, мультиплеерную гаму. В этом я помогу вам.
В качестве шаблона я буду использовать part2.rar. Мы его модернизируем, и предадим ему мультиплеерность. Сразу говорю, что part2.rar состоял из 2 интерактивных объектов, соответственно и в наш мультиплеер можно играть только вдвоем.

Теперь я расскажу, как нужно модернизировать пример из второй части под шестой :).

На мой взгляд, эта часть является единственно растолковывающая создание мультиплеера под DelphiX. Сколько я прочитал статей по программированию мультиплеера на DelphiX, ничего путного не увидел. Здесь я использую простой алгоритм. Принцип его следующий: передаются не координаты объекта, а клавиши, которые были нажаты на удалённой машине.

Кидаем на форму компонент DXPlay и устанавливаем вот такие свойства:
MaxPlayers = 2,
GUID = {218A0D84-949C-42BC-A97B-40C3E3A642F1}.
GUID можно создать любой достаточно нажать на кнопку New.

Добавляем в uses модуль: DirectX.

После uses создаём список констант:

const
  DXACTION_MESSAGE = 1; // Это нажата клавиша действия
  DXKEY_LEFT = 1;       // двигаемся влево
  DXKEY_RIGHT = 2;      // двигаемся вправо
  DXKEY_SHOOT = 3;      // Стреляем

Создаём новый тип для наших ACTION сообщений:
type
  TDXActionMessage = record
    dwType: DWORD;
    ActionCode: Integer;
  end;

Далее идут классы спрайтов:
Единственное, что поменялось это класс TBOSS, я его переименовал в TPLU.
Когда мы поменяли названия то и картинку нужно изменить на более подходящую.

Заходим в DXImageList и переименовываем спрайт Boss на Plu .
А вот картинка:


В Var перед implementation обозначаем два наших класса:
var
  Form1: TForm1;
  plu:Tplu;
  PlayerSprite:TPlayerSprite;

implementation
Теперь подходим к процедуре TPlayerSprite.DoMove. Она должна выглядеть вот так:
Procedure TPlayerSprite.DoMove(MoveCount: Integer);
  var
    Msg: ^TDXActionMessage; //Само сообщение
    msgSize: Integer; //размер сообщения
  begin
    inherited DoMove(MoveCount);
    if form1.DXPlay1.Players.Count =2 then // пока в игру не зайдёт второй игрок
                                           // ни кто, не двинется
    begin
      if isLeft in Form1.DXInput1.States then
      begin
        x:=x-5;
        msgSize := SizeOf( TDXActionMessage );
        GetMem(Msg, MsgSize);
        msg.dwType := DXACTION_MESSAGE; //тип сообщения
        msg.ActionCode := DXKEY_LEFT; //действие
        form1.DXPlay1.SendMessage(DPID_ALLPLAYERS,msg,msgsize); //отсылаем всем
      end;
      if isRight in Form1.DXInput1.States then
      begin
        X:=x+5;
        msgSize := SizeOf( TDXActionMessage );
        GetMem(Msg, MsgSize);
        msg.dwType := DXACTION_MESSAGE;
        msg.ActionCode := DXKEY_RIGHT;
        form1.DXPlay1.SendMessage(DPID_ALLPLAYERS,msg,msgsize);
      end;
      if isup in Form1.DXInput1.States then
      begin
        if lngpolet-oldlngpolet<=900 then
        begin
          Inc(lngpolet);
          with TPlayerFa.Create(Engine) do
          begin
            msgSize := SizeOf( TDXActionMessage );
            GetMem(Msg, MsgSize);
            msg.dwType := DXACTION_MESSAGE;
            msg.ActionCode := DXKEY_SHOOT;
            form1.DXPlay1.SendMessage(DPID_ALLPLAYERS,msg,msgsize);
            PixelCheck := True;
            Image := form1.dxImageList1.Items.Find('Pula');
            X := Self.X+Self.Width  -40;
            Y := Self.Y+Self.Height -80 ;
            Width := Image.Width;
            Height := Image.Height;
            stril:=-4;
          end;
          oldlngpolet := lngpolet;
        end;
      end;
      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;
      lngpolet := lngpolet + MoveCount;
      Collision;
    end;
  end;
Теперь, как вы понимаете, так как TBoss уже не существует, то и некоторые процедуры должны быть пусты:
Procedure TPLU.DoMove(MoveCount: Integer);
  begin
    inherited DoMove(MoveCount);
    Collision;
  end;

  constructor TPLU.Create(AParent: TSprite);
  begin
    inherited Create(AParent);
  end;
Далее очищаем событие для формы OnCreate и пишем в нём:
procedure TForm1.FormCreate(Sender: TObject);
begin
  try
    DXPlay1.Open;
  Except
    On E: Exception do
    begin
      Application.ShowMainForm := False;
      Application.HandleException(E);
      Application.Terminate;
    end;
  end;

  With TBackgroundSprite.Create(DXSpriteEngine1.Engine) do
  begin
    SetMapSize(1,1);
    Image := dxImageList1.Items.Find('bg');
    Z:= -2;
    Tile := True;
  end;
end;
Далее кликаем по компоненту DXPlay создаём событие: OnMessage И пишем следующее:
procedure TForm1.DXPlay1Message(Sender: TObject; From: TDXPlayPlayer;
  Data: Pointer; DataSize: Integer);
begin
  if TDXActionMessage( Data^ ).actioncode AND DXKEY_LEFT = DXKEY_LEFT then
    plu.X:=plu.X -5; // получаем сообщение влево, двигаем удалённого
                     //playerа в лево
  if TDXActionMessage( Data^ ).actioncode AND DXKEY_RIGHT = DXKEY_RIGHT then
    plu.X:=plu.X +5; // получаем сообщение вправо, двигаем удалённого
                     //playerа в право
  if TDXActionMessage( Data^ ).actioncode AND DXKEY_SHOOT = DXKEY_SHOOT then
  begin    // получаем сообщение - удалённый игрок выстрелил,
          // создаём патрон на месте PLU
    with TPlayerFa.Create(dxspriteengine1.Engine) do
    begin
      PixelCheck := True;
      Image := form1.dxImageList1.Items.Find('Pula');
      X := plu.X+plu.Width-70;
      Y := plu.Y+plu.Height+10;
      Width := Image.Width;
      Height := Image.Height;
      stril:=4;
      Collision;
    end;
  end;
end;
Далее кликаем по DXDraw создаём событие Initialize и в нем пишем:
  plu:= Tplu.Create(DXSpriteEngine1.Engine);
  plu.PixelCheck  := True;
  plu.Image := form1.dxImageList1.Items.Find('plu');
  plu.x:=350;
  plu.y:=10;
  plu.Width := plu.Image.Width;
  plu.Height := plu.Image.Height;

  PlayerSprite:= TPlayerSprite.Create(DXSpriteEngine1.Engine);
  PlayerSprite.PixelCheck := True;
  PlayerSprite.Image := form1.dxImageList1.Items.Find('Pla');
  PlayerSprite.x:=350;
  PlayerSprite.y:=500;
  PlayerSprite.Width := PlayerSprite.Image.Width;
  PlayerSprite.Height := PlayerSprite.Image.Height;

Вот, и всё запускаем, и радуемся.
А теперь я расскажу, как это всё работает.

Прежде чем начать писать мультиплеер, нужно осознавать, что тебе придётся думать за каждую из удалённых машин. Как она получит пакет, кому что нужно будет отправить и так далее. Так вот, в событии Message в DXPlay1 обрабатываются всё принятые сообщения, а отсылать их можно любым плеером из любого места в коде. Так вот когда один из игроков запускает игру, а другой присоединяется к нему, происходит такая картина:

В нашем примере все действия, которые происходят на другой машине, отражается на спрайте Plu.

Скачать всё, что мы творили можно вот здесь: part6.rar.

P.S Назвал я класс PLU потому что, так проще, расшифровывается как плеер удаленный.

P.S.S Я устал уже отвечать на такие письма типа: "У меня не распаковывается архив?" так вот, говорю, всё архивы запакованы WinRAR 3.0.

Автор: Влад Энгельгардт

Проект Delphi World © Выпуск 2002 - 2017
Автор проекта: Эксклюзивные курсы программирования