.
Создаем библиотеку для написания игр (TObject, игра, действия, action, игровая сущность).
Автор megabax   
25.10.2009 г.
New Page 2

Создаем библиотеку для написания игр (TObject, игра, действия, action, игровая сущность).

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

TEasyGameBaseClass=class(TObject)
end;

От этого класса я объявил остальные классы, большинство из которых пока сделал шаблонами. В частности, так был объявлен класс сущности:

TEasyGameNoun=class(TEasyGameBaseClass)
public
       procedure MakeQuantumOfTime; virtual; abstract;
end;

Спрашивается, для чего я объявил базовый класс игрового объекта? Сперва я сделал это "на всякий случай", но уже в версии 1.1 (забегаю вперед), я добавил в этот класс методы Serialize и Unserialize, общие для всех классов игровой библиотеки.

Теперь объясняю, для чего нужен класс сущности. Сущность - это такой игровой объект, который может обрабатывать квант времени. В частности, это сама платформа игры (во время обработки она вызывает обработчик кванта времени у всех входящих в нее объектов), а так же сами игровые объекты. Сущности могут быть объектными и не объектными. Платформа игры - это не объектная сущность, а юнит (бегающий по экрану человечек) - объектная сущностью.

Для описания объектных сущностей я создал отельный класс TEasyGameObject:

TEasyGameObject=class(TEasyGameNoun)
protected
       FOwner:TEasyGameContainer;
public
       procedure Action(ADestansion:TEasyGameObject; AAction:TEasyGameAction); virtual; abstract;
end;
 

В отличии от не объектных сущностей, объектная сущность может совершить действие по отношению к другой объектной сущности. Для этого в классе TEasyGameObject предусмотрен метод Action. Заметьте, в параметрах этого метода передается объект класса TEasyGameAction - действие. Самое действие сущностью не является (квант времени ему раздавать не нужно), поэтому этот класс объявлен от базового класса (пока только шаблон):

TEasyGameAction=class(TEasyGameBaseClass)
end;

Некоторые сущности (например, игровая платформа или карта) могут включать объектные сущности. Для этой цели я объявил класс TEasyGameContainer:

TEasyGameContainer=class(TEasyGameNoun)
protected
     FObjects:TList;
public
    procedure AddGameObject(AGameObject:TEasyGameObject);
    procedure MakeQuantumOfTime; override;
end;

Игровая платформа более сложный класс, вот как я объявил его в версии 1.0:

TEasyGamePlatform=class(TEasyGameContainer)
protected
     FDateTime:TDateTime;
     FTimeSpeed:integer; //коэффициент замедления (ускорения времени)
     FSizeOfQuantumOfTime:integer; // размер кванта времени
     procedure SetTimeSpeed(ATimeSpeed:integer);
     procedure SetSizeOfQuantumOfTime(ASizeOfQuantumOfTime:integer);
public
    property DateTime:TDateTime read FDateTime;
    property TimeSpeed:integer read FTimeSpeed write SetTimeSpeed;
    property SizeOfQuantumOfTime:integer read FSizeOfQuantumOfTime write SetSizeOfQuantumOfTime;
    constructor Create(ADateTime:TDateTime);
    destructor Destroy;
    procedure MakeQuantumOfTime; override;
end;
 

Класс TEasyGameContainer так же является родителем для класса карты TEasyGameMap. Естественно, если есть карта, то должны быть и объекты, имеющие на этой карте координаты. Поэтому я разделил объекты сущности еще на два подкласса: объектная сущность не привязанная к карте и привязанная к карте TEasyGameLocationObject:

TEasyGameLocationObject=class(TEasyGameObject)
protected
    FX,FY:integer;
    procedure SetX(AX:integer); virtual; abstract;
    procedure SetY(AY:integer); virtual; abstract;
public
    property X:integer read FX write SetX;
    property Y:integer read FY write SetX;
end;

Почему я к координатам привязал объект, а не расположил его в матрице координат? Предположим, что карта имеет размер 1000 на 1000. Значит, в матрице будет миллион элементов. Представляете, сколько памяти займет такая карта?  В оперативке ее будет хранить очень неудобно, а если подругжать с диска - то будут сильные тормоза. И при том, большинство локаций этой карты будет просто не заполнено.

Какие могут быть объектные сущности, не привязанные к координатам? Например те, что моделируют явления природы. Они могут происходить одновременно в нескольких локациях. Или, например, моделирование освещение карты солнцем - карата освещается сразу вся. Само сонце в данном случае будет не привязанная к координатам объектная сущность.

Ну, и что бы закончить урок, приведу пример реализации некоторых методов, но уже из версии 1.1

TEasyGameBaseClass=class(TPersistent)
protected
    FVersion:integer;
    function GetVersion:integer;
public
    procedure Serialize(AStream:TStream); virtual;
    procedure Unserialize(AStream:TStream); virtual;
end;
procedure TEasyGameBaseClass.Serialize(AStream:TStream);
var sign:string; version:integer;
begin
   Sign:=ClassName;
   version:=GetVersion;
   SerializeString(sign, AStream);
   AStream.Write(version,sizeof(version));
end;

procedure TEasyGameBaseClass.Unserialize(AStream:TStream);
var sign,s:string; l:integer;
begin
    sign:=UnserializeString(AStream);
    if sign<>ClassName then Raise Exception.Create('TEasyGameBaseClass.Unserialize: Неправильный формат файла (потока)');
    AStream.Read(FVersion,sizeof(FVersion));
end;

И немного комментариев. Во первых, теперь базовый класс объявлен не от TObject  а от TPersistent. Это связано с тем, что групповом чтении объектов из потока необходимо знать, какой это класс, что бы создать его. А класс для этого нужно зарегистрировать командой RegisterClass, но она может регистрировать только потомки TPersistent. Теперь о работе методов сериализации. Сперва мы пишем в поток имя класса. Для этого используем специальную процедуру SerializeString, которая входит в данную библиотеку. Для чего это см. здесь.

Затем пишем в поток номер версии. Читаем в обратном порядке. Если у нас в потоке не те данные - вызываем исключение:

if sign<>ClassName then Raise Exception.Create('TEasyGameBaseClass.Unserialize: Неправильный формат файла (потока)');

Для чего пишем номер версии? Что бы потом можно было запрограммировать корректное чтение объектов,  созданных в ранних версиях программы.

Если у нас объект содержит какие либо специальные поля, их тоже следует сериализовать, например, так:

procedure TEasyGameLocationObject.Serialize(AStream:TStream);
begin
    inherited Serialize(AStream);
    AStream.Write(FX, sizeof(FX));
    AStream.Write(FY, sizeof(FY));
end;

На этом урок закончен, до новых встреч.

 

Последнее обновление ( 06.11.2011 г. )