Антон Григорьев дата публикации 31-05-2009 09:44 Клетчатые игры
Существует много игр, полем для которых является прямоугольная доска, расчерченная на клетки. Это и "докомпьютерные" шахматы, реверси, морской бой, и компьютерные тетрис, линии, сапёр, а также многие другие. При программировании таких игр, несмотря на всё несходство их правил и изображений в клетках, реализации графической части во многом похожи, ведь они должны решать одну и ту же задачу: в зависимости от текущего состояния клетки вывести нарисовать на ней то или иное изображение. Естественно, что возникает мысль написать компонент, который возьмёт на себя всю графическую часть игры, оставив разработчику реализацию её логики. Именно такой компонент и предлагается вашему вниманию.
Примечание: я понимаю, что статья эта длинная, и читать её, не будучи точно уверенным, что оно того стоит, никому не хочется. Поэтому предлагаю сейчас перейти в самый конец, найти там ссылку на откомпилированные примеры игр, сделанные с помощью данной библиотеки, запустить их и решить, стоит ли тратить своё время на чтение этой статьи, или же возможности библиотеки вас не устраивают. Не забудьте только прочитать в описании игр их правила.
В простейшем случае уровень графики реализуется очень легко: есть двумерный массив, в котором программа меняет значения элементов. Компонент реагирует на эти изменения тем, что отрисовывает соответствующую ячейку в соответствии с её новым состоянием, используя для этого заранее заданный набор картинок. Но такой примитивный вариант сейчас мало кого устроит. Ведь хочется, например, чтобы фигуры в ячейках были анимированы. Чтобы при перемещении фигуры из одной клетки в другую она не просто исчезала в одном месте и появлялась в другом, а перемещалась бы туда плавно. А если эта фигура, например, человечек, то пусть он ещё и ножки при этом переставляет. А ещё во время игры иногда бывает полезно нарисовать на поле что-то, не являющееся изображением фигуры в клетке, надпись какую-нибудь вывести, и это что-то тоже должно быть анимированным. Проанализировав такие пожелания, я попытался сделать такой компонент, который, с одной стороны, давал бы возможность делать всё это, не задумываясь о внутренней кухне (чтобы само вовремя перерисовывалось, чтобы не мерцало и т.п.), а с другой — давал бы возможность работать в режиме простого изменения массива там, где лишние украшательства не нужны. Вам судить, насколько это у меня получилось.
Сразу скажу, что это моя первая серьёзная попытка работы с анимацией, и, вполне возможно, не всё здесь сделано наилучшим образом, так что все замечания на этот счёт принимаются с благодарностью.
Компонент TCellGameBoard сам по себе отображает простейшую клетчатую доску, каждая ячейка которой может иметь индивидуальный цвет. Кроме того, чтобы отделить ячейки друг от друга, можно провести линии по их границам.
Для рисования фигур предназначены слои, которые располагаются над доской. Слоёв может быть неограниченное количество. Сами по себе слои прозрачные, поэтому, если в какой-то клетке не нарисовать фигуру, в ней будет видна фигура нижележащего слоя. Более того, если фигура нарисована, но из-за сложной формы она занимает не весь прямоугольник клетки, фигура нижележащего слоя может быть частично видна, если она перекрыта не полностью. При каждой перерисовке компонента все слои рисуются полностью, начиная с самого нижнего, поэтому фигуры, в принципе, можно делать полупрозрачными, чтобы нижележащие фигуры просвечивали сквозь них.
Каждая ячейка слоя характеризуется двумя числами: состоянием и фазой. Состояние — это тип изображаемой фигуры, фаза — номер одного из возможных вариантов изображения данной фигуры.
Каждый слой связан с банком изображений. Банк изображений — это некоторый объект, который умеет рисовать фигуру с заданным состоянием и фазой. Разные банки могут делать это по-разному, обеспечивая различные наборы фигур для разных слоёв (использование разными слоями одного банка тоже допускается). Банк реализован в виде абстрактного класса TCellBank, от которого можно наследоваться и реализовывать такие изображения фигур, которые вам нравятся. Кроме того, в состав библиотеки входит компонент TBitmapCellBank. Это наследник TCellBank, который хранит набор готовых картинок для каждой из допустимых пар "состояние-фаза" и использует эти картинки для рисования фигур.
Компонент TCellGameBoard может быть анимированным и неанимированным. В неанимированном виде он перерисовывается только при необходимости (когда изменяется состояние или фаза ячейки в одном из слоёв). Никакая анимация в этом режиме невозможна. Этот режим позволяет экономить ресурсы компьютера, но подходит только для самых простых игр (имеются ввиду простые по графике игры; сами правила игры при этом могут быть очень сложными и интересными).
В анимированном режиме доска вместе со всеми своими слоями перерисовывается с определённой периодичностью независимо от того, были зафиксированы какие-либо изменения или нет. Период перерисовки задаётся разработчиком и может быть от 1 мс. Каждый раз увеличивается номер тика, который используется для того, чтобы определить, как должны быть отрисованы различные части изображения. Смена изображения при изменении тика реализует анимацию.
Сама доска рисуется всегда одинаково независимо от тика. По-разному отображаются клетки слоёв. Каждый слой может быть анимированным или неанимированным. Каждая клетка слоя может быть анимированной, неанимированной и "как у слоя". Анимированные клетки будут выглядеть анимированными даже на неанимированных слоях, неанимированными — неанимированными даже а анимированных слоях. Таким образом, признак анимированности слоя влияет только на те ячейки, которые "как у слоя".
Анимация ячейки заключается в последовательном увеличении её фазы. Каждая ячейка имеет собственную скорость анимации. Скорость анимации — это целое положительное число, которое равно количеству тиков, которое должно пройти, чтобы ячейка изменила свою фазу на следующую, т.е. чем больше эта величина, тем медленнее сменяют друг друга фазы ячейки. Ячейка не следит за тем, чтобы номер фазы возвращался к нулю при достижении максимального значения. Полагается, что банк изображений при выходе фазы за пределы допустимой границы сам подкорректирует это значение так, чтобы смена фаз была цикличной.
Для рисования тех анимированных изображений, которые не вписываются в определённые ячейки, существуют движения, реализуемые наследниками абстрактного класса TMovement. По сути дела движение — это объект, который рисует себя на доске произвольным образом. Движению передаётся текущий номер тика, который может учитываться при перерисовке, чтобы обеспечить анимацию. Одновременно на доске может быть произвольное число движений — их количество ограничивается только ресурсами компьютера. Движение может быть конечным или бесконечным. Бесконечные движения существуют до тех пор, пока объект этого движения не будет удалён явно. Конечные движения имеют логическое завершение (например, движение, обеспечивающее перемещение фигуру из одной клетки в другую, завершается, когда фигура достигает места назначения). Объекты конечных движений уничтожаются автоматически. Каждое движение связано с определённым слоем и рисуется сразу после отрисовки всех фигур этого слоя. Это позволяет управлять тем, какие фигуры окажутся над движением, какие — под ним. Существует возможность приостановить выполнение программы до тех пор, пока одно или несколько конечных движений не будут завершены.
Библиотека содержит целый ряд наследников TMovement, позволяющих реализовывать различные виды анимации, которые могут встречаться в играх. Кроме того, для нестандартных видов анимации можно порождать от TMovement свои классы.
Примечание: Я хорошо понимаю, что такой способ анимации — перерисовка доски целиком при каждом тике — является очень расточительным в смысле процессорного времени. В каждом конкретном случае можно было бы написать более эффективный код. И даже можно было бы написать компонент, который позволял бы более гибко настраивать то, что нужно на данном шаге перерисовать, чтобы минимизировать затраты. Но такой компонент было бы сложнее использовать. Я думаю, что выбранный мной способ — разумный компромисс между производительностью и простотой использования, тем более что на современных компьютерах эти расходы вовсе не выглядят непомерными. А ещё из этого можно сделать такой вывод: если уж вы решили использовать TCellGameBoard в анимированном режиме, не скупитесь на анимацию, пусть эти расходы не пропадают зря.
Библиотека состоит из следующих модулей:
- CellGameBoard.pas — основной модуль, содержащий описание классов доски, слоя, ячейки и абстрактные классы для движений и банков изображений.
- CellBanks.pas — модуль, содержащий различные банки изображений (в данной версии — один только класс TBitmapCellBank).
- Movements.pas — модуль, содержащий классы — наследники TMovement для реализации часто встречающихся видов анимации.
- BitmapBankEditor.pas — модуль, содержащий редактор времени проектирования для банка изображений TBitmapCellBank.
- Пакет CellBoard*.dpk — пакет времени выполнения, содержащий компоненты (вместо * может стоять номер версии Delphi).
- Пакет DCellBoard*.dpk — пакет времени разработки, содержащий редактор банка изображений и устанавливающий компоненты в палитру (вместо * может стоять номер версии Delphi).
- DCellGameBoard.dcr — файл, содержащий иконки для отображения в палитре компонентов.
Библиотека проверена на версиях Delphi 7, 2007 и 2009.
Кроме самой библиотеки, к статье прилагаются примеры игр, созданных с её помощью.
Библиотека и примеры поставляются в виде исходных текстов. При этом библиотека поставляется "как есть", т.е. с минимумом комментариев и объяснений внутренних принципов работы, в статье описан только внешний интерфейс. Код примеров игр содержит подробные комментарии, кроме того, в статье отдельно описываются наиболее важные моменты этих кодов.
К сожалению, я не имею времени, чтобы подробно описывать внутреннее устройство библиотеки. Если оно вам интересно, постарайтесь разобраться самостоятельно по исходным кодам. Я готов ответить на конкретные частные вопросы, возникающие при изучении этого кода, но прошу задавать мне их не лично, а только в обсуждении данной статьи, чтобы ваш вопрос и мой ответ были видны и другим людям. Но это должны быть действительно достаточно конкретные вопросы.
В этом разделе даны описания свойств, методов и событий классов, входящих в библиотеку и предназначенных для использования во время выполнения программы. Описаны все члены класса с областью видимости public и published. Члены с областью видимости protected описаны выборочно, описание тех, которые, по моему мнению, не представляют интереса для пользователей библиотеки, я опустил. Члены класса с областью видимости private не описываются. Информацию о не описанных членах классов можно получить из исходных кодов библиотеки.
Данный раздел задумывался как справочный. Если вы читаете статью в первый раз, вам можно сразу перейти к разделу "Особенности реализации".
Модуль: CellGameBoard
Предок: TObject
Класс TCellBackground определяет то, как будет рисоваться отдельная клетка доски (не фигура в слое, а именно клетка). Создавать экземпляры этого класса вручную вряд ли когда-либо понадобится. Класс TCellGameBoard содержит двумерный массив объектов типа TCellBackground (см. свойство TCellGameBoard.Cells), которое позволяет управлять тем, как рисуется клетка с соответствующими координатами.
Класс TCellBackground даёт относительно мало возможностей регулирования внешнего вида клетки. Если вам нужно нарисовать клетку как-то иначе, воспользуйтесь событиями OnCellBeforeDraw и OnCellAfterDraw класса TCellGameBoard.
Свойства TCellBackground
— TCellBackground.BackColor —
Свойство: BackColor
Тип: TColor
Область видимости: public
Значение по умолчанию: clNone
Права доступа: для чтения и записи
Описание: определяет основной цвет клетки. Значение clNone означает, что ячейка закрашивается цветом, определяемым свойством BackColor соответствующего экземпляра класса TCellGameBoard.
— TCellBackground.BorderWidth —
Свойство: BorderWidth
Тип: Integer
Область видимости: public
Значение по умолчанию: –1
Права доступа: для чтения и записи
Описание: определяет ширину рамки (в пикселях), которая рисуется по краям ячейки. Если значение свойства меньше нуля, ширина рамки для данной ячейки определяется свойством BorderWidth соответствующего экземпляра класса TCellGameBoard.
— TCellBackground.BottomRightColor —
Свойство: BottomRightColor
Тип: TColor
Область видимости: public
Значение по умолчанию: clNone
Права доступа: для чтения и записи
Описание: определяет цвет правой и нижней сторон рамки ячейки. Значение clNone означает, что правая и нижняя стороны рисуются цветом, определяемым свойством BottomRightColor соответствующего экземпляра класса TCellGameBoard.
— TCellBackground.TopLeftColor —
Свойство: TopLeftColor
Тип: TColor
Область видимости: public
Значение по умолчанию: clNone
Права доступа: для чтения и записи
Описание: определяет цвет левой и верхней сторон рамки ячейки. Значение clNone означает, что левая и верхняя стороны рисуются цветом, определяемым свойством TopLeftColor соответствующего экземпляра класса TCellGameBoard.
Методы TCellBackground
Методы класса TCellBackground служат для внутренних нужд класса. Вряд ли вам когда-нибудь придётся вызывать хоть один из них.
— TCellBackground.Create —
Метод: constructor Create(ABoard: TCellGameBoard);
Назначение: создание нового экземпляра TCellBackground
Параметр ABoard: компонент, содержащий вновь создаваемый экземпляр. ABoard должен быть корректной ссылкой на существущий экземпляр TCellGameBoard и не может принимать значение nil.
Область видимости: public
Тип метода: статический
Описание: конструктор Create создаёт новый экземпляр объекта TCellBackground и инициализирует его свойства значениями по умолчанию. Самостоятельное создание экземпляров TCellBackground практически лишено смысла, потому что объект, не являющийся элементом массива Cells какого-либо экземпляра TCellGameBoard, не может выполнять никакой полезной работы, а все элементы массива Cells компонент TCellFameBoard создаёт самостоятельно. Единственное возможное применение созданного вручную экземпляра TCellBackground, который служил бы шаблоном для элементов массива Cells при вызове Assign.
Доска, переданная в качестве параметра ABoard, не становится владельцем для тех экземпляров TCellBackground, которые созданы явным вызовом конструктора в программе. Ответственность за их удаление лежит на том коде, который их создал.
— TCellBackground.Assign —
Метод: procedure Assign(Source: TCellBackground);
Назначение: копирование свойств одного объекта TCellBackground в другой.
Параметр Source: экземпляр TCellBackground, который служит образцом для данного. Параметр Source не должен принимать значение nil.
Область видимости: public
Тип метода: статический
Описание: метод Assign копирует в текущий объект значение свойств BackColor, BorderWidth, BottomRightColor и TopLeftColor из объекта, переданного в качестве параметра Source. После вызова Assign для элемента массива Cells при очередной перерисовке компонента будет перерисована доска (см. раздел "Рисование доски"), даже если в результате операции не изменилось значение ни одного свойства. Если вы вызываете Assign для того объекта, который создали сами, доска будет перерисована у того компонента TCellGameBoard, который был указан при вызове конструктора данного объекта.
Обратите внимание, что класс TCellBackground не является наследником TPersistent, поэтому TCellBackground.Assign — это совсем не то же самое, что и TPersistent.Assign. В качестве параметра Source могут выступать только ссылки на TCellBackground, возможность расширения метода на другие классы не предусмотрена.
— TCellBackground.Draw —
Метод: procedure Draw(Canvas: TCanvas; const CellRect: TRect);
Назначение: рисование клетки
Параметр Canvas: холст для рисования клетки
Параметр CellRect: прямоугольник, ограничивающий клетку
Область видимости: protected
Тип метода: статический
Описание: метод Draw рисует одну клетку доски (без фигур, расположенных на ней; фигуры рисуются позже при рисовании слоёв). Вызывается автоматически экземпляром TCellGameBoard при необходимости (см. раздел "Рисование доски").
Модуль: CellGameBoard
Предок: TCustomControl
Класс TCellGameBoard реализует саму игровую доску и является хранилищем для слоёв, расположенных на ней.
Обратите внимание, что размер компонента нельзя менять с помощью свойств Width и Height, их изменение не приводит ни к каким последствиям. Ширина компонента всегда равна ColCnt*CellWidth, высота — RowCnt*CellHeight. При необходимости изменить размеры менять надо именно эти свойства.
Свойства TCellGameBoard
— TCellGameBoard.Animated —
Свойство: Animated
Тип: Boolean
Область видимости: published
Значение по умолчанию: False
Права доступа: для чтения и записи
Описание: Определяет, будет ли доска анимированной или нет. Неанимированная доска перерисовывается только по необходимости, т.е. при изменении её свойств, отвечающих за внешний вид доски, состояния клеток и т.п. Никакая анимация в этом режиме невозможна. Анимированная доска перерисовывается с периодичностью, заданной свойством TimerInterval, что позволяет менять фазы фигур, движений и т.п., создавая подвижное изображение на доске.
— TCellGameBoard.BackColor —
Свойство: BackColor
Тип: TColor
Область видимости: published
Значение по умолчанию: clBtnFace
Права доступа: для чтения и записи
Описание: определяет основной фон доски (отдельные ячейки при необходимости могут быть перекрашены с помощью Cells[X, Y].BackColor). См. раздел "Рисование доски".
— TCellGameBoard.BorderWidth —
Свойство: BorderWidth
Тип: Integer
Область видимости: published
Значение по умолчанию: 1
Права доступа: для чтения и записи
Описание: определяет толщину рамки ячеек (рамка рисуется внутри прямоугольника, заданного свойствами CellWidth и CellHeight, а не вокруг него). Ширина рамки может быть также задана для каждой ячейки индивидуально с помощью свойства Cells[X, Y].BoardWidth. См. также раздел "Рисование доски".
— TCellGameBoard.BottomRightColor —
Свойство: BottomRightColor
Тип: TColor
Область видимости: published
Значение по умолчанию: clBtnShadow
Права доступа: для чтения и записи
Описание: задаёт цвет правой и нижней границ рамки клетки (см. также свойство BorderWidth). Значение clNone означает, что нижняя и правая границы не должны рисоваться вообще. Для каждой ячейки этот цвет может быть также задан индивидуально с помощью свойства Cells[X, Y].BottomRightColor.
— TCellGameBoard.CellHeight —
Свойство: CellHeight
Тип: Integer
Область видимости: published
Значение по умолчанию: 32
Права доступа: для чтения и записи
Описание: определяет высоту одной клетки в пикселях.
— TCellGameBoard.Cells —
Свойство: Cells[X: Integer; Y: Integer]
Тип: TCellBackground
Область видимости: public
Значение по умолчанию: —
Права доступа: для чтения и записи
Описание: объект типа TCellBackground для ячейки с координатами (X; Y). Значение X должно находится в пределах 0 <= X < ColCnt, значение Y — в пределах 0 <= Y < RowCnt, в противном случае возникнет исключение EWrongCell.
Объект TCellBackground отвечает за рисование клетки (только самой клетки, а не фигур на ней) и позволяет задавать для данной клетки цвета и ширину рамки, отличные от заданных для всей доски (см. описание класса TCellBackground и раздел "Рисование доски").
При присваивании значения элементу массива Cells происходит неявный вызов метода TCellBackground.Assign. Таким образом, после присваивания соответствующий элемент массива содержит ссылку на тот же экземпляр, что и до, просто у этого экземпляра изменяются свойства в соответствии с тем объектом, который стоял справа от оператора присваивания. Компонент TCellGameBoard не становится владельцем того объекта, который стоял справа и не сохраняет на него никаких ссылок, поэтому все дальнейшие манипуляции с этим объектом должны выполняться так же, как и в том случае, если бы присваивания не было.
— TCellGameBoard.CellWidth —
Свойство: CellWidth
Тип: Integer
Область видимости: published
Значение по умолчанию: 32
Права доступа: для чтения и записи
Описание: определяет ширину одной клетки в пикселях
— TCellGameBoard.ColCnt —
Свойство: ColCnt
Тип: Integer
Область видимости: published
Значение по умолчанию: 10
Права доступа: для чтения и записи
Описание: определяет количество вертикальных столбцов на доске.
— TCellGameBoard.ImmediateUpdate —
Свойство: ImmediateUpdate
Тип: Boolean
Область видимости: published
Значение по умолчанию: False
Права доступа: для чтения и записи
Описание: определяет, будет ли доска в неанимированном режиме перерисовываться сразу после изменения свойств, влияющих на её внешний вид. При ImmediateUpdate = False изменение любого свойства доски, её слоёв или фигур приводит к тому, что компонент сообщает системе о необходимости своей перерисовки, и система сама перерисовывает компонент тогда, когда сочтёт нужным. В результате при изменении нескольких свойств подряд компонент перерисуется только один раз, и все изменения будут учтены сразу. Если же ImmediateUpdate = True, любое такое изменение приводит к немедленной перерисовке, и несколько последовательных изменений свойств вызовут последовательные перерисовки, в результате чего пользователь увидит, как компонент меняет вид постепенно.
В анимированном режиме перерисовка происходит с периодичностью, заданной свойством TimerInterval, независимо от того, были изменены какие-то свойства или нет, и изменения свойств никак не влияют на этот процесс. Таким образом, в анимированном режиме свойство ImmediateUpdate ни на что не влияет.
— TCellGameBoard.LastDraw —
Свойство: LastDraw
Тип: Int64
Область видимости: public
Значение по умолчанию: —
Права доступа: только для чтения
Описание: содержит количество тиков счётчика производительности (см. API-функцию QueryPerformaceCounter), прошедших за время, понадобившееся для последней на данный момент отрисовки доски во внутреннем буфере. Это значение следует использовать только в отладочных целях.
— TCellGameBoard.LastPaint —
Свойство: LastPaint
Тип: Int64
Область видимости: public
Значение по умолчанию: —
Права доступа: только для чтения
Описание: содержит количество тиков счётчика производительности (см. API-функцию QueryPerformaceCounter), прошедших за время, понадобившееся для последнего на данный момент переноса изображения доски из внутреннего буфера на экран. Это значение следует использовать только в отладочных целях.
— TCellGameBoard.Layers —
Свойство: Layers
Тип: TCellLayers (коллекция объектов TCellLayer)
Область видимости: published
Значение по умолчанию: —
Права доступа: для чтения и записи
Описание: коллекция слоёв с фигурами. При перерисовке доски слои рисуются в порядке возрастания индекса в коллекции, т.е. чем больше номер, тем выше лежит слой. См. описание класса TCellLayer.
Класс TCellLayers содержит индексированное свойство Items, являющееся свойством по умолчанию, поэтому выражение Layers.Items[N] или Layers[N] возвращает объект типа TCellLayer, отвечающий за N-ый слой. Слои нумеруются с нуля по возрастанию без пропусков.
При присваивании значения свойству Layers происходит неявный вызов метода Assign. Таким образом, после присваивания Layers содержит ссылку на тот же экземпляр, что и до, просто у этого экземпляра изменяются свойства в соответствии с тем объектом, который стоял справа от оператора присваивания. Компонент TCellGameBoard не становится владельцем того объекта, который стоял справа и не сохраняет на него никаких ссылок, поэтому все дальнейшие манипуляции с этим объектом должны выполняться так же, как и в том случае, если бы присваивания не было.
— TCellGameBoard.Phase —
Свойство: Phase
Тип: Int64
Область видимости: published
Значение по умолчанию: 0
Права доступа: для чтения и записи
Описание: задаёт смещение фазы для всех фигур на всех слоях (см. раздел "Вычисление фазы фигуры"). Имеет смысл только в неанимированном режиме, в анимированном значение этого свойства игнорируется.
— TCellGameBoard.RowCnt —
Свойство: RowCnt
Тип: Integer
Область видимости: published
Значение по умолчанию: 10
Права доступа: для чтения и записи
Описание: определяет количество горизонтальных строк на доске
— TCellGameBoard.Tick —
Свойство: Tick
Тип: Int64
Область видимости: public
Значение по умолчанию: —
Права доступа: только для чтения
Описание: содержит номер тика для анимированной доски, который увеличивается с периодичностью TimerInterval (см. раздел "Таймер и перерисовка").
— TCellGameBoard.TimerInterval —
Свойство: TimerInterval
Тип: Integer
Область видимости: published
Значение по умолчанию: 10
Права доступа: для чтения и записи
Описание: определяет величину интервала (в миллисекндах) между тиками при анимации. Для отсчёта интервала используется мультимедйный таймер, поэтому интервал измеряется с точностью до миллисекунды и может быть равен 1 мс и выше. Однако интервалы менее 10 мс приводят к большому росту загрузки процессора, не давая почти никаких преимуществ в анимации из-за инертности человеческого глаза, поэтому использование таких малых интервалов не рекомендуется.
Свойство TimerInterval влияет на поведение компонента только в анимированном режиме, в неанимированном его значение игнорируется. См. также раздел "Таймер и перерисовка".
Во время выполнения программы изменение значения свойства Timer возможно только при выключенной анимации (т.е. когда свойство Animated = False), в противном случае возникает исключение EAnimationIsOn. Во время разработки программы значение свойства TimerInterval можно менять при любом значении свойства Animated.
— TCellGameBoard.TopLeftColor —
Свойство: TopLeftColor
Тип: TColor
Область видимости: published
Значение по умолчанию: clBtnHighlight
Права доступа: для чтения и записи
Описание: задаёт цвет левой и верхней границ рамки клетки (см. также свойство BorderWidth). Значение clNone означает, что левая и верхняя границы не должны рисоваться вообще. Для каждой ячейки этот цвет может быть также задан индивидуально с помощью свойства Cells[X, Y].TopLeftColor.
Методы TCellGameBoard
— TCellGameBoard.Create —
Метод: constructor Create(AOwner: TComponent);
Назначение: создание нового экземпляра TCellGameBoard
Параметр AOwner: компонент, который становится владельцем создаваемого экземпляра TCellGameBoard
Область видимости: public
Тип метода: виртуальный, перекрытый (унаследован от TComponent)
Описание: конструктор Create создаёт новый экземпляр TCellGameBoard и инициализирует его. Использование конструктора ничем принципиально не отличается от обычного конструктора любого из наследников TComponent.
— TCellGameBoard.Destroy —
Метод: destructor Destroy;
Назначение: уничтожение экземпляра TCellGameBoard
Область видимости: public
Тип метода: виртуальный, перекрытый (унаследован от TObject)
Описание: деструктор Destroy освобождает все ресурсы, выделенные экземпляру TCellGameBoard, а также вызывает деструкторы коллекции Layers и всех элементов массива Cells. Использование деструктора не отличается от использования деструктора любого другого объекта.
— TCellGameBoard.BoardUpdated —
Метод: BoardUpdated(BackUpdated: Boolean);
Назначение: уведомление о необходимости перерисовки доски
Параметр BackUpdated: если False, в перерисовке нуждаются только фигуры на доске. Если True, в перерисовке нуждается также и сама доска.
Область видимости: protected
Тип метода: статический
Описание: в неанимированном режиме приводит к перерисовке доски (немедленному или отложенному в зависимости от значения свойства ImmediateUpdate). При BackUpdated = True перерисовывается сама доска, при BackUpdated = False — только фигуры на доске, а изображение доски берётся из буфера. В анимированном режиме вызов с BackUpdated = True приводит к тому, что при очередной перерисовке доски по таймеру будет перерисована и сама доска, а вызов с BackUpdated = False не даёт никакого эффекта.
— TCellGameBoard.DrawFrame —
Метод: procedure DrawFrame
Назначение: перерисовка доски
Область видимости: protected
Тип метода: статический
Описание: перерисовывает изображение доски с фигурами во внутреннем буфере. Сначала проверяет, нуждается ли изображение самой доски в обновлении, и если да, то вызывает метод DrawFrameBack. Потом копирует изображение доски и вызывает последовательно методы DrawLayer всех слоёв. Изображение, сформированное в DrawFrame, затем при необходимости переносится другими методами на экран.
— TCellGameBoard.DrawFrameBack —
Метод: procedure DrawFrameBack
Назначение: перерисовка доски
Область видимости: protected
Тип метода: статический
Описание: обновляет изображение самой доски во внутреннем буфере. Все шаги этого процесса описаны в разделе "Рисование доски".
— TCellGameBoard.HandleMessage —
Метод: HandleMessage
Назначение: обработка сообщения от мультимедийного таймера
Область видимости: public
Тип метода: статический
Описание: извлекает из очереди и передаёт на обработку одно сообщение, связанное с мультимедийным таймером и адресованное данному компоненту (см. раздел "Таймер и перерисовка"). Используется при организации циклических процессов, во время которых доска должна перерисовываться, но не должна, например, реагировать на нажатия мыши или клавиатуры. Это может использоваться, например, при ожидании завершения группы движений в тех случаях, когда возможностей метода WaitMovements недостаточно (см. пример в разделе "Использование движений").
Для извлечения сообщения из очереди метод HandleMessage использует функцию GetMessage, чтобы не нагружать процессор. Это означает, что функция не вернёт управление до тех пор, пока в очереди не появится сообщение от мультимедийного таймера. В анимированном режиме это не создаёт проблем, потому что сообщения от таймера появляются достаточно часто. Но если вызвать метод HandleMessage в неанимрованном режиме, программа зависнет.
— TCellGameBoard.InvalidateBoard —
Метод: procedure InvalidateBoard
Назначение: перерисовка доски заново
Область видимости: public
Тип метода: статический
Описание: По умолчанию доска рисуется один раз и запоминается в специальном буфере. При последующих перерисовках заново рисуются только фигуры на доске, а сама доска копируется из этого буфера для ускорения перерисовки (см. раздел "Рисование доски"). Повторная перерисовка доски выполняется только в том случае, если в этом возникает необходимость из-за изменения свойств, влияющих на внешний вид доски. Но так как на внешний вид доски могут влиять не только свойства, но и то, как программа обрабатывает события OnDrawBackground, OnCellBeforeDraw и OnCellAfterDraw, иногда может понадобиться полная перерисовка доски при неизменных свойствах. В этом случае следует вызвать метод InvalidateBoard, и тогда при следующей перерисовке доска будет перерисована заново, а не скопирована из буфера.
— TCellGameBoard.LockUpdate —
Метод: procedure LockUpdate
Назначение: приостанавливает перерисовку доски
Область видимости: public
Тип метода: статический
Описание: приостанавливает перерисовку доски в неанимированном режиме. Если перерисовка приостановлена, доска и фигуры на ней не будут перерисованы до тех пор, пока не будет вызван метод UnlockUpdate. Если свойство ImmediateUpdate = False, перерисовка в любом случае произойдёт не раньше, чем новое сообщение будет извлечено из очереди и передано на обработку, поэтому использовать LockUpdate и UnlockUpdate в этом случае имеет смысл только в тех ситуациях, когда требуется длительный запрет на перерисовку, распространяющийся на несколько сообщений. Если свойство ImmediateUpdate = True, то использование LockUpdate и UnlockUpdate может быть полезно в тех случаях, когда необходимо, чтобы доска перерисовывалась только после выполнения группы действий, а не каждого из этих действий по отдельности.
Вызовы LockUpdate и UnlockUpdate могут быть вложенными. Перерисовка доски вновь будет разрешена только после того, как UnlockUpdate будет вызван столько же раз, сколько раз перед этим был вызван LockUpdate.
На поведение доски в анимированном режиме вызовы LockUpdate и UnlockUpdate влияния не оказывают.
— TCellGameBoard.SetBounds —
Метод: procedure SetBounds(ALeft, ATop, AWidth, AHeight: Integer);
Назначение: устанавливает новый размер компонента
Параметр ALeft: новая X-координата верхнего левого угла
Параметр ATop: новая Y-координата верхнего левого угла
Параметр AWidth: новая ширина
Параметр AHeight: новая высота
Область видимости: public
Тип метода: виртуальный, перекрытый (унаследован от TControl)
Описание: все изменения размера компонента в VCL выполняются через вызов данного метода. В классе TCellGameBoard он перекрыт для того, чтобы предотвратить прямое изменение размеров компонента. Ширина доски всегда равна ColCnt*CellWidth, высота — RowCnt*CellHeight, и изменить эти размеры можно только через изменение указанных свойств.
— TCellGameBoard.UnlockUpdate —
Метод: procedure UnlockUpdate
Назначение: возобновляет перерисовку доски
Область видимости: public
Тип метода: статический
Описание: возобновляет перерисовку доски, которая была запрещена предшествующим вызовом LockUpdate.
— TCellGameBoard.WaitMovement —
Метод: procedure WaitMovement(Movement: TMovement; DiscardMouseEvents: Boolean = True);
Назначение: выполняет ожидание окончания движения
Параметр Movement: движение, окончания которого нужно дождаться
Параметр DiscardMouseEvents (может быть опущен, значение по умолчанию True): если True, после окончания движения из очереди удаляются все посланные компоненту сообщения, связанные с мышью. Если False, никакие сообщения из очереди не удаляются.
Область видимости: public
Тип метода: статический
Описание: приостанавливает дальнейшее выполнение текущей нити до тех пор, пока движение, переданное в качестве параметра Movement, не будет завершено (см. описание класса TMovement и раздел "Использование движений"). Во время ожидания обрабатываются только сообщения от таймера доски, связанные с её периодической перерисовкой.
Чтобы исключить реакцию на нажатие пользователем кнопки мыши, сделанные во время ожидания окончания движения, используется параметр DiscardMouseEvents. Если он равен True, после окончания ожидания из очереди удалаются все сообщения, предназначенные компоненту и связанные с мышью.
Так как ожидание окончания имеет смысл только для конечных движений, при передаче в качестве параметра Movement бесконечного движения возникает исключение EEndlessMovement. Использование движений возможно только в анимированном режиме, поэтому при вызове метода в неанимированном режиме возникает исключение EAnimationIsOff.
— TCellGameBoard.WaitMovements —
Метод: procedure WaitMovements(Movements: array of TMovement; WaitAll: Boolean; DiscardMouseEvents: Boolean = True);
Назначение: выполняет ожидание окончания движения
Параметр Movements: массив движений, окончания которых нужно дождаться
Параметр WaitAll: если True, ожидание заканчивается, когда все движения из массива Movements завершаются. Если False, ожидание заканчивается, когда завершается хотя бы одно движение из массива Movements.
Параметр DiscardMouseEvents (может быть опущен, значение по умолчанию True): если True, после окончания движения из очереди удаляются все посланные компоненту сообщения, связанные с мышью. Если False, никакие сообщения из очереди не удаляются.
Область видимости: public
Тип метода: статический
Описание: приостанавливает дальнейшее выполнение текущей нити до тех пор, пока движения, переданные в параметре Movements, не будут завершены (см. описание класса TMovement и раздел "Использование движений"). Во время ожидания обрабатываются только сообщения от таймера доски, связанные с её периодической перерисовкой.
Чтобы исключить реакцию на нажатие пользователем кнопки мыши, сделанные во время ожидания окончания движения, используется параметр DiscardMouseEvents. Если он равен True, после окончания ожидания из очереди удалаются все сообщения, предназначенные компоненту и связанные с мышью.
Так как ожидание окончания имеет смысл только для конечных движений, при передаче в параметре Movements бесконечного движения возникает исключение EEndlessMovement. Использование движений возможно только в анимированном режиме, поэтому при вызове метода в неанимированном режиме возникает исключение EAnimationIsOff.
События TCellGameBoard
— TCellGameBoard.OnBoardTimer —
Событие: OnBoardTimer
Назначение: уведомление об очередном тике мультимедийного таймера
Тип: TBoardTimerEvent
Прототип: procedure(Sender: TObject; InWait: Boolean)
Параметр Sender: доска, инициировавшая сообщение
Параметр InWait: если True, значит, в данный момент работает метод WaitMovement или WaitMovements
Область видимости: published
Описание: срабатывает при каждом тике мультимедийного таймера, если доска находится в анимированном режиме. В неанимированном режиме это событие не возникает.
Событие OnBoardTimer возникает в самом конце обработки доской сообщения от таймера, сразу после того, как очередное состояние доски полностью нарисовано в буфере и скопировано на экран. Таким образом, любые действия в этом обработчике могут повлиять только на состояние доски на следующем, но не на текущем шаге. См. также раздел "Таймер и перерисовка".
— TCellGameBoard.OnCellAfterDraw —
Событие: OnCellAfterDraw
Назначение: уведомление о завершении рисования клетки
Тип: TCellAfterDrawEvent
Прототип: procedure(Sender: TObject; Board: TBitmap; Col, Row: Integer; const CellRect: TRect)
Параметр Sender: доска, инициировавшая сообщение
Параметр Board: буфер, в котором рисуется доска. По размерам совпадает с компонентом, имеет формат pf24Bit
Параметр Col: колонка клетки, которая рисуется
Параметр Row: строка клетки, которая рисуется
Параметр CellRect: прямоугольник клетки, которая рисуется
Область видимости: published
Описание: возникает сразу после окончания рисования очередной клетки доски (см. раздел "Рисование доски"). Обратите внимание, что речь идёт только о рисовании собственно клетки. Фигуры на доске начинают рисоваться уже после обработки события OnCellAterDraw для всех клеток.
— TCellGameBoard.OnCellBeforeDraw —
Событие: OnCellBeforeDraw
Назначение: уведомление о начале рисования клетки
Тип: TCellBeforeDrawEvent
Прототип: procedure(Sender: TObject; Board: TBitmap; Col, Row: Integer; const CellRect: TRect; var DefaultDraw: Boolean)
Параметр Sender: доска, инициировавшая сообщение
Параметр Board: буфер, в котором рисуется доска. По размерам совпадает с компонентом, имеет формат pf24Bit
Параметр Col: колонка клетки, которая рисуется
Параметр Row: строка клетки, которая рисуется
Параметр CellRect: прямоугольник клетки, которая рисуется
Параметр DefaultDraw: выходной параметр. На входе всегда равен True. Если обработчик события изменяет его значение на False, дальнейшие действия по рисованию данной клетки выполняться не будут
Область видимости: published
Описание: возникает при рисовании клеток доски в тот момент, когда вся доска уже закрашена цветом BackColor, выполнена обработка события OnDrawBackground, но больше ничего в области данной клетки ещё не нарисовано (см. раздел "Рисование доски"). Разработчик имеет возможность вмешаться в этот процесс, нарисовав что-либо своё. Если обработчик OnCellBeforeDraw нарисовал всё, что нужно, следует присвоить False параметру DefaultDraw, чтобы отменить стандартное рисование.
Обратите внимание, что событие OnCellBeforeDraw имеет отношение только к рисованию собственно клетки доски, но не к рисованию фигур на ней, поэтому присваивание False параметру DefaultDraw не отменяет рисования фигур в этой клетке. Также оно не отменяет возникновения события OnCellAfterDraw.
Если свойство Cells[Col, Row].BackColor имеет значение, отличное от clNone, а параметр DefaultDraw обработчик оставил равным True, заливка клетки этим цветом производится после обработки события OnCellBeforeDraw, поэтому всё, что в нём нарисовано, будет стёрто.
— TCellGameBoard.OnCellDblClick —
Событие: OnCellDblClick
Назначение: уведомление о двойном щелчке кнопкой мыши над клеткой доски
Тип: TCellMouseEvent
Прототип: procedure(Sender: TObject; Button: TMouseButton; Shift: TShiftState; Col, Row, X, Y :Integer)
Параметр Sender: доска, инициировавшая сообщение
Параметр Button: задаёт, какой кнопкой мыши произведён щелчок
Параметр Shift: состояние кнопок Shift, Ctrl, Alt и кнопок мыши на момент щелчка
Параметр Col: колонка клетки, на которую пришёлся щелчок
Параметр Row: строка клетки, на которую пришёлся щелчок
Параметр X: горизонтальное смещение курсора мыши относительно левой стороны клетки
Параметр Y: вертикальное смещение курсора мыши относительно верхней стороны клетки
Область видимости: published
Описание: возникает при двойном щелчке кнопкой мыши (любой кнопкой, а не только левой) над одной из клеток доски. Параметры Col и Row задают клетку, на которой было совершён щелчок, параметры X и Y — координаты курсора мыши относительно левого верхнего угла этой клетки. Параметр Col всегда лежит в диапазоне 0..ColCnt–1, параметр Row — в диапазоне 0..RowCnt–1. Если щелчок происходит за пределами доски (такое возможно, если доска захватила мышь), событие OnCellDoubleClick не возникает.
При двойном щелчке события возникают в следующем порядке:
— TCellGameBoard.OnCellMouseDown —
Событие: OnCellMouseDown
Назначение: уведомление о нажатии кнопки мыши над клеткой доски
Тип: TCellMouseEvent
Прототип: procedure(Sender: TObject; Button: TMouseButton; Shift: TShiftState; Col, Row, X, Y :Integer)
Параметр Sender: доска, инициировавшая сообщение
Параметр Button: задаёт, какая кнопка мыши нажата
Параметр Shift: состояние кнопок Shift, Ctrl, Alt и кнопок мыши на момент нажатия
Параметр Col: колонка клетки, на которую пришлось нажатие
Параметр Row: строка клетки, на которую пришлось нажатие
Параметр X: горизонтальное смещение курсора мыши относительно левой стороны клетки
Параметр Y: вертикальное смещение курсора мыши относительно верхней стороны клетки
Область видимости: published
Описание: возникает при нажатии кнопки мыши над одной из клеток доски. Параметры Col и Row задают клетку, на которой было совершено нажатие, параметры X и Y — координаты курсора мыши относительно левого верхнего угла этой клетки. Параметр Col всегда лежит в диапазоне 0..ColCnt–1, параметр Row — в диапазоне 0..RowCnt–1. Если нажатие происходит за пределами доски (такое возможно, если доска захватила мышь), событие OnCellMouseDown не возникает.
— TCellGameBoard.OnCellMouseEnter —
Событие: OnCellMouseEnter
Назначение: уведомление о вхождении курсора мыши в область клетки
Тип: TCellMouseNotifyEvent
Прототип: procedure(Sender: TObject; Col, Row: Integer)
Параметр Sender: доска, инициировавшая сообщение
Параметр Col: колонка клетки, в которую вошёл курсор мыши
Параметр Row: строка клетки, в которую вошёл курсор мыши
Область видимости: published
Описание: возникает, когда курсор мыши переходит границу клетки и попадает в её область. Переход может осуществляться как из соседней клетки, так и при приходе курсора мыши на доску извне.
— TCellGameBoard.OnCellMouseLeave —
Событие: OnCellMouseLeave
Назначение: уведомление о покидании курсором мыши области клетки
Тип: TCellMouseNotifyEvent
Прототип: procedure(Sender: TObject; Col, Row: Integer)
Параметр Sender: доска, инициировавшая сообщение
Параметр Col: колонка клетки, которую покинул курсор мыши
Параметр Row: строка клетки, которую покинул курсор мыши
Область видимости: published
Описание: возникает, когда курсор мыши переходит границу клетки и покидает её область. Переход может осуществляться как в соседнюю клетку, так и в область, не принадлежащую доске.
— TCellGameBoard.OnCellMouseMove —
Событие: OnCellMouseMove
Назначение: уведомление о движении мыши над клеткой доски
Тип: TCellMouseMoveEvent
Прототип: procedure(Sender: TObject; Shift: TShiftState; Col, Row, X, Y :Integer)
Параметр Sender: доска, инициировавшая сообщение
Параметр Shift: состояние кнопок Shift, Ctrl, Alt и кнопок мыши на момент срабатывания события
Параметр Col: колонка клетки, над которой движется мышь
Параметр Row: строка клетки, над которой движется мышь
Параметр X: горизонтальное смещение курсора мыши относительно левой стороны клетки
Параметр Y: вертикальное смещение курсора мыши относительно верхней стороны клетки
Область видимости: published
Описание: возникает при движении мыши над одной из клеток доски. Параметры Col и Row задают клетку, над которой движется мышь, параметры X и Y — координаты курсора мыши относительно левого верхнего угла этой клетки. Параметр Col всегда лежит в диапазоне 0..ColCnt–1, параметр Row — в диапазоне 0..RowCnt–1. Если движение происходит за пределами доски (такое возможно, если доска захватила мышь), событие OnCellMouseMove не возникает.
— TCellGameBoard.OnCellMouseUp —
Событие: OnCellMouseUp
Назначение: уведомление об отпускании кнопки мыши над клеткой доски
Тип: TCellMouseEvent
Прототип: procedure(Sender: TObject; Button: TMouseButton; Shift: TShiftState; Col, Row, X, Y :Integer)
Параметр Sender: доска, инициировавшая сообщение
Параметр Button: задаёт, какая кнопка мыши отпущена
Параметр Shift: состояние кнопок Shift, Ctrl, Alt и кнопок мыши на момент нажатия
Параметр Col: колонка клетки, на которую пришлось нажатие
Параметр Row: строка клетки, на которую пришлось нажатие
Параметр X: горизонтальное смещение курсора мыши относительно левой стороны клетки
Параметр Y: вертикальное смещение курсора мыши относительно верхней стороны клетки
Область видимости: published
Описание: возникает при отпускании кнопки мыши над одной из клеток доски. Параметры Col и Row задают клетку, на которой было совершено отпускание, параметры X и Y — координаты курсора мыши относительно левого верхнего угла этой клетки. Если отпускание произошло в пределах доски, параметр Col лежит в диапазоне 0..ColCnt–1, параметр Row — в диапазоне 0..RowCnt–1. Если отпускание происходит за пределами доски (такое возможно, если доска захватила мышь, что происходит автоматически при нажатии её кнопки), параметры Col и Row имеют значения –1.
— TCellGameBoard.OnDrawBackground —
Событие: OnDrawBackground
Назначение: уведомление о рисовании доски
Тип: TDrawBackgroundEvent
Прототип: procedure(Sender:TObject; Board: TBitmap)
Параметр Sender: доска, инициировавшая сообщение
Параметр Board: буфер, в котором рисуется доска. По размерам совпадает с компонентом, имеет формат pf24Bit
Область видимости: published
Описание: возникает сразу после того как доска закрашивается цветом BackColor перед рисованием остальных элементов клеток на ней (см. раздел "Рисование доски").
Модуль: CellGameBoard
Предок: TCollectionItem
Экземпляр класса TCellLayer представляет собой один слой фигур на доске. Этот класс сделан наследником TCollectionItem, чтобы можно было организовывать коллекции слоёв и работать с ними во время разработки приложения (см. раздел "Работа с компонентами во время разработки приложения"). Сама коллекция реализуется классом TCellLayers (этот класс здесь не описан, так как представляет собой просто адаптированный для хранения экземпляров TCellLayer наследник класса TCollection). Коллекция слоёв создаётся классом TCellGameBoard, ссылка на неё хранится в свойстве Layers.
Свойства TCellLayer
— TCellLayer.Animated —
Свойство: Animated
Тип: Boolean
Область видимости: published
Значение по умолчанию: False
Права доступа: для чтения и записи
Описание: свойство Animated показывает, будут ли анимированы фигуры на слое или нет. Это свойство затрагивает только те фигуры, у которых свойство TGameCell.Animated имеет значение bpLayerDefault (см. описание класса TGameCell). Фигуры с иным значением свойства TGameCell.Animated будут анимированы или не анимированы независимо от значения свойства Animated слоя.
Значение свойства Animated также не влияет на способность слоя отображать движения — при Animated = False движения всё равно будут перерисовываться на каждом шаге. Таким образом, свойство TCellLayer.Animated предназначено только для удобного включения и выключения анимации тех фигур, у которых TGameCell.Animated = pbLayerDefault.
— TCellLayer.Bank —
Свойство: Bank
Тип: TCellBank
Область видимости: published
Значение по умолчанию: nil
Права доступа: для чтения и записи
Описание: свойство содержит ссылку на банк изображений, который используется для рисования фигур слоя (см. описание класса TCellBank). Объект TCellLayer не создаёт объект TCellBank самостоятельно и не становится владельцем того объекта, ссылка на который записана в свойство Bank. Это означает, что программист должен самостоятельно создавать банк и что объект слоя при освобождении не освобождает банк, на который ссылается, банк должен быть освобождён вручную или через обычный для компонентов VCL механизм автоматического освобождения.
При удалении экземпляра TCellBank экземпляр TCellGameBoard получает уведомление об этом через стандартный механизм VCL (через метод Notification). В этом случае TCellGameBoard проверяет все слои из своей коллекции Layers, и если находит в них ссылки на удаляемый банк, обнуляет эти ссылки. Библиотека задействует этот механизм даже в том случае, если экземпляры TCellBank и TCellGameBoard имеют разных владельцев. Таким образом, разработчик может удалять экземпляры TCellBank, не заботясь о том, сохранились ли ссылки на них в свойствах Bank слоёв доски.
Для свойства Bank допускается значение nil. В этом случае перерисовка фигур на доске не выполняется, а слой может использоваться только для отображения движений.
— TCellLayer.Board —
Свойство: Board
Тип: TCellGameBoard
Область видимости: public
Значение по умолчанию: —
Права доступа: только для чтения
Описание: содержит ссылку на компонент TCellGameBoard, к которому относится данный слой.
— TCellLayer.Cells —
Свойство: Cells[X: Integer; Y: Integer]
Тип: TGameCell
Область видимости: public. Свойство является свойством по умолчанию
Значение по умолчанию: —
Права доступа: для чтения и записи
Описание: элемент Cells[X, Y] содержит ссылку на тот экземпляр класса TGameCell, который отвечает за фигуру в ячейке с координатами (X; Y). Значение X должно находится в пределах 0 <= X < Width, значение Y — в пределах 0 <= Y < Height, в противном случае возникнет исключение EWrongCell.
Объект TGameCell отвечает за перерисовку фигуры в ячейке. Дополнительные сведения см. в описании класса TGameCell.
Свойство является свойством по умолчанию, т.е., если Layer — это некоторая ссылка на объект TCellLayer, обращение к элементу Cells можно записать не только как Layer.Cells[X, Y], но и просто Layer[X, Y].
При присваивании значения элементу массива Cells происходит неявный вызов метода Assign. Таким образом, после присваивания соответствующий элемент массива содержит ссылку на тот же экземпляр, что и до, просто у этого экземпляра изменяются свойства в соответствии с тем объектом, который стоял справа от оператора присваивания. Объект TCellLayer не становится владельцем того объекта, который стоял справа и не сохраняет на него никаких ссылок, поэтому все дальнейшие манипуляции с этим объектом должны выполняться так же, как и в том случае, если бы присваивания не было.
— TCellLayer.Height —
Свойство: Height
Тип: Integer
Область видимости: public
Значение по умолчанию: —
Права доступа: только для чтения
Описание: содержит количество строк в слое. Свойство используется только для внутренних нужд, библиотека обеспечивает, что значение свойства Height для объекта TCellLayer всегда равно значению свойства RowCnt того экземпляра TCellGameBoard, который владеет данным слоем.
— TCellLayer.Phase —
Свойство: Phase
Тип: Int64
Область видимости: published
Значение по умолчанию: 0
Права доступа: для чтения и записи
Описание: свойство задаёт смещение фазы для всех фигур данного слоя. См. раздел "Вычисление фазы фигуры".
— TCellLayer.Visible —
Свойство: Visible
Тип: Boolean
Область видимости: published
Значение по умолчанию: True
Права доступа: для чтения и записи
Описание: определяет, будет ли слой видимым. Если слой невидим, не выводятся ни его фигуры, ни движения, связанные с ним.
— TCellLayer.Width —
Свойство: Width
Тип: Integer
Область видимости: public
Значение по умолчанию: -
Права доступа: только для чтения
Описание: содержит количество столбцов в слое. Свойство используется только для внутренних нужд, библиотека обеспечивает, что значение свойства Width для объекта TCellLayer всегда равно значению свойства ColCnt того экземпляра TCellGameBoard, который владеет данным слоем.
Методы TCellLayer
— TCellLayer.Create —
Метод: constructor Create(Collection: TCollection);
Назначение: создание нового экземпляра TCellLayer
Параметр Collection: ссылка на коллекцию, в которую добавляется создаваемый объект (коллекция должна иметь тип TCellLayers, иначе возникнет исключение EWrongLayerParent).
Область видимости: public
Тип метода: виртуальный, перекрытый (унаследован от TCollectionItem)
Описание: конструктор, создающий новый экземпляр класса TCellLayer. Явный вызов этого конструктора не рекомендуется. Для создания нового слоя следует воспользоваться методом Add или Insert свойства Layers соответствующего экземпляра TCellGameBoard.
— TCellLayer.Destroy —
Метод: destructor Destroy
Назначение: уничтожение экземпляра TCellLayer
Область видимости: public
Тип метода: виртуальный, перекрытый (унаследован от TObject)
Описание: стандартный деструктор, уничтожающий объект. Также вызывает деструкторы всех объектов массива Cells[X, Y] и всех движений, связанных со слоем.
Обратите внимание, что при удалении слоя индексы всех вышележащих слоёв коллекции уменьшаются на единицу, что создаёт определённые трудности при доступе к ним из кода. С другой стороны, динамическое создание и удаление слоёв требуется лишь в редких случаях, и обычно достаточно не удалять слой, а просто сделать его невидимым (см. свойство Visible) — в этом случае индексы не изменяются.
— TCellLayer.AddMovement —
Метод: procedure AddMovement(Movement: TMovement);
Назначение: связь движения со слоем
Параметр Movement: ссылка на объект движения, который связывается со слоем
Область видимости: protected
Тип метода: статический
Описание: добавляет движение в список движений слоя. После выполнения AddMovement данный объект слоя становится владельцем объекта движения, и этот объект не может быть добавлен другому слою (но может быть удалён, так как при удалении объект движения уведомляет об этом слой, в чей список он добавлен, и слой удаляет этот объект из своего списка).
Метод AddMovement вызывается из конструктора класса TMovement, вызывать его вручную не рекомендуется. См. также раздел "Использование движений".
— TCellLayer.DrawLayer —
Метод: procedure DrawLayer(Frame: TBitmap);
Назначение: перерисовка слоя
Параметр Frame: буфер, в котором происходит рисование слоя
Область видимости: protected
Тип метода: статический
Описание: метод служит для рисования фигур и движений слоя во внутреннем буфере объекта TCellGameBoard, который затем переносится на экран. Сам по себе этот метод ничего не рисует, он вызывает методы DrawCell для всех элементов массива Cells, а затем методы NextStep всех движений, добавленных в список движений слоя (таким образом, движения слоя оказываются выше фигур слоя, и движения рисуются в порядке их добавления в список слоя, т.е. в порядке создания, так как движение добавляется в список сразу при создании; движения, созданные позже, оказываются над теми, которые созданы раньше). См. также раздел "Таймер и перерисовка".
— TCellLayer.RemoveMovement —
Метод: procedure RemoveMovement(Movement: TMovement);
Назначение: удаляет объект движения из списка движений слоя
Параметр Movement: движение, которое следует удалить
Область видимости: protected
Тип метода: статический
Описание: удаляет движение из списка движений слоя. Метод не должен вызываться вручную, он вызывается из деструктора объекта TMovement.
Модуль: CellGameBoard
Предок: TObject
Экземпляр класса TGameCell представляет собой фигуру на доске, точнее, на одном из слоёв доски. В большинстве случаев экземпляры класса создаются автоматически классом TCellLayer, и доступ к ним осуществляется через свойство TCellLayer.Cells[X,Y]. Самостоятельное создание экземпляров TGameCell может понадобиться только при использовании некоторых видов движений (см. описание класса TCellMovement и раздел "Использование движений").
Свойства TGameCell
— TGameCell.Animated —
Свойство: Animated
Тип: TParentedBool
Область видимости: publiс
Значение по умолчанию: pbLayerDefault
Права доступа: для чтения и записи
Описание: определяет, будет ли фигура анимированной, т.е. будут ли при её отображении последовательно меняться фазы. Имеет смысл только при работе доски в анимированном режиме. Тип TParentedBool представляет собой перечислимый тип из трёх элементов: pbFalse, pbLayerDefault, pbTrue. При значении свойства, равном pbFalse, фигура не будет анимированной. При pbTrue будет анимированной. При pbLayerDefault фигура может быть как анимированной, так и неанимированной в зависимости от значения свойства Animated экземпляра TCellLayer, к которому относится фигура.
— TGameCell.Layer —
Свойство: Layer
Тип: TCellLayer
Область видимости: public
Значение по умолчанию: —
Права доступа: только для чтения
Описание: содержит ссылку на слой, которому принадлежит фигура
— TGameCell.Phase —
Свойство: Phase
Тип: Int64
Область видимости: public
Значение по умолчанию: 0
Права доступа: для чтения и записи
Описание: содержит индивидуальный для данной фигуры сдвиг фазы. См. раздел "Вычисление фазы фигуры".
— TGameCell.Speed —
Свойство: Speed
Тип: Integer
Область видимости: public
Значение по умолчанию: 1
Права доступа: для чтения и записи
Описание: определяет скорость анимации фигуры (имеет смысл только для анимированной фигуры в анимированном режиме доски). При значении свойства, равном 1, фаза фигуры меняется при каждом тике таймера, при значении 2 — раз в два тика, при значении 3 — раз в три тика и т.д., т.е. чем больше значение свойства, тем медленнее анимация фигуры (значения меньше единицы не допускаются). См. также разделы "Таймер и перерисовка" и "Вычисление фазы фигуры".
— TGameCell.State —
Свойство: State
Тип: Integer
Область видимости: public
Значение по умолчанию: –1
Права доступа: для чтения и записи
Описание: задаёт состояние (т.е. тип) фигуры. Все фигуры, доступные для отображения на данном слое, имеют уникальный номер в банке изображений, и свойство State задаёт номер этой фигуры. На диапазон значений свойства State не накладывается никаких специальных ограничений, и если будет задано состояние, отсутствующее в банке изображений, в данной позиции данного слоя просто не будет нарисовано никакой фигуры. Предполагается, что классы банков изображений для нумерации фигур не будут использовать отрицательные числа (хотя при необходимости пользователь может создать свой класс банка изображений, поддерживающий отрицательные состояния), поэтому значение –1, присваиваемое свойству по умолчанию, при работе со стандартными банками означает отсутствие фигуры.
Методы TGameCell
— TGameCell.Create —
Метод: constructor Create(ALayer: TCellLayer);
Назначение: создание нового экземпляра TGameCell
Параметр ALayer: ссылка на слой, которому будет принадлежать создаваемая фигура. Данный параметр не должен иметь значение nil.
Область видимости: public
Тип метода: статический
Описание: конструктор, создающий новый экземпляр класса TGameCell. Обычно вызывается неявно внутри библиотеки. Явный вызов конструктора может понадобиться только при использовании некоторых видов движений (см. описание класса TCellMovement и раздел "Использование движений").
Слой, переданный в качестве параметра ALayer, не становится владельцем для тех экземпляров TGameCell, которые созданы явным вызовом конструктора в программе. Ответственность за их удаление лежит на том коде, который их создал.
— TGameCell.Assign —
Метод: procedure Assign(Source: TGameCell);
Назначение: копирование свойств одного объекта TGameCell в другой
Параметр Source: ссылка на экземпляр TGameCell, который будет являться образцом для данного. Параметр Source не должен принимать значение nil.
Область видимости: public
Тип метода: статический
Описание: метод Assign копирует в текущий объект значение свойств Animated, Phase, Speed и State из объекта, переданного в качестве параметра Source. В неанимрованном режиме после вызова Assign будет перерисована доска, владеющая слоем, которому принадлежит данная ячейка, даже если не произошло изменение ни одного свойства.
Обратите внимание, что класс TGameCell не является наследником TPersistent, поэтому TGameCell.Assign — это совсем не то же самое, что и TPersistent.Assign. В качестве параметра Source могут выступать только ссылки на TGameCell, возможность расширения метода на другие классы не предусмотрена.
— TGameCell.DrawCell —
Метод: procedure DrawCell(Board: TBitmap; const CellRect: TRect; Index: Int64);
Назначение: рисование ячейки
Параметр Board: буфер, в котором рисуется доска
Параметр CellRect: прямоугольник, ограничивающий фигуру
Параметр Index: номер тика в анимированном режиме
Область видимости: public
Тип метода: статический
Описание: вычисляет текущую фазу фигуры в зависимости от анимированности фигуры, скорости, номера тика, фазы фигуры, слоя и доски и вызывает метод DrawCell банка изображений, назначенного слою, которому принадлежит фигура. См. также разделы "Таймер и перерисовка" и "Вычисление фазы фигуры".
Модуль: CellGameBoard
Предок: TComponent
Класс TCellBank является абстрактным предком для всех классов, реализующих банки изображений.
TCellBank унаследован от TComponent, чтобы банки изображений можно было редактировать во время разработки программы в интерактивном режиме.
Методы TCellBank
— TCellBank.DrawCell —
Метод: procedure DrawCell(Board: TBitmap; const CellRect: TRect; State: Integer; Phase: Int64);
Назначение: рисование фигуры с заданным состоянием и фазой
Параметр Board: буфер, на котором рисуется доска с фигурами
Параметр CellRect: прямоугольник, который отводится для фигуры
Параметр State: состояние фигуры
Параметр Phase: фаза фигуры
Область видимости: public
Тип метода: виртуальный, абстрактный
Описание: метод DrawCell вызывается при рисовании фигур слоя (см. описание метода DrawCell класса TGameCell и раздел "Таймер и перерисовка"). В качестве поверхности для рисования передаётся экземпляр TBitmap, который используется в качестве буфера для рисования доски. Это несколько отличается от общепринятой практики, когда для рисования передаётся экземпляр класса TCanvas. Передача TCanvas ограничила бы возможности рисования фигур тем, что позволяют сам класс TCanvas и GDI. Между тем буфер доски имеет фиксированный формат пикселей pf24Bit, что позволяет дополнительно использовать свойство ScanLine для создания необычных эффектов смешения цветов рисуемой фигуры с тем, что уже есть на доске.
Если значение параметра State выходит за пределы возможного диапазона состояний фигур, метод DrawCell не должен возбуждать никаких исключений, он просто должен ничего не рисовать. (Предполагается, что возможные состояния нумеруются последовательно, начиная с нуля, хотя при необходимости можно создавать классы банков изображений с иной нумерацией состояний.)
Если параметр Phase выходит за пределы диапазона фаз для заданной фигуры, метод DrawCell должен привести его к этому диапазону, взяв остаток от деления значения Phase на количество фаз фигуры. (Предполагается, что для каждого состояния фазы номеруются последовательно, начиная с нуля. В принципе, в наследниках TCellBank можно использовать иной способ нумерации фаз, но с такими банками изображений не сможет работать класс TAnimationSprite.)
— TCellBank.GetPhasesCount —
Метод: function GetPhasesCount(State: Integer): Int64;
Назначение: возвращает количество фаз для заданного состояния
Параметр State: номер состояния
Результат: количество фаз состояния
Область видимости: public
Тип метода: виртуальный, абстрактный
Описание: возвращает количество фаз, доступное для заданного состояния. Данное значение имеет смысл только в том случае, если наследник TCellBank следует рекомендованному правилу нумеровать фазы последовательно, начиная с нуля. Единственное место в библиотеке, где используется метод GetPhasesCount, — это код класса TAnimationSprite. Если вы создаёте свой класс для банка изображений, который не будете использовать совместно с TAnimationSprite, можете быть уверены, что перекрытый в нём метод GetPhasesCount никогда не будет вызван библиотекой. Однако рекомендуется всё-таки реализовывать метод GetPhasesCount должным образом для совместимости с возможными будущими версиями библиотеки.
— TCellBank.GetStatesCount —
Метод: function GetStatesCount: Integer;
Назначение: возвращает количество состояний в данном банке
Результат: количество состояний
Область видимости: public
Тип метода: виртуальный, абстрактный
Описание: возвращает количество состояний, реализуемых данных объектом банка. Данное значение имеет смысл только в том случае, если наследник TCellBank следует рекомендованному правилу нумеровать состояния последовательно, начиная с нуля. Данная версия библиотеки вызывает метод GetStatesCount только в тех случаях, когда используется банк данных TBitmapCellBank. Если вы создаёте свой класс для банка изображений, можете быть уверены, что перекрытый в нём метод GetStatesCount никогда не будет вызван библиотекой. Однако рекомендуется всё-таки реализовывать метод GetStatesCount должным образом для совместимости с возможными будущими версиями библиотеки.
Модуль: CellBanks
Предок: TCellBank
Класс TBitmapCellBank реализует банк изображений на основе растровых рисунков (TBitmap). Поддерживаются прозрачные и полупрозрачные изображения. Для данного класса имеется редактор компонентов, позволяющий управлять содержимым банка во время разработки программы (см. раздел "Работа с компонентами во время разработки").
Банк изображений, реализуемый классом TBitmapCellBank, содержит фигуры, которые нумеруются от нуля по возрастанию без пропусков (параметр, соответствующий номеру фигуры, во всех членах этого класса называется State). Для каждой фигуры может быть определена одна или несколько фаз, которые также нумеруются с нуля по возрастанию без пропусков (номер фазы задаётся параметром Phase). Количество фаз может быть разным для разных фигур. Таким образом, банк изображений инкапсулирует массив массивов изображений TBitmap одинакового размера.
Обратите внимание, что во многих функциях класса TBitmapCellBank параметр Phase имеет тип Integer, хотя обычно фаза задаётся значением типа Int64. Так сделано для экономии, потому что ресурсов даже самых мощных 32-разрядных компьютеров всё равно не хватит для того, чтобы создать такое количество изображений, для которого понадобятся 64-разрядные индексы.
Поддержка полупрозрачных изображений реализована с помощью API-функции AlphaBlend. У этой функции есть одна особенность работы. Нормальная формула для смешения цветов пикселя такова:
DestColor := Alpha*PictColor + (1 - Alpha)*DestColor
где DestColor — цвет пикселя приёмника, PictColor — цвет пикселя выводимой картинки, Alpha — коэффициент прозрачности (0 — полностью прозрачное изображение, 1 — полностью непрозрачное). Но функция AlphaBlend использует для повышения производительности использует упрощённую формулу:
DestColor := PictColor + (1 - Alpha)*DestColor
Здесь предполагается, что все пиксели рисунка заранее умножены на соответствующую величину прозрачности. При подготовке изображений для смешивания эту особенность необходимо учитывать, иначе можно получить результат, далёкий от ожидаемого. Класс TBitmapCellBank содержит методы, позволяющие при необходимости заранее умножить рисунок на его альфа-канал.
Редактирование рисунка с альфа-каналом непосредственно довольно затруднительно из-за низкой наглядности, поэтому нередко альфа-канал представляется в виде отдельной маски, которая является обычным растровым рисунком. Класс TBitmapCellBank, наряду со стандартным, поддерживает и такой метод представления маски. Для этого рисунок должен иметь удвоенную высоту. Его верхняя часть рассматривается как собственно рисунок, нижняя — как маска альфа-канала. Так как в цветовом пространстве есть три составляющих (красный, зелёный и синий), а у альфа-канала — только одна, подобное представление к избыточности и неоднозначности восстановления альфа-канала по его маске. Для решения этой проблемы в библиотеке предусмотрен тип TMaskChannel (модуль CellBanks), который позволяет выбрать один из наиболее часто встречающихся методов восстановления альфа-канала. Это перечислимый тип, содержащий следующие значения:
- mcRedChannel — значение альфа-составляющей совпадает с интенсивностью красного цвета, прочие цвета игнорируются
- mcGreenChannel — значение альфа-составляющей совпадает с интенсивностью зелёного цвета, прочие цвета игнорируются
- mcBlueChannel — значение альфа-составляющей совпадает с интенсивностью синего цвета, прочие цвета игнорируются
- mcIntensity — значению альфа-составляющей совпадает с яркостью, которая вычисляется по классической формуле 0.30*Red + 0.59*Green + 0.11*Blue
Все методы, которые работают с маской альфа-канала, имеют параметр типа TMaskChannel для выбора одного из этих методов.
При описании метода класса будут встречаться термины "лента рисунков" и "блок рисунков". Это несколько рисунков, объединённые в одном. Лента рисунков — это несколько рисунков, соединённых горизонтально в одно длинное изображение. Такой способ представления бывает удобен для представления фаз одного состояния. Блок рисунков — это большой рисунок, который может быть разбит вертикальными и горизонтальными линиями на рисунки требуемого размера. Блоки используются для представления нескольких состояний с одинаковым количеством фаз: каждая строка представляет собой состояние, все рисунки в строке — фазы этого состояния. Ленты и блоки могут передаваться в качестве параметров при загрузке изображений в банк — в этом случае банк сам разбивает их на отдельные рисунки.
Обратите внимание, что методы класса TBitmapCellBank допускают создавать такие состояния, в которых нет ни одной фазы. Однако это может рассматриваться только как промежуточный вариант при загрузке рисунков. К моменту использования банка все состояния должны содержать хотя бы по одной фазе, иначе при рисовании доски возникнут ошибки.
Свойства TBitmapCellBank
— TBitmapCellBank.Blended —
Свойство: Blended[State: Integer; Phase: Integer]
Тип: Boolean
Область видимости: public
Значение по умолчанию: —
Права доступа: для чтения и записи
Описание: определяет, имеет ли изображение фазы Phase фигуры State альфа-канал. При значении True соответствующее изображение имеет формат pf32Bit с альфа-каналом и выводится полупрозрачным. Значения свойств Transparent, TransparentColor и TransparentMode для такого изображения игнорируются, прозрачность определяется только значениями альфа-канала изображения. При значении False рисунок имеет формат pf24Bit и выводится обычным для класса TBitmap образом. В этом случае с помощью свойств Transparent, TransparentColor и TransparentMode может быть задан цвет, считающийся прозрачным.
Если задано недопустимое значение State или Phase, возникает исключение EWrongImageIndex.
— TBitmapCellBank.HorzAlign —
Свойство: HorzAlign
Тип: TCellHorzAlign
Область видимости: published
Значение по умолчанию: chaLeft
Права доступа: для чтения и записи
Описание: определяет способ горизонтального выравнивания изображения в прямоугольнике, который передаётся в метод DrawCell. Тип TCellHorzAlign является перечислением, содержащим три возможных значения:
- chaLeft — выравнивание по левому краю
- chaCenter — выравнивание по центру
- chaRight — выравнивание по правому краю
На горизонтальное выравнивание влияет также свойство HorzOffset
— TBitmapCellBank.HorzOffset —
Свойство: HorzOffset
Тип: Integer
Область видимости: published
Значение по умолчанию: 0
Права доступа: для чтения и записи
Описание: определяет горизонтальное смещение рисунка относительно вычисленной позиции. Сначала позиция вычисляется в соответствии с правилом, заданным свойством HorzAlign, затем она смещается на HorzOffset
— TBitmapCellBank.ImageHeight —
Свойство: ImageHeight
Тип: Integer
Область видимости: published
Значение по умолчанию: 32
Права доступа: для чтения и записи
Описание: содержит высоту рисунков, которые хранятся в банке изображений. Рисунки с иной высотой не могут быть добавлены в банк.
Данное свойство может быть изменено только в том случае, если банк не содержит ни одного изображения, в противном случае возникает исключение EBankIsNotEmpty.
— TBitmapCellBank.ImageWidth —
Свойство: ImageWidth
Тип: Integer
Область видимости: published
Значение по умолчанию: 32
Права доступа: для чтения и записи
Описание: содержит ширину рисунков, которые хранятся в банке изображений. Рисунки с иной шириной не могут быть добавлены в банк.
Данное свойство может быть изменено только в том случае, если банк не содержит ни одного изображения, в противном случае возникает исключение EBankIsNotEmpty.
— TBitmapCellBank.Transparent —
Свойство: Transparent[State: Integer; Phase: Integer]
Тип: Boolean
Область видимости: public
Значение по умолчанию: —
Права доступа: для чтения и записи
Описание: позволяет получать и устанавливать значение свойства Transparent экземпляра TBitmap, соответствующего изображению фазы Phase фигуры State.
Если задано недопустимое значение State или Phase, возникает исключение EWrongImageIndex.
— TBitmapCellBank.TransparentColor —
Свойство: TransparentColor[State: Integer; Phase: Integer]
Тип: TColor
Область видимости: public
Значение по умолчанию: —
Права доступа: для чтения и записи
Описание: позволяет получать и устанавливать значение свойства TransparentColor экземпляра TBitmap, соответствующего изображению фазы Phase фигуры State.
Если задано недопустимое значение State или Phase, возникает исключение EWrongImageIndex.
— TBitmapCellBank.TransparentMode —
Свойство: TransparentMode[State: Integer; Phase: Integer]
Тип: TTransparentMode
Область видимости: public
Значение по умолчанию: —
Права доступа: для чтения и записи
Описание: позволяет получать и устанавливать значение свойства TransparentMode экземпляра TBitmap, соответствующего изображению фазы Phase фигуры State.
Если задано недопустимое значение State или Phase, возникает исключение EWrongImageIndex.
— TBitmapCellBank.VertAlign —
Свойство: VertAlign
Тип: TCellVertAlign
Область видимости: published
Значение по умолчанию: cvaTop
Права доступа: для чтения и записи
Описание: определяет способ вертикального выравнивания изображения в прямоугольнике, который передаётся в метод DrawCell. Тип TCellVertAlign является перечислением, содержащим три возможных значения:
- cvaTop — выравнивание по верхнему краю
- cvaCenter — выравнивание по центру
- cvaBottom — выравнивание по нижнему краю
На горизонтальное выравнивание влияет также свойство VertOffset
— TBitmapCellBank.VertOffset —
Свойство: VertOffset
Тип: Integer
Область видимости: published
Значение по умолчанию: 0
Права доступа: для чтения и записи
Описание: определяет вертикальное смещение рисунка относительно вычисленной позиции. Сначала позиция вычисляется в соответствии с правилом, заданным свойством VertAlign, затем она смещается на VertOffset
— TBitmapCellBank.Waved —
Свойство: Waved
Тип: Boolean
Область видимости: published
Значение по умолчанию: False
Права доступа: для чтения и записи
Описание: если это свойство равно False, то фазы фигуры при анимации в клетке меняются циклически: за фазой, соответствующей последней картинки для данной фигуры, следует фаза, соответствующая первой картинке, и далее — снова по возрастанию. Если свойство Waved равно True, за фазой, соответствующей последней картинке, следует фаза, соответствующая предпоследней картинке, и так — до первой картинки, и только после этого порядок снова становится прямым. Другими словами, если для фигуры имеется четыре рисунка с номерами от 0 до 3, то при Waved = False они меняются в порядке 0 1 2 3 0 1 2 3 0 1 2 и т.д., а при Waved = True — в порядке 0 1 2 3 2 1 0 1 2 3 2 1 0 1 и т.д.
Методы TBitmapCellBank
— TBitmapCellBank.Create —
Метод: constructor Create(AOwner: TComponent);
Назначение: создание нового экземпляра TBitmapCellBank
Параметр AOwner: компонент, который должен быть владельцем создаваемого экземпляра
Область видимости: public
Тип метода: виртуальный, перекрытый (унаследован от TComponent)
Описание: стандартный конструктор компонента, создающий его новый экземпляр.
— TBitmapCellBank.Destroy —
Метод: destructor Destroy;
Назначение: уничтожение экземпляра TBitmapCellBank
Область видимости: public
Тип метода: виртуальный, перекрытый (унаследован от TObject)
Описание: стандартный деструктор, освобождающий объект и все занятые им ресурсы.
— TBitmapCellBank.AddBand —
Метод: function AddBand(Band: TBitmap): Integer;
Назначение: добавляет новое состояние и загружает в него ленту рисунков
Параметр Band: рисунок, содержащий ленту
Результат: номер добавленного состояния
Область видимости: public
Тип метода: статический
Описание: добавляет новое состояние в банк и загружает в него рисунки. Новое состояние добавляется в конец банка и получает номер, на единицу больший, чем самый большой номер состояния в банке до вызова этого метода. Если на момент вызова AddBand в банке нет ни одного состояния, новое состояние получает номер 0.
Рисунок, на который ссылается параметр Image, должен иметь ширину N*ImageWidth и высоту ImageHeight, где N — некоторое натуральное число, в противном случае возникает исключение EWrongImageSize. Рисунок разбивается по горизонтали на N равных частей, и каждый из них добавляется к заданному состоянию. Порядок добавления — слева направо, первый рисунок получает номер фазы, равный нулю, остальные нумеруются последовательно по возрастанию.
Банк не становится владельцем объекта Image, не сохраняет никаких ссылок на него и не несёт ответственности за его удаление.
Если рисунок Image имеет формат pf32Bit, рисунки к состоянию добавляются с альфа-каналом, в противном случае — без альфа-канала.
— TBitmapCellBank.AddBandToState —
Метод: procedure AddBandToState(State: Integer; Band: TBitmap);
Назначение: добавляет ленту рисунка к заданному состоянию
Параметр State: номер состояния, которому добавляются рисунки
Параметр Band: рисунок, содержащий ленту
Область видимости: public
Тип метода: статический
Описание: добавляет ленту рисунков к заданному состоянию. Рисунок, на который ссылается параметр Image, должен иметь ширину N*ImageWidth и высоту ImageHeight, где N — некоторое натуральное число, в противном случае возникает исключение EWrongImageSize. Рисунок разбивается по горизонтали на N равных частей, и каждый из них добавляется к заданному состоянию. Порядок добавления — слева направо, первый рисунок получает номер фазы, на единицу больший максимального номера фазы для данного состояния до вызова AddBandToState, остальные нумеруются последовательно по возрастанию.
Банк не становится владельцем объекта Image, не сохраняет никаких ссылок на него и не несёт ответственности за его удаление.
Если рисунок Image имеет формат pf32Bit, рисунки к состоянию добавляются с альфа-каналом, в противном случае — без альфа-канала.
Значение параметра State не должно выходить за пределы диапазона номеров состояний банка, иначе возникнет исключение EWrongImageIndex.
— TBitmapCellBank.AddBox —
Метод: procedure AddBox(Box: TBitmap);
Назначение: добавляет блок рисунков
Параметр Box: блок рисунков
Область видимости: public
Тип метода: статический
Описание: добавляет блок рисунков к банку данных. Ширина рисунка, заданного параметром Image, должна быть равна N*ImageWidth, высота — M*ImageHeight, где M и N — некоторые натуральные числа, иначе возникнет исключение EWrongImageSize. Сначала рисунок разбивается на N строк, потом каждая строка — на M частей. Каждая строка порождает новое состояние. Самая верхняя строка получает номер состояния, на единицу больше, чем наибольший номер состояния до вызова AddBox, остальные состояния нумеруются последовательно сверху вниз. Если до вызова AddBox в банке не было ни одного состояния, состояние, сформированное из первой строки, получает нулевой номер. Фазы в каждом состоянии нумеруются с нуля последовательно по возрастанию с левой части направо.
Банк не становится владельцем объекта Image, не сохраняет никаких ссылок на него и не несёт ответственности за его удаление.
Если рисунок Image имеет формат pf32Bit, рисунки в банк добавляются с альфа-каналом, в противном случае — без альфа-канала.
— TBitmapCellBank.AddImage —
Метод: function AddImage(State: Integer; Image: TBitmap): Integer;
Назначение: добавление рисунка к заданному состоянию
Параметр State: номер состояния, которому добавляется рисунок
Параметр Image: рисунок, который следует добавить к состоянию
Результат: номер фазы, который присваивается добавленному рисунку
Область видимости: public
Тип метода: статический
Описание: добавляет рисунок к заданному состоянию. Рисунок используется для того, чтобы рисовать одну из фаз состояния. Номер фазы, присваиваемый добавленному рисунку, на единицу больше максимального номера фазы для данного состояния до вызова AddImage. Если до вызова AddImage в состояние не было добавлено ни одной фазы, вновь добавленный рисунок получает номер фазы, равный нулю.
Банк не становится владельцем объекта Image, не сохраняет никаких ссылок на него и не несёт ответственности за его удаление. Для добавления в банк делается копия рисунка.
Если рисунок Image имеет формат pf32Bit, рисунок добавляется с альфа-каналом, в противном случае — без альфа-канала. Во втором варианте предпочтительнее использовать рисунок формата pf24Bit, так как это повышает производительность.
Размер рисунка Image должен быть ImageWidth*ImageHeight, иначе возникнет исключение EWrongImageSize. Значение параметра State не должно выходить за пределы диапазона номеров состояний банка, иначе возникнет исключение EWrongImageIndex.
— TBitmapCellBank.AddMaskedBand —
Метод: function AddMaskedBand(Band: TBitmap; MaskChannel: TMaskChannel): Integer;
Назначение: добавляет новое состояние и загружает в него ленту рисунков с маской альфа-канала
Параметр Band: рисунок, содержащий ленту
Параметр MaskChannel: способ восстановления альфа-канала по маске
Результат: номер добавленного состояния
Область видимости: public
Тип метода: статический
Описание: добавляет новое состояние в банк и загружает в него рисунки с маской. Новое состояние добавляется в конец банка и получает номер, на единицу больший, чем самый большой номер состояния в банке до вызова этого метода. Если на момент вызова AddMaskedBank в банке нет ни одного состояния, новое состояние получает номер 0.
Рисунок, на который ссылается параметр Image, должен иметь ширину N*ImageWidth и высоту 2*ImageHeight, где N — некоторое натуральное число, в противном случае возникает исключение EWrongImageSize. Рисунок разбивается по горизонтали на N равных частей, и каждая из них добавляется к заданному состоянию. Порядок добавления — слева направо, первый рисунок получает номер фазы, равный нулю, остальные нумеруются последовательно по возрастанию. При добавлении каждая часть рисунка разбивается по вертикали на две равные части. Верхняя часть рассматривается как собственно рисунок, нижняя — как маска альфа-канала для него. По какому правилу нижняя часть будет конвертироваться в маску, зависит от значения параметра MaskChannel.
Банк не становится владельцем объекта Image, не сохраняет никаких ссылок на него и не несёт ответственности за его удаление.
— TBitmapCellBank.AddMaskedBandToState —
Метод: procedure AddMaskedBandToState(State: Integer; Band: TBitmap; MaskChannel: TMaskChannel);
Назначение: добавляет ленту рисунка с маской альфа-канала к заданному состоянию
Параметр State: номер состояния, которому добавляются рисунки
Параметр Band: рисунок, содержащий ленту
Параметр MaskChannel: способ восстановления альфа-канала по маске
Область видимости: public
Тип метода: статический
Описание: добавляет ленту рисунков к заданному состоянию. Рисунок, на который ссылается параметр Image, должен иметь ширину N*ImageWidth и высоту 2*ImageHeight, где N — некоторое натуральное число, в противном случае возникает исключение EWrongImageSize. Рисунок разбивается по горизонтали на N равных частей, и каждая из них добавляется к заданному состоянию. Порядок добавления — слева направо, первый рисунок получает номер фазы, на единицу больший максимального номера фазы для данного состояния до вызова AddMaskedBandToState, остальные нумеруются последовательно по возрастанию. При добавлении каждая часть рисунка разбивается по вертикали на две равные части. Верхняя часть рассматривается как собственно рисунок, нижняя — как маска альфа-канала для него. По какому правилу нижняя часть будет конвертироваться в маску, зависит от значения параметра MaskChannel.
Банк не становится владельцем объекта Image, не сохраняет никаких ссылок на него и не несёт ответственности за его удаление.
Значение параметра State не должно выходить за пределы диапазона номеров состояний банка, иначе возникнет исключение EWrongImageIndex.
— TBitmapCellBank.AddMaskedBox —
Метод: procedure AddMaskedBox(Box: TBitmap; MaskChannel: TMaskChannel);
Назначение: добавляет блок рисунков с маской
Параметр Box: блок рисунков
Параметр MaskChannel: способ восстановления альфа-канала по маске
Область видимости: public
Тип метода: статический
Описание: добавляет блок рисунков к банку данных. Ширина рисунка, заданного параметром Image, должна быть равна N*ImageWidth, высота — 2*M*ImageHeight, где M и N — некоторые натуральные числа, иначе возникнет исключение EWrongImageSize. Сначала рисунок разбивается на N строк, потом каждая строка — на M частей. Каждая строка порождает новое состояние. Самая верхняя строка получает номер состояния, на единицу больше, чем наибольший номер состояния до вызова AddMaskedBox, остальные состояния нумеруются последовательно сверху вниз. Если до вызова AddMaskedBox в банке не было ни одного состояния, состояние, сформированное из первой строки, получает нулевой номер. Фазы в каждом состоянии нумеруются с нуля последовательно по возрастанию с левой части направо. Верхняя половина каждой части рисунка рассматривается как собственно рисунок, нижняя — как маска альфа-канала для него. По какому правилу нижняя часть будет конвертироваться в маску, зависит от значения параметра MaskChannel.
Банк не становится владельцем объекта Image, не сохраняет никаких ссылок на него и не несёт ответственности за его удаление.
— TBitmapCellBank.AddMaskedImage —
Метод: function AddMaskedImage(State: Integer; Image: TBitmap; MaskChannel: TMaskChannel): Integer;
Назначение: добавление рисунка с маской к заданному состоянию
Параметр State: номер состояния, которому добавляется рисунок
Параметр Image: рисунок, который следует добавить к состоянию
Параметр MaskChannel: способ восстановления альфа-канала по маске
Результат: номер фазы, который присваивается добавленному рисунку
Область видимости: public
Тип метода: статический
Описание: добавляет рисунок к заданному состоянию, используя часть рисунка как маску для альфа-канала. Номер фазы, присваиваемый добавленному рисунку, на единицу больше максимального номера фазы для данного состояния до вызова AddMaskedImage. Если до вызова AddMaskedImage в состояние не было добавлено ни одной фазы, вновь добавленный рисунок получает номер фазы, равный нулю.
Рисунок, содержащийся в Image, должен иметь ширину ImageWidth и высоту 2*ImageHeight (если это условие не будет выполнено, возникнет исключение EWrongImageSize). Он разбивается по вертикали на две равные части. Верхняя часть рассматривается как собственно рисунок, нижняя — как маска альфа-канала для него. По какому правилу нижняя часть будет конвертироваться в маску, зависит от значения параметра MaskChannel.
Банк не становится владельцем объекта Image, не сохраняет никаких ссылок на него и не несёт ответственности за его удаление. Для добавления в банк делается копия рисунка.
Значение параметра State не должно выходить за пределы диапазона номеров состояний банка, иначе возникнет исключение EWrongImageIndex.
— TBitmapCellBank.AddState —
Метод: function AddState: Integer;
Назначение: добавление нового состояния
Результат: номер добавленного состояния
Область видимости: public
Тип метода: статический
Описание: добавляет новое состояние в банк. Новое состояние добавляется в конец банка и получает номер, на единицу больший, чем самый большой номер состояния в банке до вызова этого метода. Если на момент вызова AddState в банке нет ни одного состояния, новое состояние получает номер 0.
— TBitmapCellBank.Assign —
Метод: procedure Assign(Source: TPersistent);
Назначение: копирование содержимого одного объекта в другой
Параметр Source: объект, служащий образцом для данного
Область видимости: public
Тип метода: виртуальный, перекрытый (унаследован от TPersistent)
Описание: добавляет к обычному поведению Assign возможность копирование содержимого одного объекта типа TBitmapCellBank в другой. Копируются рисунки и значения всех свойств. Собственные рисунки перед этим уничтожаются.
— TBitmapCellBank.AssignPictures —
Метод: procedure AssignPictures(Source: TBitmapCellBank);
Назначение: копирование рисунков одного объекта в другой
Параметр Source: объект, служащий образцом для данного
Область видимости: public
Тип метода: статический
Описание: копирует рисунки из указанного объекта в данный. Собственные рисунки при этом уничтожаются. Также копируются значения свойств ImageWidth и ImageHeight. В отличие от Assign, значения свойств HorzAlign, VertAlign, HorzOffset, VertOffset и Waved остаются без изменений.
— TBitmapCellBank.Clear —
Метод: procedure Clear;
Назначение: очистка содержимого банка
Область видимости: public
Тип метода: статический
Описание: удаляет из банка все изображения
— TBitmapCellBank.CreateSimpleMask —
Метод: procedure CreateSimpleMask(State, Phase: Integer; Transparency: Byte; Normalize: Boolean);
Назначение: создание альфа-канала на основе рисунка
Параметр State: номер состояния, для которого создаётся альфа-канал
Параметр Phase: номер фазы, для которой создаётся альфа-канал
Параметр Transparency: общая прозрачность (0 — непрозрачный рисунок, 255 — полностью прозрачный)
Параметр Normalize: следует ли нормализовать рисунок в соответствии с новой маской
Область видимости: public
Тип метода: статический
Описание: заполняет альфа-канал указанного изображения по следующему правилу: если Transparent = True и цвет пикселя совпадает с цветом, заданный свойством TransparentColor, для этого пикселя устанавливается полная прозрачность. В остальных случаях степень прозрачности пикселя определяется параметром Transparency. До вызова CreateSimpleMask указанное изображение может как иметь, так и не иметь альфа-канал, при необходимости он будет создан.
Из-за особенностей работы функции AlphaBlend изображение, которое нужно выводить с альфа-каналом, должно быть предварительно нормализовано (попиксельно умножено на значение непрозрачности). При необходимости метод CreateSimpleMask может выполнить нормализацию, для этого надо, чтобы параметр Normalize был равен True.
Если задано недопустимое значение State или Phase, возникает исключение EWrongImageIndex.
— TBitmapCellBank.DeletePict —
Метод: procedure DeletePict(State, Phase: Integer);
Назначение: удаление рисунка из банка
Параметр State: номер состояния, из которого удаляется рисунок
Параметр Phase: номер фазы удаляемого рисунка
Область видимости: public
Тип метода: статический
Описание: удаляет из банка рисунок с заданным состоянием и фазой. Если в данном состоянии были фазы после удаляемого рисунка, их номера уменьшаются на единицу, чтобы сохранить непрерывность нумерации фаз. Само состояние не удаляется, даже если это был последний рисунок в нём.
Если задано недопустимое значение State или Phase, возникает исключение EWrongImageIndex.
— TBitmapCellBank.DeleteState —
Метод: procedure DeleteState(State: Integer);
Назначение: удаление состояния из банка
Параметр State: номер удаляемого состояния
Область видимости: public
Тип метода: статический
Описание: удаляет из банка заданное состояние. Если в данном банке были состояния с большими номерами, их номера уменьшаются на единицу, чтобы сохранить непрерывность нумерации состояний. Допускается удаление как пустых состояний, так и состояний, в которые уже загружены рисунки.
Если задано недопустимое значение State, возникает исключение EWrongImageIndex.
— TBitmapCellBank.DrawCell —
Метод: procedure DrawCell(Board: TBitmap; const CellRect: TRect; State: Integer; Phase: Int64);
Назначение: рисование фигуры с заданным состоянием и фазой
Параметр Board: буфер, на котором рисуется доска с фигурами
Параметр CellRect: прямоугольник, который отводится для фигуры
Параметр State: состояние фигуры
Параметр Phase: фаза фигуры
Область видимости: public
Тип метода: виртуальный, перекрытый (унаследован от TCellBank)
Описание: рисует фигуры с заданными состоянием и фазой в прямоугольнике CellRect. Расположение фигуры относительно границ прямоугольника определяется свойствами HorzAlign, VertAlign, HorzOffset и VertOffset. Если значение параметра State лежит за пределами диапазона номеров фигур, никакая фигура не выводится. Если значение параметра Phase лежит за пределами диапазона фаз данной фигуры (с учётом свойства Waved), берётся остаток от деления этого числа на количество фаз и получившееся значение используется как фаза фигуры.
Те изображения, для которых свойство Blended = True, выводятся с учётом их альфа-канала, что допускает вывод полупрозрачных изображений. Изображения со свойством Blended = False выводятся обычным для класса TBitmap способом, их прозрачность регулируется свойствами Transparent, TransparentColor и TransparentMode.
— TBitmapCellBank.DrawImage —
Метод: procedure DrawImage(Canvas: TCanvas; X, Y, State, Phase: Integer);
Описание: выводит заданное изображение в заданной точке
Параметр Canvas: канва, на которую осуществляется вывод
Параметр X: горизонтальная координата левой границы изображения
Параметр Y: вертикальная координата верхней границы изображения
Параметр State: состояние фигуры
Параметр Phase: фаза фигуры
Область видимости: public
Тип метода: статический
Описание: выводит заданное изображение в заданной точке. Изображение всегда выводится без учёта альфа-канала. Изображения с альфа-каналом всегда выводятся без прозрачного цвета, даже если для них Transparent = True (связано с тем, что класс TBitmap некорректно выводит такие изображения в режиме с прозрачным цветом).
Данный метод используется для обеспечения работы редактора компонента во время разработки программы и не должен вызываться во время её выполнения.
— TBitmapCellBank.ExchangePicts —
Метод: procedure ExchangePicts(State1, Phase1, State2, Phase2: Integer);
Назначение: обмен местами двух рисунков
Параметр State1: номер состояния первого рисунка
Параметр Phase1: номер фазы первого рисунка
Параметр State2: номер состояния второго рисунка
Параметр Phase2: номер фазы второго рисунка
Область видимости: public
Тип метода: статический
Описание: меняет местами два указанных рисунка в банке.
Если задано недопустимое значение State1, State2, Phase1 или Phase2, возникает исключение EWrongImageIndex.
— TBitmapCellBank.ExchangeStates —
Метод: procedure ExchangeStates(State1, State2: Integer);
Назначение: обмен местами двух состояний
Параметр State1: номер первого состояния
Параметр State2: номер второго состояния
Область видимости: public
Тип метода: статический
Описание: меняет местами два указанных состояния.
Если задано недопустимое значение State1 или State2, возникает исключение EWrongImageIndex.
— TBitmapCellBank.GetAlphaMask —
Метод: function GetAlphaMask(State, Phase: Integer): TBitmap;
Назначение: возвращает маску альфа-канала
Параметр State: номер состояния, маска изображения которого требуется
Параметр Phase: номер фазы, маска изображения которого требуется
Область видимости: public
Тип метода: статический
Описание: возвращает маску для заданного рисунка в виде градаций серого (таким образом, при использовании этой маски, например, в методе SetAlphaMask она будет правильно преобразована в альфа-канал при любом способе преобразования). Объект не сохраняет никаких ссылок на возвращаемый рисунок, который создаётся внутри GetAlphaMask. За освобождение этого рисунка отвечает программа, вызвавшая GetAlphaMask.
Если задано недопустимое значение State или Phase, возникает исключение EWrongImageIndex. Рисунок, заданный параметрами State и Phase, должен иметь альфа-канал, в противном случае возникает исключение ENoAlphaChannel.
— TBitmapCellBank.GetPhasesCount —
Метод: function GetPhasesCount(State: Integer): Int64;
Назначение: возвращает количество фаз для заданного состояния
Параметр State: номер состояния
Результат: количество фаз состояния
Область видимости: public
Тип метода: виртуальный, перекрытый (унаследован от TCellBank)
Описание: возвращает количество фаз, доступное для заданного состояния. Количество фаз возвращается с учётом значения свойства Waved. Если свойство Waved = False, возвращаемое значение совпадает с количеством загруженных фаз для данной фигуры. Если Waved = True, а количество фаз больше одной, то результатом будет удвоенное количество фаз минус два. Это связано с тем, что именно такое число фаз будет содержать один период цикла, в котором фаза сначала возрастает, а затем убывает.
— TBitmapCellBank.GetRealPhasesCount —
Метод: function GetRealPhasesCount(State: Integer): Integer;
Назначение: возвращает количество фаз для заданного состояния
Параметр State: номер состояния
Результат: количество фаз состояния
Область видимости: public
Тип метода: статический
Описание: возвращает количество фаз, загруженное для заданного состояния. В отличие от GetPhasesCount, возвращает количество загруженных изображений, которое не зависит от значения свойства Waved.
— TBitmapCellBank.GetStatesCount —
Метод: function GetStatesCount: Integer;
Назначение: возвращает количество состояний в данном банке
Результат: количество состояний
Область видимости: public
Тип метода: виртуальный, перекрытый (унаследован от TCellBank)
Описание: возвращает количество состояний, созданных в данном банке изображений. Количество состояний возвращается с учётом тех состояний, для которых ещё не созданы фазы.
— TBitmapCellBank.Normalize —
Метод: procedure Normalize(State,Phase:Integer);
Назначение: нормализация рисунка по его альфа-каналу
Параметр State: номер состояния, изображение которого нормализуется
Параметр Phase: номер фазы, изображение которой нормализуется
Область видимости: public
Тип метода: статический
Описание: из-за особенностей работы функции AlphaBlend изображение, которое нужно выводить с альфа-каналом, должно быть предварительно нормализовано (попиксельно умножено на значение непрозрачности). Метод Normalize позволяет выполнить эту нормализацию для указанного рисунка. Будьте осторожны, чтобы не нормализовать рисунок, который в этом не нуждается, так как в общем случае восстановить исходный рисунок из нормализованного невозможно.
Если задано недопустимое значение State или Phase, возникает исключение EWrongImageIndex. Нормализуемый рисунок должен иметь альфа-канал, иначе возникает исключение ENoAlphaChannel.
— TBitmapCellBank.SetAlphaMask —
Метод: procedure SetAlphaMask(State, Phase: Integer; Mask: TBitmap; MaskChannel: TMaskChannel);
Назначение: формирует альфа-канал рисунка в соответствии с маской
Параметр State: номер состояния рисунка
Параметр Phase: номер фазы рисунка
Параметр Mask: рисунок, содержащий маску
Параметр MaskChannel: способ восстановления альфа-канала по маске
Область видимости: public
Тип метода: статический
Описание: формирует альфа-канал заданного изображения по его маске. До вызова SetAlphaMask изображение может как иметь, так и не иметь альфа-канал — при необходимости тип изображения будет изменён.
Банк не становится собственником объекта Mask, не сохраняет никаких ссылок на него и не отвечает за его удаление.
Если задано недопустимое значение State или Phase, возникает исключение EWrongImageIndex. Если ширина рисунка Mask отлична от ImageWidth или высота отлична от ImageHeight, возникает исключение EWrongImageSize.
Модуль: CellGameBoard
Предок: TObject
Класс TMovementFinishNotifier — это абстрактный класс, потомки которого служат для получения уведомлений о том, что движение завершено. Это низкоуровневый класс, работать с которым придётся крайне редко, так как его главная задача — это обеспечение функциональности методов WaitMovement и WaitMovements класса TCellGameBoard, а также класса TMovementsManager. В подавляющем большинстве случаев достаточно этих высокоуровневых средств, а самостоятельное создание наследников от TMovementFinishNitificator или иная работа с этим классом не требуется. В библиотеке все наследники от этого класса создаются только в секции implementation, т.е. пользователю они недоступны, так как нужды в них нет.
По сути дела, каждый экземпляр такого класса — приёмник события об окончании движения. Реализация дополнительных объектов вместо обычных обработчиков событий понадобилась потому, что для одного события требуется иметь несколько независимых обработчиков, а обычные события делать это не позволяют.
Подписка на события, реализуемые с помощью этого класса, осуществляется методом AddNotifier класса TMovement.
Методы TMovementFinishNotifier
— TMovementFinishNotifier.MovementFinished —
Метод: procedure MovementFinished(Sender: TMovement);
Назначение: получает уведомление о завершении движения
Параметр Sender: движение, которое завершается
Область видимости: public
Тип метода: виртуальный, абстрактный
Описание: вызывается из деструктора объекта TMovement, на событие завершения которого подписан данный объект. Должен быть перекрыт в потомке, чтобы обеспечить нужную реакцию на это событие.
Модуль: CellGameBoard
Предок: TObject
Класс TMovement представляет собой базовый абстрактный класс, от которого наследуются все движения. Движение — это специальный объект, с помощью которого на доске можно рисовать произвольные изображения, не являющиеся фигурами. В частном случае движение может рисовать статическое, неподвижное изображение, но основная задача движений — это реализация тех видов анимации, которые не сводятся к изменению фаз фигур, поэтому они и называются движениями. Подробнее движения описаны в разделе "Использование движений".
Свойства TMovement
— TMovement.Layer —
Свойство: Layer
Тип: TCellLayer
Область видимости: public
Значение по умолчанию: —
Права доступа: только для чтения
Описание: содержит ссылку на слой, которому принадлежит движение.
Методы TMovement
— TMovement.Create —
Метод: constructor Create(ALayer: TCellLayer);
Назначение: создаёт новый экземпляр TMovement
Параметр ALayer: ссылка на слой, к которому будет привязано движение. Не должен иметь значение nil
Область видимости: public
Тип метода: статический
Описание: конструктор, создающий новый экземпляр класса TMovement и привязывающий его к слою ALayer. Слой становится владельцем движения, т.е. при удалении слоя движение будет удалено автоматически. Тем не менее, ручное удаление движений тоже возможно.
Так как TMovement является абстрактным классом, вызов TMovement.Create имеет смысл делать только из конструкторов унаследованных классов.
— TMovement.Destroy —
Метод: destructor Destroy;
Назначение: уничтожает объект движения
Область видимости: public
Тип метода: виртуальный, перекрытый (унаследован от TObject)
Описание: удаляет заданный объект. В большинстве случаев явное удаление движений не требуется, так как конечные движения удаляются автоматически при завершении, а при удалении слоя он удаляет все связанные с ним движения. Тем не менее, при необходимости можно удалять движение и явно. Используемый при этом механизм уведомлений извещает слой, что принадлежащее ему движение удаляется, поэтому явное удаление движения не приводит ни к каким неприятным последствиям.
— TMovement.AddNotifier —
Метод: procedure AddNotifier(Notifier: TMovemenTFinishNotifier);
Назначение: подписывает объект на уведомление о завершении
Параметр Notifier: объект, осуществляющий реакцию на завершение движения
Область видимости: public
Тип метода: статический
Описание: добавляет объект типа TMovementFinishNotifier (точнее, наследника данного типа) в список объектов для уведомления об окончании движения. Вызов данного метода допустим только для конечного движения, при его вызове для бесконечного движения возникнет исключение EEndlessMovement. После завершения движения у объекта Notifier будет вызван метод MovementFinished. Программист, вызывающий метод AddNotifier, обязан сам следить за тем, чтобы к моменту окончания движения объект, на который ссылается параметр Notifier, не перестал существовать.
Добавление одного и того же объекта в список несколько раз не допускается, при попытки повторного добавления в список объекта, который уже есть в нём, возникает исключение EDuplicateNotifier.
Явный вызов метода AddNotifier обычно не требуется. Он используется для внутренних нужд методов WaitMovement и WaitMovements класса TCellGameBoard, а также класса TMovementsManager.
— TMovement.IsEndless —
Метод: function IsEndless: Boolean;
Назначение: позволяет узнать, является ли движение бесконечным
Результат: True, если движение бесконечно, False, если конечно
Область видимости: protected
Тип метода: виртуальный, абстрактный
Описание: позволяет узнать, является ли движение бесконечным. Бесконечное движение — это движение, которое не имеет логического конца, оно будет продолжаться до тех пор, пока его объект не будет удалён явным образом. Конечные движения имеют логический конец (например, перемещение фигуры из одной клетки в другую заканчивается, когда фигура оказывается во второй клетке).
— TMovement.NextStep —
Метод: function NextStep(Board: TBitmap; Phase: Int64): Boolean;
Назначение: рисует очередную фазу движения
Параметр Board: буфер, на котором рисуется доска
Параметр Phase: номер тика (значение всегда совпадает со значением свойства Tick той доски, к которой относится движение)
Результат: False, если нарисованная фаза движения не последняя, True, если последняя
Область видимости: protected
Тип метода: виртуальный, абстрактный
Описание: данный метод вызывается каждый раз при перерисовке доски и рисует фазу движения, соответствующую текущему тику (см. раздел "Таймер и перерисовка"). Данный метод вызывается не чаще раза за один тик. В качестве поверхности для рисования, как обычно, передаётся сам буфер, а не его Canvas, чтобы можно было использовать нестандартные возможности смешения цветов. Метод NextStep вызывается из метода DrawLayer класса TCellLayer. Если NextStep вернул True, объект движения сразу же уничтожается. Таким образом, разработчик класса движения несёт ответственность за согласованность действий методов IsEndless и NextStep: у бесконечного движения IsEndless возвращает True, а NextStep всегда возвращает False. У конечного движения IsEndless возвращает False, а NextStep должен в конце концов вернуть True.
— TMovement.RemoveNotifier —
Метод: procedure RemoveNotifier(Notifier: TMovemenTFinishNotifier);
Назначение: удаляет объект из списка получателей уведомления о завершении движения
Параметр Notifier: объект, который должен быть удалён из списка
Область видимости: public
Тип метода: статический
Описание: удаляет из списка получателей уведомления о завершении движения объект, ранее добавленный туда с помощью метода AddNotifier. Допускается в качестве параметра передавать и объекты, отсутствующие в списке — в этом случае метод RemoveNotifier ничего не делает.
Модуль: Movements
Предок: TMovement
Значительная часть движения, которые используются в играх, может быть сведена к перемещению некоторого графического объекта (далее — спрайта) по некоторой траектории. Спрайт может быть анимированным или не анимированным, траектория в частном случае может вырождаться в точку, но всё равно у реализации такого рода движений есть много общего. Это общее реализуется классом TSpriteMovement и двумя тесно связанными с ним абстрактными классами TSpriteProducer (поставщик спрайтов) и TCoordProducer (поставщик координат). Если коротко, то поставщик спрайтов отвечает за рисование очередной фазы спрайта в заданной точке, а поставщик координат обеспечивает вычисление этой очередной точки. Наследники абстрактных классов TSpriteProducer и TCoordProducer обеспечивают конкретную логику рисования спрайтов и вычисления координат. Соответственно, основная задача класса TSpriteMovement — это получение у поставщика координат координаты и передачи её поставщику спрайтов, чтобы он нарисовал очередную фазу в ней.
Класс TSpriteMovement является полностью функциональным, т.е. разработчик может создавать его экземпляры и использовать их непосредственно. Но для ряда движений характерны определённые часто используемые комбинации поставщиков спрайтов и координат, поэтому от TSpriteMovement тоже порождаются наследники. Основная функциональность этих наследников заключается в том, что они не заставляют разработчика вручную создавать экземпляры определённых наследников TCoordProducer и TSpriteProducer, а создают их самостоятельно на основе переданных в конструкторе параметров.
Свойства TSpriteMovement
— TSpriteMovement.CoordProducer —
Свойство: CoordProducer
Тип: TCoordProducer
Область видимости: protected
Значение по умолчанию: —
Права доступа: для чтения и записи
Описание: содержит ссылку на экземпляр класса TCoordProducer или его наследника, связанного с данным экземпляром TSpriteMovement. Изменение значения свойства CoordProducer возможно только до начала рисования первой фазы движения, в противном случае будет выдано исключение EMovementAlreadyStarted.
Если на момент присваивания значения свойству CoordProducer оно уже ссылается на некоторый объект, этот объект уничтожается.
— TSpriteMovement.Duration —
Свойство: Duration
Тип: Integer
Область видимости: public
Значение по умолчанию: 0
Права доступа: для чтения и записи
Описание: продолжительность существования движения в миллисекундах. Позволяет ограничить продолжительность движения определённым временем, после которого оно перестаёт существовать. Значение 0 означает, что время жизни движения не ограничено (это не значит, что движение будет бесконечным — оно может быть конечным из-за конечности траектории или спрайта).
Время существования движения вычисляется по количеству тиков и продолжительности одного тика доски. Если за время существования движения значение свойства TimerInterval доски было изменено, возможны ошибки при определении времени, которое существует движение, и тогда время существования движения не будет совпадать с временем, заданным свойством Duration.
— TSpriteMovement.InitialPhase —
Свойство: InitialPhase
Тип: Int64
Область видимости: public
Значение по умолчанию: 0
Права доступа: для чтения и записи
Описание: сдвиг фазы спрайта. При каждом тике вычисляется очередная фаза движения (см. свойство Speed), которая определяет, какой этап движения будет нарисован. При передаче этой фазы поставщику спрайтов к ней добавляется значение InitialPhase, чтобы можно было управлять начальной фазой спрайта. Наиболее полезным это свойство является для циклических бесконечных спрайтов, когда надо, чтобы движение было синхронизировано с некоторым событием определённым образом.
Аналогичного свойства для начальной фазы поставщика координат нет, потребность подстраивать фазу координат требуется редко. В принципе, можно предположить, что она может потребоваться для некоторых бесконечных движений по замкнутой траектории, но в этом случае такая возможность легко реализуется порождением наследника от TCoordProducer, обладающего соответствующим свойством.
— TSpriteMovement.Speed —
Свойство: Speed
Тип: Integer
Область видимости: public
Значение по умолчанию: 1
Права доступа: для чтения и записи
Описание: скорость движения. При рисовании очередного шага движения вычисляется его фаза по формуле:
Phase = (Tick - Tick0) div Speed
где Tick — текущий номер тика доски, Tick0 — номер тика на момент рисования начальной фазы движения. Если значение вычисленной таким образом фазы отличается от значения, полученного при предыдущем вызове NextStep, движение запрашивает у поставщика координат очередную координату траектории, если не отличается, использует те же координаты, что и на предыдущем шаге. Эта же фаза, сдвинутая на значение InitialPhase, передаётся поставщику спрайтов, который при одинаковой фазе должен нарисовать одинаковое изображение. Таким образом, свойство Speed, по сути дела, является демультипликатором скорости: при значении 1 скорость максимальна, при значении 2 — в два раза меньше максимальной, при значении 3 — в три раза, и т.д. Значения, меньшие 1, не допускаются.
— TSpriteMovement.Sprite —
Свойство: Sprite
Тип: TSpriteProducer
Область видимости: protected
Значение по умолчанию: —
Права доступа: для чтения и записи
Описание: содержит ссылку на экземпляр класса TSpriteProducer или его наследника, связанного с данным экземпляром TSpriteMovement. Изменить значение свойства Sprite можно в любой момент.
Если на момент присваивания значения свойству Sprite оно уже ссылается на некоторый объект, этот объект уничтожается.
Методы TSpriteMovement
— TSpriteMovement.Create —
Метод: constructor Create(ALayer: TCellLayer; ACoordProducer: TCoordProducer; ASprite: TSpriteProducer);
Назначение: создание нового экземпляра TSpriteMovement
Параметр ALayer: слой, которому принадлежит движение. Не должен быть равен nil
Параметр ACoordProducer: поставщик координат
Параметр ASprite: поставщик спрайтов
Область видимости: public
Тип метода: статический
Описание: конструктор, создающий новый экземпляр класса TSpriteMovement. Если программист использует данный конструктор для создания непосредственно класса TSpriteMovement, значения параметров ACoordProducer и ASprite не должны быть равными nil. Если конструктор вызывается из конструктора унаследованного класса, эти параметры могут быть nil, но тогда ссылки на поставщиков должны быть заданы через свойства CoordProducer и Sprite до начала рисования движения (т.е. до первого вызова NextStep).
Экземпляр TSpriteMovement становится владельцем своих поставщиков спрайтов и координат независимо от того, как они были ему назначены — через конструктор или через свойства CoordProducer и Sprite. Это означает, что экземпляр TSpriteMovement полностью отвечает за удаление экземпляров поставщиков после того как нужда в них отпала, и, следовательно, программист не должен ни самостоятельно уничтожать объекты поставщиков, ни передавать одни и те же объекты разным экземплярам TSpriteMovement.
— TSpriteMovement.Destroy —
Метод: destructor Destroy;
Назначение: удаление экземпляра TSpriteMovement
Область видимости: public
Тип метода: виртуальный, перекрытый (унаследован от TObject)
Описание: уничтожает экземпляр TSpriteMovement и экземпляры поставщиков координат и спрайтов, которыми он владеет.
— TSpriteMovement.IsEndless —
Метод: function IsEndless: Boolean;
Назначение: позволяет узнать, является ли движение бесконечным
Результат: True, если движение бесконечно, False, если конечно
Область видимости: protected
Тип метода: виртуальный, перекрытый (унаследован от TMovement)
Описание: позволяет определить, будет ли движение бесконечным. Бесконечным движение является тогда и только тогда, когда выполняются следующие условия: свойство Duration имеет значение 0, а методы IsEndless и поставщика координат, и поставщика спрайтов, связанных с данным экземпляром TSpriteMovement, возвращают True.
— TSpriteMovement.NextStep —
Метод: function NextStep(Board: TBitmap; Phase: Int64): Boolean;
Назначение: рисует очередную фазу движения
Параметр Board: буфер, на котором рисуется доска
Параметр Phase: номер тика (значение всегда совпадает со значением свойства Tick той доски, к которой относится движение)
Результат: False, если нарисованная фаза движения не последняя, True, если последняя
Тип метода: виртуальный, перекрытый (унаследован от TMovement)
Описание: рисует очередную фазу движения. Для этого получает очередные координаты от поставщика координат и передаёт их поставщику спрайтов. Оба поставщика должны быть заданы, иначе возникнет исключение EProducerNotAssigned.
Метод возвращает True, если истекло время, заданное свойством Duration, или если один из поставщиков сообщил, что передал последнюю координату или последний спрайт. Метод NextStep следит также за тем, чтобы не вернуть True раньше времени в тех случаях, когда Speed > 1. В этом случае True возвращается не сразу после того, как поставщик сообщит о завершении работы, а после того, как пройдёт нужное количество тиков, чтобы последняя фаза была по продолжительности такой же, как и предыдущие.
Модуль: Movements
Предок: TObject
Класс TCoordProducer является абстрактным классом, от которого порождаются различные классы поставщиков координат (см. описание класса TSpriteMovement).
Методы TCoordProducer
— TCoordProducer.GetNextCoord —
Метод: function GetNextCoord(var X, Y: Integer): Boolean;
Назначение: возвращает координаты очередной точки траектории
Параметр X: выходной параметр, через который возвращается горизонтальная координата
Параметр Y: выходной параметр, через который возвращается вертикальная координата
Результат: True, если была возвращена координата конечной точки траектории, False — если не конечной
Область видимости: protected
Тип метода: виртуальный, абстрактный
Описание: возвращает координаты очередной точки траектории для движения, реализуемого классом TCoordProducer. Данный метод вызывается из метода NextStep класса TSpriteMovement, когда требуется получить следующую точки траектории. Таким образом, наследники TCoordProducer должны реализовывать этот метод таким образом, чтобы он "помнил", какую точку вернул в предыдущий раз, и при очередном вызове возвращал следующую.
Программист, создающий наследника от TCoordProducer, несёт ответственность за согласованность реализации методов IsEndless и GetNextCoord: если IsEndless возвращает True, GetNextCoord не должен никогда возвращать True, если IsEndless возвращает False, GetNextCoord на каком-то шаге должен вернуть True.
— TCoordProducer.IsEndless —
Метод: function IsEndless: Boolean;
Назначение: позволяет узнать, является ли траектория бесконечной
Результат: True, если траектория бесконечна, False, если конечна
Область видимости: protected
Тип метода: виртуальный, абстрактный
Описание: возвращает True, если объект реализует бесконечную (например, замкнутую) траекторию, False — если конечную. Реализация метода IsEndless должна быть согласована с реализацией GetNextCoord (см. его описание).
Модуль: Movements
Предок: TObject
Класс TSpriteProducer является абстрактным классом, от которого порождаются различные классы поставщиков спрайтов (см. описание класса TSpriteMovement).
Методы TSpriteProducer
— TSpriteProducer.DrawSprite —
Метод: function DrawSprite(Board: TBitmap; X, Y: Integer; Phase: Int64): Boolean;
Назначение: рисует спрайт с заданной фазой в заданной координате
Параметр Board: буфер, в котором рисуется доска
Параметр X: горизонтальная координата спрайта
Параметр Y: вертикальная координата спрайта
Параметр Phase: фаза спрайта
Результат: True, если нарисована последняя фаза спрайта, False — если не последняя
Область видимости: protected
Тип метода: виртуальный, абстрактный
Описание: рисует спрайт с заданной фазой в заданных координатах. Передаваемые координаты — это координаты левого верхнего угла спрайта, фаза вычисляется методом NextStep класса TSpriteMovement. Если переданная фаза — последняя, возвращает True, если не последняя — False. Реализация метода должна быть согласована с реализацией метода IsEndless: если IsEndless возвращает True, DrawSprite не должен возвращать False, если IsEndless возвращает False, DrawSprite должен при какой-то фазе вернуть True.
— TSpriteProducer.IsEndless —
Метод: function IsEndless: Boolean;
Назначение: позволяет узнать, является ли последовательность спрайтов бесконечной
Результат: True, если последовательность спрайтов бесконечна, False, если конечна
Область видимости: protected
Тип метода: виртуальный
Описание: возвращает True, если объект реализует бесконечную (например, циклическую) последовательность спрайов, False — если конечную. Реализация метода IsEndless должна быть согласована с реализацией DrawSprite (см. его описание).
Метод не является абстрактным, в реализации класса TSpriteProducer он всегда возвращает True. Если наследник реализует конечную последовательность спрайтов, он должен перекрыть этот метод.
Модуль: Movements
Предок: TCoordProducer
Класс TPathCoords — это поставщик координат, который реализует движение по некоторой заранее рассчитанной траектории, которая может быть как замкнутой, так и не замкнутой. Замкнутые траектории считаются бесконечными, незамкнутые — конечными. TPathCoords возвращает координаты из заданного при создании массива координат последовательно без повторов. Если требуется, чтобы спрайт задержался в некоторой точке на какое-то время, эту точку в массиве нужно продублировать несколько раз подряд.
С помощью данного класса также можно реализовать движение, заключающееся в смене изображений на одном месте — для этого надо задать замкнутую траекторию, состоящую из одной точки. В общем случае для реализации такого движения лучше написать свой класс, унаследовав его напрямую от TMovement, но в тех случаях, когда уже имеется готовый класс поставщика спрайтов, реализующий подходящее изображение, может оказаться удобным не писать такого наследника, а воспользоваться классом TSpriteMovement, назначив ему поставщиком координат экземпляр TPathCoords с такой вырожденной траекторией.
Свойства TPathCoords
— TPathCoords.Looped —
Свойство: Looped
Тип: Boolean
Область видимости: public
Значение по умолчанию: —
Права доступа: для чтения и записи
Описание: если True, траектория считается замкнутой, если False — не замкнутой. Значение по умолчанию у данного свойства отсутствует, потому что его начальное значение выбирается исходя из параметров конструктора.
— TPathCoords.Path —
Свойство: Path
Тип: TDynPointArray
Область видимости: protected
Значение по умолчанию: —
Права доступа: только для чтения
Описание: массив точек, составляющих траекторию. Тип TDynPointArray — это динамический массив из элементов типа TPoint. В этом массиве хранятся координаты, которые будет возвращать функция GetNextCoord.
Методы TPathCoords
— TPathCoords.Create (overload 1) —
Метод: constructor Create(Len: Integer; ALooped: Boolean);
Назначение: создание нового экземпляра TPathCoords
Параметр Len: количество точек в траектории
Параметр ALooped: определяет, будет ли траектория замкнутой
Область видимости: protected
Тип метода: статический, перегруженный
Описание: создаёт новый экземпляр класса TPathCoords, массив Path которого содержит Len точек, а свойство Looped имеет значение ALooped. Содержимое массива координат никак не инициализируется, его нужно инициализировать потом через свойств Path. Так как этот свойство имеет видимость protected, использовать этот вариант конструктора имеет смысл только в наследниках TPathCoords, и, соответственно, он тоже имеет видимость protected.
— TPathCoords.Create (overload 2) —
Метод: constructor Create(const APath: array of TPoint; ALooped: Boolean);
Назначение: создание нового экземпляра TPathCoords
Параметр APath: массив точек, из которых будет состоять траектория
Параметр ALooped: определяет, будет ли траектория замкнутой
Область видимости: public
Тип метода: статический, перегруженный
Описание: создаёт новый экземпляр класса TPathCoords, массив Path которого содержит точки, переданные в качестве параметра APath, а свойство Looped имеет значение ALooped. Данный конструктор подходит для создания экземпляров TPathCoords вручную, а не только для вызова из наследников.
— TPathCoords.Create (overload 3) —
Метод: constructor Create(X, Y: Integer);
Назначение: создание нового экземпляра TPathCoords
Параметр X: горизонтальная координата единственной точки пути
Параметр Y: вертикальная координата единственной точки пути
Область видимости: public
Тип метода: статический, перегруженный
Описание: создаёт новый экземпляр класса TPathCoords, массив Path которого содержит вырожденную траекторию, замкнутую и состоящую из одной точки с координатами (X; Y). Данный вариант конструктора следует использовать в тех случаях, когда нужно вывести неподвижный спрайт.
— TPathCoords.GetNextCoord —
Метод: function GetNextCoord(var X, Y: Integer): Boolean;
Назначение: возвращает координаты очередной точки траектории
Параметр X: выходной параметр, через который возвращается горизонтальная координата
Параметр Y: выходной параметр, через который возвращается вертикальная координата
Результат: True, если была возвращена координата конечной точки траектории, False — если не конечной
Область видимости: protected
Тип метода: виртуальный, перекрытый (унаследован от TCoordProducer)
Описание: возвращает координаты очередной точки из массива Path. Точки выбираются последовательно без повторов. Если при очередном вызове GetNextCoord была извлечена последняя точка, а свойство Looped имеет значение False, метод GetNextCoord вернёт True. Если же свойство Looped равно True, GetNextCoord всегда возвращает False, а после последней точки массива вновь перейдёт к первой.
— TPathCoords.IsEndless —
Метод: function IsEndless: Boolean;
Назначение: позволяет узнать, является ли траектория бесконечной
Результат: True, если траектория бесконечна, False, если конечна
Область видимости: protected
Тип метода: виртуальный, перекрытый (унаследован от TCoordProducer)
Описание: возвращает True, если траектория замкнутая (Looped = True) и False, если не замкнутая (Looped = False).
Модуль: Movements
Предок: TPathCoords
Класс TLineCoords является наследником TPathCoords, специализирующимся на незамкнутых прямых траекториях. От своего предка он отличается только конструкторами, которые облегчают инициализацию массива точек точками именно такой траектории.
Методы TLineCoords
— TLineCoords.Create (overload 1) —
Метод: constructor Create(X1, Y1, X2, Y2, PtCnt: Integer);
Назначение: создание нового экземпляра TLineCoords
Параметр X1: горизонтальная координата начальной точки траектории
Параметр Y1: вертикальная координата начальной точки траектории
Параметр X2: горизонтальная координата конечной точки траектории
Параметр Y2: вертикальная координата конечной точки траектории
Параметр PtCnt: количество точек траектории. Если этот параметр равен 0, траектория будет содержать столько точек, из скольких пикселей будет состоять отрезок от (X1;Y1) до (X2;Y2), включая начало и конец
Область видимости: public
Тип метода: статический, перегруженный
Описание: создаёт новый экземпляр TLineCoords, координаты точек из массива Path которого равномерно распределены вдоль отрезка прямой от (X1; Y1) до (X2; Y2), причём первая точка этого массива всегда будет (X1;Y1), последняя — (X2;Y2). Массив содержит PtCnt точек. Если PtCnt окажется больше, чем нужно пикселей для построения отрезка от (X1;Y1) до (X2;Y2), некоторые точки массива будут повторяться.
Значение свойства Looped устанавливается равным False.
Параметр PtCnt не должен быть равен 1 или принимать отрицательные значения, иначе возникнет исключение EWrongLineLength.
— TLineCoords.Create (overload 2) —
Метод: constructor Create(X1, Y1, X2, Y2: Integer; Scale: Extended);
Назначение: создание нового экземпляра TLineCoords
Параметр X1: горизонтальная координата начальной точки траектории
Параметр Y1: вертикальная координата начальной точки траектории
Параметр X2: горизонтальная координата конечной точки траектории
Параметр Y2: вертикальная координата конечной точки траектории
Параметр Scale: отношение количества точек траектории к количеству пикселей, составляющих отрезок от (X1;Y1) до (X2;Y2)
Область видимости: public
Тип метода: статический, перегруженный
Описание: создаёт новый экземпляр TLineCoords, координаты точек из массива Path которого равномерно распределены вдоль отрезка прямой от (X1; Y1) до (X2; Y2), причём первая точка этого массива всегда будет (X1;Y1), последняя — (X2;Y2). Количество точек массива определяется параметром Scale: оно равно количеству пикселей, которые составляют отрезок от (X1;Y1) до (X2;Y2), включая концы, умноженному на Scale. Если Scale > 1, некоторые точки массива будут повторяться.
Значение свойства Looped устанавливается равным False.
Моудль: Movements
Предок: TSpriteProducer
Класс TCellSprite реализует спрайт в виде фигуры на доске. По сути дела, на время существования движения на доске создаётся ещё одна фигура (экземпляр TGameCell), которая не привязана к какой-либо клетке и может быть нарисована в любом месте доски.
Свойства TCellSprite
— TCellSprite.Looped —
Свойство: Cell
Тип: TGameCell
Область видимости: public
Значение по умолчанию: —
Права доступа: только для чтения
Описание: содержит ссылку на экземпляр класса TGameCell, который реализует дополнительную фигуру. Способом отображения этой фигуры можно управлять через свойства класса TGameCell точно так же, как можно управлять отображением обычной фигуры через TCellLayer.Cells.
Методы TCellSprite
— TCellSprite.Create —
Метод: constructor Create(ACell: TGameCell);
Назначение: создание нового экземпляра TCellSprite
Параметр ACell: ссылка на объект типа TGameCell, который будет образцом для создаваемой фигуры. Не должен быть nil
Область видимости: public
Тип метода: статический
Описание: создаёт новый экземпляр класса TCellSprite, используя ACell как образец для новой фигуры. TCellSprite создаёт внутри себя собственный экземпляр TGameCell, а параметр ACell используется только как образец для этого экземпляра. Поэтому TCellSprite не становится владельцем объекта ACell, не несёт ответственности за его удаление и вообще не сохраняет на него никаких ссылок. Дальнейшие манипуляции с объектом ACell не оказывают никакого влияния на то, какая фигура будет нарисована в результате работы экземпляра TCellSprite.
— TCellSprite.Destroy —
Метод: destructor Destroy;
Назначение: уничтожение экземпляра TCellSprite
Область видимости: public
Тип метода: виртуальный, перекрытый (унаследован от TObject)
Описание: стандартный деструктор, уничтожающий экземпляр TCellSprite и все выделенные им ресурсы.
— TCellSprite.DrawSprite —
Метод: function DrawSprite(Board: TBitmap; X, Y: Integer; Phase: Int64): Boolean;
Назначение: рисует спрайт с заданной фазой в заданной координате
Параметр Board: буфер, в котором рисуется доска
Параметр X: горизонтальная координата спрайта
Параметр Y: вертикальная координата спрайта
Параметр Phase: фаза спрайта
Результат: True, если нарисована последняя фаза спрайта, False — если не последняя
Область видимости: protected
Тип метода: виртуальный, перекрытый (унаследован от TSpriteProducer)
Описание: рисует спрайт с заданной фазой в заданных координатах. По сути, вызывает метод Cell.DrawCell, для рисования фигуры с заданной фазой в прямоугольнике с координатами верхнего угла (X;Y) и шириной и высотой, равной значениям свойств доски CellWidth и CellHeight соответственно. Всегда возвращает False, предполагая бесконечную циклическую смену изображений.
Модуль: Movements
Предок: TSpriteMovement
Класс TCellMovement — это наследник TSpriteMovement, специализирующийся на перемещении фигур из одной клетки доски в другую по прямолинейной траектории. Он самостоятельно создаёт поставщиков координат и спрайтов, используя классы TCellSprite и TLineCoords. Класс TCellMovement ориентирован на работу по принципу "создал и забыл": для реализации нужного движения достаточно создать экземпляр TCellMovement задать нужные параметры, и перемещение фигуры будет реализовано, а после его завершения объект будет удалён (как, впрочем, и любой объект конечного движения).
Свойства TCellMovement
— TCellMovement.AssignDest —
Свойство: AssignDest
Тип: Boolean
Область видимости: public
Значение по умолчанию: —
Права доступа: для чтения и записи
Описание: определяет, будет ли изменена фигура в той ячейке, которая является целью движения. Если False, то никаких дополнительных действий после окончания движения не выполняется. Если True, то после завершения движения в той клетке, которая была указана как клетка назначения фигура подменяется на такую же, какая задана свойством Cell (подмена производится на том слое, которому принадлежит движение).
Данному свойству не всегда может быть присвоено True. Реализация класса TCellMovment допускает задавать в качестве координат клетки назначения такие координаты, которые лежат за пределами доски (это может понадобиться для реализации движений, в которых фигура улетает за край доски). В этом случае значение свойства AssignDest всегда будет False, присваивание ему значения True не возбудит исключения, но и не изменит значение свойства. Если же координаты клетки назначения лежат в пределах доски, то значение свойства AssignDest по умолчанию будет True, и его можно будет изменить на False и обратно в любой момент, пока существует объект движения.
— TCellMovement.Cell —
Свойство: Cell
Тип: TGameCell
Область видимости: public
Значение по умолчанию: —
Права доступа: только для чтения
Описание: ссылка на экземпляр TGameCell, реализующий перемещаемую фигуру. Это свойство просто даёт доступ к свойству Cell экземпляра класса TCellSprite, который является поставщиком спрайтов для данного экземпляра TCellMovement.
Методы TCellMovement
— TCellMovement.Create (overload 1) —
Метод: constructor Create(ACell: TGameCell; X1, Y1, X2, Y2: Integer; PtCnt: Integer);
Назначение: создание нового экземпляра TCellMovement
Параметр ACell: ссылка на экземпляр TGameCell, служащий образцом для перемещаемой фигуры. Не должен быть nil
Параметр X1: номер столбца клетки, из которой начинается движение
Параметр Y1: номер строки клетки, из которой начинается движение
Параметр X2: номер столбца клетки, в которой заканчивается движение
Параметр Y2: номер строки клетки, в которой заканчивается движение
Параметр PtCnt: количество точек в траектории
Область видимости: public
Тип метода: статический, перегруженный
Описание: создаёт экземпляр TCellMovement, реализующий движение фигуры, образцом которой служит ACell, из ячейки (X1;Y1) в ячейку (X2;Y2). Траектория состоит из PtCnt точек, равномерно расставленных по отрезку, соединяющему левые верхние углы клеток (X1;Y1) и (X2;Y2).
Создаваемый экземпляр TCellMovement не становится владельцем объекта ACell, не несёт ответственности за его удаление и вообще не сохраняет на него никаких ссылок. Дальнейшие манипуляции с объектом ACell не оказывают никакого влияния на то, какая фигура будет нарисована в результате работы экземпляра TCellMovement.
Создаваемый экземпляр движения будет принадлежать тому слою, который задаётся значением ACell.Layer.
Координаты как начальной, так и конечной клеток могут лежать вне доски.
— TCellMovement.Create (overload 2) —
Метод: constructor Create(ACell: TGameCell; X1, Y1, X2, Y2: Integer; Scale: Extended);
Назначение: создание нового экземпляра TCellMovement
Параметр ACell: ссылка на экземпляр TGameCell, служащий образцом для перемещаемой фигуры. Не должен быть nil
Параметр X1: номер столбца клетки, из которой начинается движение
Параметр Y1: номер строки клетки, из которой начинается движение
Параметр X2: номер столбца клетки, в которой заканчивается движение
Параметр Y2: номер строки клетки, в которой заканчивается движение
Параметр Scale: отношение количества точек траектории к количеству пикселей, составляющих отрезок от левого верхнего угла ячейки (X1;Y1) до левого верхнего угла ячейки (X2;Y2)
Область видимости: public
Тип метода: статический, перегруженный
Описание: создаёт экземпляр TCellMovement, реализующий движение фигуры, образцом которой служит ACell, из ячейки (X1;Y1) в ячейку (X2;Y2). Траектория состоит из точек, равномерно расставленных по отрезку, соединяющему левые верхние углы клеток (X1;Y1) и (X2;Y2). Количество точек траектории определяется параметром Scale: оно равно количеству пикселей, которые составляют отрезок от левого верхнего угла начальной клетки до левого верхнего угла конечной клетки, включая концы, умноженному на Scale.
Создаваемый экземпляр TCellMovement не становится владельцем объекта ACell, не несёт ответственности за его удаление и вообще не сохраняет на него никаких ссылок. Дальнейшие манипуляции с объектом ACell не оказывают никакого влияния на то, какая фигура будет нарисована в результате работы экземпляра TCellMovement.
Создаваемый экземпляр движения будет принадлежать тому слою, который задаётся значением ACell.Layer.
Координаты как начальной, так и конечной клеток могут лежать вне доски.
— TCellMovement.Create (overload 3) —
Метод: constructor Create(ALayer: TCellLayer; X1, Y1, X2, Y2, PtCnt: Integer; ClearSource: Boolean = True);
Назначение: создание нового экземпляра TCellMovement
Параметр ALayer: слой, которому принадлежит движение. Не должен быть nil
Параметр X1: номер столбца клетки, из которой начинается движение
Параметр Y1: номер строки клетки, из которой начинается движение
Параметр X2: номер столбца клетки, в которой заканчивается движение
Параметр Y2: номер строки клетки, в которой заканчивается движение
Параметр PtCnt: количество точек в траектории
Параметр ClearSource (может быть опущен, значение по умолчанию True): определяет, будет ли очищена клетка, из которой начинается движение.
Область видимости: public
Тип метода: статический, перегруженный
Описание: создаёт экземпляр TCellMovement, реализующий движение фигуры из ячейки (X1;Y1) в ячейку (X2;Y2). Образцом для перемещаемой фигуры становится ALayer.Cells[X1, Y1]. Траектория состоит из PtCnt точек, равномерно расставленных по отрезку, соединяющему левые верхние углы клеток (X1;Y1) и (X2;Y2).
Координаты конечной клетки могут лежать вне доски. Координаты начальной клетки должны лежать в пределах доски, иначе возникнет исключение EWrongCell.
Если параметр ClearSource равен True, конструктор устанавливает состояние клетки ALayer.Cells[X1, Y1] равным –1, очищая её от фигуры.
— TCellMovement.Create (overload 4) —
Метод: constructor Create(ALayer: TCellLayer; X1, Y1, X2, Y2 :Integer; Scale: Extended; ClearSource: Boolean = True);
Назначение: создание нового экземпляра TCellMovement
Параметр ALayer: слой, которому принадлежит движение. Не должен быть nil
Параметр X1: номер столбца клетки, из которой начинается движение
Параметр Y1: номер строки клетки, из которой начинается движение
Параметр X2: номер столбца клетки, в которой заканчивается движение
Параметр Y2: номер строки клетки, в которой заканчивается движение
Параметр Scale: отношение количества точек траектории к количеству пикселей, составляющих отрезок от левого верхнего угла ячейки (X1;Y1) до левого верхнего угла ячейки (X2;Y2)
Параметр ClearSource (может быть опущен, значение по умолчанию True): определяет, будет ли очищена клетка, из которой начинается движение.
Область видимости: public
Тип метода: статический, перегруженный
Описание: создаёт экземпляр TCellMovement, реализующий движение фигуры из ячейки (X1;Y1) в ячейку (X2;Y2). Образцом для перемещаемой фигуры становится ALayer.Cells[X1, Y1]. Траектория состоит из точек, равномерно расставленных по отрезку, соединяющему левые верхние углы клеток (X1;Y1) и (X2;Y2). Количество точек траектории определяется параметром Scale: оно равно количеству пикселей, которые составляют отрезок от левого верхнего угла начальной клетки до левого верхнего угла конечной клетки, включая концы, умноженному на Scale.
Координаты конечной клетки могут лежать вне доски. Координаты начальной клетки должны лежать в пределах доски, иначе возникнет исключение EWrongCell.
Если параметр ClearSource равен True, конструктор устанавливает состояние клетки ALayer.Cells[X1, Y1] равным –1, очищая её от фигуры.
— TCellMovement.NextStep —
Метод: function NextStep(Board: TBitmap; Phase: Int64): Boolean;
Назначение: рисует очередную фазу движения
Параметр Board: буфер, на котором рисуется доска
Параметр Phase: номер тика (значение всегда совпадает со значением свойства Tick той доски, к которой относится движение)
Результат: False, если нарисованная фаза движения не последняя, True, если последняя
Тип метода: виртуальный, перекрытый (унаследован от TMovement)
Описание: рисует очередную фазу движения. Собственно рисованием занимается реализация, унаследованная от TSpriteMovement, а класс TCellMovement только выполняет дополнительную проверку результата: если этот результат окажется равным True, и свойство AssignDest тоже имеет значение True, то в ячейку Layer.Cells[X2, Y2] копируется перемещаемая фигура.
Модуль: Movements
Предок: TSpriteProducer
Реализует поставщик спрайтов, который создаёт анимированный спрайт на основе рисунков из банка изображений, предоставляя более тонкие настройки для управления анимацией, чем это делает класс TGameCell.
Класс TAnimationSprite имеет собственную ссылку на банк изображений, что позволяет ему использовать банк, отличный от назначенного слою, которому он принадлежит.
Последовательность смены изображений из банка определяется значением типа TAnimationType. Это перечислимый тип, содержащий следующие значения:
- atDirect — однократная смена изображений в прямом порядке
- atReverse — однократная смена изображений в обратном порядке
- atDirectCycle — циклическая смена изображений в прямом порядке
- atReverseCycle — циклическая смена изображений в обратном порядке
- atDirectWave — однократная смена изображений сначала в прямом порядке, затем в обратном
- atReverseWave — однократная смена изображений сначала в обратном порядке, потом в прямом
- atDirectWaveCycle — циклическая смена изображений сначала в прямом порядке, затем в обратном
- atReverseWaveCycle — циклическая смена изображений сначала в обратном порядке, потом в прямом
- atRandom — смена изображений в случайном порядке
Свойства TAnimationSprite
— TAnimationSprite.Finish —
Свойство: Finish
Тип: Int64
Область видимости: public
Значение по умолчанию: —
Права доступа: для чтения и записи
Описание: наибольшее значение фазы, которое используется при анимации. Если анимация с помощью TGameCell использует все фазы от начальной до конечной, то TAnimationSprite с помощью свойств Start и Finish позволяет задать диапазон, в котором будет меняться фаза.
Значение –1 означает, что максимальная фаза равна максимально возможной для данного состояния.
Свойству Finish нельзя присваивать значение, меньшее текущего значения свойства Start, иначе возникнет исключение EBadAnimationLimits.
— TAnimationSprite.Speed —
Свойство: Speed
Тип: Integer
Область видимости: public
Значение по умолчанию: 1
Права доступа: для чтения и записи
Описание: скорость смены изображений (демультипликатор скорости). При значении 1 скорость максимальна, при значении 2 — в два раза меньше максимальной, при значении 3 — в три раза, и т.д. Значения, меньшие 1, не допускаются.
Учтите, что экземпляр TSpriteMovement, который будет использовать TAnimationSprite, также имеет свойство Speed, и реальное замедление скорости определяется значением TAnimationSprite.Speed*TSpriteMovement.Speed.
— TAnimationSprite.Start —
Свойство: Start
Тип: Int64
Область видимости: public
Значение по умолчанию: —
Права доступа: для чтения и записи
Описание: наименьшее значение фазы, которое используется при анимации. Если анимация с помощью TGameCell использует все фазы от начальной до конечной, то TAnimationSprite с помощью свойств Start и Finish позволяет задать диапазон, в котором будет меняться фаза.
Значение –1 означает, что минимальная фаза равна нулю.
Свойству Start нельзя присваивать значение, большее текущего значения свойства Finish, иначе возникнет исключение EBadAnimationLimits.
— TAnimationSprite.State —
Свойство: State
Тип: Integer
Область видимости: public
Значение по умолчанию: —
Права доступа: для чтения и записи
Описание: номер фигуры, которая будет использована для отображения спрайта.
При изменении свойства State свойства Start и Finish могут изменить свои значения, чтобы диапазон изменения фазы остался внутри диапазона допустимых для данной фигуры фаз.
Методы TAnimationSprite
— TAnimationSprite.Create —
Метод: constructor Create(Bank: TCellBank; State: Integer; AWidth, AHeight: Integer; AnimationType: TAnimationType = atDirect; const Start: Int64 = –1; const Finish: Int64 = –1);
Назначение: создание нового экземпляра TAnimationSprite
Параметр Bank: ссылка на банк изображений, рисунки которого будут использоваться для анимации. Не может быть равен nil
Параметр State: номер фигуры, которая будет использоваться для отображения спрайта
Параметр AWidth: ширина прямоугольника для рисования фигуры
Параметр AHeight: высота прямоуголника для рисования фигуры
Параметр AnimationType (может быть опущен, значение по умолчанию atDirect): определяет, в какой последовательности фазы будут сменять друг друга.
Параметр Start (может быть опущен, значение по умолчанию –1): нижняя граница диапазона изменения фазы. Значение –1 означает 0
Параметр Finish (может быть опущен, значение по умолчанию –1): верхняя граница диапазона изменения фазы. Значение –1 означает максимально возможную для данного значения State фазу
Область видимости: public
Тип метода: статический
Описание: создаёт новый экземпляр TAnimationSprite и инициализирует его. Новый экземпляр сохраняет ссылку на объект Bank, но не становится его владельцем и не отвечает за его уничтожение. Механизма уведомления об уничтожении банка не предусмотрено, поэтому программист несёт ответственность за то, чтобы время жизни банка было не меньше, чем время жизни ссылающегося на него экземпляра TAnimationSprite.
Конструктор также устанавливает начальную фазу для движения. Для тех последовательностей, которые начинаются с движения в прямом порядке (atDirect, atDirectCycle, AtDirectWave, atDirectWaveCycle) начальная фаза устанавливается равной параметру Start, для тех последовательностей, которые начинаются с движения в обратном порядке (atReverse, atReverseCycle, atReverseWave, atReverseWaveCycle) — равной параметру Finish. Для последовательности atRandom начальное значение выбирается случайным образом из диапазона [Start..Finish].
— TAnimationSprite.DrawSprite —
Метод: function DrawSprite(Board: TBitmap; X, Y: Integer; Phase: Int64): Boolean;
Назначение: рисует спрайт с заданной фазой в заданной координате
Параметр Board: буфер, в котором рисуется доска
Параметр X: горизонтальная координата спрайта
Параметр Y: вертикальная координата спрайта
Параметр Phase: фаза спрайта
Результат: True, если нарисована последняя фаза спрайта, False — если не последняя
Область видимости: protected
Тип метода: виртуальный, перекрытый (унаследован от TSpriteProducer)
Описание: рисует спрайт с заданной фазой в заданных координатах. Для рисования вызывается метод DrawCell банка изображений, связанного с затем, если требуется смена фазы, вызывает метод NextPhase и возвращает его результат. Если смена фазы не требуется (это может быть в случае, когда Speed > 1, и одна фаза должна показываться в течение нескольких тиков), возвращается False.
— TAnimationSprite.IsEndless —
Метод: function IsEndless: Boolean;
Назначение: позволяет узнать, является ли последовательность спрайтов бесконечной
Результат: True, если последовательность спрайтов бесконечна, False, если конечна
Область видимости: protected
Тип метода: виртуальный, перекрытый (унаследован от TSpriteProducer)
Описание: возвращает True для всех циклических последовательностей (atDirectCycle, atReverseCycle, atDirectWaveCycle, atReverseWaveCycle, atRandom), для всех остальных возвращает False.
— TAnimationSprite.NextPhase —
Метод: function NextPhase: Boolean;
Назначение: вычисляет следующую фазу
Результат: True, если движение уже закончилось, False, если последняя фаза ещё не была достигнута
Область видимости: protected
Тип метода: статический
Описание: вычисляет следующую фазу на основании текущей фазы и выбранной последовательности смены фаз. Текущая фаза хранится в специальном внутреннем поле класса, туда же помещается вычисленная новая фаза, которая затем используется методом DrawSprite. Для циклических последовательностей (atDirectCycle, atReverseCycle, atDirectWaveCycle, atReverseWaveCycle, atRandom) метод всегда возвращает False, для остальных может вернуть True, если при попытке вычислить следующую фазу окажется, что текущая фаза — конечная.
Модуль: Movements
Предок: TSpriteMovement
Класс TCellAnimationMovement реализует неподвижный спрайт на основе TAnimationSprite (поставщиком координат служит экземпляр TPathCoords с замкнутой траекторией, состоящей из одной точки). Единственный член класса — это конструктор, который позволяет удобным образом задавать параметры такого движения.
Методы TCellAnimationMovement
— TCellAnimationMovement.Create (overload 1) —
Метод: constructor Create(ALayer: TCellLayer; X, Y: Integer; State: Integer; AnimationType: TAnimationType = atDirect; ASpeed: Integer = 1; const Start: Int64 = –1; const Finish: Int64 = –1);
Назначение: создание нового экземпляра TCellAnimationMovement
Параметр ALayer: слой, которому будет принадлежать движение
Параметр X: столбец клетки, в которой следует отобразить движение
Параметр Y: строка клетки, в которой следует отобразить движение
Параметр State: номер фигуры, которая будет использоваться для отображения спрайта
Параметр AnimationType (может быть опущен, значение по умолчанию atDirect): определяет, в какой последовательности фазы будут сменять друг друга.
Параметр Speed (может быть опущен, значение по умолчанию 1): скорость изменения фазы спрайта
Параметр Start (может быть опущен, значение по умолчанию –1): нижняя граница диапазона изменения фазы. Значение –1 означает 0
Параметр Finish (может быть опущен, значение по умолчанию –1): верхняя граница диапазона изменения фазы. Значение –1 означает максимально возможную для данного значения State фазу
Область видимости: public
Тип метода: статический
Описание: создаёт новый экземпляр TCellAnimationMovment, а также экземпляры TPathCoords и TAnimationSprite, реализующие нужное движение. В качестве банка изображений используется банк слоя ALayer, а координаты (X;Y) — это координаты не в пикселях, а номера строки и столбца клетки. Это уменьшает гибкость класса, приспосабливая его для решения конкретной задачи: отображении анимации в клетке для рисования появления, исчезновения или трансформации фигуры.
Обратите также внимание, что значение параметра Speed присваивается свойству Speed внутреннего объекта типа TAnimationSprite и никак не влияет на значение свойства Speed самого объекта TCellAnimationMovement (это свойство унаследовано им от TSpriteMovement).
— TCellAnimationMovement.Create (overload 2) —
Метод: constructor Create(ALayer: TCellLayer; Bank: TCellBank; X, Y: Integer; State: Integer; AnimationType: TAnimationType = atDirect; ASpeed: Integer = 1; const Start: Int64 = –1; const Finish: Int64 = –1);
Назначение: создание нового экземпляра TCellAnimationMovement
Параметр ALayer: слой, которому будет принадлежать движение
Параметр Bank: банк изображений, используемый спрайтом
Параметр X: столбец клетки, в которой следует отобразить движение
Параметр Y: строка клетки, в которой следует отобразить движение
Параметр State: номер фигуры, которая будет использоваться для отображения спрайта
Параметр AnimationType (может быть опущен, значение по умолчанию atDirect): определяет, в какой последовательности фазы будут сменять друг друга.
Параметр Speed (может быть опущен, значение по умолчанию 1): скорость изменения фазы спрайта
Параметр Start (может быть опущен, значение по умолчанию –1): нижняя граница диапазона изменения фазы. Значение –1 означает 0
Параметр Finish (может быть опущен, значение по умолчанию –1): верхняя граница диапазона изменения фазы. Значение –1 означает максимально возможную для данного значения State фазу
Область видимости: public
Тип метода: статический
Описание: данный вариант конструктора полностью эквивалентен варианту, описанному выше, за исключением того, что для внутреннего экземпляра TAnimationSprite используется не банк слоя, а банк изображений, заданный дополнительным параметром Bank. Экземпляр TCellAnimationMovement не становится владельцем банка, переданного через этот параметр, и не отвечает за его освобождение. Кроме того, разработчик обязан следить, чтобы объект, на который ссылается Bank, не был удалён до тех пор, пока существует хотя бы один экземпляр TCellAnimationMovement, использующий этот банк.
Модуль: Movements
Предок: TSpriteProducer
Класс TBitmapSprite реализует простейшего поставщика спрайтов, который выводит заданное растровое изображение. Бывает очень полезен, когда надо реализовать движение в виде неизменного изображения, двигающегося по определённой траектории.
Свойства TBitmapSpriteSprite
— TBitmapSprite.Sprite —
Свойство: Sprite
Тип: TBitmap
Область видимости: protected
Значение по умолчанию: —
Права доступа: только для чтения
Описание: изображение, которое будет выводиться в качестве спрайта
Методы TBitmapSprite
— TBitmapSprite.Create —
Метод: constructor Create(ABitmap: TBitmap);
Назначение: создание нового экземпляра TBitmapSprite
Параметр ABitmap: растровое изображение, которое будет образцом для спрайта
Область видимости: public
Тип метода: статический
Описание: создаёт новый экземпляр класса TBitmapSprite, используя изображение ABitmap как образец. Данное изображение копируется во внутренний буфер. Ссылку на ABitmap экземпляр TBitmapSprite не сохраняет, владельцем этого объекта не становится.
— TBitmapSprite.Destroy —
Метод: destructor Destroy;
Назначение: уничтожение экземпляра TBitmapSprite
Область видимости: public
Тип метода: виртуальный, перекрытый (унаследован от TObject)
Описание: уничтожает объект TBitmapSprite и его внутренний буфер.
— TBitmapSprite.DrawSprite —
Метод: function DrawSprite(Board: TBitmap; X, Y: Integer; Phase: Int64): Boolean;
Назначение: рисует спрайт с заданной фазой в заданной координате
Параметр Board: буфер, в котором рисуется доска
Параметр X: горизонтальная координата спрайта
Параметр Y: вертикальная координата спрайта
Параметр Phase: фаза спрайта
Результат: True, если нарисована последняя фаза спрайта, False — если не последняя
Область видимости: protected
Тип метода: виртуальный, перекрытый (унаследован от TSpriteProducer)
Описание: выводит изображение Sprite, помещая его левый верхний угол в точку (X;Y). Для вывода использует метод TCanvas.Draw, поэтому пригоден в т.ч. и для вывода изображений с прозрачным фоном (см. свойства Transparent, TransparentMode и TransparentColor класса TBitmap). Всегда возвращает False.
Модуль: Movements
Предок: TBitmapSprite
Реализует поставщика спрайтов, который выводит полупрозрачный растровый рисунок. Степень прозрачности может задаваться как альфа-каналом рисунка, так и отдельным свойством для рисунка в целом, если он не имеет альфа-канала. Степень прозрачности может быть постоянной или циклически меняться от нуля до максимума и обратно.
Свойства TTransparentSprite
— TTransparentSprite.Alpha —
Свойство: Alpha
Тип: Byte
Область видимости: public
Значение по умолчанию: 0
Права доступа: для чтения и записи
Описание: содержит постоянную степень непрозрачности изображения (0 — полностью прозрачное, 255 — полностью непрозрачное). Если вы не знакомы с тем, как API-функция AlphaBlend смешивает цвета, при использовании изображений с альфа-каналом не рекомендуется устанавливать значения Alpha, отличные от 255.
Если AlphaDelta <> 0, значение свойства Alpha игнорируется
— TTransparentSprite.AlphaDelta —
Свойство: AlphaDelta
Тип: Byte
Область видимости: public
Значение по умолчанию: 1
Права доступа: для чтения и записи
Описание: определяет величину, на которую изменяется прозрачность за один шаг (продолжительность шага в тиках определяется значением свойства AlphaSpeed). Если AlphaDelta равно нулю, прозрачность остаётся постоянной и определяется значением свойства Alpha.
— TTransparentSprite.AlphaSpeed —
Свойство: AlphaSpeed
Тип: Integer
Область видимости: public
Значение по умолчанию: 1
Права доступа: для чтения и записи
Описание: определяет скорость изменения прозрачности. Раз в AlphaSpeed тиков прозрачность меняется на величину, задаваемую свойством AlphaDelta. При AlphaDelta = 0 значение свойства AlphaSpeed ни на что не влияет.
Значение свойства AlphaSpeed не может быть меньше единицы.
— TTransparentSprite.SrcAlpha —
Свойство: SrcAlpha
Тип: Boolean
Область видимости: public
Значение по умолчанию: False
Права доступа: для чтения и записи
Описание: определяет, следует ли при выводе изображения использовать его альфа-канал (True — использовать, False — не использовать). При значении False изображение будет выводится без учёта альфа-канала, даже если он есть. Присваивать свойству значение True, если используемый рисунок не содержит альфа-канала, нельзя, это приведёт к исключению EBadAlphaPicture при работе метода DrawSprite.
Методы TTransparentSprite
— TTransparentSprite.Create —
Метод: constructor Create(ABitmap: TBitmap);
Назначение: создание нового экземпляра TTransparentSprite
Параметр ABitmap: растровое изображение, которое будет образцом для спрайта
Область видимости: public
Тип метода: статический
Описание: создаёт новый экземпляр класса TTransparentSprite и инициализирует его свойства. Изображение ABitmap используется как образец. Данное изображение копируется во внутренний буфер. Ссылку на ABitmap экземпляр TTransparentSprite не сохраняет, владельцем этого объекта не становится.
— TTransparentSprite.DrawSprite —
Метод: function DrawSprite(Board: TBitmap; X, Y: Integer; Phase: Int64): Boolean;
Назначение: рисует спрайт с заданной фазой в заданной координате
Параметр Board: буфер, в котором рисуется доска
Параметр X: горизонтальная координата спрайта
Параметр Y: вертикальная координата спрайта
Параметр Phase: фаза спрайта
Результат: True, если нарисована последняя фаза спрайта, False — если не последняя
Область видимости: protected
Тип метода: виртуальный, перекрытый (унаследован от TSpriteProducer)
Описание: выводит изображение Sprite с требуемой степенью прозрачности, помещая его левый верхний угол в точку (X;Y). Всегда возвращает False.
Модуль: Movements
Предок: TCoordProducer
Создаёт бесконечную траекторию для точки в некотором замкнутом прямоугольнике. Точка движется по диагональным линиям под углом 45 градусов к горизонтали, зеркально отражаясь от стен прямоугольника.
Свойства TPingPongProducer
— TPingPongProducer.ToLeft —
Свойство: ToLeft
Тип: Boolean
Область видимости: public
Значение по умолчанию: False
Права доступа: для чтения и записи
Описание: показывает направление горизонтальной составляющей смещения точки на шаге. Если True, горизонтальная составляющая направлена справа налево, если False — слева направо. Свойство меняет своё значение на противоположное, когда точка достигает вертикальных границ прямоугольника, но его значение можно также менять и извне, меняя направление движения в произвольной точке траектории.
— TPingPongProducer.ToTop —
Свойство: ToTop
Тип: Boolean
Область видимости: public
Значение по умолчанию: False
Права доступа: для чтения и записи
Описание: показывает направление вертикальной составляющей смещения точки на шаге. Если True, вертикальная составляющая направлена снизу вверх, если False — сверху вниз. Свойство меняет своё значение на противоположное, когда точка достигает горизонтальных границ прямоугольника, но его значение можно также менять и извне, меняя направление движения в произвольной точке траектории.
— TPingPongProducer.X —
Свойство: X
Тип: Integer
Область видимости: public
Значение по умолчанию: —
Права доступа: для чтения и записи
Описание: содержит текущую горизонтальную координату точки. Значение свойства меняется на единицу (увеличивается или уменьшается в зависимости от значения ToLeft) каждый тик, но при необходимости его можно изменить извне. Значение свойства не может выходить за границы прямоугольника.
— TPingPongProducer.Y —
Свойство: Y
Тип: Integer
Область видимости: public
Значение по умолчанию: —
Права доступа: для чтения и записи
Описание: содержит текущую вертикальную координату точки. Значение свойства меняется на единицу (увеличивается или уменьшается в зависимости от значения ToTop) каждый тик, но при необходимости его можно изменить извне. Значение свойства не может выходить за границы прямоугольника.
Методы TPingPongProducer
— TPingPongProducer.Create —
Метод: constructor Create(MinX, MinY, MaxX, MaxY :Integer);
Назначение: создание экземпляра TPingPongProducer
Параметр MinX: левая граница прямоугольника для движения точки
Параметр MinY: верхняя граница прямоугольника для движения точки
Параметр MaxX: правая граница прямоугольника для движения точки
Параметр MaxY: нижняя граница прямоугольника для движения точки
Область видимости: public
Тип метода: статический
Описание: создаёт новый экземпляр класса TPingPongProducer, прямоугольник движения точки которого ограничен заданным прямоугольником. MaxX должен быть больше MinX, а MaxY — больше MinY, иначе возникнет исключение EBadPingPongArea.
Свойствам X и Y конструктор присваивает значения, равные MinX и MinY соответственно, т.е. движение начинается из левого верхнего угла прямоугольника.
— TPingPongProducer.GetNextCoord —
Метод: function GetNextCoord(var X, Y: Integer): Boolean;
Назначение: возвращает координаты очередной точки траектории
Параметр X: выходной параметр, через который возвращается горизонтальная координата
Параметр Y: выходной параметр, через который возвращается вертикальная координата
Результат: True, если была возвращена координата конечной точки траектории, False — если не конечной
Область видимости: protected
Тип метода: виртуальный, перекрытый (унаследован от TCoordProducer)
Описание: возвращает координаты очередной точки траектории. Для вычисления очередных координат берутся значения свойств X и Y и, в зависимости от значений свойств ToLeft и ToTop, увеличиваются или уменьшаются на единицу. Здесь же проверяется, достигла ли точка какой-либо границы прямоугольника, и если достигла, свойства ToLeft или ToTop меняются соответствующим образом.
Метод GetNextCoord в реализации класса TPingPongProducer всегда возвращает False.
— TPingPongProducer.IsEndless —
Метод: function IsEndless: Boolean;
Назначение: позволяет узнать, является ли траектория бесконечной
Результат: True, если траектория бесконечна, False, если конечна
Область видимости: protected
Тип метода: виртуальный, перекрытый (унаследован от TCoordProducer)
Описание: всегда возвращает True, так как траектория бесконечна.
— TPingPongProducer.RandomizeStartPos —
Метод: procedure RandomizeStartPos;
Назначение: устанавливает случайное положение и направление движения
Область видимости: public
Тип метода: статический
Описание: присваивает свойствам X, Y, ToLeft, ToTop случайные значения, выбирая тем самым случайное положение и направление движения точки.
Модуль: Movements
Предок: TSpriteMovement
Предназначен для реализации движения некоторого спрайта, отражающегося от стенок доски. Вычисление координат выполняется классом TPingPongProducer.
Методы TPingPongMovement
— TPingPongMovement.Create —
Метод: constructor Create(ALayer: TCellLayer; Margin, SpriteWidth, SpriteHeight: Integer; Sprite: TSpriteProducer);
Назначение: создание экземпляра TPingPongMovement
Параметр ALayer: слой, которому будет принадлежать объект движения. Не должен быть равен nil
Параметр Margin: величина отступа от границ доски линий отражения
Параметр SpriteWidth: ширина спрайта
Параметр SpriteHeight: высота спрайта
Параметр Sprite: ссылка на поставщика спрайтов
Область видимости: public
Тип метода: статический
Описание: создаёт новый экземпляр класса TPingPongMovement для реализации движения некоторого спрайта в прямоугольнике, границы которого отстоят на Margin пикселей внутрь от границ доски.
Создаваемый объект не становится владельцем объекта ALayer, но становится владельцем объекта Sprite. Параметр Sprite при вызове конструктора может быть равен nil, но в этом случае поставщик спрайтов должен быть назначен до начала движения с помощью свойства Sprite, унаследованного от TSpriteMovement.
Чтобы от правой и нижней границ отражался не верхний левый угол спрайта, а сам спрайт, необходимо учитывать размеры спрайта. В общем случае спрайт может не иметь фиксированных размеров (это зависит от реализации поставщика спрайтов), но использование таких спрайтов совместно с TPingPongMovement вряд ли даст красивый эффект. Поэтому желательно использовать только спрайты фиксированных размеров, передавая эти размеры через параметры SpriteWidth и SpriteHeight.
— TPingPongMovement.RandomizeStartPos —
Метод: procedure RandomizeStartPos;
Назначение: устанавливает случайное положение и направление движения
Область видимости: public
Тип метода: статический
Описание: устанавливает случайное положение и направление движения, вызывая метод RandomizeStartPos экземпляра класса TPingPongProducer, служащего поставщиком координат.
Модуль: Movments
Предок: TComponent
Движения, реализуемые наследниками TMovement, можно создать и можно дождаться их окончания (одного движения или группы). Практика показывает, что для большинства игр этого вполне достаточно. Но в некоторых случаях всё же может потребоваться более сложный механизм управления движением. Например, реакция на окончание движения, проверка того, завершено движение или нет и т.п. Перегружать всей этой редко используемой функциональностью класс TMovement нерационально. Поэтому создан класс TMovementsManager, реализующий всю эту дополнительную функциональность.
Класс TMovementsManager сделан наследником TComponent, чтобы его можно было размещать на форме и назначать его обработчики событий во время разработки программы. Но всё же большая часть функциональности этого класса становится доступной только во время работы программы через вызов его методов.
Основная идея заключается в том, что TMovementsManager поддерживает список движений, каждому из которых присваивается уникальный псевдоним (псевдонимы регистрозависимы). Добавив движение в список компонента TMovementsManager, программист получает возможность в любой момент времени узнать, завершено ли движение, а также получить событие о его завершении. Добавлять в список можно только конечные движения.
Допускается добавление одного движения в списки нескольких разных экземпляров TMovementsManager, а также добавление одного движения в список одного экземпляра TMovementsManager несколько раз под разными псевдонимами.
Свойства TMovementsManager
— TMovementsManager.Aliases —
Свойство: Aliases[Index: Integer]
Тип: string
Область видимости: public
Значение по умолчанию: —
Права доступа: только для чтения
Описание: список всех псевдонимов, которые уже добавлены в список. Псевдонимы в списке упорядочены по алфавиту. Положение псевдонимов в списке меняется при добавлении движений в список и при удалении их из него.
Если индекс меньше нуля или больше или равен значению свойства Count, при обращении с таким индексом к свойству Alias возникает исключение EStringListError (исключение объявлено в стандартном модуле Classes).
— TMovementsManager.Count —
Свойство: Count
Тип: Integer
Область видимости: public
Значение по умолчанию: 0
Права доступа: только для чтения
Описание: количество движений, содержащихся в списке
— TMovementsManager.ExistingCount —
Свойство: ExistingCount
Тип: Integer
Область видимости: public
Значение по умолчанию: 0
Права доступа: только для чтения
Описание: количество тех движений, содержащихся в списке, которые ещё не успели завершиться
— TMovementsManager.MovementExists —
Свойство: MovementExists[Alias: string]
Тип: Boolean
Область видимости: public
Значение по умолчанию: —
Права доступа: только для чтения
Описание: True, если движение с заданным псевдонимом ещё не завершено, False, если уже завершено.
Если задан псевдоним, отсутствующий в списке, возникает исключение EWrongMovementAlias.
— TMovementsManager.Movements —
Свойство: Movements[Alias: string]
Тип: TMovement
Область видимости: public
Значение по умолчанию: —
Права доступа: только для чтения
Описание: возвращает ссылку на движение, соответствующее данному псевдониму. Если движение уже завершено, возвращается nil.
Если задан псевдоним, отсутствующий в списке, возникает исключение EWrongMovementAlias.
Методы TMovementsManager
— TMovementsManager.Create —
Метод: constructor Create(AOwner: TComponent);
Назначение: создание нового экземпляра TMovementsManager
Параметр AOwner: компонент, который должен быть владельцем создаваемого экземпляра
Область видимости: public
Тип метода: виртуальный, перекрытый (унаследован от TComponent)
Описание: стандартный конструктор компонента, создающий новый экземпляр
— TMovementsManager.Destroy —
Метод: destructor Destroy;
Назначение: уничтожение экземпляра TMovementsManager
Область видимости: public
Тип метода: виртуальный, перекрытый (унаследован от TObject)
Описание: стандартный деструктор, освобождающий объект и все занятые им ресурсы.
— TMovementsManager.AddMovement —
Метод: procedure AddMovement(Movement: TMovement; const Alias: string);
Назначение: добавление движения в список
Параметр Movement: движение, которое должно быть добавлено в список
Параметр Alias: псевдоним, который назначается движению
Область видимости: public
Тип метода: статический
Описание: добавляет движение в список экземпляра TMovementsManager.
Если движение с таким псевдонимом уже существует в списке, возникает исключение EDuplicateMovementAlias. Если движение не является конечным, возникает исключение EEndlessMovement.
— TMovementsManager.RemoveAll —
Метод: procedure RemoveAll;
Назначение: очистка списка движений
Область видимости: public
Тип метода: статический
Описание: удаляет из списка экземпляра TMovementsManager все движения.
— TMovementsManager.RemoveFinished —
Метод: procedure RemoveFinished;
Назначение: очистка списка от завершённых движений
Область видимости: public
Тип метода: статический
Описание: удаляет из списка экземпляра TMovementsManager все движения, которые уже завершены.
— TMovementsManager.RemoveMovement —
Метод: procedure RemoveMovement(const Alias: string);
Назначение: удаление движения из списка
Параметр Alias: псевдоним движения, которое следует удалить
Область видимости: public
Тип метода: статический
Описание: удаляет движение из списка экземпляра TMovementsManager.
Если движение с таким псевдонимом отсутствует в списке, исключение не возникает, метод RemoveMovement просто ничего не делает.
— TMovementsManager.ReplaceMovement —
Метод: procedure ReplaceMovement(Movement: TMovement; const Alias: string);
Назначение: замещает движение в списке
Параметр Movement: движение, которое должно быть добавлено в список
Параметр Alias: псевдоним движения, которое должно быть замещено
Область видимости: public
Тип метода: статический
Описание: заменяет одно движение из списка экземпляра TMovementsManager другим движением. Если движение с заданным псевдонимом отсутствует в списке, просто добавляет движение в список аналогично тому, как это делает метод AddMovement.
Если движение не является конечным, возникает исключение EEndlessMovement.
— TMovementsManager.UpdateMovement —
Метод: procedure UpdateMovement(Movement: TMovement; const Alias: string);
Назначение: добавление движения в список
Параметр Movement: движение, которое должно быть добавлено в список
Параметр Alias: псевдоним, который назначается движению
Область видимости: public
Тип метода: статический
Описание: добавляет движение в список экземпляра TMovementsManager. От AddMovement отличается тем, что исключение EDuplicateMovementAlias возникает только в том случае, если движение с таким же псевдонимом, содержащееся в списке, ещё не завершено. Если же оно завершено, то добавляемое движение подменяет то, которое было в списке раньше.
Если движение не является конечным, возникает исключение EEndlessMovement.
События TMovementsManager
— TMovementsManager.OnMovementFinished —
Событие: OnMovementFinished
Назначение: уведомление о завершении движения
Тип: TMovementFinishedEvent
Прототип: procedure(Sender: TObject; const Alias: string)
Параметр Sender: объект TMovementsManager, инициировавший событие
Параметр Alias: псевдоним завершившегося движения
Область видимости: published
Описание: возникает, когда движение, содержащееся в списке экземпляра TMovementsManager, завершается. В момент срабатывания этого события экземпляр объекта движения ещё не удалён, поэтому MovementExists[Alias] вернёт True, а Movements[Alias] — корректный указатель. Однако пользоваться этим указателем можно только для сравнения его с указателем, сохранённым ранее, так как процесс удаления объекта движения в этот момент уже начат, и этот объект может некорректно реагировать на другие действия с ним.
В библиотеке описан ряд специфичных исключений для сигнализации о проблемах, возникающих в ходе выполнения кода библиотеки. Все исключения являются прямыми наследниками класса Exception и не добавляют к нему никаких новых членов.
— EAnimationIsOff —
Исключение: EAnimationIsOff
Модуль: CellGameBoard
Описание: возникает в тех ситуациях, когда анимация выключена, а разработчик пытается выполнить действие, доступное только при включенной анимации. К таким действиям относится вызов методов WaitMovement и WaitMovements класса TCellGameBoard.
— EAnimationIsOn —
Исключение: EAnimationIsOn
Модуль: CellGameBoard
Описание: возникает в тех ситуациях, когда анимация включена, а разработчик пытается выполнить действие, доступное только при выключенной анимации. К таким действиям относится изменение значения свойства Timer класса TCellGameBoard.
— EBadAlphaPictute —
Исключение: EBadAlphaPictute
Модуль: Movements
Описание: возникает при работе поставщика спрайтов TTransparentSprite, если рисунок не может быть выведен с помощью API-функции AlphaBlend. Чаще всего это бывает в том случае, если свойство SrcAlpha имеет значение True, но рисунок не содержит альфа-канала.
— EBadAnimationLimits —
Исключение: EBadAnimationLimits
Модуль: Movements
Описание: возникает при выполнении конструктора TAnimationSprite.Create или при изменении свойств Start и Finish этого класса, если устанавливаемый верхний предел фазы оказывается меньше, чем нижний. Как следствие, может возникать при вызове TCellAnimationMovement.Create, так как этот конструктор создаёт для внутреннего пользования экземпляр TAnimationSprite.
— EBadPingPongArea —
Исключение: EBadPingPongArea
Модуль: Movements
Описание: возникает при выполнении конструктора TPingPongProducer.Create, если прямоугольник для движения задан неверно: координата правой границы меньше или равна координате левой или координата нижней границы меньше или равна координате верхней. Как следствие, может возникнуть при вызове TPingPongMovement.Create, так как TPingPongMovement для внутренних нужд создаёт экземпляр TPingPongProducer.
— EBankIsNotEmpty —
Исключение: EBankIsNotEmpty
Модуль: CellBanks
Описание: возникает при выполнении таких действий над банком растровых изображений (TBitmapCellBank), содержащим изображения, которые можно выполнять только с пустым банком. К этим действиям относится изменение свойств ImageWidth и ImageHeight.
— EDuplicateMovementAlias —
Исключение: EDuplicateMovementAlias
Модуль: Movements
Описание: возникает при вызове метода AddMovement класса TMovementsManager, если в списке объекта уже существует движение с заданным псевдонимом, а также при вызове метода UpdateMovement того же класса, если движение с таким псевдонимом уже существует и оно не завершено.
— EDuplicateMovements —
Исключение: EDuplicateMovements
Модуль: CellGameBoard
Описание: возникает, когда в слой добавляется движение, которое уже добавлено в этот слой. По умолчанию движение в слой добавляется в конструкторе TMovement, что обеспечивает уникальность этого движения и гарантию того, что исключение EDuplicateMovements не будет возникать. Оно может возникнуть только при явном вызове метода AddMovement класса TCellLayer. Так как данный метод имеет область видимости protected, такое может случиться, только если разработчик порождает наследников от TCellLayer и манипулирует в них движениями нестандартным способом.
— EDuplicateNotifier —
Исключение: EDuplicateNotifier
Модуль: CellGameBoard
Описание: возникает при вызове метода AddNotifier класса TMovement в том случае, если указанный получатель уведомлений уже есть в списке получателей уведомлений данного движения. Все методы библиотеки, вызывающие внутри себя AddNotifier, кроме TCellGameBoard.WaitMovements, гарантируют уникальность получателя уведомлений. Таким образом, исключение может возникнуть либо при явном вызове метода AddNotifier, либо при вызове WaitMovements в том случае, если одно движение включено в массив два или более раз.
— EEndlessMovement —
Исключение: EEndlessMovement
Модуль: CellGameBoard
Описание: возникает, когда разработчик пытается выполнить действие, допустимое только для конечных движений, с бесконечным движением. В данной версии библиотеки оно возникает только в методе AddNotifier класса TMovement. Как следствие, разработчик может получить это исключение и в тех случаях, когда данный метод вызывается неявно, а именно при вызове методов WaitMovement и WaitMovements класса TCellGameBoard и методов AddMovement, ReplaceMovement и UpdateMovement класса TMovementsManager, если разработчик передаст в качестве параметров этих методов бесконечные движения.
— EMMTimerError —
Исключение: EMMTimerError
Модуль: CellGameBoard
Описание: возникает при присваивании свойству TCellGameBoard.Animated значения True в тех случаях, когда система не может запустить мультимедийный таймер. Так как практически все современные варианты платформы Win32 имеют всю необходимую аппаратную и программную поддержку мультимедийных таймеров, крайне маловероятно, что разработчику когда-нибудь придётся столкнуться с этим исключением.
— EMovementAlreadyStarted —
Исключение: EMovementAlreadyStarted
Модуль: Movements
Описание: возникает в тех случаях, когда разработчик пытается выполнить с уже запустившимся движением (т.е. с движением, для которого уже хотя бы один раз был вызван метод NextStep) такое действие, которое допустимо только для ещё не запустившихся движений. К таким действиям относится назначение поставщика координат (т.е. изменение свойства CoordProducer) для объектов типа TSpriteMovement и его наследников.
— ENoAlphaChannel —
Исключение: ENoAlphaChannel
Модуль: CellBanks
Описание: возникает при попытке выполнить действие, требующее маски, над изображением, не имеющим маски. Данное исключение может возбуждаться методами GetAlphaMask и Normalize класса TBitmapCellBank.
— EPathIsEmpty —
Исключение: EPathIsEmpty
Модуль: Movements
Описание: возникает при вызове метода GetNextCoord класса TPathCoords или его наследников, если оказывается, что до этого момента траектория не задана (т.е. длина массива Path равна нулю).
— EProducerNotAssigned —
Исключение: EProducerNotAssigned
Модуль: Movements
Описание: возникает при вызове метода NextStep класса TSpriteMovement и его наследников, если на момент запуска этого метода движению не назначен поставщик координат и/или поставщик спрайтов (т.е. хотя бы одно из свойств CoordProducer и Sprite имеет значение nil).
— EWrongCell —
Исключение: EWrongCell
Модуль: CellGameBoard
Описание: возникает при обращениях к свойствам TCellGameBoard.Cells и TCellLayer.Cells, если координаты ячеек выходят за пределы допустимого диапазона.
— EWrongImageIndex —
Исключение: EWrongImageIndex
Модуль: CellBanks
Описание: возникает при работе со свойствами и методами банка растровых изображений (TBitmapCellBank), которые требуют задания состояния и/или фазы изображения в тех случаях, когда указанные состояние или фаза выходят за пределы допустимого диапазона. Свойства, при обращении к которым может возникать исключение EWrongImageIndex: Blended, Transparent, TransparentColor, TransparentMode. Методы, при обращении к которым может возникать это исключение: AddBandToState, AddImage, AddMaskedBandToState, AddMaskedImage, CreateSimpleMask, DeletePict, DeleteState, DrawImage, ExchangePicts, ExchangeStates, GetAlphaMask, GetPhasesCount, GetRealPhasesCount, Normalize, SetAlphaMask. При вызове метода DrawCell это исключение не возникает, даже если состояние или фаза выходят за рамки диапазона. В этом случае неправильные значения состояния корректируются, а фазы — укладываются в нужный диапазон.
— EWrongImageSize —
Исключение: EWrongImageSize
Модуль: CellBanks
Описание: возникает при попытке добавить в банк TBitmapCellBank изображение, имеющее размеры, отличные от ImageWidth*ImageHeigth. Может выбрасываться следующими методами данного класса: AddImage, AddMaskedImage, AddBand, AddMaskedBand, AddBandToState, AddMaskedBandToState, AddBox, AddMaskedBox. У каждого из этих методов свои требования к размеру исходного изображения, см. их описание. Также возникает при вызове метода TBitmapCellBank.SetAlphaMask, если размер маски не совпадает с размером изображения, которому она назначается.
— EWrongLayerParent —
Исключение: EWrongLayerParent
Модуль: CellGameBoard
Описание: возникает в конструкторе TCellLayer.Create в том случае, если объект, переданный в качестве параметра Collection, не является объектом типа TCellLayers. Для единообразия работы с различными коллекциями компилятор разрешает в данном месте использовать любой наследник TCollection, но библиотека выполняет дополнительную проверку, так как работа класса TCellLayer с коллекциями других типов невозможна.
— EWrongLineLength —
Исключение: EWrongLineLength
Модуль: Movements
Описание: возникает при работе конструктора класса TLineCoords.Create в том случае, если перееденные параметры не позволяют построить линию более чем из одной точки, например, если параметр PtCnt равен 1, или параметр PtCnt равен 0, а координаты начальной и конечной точек совпадают. Как следствие, данное исключение может возникнуть и при вызове конструктора TCellMovement.Create, который внутри себя создаёт по заданным параметрам объект типа TLineCoords.
— EWrongMovementAlias —
Исключение: EWrongMovementAlias
Модуль: Movements
Описание: возникает при обращении к свойствам Movements, MovementExits и вызове метода RemoveMovement класса TMovementsManager, если заданный псевдоним отсутствует в списке объекта.
После установки библиотеки в палитре компонентов появляется закладка GameBoard, и в ней — три компонента: TCellGameBoard, TBitmapCellBank и TMovementsManager.
TCellGameBoard, который реализует саму доску, не имеет каких-либо особых редакторов компонента или свойств. Сначала я хотел их сделать, но потом понял, что задавать какие-то свойства клеток доски, фигур и т.п. раз и навсегда приходится редко. Обычно работает генератор случайных чисел или другой алгоритм динамической расстановки фигур, который всё равно не реализуешь никаким редактором времени разработки.
Главная особенность, отличающая TCellGameBoard от большинства других визуальных компонентов — это то, что его размеры нельзя изменить ни мышью, ни через свойства Width и Height. Эти размеры определяются свойствами ColCnt, CellWidth, RowCnt, CellHeight.
Свойство TCellGameBoard.Layers является коллекцией, и для него имеется стандартный редактор. Он отображает список всех имеющихся в коллекции слоёв, позволяя их создавать, удалять, перемещать и редактировать их свойства. Это полностью аналогично, например, работе со свойством Panels компонента TStatusBar.
Компонент TMovementsManager тоже не имеет никаких особых свойств для его редактирования во время разработки. По сути дела, его можно только создать и назначить обработчик его единственного события OnMovementFinished.
А вот TBitmapCellBank обладает специальным редактором, который можно вызвать, дважды щёлкнув на иконке компонента или выбрав пункт "Edit bank..." в его контекстном меню. Окно редактора показано на рисунке 1.
Рисунок 1 — Редактор компонента TBitmapCellBank
Основную часть окна занимает таблица с изображениями, загруженными в банк. Каждая строка соответствует одному состоянию, рисунки в строке идут слева направо в порядке возрастания фазы. Если для данной фазы в данном состоянии рисунок отсутствует, соответствующая позиция заштриховывается диагональной решёткой (как, например, все фазы, начиная со 2-ой, в состояниях 1..6 на рисунке 1). С помощью закладок в верхней части окна можно переключаться между показами изображений и их альфа-каналов. При показе альфа-канала некоторые позиции перечёркнуты косым красным крестом — это означает, что для данных состояния и фазы рисунок есть, но альфа-канал у него отсутствует.
Для работы с изображением его можно выбрать щелчком мыши. Фон выбранного изображения подсвечивается (см. состояние 2, фазу 1 на рисунке 1). Удерживая клавишу Ctrl, можно выбрать несколько изображений. Двойной щелчок на изображении выделяет всю строку, в которой оно находится. Если все выделенные рисунки принадлежат одному состоянию, то это состояние подсвечивается в левой колонке с номерами.
В правом верхнем углу окна находятся элементы управления, позволяющие видеть и менять значения свойств выделенных изображений. Значения в полях показывают текущие значения этих свойств для выбранных изображений. Если какое-либо поле осталось пустым, значит, выделенные изображения имеют разные значения этого свойства. Свойства соответствуют одноимённым свойствам класса TBitmapCellBank. Blended = True означает, что изображение имеет альфа-канал, и прозрачность определяется только им, значения остальных свойств игнорируются. Свойства Transparent, TransparentMode и TransparentColor позволяют управлять прозрачностью изображений без альфа-канала точно так же, как одноимённые свойства класса TBitmap.
Редактор практически не имеет возможностей модификации изображений, загруженных в банк, поэтому изображения должны быть заранее подготовлены в графическом редакторе и загружены в редактор компонента. При этом в файле можно склеить (как в длину, так и в ширину) несколько изображений, редактор компонентов разобьёт их на отдельные элементарные изображения. Поддерживается только один графический формат — BMP.
Ряд из пяти кнопок с зависимой фиксацией под списком свойств позволяет выбрать режим загрузки изображения из файла. Это даёт возможность выбирать, будет ли у рисунка альфа-канал, и если да, то какой. Самая левая в этом ряду кнопка, которая выбрана по умолчанию, указывает, что альфа-канал отсутствует. В этом случае размер элементарного изображения совпадает с размером изображения в банке, т.е. равен ImageWidth x ImageHeight. В остальных случаях размер элементарного изображения рамен ImageWidth x 2*ImageHeight. Верхняя половина изображения содержит собственно рисунок, нижняя — его альфа-канал. Так как альфа-канал содержит только одно значение на пиксель, а обычный рисунок — три значения (красный, зелёный и синий каналы), возникает неоднозначность в превращении нижней части элементарного изображения в альфа-канал рисунка. При выборе второй кнопки альфа-канал задаётся красным каналом нижней половины, третьей — зелёным каналом, четвёртой — синим каналом. При выборе пятой кнопки альфа-канал задаётся яркостью изображения, т.е. для каждого пикселя он вычисляется по формуле A = 0.3*R + 0.59*G + 0.11*B.
Ещё ниже располагаются кнопки для выполнения основных действий над банком изображений. Назначение кнопок таково:
- Добавить блок. Эта кнопка используется для загрузки блока изображений и добавлению их в виде новых состояний. Ширина и высота добавляемого рисунка должны быть кратны соответственно ширине и высоте элементарного изображения в соответствии с выбранным режимом. Пусть в добавляемом рисунке укладывается N элементарных изображений по ширине и M — по высоте. Тогда в банк будет добавлено M новых состояний по N изображений в каждом. Расположение изображений будет соответствовать их расположению в исходном рисунке.
- Добавить ленту. Этой кнопкой можно добавить изображения из файла в качестве новых фаз для текущего состояния. Ширина добавляемого изображения должна быть кратна ширине элементарного изображения, высота — равна высоте элементарного изображения. Рисунок разбивается на отдельные изображения, каждое из которых соответствует одной из фаз. Фазы добавляются в том же порядке, в каком изображения идут в указанном рисунке. Кнопка доступна, если выбрано хотя бы одно изображение, и все выбранные изображения принадлежат одному состоянию.
- Добавить маску. Создаёт альфа-канал для выбранного изображения на основе рисунка из файла (если изображение уже имеет альфа-канал, то подменяет имеющийся новым). Рисунок в указанном файле должен иметь размеры ImageWidth*ImageHeight. Трансформация рисунка в альфа-канал производится в соответствии с заданным режимом загрузки изображения. Нормализация изображения не производится, её при необходимости нужно делать отдельно. Кнопка доступна, когда выбрано ровно одно изображение, а установленный режим загрузки изображения подразумевает наличие альфа-канала.
- Нормализовать. Нормализует выбранное изображение в соответствии с его маской. Функция AlphaBlend, используемая для вывода полупрозрачных изображений, предполагает, что цветовые каналы передаваемого рисунка уже домножены на соответствующую альфа-составляющую, т.е. интенсивности полупрозрачных областей уже уменьшены. Между тем, при подготовке рисунка удобнее иметь дело с ненормализованным изображением, в котором цветовые каналы имеют оригинальные значения. Если вы загрузили такой рисунок, не забудьте его нормализовать, иначе получите результат, далёкий от ожидаемого. Кнопка доступна, когда выбрано хотя бы одно изображение, и все изображения имеют альфа-канал.
- Создать маску. Создаёт новый (или подменяет имеющийся) альфа-канал для выбранного изображения. Альфа-канал создаётся по следующему алгоритму: если Transparent=True, то для всех точек, чей цвет совпадает с прозрачным, альфа-канал устанавливает полную прозрачность, для остальных точек устанавливается прозрачность, заданная в поле ввода рядом с кнопкой (0 — непрозрачный, 255 — полностью прозрачный). Изображение при этом не нормализуется, при необходимости его следует нормализовать самостоятельно. Кнопка доступна, когда выбрано хотя бы одно изображение.
- Удалить выделенное. Удаляет все выделенные изображения из банка. Всё, что находится левее удаляемых изображений, смещается вправо. Хотя сам компонент TBitmapCellBank допускает существование состояний, в которые не загружено ни одного рисунка (т.е. нет ни одной фазы), редактор такого не допускает, поэтому, если при удалении окажутся выбранными все рисунки какого-то состояния, то оно не останется пустым, а будет уничтожено полностью, и все нижележащие состояния сместятся вверх. Кнопка доступна, когда выбран хотя бы один рисунок.
- Удалить состояние. Удаляет выбранное состояние целиком. Нижележащие состояния смещаются вверх. Кнопка доступна, если выбрано хотя бы одно изображение, и все выбранные изображения принадлежат одному состоянию.
- Переместить состояние вверх. Обменивает местами текущее состояние и состояние, находящееся над ним. Кнопка доступна, если выбрано хотя бы одно изображение, все выбранные изображения принадлежат одному состоянию, и это состояние не является самым верхним.
- Переместить состояние вниз. Обменивает местами текущее состояние и состояние, находящееся под ним. Кнопка доступна, если выбрано хотя бы одно изображение, все выбранные изображения принадлежат одному состоянию, и это состояние не является самым нижним.
- Переместить рисунок влево. Обменивает местами выбранный рисунок, и рисунок, стоящий слева от него. Кнопка доступна, когда выбран ровно один рисунок, не являющийся первым в своём состоянии.
- Переместить рисунок вправо. Обменивает местами выбранный рисунок, и рисунок, стоящий справа от него. Кнопка доступна, когда выбран ровно один рисунок, не являющийся последним в своём состоянии.
- Обменять рисунки. Меняет местами два выбранных рисунка. Кнопка доступна, когда выбрано ровно два рисунка.
В этом разделе изложены некоторые особенности реализации библиотеки, которые полезно знать при её использовании.
Прежде чем начинать рисовать фигуры на доске, надо нарисовать саму доску. Действия по рисованию доски выполняются в следующей последовательности:
- Весь компонент заливается цветом, заданным свойством BackColor
- Если назначен обработчик события OnDrawBackground, он вызывается.
- Выполняется цикл по всем элементам массива Cells. Для каждого выполняются следующие действия:
- Если назначен обработчик OnCellBeforeDraw, он вызывается для данной клетки
- Если обработчик OnCellBeforeDraw не установил параметр DefaultDraw в False, вызывается Cells[X, Y].Draw
- Если назначен обработчик OnCellAfterDraw, он вызывается для данной ячейки
Обратите внимание, что OnCellAfterDraw вызывается даже в том случае, если OnCellBeforeDraw установил DefaultDraw в False.
Метод Cells[X, Y].Draw делает следующие действия:
- Если цвет Cells[X, Y].BackColor отличен от clNone, заполняет прямоугольник ячейки этим цветом. В противном случае в прямоугольнике сохраняется то, что было нарисовано ранее.
- Вычисляет ширину рамки для данной клетки. Эта ширина равна Cells[X, Y].BorderWidth, если это значение больше или равно нулю, в противном случае — BorderWidth, установленному для всей доски.
- Если вычисленная ширина рамки больше нуля, рамка рисуется следующим образом:
- Левая и верхняя границы рисуются цветом Cells[X, Y].TopLeftColor, если это свойство отлично от clNone. Если оно равно clNone, эти границы рисуются цветом TopLeftColor, заданным для всей доски (если оно тоже равно clNone, верхняя и левая границы не рисуются вообще).
- Правая и нижняя границы рисуются цветом Cells[X, Y].BottomRightColor, если это свойство отлично от clNone. Если оно равно clNone, эти границы рисуются цветом BottomRightColor, заданным для всей доски (если оно тоже равно clNone, правая и нижняя границы не рисуются вообще).
Перерисовка фона может потребовать относительно много времени, особенно если клетки закрашены в разные цвета, а границы широкие. Чтобы не тратить на это время каждый раз, фон рисуется на отдельном растровом рисунке, который при рисовании компонента на экране выводится целиком. Это значит, что если вы не предпринимаете никаких специальных действий, обработчик OnDrawBackground будет вызван всего один раз, а обработчики OnCellBeforeDraw и OnCellAfterDraw — по одному разу для каждой клетки. При последующих перерисовках компонента будет использоваться сохранённый рисунок доски, и перечисленные выше действия, в т.ч. и вызов обработчиков событий, выполняться не будет. Доска перерисовывается заново только в том случае, если были изменены свойства, влияющие на вид доски (у компонента в целом или у элемента массива Cells): BackColor, BorderWidth и т.п. Если вы хотите, чтобы при очередной перерисовке компонента при неизменных указанных свойствах доска была перерисована, вызовите метод InvalidateBoard. Описанное поведение касается как анимированного, так и неанимированного режима работы компонента.
Для перерисовки доски используется два буфера: один хранит изображение самой доски, другой — изображение доски с фигурами. Перерисовка компонента на экране заключается в проверке необходимости перерисовки буферов и копированию буфера доски на экран. Этим достигается отсутствие мерцания и высокая скорость перерисовки в тех случаях, когда изменять изображение доски не нужно (например, когда просто требуется восстановить доску после перекрытия её другим окном).
Перерисовка буфера доски с фигурами осуществляется в следующем порядке:
- Проверяется, нуждается ли в перерисовке буфер с изображением самой доски, и если да, то он перерисовывается.
- Буфер с изображением самой доски копируется в буфер с изображением доски с фигурами
- Выполняется цикл по слоям в порядке возрастания, начиная с нуля. Для каждого слоя выполняются следующие действия:
- Для всех клеток рисуются фигуры, которые находятся на этом слое.
- Рисуются все движения, принадлежащие данному слою. Те движения, которые были созданы раньше, рисуются первыми.
В неанимированном режиме буфер доски с фигурами перерисовывается только в том случае, если были изменены какие-либо свойства доски, слоёв, фигур. В противном случае на экран копируется уже существующее на данный момент изображение из буфера доски с фигурами. Если никакие свойства не изменялись, но разработчику требуется, чтобы буфер был перерисован, он может воспользоваться методом InvalidateBoard. Стандартные методы Refresh и Repaint сами по себе не приводят к полной перерисовке доски: если не было зафиксировано никаких изменений, они только копируют на экран изображение из буфера, не обновляя его.
Обратите внимание, что банки изображений не уведомляют использующие их доски об изменениях, которые в них произошли. Это означает, что если вы выполнили какие-то изменения в используемом банке, то для отображения этих изменений в неанимированном режиме придётся вызвать InvalidateBoard, иначе существующее изображение доски с фигурам так и будет копироваться из буфера до тех пор, пока не будет изменено что-либо ещё.
Свойство ImmediateUpdate влияет на то, как влияют изменения свойств на перерисовку доски в неанимированном режиме. Если оно имеет значение True, действия, которые приводят к перерисовке доски, вызывают немедленную перерисовку буфера и копирование изображения на экран. Несколько последовательных изменений приводят к нескольким перерисовкам доски, и пользователь успеет заметить, что изменения выполняются последовательно по одному. Если это свойство равно False (это значение по умолчанию), эти действия приводят только к установке внутреннего флага, сигнализирующего о необходимости перерисовки, а также к вызову функции Invalidate, чтобы ОС при случае дала команду на перерисовку окна доски — тогда и будет обновлён буфер. Это приводит к тому, что несколько идущих подряд изменений, между которыми нет выборки и диспетчеризации сообщений, приводят только к однократной перерисовке доски уже после того, как все эти изменения сделаны. С точки зрения пользователя все изменения будут сделаны одновременно.
Если ImmediateUpdate имеет значение True, в некоторых случаях всё же может потребоваться, чтобы несколько последовательных изменений приводили к однократной, а не многократной перерисовке доски. Для этого служат методы LockUpdate и UnlockUpdate. Все изменения, которые выполнены между этими методами, не приводят к перерисовке доски до тех пор, пока не вызван UnlockUpdate. При значении свойства ImmediateUpdate = False эти методы тоже могут понадобится, если нужно запретить изменения в коде, который содержит локальную петлю сообщений, или же эти изменения продолжаются в течение нескольких циклов глобальной петли.
В анимированном режиме перерисовка буфера с изображением доски выполняется каждый раз при получении сигнала от таймера независимо от того, зафиксированы ли какие-либо изменения с предыдущей перерисовки или нет. Здесь компонент исходит из того, что, помимо явных изменений изображения могут быть неявные изменения, связанные с тем, что рисование фигур и движений зависит от номера тика. Никакие изменения свойств доски и связанных с ней объектов в анимированном режиме не приводят к перерисовке доски в буфере. Все изменения будут учтены при перерисовке доски на следующем тике.
Такая схема работы доски в анимированном режиме приводит к ненужному расходу процессорного времени в тех случаях, когда анимация используется мало, реальное изменение доски происходит не на каждом шаге. Но оптимизировать эту часть работы библиотеки можно только с учётом специфики конкретной игры, которая разрабатывается с её помощью. Так что учитывайте это, и уж если используете анимацию, используйте её как можно больше.
В качестве таймера доска использует мультимедийный таймер. Этот таймер имеет разрешение 1 мс, что позволяет плавно менять величину интервала между тиками, регулируя скорость анимации. К недостатком этого таймера относится то, что он для своей работы меняет глобальный системный параметр — величину кванта времени. Обычно это проходит незаметно, но некоторые эффекты могут быть заметны, когда запущено ещё одно приложение, использующее мультимедийный таймер. К таким приложениям относится, например, стандартный клиент ICQ. Замечено, что программа, написанная с использованием компонента TCellGameBoard в анимированном режиме потребляет больше процессорного времени при запущенном клиенте ICQ, чем без него. Впрочем, в большинстве случаев это заметно только по менеджеру задач, так как до полной загрузки процессора остаётся далеко. И, если кто-нибудь сможет подсказать мне таймер, дающий такую же точность и разрешение, чтобы его можно было использовать вместо мультимедийного, с большим интересом отнесусь к такому совету.
Для передачи сигналов таймера доске предусмотрено два оконных сообщения: WM_MMTimerTick и WM_UpdateBoard. Сам таймер уведомляет о наступлении очередного интервала вызовом указанной ему при запуске процедуры. Эта процедура вызывается в неглавной нити, так что ни о какой перерисовке доски прямо в ней не может быть и речи. Поэтому эта процедура просто ставит в очередь сообщений доски сообщение WM_TimerTick. Но обработчик этого сообщения тоже не выполняет рисование доски. Он проверяет, есть ли в очереди доски сообщение WM_UpdateBoard, и если нет, то добавляет его в очередь. Перерисовка доски осуществляется при обработке WM_UpdateBoard. Это сделано для того, чтобы минимизировать последствия большой загрузки процессора другими приложениями. Предположим, что в какое-то время процессор загружен другим приложением, и игре с доской не достаётся достаточно квантов времени, чтобы за интервал между тиками перерисовать доску. Это приводит к забиванию очереди сообщениями WM_MMTimerTick. Если бы рисование доски осуществлялось бы в обработчике WM_MMTimerTick, то после того, как нагрузка на процессор снизилась бы, игру ещё долго лихорадило бы, пока доска извлекала бы из очереди все сообщения и вновь и вновь перерисовывала бы доску. В текущем же варианте обработка одного сообщения WM_MMTimerTick занимает очень мало времени, а сообщения WM_UpdateBoard в очереди просто не накапливаются, так как второе туда не помещается, если одно уже есть. Таким образом, накопленные сообщения быстро разбираются, и игра восстанавливает работоспособность. Реализовать аналогичный запрет на помещение в очередь нескольких сообщений WM_MMTimerTick мешает то, что процедура таймера вызывается в отдельной нити, которая не может проверить, что лежит в очереди сообщений главной нити.
Каждая фигура на доске характеризуется своей фазой. Изменение фазы может быть использовано для выбора одного из нескольких вариантов изображения фигуры, а их последовательная смена — для анимации. В общем случае фаза каждой конкретной фигуры зависит от фазы доски в целом (далее — CellGameBoard.Phase), от фазы слоя, которому принадлежит фигура (Layer.Phase), от собственной фазы фигуры (Cell.Phase), а в анимированном режиме — ещё и от номера тика (Tick) и скорости анимации фигуры (Cell.Speed). Здесь мы рассмотрим, как вычисляется фаза фигуры в разных режимах.
В неанимированном режиме фаза является простой суммой трёх величин:
Phase = Cell.Phase + Layer.Phase + CellGameBoard.Phase
По этой же формуле вычисляется фаза неанимированной фигуры на анимированной доске.
В анимированном режиме для анимированной фигуры зависимость от CellGameBoard.Phase пропадает, и формула выглядит так:
Phase = (Index div Cell.Speed) + Layer.Phase + Cell.Phase
Здесь Index — это параметр, переданный в метод TGameCell.DrawCell. Слой передаёт в этом параметре значение Tick, поэтому итоговая формула выглядит так:
Phase = (Tick div Cell.Speed) + Layer.Phase + Cell.Phase
Вычисленное таким образом значение Phase передаётся банку изображений. Легко видеть, что в анимированном режиме эта величина возрастает неограниченно (именно поэтому для неё выбран тип Int64 вместо Integer — типа Integer при интервале 1 мс хватило бы только на 24 дня непрерывной работы, а непрерывная работа в течение такого времени не является, в принципе, чем-то абсолютно невозможным, в то время как Int64 хватит почти на 300 миллионов лет). Это значит, что рано или поздно вычисленная таким образом фаза выйдет за пределы диапазона, допустимого для фазы в банке. Но банк должен отобразить фигуру при любом значении фазы, и ответственность за приведение фазы к диапазону лежит на банке. Для банка в качестве фазы рекомендуется брать остаток от деления параметра Phase на количество фаз. Стандартный банк TBitmapCellBank именно так и поступает. Благодаря этому при анимации обеспечивается цикличность: после того как фигура дошла до конечной фазы, она снова возвращается в начальную.
Помимо обычного рисования фигуры на доске существует ещё одна ситуация, когда рисуется фигура — при рисовании спрайта типа TCellSprite. Метод DrawSprite этого класса просто подставляет переданный ему параметр Phase в качестве значения Index. Фаза неанимированной фигуры не зависит от этого параметра и поэтому по-прежнему является суммой трёх фаз: собственной, слоя и доски (эти сведения носят чисто теоретический характер, так как в неанимированном случае нельзя использовать движения). Для анимированного же случая значение параметра Index определяется классом TSpriteMovement. Он вычиляет Index по следующей формуле:
Index = SpriteMovement.Tick + SpriteMovement.InitialPhase
Здесь SpriteMovement.Tick — это собственный тик движения. При первом вызове метода NextStep он равен 0, при втором — 1 и т.д. SpriteMovement.InitialPhase — это свойство класса TSpriteMovement. Таким образом, итоговая формула для расчёта фазы выглядит так:
Phase = ((SpriteMovement.Tick + SpriteMovement.InitialPhase) div Cell.Speed) +
Layer.Phase + Cell.Phase
Как уже было сказано выше, движения — это специальные объекты, с помощью которых можно отобразить на доске всё, что угодно, не ограничиваясь теми возможностями, которые даёт класс TGameCell для рисования в ячейке.
Базовый класс для всех движений — это TMovement. Его метод NextStep вызывается, когда надо нарисовать очередную фазу движения. В классе TMovement это абстрактный метод. Наследники перекрывают его, чтобы рисовать то, что нужно.
Каждый объект движения привязывается к определённому слою — это позволяет управлять очерёдностью рисования движений. Слой рисует все свои движения сразу после отрисовки фигур. Движения рисуются в порядке их добавления к слою.
Привязка движения к слою осуществляется в конструкторе класса TMovement. Таким образом, достаточно просто создать экземпляр требуемого класса движения, чтобы это движение начало отображаться.
Использование движений имеет смысл только в анимированном режиме работы доски. Перерисовка в этом режиме выполняется строго по тикам таймера. Таким образом, первый вызов NextStep для вновь созданного объекта движения будет выполнен при обработке следующего тика таймера доски. Обработка очередного тика начнётся не раньше, чем очередное сообщение будет извлечено из очереди и отправлено в соответствующую оконную процедуру. Таким образом, сразу после того как вы создали объект движения, вы можете менять его свойства, не опасаясь того, что первая фаза уже отрисована на доске в соответствии со старыми значениями свойств.
Сказанное выше относится и к случаю, когда создание движения выполняется в обработчике события OnBoardTimer. Это событие срабатывает в самый последний момент обработки сигнала таймера, когда изображение доски, соответствующее данному тику, уже перерисовано и выведено на экран. Таким образом, созданный в этом обработчике объект движения получит управление только уже при следующем тике.
По времени жизни объекты движений делятся на конечные и бесконечные. Конечные движения — это такие движения, которые по своей сути ограничены по времени жизни. Ярким примером такого движения является перемещение фигуры из одной клетки в другую: как только фигура заняла клетку, являющуюся целью перемещения, смысла в дальнейшем существовании объекта движения больше нет. Бесконечные движения не имеют внутренних причин для ограничения времени жизни, момент окончания существования движения определяется какими-то внешними по отношению к этому объекту событиями.
Класс TMovement имеет два метода, которые определяют время жизни объекта. Во-первых, это функция IsEndless, которая возвращает True для бесконечных движений и False для конечных. Во-вторых, это уже упоминавшийся метод NextStep. Он возвращает False, если нарисованная им очередная фаза не является конечной и True, если является. Реализации методов IsEndless и NextStep должны быть согласованы: если IsEndless возвращает True, NextStep должен всегда возвращать False; если IsEndless возвращает False, на каком-то шаге NextStep должен вернуть True. Несоблюдение этих требований может привести к зависанию программы или получению исключения Access violation.
Управление временем жизни бесконечных движений ничем не отличается от управления временем жизни обычного объекта. Когда движение больше не нужно, его объект удаляется обычным образом — вызовом Destroy, Free или FreeAndNil. При этом движение исключается из списка движений слоя, и при обработке очередного тика оно уже не будет отрисовываться. В библиотеке не предусмотрено никаких дополнительных средств управления видимостью и активностью движений, поэтому всё управление ими сводится к двум операциям: созданию объекта движения, когда требуется начать движение, и удалению этого объекта, когда движение больше не нужно. Приостановить или скрыть движение, не удаляя его объект, в общем случае невозможно. Можно только скрыть слой, и тогда все принадлежащие ему движения не будут отрисовываться. Но в какой фазе движения будут отображены после восстановления видимости этого слоя, в общем случае непредсказуемо и зависит от реализации конкретного класса движения.
Временем жизни конечных движений управляет также слой, которому они принадлежат. Если очередной вызов NextStep вернул True, объект движения сразу же уничтожается. Таким образом, для конечных движений в большинстве случаев достаточно только создать объект, а удалён он будет автоматически. Ручное удаление объектов конечных движений также не запрещено, но надо быть уверенным, что объект ещё не был удалён автоматически. Это создаёт некоторые проблемы, так как библиотека не предоставляет универсального средства проверки того, был ли удалён объект движения или нет.
Тем не менее, задача определения момента окончания движения или группы движений в том или ином виде возникает часто, поэтому библиотека предоставляет для этого некоторые средства. Простейшим из них являются методы WaitMovement и WaitMovements, которые приостанавливают выполнение программы до тех пор, пока не будет завершено одно движение или группа движений. Приостановка выполняется специализированной петлёй сообщений, которая извлекает из очереди и передаёт на обработку только сообщения, касающиеся таймера. Таким образом, программа входит в цикл перерисовки доски по таймеру до тех пор, пока все указанные движения не будут завершены.
В более сложных случаях следует использовать класс TMovementsManager. Этот класс позволяет создавать ассоциативные списки движений. Про движение, добавленное в такой список, можно в любой момент получить информацию, завершено оно или нет, и если нет, получить ссылку на его объект. Этот класс удобен тем, что его можно разместить на форме во время проектирования и сразу назначить обработчик события, которое возникает, когда одно из движений, входящих в список, завершается.
И, наконец, самый гибкий способ получения информации о движениях — использование низкоуровневых уведомлений. Эти уведомления чем-то похожи на события, но реализованы более сложным образом, чтобы обойти основное ограничение механизма событий, который не позволяет назначать одному событию несколько получателей. Каждое движение хранит список объектов-нотификаторов, через которые оно уведомляет все заинтересованные стороны о своём завершении. Добавить такой объект в список движения можно с помощью метода TMovement.AddNotifier. Объект-нотификатор должен быть наследником класса TMovementFinishNotifier. В этом классе есть абстрактный метод MovementFinished — уведомление об окончании движения производится вызовом этого метода. Один объект-нотификатор может использоваться для получения уведомлений от любого количества движений, а одно движение может посылать уведомления любому количеству объектов-нотификаторов. Все описанные выше способы проверки завершения движений работают через этот механизм, поэтому их можно использовать одновременно.
Библиотека содержит ряд готовых классов движений, которые реализуют самые распространённые движения. Чаще всего вам придётся, скорее всего, использовать класс TCellMovement, который перемещает фигуру из одной клетки в другую, причём как исходная клетка, так и целевая могут лежать за пределами доски, что также позволяет осуществлять уход фигуры из клетки за край доски, приход фигуры в клетку из-за края доски и пролёт фигуры по доске от края до края. Следует понимать, что TCellMovement перемещает по доске не собственно фигуру, размещённую на слое, а её визуальную копию. Но по умолчанию он очищает от фигуры исходную клетку, а после завершения движения помещает такую же фигуру в целевую клетку, что создаёт полную иллюзию перемещения фигуры.
Многие движения могут быть представлены как перемещение некоторого неизменного или меняющегося изображения (спрайта) вдоль какой-либо траектории (сюда же относится и вырожденный случай, когда изображение неподвижно — это просто замкнутая траектория из одной точки). Специально для реализации таких движений существуют классы TCoordProducer (поставщик координат), TSpriteProducer (поставщик спрайтов) и объединяющий их TSpriteMovement. TCoordProducer умеет возвращать очередную точку траектории, TSpriteProducer — рисовать очередную фазу спрайта в заданных координатах. TSpriteMovement — это движение, которое при отрисовке очередной фазы получает от поставщика координат очередную точку и передаёт её поставщику спрайтов, чтобы тот нарисовал там спрайт. TCoordProducer и TSpriteProducer — это абстрактные классы, от них нужно порождать наследников, реализующих конкретные траектории и конкретные спрайты (несколько таких наследников уже входит в состав библиотеки). Класс TSpriteMovement может использоваться непосредственно, но в некоторых случаях может оказаться удобно породить от него специализированный наследник, чтобы не манипулировать поставщиками вручную. Так, упоминавшийся выше TCellMovement является таким наследником. В нём поставщик спрайтов рисует фигуру, а поставщиком траектории служит TLineCoords — наследник TCoordProducer, который реализует прямолинейные траектории.
Существующий набор классов движений вместе с возможностью писать свои классы даёт разработчику большие возможности по встраиванию анимационных эффектов в игры. Пять из прилагающихся к статье шести примеров используют движения, причём некоторые из них — очень активно. Чтобы познакомиться с движениями поближе, рекомендую разобраться с кодами этих примеров.
В этом разделе описаны примеры игр, которые демонстрируют возможности библиотеки. Сразу признаюсь, что мои способности к рисованию весьма скромны, поэтому большинство рисунков — либо простые геометрические формы, либо откуда-то позаимствованы. А то, что пришлось нарисовать мне самому, выглядит далеко не лучшим образом. Так что если кто-то сможет нарисовать более красивые картинки, буду очень благодарен.
Я не буду описывать здесь реализации примеров во всех подробностях. Подробности содержатся в комментариях в самом коде. Здесь я буду больше говорить об основных идеях реализации, которые "размазаны" по всему значительной части программы, и потому для них трудно найти место в коде, где соответствующий комментарий будет уместен.
Рекомендую разбираться с кодом игр в том порядке, в котором они здесь описаны. Во-первых, они упорядочены по возрастанию сложности, а во-вторых, в тех играх, которые идут позднее, я в комментариях опускаю некоторые детали, которые должны быть хорошо понятны человеку, разобравшемуся с предыдущими играми.
В качестве первого, самого простого, примера реализуем с помощью компонента TCellGameBoard известную игру Minesweeper, которая входит в состав Windows (в русских версиях она называется "Сапёр", хотя на самом деле minesweeper — это минный тральщик; мины в игре, как нетрудно заметить, морские).
Отчасти чтобы не перегружать первый пример не относящимся к демонстрации возможностей библиотеки кодом, отчасти потому, что мне так нравится, наша реализация будет иметь следующие отличия от системной:
- Размер клетки будет не 16х16, а 24х24 — на современных больших мониторах это лучше смотрится
- Будет только один размер доски, соответствующий максимальному уровню сложности в системной реализации
- Закрытая клетка будет либо пустой, либо с флагом, а возможности поставить знак вопроса не будет (сколько ни играл, так и не понял, зачем это нужно)
- Открытие клетки будет происходить не в момент отпускания, а в момент нажатия кнопки мыши — это намного проще в реализации, а на удобстве игры, на мой взгляд, совсем не сказывается
- Тем, кому не нравятся эти изменения, предлагаю в порядке первого упражнения доработать игру — это будет совсем не сложно.
Лирическое отступление: : работа над этим примером навеяла множество воспоминаний из школьных времён. Дело в том, что это далеко не первая моя реализация этой игры. Первая была для ZX Spectrum'а, который был у меня дома. Очень хотелось и поиграть, и применить к чему-нибудь свежие знания ассемблера. Правда, прежде чем приступать к работе, пришлось написать на бейсике компилятор ассемблера (только без компоновщика — как делать компоновщик, я тогда ещё не додумался, поэтому размещал все процедуры в памяти руками, строго планируя, в какой последовательности их писать, чтобы каждая новая использовала только то, что уже написано, и чтобы ни в коем случае ничего потом не править, так как исходники не сохранялись, а дизассемблер я писать не стал, хотелось побыстрее приступить к работе над игрой). Это была моя первая и последняя программа целиком на ассемблере, и она, как ни странно, оказалась вполне работоспособной, за исключением одной детали: я не смог реализовать нормальный генератор псевдослучайных чисел и после некоторого количества игр обнаружил, что, открыв примерно половину доски, я могу с большой точностью угадать, где будут мины на второй половине. Вторя моя реализация minesweeper'а была сделана на Турбо Паскале по заданию школьного учителя, но там не было ничего особо интересного. Зато потом началось моё увлечение резидентными программами(под DOS, разумеется), и я написал три или четыре варианта резидентного minesweeper'а (все на том же Турбо Паскале), в который можно было играть в школьном компьютерном классе, мгновенно возвращаясь к заданию урока, когда подходил учитель (старался, кстати, не для себя — IBM-совместимый компьютер у меня тогда дома уже был, и я сам мог играть сколько хотел). Несколько вариантов потребовалось потому, что, пока мне не попалась очень толковая статья про резидентные программы (попалась, конечно, в журнале, так как про интернет мы тогда даже не слышали), я не мог написать устойчивой резидентной версии. После школы мне до сих пор ни разу больше не приходилось реализовывать minesweeper, но, "взяв в руки шашки", я обнаружил, что всё ещё способен сделать это :)
Главное окно программы показано на рисунке 2.
Реализация игры очень проста. Состояние доски описывается двумерным массивом, каждый элемент которого кодирует состояние клетки. Это состояние описывает всё: содержимое клетки (пусто, число, мина) и её статус (закрыта, открыта, стоит флаг). Получается, что у нас есть, например, такие состояния: "клетка открыта, в ней пусто", "клетка открыта, в ней четвёрка", "клетка закрыта, в ней двойка", "клетка закрыта, на ней флаг, в ней мина" и т.п. Чтобы не присваивать незначащие номера каждому состоянию, обычно делают, например, так: каждый элемент массива имеет тип Byte. Младшие четыре бита отводятся на хранение числа, которое стоит в клетке (0 для пустой клетки), один бит на признак того, есть ли мина или нет, один бит — на признак закрытости клетки, ещё один — на признак того, стоит ли в ней флаг. Получается число, анализируя отдельные биты которого, легко понять, в каком состоянии находится клетка.
Но при использовании готовых компонентов из библиотеки этот способ не подходит по двум причинам. Во-первых, нумерация состояний получается не сплошной. Во-вторых, многие состояния рисуются одинаковыми картинками, а так как TBitmapCellBank подразумевает отдельное изображение для каждого состояния, получилось бы многократное дублирование одного изображения. Первый выход, который приходит в голову — манипуляции с фазами. Но это спасает только от несплошной нумерации, оставляя проблему дублирования изображений. Поэтому я сделал по-другому — воспользовался возможностью TCellGameBoard делать несколько слоёв. Я сделал два слоя: нижний, в котором помещаются цифры и мины, и верхний, в который помещаются различные варианты "крышек" (пустая, с флагом, с неправильным флагом, с неоткрытой миной). Для каждого из этих слоёв создан свой экземпляр TBitmapCellBank (хотя можно было бы обойтись и одним, но с двумя пример получается нагляднее).
Примечание: вообще, более правильным был бы другой вариант — порождение от TBitmapCellBank специализированного наследника с перекрытым методом DrawCell. Банк содержал бы все изображения по одному разу, а DrawCell анализировал бы биты переданного значения State, на основе этого выбирал бы номер нужной картинки и уже это значение передавал бы при вызове унаследованного DrawCell. Это позволило бы и удобно разбирать состояние по отдельным битам, и обойтись одним слоем. Но у нас всё-таки первый пример работы с библиотекой, и начинать сразу с вещей, требующих хорошего знакомства с ней, было бы неправильно.
Теперь нам остаётся только манипулировать числами в массивах фигур слоёв. Тут нет никакой хитрости, надо просто делать всё аккуратно. Все более конкретные комментарии по этому поводу вы найдёте в коде примера. В коде действительно приходится только манипулировать номерами состояний, почти не отвлекаясь на графику и на то, как это всё будет отображаться. "Почти" только потому, что я решил немного оживить игру и установил свойство ImmediateUpdate доски равным True, чтобы рекурсивное открытие клеток вокруг пустого места происходило не моментально, а постепенно. Но в некоторых ситуациях постепенное отображение изменений не нужно, и приходится вызывать LockUpdate и UnlockUpdate. Больше ничего, относящегося к графике, в коде нет. Размер модуля главной и единственной формы до того, как я вставил туда комментарии, был ровно 300 строк (включая пустые) — совсем неплохо для такой игры.
Единственное, что заслуживает отдельного комментария здесь, это алгоритм расстановки мин. Этот алгоритм придумал я сам, но идея настолько очевидна, что вряд ли я был единственным, кто до него додумался, и удивляет меня только одно: почему я до сих пор нигде не встречал его описания. Может, кто-то из читателей знает, где этот алгоритм описан впервые, и подскажет мне.
Итак, у нас есть N клеток, в которые нужно поставить M мин (M<=N). Проходим последовательно все клетки и ставим в каждую мину с вероятностью (M–m)/(N–n), где m — количество уже поставленных мин, n — количество уже пройденных клеток, не считая той, судьба которой сейчас определяется (поставить мину с заданной вероятностью P очень просто: надо вызвать функцию Random без параметров, и если результат меньше или равен P, мину ставим, если больше — не ставим).
Достаточно очевидно, что в результате работы этого алгоритма в любом случае будет поставлено ровно M мин. Действительно, если на каком-то шаге мы поставим M-ую мину, то для всех оставшихся клеток вероятность будет равной нулю. С другой стороны, если на каком-то шаге окажется, что не поставленных мин осталось столько же, сколько осталось клеток, для всех оставшихся клеток вероятность будет равна 1.
Менее очевидно, что алгоритм даёт равновероятное распределение мин, т.е. для каждой клетки вероятность появления в ней мины будет ровно M/N. Для первой клетки m=0 и n=0, поэтому вероятность именно такая. Для второй клетки n=1, а m=1, если в первую клетку мина поставлена, и m=0, если не поставлена. Вероятность первого варианта M/N, второго — (N–M)/N. Вспоминая формулу вычисления условной вероятности, получаем, что вероятность появления мины во второй клетке равна (M–1)/(N–1)*(M/N)+M/(N–1)*(N–M)/N. Упростив это выражение, нетрудно убедиться, что оно равно M/N.
Для остальных клеток это утверждение доказывается по индукции. Я не буду приводить здесь эти громоздкие формулы, надеюсь, что читатели поверят мне на слово — я это честно доказал. А ещё до того, как доказал, проверил экспериментально.
Обычно используют другой алгоритм: случайным образом выбирают номер клетки (в двумерном случае — две координаты) и, если в выбранной таким образом клетке ещё не стоит мина, ставят её и увеличивают счётчик поставленных мин на единицу. Процесс повторяют до тех пор, пока счётчик не достигнет значения M.
Описанный здесь алгоритм имеет как минимум два преимущества перед "обычным". Во-первых, его время работы O(N). Время работы "обычного" алгоритма непредсказуемо, и теоретически он может никогда не завершиться, если всё время будет попадать в клетки, куда уже поставлены мины (это особенно заметно, если N–M<<N). Во-вторых, описанный здесь алгоритм гарантирует равновероятное распределение мин, а "обычный" — не гарантирует. Поэтому я всегда использую этот алгоритм в подобных ситуациях.
Сюжет игры очень прост: человеку надо расставить ящики на складе в определённые места (sokoban в переводе с японского — работник склада). Ящики можно только толкать, тянуть нельзя. Толкать более одного ящика тоже нельзя. Соответственно, если ящик оказывается, например, в углу, вытащить оттуда его уже нельзя, так что подобных ситуаций надо избегать. Пример уровня показан на рисунке 3.
Для изображения стен и пола используются текстуры, взятые с сайта http://newtextures.h1.ru
Первая из ставших известными в России версий игры Sokoban была выпущена фирмой Spectrum Holobyte в 1984 году. Она использовала наиболее распространённый в то время графический стандарт CGA (для молодых поясню — 320x200 пикселей и 4 цвета: чёрный, белый, голубой, малиновый). Изображение одного из уровней той версии можно увидеть здесь. Позже был выпущен целый ряд вариантов игры с более продвинутой графикой и другими наборами уровней. Игра очень интересна и проста в реализации, поэтому с помощью любого поисковика можно обнаружить множество различных её вариантов на разных языках.
Лирическое отступление: игру Sokoban мне тоже уже приходилось писать раньше — ещё для ZX Spectrum. Это было ещё раньше, чем игра Minesweeper, и писал я её на Бейсике. Теперь смешно вспоминать, сколько ошибок я наделал, особенно сложно мне далась проверка того, что уровень закончен. Я долго не мог додуматься до простого критерия — равенства ящиков на местах количеству мест, и изобретал вместо этого всякие сканирования всего массива с проверкой и подсчётом, что заставляло компьютер заметно задумываться после каждого шага. А ещё я тогда осваивал работу со знакогенератором ZX Spectrum и учился загружать туда русские шрифты. Естественно, игру я сделал русифицированной. Проблема была в том, что русские буквы приходилось загружать вместо латинских — других мест для этого просто не было, и в итоге программу приходилось редактировать примерно в таком виде:
...
150 ФОР ы=1 ТО 16
160 РЕАД с
170 ЛОЦАТЕ 1,ы
180 ПРИНТ с
190 НЕЧТ ы
...
|
|
Этот пример, в отличие от предыдущего, использует анимацию: как анимацию фигур, так и движения. Для реализации игры нам потребуются три слоя. На нижнем будет изображён сам лабиринт: стены и пол. На среднем — анимированные ромбики, изображающие места для ящиков. На верхнем — ящики и человечек. Каждому уровню соответствует свой экземпляр класса TBitmapCellBank.
Для оживления игры будем использовать несколько вариантов текстур пола и стен. В банке изображений состояние 0 соответствует полу, 1 — стене. У каждого из них есть несколько фаз, каждая из которых соответствует одному из вариантов текстуры (конкретный вариант выбирается при загрузке уровня в соответствии с установками пользователя). В меню "Вид" пользователь может выбрать текстуру, которая ему больше нравится, или сделать выбор текстуры случайным.
Ромбики, в принципе, можно было бы изобразить на том же слое, что и пол со стенами, но возникли бы проблемы с их анимацией с учётом того, что все восемь фаз изображения ромбика пришлось бы рисовать для каждой текстуры. И следить за сохранением соответствия, если захочется добавить или удалить текстуру. Кроме того, так как фазы уже заняты под анимацию, разные текстуры пришлось бы отображать разными состояниями, что добавило бы возни при проверке, является ли заданная позиция местом для ящика. Поэтому я решил выделить ромбики в отдельный слой — это сильно упростило как подготовку изображений, так и код.
В верхнем слое ящики и человечек не анимированы. Анимация появляется только при перемещении человечка. Перемещения человечка и ящика реализуются экземплярами класса TCellMovement, причём для изображения человечка на время движения включается анимация, чтобы он переставлял ноги.
В игре реализованы все 50 уровней из версии от Spectrum Holobyte. Я постарался сделать добавление новых уровней по возможности более лёгким. Наиболее естественное описание уровня для игры — в виде двумерного массива чисел, каждый элемент в котором кодирует начальное состояние соответствующей клетки. Но ещё лучше будет кодировать уровень массивом строк — тогда соответствующая константа будет очень наглядна. Вот, например, фрагмент кода, отвечающий за первый уровень:
const
Levels:array[1..LevelCount] of TLevel =
(
('....................',
'....................',
'....................',
'....00000...........',
'....0 0...........',
'....0* 0...........',
'..000 *00..........',
'..0 * * 0..........',
'000 0 00 0...000000.',
'0 0 00 00000 ++0.',
'0 * * ++0.',
'00000 000 0!00 ++0.',
'....0 000000000.',
'....0000000.........',
'....................',
'....................'),
|
|
Согласитесь, что здесь очень легко угадывается уровень, изображённый на рисунке 3. В комментариях к коду описано, какой символ что кодирует.
Самой нетривиальной частью игры оказалась правильная реакция на клавиатуру. Связано это с тем, что перемещение человечка не мгновенно. Управление человечком осуществляется стрелками. Если пользователь нажал и удерживает стрелку, за время движения человечка в буфере накапливаются сообщения WM_KeyDown, связанные с автоповтором нажатия, причём накапливаются быстрее, чем извлекаются, поэтому когда пользователь отпускает клавишу, человечек продолжает двигаться и останавливается дальше, чем это было задумано. С другой стороны, время перемещения на одну клетку оказалось меньше, чем стандартная величина задержки перед первым повтором нажатия, и из-за этого при удерживании стрелки возникала неприятная пауза между первым и вторым перемещениями. Я попробовал несколько способов, которые имели другие проблемы: например, человечек не реагировал на изменение направления движения, если новая стрелка была нажата до того, как человечек закончил движение в предыдущем направлении. Или неадекватно реагировал, если нажать стрелку, а за время движения успеть отпустить её и снова нажать. В результате экспериментов я остановился на следующем алгоритме.
- При получении сообщения WM_KeyDown о нажатии стрелки проверяем по его параметрам, является ли нажатие первым, или это автоповтор. Автоповторы просто игнорируем.
- Если нажатие первое, помещаем в очередь сообщений специальное пользовательское сообщение WM_DoStep, передавая через его параметры информацию о том, какая из стрелок нажата. На этом обработка WM_KeyDown заканчивается.
- Обработчик WM_DoStep начинает с того, что проверяет текущее состояние нужной стрелки с помощью API-функции GetAsyncKeyState. Если окажется, что пользователь уже отпустил стрелку, ничего делать не надо, сразу выходим. (При реакции на первое нажатие клавиши эта проверка явно излишняя — вряд ли пользователь сумеет так быстро её отпустить. Но зато она хорошо спасает от повторного нажатия/отпускания во время движения человечка — он всё равно остановится, если клавиша в момент принятия решения не будет нажата.)
- Если проверка пройдена, осуществляем перемещение.
- В конце работы WM_DoStep вновь проверяет состояние нужной стрелки, и если она до сих пор не отпущена, помещает в очередь ещё одно сообщение WM_DoStep с теми же параметрами.
Эта реализация обеспечивает наиболее комфортное, на мой взгляд, поведение человечка. Во-первых, он движется плавно, без пауз. Во-вторых, после отпускания стрелки останавливается при первой же возможности. В-третьих, если игрок захочет поменять направление движения, он может наживать другую стрелку до того, как человечек остановится.
Если бы игра, как и предыдущий пример, не была анимированной, реализация получилась бы совсем короткой — в этом случае не пришлось бы манипулировать с движениями и возиться с клавиатурой, а сама логика здесь очень проста. А так получилось около 350-и строк чистого (т.е. без комментариев) кода в главном модуле. Правда, есть ещё один модуль, который содержит константы со всеми уровнями, но это можно считать не кодом, а данными, так что результат тоже неплохой.
Следующий наш пример — игра Bunch. В ней мы продемонстрируем, как с помощью библиотеки сделать несколько украшательств для игры. Главная форма игры показана на рисунке 4.
Рисунок 4 — Главная форма игры Bunch
Правила игры таковы. По доске размером 8х8 клеток случайным образом разбросаны шарики семи различных цветов. Пользователь может обменять местами любые два соседних из них при условии, что после обмена хотя бы один из этих двух шариков будет иметь соседа одного с ним цвета (соседними считаются шарики, расположенные рядом по вертикали или по горизонтали, но не по диагонали). Цель игры — рассортировать шарики так, чтобы все шарики одного цвета оказались собранными вместе. Другими словами, все шарики одного цвета должны занимать односвязную область на доске. Достаточно очевидно, что не при любых начальных конфигурациях можно сделать это. Но запрет, видимо, относится только к некоторым сильно упорядоченным начальным конфигурациям. На практике же я за всё время тестирования игры с такой ситуацией не столкнулся ни разу.
Прототипом для этой игры послужила игра http://www.miniclip.com/games/bunch/en/. Я немного изменил правила — в оригинале надо было за ограниченное время собрать вместе как можно больше шариков одного цвета, сортировать полностью не требовалось. Я поставил такую цель, но убрал ограничение времени.
Однако время, затраченное на поиски решения, учитывается при подсчёте очков. Формула подсчёта числа очков такова: 1800000/t+10000/s–100*b, где t — количество секунд, потраченное на поиск решения, s — количество ходов, потребовавшихся для этого, b — количество попыток сделать недопустимый ход.
В игре реализованы следующие украшательства:
- При попытке сделать недопустимый ход шарики сначала меняются местами, как и при допустимом, но потом перескакивают обратно
- Шарик, над которым сейчас находится мышь, подпрыгивает
- При постановке игры на паузу все шарики становятся белыми (чтобы пользователь не мог во время паузы обдумывать решение, а по доске начинает летать полупрозрачная табличка с надписью "Игра остановлена"
Первый из этих пунктов реализуется очень легко: условие допустимости хода проверяется только после перестановки шариков, и если оно не выполнено, производится обратная перестановка.
Для реализации второго пункта используются возможности анимации. Если посмотреть изображения в банке шаров, то видно, что для каждого шарика нарисован ряд фаз от самого нижнего положения, в котором он слегка приплюснут, до самого верхнего. Дальше всё просто: по событию OnCellMouseEnter включаем для заданной клетки анимацию, по событию OnCellMouseLeave выключаем. Чтобы анимация шла по фазам "туда и обратно", включаем свойство Waved у банка изображений. И фазу всем клеткам устанавливаем равной 4 — именно этой фазе соответствует изображение шарика в основном состоянии.
Сделать шарики белыми на время паузы можно разными способами, и все они довольно просты. Но надо, чтобы где-то сохранились настоящие цвета, чтобы после снятия игры с паузы их можно было восстановить. Я выбрал очень простой с точки зрения кодирования способ: сделал ещё один слой, на котором разместил белые шарики. В обычном состоянии этот слой невидим, но при паузе он показывается, а слой с обычными шариками, наоборот, скрывается. Для пользователя это выглядит так, как и задумано: при паузе шары становятся белыми, при снятии с паузы восстанавливают цвета.
Для отображения летающей во время паузы таблички с надписью используется заранее подготовленный растровый рисунок, который включен в ресурсы программы (файл BunchPics.rc). На основе этого рисунка и класса TTransparentSprite создаётся спрайт, изображающий эту табличку. Полупрозрачность устанавливается средствами этого класса, сама по себе табличка — обычный 24-битный рисунок без альфа-канала. А затем на основе класса TPingPongMovement создаётся движение, которое заставляет этот спрайт летать, отражаясь от стенок доски.
Для выполнения хода пользователь должен сделать два щелчка: сначала выделить первый из обменивающихся местами шаров, затем щёлкнуть на том из его соседей, с которым должен быть произведён обмен. Соответственно, после первого щелчка шарик должен быть как-то выделен для пользователя. Для этого на доске есть ещё один слой, на котором отображается рамка-курсор над выделенным шариком. Итого имеем три слоя: слой цветных шаров, слой белых шаров (виден только при паузе), слой курсора.
Игра содержит две формы: главную форму и небольшой диалог для отображения итогового счёта после выигрыша. Вся логика игры, разумеется, сосредоточена в модуле главной формы, который содержит 320 строк чистого кода. Таким образом, и для этой игры компонент существенно сократил затраты на кодирование.
Следующий пример, который мы рассмотрим — это реализация одной очень популярной игры. Эту игру в различных вариантах и под разными названиями можно встретить и в интернете, и в телефонах. Лично меня вдохновила та реализация, которую можно увидеть здесь; сделано красиво и приятно для игры, но, к сожалению, в ней есть глюки, а начиная с 8-го уровня непонятным образом меняются правила, из-за чего становится практически невозможно играть, а в инструкции об этом ни слова. В общем, я решил сделать такую же, но лучше.
Название я оставил почти без изменения — Bugs (насекомые). Так как борьба с багами входит в обязанности программиста, вы можете честно сказать начальнику, когда он застукает вас за этой игрой, что вы работали.
Рисунок 5 — Главное окно игры Bugs
Сама игра заключается в следующем. На доске размером 8x8 разбросаны насекомые шести различных видов. Вы можете менять местами любые два соседних (по вертикали или горизонтали) насекомых, при условии что в результате этого обмена образуется хотя бы один вертикальный или горизонтальный ряд из не менее чем трёх одинаковых насекомых подряд. Насекомые из таких рядов исчезают. Те, которые находились над ними, падают вниз, заполняя оставшиеся пустыми клетки, а сверху добавляются новые насекомые, чтобы пустых клеток не оставалось. Чтобы перейти на следующий уровень, нужно убрать заданное количество насекомых каждого вида (это количество растёт с ростом уровня). Время, которое отводится на прохождение уровня, ограничено.
Когда убираются одни насекомые и появляются другие, может получиться, что в новой конфигурации сразу есть готовые ряды из трёх насекомых. В этом случае эти насекомые также исчезают, и процесс повторяется до тех пор, пока в результате очередной итерации не получится конфигурация без готовых рядов.
Уничтожение ряда с насекомыми, помимо приближения к цели уровня, даёт дополнительное время и приносит очки. Добавленное время пропорционально количеству исчезнувших насекомых. Очки начисляются по более сложной схеме. Точные формулы я здесь приводить не буду, кому интересно, посмотрит их в коде. Но общие правила следующие. Чем больше уровень, тем больше даётся очков за одно и то же количество убранных насекомых. Чем больше насекомых убирается за один раз, тем больше очков даётся за каждое из этих насекомых. Если в результате убирания ряда возникает ещё одна итерация, то за на каждой следующей итерации за насекомых даётся всё больше и больше очков, причём это увеличение очень значительно. Основной способ набирать много очков — делать комбинации из многих итераций.
В левой части окна есть таблица со статистикой уровня. Таблица состоит из семи строк и двух столбцов. В левом верхнем углу таблицы отображается номер уровня, в правом верхнем — число насекомых каждого вида, которое необходимо уничтожить для прохождения уровня. В остальных строках в левом столбце нарисовано насекомое одного из видов, в правом — количество насекомых этого вида, которое было удалено в текущем уровне. Если это число меньше необходимого для прохождения уровня, оно отображается тёмно-красным цветом, если больше ли равно — зелёным.
Кроме насекомых, на доске есть ещё и бомбы. Они так же убираются при построении трёх в ряд, но их исчезновение не входит в задачу уровня. За уничтожение бомб так же, как и за насекомых, даётся дополнительное время, но зато очки уменьшаются. Количество штрафных очков зависит только от номера уровня и количества уничтоженных бомб и не зависит от номера итерации. На первой итерации уничтоженная бомба отбирает столько же очков, сколько добавляет уничтоженное насекомое.
Начиная с 7-го уровня на доске появляются динамитные шашки. Они полностью эквивалентны бомбам, но приносят в четыре раза больше штрафных очков.
Иногда на доске можно увидеть, что в клетке на жёлтом фоне быстро меняется изображение разных насекомых. Это джокер. При нажатии на него случайным образом выбирается один из шести видов насекомых, и все насекомые данного вида исчезают. Насекомые, убранные джокером, приносят минимум очков и не дают дополнительного времени, но учитываются при подсчёте количества убранных насекомых. Если в результате уничтожения насекомых джокером возникает несколько итераций, то при последующих итерациях очки и дополнительное время за уничтоженных насекомых даются обычным образом.
Если во время игры возникает патовая ситуация, при которой невозможно сделать ни одного шага по правилам, содержимое доски полностью обновляется. За это не снимаются очки и не уменьшается время, так что избегать таких ситуаций необходимости нет.
При смене уровня содержимое доски также полностью обновляется. Игра считается проигранной, если время, отведённое на уровень, истекло, а нужное количество насекомых не уничтожено. Так как с ростом уровня это количество увеличивается, а время уменьшается, рано или поздно игрок не успеет. Оставшееся время показывается индикатором справа от доски: когда синяя полоса уменьшается до нуля, время истекает. Оставшееся время не может превышать некоторой величины, которая соответствует максимальной заполненности полосы синим. Если полоса дошла до максимума, время за уничтожение фигур добавляться не будет.
В этой игре много различных анимационных эффектов, которые реализуются движениями. Из-за этого объём чистого кода в двух её модулях получился около 1000 строк.
Здесь мы рассмотрим основные идеи реализации этих эффектов. Для некоторых из них пришлось реализовывать свои классы движений. Я старался сделать эти классы немного более универсальными, чем это необходимо для программы, чтобы вы при желании могли использовать их у себя в программах.
Сначала рассмотрим самые простые эффекты. Во-первых, курсор частично полупрозрачный — это достигается использованием альфа-канала в банке изображений. Во-вторых, при выборе насекомого оно не только выделяется курсором, но и начинает шевелиться. Это сделано так же, как и прыжки шарика при наведении мыши в примере Bunch: каждому состоянию (т.е. виду насекомого) сопоставлены две фазы. Анимация для клетки при необходимости включается или выключается. А для бомб и динамита анимация включена всегда — если присмотреться, можно заметить, что их фитили горят.
Кстати, обратите внимание, что, начиная с того момента, когда для прохождения уровня не хватает насекомых только одного вида, все насекомые этого вида на доске начинают отображаться с выпученными глазами. Это делается тоже с использованием второй фазы состояния, в которой глаза выпучены. При необходимости свойству Phase соответствующей фигуры слоя просто присваивается 1, и в неанимированном состоянии для отображения этой фигуры используется вторая фаза, а не первая.
Обмен местами двух выбранных фигур реализуется так же, как и в примере Bunch: просто создаются два экземпляра класса TCellMovement, каждый из которых делает всё, что нужно: очищает исходную клетку от фигуры, рисует перемещение, помещает фигуру в целевую клетку.
Мигающая надпись с приглашением начать игру реализована с помощью наследника от TBitmapSprite. Этот наследник отличается от оригинального класса тем, что часть времени выводит рисунок в указанное место, а часть — не выводит, отсюда получается эффект мигания. Для того, чтобы задать координаты надписи, используется поставщик координат TPathCoords с вырожденной траекторией. Спрайт и поставщик координат объединяются в движение классом TSpriteMovement.
Если наступает такой момент, что нет ни одного допустимого хода, доска сначала очищается, а потом на ней появляется новая комбинация фигур. Очистка визуально выглядит так: все фигуры дружно начинают двигаться вниз и двигаются до тех пор, пока все не окажутся за нижним краем доски. Этот эффект создаётся с помощью всё того же класса TCellMovement: для каждой фигуры создаётся по одному экземпляру этого класса, который обеспечивает перемещение фигуры в клетку с такой же горизонтальной координатой, а вот вертикальная координата клетки назначения на единицу больше, чем у самой нижней строки доски, что и создаёт эффект ухода фигуры за нижний край. Теперь осталось просто дождаться окончания всех движений.
Новая комбинация наоборот, выплывает из-за верхнего края доски. Делается это практически точно так же, но в обратной последовательности: фигуры начинают путь из-за края доски и заканчивают его в одной из её клеток. Но если бы мы заставили фигуры начинать движение сразу из-за края доски, то для обеспечения синхронности пришлось бы создавать объекты TCellMovement не одновременно: сначала создать движения для всех фигур, которые должны оказаться в нижнем ряду, потом, когда эти фигуры достигнут нижнего ряда, создать движения для фигур второго снизу ряда и т.п. Чтобы исключить эту возню, мы создаём все движения одновременно, а координата начала движения зависит от того, где фигура должна оказаться. Так, те фигуры, которые должны оказаться в самом нижнем ряду, начинают движение из ряда с номером –1, те, которые должны оказаться во втором снизу ряду — из ряда –2 и т.д. Получается, что движения, особенно для фигур верхних рядов, большую часть времени своего существования пытаются нарисовать что-то за пределами доски без видимого результата для пользователя. Зато код становится очень простым, а небольшое увеличение нагрузки на процессор в данном случае оправдано.
Смена уровня выглядит так: сначала все фигуры уходят вниз, затем новая комбинация приходит сверху. Но вместе с фигурами двигается ещё и надпись с номером уровня. Эта надпись начинает спускаться из-за верхнего края доски вместе с уходящими вниз фигурами, но потом она останавливается на середине доски и продолжает там висеть, пока спускающиеся сверху новые фигуры не начнут её выталкивать вниз за доску. Когда фигуры занимают свои позиции, надпись как раз исчезает за нижнем краем доски.
Хитрость этого эффекта заключается в том, что двигающаяся надпись реализуется не одним, а двумя последовательно существующими объектами движения. Первый из них существует в то время, пока старые фигуры уползают за нижний край доски, второй — в то время, когда новые фигуры выползают из-за верхнего края. Сначала рассмотрим уход фигур с доски. Представим себе, что вдобавок к объектам TCellMovement, двигающим фигуры, мы создали ещё один объект движения, который двигает вертикально вниз спрайт с надписью "Уровень N". Если начальная позиция спрайта будет сразу за верхним краем доски, а скорость движения — такая же, как у двигающихся вниз фигур, то к тому моменту, когда верхний ряд уйдёт за нижний край доски, спрайт своей нижней стороной как раз коснётся этого края. Сделать такое движение совсем несложно: надо нарисовать растр с номером уровня, сделать на его основе спрайт с помощью класса TBitmapSprite, в качестве поставщика координат выбрать TLineCoords и объединить их с помощью TSpriteMovement.
Теперь следующая хитрость: нам ведь не нужно, чтобы надпись двигалась до нижнего края доски. Наоборот, она должна замереть в середине. Чтобы добиться этого эффекта, вспомним, как устроен класс TLineCoords. На основе заданной начальной и конечной точек и указанного числа точек в траектории он в конструкторе создаёт массив, содержащий нужное число точек, которые равномерно распределены между начальной и конечной точками. Потом последовательно возвращает объекту TSpriteMovement эти точки, пока не дойдёт до конца массива. После этого существование движения прекращается. Таким образом, время этого существования определяется количеством точек в массиве. Теперь давайте представим, что сразу после создания массива мы прошли по нему, и у всех точек, Y-координата которой больше некоторого значения Y0, заменили эту координату до Y0. Поставщик координат с таким массивом будет реализовывать как раз нужное нам движение: спрайт будет спускаться, пока не дойдёт до координаты Y0, а потом остановится. А так как количество точек в траектории не изменилось, то и время жизни движения осталось прежним: ровно до того момента, как фигуры верхней строки скроются за нижнем краем доски. Осталось только подобрать такой Y0, чтобы это соответствовало положению спрайта в середине доски.
Те, кто разобрался с написанным выше, наверное, уже догадались, как реализован второй объект — тот, который существует во время спуска новых фигур. Сначала представим себе такое движение: сначала спрайт своим верхним краем касается верхнего края доски, потом начинает двигаться вниз с той же скоростью, что и спускающиеся сверху фигуры. Когда фигуры занимают свои места, спрайт оказывается точно за нижнем краем доски. А теперь модифицируем поставщика координат так, что все координаты Y<Y0 он заменит на Y0. Тогда спрайт сначала будет висеть на заданной высоте, а как только фигуры дойдут до него, начнёт спускаться вместе с ними. Надо только выбрать Y0 таким же, как и в предыдущем случае, чтобы надпись не прыгала с места на место.
Осталось заметить, что для реализации всего этого создан новый поставщик координат — TLevelCoords. Это наследник TLineCoords, который имеет два конструктора. Оба этих конструктора сначала вызывают конструктор, унаследованный от TLineCoords, а потом в сформированном им массиве один из них заменяет координаты по правилу Y<Y0, второй — по правилу Y>Y0.
Следующий эффект, который мы рассмотрим, реализуется гораздо проще. Это исчезновение выстроенных в ряд жуков. Здесь мы не можем обойтись включением анимации в ячейке, потому что, во-первых, фазы состояний уже заняты под другие изображения, а во-вторых, начальную фазу и время окончания анимации трудно контролировать — возможности класса TGameCell ориентированы на непрерывную, а не на разовую анимацию. Поэтому мы просто убираем фигуры из клеток, а на их месте размещаем движения, реализуемые классом TCellAnimationMovement. В качестве банка изображений назначаем этим движениям BankDisappearance — банк, не связанный ни с одним слоем и хранящий изображения уменьшающихся жуков (изображения из-за моих не слишком выдающихся художественных способностей весьма некачественные, но из-за высокой скорости анимации в игре это не очень заметно) и взрывающихся бомб и шашек. Тип анимации устанавливаем atDirect — это значит, что будут показаны все фазы с первой по последнюю, после чего движение завершится.
После того как фигуры исчезли, надо "уронить" то, что осталось сверху, дополнив доску новыми фигурами. Это, разумеется, реализуется с помощью всё того же TCellMovement, который умеет и переставлять фигуры на доске, и выдвигать из из-за края доски. Надо только аккуратно разобраться, какие существующие фигуры куда должны переместиться и какие фигуры придут из-за верхнего края, а также откуда приходящие фигуры должны начинать свой путь, чтобы движение было синхронным.
Хаотичный разлёт фигур в разные стороны при проигрыше делается очень похоже на уход фигур за нижний край доски. Каждая фигура убирается с доски объектом TCellMovement, конечная точка траектории которого лежит за пределами доски. Только теперь эта точка выбирается для каждой фигуры случайным образом и может оказаться не только за нижним, но и за любым другим карем доски, что и создаёт хаотичное движение.
И последний эффект — хоровод жуков вокруг надписи о том, что игра поставлена на паузу. Cама надпись делается очень просто — движением TSpriteMovement, состоящим из TPathCoords с единственной точкой в траектории и TBitmapSprite с рисунком, содержащим нужный текст. Каждый из жуков, который крутится вокруг надписи, также реализован движением TSpriteMovement. Поставщиком спрайтов в данном случае выступает TAnimationSprite, который берёт свои изображения из того же банка, что и слой жуков. А поставщиком координат — всё тот же TPathCoords, который на этот раз содержит координаты замкнутой траектории. Все жуки двигаются по одной траектории, только у каждого сдвинуто её начало (класс TPathCoords не поддерживает сдвиг начала, поэтому для каждого объекта пришлось вручную передвигать начало).
Хоровод реализуется классом TFramedNotification. Это не движение, TFramedNotification — наследник TObject, просто внутрь этого класса спрятана вся работа по созданию и удалению движений для хоровода. Основной программе достаточно создать экземпляр TFramedNotification, чтобы уведомление появилось, и удалить его, чтобы оно исчезло.
Ещё несколько слов стоит сказать о таблице со статистикой. Она реализуется ещё одним экземпляром TCellGameBoard (использование, например, TDrawGrid было бы в данном случае в чём-то удобнее, но ведь это пример, показывающий возможности именно TCellGameBoard). Это неанимированная доска с двумя слоями. Нижний слой используется для отображения насекомых, он связан с тем же банком изображений, что и слой насекомых основного банка доски. На втором слое отображаются чисел. С одной стороны, количество различных вариантов того, что должно появиться в соответствующих клетках, очень велико, потенциально — бесконечно. С другой стороны, изображения чисел очень легко генерируются "на лету". Поэтому хочется не хранить множество готовых изображений, а использовать возможности GDI для вывода нужного числа. Чтобы это стало возможным, в программе используется собственный наследник класса TCellBank, который предназначен для вывода чисел. Метод DrawCell переопределён в нём таким образом, что использует State как число, которое нужно вывести, а Phase — как селектор цвета и размера шрифта: 0 — маленький тёмно-красный шрифт, 1 — маленький зелёный шрифт, 2 — маленький чёрный шрифт (используется для вывода требуемого количества насекомых в правом верхнем углу), 3 — крупный пурпурный шрифт (для вывода номера уровня). Таким образом, меняя значения State и Phase фигур этого слоя, мы можем добиться вывода нужного числа нужным шрифтом.
Отсчёт времени реализован с помощью таймера доски, т.е. ведётся в обработчике события OnBoardTimer. Полоса реализована с помощью TPaintBox. Сначала я сделал её с помощью TProgressBar, но оказалось, что это нормально выглядит только в Windows Vista, а в Windows XP эта полоса заметно моргает. Чтобы не тратить ресурсы на режим DoubleBuffered, я сделал полосу вручную.
До сих пор мы рассматривали только игры с доской фиксированного размера. В этом примере доска будет растягиваться, подгоняя свои размеры под текущие размеры окна.
Подгонке мешают две проблемы. Во-первых, свойства типа Align или Anchors работать с компонентом Класс TCellGameBoard">TCellGameBoard не будут, так как его размер полностью определяется размером и количеством клеток. Во-вторых, имеющиеся банки изображений могут содержать рисунки только фиксированного размера.
С первой проблемой справиться достаточно просто. Надо в обработчике события OnResize формы вручную вычислять, какую максимальную ширину может иметь доска и какой должна быть ширина ячейки, чтобы максимально приблизиться к этому значению, но не превысить его. Потом аналогичным образом вычислить высоту ячейки исходя из максимально возможной высоты доски. Так как клетка в нашем случае квадратная, из этих двух чисел надо взять минимальное и установить соответствующие размеры клетки, и доска изменит свой размер. В качестве последнего штриха поправим координаты левого верхнего угла доски так, чтобы она оказалась в центре отведённой ей части окна. Со всем этим надо немного повозиться, но ничего принципиально сложного нет.
Проблему размеров фигур можно решить двумя способами. Первый — это создание наследника от TCellBank, метод DrawCell которого будет рисовать фигуру с учётом размера ячейки. Второй способ — воспользоваться классом TBitmapCellBank и каждый раз при изменении размеров клетки доски генерировать изображения фигур заново в соответствии с новыми размерами. Я попробовал оба способа и остановился на втором. Мне он понравился тем, нагрузка на процессор во время игры не зависит от сложности изображения фигуры (дополнительная нагрузка возникает только при смене размеров доски), в то время как первый способ сильно нагружает процессор, если сделать изображение более-менее сложным. И хотя в данной игре изображения достаточно просты, я всё-таки предпочёл использовать TBitmapCellBank на тот случай, если мне всё-таки захочется усложнить изображение фигур.
Теперь перейдём к правилам игры. Доска (которую в данном случае логичнее называть стаканом) размером 16 клеток в ширину и 20 в высоту в начале игры заполнена снизу на 10 строчек разноцветными квадратными блоками. Количество цветов может быть от трёх до шести — это выбирает пользователь перед началом игры. Чем больше цветов, тем сложнее играть; лично мне кажется, что даже пять — это уже перебор. Группой называется связная (по граням; углы не в счёт) область из одноцветных блоков. Пользователь может щёлкнуть мышью на любой группе, состоящей из трёх или более блоков, и она исчезнет. При этом все блоки, которые находились выше исчезнувшей группы, упадут вниз. Если после этого в центре стакана останутся пустые столбцы, крайние столбцы с блоками сдвигаются в сторону центра, чтобы пустые столбцы оставались только по краям стакана.
С определённой периодичностью все блоки в стакане сдвигаются вверх на одну строку, а освободившаяся нижняя строка заполняется блоками случайных цветов. Если в момент очередного сдвига окажется, что хотя бы в одном столбце есть блок в верхней строке, и сдвигать этот столбец уже некуда, игра считается проигранной. Задача игрока — продержаться как можно дольше.
Каждые добавленные 15 строк означают переход на следующий уровень. Более высокий уровень означает увеличение скорости добавления новых строк. Максимально возможный уровень — 20-ый, после него скорость поднимать уже некуда, так что теоретически на нём можно оставаться неограниченно долго. Но на практике долго удержаться не удаётся — исправлять допущенные ошибки на такой скорости почти невозможно.
В игре существует предварительный просмотр добавляемой строки. Под стаканом (см. рисунок 6) расположена полоска, которая заполняется блоками слева направо. Как только полоска будет заполнена до конца, именно эта комбинация блоков добавляется в качестве новой строки, а полоска очищается, и её заполнение начинается заново.
Рисунок 6 — Главное окно игры Crash Down
Эта игра тоже имеет свой прототип. Правила оригинала мне не очень понравились, и я изменил их. В оригинале при смене уровня создавалась новая комбинация блоков, и в каждом третьем уровне увеличивалось число цветов, а скорость добавления новых линий оставалась неизменной.
Лирическое отступление: вообще, идея компонента TCellGameBoard выросла из желания изменить правила некоторых игр, в которые я играю, когда нужно сделать перерыв в работе. Пока не все из этих желаний реализованы — практика показала, что пока я напишу и протестирую игру, желание играть как в свою, так и в оригинальную версию просто пропадает из-за пресыщения.
Анимационных эффектов в этой игре меньше, чем в предыдущей. Уничтоженные блоки исчезают мгновенно, анимация — только при перестановке квадратов с места на место. Некоторое оживление вносит только надпись "GAME OVER", по которой пробегают волны. Эта надпись тоже масштабируется, поэтому обычные спрайты для букв непригодны, пришлось создавать свой класс TGameOverMovement (см. модуль GameOver). Изображение букв взято из шрифта Playbill, но его установка на компьютер не требуется, программа содержит нужные данные внутри себя. Для каждой из нужных букв хранятся два массива: координат точек и типов точек, а также ширина буквы. Значения в этих массивах получены в отдельной программе с помощью GetPath для букв большого размера и вставлены в игру как константы. Когда нужно вывести надпись, рассчитывается масштабный коэффициент для координат исходя из оригинальной и требуемой ширин букв (надпись занимает 2/3 ширины стакана), координаты умножаются на этот коэффициент, и на их основе рисуется растр с изображением буквы требуемого размера. Из растров с отдельными буквами составляется надпись, причём буквы время от времени двигаются вверх-вниз, создавая эффект пробегающей волны.
Так как пользователь может изменить размеры окна и в то время, когда надпись "GAME OVER" видна на экране, соответствующее движение должно уметь изменять размеры букв. Отреагировать на изменение размеров доски самостоятельно движение не может, поэтому в классе просто предусмотрен специальный метод Resize, который надо не забыть вызвать в основной программе сразу после изменения размеров доски.
Главный вывод, который можно сделать из этого примера — это то, что компонент TCellGameBoard не очень хорошо подходит для игр, размеры изображений в которых могут меняться. Ценой некоторых усилий удаётся сделать игру с небольшим количеством эффектов, но чем более красивой мы хотим сделать игру, тем больше придётся делать рутинной работы. Избежать этой рутины можно было бы, перейдя на векторную графику. Для этого нужен соответствующий класс банка изображений, а также набор классов для движений, которые используют относительные координаты, привязанные к текущим размерам доски. Лично мне для создания таких классов не хватает опыта работы с векторной графикой — я не знаю, как сделать, чтобы это всё работало достаточно быстро. Но если человек, обладающий таким опытом, захочет поучаствовать в развитии библиотеки в этом направлении, пусть пишет мне, договоримся.
Последний пример, который мы здесь рассмотрим — игра Drops (капли). Её прототипом стала flash-игра Splash Back. К сожалению, автор Splash Back не оставил никаких данных о себе, поэтому ссылку на оригинальный сайт я дать не могу. Кому интересно, может познакомиться с этой игрой, например, здесь.
Фотография, использованная в качестве фона — это, если кому интересно, вид на Анталию с заходящего на посадку самолёта.
Правила игры очень просты. На доске размером 6x6 разбросаны капли размером от одного до четырёх. Некоторые клетки оставлены пустыми (см. рисунок 7). У игрока в запасе изначально десять капелек, которые он может добавлять в любую клетку. Добавление капельки в пустую клетку приводит к появлению там капли первого размера, добавление в непустую — к увеличению находящейся там капли на один размер. Если капелька добавляется в клетку с каплей четвёртого, максимального, размера, эта капля лопается, разбрасывая в четыре стороны брызги. Если на пути брызг не оказалось капель, брызги просто врезаются в стены и исчезают без всяких последствий. Если же они попадают в каплю, то такое попадание приводит к тому же эффекту, что и добавление капельки в эту клетку пользователем, т.е. капли размером от 1 до 3 увеличиваются на один размер, капли четвёртого размера — лопаются, порождая новые брызги. Таким образом, удачно добавив одну капельку, можно вызвать целый каскад разрушений капель на доске. Сталкивающиеся брызги предполагаются невзаимодействующими, т.е. они свободно пролетают друг сквозь друга.
Задача игрока — полностью очистить доску от капель. После этого он переходит на новый уровень, при этом к запасам игрока добавляется одна призовая капелька. Запасы также пополняют каскадные удаления капель на доске: за каждые три лопнувшие в рамках одного каскада капли игроку даётся дополнительная капелька.
Каждый следующий уровень отличается от предыдущего тем, что вероятность появления на доске больших капель падает, а маленьких — растёт. Таким образом, первые несколько уровней игрок будет, вероятнее всего, наращивать свой запас капелек, и потом "играть в минусе" — тратить то, что заработал. Игра заканчивается, когда у игрока не остаётся ни одной капельки в запасе. Цель игры — очистить как можно больше уровней, пока капельки не кончились.
Прежде чем мы перейдём к описанию внутреннего устройства игры, хочу обратить ваше внимание на несколько особенностей её реализации:
- Если клетку с каплей "задеть" мышкой, капля начинает колебаться, меняя свои размеры. Постепенно колебания сходят на нет, причём это не зависит от того, остаётся ли курсор мыши в клетке или уходит из неё.
- Добавить капельку можно как в колеблющуюся, так и в неколеблющуюся каплю.
- Рост капли от одного размера к другому анимирован и состоит из нескольких изображений промежуточных между двумя размерами состояний. Причём корректно обрабатывается, например, ситуация, когда в каплю, находящуюся в промежуточном состоянии, попадает брызга — в этом случае капля продолжает рост, увеличиваясь на несколько размеров сразу.
Если кто-то из читателей успел близко познакомиться с основными возможностями библиотеки, ему должно быть сразу очевидно, почему я остановился именно на этих аспектах. Дело в том, что возможностей класса TGameCell не хватает для реализации такого поведения капли, этот класс предназначен только для циклической анимации, которой здесь нет. Поэтому, если бы мы пошли по обычному пути, то на время анимации нам пришлось бы скрывать фигуры и показывать вместо них движения. Но тут возникает другая проблема: то, что капля в промежуточном, а также в колеблющемся состояниях должна реагировать на брызги так же, как и в статическом, усложнило бы, например, проверку попадания брызги в каплю. Пришлось бы проверять как состояние самой клетки, так и наличие соответствующих движений и, в зависимости от того, что и в каком состоянии обнаружилось, порождать новые движения или модифицировать существующие.
Чтобы избежать этих проблем, я построил игру по-другому — полностью отказался от использования фигур и реализовал всё одними только движениями. В игре единственный слой, который используется только для отображения этих движений. Капля реализуется классом TStaticDrop (наследник TMovement). Изначально создаются 6x6 экземпляров этого класса (для пустых клеток тоже: отсутствие капли или, точнее, капля нулевого размера — это одно из возможных состояний объекта типа TStaticDrop). После этого у нас оказываются развязаны руки, и мы можем реализовывать любое сложное поведение капли, а также использовать все достоинства ООП, например, инкапсуляцию. В частности, объект TStaticDrop обладает методом Impaction, с помощью которого брызги уведомляют его, когда находится внутри его клетки, а он сам решает, реагировать ли на эти уведомления или нет. Например, капля нулевого размера не реагирует. И, разумеется, метод Impaction внутри себя учитывает только текущий размер капли, но не учитывает такие дополнительные признаки, как колебания или нахождение в промежуточном состоянии. Всё это делает код очень простым и понятным.
Для отображения капли используется пять банков изображений — по одному на каждый размер от нулевого до четвёртого. Все эти банки устроены по одному принципу. Изображения с нулевым состоянием и нулевой фазой — это изображения капли в соответствующем данному размеру основном состоянии. Остальные фазы нулевого состояния — изображения разных фаз колебания капли (банк изображений нулевого размера не имеет фаз колебания, так как "отсутствие капли" не может колебаться). Остальные состояния имеют по одной фазе и содержат изображения различных этапов перехода капли к следующему состоянию (именно поэтому потребовался банк изображений и для нулевого размера — чтобы хранить этапы появления капли первого размера "из ничего").
Рост капли при добавлении в неё капельки или брызги реализован очень просто. Есть поле FSize, которое содержит требуемый размер капли. И есть поля, определяющие номер банка и текущую фазу. Если банк и фаза не соответствуют требуемому размеру, значит, при каждой перерисовке капли мы увеличиваем её фазу, а при необходимости — ещё и банк, пока изображение не будет соответствовать размеру капли. Капля четвёртого размера после отображения всех этапов перехода возвращается к нулевому банку, обеспечивая своё исчезновение.
Достаточно очевидно, что использование пяти разных банков изображений — это усложнение программы. Можно было бы хранить изображения всех переходных и колебательных состояний в одном банке, и тогда код был бы проще. Разбиение на пять банков сделано из соображений производительности. Все капли — полупрозрачные, они выводятся с помощью не слишком быстрой функции AlphaBlend, которая работает тем медленнее, чем больше выводимое изображение. А так как изображение капли, например, первого размера намного меньше, чем четвёртого, возникает желание для маленьких капель отрезать пустую часть рисунка, чтобы не тратить время на её рисование. Использование различных банков для капель разных размеров — следствие этого желания: размер изображений в каждом банке не больше необходимого, что заметно повышает производительность игры.
По правилам игры пользователь не может добавить новую каплю в клетку до тех пор, пока не закончатся все изменения, вызванные предыдущей каплей. Следовательно, программа как-то должна уметь дожидаться окончания реакции на каплю. Легко видеть, что метод TCellGameBoard.WaitMovements, который мы ранее использовали для подобных целей, здесь не подходит, и тому есть две причины. Во-первых, рост капель реализуется не конечным движением, поэтому дождаться его окончания таким образом нельзя. Во-вторых, на момент начала каскада неизвестно, окончания скольких движений придётся ждать, так как в число таких движений попадают и брызги, образующиеся в результате вторичного, третичного и т.п. разбивания капель. Класс TMovementsManager здесь тоже не подходит, так как он тоже работает только с конечными движениями. Следовательно, нужен специальный механизм для ожидания.
Этот механизм реализуется классом TDropWaiter, который унаследован от TMovementFinishNotifier. В программе создан единственный объект этого класса, и все движения брызг уведомляют его о своём завершении. Кроме того, движения, реализующие капли в клетках, тоже уведомляют его о том, когда они начинают и заканчивают рост. Таким образом, данный экземпляр всегда знает количество выполняемых движений и позволяет организовать ожидание до тех пор, пока это количество не уменьшится до нуля.
Полупрозрачная доска сделана очень просто. Обработчик её события OnDrawBackground копирует часть фона, которая находится под доской, смешивая его с серым фоном, которым уже закрашена доска. В результате фон доски начинает выглядеть так, как будто рисунок на форме проступает сквозь неё.
Последний аспект реализации, который стоит упомянуть здесь, — это распределение вероятностей появления капель при генерации нового уровня. Выше я уже говорил, что с ростом номера уровня максимум распределения смещается от больших капель к меньшим. Конкретный вид распределения задаётся кусочно-линейной функцией и показан на рисунке 8.
Рисунок 8 — Распределение вероятностей выпадения капель разных размеров
Единственный переменный параметр здесь — это величина D, которая задаёт абсциссу максимума. Этот параметр уменьшается с ростом уровня, но всегда находится в диапазоне [0; 4]. Наклон прямых одинаков для всех значений D и составляет 450 для левой прямой и –450 для правой. Таким образом, формула функции распределения имеет следующий вид:
y = x+4-D, x <= D
y =-x+4+D, x > D
Вероятность появления капли первого размера пропорциональна площади под кривой на отрезке [0; 1], второго размера — на отрезке [1; 2] и т.д. Полная площадь под кривой на отрезке [0; 4] зависит от D и не равна единице, поэтому вероятность появление капли заданного размера рассчитывается как отношение площади на соответствующем отрезке к площади на всём диапазоне — я решил, что так будет проще, чем вводить зависимость углов наклона прямых от D с тем, чтобы нормировать распределение на единицу. Формулы для расчёта площадей здесь не привожу, надеюсь, что площадь под графиком кусочно-линейной функции все и без меня считать умеют.
Вероятность отсутствия капли в клетке этим распределением не задаётся. Она всегда равна 20% не зависит от номера уровня. В оставшихся 80% случаев используется описанное выше распределение.
Пример Drops очень хорош тем, что показывает как достоинства, так и недостатки получившейся библиотеки. Начнём с недостатков:
- Возможность размещения в клетках слоя только экземпляров TGameCell существенно ограничивает возможности библиотеки. Возможно, стоит подумать о том, чтобы в качестве фигур можно было использовать другие классы, разработанные пользователем для конкретной игры. В противном случае приходится самостоятельно имитировать "клетчатость" игры.
- То, что класс TBitmapCellBank требует одинакового размера всех картинок, заставляет изобретать либо дополнять картинки ненужными полями, либо разносить по разным банкам то, что должно хранится в одном.
- Скорость работы далека от идеала. Не знаю, можно ли её существенно повысить, оставаясь в рамках GDI, но игра Drops — фактически, предел для слабых компьютеров, хотя в ней нет ничего такого особенного по нынешним временам.
В общем, мне есть над чем подумать, если я решу делать вторую версию библиотеки.
Теперь о достоинствах:
- Библиотеке удаётся существенно упростить разработку игр. Чистого кода в игре Drops — около 650 строк, и на его написания у меня ушло в несколько раз меньше времени, чем на подготовку картинок.
- Библиотека получилась настолько универсальной, что её можно использовать и для простых "неклетчатых" игр. Действительно, в данном примере движения TStaticDrop специально привязывались к клеткам доски. Но никто не мешает не делать такую привязку и реализовывать совсем другие типы движений. Например, какой-нибудь космический кораблик, свободно летающий по доске и расстреливающий хаотически двигающиеся астероиды. Всю низкоуровневую работу по отсчёту игрового времени и отображению без мерцания библиотека возьмёт на себя. Сравнительно легко сделать и надстройку над библиотекой для отслеживания столкновений, физики движений и т.п.
В общем, если у вас есть какие-нибудь задумки по созданию игр, особенно клетчатых, то TCellGameBoard, возможно, станет подходящей платформой для их реализации. Я надеюсь, что приведённые здесь примеры не останутся в одиночестве и будут пополнены играми других авторов.
Так как получение Delphi 2009 у меня пока откладывается, а задерживать эту статью ещё больше не хотелось бы, пока я выкладываю код только для Delphi 7 и 2007. Как только я получу Delphi 2009, выложу код и для этой версии.
К статье приложены два архива. Первый содержит откомпилированные примеры для тех, кто хочет побыстрее познакомиться с возможностями библиотеки. Эти примеры вы можете свободно распространять все вместе или по отдельности, если будет такое желание.
Второй архив содержит исходные коды библиотеки и примеров к ней. В архиве хранятся следующие папки:
- Sources — папка с исходными кодами библиотеки
- Delphi7 — папка с установочными пакетами для Delphi 7
- Delphi2007 — папка с установочными пакетами для Delphi 2007
- Delphi2009 — папка с установочными пакетами для Delphi 2009 (пока пустая)
- Samples — папка с примерами игр
- Pictures — папка с рисунками для примеров игр. Так как рисунки помещаются библиотекой в DFM-файл, их исходные файлы для компиляции не нужны. Эти рисунки помещены в архив для тех людей, кто захочет модифицировать рисунки в примерах. Учтите, что рисунки с альфа-каналом не нормализованы.
- Для установки библиотеки создайте новую папку и пропишите путь к ней в настройках среды (в Delphi 7 это меню Tools\Environment, закладка Library, поле Library Path, в Delphi 2007 — меню Tools\Options, закладка Environment Options\Delphi Options\Library - Win32, поле Library Path). Или можете использовать уже существующую такую папку.
- Скопируйте в эту папку всё содержимое папки Sources из архива.
- Скопируйте в эту папку всё содержимое папки Delphi7, Delphi2007 или Delphi2009 в зависимости от вашей версии Delphi.
- Откройте в Delphi файл GameBoard*.dpk из этой папки (вместо * должна стоять версия Delphi). В Delphi 7 в появившемся окне нажмите кнопку Compile. В Delphi 2007 в дереве проектов выберите этот проект и в его контекстном меню выберите пункт Compile.
- Откройте в Delphi файл DGameBoard*.dpk из этой папки (вместо * должна стоять версия Delphi). В Delphi 7 в появившемся окне нажмите кнопку Install. В Delphi 2007 в дереве проектов выберите этот проект и в его контекстном меню выберите пункт Install. В результате этого должно быть выведено сообщение о том, что зарегистрированы компоненты TCellGameBoard, TBitmapCellBank и TMovementsManager. Эти компоненты появятся на вкладке GameBoard.
К статье прилагаются архивы:
[Мерцание при перерисовке] [Создание собственных компонент] [Скорость отображения графики] [Программирование игр.] [Анимация] [Растры, BMP]
Обсуждение материала [ 24-10-2013 10:58 ] 53 сообщения |