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

== Изучаем DelphiX. Часть 6: Мультиплеер. ==
 "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);
 MsgSize := SizeOf(TDXActionMessage);
 // пока в игру не зайдёт второй игрок ни кто, не двинется
 if Form1.DXPlay1.Players.Count = 2 then 
 begin
 if isLeft in Form1.DXInput1.States then
 begin
 x := x-5;
 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;
 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
 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.
Author: Влад Энгельгардт
Source: https://gamedev.ru
ID: 03934
Category:Графика
Category:DirectX
Category:Учебники