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

Автор: Релорт
WEB-сайт: http://daddy.mirgames.ru

Введение.

Совсем недавно предо мной встал вопрос создания редактора уровней для создаваемой мной игры «Червяк». В большинстве виденных мною версиях уровень сложности игры определялся или скоростью самого червя, или наличием определенного количества статичных стен, добавляемых random’ом. Создавая каркас редактора, я столкнулся с несколькими вопросами, вызванными моим желанием разнообразить игру:

  1. Требовалось создать довольно большое количество типов объектов: статичные, движущиеся стены; порталы; двери; подающие сверху камни и т. д. При этом каждый из объектов мог, по определению, обладать индивидуальной характеристикой в контексте своего класса.
  2. Все это довольно быстро привело к загромождению класса моего лабиринта TMaze массивами этих объектов.
  3. Вскоре я начал путаться в программном коде. Проверка на пересечение объектов разрослась до неимоверных размеров еще и потому, что я отказался от массивов типа Objects: array [0..X, 0..Y] of MyObj, в сторону динамических (Objects: array of MyObj). Требовалось срочное пересмотрение подхода к организации объектов игры.

И таким решением стало создание иерархично построенной структуры объектов.

Часть 1. Иерархия объектов игры.

Итак, воспользуемся одним из «столпов» ООП – наследованием. Этот принцип означаем, что если мы хотим создать класс, лишь немного отличающийся от старого, то объявляем новый класс потомком старого (родителя), и добавляем новые поля, свойства и методы. В моем случае любой объект лабиринта становился дочерним класса TObj.

TObj = class
  // положение обекта, его координаты
  X, Y: ShortInt;
  // constructor
  constructor Create(tx, ty: ShortInt);
end;
{ TObj }

constructor TObj.Create(tx, ty: ShortInt);
begin
  X := tx;
  Y := ty;
end;

Как видите, новый класс содержит переменные положения объекта в лабиринте, а также конструктор, перекрывающий стандартный конструктор TОbject’а. Следующим нашим шагом должно стать объявление всех дочерних классов. Для моей игры они таковы: TWall, TMoveWall (потомок TWall), TGate, TDoor... Рассмотрим лишь два из них. С остальными вы должны уж как-нибудь разобраться сами, но если что, пишите мне на мыло.

type
  // текущее состояние
  WallState = (ws_Burn, ws_Life, ws_Dead);
  // направление движения
  MoveDir = (md_Up, md_Down, md_Left, md_Right, md_Random);

  TWall = class(Tobj)
    State: WallState;
    constructor Create(tx, ty: ShortInt; tstate: WallState);
  end;
  TMoveWall = class(TWall)
    Direction: MoveDir;
    constructor Create(tx, ty: ShortInt; tdir: MoveDir; tstate: WallState);
  end;
  { TWall }

constructor TWall.Create(tx, ty: ShortInt; tstate: WallState);
begin
  inherited Create(tx, ty);
  State := tstate;
end;
{ TMoveWall }

constructor TMoveWall.Create(tx, ty: ShortInt; tdir: MoveDir; tstate:
  WallState);
begin
  inherited Create(tx, ty, tstate);
  Direction := tdir;
end;

Вы наверно обратили внимание на выделенное в теле конструктора слово inherited? Это строка необходима для того, чтобы правильно проинициализировать в создаваемом объекте поля, относящиеся к предку. Здесь при создании, например, объекта класса TMoveWall происходит вызов конструкторов классов TWall и TObj, которые устанавливают положение объекта и его состояние. Далее происходит инициализация первоначального значения переменной Direction. Да мы определили иерархию классов, скажите вы, но как это может помочь при создании игры, если так и придется определять массивы объектов этих классов? И вы правы, но и это еще не конец статьи. Вопрос создания массивов объектов рассмотрен в следующей главе.

Глава 2. Массив объектов.

Создание иерархии объектов не только несколько облегчила процесс создания новых классов, но и подвела нас к возможности избавления от нагромождения массивов разнообразных объектов в пользу одного (Items: array of TObj). Реализацией может служить следующий класс:

TObjList = class
  Count: Word;
  Items: array of TObj;
  procedure AddObj(tx, ty: ShortInt); overload;
  procedure AddObj(tx, ty: ShortInt; tstate: WallState); overload;
  procedure AddObj(tx, ty: ShortInt; tdir: MobeDir; tstate: WallState = ws_Life);
    overload;
  procedure DeleteObj(index: Word);
end;
{ TObjList }

procedure TObjList.AddObj(tx, ty: ShortInt);
begin
  inc(Count);
  SetLength(Items, Count);
  Items[Count - 1] := TObj.Create(tx, ty);
end;

procedure TObjList.AddObj(tx, ty: ShortInt; tstate: WallState);
begin
  inc(Count);
  SetLength(Items, Count);
  Items[Count - 1] := TWall.Create(tx, ty, tstate);
end;

procedure TObjList.AddObj(tx, ty: ShortInt; tdir: MoveDir; tstate: WallState =
  ws_Life);
begin
  inc(Count);
  SetLength(Items, Count);
  Items[Count - 1] := TMoveWall.Create(tx, ty, tdir, tstate);
end;

procedure TObjList.DeleteObj(index: Word);
begin
  Items[index] := Items[High(Items)];
  dec(Count);
  SetLength(Items, Count);
end;

В приведенном выше коде в массив могут быть добавлены только объекты классов TObj, TWall, TMoveWall, но его можно расширить добавлением новых процедур, на подобии AddObj(...); overload;. Директива overload позволяет использовать в программе все три реализации метода AddObj: компилятор, определяя типы и количество аргументов, вызывает соответствующий им метод.

В третьем методе AddObj использован параметр по умолчанию tstate: WallState = ws_Life. Но для более безопасной реализации списка не советую вам его использовать в перегружаемых функциях. Пример ошибочного использования можно сделать и в вышеизложенном коде:

procedure AddObj(tx, ty: ShortInt); overload;
procedure AddObj(tx, ty: ShortInt; tstate: WallState = ws_Life); overload;
...
AddObj(7, 11);

Здесь невозможно определить какую процедуру добавления необходимо использовать.

Доступ к объектам списка осуществляется по индексу

SomeObj := Items[index];  

но и здесь есть небольшой нюанс. Если класс объекта SomeObj не TObj, то для получения доступа к полям State класса TWall и Direction у TMoveWall потребует приведения объектного типа. Это осуществляется использованием оператора as. После применения оператора as можно вызывать методы, соответствующие присваиваемому классу.

SomeObj1 := Items[index] as TWall;
SomeObj2 := Items[index] as TMoveWall;  

где SomeObj1 объект класса TWall, а SomeObj2 - TMoveWall.
Но как узнать тип объекта, находящегося по индексу index? Для этого необходимо вспомнить, что все классы в Object Pascal являются потомками класса TObject, в котором определены важнейшие методы. Два из них:

class function ClassName: ShortString;
class function ClassNameIs(const Name: string): Boolean;

позволяют узнать имя класса или равенство имени класса заданному.

if Items[index].ClassName = 'TWall' then
  SomeObj1 := Items[index] as TWall;

if Items[index].ClassNameIs('TMoveWall') = True then
  SomeObj2 := Items[index] as TMoveWall;

где SomeObj1 объект класса TWall, а SomeObj2 - TMoveWall.

Заключение.

Рассмотренная здесь иерархия достаточно проста. Ее расширением может стать ходя бы включение подчиненности между объектами, т. е. наша иерархия сможет содержать родительские объекты, которые смогут иметь несколько дочерних. Дочерние объекты, в свою очередь, также могут иметь свои дочерние объекты, для которых они уже будут являться родительскими. Это позволит, допустим, передвигать сразу несколько объектов изменением положения их родителя. Если вам интересна эта тема, то пишите мне и я, в свою очередь, обязуюсь, при наличии заявок, выложить через некоторое время статью и по этому вопросу.

Ну вот вроде бы как и все. Мы добрались до завершения статьи. Если что непонятно, необходимо пояснение или пример пишите. Отвечу всем.

Посмотреть пример к статье

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