ВВЕДЕНИЕ
Технология разработки компьютерных игр за последние годы достигла небывалых высот. Большая часть игр сейчас выпускается на основе трёхмерных движков, однако, несмотря на непревзойдённую зрелищность таких продуктов, достаточно много игр создаются двухмерными. И этому можно найти несколько объяснений. Во-первых, создание хорошей игры с пространственной графикой требует очень больших усилий от программистов и прочего персонала. Одному выполнить такой проект за разумный срок практически невозможно. Во-вторых, сравнивать трёхмерные и двухмерные игры – то же самое, что сравнивать, например, кино и литературу. У каждого из типов есть свои преимущества и недостатки. Так, играбельность в двухмерных играх, как правило, выше, чем в трёхмерных. Кроме того, даже сегодня всё ещё есть достаточно большой парк компьютеров, на которых требовательные к системным ресурсам трёхмерные игры просто не работают.
Вышеприведенное доказывает уместность организации проектов создания двухмерных игр. Как известно, никакая двухмерная игра не обойдётся без редактора уровней, представляющего собой утилиту, которая помогает создать игровое поле и нанести на него все необходимые объекты.
В данной курсовой работе ведётся разработка подобного программного продукта. При этом выдвигаются следующие требования: программа должна быть универсальной и не привязываться к конкретной игре, а также функционировать под разными программными платформами. Для реализации поставленных целей в качестве языка разработки был выбран C#, работающий по технологии .NET (что обеспечивает переносимость с одной платформы на другую) и соответствующая среда программирования – Microsoft Visual Studio 2003.
1 АНАЛИЗ ПРЕДМЕТНОЙ ОБЛАСТИ
Редактор уровней к компьютерным играм (иногда ещё он носит название редактора карт) предназначен для визуального, а значит достаточно быстрого, но в то же время качественного проектирования игрового поля (уровня, карты). Данные о содержимом уровня сохраняются в файле (или файлах), который затем загружается игровым движком. Сам движок не должен включать в себя готовые уровни. Он отвечает только за все взаимодействия, происходящие на игровом поле в процессе игрового цикла.
Как правило, в двухмерных играх применяется многослойная клеточная модель игрового поля. Это означает, что базовыми элементами, из которых строится изображение уровня, являются клетки. Клетка – это всего лишь картинка заданного размера. Так что основная цель использования подобных клеток при создании игр – это сделать процесс более абстрактным. То есть вместо того, чтобы работать с картой, заданной изображением некоторого фиксированного размера, вы можете работать с массивом из номеров клеток, из которого можно выложить изображение карты любого размера и просто вывести на экран только часть ее. Помимо этого можно задать атрибуты каждой клетки и накладывать клетки друг на друга, что позволит создавать динамические карты.
Многослойность означает, что лучше использовать не один массив из клеток, а несколько, наложенных друг на друга. Это позволяет реализовать эффект параллакса, широко используемый в двухмерных аркадах.
Кроме карт, составленных из клеток, на игровом поле могут находиться также целостные объекты (обычно игровые персонажи; далее будем назвать актёрами). В игре они представляются динамическими объектами, а в файлах уровней – структурами, содержащими важнейшие атрибуты объектов (обычно координаты).
Таким образом, редактор уровней должен уметь работать со слоями клеток (загружать для них картинки-клетки, создавать или удалять слои, модифицировать элементы слоёв) и актёрами (загрузка их изображений, вставка или удаление с поля), обеспечивая экспорт всех этих объектов в файлы.
Часто редакторы уровней идут «вдогонку» к существующим играм, т. е. специально создаются для определённой игры. Однако в данной работе предпринята попытка написать ПП, применимый для любых игр, не предъявляющих особо специфических требований к дизайну уровней. Вследствие этого, потребителем ПП не будет конкретный заказчик, так что в данном случае целесообразно выбрать для разработки продукта каскадную модель, предусматривающую достаточно редкий выпуск новых версий. В качестве методологии выбрана «тяжеловесная», предусматривающая прохождение всех этапов разработки ПП, в том числе и хорошо проработанную проектную документацию
2 ТЕХНИЧЕСКОЕ ЗАДАНИЕ НА РАЗРАБОТКУ ПРОГРАММНОГО ПРОДУКТА
Введение
Программный продукт (далее – ПП) представляет собой среду для разработки двухмерных уровней к компьютерным играм. Может быть использован разработчиками компьютерных игр как вспомогательный инструмент проектирования. При этом не имеет значения, на каком языке программирования и под какой платформой ведётся разработка, т. к. данный ПП является универсальным.
2.1 Основание для разработки
Разработка выполняется на основании индивидуального задания на курсовую работу по дисциплине «Технология программирования и создания программных продуктов», выданного руководителем работы Спаский А.И , преподователь кафедры компьютерных информационных технологий Донбасской государственной машиностроительной академии 08.03.2013.
Условное обозначение разработки – GameDesigner.
2.2 Назначение разработки
ПП предназначен для автоматизации работы над одним из аспектов разработки игр – проектирования уровней.
2.3 Требования к программному продукту
2.3.1 Требования к функциональным характеристикам
ПП должен выполнять следующие функции:
предоставлять пользователю дружественный и информативный интерфейс;
обеспечивать создание «заготовок» игровых уровней (пустых уровней);
предоставлять возможность редактирования уровней (добавление элементов, слоёв-контейнеров и актёров, удаление и модификация перечисленного);
предоставлять возможность лёгкого исправления неверных действий;
обеспечивать возможность удобной и быстрой навигации по уровню;
предоставлять возможность показа уровня в удобном для пользователя виде (отключение видимости ненужных слоёв, полноэкранный просмотр);
исключать возможность редактирования ошибочных элементов путём их блокировки;
автоматизировать некоторые повторяющиеся операции (заполнение поля однотипными или случайными элементами);
быть независимым от аппаратной платформы;
функционировать под любой программной платформой, поддерживающий .NET.
2.3.2 Требования к надежности
ПП должен устойчиво функционировать и не приводить к сбоям операционной системы;
ПП должен обеспечивать контроль входной и выходной информации на соответствие заданным форматам данных;
ПП должен обеспечивать обработку ошибочных действий пользователя с выдачей соответствующих сообщений.
2.3.3 Условия эксплуатации
Условия эксплуатации ПП определяются СанПиН 2.2.2 545-96 «Гигиенические требования к видеодисплейным терминалам, персональным вычислительным машинам и организации работы».
2.3. 4 Требования к составу и параметрам технических средств
Требования к параметрам технических средств, необходимых для эксплуатации ПП, определяются требованиями к функционированию информационной платформы .NET:
процессор Intel Pentium II 450 МГц;
объём необходимого ОЗУ зависит от операционной системы, рекомендуется не менее 64 Мбайт;
при установке программа потребует около 5 Мбайт свободного дискового пространства, однако для последующей стабильной и продуктивной работы необходимо дополнительное место на диске для сохранения файлов уровней;
объём видеопамяти 4 Мбайта или более, видеоадаптер должен обеспечивать режим True Color, рекомендуется 2D-акселератор;
рекомендуемая разрешающая способность монитора 1024x768 или выше;
установлена одна из операционных систем: Microsoft Windows Server 2003, Windows XP Professional, Windows XP Home Edition, Windows 2000 (рекомендуется Service Pack 3), Windows Millennium Edition (Windows Me), Windows 98, Microsoft Windows NT 4.0 (требуется Service Pack 6a).
2.4 Требования к информационной и программной совместимости
Программный продукт функционирует в среде информационной платформы .NET. ПП создается с использованием инструментального средства разработки приложений Microsoft Visual Studio .NET на языке С#.
2.5 Требования к программной документации
Программная документация должна включать:
рабочий проект ПП на языке UML;
исходные коды ПП с комментариями;
модульные тесты ПП;
перечень изменений в исходных кодах;
контекстно-зависимую помощь в ПП;
руководство пользователя;
руководство по установке.
2.6 Технико-экономические показатели
Экономическая эффективность от внедрения ПП обеспечивается за счет сокращения сроков разработок, достигаемого путем автоматизации процесса проектирования.
Стоимость разработки ПП составляет 763 грн. (см приложение А).
2.7 Стадии и этапы разработки
Этап/
Срок выполнения
Содержание работ

Техническое задание
25.01.13
Анализ и формализация требования к ПП,планирование работ.

Эскизный проект
30.02.13
Предварительная разработка проекта ПП с использованием UML: диаграммы прецедентов использования, диаграммы классов и последовательности.

Технический проект
10.03.13
Реализация рабочей версии ПП с основной функциональностью; модульные тесты.

Рабочий проект
20.03.13
Корректировка и доработка программного обеспечения; разработка документации.

Внедрение
25.03.13
Разработка мероприятий по внедрению и сопровождению ПП


2.8 Порядок контроля и приемки
Контроль корректности функционирования и пригодности ПП к эксплуатации выполняется совместно Разработчиком и Заказчиком ПП на основании приемочных тестов, предоставляемых Заказчиком. Решение о приемке в эксплуатацию принимается на основании акта тестовых испытаний.
3 ПРОЕКТ ПРОГРАММНОГО ПРОДУКТА
3.1 Диаграмма классов
Диаграмма классов (class diagram) служит для представления статической структуры модели системы в терминологии классов объектно-ориентированного программирования. Диаграмма классов может отражать, в частности, различные взаимосвязи между отдельными сущностями предметной области, такими как объекты и подсистемы, а также описывает их внутреннюю структуру и типы отношений. На данной диаграмме не указывается информация о временных аспектах функционирования системы. С этой точки зрения диаграмма классов является дальнейшим развитием концептуальной модели проектируемой системы. Диаграмма классов состоит из множества элементов, которые в совокупности отражают декларативные знания о предметной области. Эти знания интерпретируются в базовых понятиях языка UML, таких как классы, интерфейсы и отношения между ними и их составляющими компонентами. При этом отдельные компоненты этой диаграммы могут образовывать пакеты для представления более общей модели системы. Если диаграмма классов является частью некоторого пакета, то ее компоненты должны соответствовать элементам этого пакета, включая возможные ссылки на элементы из других пакетов.
Для данного ПП диаграмма классов изображена на рисунке 3.1

Рисунок 3.1 – Общий вид диаграммы классов
Дабы не загромождать диаграмму, комментариев к классам на рисунке нет – они приводятся чуть ниже.
Класс GameField. Представляет собой игровое поле в целом. Включает в себя графические слои, актёров, списки используемых спрайтов и т. п.
Основные поля и свойства:
Layers – список визуальных и невизуальных слоёв;
Actors – список актёров;
NonVisualLayerSybols – список условных обозначений для невизуальных слоёв;
VisualLayerSprites – спрайтов для визуальных слоёв;
ActorPictures – список изображений актёров;
CurrentActor – текущий актёр;
CurrentLayer – текущий слой;
CurrentActorIndex – индекс текущего актёра;
CurrentLayerIndex – индекс текущего слоя.
Основные методы:
Clear – очистить всё поле;
DeleteElement – обнулить ячейку (элемент) текущего слоя;
Draw – отрисовать содержимое поля в графический буфер;
GetMaxX – получить ширину поля;
GetMaxY – получить высоту поля;
GetSelectedObjCode – получить код выбранного объекта;
InsertElement – записать значение в ячейку текущего слоя;
PickActor – выбрать актёра путём указания точки на поле;
RemoveCurrentActor – удалить текущего актёра;
SelectRange – выбрать диапазон ячеек текущего поля;
Save – сохранить игровое поле в файл;
Load – загрузить игровое поле из файла.
Класс Layer. Представляет отдельный слой (визуальный – состоит из спрайтов – или невизуальный – состоит из кодов условных обозначений) на игровом поле.
Основные поля и свойства:
CellWidth – ширина ячейки;
CellHeight – высота ячейки;
ColumnCount – число столбцов матрицы;
Commentary – комментарий;
Depth – глубина слоя;
IsLocked – признак блокировки;
Visible – видимость слоя;
RowCount – число строк матрицы;
Title – название слоя;
TypeOfLayer – тип слоя (визуальный или невизуальный);
matrix – матрица элементов слоя.
Основные методы:
Clear – обнуление всех элементов слоя;
ExchangeElements – обмен значений групп элементов;
GetElement – получить значение элемента матрицы;
SetElement – записать значение элемента матрицы;
SetRandomRange – записать в матрицу элементы со случайными кодами из заданного диапазона значений.
Класс DynamicArray2D. Класс, представляющий собой двумерный динамический массив (матрицу).
Класс Actor. Инкапсулирует информацию об актёре.
Класс IndexedList. Список с двойной индексацией. Первый из индексов является порядковым номером, а второй – альтернативным идентификатором. Данный класс является предком классов VisualLayerSpriteList, NonVisualLayerSymbol и ActorPictureList.
Класс VisualLayerSpriteList. Представляет собой список объектов класса VisualLayerSprite – спрайтов для графических слоёв. Обеспечивает удобное удаление, добавление, модификацию спрайтов, доступ к их содержимому и двойную индексацию.
Класс NonVisualLayerSymbolList. Список объектов класса NonVisualLayerSymbol – условных обозначений для невизуальных слоёв. Работает аналогично VisualLayerSpriteList.
Класс ActorPictureList. Список объектов класса ActorPicture – изображений актёров. Работает аналогично VisualLayerSpriteList.
Класс MainForm. Включает в себя интерфейс пользователя и отвечает за синхронизацию внутреннего состояние классов из группы модели (все вышеперечисленные классы, подробнее см. пункт 3.4) и интерфейса (т. е. визуального представления модели).
3.2 Диаграмма прецедентов использования
Функциональное назначение системы описывается диаграммой вариантов, или прецедентов, использования. Суть данной диаграммы состоит в следующем: проектируемая система представляется в виде множества сущностей или актеров, взаимодействующих с системой с помощью так называемых прецедентов использования. При этом актёром (actor) или действующим лицом называется любая сущность, взаимодействующая с системой извне. Это может быть человек, техническое устройство, программа или любая другая система, которая может служить источником воздействия на моделируемую систему так, как определит сам разработчик. В свою очередь, прецедент использования (use case) служит для описания сервисов, которые система предоставляет актеру. Другими словами, каждый прецедент использования определяет некоторый набор действий, совершаемый системой при диалоге с актером. При этом ничего не говорится о том, каким образом будет реализовано взаимодействие актеров с системой.
Диаграмма прецедентов использования для данного ПП изображена на рисунке 3.2

Рисунок 3.2 – Диаграмма прецедентов использования
3.3 Диаграмма последовательностей
На диаграмме последовательности изображаются исключительно те объекты, которые непосредственно участвуют во взаимодействии и не показываются возможные статические ассоциации с другими объектами. Для диаграммы последовательности ключевым моментом является именно динамика взаимодействия объектов во времени. При этом диаграмма последовательности имеет как бы два измерения. Одно — слева направо в виде вертикальных линий, каждая из которых изображает линию жизни отдельного объекта, участвующего во взаимодействии. Графически каждый объект изображается прямоугольником и располагается в верхней части своей линии жизни. Внутри прямоугольника записываются имя объекта и имя класса, разделенные двоеточием. При этом вся запись подчеркивается, что является признаком объекта, который, как известно, представляет собой экземпляр класса.
Диаграмма последовательностей для данного ПП изображена на рисунке 3.3

Рисунок 3.3 – Диаграмма последовательностей
3.4 Использованные шаблоны проектирования
Паттерн (шаблон, образец) – это типичное решение типичной проблемы в данном контексте. Паттерн проектирования именует, абстрагирует и идентифицирует ключевые аспекты структуры общего решения, которые и позволяют применить его для создания повторно используемого дизайна. Он выделяет участвующие классы и объекты, их роли и отношения, а также операции. Шаблоны проектирования позволяют не только быстрее строить решения и получать качественный исходный код, но и проще становится взаимодействие с коллективом разработчиков ПП при разработке больших систем.
При выполнении данной работы был использован паттерн Модель-Вид-Контроллёр (MVC). MVC состоит из объектов трех видов. Модель – это объект приложения, а вид – экранное представление. Контроллер описывает, как интерфейс реагирует на управляющие воздействия пользователя. До появления схемы MVC эти объекты в пользовательских интерфейсах смешивались.
MVC отделяет вид от модели, устанавливая между ними протокол взаимодействия «подписка/оповещение». Вид должен гарантировать, что внешнее представление отражает состояние модели. При каждом изменении внутренних данных модель оповещает все зависящие от нее виды, в результате чего вид обновляет себя. Такой подход позволяет присоединить к одной модели несколько видов, обеспечив тем самым различные представления. Можно создать новый вид, не переписывая модель.
На рисунке 3.4 показана диаграмма классов для паттерна MVC.

Рисунок 3.4 – Диаграмма классов для паттерна MVC
Иногда с целью упрощения структуры программы Controller и View объединяют, что и было сделано при разработке данного ПП. Таким образом, в настоящей работе роль модели играет класс GameField со всеми подчинёнными ему классами, а в качестве контроллера и вида выступает класс MainForm.
4 МОДУЛЬНЫЕ ТЕСТЫ
Модульные тесты являются важнейшим элементом методологии TDD. Для языка C# существует специальная утилита для поддержки модульного тестирования – NUnit. Процесс тестирования данного ПП проиллюстрирован на рисунке 4.1

Рисунок 4.1 – Тестирование ПП
4.1 Тест класса ActorPictureList
Тестирование добавления изображения актёра:
public void AddPictureTest()
{
VLSLTest.CreateBitmap();
ActorPictureList ActorPictures = new ActorPictureList();
ActorPictures.AddPicture(1, "Тест-актёр 1", "bitmap.bmp");
Assert.AreEqual(1, ActorPictures.Count);
Assert.AreEqual(-1, ActorPictures.CurrentIndex);
}
Тестирование получения идентификатора актёра:
public void GetAutoActorCodeTest()
{
VLSLTest.CreateBitmap();
ActorPictureList ActorPictures = new ActorPictureList();
Assert.AreEqual(1, ActorPictures.GetAutoActorCode());
ActorPictures.AddPicture(1, "Тест-актёр 1", "bitmap.bmp");
Assert.AreEqual(2, ActorPictures.GetAutoActorCode());
ActorPictures.AddPicture(byte.MaxValue, "Тест-актёр 2", "bitmap.bmp");
Assert.AreEqual(2, ActorPictures.GetAutoActorCode());
}
Тестирование получения и установки значений полей текущего типа актёров:
public void GetSetCurrentTest()
{
VLSLTest.CreateBitmap();
ActorPictureList ActorPictures = new ActorPictureList();
ActorPictures.AddPicture(1, "Тест-актёр 1", "bitmap.bmp");
ActorPictures.CurrentIndex = 0;
ActorPictures.CurrentActorCode = 2;
Assert.AreEqual(2, ActorPictures.CurrentActorCode);
ActorPictures.SetCurrentDirective("Директива 1", 0);
Assert.AreEqual("Директива 1", ActorPictures.GetCurrentDirective(0));
ActorPictures.CurrentCommentary = "Новый комментарий";
Assert.AreEqual("Новый комментарий", ActorPictures.CurrentCommentary);
}
Тестирование получения и установки значений полей для типа актёра по индексу:
public void GetSetByActorCodeTest()
{
VLSLTest.CreateBitmap();
ActorPictureList ActorPictures = new ActorPictureList();
ActorPictures.AddPicture(1, "Тест-актёр 1", "bitmap.bmp");
ActorPictures.SetActorCode(1, 2);
ActorPictures.SetDirective(2, "Директива 1", 0);
Assert.AreEqual("Директива 1", ActorPictures.GetDirective(2, 0));
ActorPictures.SetCommentary(2, "Новый комментарий");
Assert.AreEqual("Новый комментарий", ActorPictures.GetCommentary(2));
}
Тестирование удаления изображения актёров:
public void DeleteByActorCodeTest()
{
VLSLTest.CreateBitmap();
ActorPictureList ActorPictures = new ActorPictureList();
ActorPictures.AddPicture(1, "Тест-актёр 1", "bitmap.bmp");
ActorPictures.DeleteByActorCode(1);
Assert.AreEqual(0, ActorPictures.Count);
}
4.2 Тест класса DynamicArray2D
Тестирование получения и записи элемента:
public void SetGetElementTest()
{
DynamicArray2D DynArr = new DynamicArray2D(3, 4);
for (int i = 0; i < DynArr.RowCount; i++)
for (int j = 0; j < DynArr.ColumnCount; j++)
DynArr.SetElement(i, j, j + i*DynArr.ColumnCount);
for (int i = 0; i < DynArr.RowCount; i++)
for (int j = 0; j < DynArr.ColumnCount; j++)
Assert.AreEqual(j + i*DynArr.ColumnCount, DynArr.GetElement(i, j));
}
Тестирование записи диапазона:
public void SetRangeTest()
{
DynamicArray2D DynArr = new DynamicArray2D(4, 3);
DynArr.SetRange(0, DynArr.RowCount-1, 0, DynArr.ColumnCount-1, 100);
for (int i = 0; i < DynArr.RowCount; i++)
for (int j = 0; j < DynArr.ColumnCount; j++)
Assert.AreEqual(100, DynArr.GetElement(i, j));
}
Тестирование получения и установки числа строк двухмерного массива:
public void SetGetRowCountTest()
{
DynamicArray2D DynArr = new DynamicArray2D(2, 3);
for (int i = 0; i < DynArr.RowCount; i++)
for (int j = 0; j < DynArr.ColumnCount; j++)
DynArr.SetElement(i, j, j + i*DynArr.ColumnCount);
int Rows = DynArr.RowCount;
DynArr.RowCount = DynArr.RowCount + 2;
Assert.AreEqual(4, DynArr.RowCount);
for (int i = 0; i < Rows; i++)
for (int j = 0; j < DynArr.ColumnCount; j++)
Assert.AreEqual(j + i*DynArr.ColumnCount, DynArr.GetElement(i, j));
for (int i = 2; i <= 3; i++)
for (int j = 0; j < DynArr.ColumnCount; j++)
DynArr.SetElement(i, j, 7);
for (int i = 2; i <= 3; i++)
for (int j = 0; j < DynArr.ColumnCount; j++)
Assert.AreEqual(7, DynArr.GetElement(i, j));
}
Тестирование получения и установки числа столбцов двухмерного массива:
public void SetGetColumnCountTest()
{
DynamicArray2D DynArr = new DynamicArray2D(3, 2);
for (int i = 0; i < DynArr.RowCount; i++)
for (int j = 0; j < DynArr.ColumnCount; j++)
DynArr.SetElement(i, j, j + i*DynArr.ColumnCount);
int Columns = DynArr.ColumnCount;
DynArr.ColumnCount = DynArr.ColumnCount + 2;
Assert.AreEqual(4, DynArr.ColumnCount);
for (int i = 0; i < DynArr.RowCount; i++)
for (int j = 0; j < Columns; j++)
Assert.AreEqual(j + i*Columns, DynArr.GetElement(i, j));
for (int i = 0; i < DynArr.RowCount; i++)
for (int j = 2; j <= 3; j++)
DynArr.SetElement(i, j, 7);
for (int i = 0; i < DynArr.RowCount; i++)
for (int j = 2; j <= 3; j++)
Assert.AreEqual(7, DynArr.GetElement(i, j));
}
4.3 Тест класса Layer
Тестирование записи и взятия значения элемента:
public void GetSetElementTest()
{
Layer layer = new Layer("New layer", "Layer for test", LayerType.Visual, 1, 5, 5, 10, 20);
layer.SetElement(5, 6, 100);
Assert.AreEqual(100, layer.GetElement(5, 6));
}
Тестирование обнуления всех элементов:
public void ClearTest()
{
Layer layer = new Layer("New layer", "Layer for test", LayerType.Visual, 1, 5, 5, 10, 20);
for (int i = 0; i <= 9; i++)
for (int j = 0; j <= 19; j++)
layer.SetElement(i, j, j + i*20);
layer.Clear();
for (int i = 0; i <= 9; i++)
for (int j = 0; j <= 19; j++)
Assert.AreEqual(0, layer.GetElement(i, j));
}
Тестирование обмена значений групп элементов
public void ExchangeElementsTest()
{
Layer layer = new Layer("New layer", "Layer for test", LayerType.Visual, 1, 5, 5, 10, 20);
for (int i = 0; i <= 9; i++)
for (int j = 0; j <= 19; j+=2)
{
layer.SetElement(i, j, 1);
layer.SetElement(i, j+1, 2);
}
layer.ExchangeElements(1, 2);
for (int i = 0; i <= 9; i++)
for (int j = 0; j <= 19; j+=2)
{
Assert.AreEqual(2, layer.GetElement(i, j));
Assert.AreEqual(1, layer.GetElement(i, j+1));
}
}
Тестирование установки значений диапазона элементов:
public void SetRangeTest()
{
Layer layer = new Layer("New layer", "Layer for test", LayerType.Visual, 1, 5, 5, 10, 20);
layer.SetRange(0, 9, 0, 19, 100);
for (int i = 0; i <= 9; i++)
for (int j = 0; j <= 19; j++)
Assert.AreEqual(100, layer.GetElement(i, j));
}
Тестирование установки случайных значений диапазона элементов:
public void RandomRangeTest()
{
Layer layer = new Layer("New layer", "Layer for test", LayerType.Visual, 1, 5, 5, 10, 20);
int[] ElementValues = { 1, 5, 7, 10 };
layer.SetRandomRange(0, 9, 0, 19, ElementValues);
for (int i = 0; i <= 9; i++)
for (int j = 0; j <= 19; j++)
{
bool OK = false;
for (int k = 0; k < ElementValues.Length; k++)
if (ElementValues[k] == layer.GetElement(i, j)) OK = true;
Assert.IsTrue(OK);
}
}
Тестирование взятия и установки высоты ячейки поля:
public void GetSetCellHeightTest()
{
Layer layer = new Layer();
layer.CellHeight = 7;
Assert.AreEqual(7, layer.CellHeight);
}
Тестирование взятия и установки ширины ячейки поля:
public void GetSetCellWidthTest()
{
Layer layer = new Layer();
layer.CellWidth = 7;
Assert.AreEqual(7, layer.CellWidth);
}
Тестирование взятия и установки числа строк матрицы:
public void GetSetRowCountTest()
{
Layer layer = new Layer("New layer", "Layer for test", LayerType.Visual, 1, 5, 5, 1, 20);
layer.RowCount = 10;
Assert.AreEqual(10, layer.RowCount);
for (int i = 0; i <= 9; i++)
for (int j = 0; j <= 19; j++)
Assert.AreEqual(0, layer.GetElement(i, j));
}
Тестирование взятия и установки числа столбцов матрицы:
public void GetSetColumnCountTest()
{
Layer layer = new Layer("New layer", "Layer for test", LayerType.Visual, 1, 5, 5, 10, 1);
layer.ColumnCount = 20;
Assert.AreEqual(20, layer.ColumnCount);
for (int i = 0; i <= 9; i++)
for (int j = 0; j <= 19; j++)
Assert.AreEqual(0, layer.GetElement(i, j));
}
Тестирование взятия и записи комментария:
public void GetSetCommentary()
{
Layer layer = new Layer();
layer.Commentary = "New commentary";
Assert.AreEqual("New commentary", layer.Commentary);
}
Тестирование взятия и установки глубины слоя:
public void GetSetDepth()
{
Layer layer = new Layer();
layer.Depth = 2;
Assert.AreEqual(2, layer.Depth);
}
Тестирование блокировки слоя:
public void GetSetLock()
{
Layer layer = new Layer();
layer.IsLocked = true;
Assert.IsTrue(layer.IsLocked);
layer.SetElement(0, 0, 100);
Assert.AreEqual(0, layer.GetElement(0, 0));
}
Тестирование видимости слоя:
public void GetSetVisible()
{
Layer layer = new Layer();
layer.IsVisible = false;
Assert.IsFalse(layer.IsVisible);
layer.SetElement(0, 0, 100);
Assert.AreEqual(0, layer.GetElement(0, 0));
}
Тестирование признака изменения внутреннего состояния слоя:
public void GetSetModified()
{
Layer layer = new Layer();
layer.Modified = false;
layer.SetElement(0, 0, 100);
Assert.IsTrue(layer.Modified);
}
Тестирование записи и чтения названия слоя:
public void GetSetTitle()
{
Layer layer = new Layer();
layer.Title = "New title";
Assert.AreEqual("New title", layer.Title);
}
Тестирование взятия и установки типа слоя:
public void GetSetTypeOfLayer()
{
Layer layer1 = new Layer(LayerType.Visual);
Layer layer2 = new Layer(LayerType.NonVisual);
Assert.AreEqual(LayerType.Visual, layer1.TypeOfLayer);
Assert.AreEqual(LayerType.NonVisual, layer2.TypeOfLayer);
}
Тестирование взятия и установки смещения по X:
public void GetSetXOffset()
{
Layer layer = new Layer();
layer.XOffset = 10;
Assert.AreEqual(10, layer.XOffset);
}
Тестирование взятия и установки смещения по Y:
public void GetSetYOffset()
{
Layer layer = new Layer();
layer.YOffset = 10;
Assert.AreEqual(10, layer.YOffset);
}
4.4 Тест класса VisualLayerSpriteList
Тестирование добавления спрайта:
public void AddSpriteTest()
{
CreateBitmap();
VisualLayerSpriteList VisualLayerSprites = new VisualLayerSpriteList();
VisualLayerSprites.AddSprite(1, "Тест-ячейка 1", "bitmap.bmp", false, false, 1);
Assert.AreEqual(1, VisualLayerSprites.Count);
Assert.AreEqual(-1, VisualLayerSprites.CurrentIndex);
}
Получения автоматически сгенерированного индекса спрайта:
public void GetAutoSpriteIndexTest()
{
CreateBitmap();
VisualLayerSpriteList VisualLayerSprites = new VisualLayerSpriteList();
Assert.AreEqual(1, VisualLayerSprites.GetAutoSpriteIndex());
VisualLayerSprites.AddSprite(1, "Тест-ячейка 1", "bitmap.bmp", false, false, 1);
Assert.AreEqual(2, VisualLayerSprites.GetAutoSpriteIndex());
VisualLayerSprites.AddSprite(Int16.MaxValue, "Тест-ячейка 2", "bitmap.bmp", false, false, 1);
Assert.AreEqual(2, VisualLayerSprites.GetAutoSpriteIndex());
}
Тестирование установки и взятия значений полей текущего спрайта:
public void GetSetCurrentTest()
{
CreateBitmap();
VisualLayerSpriteList VisualLayerSprites = new VisualLayerSpriteList();
VisualLayerSprites.AddSprite(1, "Тест-ячейка 1", "bitmap.bmp", false, false, 1);
VisualLayerSprites.CurrentIndex = 0;
VisualLayerSprites.CurrentSpriteIndex = 2;
Assert.AreEqual(2, VisualLayerSprites.CurrentSpriteIndex);
VisualLayerSprites.CurrentTransparent = true;
Assert.IsTrue(VisualLayerSprites.CurrentTransparent);
VisualLayerSprites.CurrentAlphaBlend = true;
Assert.IsTrue(VisualLayerSprites.CurrentAlphaBlend);
VisualLayerSprites.CurrentAlphaBlendValue = 0.5F;
Assert.AreEqual(0.5, VisualLayerSprites.CurrentAlphaBlendValue);
VisualLayerSprites.CurrentCommentary = "Новый комментарий";
Assert.AreEqual("Новый комментарий", VisualLayerSprites.CurrentCommentary);
}
Тестирование установки и взятия значений полей спрайта по индексу:
public void GetSetBySpriteIndexTest()
{
CreateBitmap();
VisualLayerSpriteList VisualLayerSprites = new VisualLayerSpriteList();
VisualLayerSprites.AddSprite(1, "Тест-ячейка 1", "bitmap.bmp", false, false, 1);
VisualLayerSprites.SetSpriteIndex(1, 2);
VisualLayerSprites.SetTransparent(2, true);
Assert.IsTrue(VisualLayerSprites.GetTransparent(2));
VisualLayerSprites.SetAlphaBlend(2, true);
Assert.IsTrue(VisualLayerSprites.GetAlphaBlend(2));
VisualLayerSprites.SetAlphaBlendValue(2, 0.5F);
Assert.AreEqual(0.5, VisualLayerSprites.GetAlphaBlendValue(2));
VisualLayerSprites.SetCommentary(2, "Новый комментарий");
Assert.AreEqual("Новый комментарий", VisualLayerSprites.GetCommentary(2));
}
Тестирование изменения индекса спрайта:
public void ChangeIndexTest()
{
CreateBitmap();
VisualLayerSpriteList VisualLayerSprites = new VisualLayerSpriteList();
VisualLayerSprites.AddSprite(1, "Тест-ячейка 1", "bitmap.bmp", false, false, 1);
VisualLayerSprites.AddSprite(2, "Тест-ячейка 2", "bitmap.bmp", false, false, 1);
VisualLayerSprites.AddSprite(3, "Тест-ячейка 3", "bitmap.bmp", false, false, 1);
VisualLayerSprites.CurrentIndex = 0;
Assert.AreEqual(0, VisualLayerSprites.CurrentIndex);
VisualLayerSprites.CurrentIndex = -10;
Assert.AreEqual(0, VisualLayerSprites.CurrentIndex);
VisualLayerSprites.CurrentIndex = 10;
Assert.AreEqual(2, VisualLayerSprites.CurrentIndex);
}
Тестирование удаления текущего спрайта:
public void DeleteCurrentTest()
{
CreateBitmap();
VisualLayerSpriteList VisualLayerSprites = new VisualLayerSpriteList();
VisualLayerSprites.AddSprite(1, "Тест-ячейка 1", "bitmap.bmp", false, false, 1);
VisualLayerSprites.CurrentIndex = 0;
VisualLayerSprites.DeleteCurrent();
Assert.AreEqual(-1, VisualLayerSprites.CurrentIndex);
Assert.AreEqual(0, VisualLayerSprites.Count);
}
Тестирование удаления спрайта по индексу:
public void DeleteBySpriteIndexTest()
{
CreateBitmap();
VisualLayerSpriteList VisualLayerSprites = new VisualLayerSpriteList();
VisualLayerSprites.AddSprite(1, "Тест-ячейка 1", "bitmap.bmp", false, false, 1);
VisualLayerSprites.DeleteBySpriteIndex(1);
Assert.AreEqual(0, VisualLayerSprites.Count);
}
5 ОПИСАНИЕ ОПЕРАЦИЙ РЕФАКТОРИНГА
5.1 Выделение нового класса
До рефакторинга:
public class NonVisualLayerSymbolList
{
private ArrayList elements;
private int currentIndex;
private ModificationType modification;
private int correctedIndex;
private int correctedCode;
public int CurrentIndex {...}
public int Count {...}
................................................................................
private bool CheckSymbolIndex(byte symbolIndex) {...}
public byte GetAutoSymbolIndex(){...}
public void AddSymbol(byte symbolIndex, string commentary) {...}
public void DeleteBySymbolIndex(byte symbolIndex) {...}
public byte CurrentSymbolIndex {...}
................................................................................
public void SetCommentary(int spriteIndex, string commentary) {...}
}
public class VisualLayerSpriteList
{
private ArrayList elements;
private int currentIndex;
private ModificationType modification;
private int correctedIndex;
private int correctedCode;
public int CurrentIndex {...}
public int Count {...}
................................................................................
private bool CheckSpriteIndex(int spriteIndex) {...}
public int GetAutoSpriteIndex() {...}
public void AddSprite(int spriteIndex, string commentary, string filename,
bool transparent, bool alphaBlend, float alphaBlendValue) {...}
public void DeleteBySpriteIndex(int spriteIndex) {...}
................................................................................
public string GetCommentary(int spriteIndex) {...}
public void SetCommentary(int spriteIndex, string commentary) {...}
}
public class ActorPictureList
{
private ArrayList elements;
private int currentIndex;
private ModificationType modification;
private int correctedIndex;
private int correctedCode;
public int CurrentIndex {...}
public int Count {...}
................................................................................
private bool CheckActorCode(byte actorCode) {...}
public byte GetAutoActorCode() {...}
public void AddPicture(byte actorCode, string commentary, string filename) {...}
public void DeleteByActorCode(byte actorCode) {...}
public byte CurrentActorCode {...}
................................................................................
public string GetDirective(byte actorCode, byte directiveIndex) {...}
public void SetDirective(byte actorCode, string directive, byte directiveIndex)
{...}
}
После рефакторинга:
public class NonVisualLayerSymbolList : IndexedList
{
private bool CheckSymbolIndex(byte symbolIndex) {...}
public byte GetAutoSymbolIndex(){...}
public void AddSymbol(byte symbolIndex, string commentary) {...}
public void DeleteBySymbolIndex(byte symbolIndex) {...}
public byte CurrentSymbolIndex {...}
................................................................................
public void SetCommentary(int spriteIndex, string commentary) {...}
}
public class VisualLayerSpriteList : IndexedList
{
private bool CheckSpriteIndex(int spriteIndex) {...}
public int GetAutoSpriteIndex() {...}
public void AddSprite(int spriteIndex, string commentary, string filename,
bool transparent, bool alphaBlend, float alphaBlendValue) {...}
public void DeleteBySpriteIndex(int spriteIndex) {...}
................................................................................
public string GetCommentary(int spriteIndex) {...}
public void SetCommentary(int spriteIndex, string commentary) {...}
}
public class ActorPictureList : IndexedList
{
private bool CheckActorCode(byte actorCode) {...}
public byte GetAutoActorCode() {...}
public void AddPicture(byte actorCode, string commentary, string filename) {...}
public void DeleteByActorCode(byte actorCode) {...}
public byte CurrentActorCode {...}
................................................................................
public string GetDirective(byte actorCode, byte directiveIndex) {...}
public void SetDirective(byte actorCode, string directive, byte directiveIndex)
{...}
}
public abstract class IndexedList
{
protected ArrayList elements;
protected int currentIndex;
protected ModificationType modification;
protected int correctedIndex;
protected int correctedCode;
public IndexedList(){...}
public int CurrentIndex {...}
public int Count {...}
public void DeleteCurrent(){...}
public bool Modified {...}
public void ResetModified(){...}
public ModificationType GetModificationType(){...}
public int GetCorrectedIndex(){...}
public int GetCorrectedCode(){...}
}
5.2 Инкапсулирование коллекции
До рефакторинга:
public class Layer
{
public DynamicArray2D matrix;
private string title;
private string commentary;
private bool visible;
private bool locked;
private LayerType layerType;
................................................................................
public Layer(string aTitle, string aCommentary, LayerType typeOfLayer, float aDepth, int cellWidth, int cellHeight, int matrixRowCount, int matrixColumnCount) {...}
................................................................................
}
После рефакторинга:
public class Layer
{
private DynamicArray2D matrix;
private string title;
private string commentary;
private bool visible;
private bool locked;
................................................................................
public Layer(string aTitle, string aCommentary, LayerType typeOfLayer, float aDepth, int cellWidth, int cellHeight, int matrixRowCount, int matrixColumnCount) {...}
................................................................................
public int RowCount {...}
public int ColumnCount {...}
public void SetElement(int row, int column, object elementValue) {...}
public int GetElement(int row, int column) {...}
public void Clear(){...}
public void SetRange(int row1, int row2, int column1, int column2, int elementValue) {...}
................................................................................
}
5.3 Замена «магических чисел»
До рефакторинга:
public class Layer
{
private byte layerType;
................................................................................
}
После рефакторинга:
public enum LayerType { Visual, NonVisual }
public class Layer
{
private LayerType layerType;
................................................................................
}
5.4 Повсеместное улучшение читабельности кода
До рефакторинга:
public Layer(string aTitle,string aCommentary,LayerType typeOfLayer,float aDepth,
int cellWidth,int cellHeight,int matrixRowCount,int matrixColumnCount)
{
title = string.Copy(aTitle); commentary = string.Copy(aCommentary);
layerType = typeOfLayer; depth = aDepth;
cellW = cellWidth; cellH = cellHeight;
matrix = new DynamicArray2D(matrixRowCount, matrixColumnCount);
visible = true; locked = false; xOfs = 0; yOfs = 0;
Clear();
}
public int CellWidth
{
get { return cellW; }
set {
if (visible && !locked) { cellW = value; modified = true; }
}
}
После рефакторинга:
public Layer(string aTitle, string aCommentary, LayerType typeOfLayer, float aDepth,
int cellWidth, int cellHeight, int matrixRowCount, int matrixColumnCount)
{
title = string.Copy(aTitle);
commentary = string.Copy(aCommentary);
layerType = typeOfLayer;
depth = aDepth;
cellW = cellWidth;
cellH = cellHeight;
matrix = new DynamicArray2D(matrixRowCount, matrixColumnCount);
visible = true;
locked = false;
xOfs = 0;
yOfs = 0;
Clear();
}
public int CellWidth
{ get
{ return cellW;
}
set
{
if (visible && !locked)
{
cellW = value;
modified = true; }}}
6 ОПИСАНИЕ СЦЕНАРИЯ СБОРКИ
Для выполнения сценариев сборки ПП на платформе .NET используют свободно распространяемую программу NAnt. Файл сценария обычно имеет расширение *.build и является XML-документом. В данной работе создан файл сценария сборки, выполняющий три задачи: сборка (build), тестирование (test) и удаление ненужных файлов (clean). Задача по умолчанию: сборка. Листинг файла:
<?xml version="1.0"?>
<project name="TestBuild" default="build">

<property name="build.dir" value=".\bin" />
<property name="src.dir" value=".\src" />
<property name="output" value="GameDesigner"/>
<property name="NUnitLocation" value="D:\Program Files\NAnt\lib\net\1.1"/>

<target name="build">
<echo message="Building GameDesigner"/>
<mkdir dir="bin" />
<csc target="exe" output="${build.dir}\${output}.exe" debug="true">
<references basedir="${NUnitLocation}">
<includes asis="true" name="NUnit.Framework.dll"/>
<includes asis="true" name="System.dll"/>
<includes asis="true" name="System.Data.dll"/>
<includes asis="true" name="System.Drawing.dll"/>
<includes asis="true" name="System.Windows.Forms.dll"/>
<includes asis="true" name="System.XML.dll"/>
</references>
<sources>
<includes name="Actor.cs"/>
<includes name="ActorPictureList.cs"/>
<includes name="ActorPictureListTest.cs"/>
<includes name="AssemblyInfo.cs"/>
<includes name="DynamicArray2D.cs"/>
<includes name="DynamicArray2DTest.cs"/>
<includes name="GameField.cs"/>
<includes name="IndexedList.cs"/>
<includes name="Layer.cs"/>
<includes name="LayerTest.cs"/>
<includes name="NonVisualLayerSymbolList.cs"/>
<includes name="VisualLayerSpriteList.cs"/>
<includes name="VLSLTest.cs"/>
<includes name="AboutBox.cs"/>
<includes name="ActorsDialog.cs"/>
<includes name="DirectivesDialog.cs"/>
<includes name="FullScreen.cs"/>
<includes name="LayersDialog.cs"/>
<includes name="MainForm.cs"/>
<includes name="SpritesDialog.cs"/>
<includes name="SymbolsDialog.cs"/>
</sources>
</csc>
<call target="test" />
<copy file="Actor.cs" todir="${src.dir}" />
<copy file="ActorPictureList.cs" todir="${src.dir}" />
<copy file="ActorPictureListTest.cs" todir="${src.dir}" />
<copy file="AssemblyInfo.cs" todir="${src.dir}" />
<copy file="DynamicArray2D.cs" todir="${src.dir}" />
<copy file="DynamicArray2DTest.cs" todir="${src.dir}" />
<copy file="GameField.cs" todir="${src.dir}" />
<copy file="IndexedList.cs" todir="${src.dir}" />
<copy file="Layer.cs" todir="${src.dir}" />
<copy file="LayerTest.cs" todir="${src.dir}" />
<copy file="NonVisualLayerSymbolList.cs" todir="${src.dir}" />
<copy file="VLSLTest.cs" todir="${src.dir}" />
<copy file="AboutBox.cs" todir="${src.dir}" />
<copy file="ActorsDialog.cs todir="${src.dir}"/>
<copy file="DirectivesDialog.cs todir="${src.dir}"/>
<copy file="FullScreen.cs" todir="${src.dir}/>
<copy file="LayersDialog.cs" todir="${src.dir}/>
<copy file="MainForm.cs" todir="${src.dir}/>
<copy file="SpritesDialog.cs" todir="${src.dir}/>
<copy file="SymbolsDialog.cs" todir="${src.dir}/>
<exec program="${output}.exe" basedir="${build.dir}" />
</target>
<target name="test">
<nunit2>
<formatter type="Plain" />
<test assemblyname="${build.dir}\${output}.exe" />
</nunit2>
</target>
<target name="clean">
<echo message="Cleaning..."/>
<delete dir="${src.dir}" />
<delete>
<fileset basedir=".\bin">
<includes name="*.exe" />
<includes name="*.pdb" />
</fileset>
</delete>
</target>
</project>
7 ПРИЁМОЧНЫЕ ТЕСТЫ
Приемочные тесты – это типичные/критичные сценарии работы с системой. Позволяют при автоматизированном прогоне утверждать, что система в общем работает по основным/критичным сценариям. В такие тесты обычно включаются тесты функций ядра программного обеспечения системы.
Заказчик разрабатывает приемочные тесты после того, как он определил директивы программы. Если тестирование модулей показывает разработчикам, как работает система в любой ее части, то с помощью приемочных тестов команда разработчиков определяет, делает ли система то, что от нее ожидают потребители. Заказчики ответственны за то, чтобы каждая директива имела приемочные тесты для проверки на соответствие. Заказчик может писать тесты самостоятельно, привлекать к работе других членов его учреждения (например, бизнес-аналитиков или QA-персонал (приемщики программы по качеству)) или комбинировать два этих подхода. Тестирование показывает заказчику, имеет ли система те черты, которые ей следует иметь, и правильно ли они реализованы. В идеале, заказчик должен иметь приемочные тесты для директив каждого периода, написанные до того, как этот период закончен. Приемочные тесты должны быть автоматизированы, и выполняться настолько часто, чтобы можно было гарантировать, что разработчики не нарушили никакие имеющиеся черты после внесения новых особенностей.
Признаком готовности сдачи ПП «Гейм-дизайнер» потребителям является успешное выполнение следующих приёмочных тестов:
1. Сразу после запуска программы должно быть подготовлено к редактированию пустое игровое поле: списки спрайтов, условных обозначений, актёров и директив должны быть доступны, но не содержать элементов; игровое поле не должно содержать никаких слоёв, кроме зарезервированного слоя «Актёры», который тоже должен быть пуст.
2. Должны успешно выполняться функции по добавлению и удалению элементов из списков:
а) спрайтов;
б) условных обозначений;
в) актёров;
г) директив актёрам;
д) визуальных и невизуальных слоёв.
Об успешности выполнения данного теста в настоящей версии ПП следует судить по отображаемому содержимому соответствующих визуальных компонентов – списков объектов.
3. Должны успешно выполняться функции по модификации элементов (ячеек) слоёв и их диапазонов – вставка, замена, обнуление.
4. Должны успешно выполняться функции добавления и удаления актёров.
5. Должны успешно выполняться функции по выделению текущего слоя, ячейки слоя (или их диапазона), актёра и элемента, предназначенного для установки на поле.
6. Содержимое списков элементов, предназначенных для установки на определённых слой игрового поля должно соответствовать типу текущего слоя.
7. Содержимое игрового поля должно адекватно и своевременно перерисовываться в предназначенном для этого элементе интерфейса.
8. Должна успешно выполняться функция навигации по игровому полю (т. е. к любому элементу поля можно получить доступ). В настоящей версии ПП данная функция осуществляется посредством полос прокрутки.
9. Должна выполняться функция полноэкранного просмотра уровня.
10. Должны выполняться функции по блокировке и управлению видимостью слоёв (заблокированные или невидимые слои не могут быть модифицированы, невидимые слои не отображаются на экране).
11. Должна успешно выполняться функция по очистке содержимого всего уровня.
12. Должны успешно выполняться функции сохранения/загрузки файлов уровней.
8 РУКОВОДСТВО ПОЛЬЗОВАТЕЛЯ
Как уже указывалось, ПП «Гейм-дизайнер» предназначен для проектирования уровней к играм. Инсталляция программы осуществляется простым копированием дистрибутива на жёсткий диск с последующей распаковкой. Запуск производится традиционным способом – двойным щелчком по значку программы или аналогичным.
После запуска ПП на экране появляется окно, подобное изображённому на рисунке 8.1

Рисунок 8.1 – Главное окно программы
В верхней части окна располагается главное меню, в котором есть пункты, позволяющие выйти из программы и узнать о назначении ПП. Чуть ниже расположена панель инструментов, которая предоставляет доступ к большинству функциональных возможностей программы. Непосредственно под панелью инструментов находится область с полосами прокрутки, куда отображается непосредственное содержимое игрового уровня. Правее установлен список элементов, которые могут быть нанесены на текущий слой игрового поля. Ещё правее – список всех слоёв. В нижней части окна располагается информационная панель, где отображается позиция элемента (для ячеек слоев – столбец и строка матрицы, для актёров – координаты X и Y) и код объекта (индекс).
Проектирование уровня начинается с формирования списков спрайтов для визуальных слоёв, условных обозначений для невизуальных слоёв, изображений актёров, директив актёрам (если это целесообразно) и списка слоёв. Открытие диалогов, позволяющих редактировать указанные списки, происходит по нажатию соответствующих кнопок на панели инструментов. Все диалоги довольно похожи между собой и предоставляют доступ только к базовым функциям по удалению и добавлению элементов списков.
Спрайты для визуальных слоёв представляют собой «кирпичики», из которых строится изображение слоя. Предположительно, они должны в редакторе отображаться точно так же, как и в игре. Условные обозначения для невизуальных слоёв являются кодами, которые несут информацию о физических свойствах ячеек слоёв. Эта информация необходима для динамического взаимодействия актёров с остальной частью игрового поля и некоторых других целей. Условные обозначения не имеют (и не должны иметь) визуального представления в играх, они видны только в редакторе. Изображения актёров вводятся для удобства и наглядности проектирования уровня. Директивы актёров являются кодами, которые обозначают действие, выполняемое актёром сразу после своего создания; необязательны к применению и поддрерживаются в данном ПП для универсальности.
Список слоёв состоит из слоёв трёх типов: визуальных (содержащих графику), невизуальных (содержащих коды взаимодействий с актёрами) и слоя актёров. Последний является зарезервированным и не подлежащим удалению. Он автоматически отрисовывается поверх всех слоёв.
После формирования всех списков следует переходить непосредственно к редактированию игрового поля. Для этого можно использовать следующую стратегию. Из списка слоёв нужно выбрать тот, который вы собираетесь модифицировать. Для добавления элементов на слой нужно сначала нажать кнопку модификации на панели инструментов, затем выбрать из списка элементов подходящий элемент и мышью указать позицию вставки на игровом поле. Для слоёв, состоящих из ячеек можно выбрать целый диапазон, куда будут вставлены копии выбранного элемента. Для удаления элементов нужно сначала их выделить, а затем нажать кнопку удаления на панели инструментов.
Для удобства редактирования предусмотрены функции блокировки и отключения видимости слоёв. Активизация этих функций происходит по нажатию на соответствующие значки в списке слоёв. Заблокированные слои не подлежат редактированию, а невидимые, кроме этого, не отображаются на экране.
Увидеть спроектированный уровень целиком можно, нажав кнопку полноэкранного просмотра на панели инструментов. По окончании работы следует сохранить уровень в файле (откуда его можно будет впоследствии загрузить). Можно также стереть содержимое текущего уровня, начав проектировать новый. Все эти функции вызываются нажатием кнопок на панели инструментов.
ЗАКЛЮЧЕНИЕ
В ходе выполнения индивидуального задания по дисциплине «Технология программирования и создания программных продуктов» был разработан программный продукт, представляющий собой утилиту для создания уровней к компьютерным играм. В ходе работы была изучена специфика программирования подобных программных продуктов, приобретены навыки использования практик методологии XP: модульного тестирования и рефакторинга. Были составлены техническое задание, UML-диаграммы, приёмочные тесты.
Разработанный ПП – не только учебный проект. Он является полноценным продуктом, вполне справляющимся со своими задачами. Однако, в силу ограничения по времени, некоторые функции ПП остались нереализованными или недостаточно проработанными, поэтому в ближайшем будущем будут выпущены новые релизы.
ЛИТЕРАТУРА
1 Бэк Кент «Экстремальное программирование»
2 Ларман «Применение UML и шаблонов проектирования»
3 Фаулер, Бэк «Рефакторинг – улучшение существующего кода»
4Троелсен Э. C# и платформа .NET. Библиотека программиста. – СПб.: Питер, 2004. – 796 с.: ил.
5Internet
ПРИЛОЖЕНИЕ A. РАСЧЕТ КАПИТАЛЬНЫХ ЗАТРАТ НА РАЗРАБОТКУ ПРОГРАММНОГО ПРОДУКТА
Затраты на создание программного продукта определяются как: , где
К1 – затраты на оборудование;
К2 – затраты на лицензионное программное обеспечение;
К3 – затраты, необходимые для создания программного изделия.
Поскольку для данной разработки не требуется покупка специализированного оборудования и программного обеспечения, то К1=0 и К2=0. Таким образом, затраты на создание программного изделия составят: , где
З1 – затраты труда разработчиков;
З2 – затраты компьютерного времени;
З3 – накладные расходы.
В соответствии с планом работ данные о затратах на заработную плату приведены в таблице A.1.
Таблица A.1 - Затраты на заработную плату
Этап создания ПП
К-во разработчиков (чел.)
Средн. время разработки (час/чел.)
Средн. стоимость разработки (грн.час/чел.)
Всего(грн.)

Техническое задание
1
3
15
45

Эскизный проект
1
10
15
150

Технический проект
1
20
10
200

Рабочий проект
1
12
10
120

Внедрение
1
5
20
100

З1=
615


Затраты компьютерного времени составят: З2 = Сk(F0 , где
Сk – себестоимость компьютерного часа, грн.;
F0 – затраты компьютерного времени на разработку программы.
В соответствии с таблицей А.1, данные о затратах, связанных с эксплуатацией ЭВМ, приведены в таблице А.2.
Таблица А.2 - Затраты на эксплуатацию ЭВМ
Этап создания ПМК
К-во разработчиков (чел.)
Затраты комп-ного времени (час/чел.)
Средн. себестоимость комп-ного (грн./час)
Всего(грн.)

Техническое задание
1
2
1,5
3

Эскизный проект
1
10
1,5
15

Технический проект
1
20
2
40

Рабочий проект
1
12
2
24

Внедрение
1
3
1,5
4,5

З2=
86,5


Накладные расходы, связанные с затратами на содержание помещений и затратами на расходные материалы, принимаются в размере 10% от фонда заработной платы, что составит З3= 61,5 грн.
Капитальные затраты на разработку программного продукта составят:
= 615 + 86,5 + 61,5 = 763 грн.
ПРИЛОЖЕНИЕ Б. ЛИСТИНГ ЧАСТИ ПРОГРАММЫ
using System;
using System.Drawing;
namespace ClassesForEditor
{
/// <summary>
/// Summary description for GameField.
/// </summary>
public class GameField
{
private int currentLayerIndex;
private int currentActorIndex;
private Rectangle selection;
private bool modified;
private bool wantsRepaint;
private bool actorsVisible;
private bool actorsLocked;
public System.Collections.ArrayList Layers;
public System.Collections.ArrayList Actors;
public VisualLayerSpriteList VisualLayerSprites;
public NonVisualLayerSymbolList NonVisualLayerSymbols;
public ActorPictureList ActorPictures;
public GameField()
{
currentLayerIndex = -1;
currentActorIndex = -1;
selection = new Rectangle(0, 0, 0, 0);
modified = false;
wantsRepaint = false;
actorsLocked = false;
actorsVisible = true;
Layers = new System.Collections.ArrayList();
Actors = new System.Collections.ArrayList();
VisualLayerSprites = new VisualLayerSpriteList();
NonVisualLayerSymbols = new NonVisualLayerSymbolList();
ActorPictures = new ActorPictureList();
}
public Layer CurrentLayer
{
get
{
if (currentLayerIndex == -1) return null;
return (Layer)(Layers[currentLayerIndex]);
}
}
public Actor CurrentActor
{
get
{
if (Actors.Count == 0)
{
currentActorIndex = -1;
return null;
}
if (currentActorIndex == -1) return null;
return (Actor)(Actors[currentActorIndex]);
}
}
public int CurrentLayerIndex
{
get
{
return currentLayerIndex;
}
set
{
if (Layers.Count == 0) return;
currentLayerIndex = value;
if (value < 0) currentLayerIndex = 0;
if (value > Layers.Count-1) currentLayerIndex = Layers.Count-1;
selection.X = 0;
selection.Y = 0;
selection.Width = 0;
selection.Height = 0;
currentActorIndex = -1;
}
}
public int CurrentActorIndex
{
get
return currentActorIndex;
}
set
{
if (Actors.Count == 0) return;
currentActorIndex = value;
if (value < 0) currentActorIndex = 0;
if (value > Actors.Count-1) currentActorIndex = Actors.Count-1;
currentLayerIndex = -1;
}
}
public void SelectRange(Rectangle clickRect)
{
if (currentLayerIndex == -1) return;
selection.X = Convert.ToInt32(System.Math.Floor((clickRect.X-(int)CurrentLayer.XOffset) /
CurrentLayer.CellWidth));
selection.Y = Convert.ToInt32(System.Math.Floor((clickRect.Y-(int)CurrentLayer.YOffset)/
CurrentLayer.CellHeight));
selection.Width = (clickRect.Right-(int)CurrentLayer.XOffset)/CurrentLayer.CellWidth -
selection.X;
selection.Height = (clickRect.Bottom-(int)CurrentLayer.YOffset)/CurrentLayer.CellHeight -
selection.Y;
if (selection.X < 0) selection.X = 0;
if (selection.Y < 0) selection.Y = 0;
if (selection.X > CurrentLayer.ColumnCount-1) selection.X = CurrentLayer.ColumnCount-1;
if (selection.Y > CurrentLayer.RowCount-1) selection.Y = CurrentLayer.RowCount-1;
if (selection.Right < 0) selection.Width = - selection.X;
if (selection.Bottom < 0) selection.Height = - selection.Y;
if (selection.Right > CurrentLayer.ColumnCount-1) selection.Width = CurrentLayer.ColumnCount-1 -
selection.X;
if (selection.Bottom > CurrentLayer.RowCount-1) selection.Height = CurrentLayer.RowCount-1 -
selection.Y;
}
public bool PickActor(Point clickPoint)
{
if (currentActorIndex == -1) return false;
for (int i = 0; i < Actors.Count; i++)
{
ActorPictures.CurrentIndex = i;
Bitmap Bmp = ActorPictures.GetCurrentPicture();
Rectangle ActorRect = new Rectangle(Convert.ToInt32(((Actor)Actors[i]).X),
Convert.ToInt32(((Actor)Actors[i]).Y), Bmp.Width, Bmp.Height);
if (ActorRect.Contains(clickPoint.X, clickPoint.Y))
{
currentActorIndex = i;
return true;
}
}
return false;
}
public void InsertElement(int elementIndex)
{
if (CurrentLayerIndex != -1)
{
int elementValue;
if (CurrentLayer.TypeOfLayer == LayerType.Visual)
{
VisualLayerSprites.CurrentIndex = elementIndex;
elementValue = VisualLayerSprites.CurrentSpriteIndex;
}
else
{
NonVisualLayerSymbols.CurrentIndex = elementIndex;
elementValue = NonVisualLayerSymbols.CurrentSymbolIndex;
}
CurrentLayer.SetRange(selection.Top, selection.Bottom, selection.Left, selection.Right,
elementValue);
}
}
public void DeleteElement()
{
if (CurrentLayerIndex != -1)
{
CurrentLayer.SetRange(selection.Top, selection.Bottom, selection.Left, selection.Right, 0);
}
}
private void DeleteInvalidElements()
{
bool[] UsedIndexes = new bool[Int16.MaxValue + 1];
UsedIndexes[1] = true;
for (int i = 1; i < UsedIndexes.Length; i++)
UsedIndexes[i] = false;
for (int i = 0; i < VisualLayerSprites.Count; i++)
{
VisualLayerSprites.CurrentIndex = i;
UsedIndexes[VisualLayerSprites.CurrentSpriteIndex] = true;
}
for (int k = 0; k < Layers.Count; k++)
{
CurrentLayerIndex = k;
if (CurrentLayer.TypeOfLayer == LayerType.Visual)
for (int i = 0; i < CurrentLayer.RowCount; i++)
for (int j = 0; j < CurrentLayer.ColumnCount; j++)
if (!UsedIndexes[CurrentLayer.GetElement(i, j)])
CurrentLayer.SetElement(i, j, 0);
}
for (int i = 1; i < UsedIndexes.Length; i++)
UsedIndexes[i] = false;
for (int i = 0; i < NonVisualLayerSymbols.Count; i++)
{
NonVisualLayerSymbols.CurrentIndex = i;
UsedIndexes[NonVisualLayerSymbols.CurrentSymbolIndex] = true;
}
for (int k = 0; k < Layers.Count; k++)
{
CurrentLayerIndex = k;
if (CurrentLayer.TypeOfLayer == LayerType.NonVisual)
for (int i = 0; i < CurrentLayer.RowCount; i++)
for (int j = 0; j < CurrentLayer.ColumnCount; j++)
if (!UsedIndexes[CurrentLayer.GetElement(i, j)])
CurrentLayer.SetElement(i, j, 0);
}

for (int i = 0; i < UsedIndexes.Length; i++)
UsedIndexes[i] = false;
for (int i = 0; i < ActorPictures.Count; i++)
{
ActorPictures.CurrentIndex = i;
UsedIndexes[ActorPictures.CurrentActorCode] = true;
}
for (int i = 0; i < Actors.Count; i++)
if (!UsedIndexes[((Actor)Actors[i]).ActorCode])
{
Actors.RemoveAt(i);
i--;
}
}
public void ChangeActors(byte actorCode1, byte actorCode2)
{
for (int i = 0; i < Actors.Count; i++)
if (((Actor)Actors[i]).ActorCode == actorCode1)
((Actor)Actors[i]).ActorCode = actorCode2;
}
public void CheckModifications()
{
if ((VisualLayerSprites.GetModificationType() == ModificationType.Deletion)||
(NonVisualLayerSymbols.GetModificationType() == ModificationType.Deletion)||
(ActorPictures.GetModificationType() == ModificationType.Deletion))
DeleteInvalidElements();
if (VisualLayerSprites.GetModificationType() == ModificationType.CodeCorrection)
{
VisualLayerSprites.CurrentIndex = VisualLayerSprites.GetCorrectedIndex();
CurrentLayer.ChangeElements(VisualLayerSprites.GetCorrectedCode(),
VisualLayerSprites.CurrentSpriteIndex);
}
if (NonVisualLayerSymbols.GetModificationType() == ModificationType.CodeCorrection)
{
NonVisualLayerSymbols.CurrentIndex = NonVisualLayerSymbols.GetCorrectedIndex();
CurrentLayer.ChangeElements(NonVisualLayerSymbols.GetCorrectedCode(),
NonVisualLayerSymbols.CurrentSymbolIndex);
}
if (ActorPictures.GetModificationType() == ModificationType.CodeCorrection)
{
ActorPictures.CurrentIndex = ActorPictures.GetCorrectedIndex();
ChangeActors(Convert.ToByte(ActorPictures.GetCorrectedCode()),
ActorPictures.CurrentActorCode);
}
}
public bool ActorsAreVisible
{
get
{
return actorsVisible;
}
set
{
if (actorsVisible != value) modified = true;
actorsVisible = value;
}
}
public bool ActorsAreLocked
{
get
{
return actorsLocked;
}
set
{
if (actorsLocked != value) modified = true;
actorsLocked = value;
}
}
public static void DrawSymbol(byte symbolIndex, Graphics graph, Rectangle rect)
{
graph.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
Color color = Color.White;
switch (symbolIndex%4)
{
case 0: color = Color.FromArgb(255, (symbolIndex*4)%256, (symbolIndex*4)%256,
(symbolIndex*4)%256);
break;
case 1: color = Color.FromArgb(255, 127, (symbolIndex*4)%256,
(symbolIndex*4)%256);
break;
case 2: color = Color.FromArgb(255, (symbolIndex*4)%256, 127,
(symbolIndex*4)%256);
break;
case 3: color = Color.FromArgb(255, (symbolIndex*4)%256, (symbolIndex*4)%256,
127);
break;
}
switch (symbolIndex/32)
{
case 0: graph.FillRectangle(new SolidBrush(color), rect.X+rect.Width/4, rect.Y+rect.Height/4,
rect.Width/2, rect.Height/2);
break;
case 1: graph.FillRectangle(new SolidBrush(color), rect.X+2, rect.Y+2,
rect.Width-4, rect.Height-4);
break;
case 2: graph.FillEllipse(new SolidBrush(color), rect.X+rect.Width/4, rect.Y+rect.Height/4,
rect.Width/2, rect.Height/2);
break;
case 3: graph.FillEllipse(new SolidBrush(color), rect.X+2, rect.Y+2,
rect.Width-4, rect.Height-4);
break;
case 4:
Point[] pts1 = { new Point(rect.X+2, rect.Y+rect.Height-2),
new Point(rect.X+rect.Width-2, rect.Y+rect.Height-2),
new Point(rect.X+rect.Width/2, rect.Y+2) };
graph.FillPolygon(new SolidBrush(color), pts1);
break;
case 5:
Point[] pts2 = { new Point(rect.X+2, rect.Y+2),
new Point(rect.X+rect.Width-2, rect.Y+2),
new Point(rect.X+rect.Width/2, rect.Y+rect.Height-2) };
graph.FillPolygon(new SolidBrush(color), pts2);
break;
case 6:
graph.DrawRectangle(new Pen(color), rect.X+2, rect.Y+2,
rect.Width-4, rect.Height-4);
break;
case 7:
Pen pen = new Pen(color);
graph.DrawRectangle(pen, rect.X+2, rect.Y+2, rect.Width-4, rect.Height-4);
graph.DrawLine(pen, rect.X+2, rect.Y+2,
rect.X+rect.Width-2, rect.Y+rect.Height-2);
graph.DrawLine(pen, rect.X+rect.Width-2, rect.Y+2, rect.X+2, rect.Y+rect.Height-2);
break;
}
graph.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.Default;
}
public void Draw(Graphics graph, int xOffset, int yOffset)
{
graph.Clear(Color.Black);
for (int k = Layers.Count-1; k >= 0; k--)
if (((Layer)Layers[k]).IsVisible)
if (((Layer)Layers[k]).TypeOfLayer == LayerType.Visual)
{
int width = ((Layer)Layers[k]).CellWidth;
int height = ((Layer)Layers[k]).CellHeight;
int xOfs = Convert.ToInt32(((Layer)Layers[k]).XOffset) + xOffset;
int yOfs = Convert.ToInt32(((Layer)Layers[k]).YOffset) + yOffset;
for (int i = 0; i < ((Layer)Layers[k]).RowCount; i++)
for (int j = 0; j < ((Layer)Layers[k]).ColumnCount; j++)
{
Bitmap bmp = VisualLayerSprites.GetSprite(((Layer)Layers[k]).GetElement(i, j));
if (bmp == null) continue;
graph.DrawImageUnscaled(bmp, xOfs + j*width, yOfs + i*height);
}
if (currentLayerIndex == k)
for (int i = Math.Min(selection.Top, selection.Bottom);
i <= Math.Max(selection.Top, selection.Bottom); i++)
for (int j = Math.Min(selection.Left, selection.Right);
j <= Math.Max(selection.Left, selection.Right); j++)
graph.FillRectangle(new SolidBrush(Color.FromArgb(127, Color.Blue)),
xOfs + j*width, yOfs + i*height, width, height);
}
else
{
int width = ((Layer)Layers[k]).CellWidth;
int height = ((Layer)Layers[k]).CellHeight;
int xOfs = Convert.ToInt32(((Layer)Layers[k]).XOffset) + xOffset;
int yOfs = Convert.ToInt32(((Layer)Layers[k]).YOffset) + yOffset;
for (int i = 0; i < ((Layer)Layers[k]).RowCount; i++)
for (int j = 0; j < ((Layer)Layers[k]).ColumnCount; j++)
{
if (((Layer)Layers[k]).GetElement(i, j) == 0) continue;
Rectangle rect = new Rectangle(xOfs + j*width, yOfs + i*height, width, height);
DrawSymbol(Convert.ToByte(((Layer)Layers[k]).GetElement(i, j)), graph, rect);
}
if (currentLayerIndex == k)
for (int i = Math.Min(selection.Top,
i <= Math.Max(selection.Top, selection.Bottom); i++)
for (int j = Math.Min(selection.Left, selection.Right);
j <= Math.Max(selection.Left, selection.Right); j++)
graph.FillRectangle(new SolidBrush(Color.FromArgb(127, Color.Green)),
xOfs + j*width, yOfs + i*height, width, height);
}
if (!ActorsAreVisible) return;
for (int i = 0; i < Actors.Count; i++)
{
graph.DrawImageUnscaled(ActorPictures.GetPicture(Convert.ToByte(((Actor)Actors[i]).ActorCode)),
Convert.ToInt32(((Actor)Actors[i]).X) + xOffset, Convert.ToInt32(((Actor)Actors[i]).Y) + yOffset);
if (i == currentActorIndex)
graph.FillRectangle(new SolidBrush(Color.FromArgb(127, Color.Red)),
Convert.ToInt32(((Actor)Actors[i]).X) + xOffset, Convert.ToInt32(((Actor)Actors[i]).Y) + yOffset,
ActorPictures.GetPicture(Convert.ToByte(((Actor)Actors[i]).ActorCode)).Width,
ActorPictures.GetPicture(Convert.ToByte(((Actor)Actors[i]).ActorCode)).Height);
}
}
public int GetMaxX()
{
int maxX = 0;
for (int i = Layers.Count-1; i >= 0; i--)
{
int width = ((Layer)Layers[i]).CellWidth;
int xOfs = Convert.ToInt32(((Layer)Layers[i]).XOffset);
if (maxX < xOfs+((Layer)Layers[i]).ColumnCount*width-1)
maxX = xOfs+((Layer)Layers[i]).ColumnCount*width-1;
}
for (int i = 0; i < Actors.Count; i++)
if (maxX < Convert.ToInt32(((Actor)Actors[i]).X)+
ActorPictures.GetPicture(Convert.ToByte(((Actor)Actors[i]).ActorCode)).Width)
maxX = Convert.ToInt32(((Actor)Actors[i]).X)+
ActorPictures.GetPicture(Convert.ToByte(((Actor)Actors[i]).ActorCode)).Width;
return maxX;
}
public int GetMaxY()
{
int maxY = 0;
for (int i = Layers.Count-1; i >= 0; i--)
{
int height = ((Layer)Layers[i]).CellHeight;
int yOfs = Convert.ToInt32(((Layer)Layers[i]).YOffset);
if (maxY < yOfs+((Layer)Layers[i]).RowCount*height-1)
maxY = yOfs+((Layer)Layers[i]).RowCount*height-1;
}
for (int i = 0; i < Actors.Count; i++)
if (maxY < Convert.ToInt32(((Actor)Actors[i]).Y)+
ActorPictures.GetPicture(Convert.ToByte(((Actor)Actors[i]).ActorCode)).Height)
maxY = Convert.ToInt32(((Actor)Actors[i]).Y)+
ActorPictures.GetPicture(Convert.ToByte(((Actor)Actors[i]).ActorCode)).Height;
return maxY;
}
public void StretchDraw(Graphics graph, int screenWidth, int screenHeight)
{
float xScale = (float)screenWidth/GetMaxX();
float yScale = (float)screenHeight/GetMaxY();
graph.Clear(Color.Black);
for (int k = Layers.Count-1; k >= 0; k--)
if (((Layer)Layers[k]).IsVisible)
if (((Layer)Layers[k]).TypeOfLayer == LayerType.Visual)
{
float width = ((Layer)Layers[k]).CellWidth*xScale;
float height = ((Layer)Layers[k]).CellHeight*yScale;
float xOfs = ((Layer)Layers[k]).XOffset*xScale;
float yOfs = ((Layer)Layers[k]).YOffset*yScale;
for (int i = 0; i < ((Layer)Layers[k]).RowCount; i++)
for (int j = 0; j < ((Layer)Layers[k]).ColumnCount; j++)
{
Bitmap bmp = VisualLayerSprites.GetSprite(((Layer)Layers[k]).GetElement(i, j));
if (bmp == null) continue;
Rectangle rect = new Rectangle(Convert.ToInt32(xOfs + j*width), Convert.ToInt32(yOfs + i*height),
Convert.ToInt32(width), Convert.ToInt32(height));
graph.DrawImage(bmp, rect, 0, 0, bmp.Width, bmp.Height, GraphicsUnit.Pixel);
}
}
else
{
float width = ((Layer)Layers[k]).CellWidth*xScale;
float height = ((Layer)Layers[k]).CellHeight*yScale;
float xOfs = ((Layer)Layers[k]).XOffset*xScale;
float yOfs = ((Layer)Layers[k]).YOffset*yScale;
for (int i = 0; i < ((Layer)Layers[k]).RowCount; i++)
for (int j = 0; j < ((Layer)Layers[k]).ColumnCount; j++)
{
if (((Layer)Layers[k]).GetElement(i, j) == 0) continue;
Rectangle rect = new Rectangle(Convert.ToInt32(xOfs + j*width), Convert.ToInt32(yOfs + i*height),
Convert.ToInt32(width), Convert.ToInt32(height));
DrawSymbol(Convert.ToByte(((Layer)Layers[k]).GetElement(i, j)), graph, rect);
}
}
if (!ActorsAreVisible) return;
for (int i = 0; i < Actors.Count; i++)
{
Bitmap bmp = ActorPictures.GetPicture(Convert.ToByte(((Actor)Actors[i]).ActorCode));
Rectangle rect = new Rectangle(Convert.ToInt32(((Actor)Actors[i]).X*xScale),
Convert.ToInt32(((Actor)Actors[i]).Y*yScale), Convert.ToInt32(bmp.Width*xScale),
Convert.ToInt32(bmp.Height*yScale));
graph.DrawImage(bmp, rect, 0, 0, bmp.Width, bmp.Height, GraphicsUnit.Pixel);
}
}
public int GetSelectedObjX()
{
if (currentLayerIndex != -1) return selection.X;
if (currentActorIndex != -1) return Convert.ToInt32(CurrentActor.X);
return -1;
}
public int GetSelectedObjY()
{
if (currentLayerIndex != -1) return selection.Y;
if (currentActorIndex != -1) return Convert.ToInt32(CurrentActor.Y);
return -1;
}
public int GetSelectedObjCode()
{
if (currentLayerIndex != -1) return CurrentLayer.GetElement(selection.Y, selection.X);
if (currentActorIndex != -1) return CurrentActor.ActorCode;
return -1;
}
public void RemoveCurrentActor()
{
if (Actors.Count == 0) return;
Actors.RemoveAt(CurrentActorIndex);
if (Actors.Count == 0) currentActorIndex = -1;
}
public void Save(string directoryName)
{
string fileName = directoryName.Substring(directoryName.LastIndexOf('\\') + 1,
directoryName.Length - directoryName.LastIndexOf('\\') - 1) + ".map";
System.IO.StreamWriter SW = new System.IO.StreamWriter(directoryName + "\\" + fileName);
SW.WriteLine("[Visual layer sprites]");
SW.WriteLine(VisualLayerSprites.Count);
for (int i = 0; i <= VisualLayerSprites.Count - 1; i++)
{
VisualLayerSprites.CurrentIndex = i;
SW.WriteLine(VisualLayerSprites.CurrentAlphaBlend);
SW.WriteLine(VisualLayerSprites.CurrentAlphaBlendValue);
SW.WriteLine(VisualLayerSprites.CurrentCommentary);
VisualLayerSprites.GetCurrentSprite().Save(directoryName + '\\' +
VisualLayerSprites.CurrentCommentary, System.Drawing.Imaging.ImageFormat.Bmp);
SW.WriteLine(VisualLayerSprites.CurrentSpriteIndex);
SW.WriteLine(VisualLayerSprites.CurrentTransparent);
}
SW.WriteLine("[Non-visual layer symbols]");
SW.WriteLine(NonVisualLayerSymbols.Count);
for (int i = 0; i <= NonVisualLayerSymbols.Count - 1; i++)
{
NonVisualLayerSymbols.CurrentIndex = i;
SW.WriteLine(NonVisualLayerSymbols.CurrentCommentary);
SW.WriteLine(NonVisualLayerSymbols.CurrentSymbolIndex);
}
SW.WriteLine("[Actor pictures]");
SW.WriteLine(ActorPictures.Count);
for (int i = 0; i <= ActorPictures.Count - 1; i++)
{
ActorPictures.CurrentIndex = i;
SW.WriteLine(ActorPictures.CurrentActorCode);
SW.WriteLine(ActorPictures.CurrentCommentary);
for (int j = 0; j <= 255; j++)
SW.WriteLine(ActorPictures.GetDirective(ActorPictures.CurrentActorCode, Convert.ToByte(j)));
ActorPictures.GetCurrentPicture().Save(directoryName + '\\' +
ActorPictures.CurrentCommentary, System.Drawing.Imaging.ImageFormat.Bmp);
}
SW.WriteLine("[Layers]");
SW.WriteLine(Layers.Count);
for (int k = 0; k <= Layers.Count - 1; k++)
{
CurrentLayerIndex = k;
SW.WriteLine(CurrentLayer.CellHeight);
SW.WriteLine(CurrentLayer.CellWidth);
SW.WriteLine(CurrentLayer.ColumnCount);
SW.WriteLine(CurrentLayer.Commentary);
SW.WriteLine(CurrentLayer.Depth);
SW.WriteLine(CurrentLayer.RowCount);
SW.WriteLine(CurrentLayer.Title);
SW.WriteLine(CurrentLayer.TypeOfLayer);
SW.WriteLine(CurrentLayer.XOffset);
SW.WriteLine(CurrentLayer.YOffset);
for (int i = 0; i <= CurrentLayer.RowCount - 1; i++)
for (int j = 0; j <= CurrentLayer.ColumnCount - 1; j++)
SW.WriteLine(CurrentLayer.GetElement(i, j));
}
SW.WriteLine("[Actors]");
SW.WriteLine(Actors.Count);
for (int i = 0; i <= Actors.Count - 1; i++)
{
CurrentActorIndex = i;
SW.WriteLine(CurrentActor.ActorCode);
SW.WriteLine(CurrentActor.DirectiveCode);
SW.WriteLine(CurrentActor.X);
SW.WriteLine(CurrentActor.Y);
}
SW.Close();
}

public void Clear()
{
Layers.Clear();
int count = VisualLayerSprites.Count;
for (int i = 0; i <= count - 1; i++)
{
VisualLayerSprites.CurrentIndex = 0;
VisualLayerSprites.DeleteCurrent();
}
count = NonVisualLayerSymbols.Count;
for (int i = 0; i <= count - 1; i++)
{
NonVisualLayerSymbols.CurrentIndex = 0;
NonVisualLayerSymbols.DeleteCurrent();
}
count = Actors.Count;
for (int i = 0; i <= count - 1; i++)
{
CurrentActorIndex = 0;
RemoveCurrentActor();
}
count = ActorPictures.Count;
for (int i = 0; i <= count - 1; i++)
{
ActorPictures.CurrentIndex = 0;
ActorPictures.DeleteCurrent();

public void Load(string directoryName)
{
string fileName = directoryName.Substring(directoryName.LastIndexOf('\\') + 1,
directoryName.Length - directoryName.LastIndexOf('\\') - 1) + ".map";
System.IO.StreamReader SR = new System.IO.StreamReader(directoryName + "\\" + fileName);
SR.ReadLine();
int count = Convert.ToInt32(SR.ReadLine());
for (int i = 0; i <= count - 1; i++)
{
bool alphaBlend = Convert.ToBoolean(SR.ReadLine());
float alphaBlendValue = Convert.ToSingle(SR.ReadLine());
string commentary = SR.ReadLine();
string filename = directoryName + '\\' + commentary;
int spriteIndex = Convert.ToInt32(SR.ReadLine());
bool transparent = Convert.ToBoolean(SR.ReadLine());
VisualLayerSprites.AddSprite(spriteIndex, commentary, filename,
transparent, alphaBlend, alphaBlendValue);
}
SR.ReadLine();
count = Convert.ToInt32(SR.ReadLine());
for (int i = 0; i <= count - 1; i++)
{
string commentary = Convert.ToString(SR.ReadLine());
byte symbolIndex = Convert.ToByte(SR.ReadLine());
NonVisualLayerSymbols.AddSymbol(symbolIndex, commentary);
}
SR.ReadLine();
count = Convert.ToInt32(SR.ReadLine());
for (int i = 0; i <= count - 1; i++)
{
byte actorCode = Convert.ToByte(SR.ReadLine());
string commentary = SR.ReadLine();
string[] directives = new string[byte.MaxValue+1];
for (int j = 0; j <= 255; j++)
directives[j] = SR.ReadLine();
string filename = directoryName + '\\' + commentary;
ActorPictures.AddPicture(actorCode, commentary, filename);
ActorPictures.CurrentIndex = i;
for (int j = 0; j <= 255; j++)
ActorPictures.SetCurrentDirective(directives[j], Convert.ToByte(j));
}
SR.ReadLine();
count = Convert.ToInt32(SR.ReadLine());
for (int k = 0; k <= count - 1; k++)
{
int cellHeight = Convert.ToInt32(SR.ReadLine());
int cellWidth = Convert.ToInt32(SR.ReadLine());
int matrixColumnCount = Convert.ToInt32(SR.ReadLine());
string aCommentary = SR.ReadLine();
float aDepth = Convert.ToSingle(SR.ReadLine());
int matrixRowCount = Convert.ToInt32(SR.ReadLine());
string aTitle = SR.ReadLine();
LayerType typeOfLayer;
if (SR.ReadLine() == "Visual")
typeOfLayer = LayerType.Visual;
else
float xOffset = Convert.ToSingle(SR.ReadLine());
float yOffset = Convert.ToSingle(SR.ReadLine());
Layers.Add(new Layer(aTitle, aCommentary, typeOfLayer, aDepth, cellWidth,
cellHeight, matrixRowCount, matrixColumnCount));
CurrentLayerIndex = k;
CurrentLayer.XOffset = xOffset;
CurrentLayer.YOffset = yOffset;
for (int i = 0; i <= CurrentLayer.RowCount - 1; i++)
for (int j = 0; j <= CurrentLayer.ColumnCount - 1; j++)
CurrentLayer.SetElement(i, j, Convert.ToInt32(SR.ReadLine()));
}
SR.ReadLine();
count = Convert.ToInt32(SR.ReadLine());
for (int i = 0; i <= count - 1; i++)
{
int codeOfActor = Convert.ToInt32(SR.ReadLine());
byte codeOfDirective = Convert.ToByte(SR.ReadLine());
float xPos = Convert.ToSingle(SR.ReadLine());
float yPos = Convert.ToSingle(SR.ReadLine());
Actors.Add(new Actor(xPos, yPos, codeOfActor, codeOfDirective));
}
SR.Close();
}
}
}
using System;
namespace ClassesForEditor
{
public enum LayerType { Visual, NonVisual }
/// <summary>
/// Summary description for Layer.
/// </summary>
public class Layer
{
private string title;
private string commentary;
private bool visible;
private bool locked;
private LayerType layerType;
private float depth;
private int cellW;
private int cellH;
private DynamicArray2D matrix;
private float xOfs;
private float yOfs;
private bool modified;
public Layer(string aTitle, string aCommentary, LayerType typeOfLayer, float aDepth,
int cellWidth, int cellHeight, int matrixRowCount, int matrixColumnCount)
{
title = string.Copy(aTitle);
commentary = string.Copy(aCommentary);
layerType = typeOfLayer;
depth = aDepth;
cellW = cellWidth;
cellH = cellHeight;
matrix = new DynamicArray2D(matrixRowCount, matrixColumnCount);
visible = true;
locked = false;
xOfs = 0;
yOfs = 0;
Clear();
}
public Layer(LayerType typeOfLayer)
{
title = "Untitled layer";
commentary = "No commentary";
layerType = typeOfLayer;
depth = 1;
cellW = 10;
cellH = 10;
matrix = new DynamicArray2D(30, 30);
visible = true;
locked = false;
xOfs = 0;
yOfs = 0;
Clear();
}
public Layer()
{
title = "Untitled layer";
commentary = "No commentary";
layerType = LayerType.Visual;
depth = 1;
cellW = 10;
cellH = 10;
matrix = new DynamicArray2D(30, 30);
visible = true;
locked = false;
xOfs = 0;
yOfs = 0;
Clear();
}
public string Title
{
get
{
return title;
}
set
{
if (visible && !locked)
{
title = string.Copy(value);
modified = true;
}
}
}
public string Commentary
{
get
{
return commentary;
}
set
{
if (visible && !locked)
{
commentary = string.Copy(value);
modified = true;
}
}
}
public bool IsVisible
{
get
{
return visible;
}
set
{
if (visible != value) modified = true;
visible = value;
}
}
public bool IsLocked
{
get
{
return locked;
}
set
{
if (locked != value) modified = true;
locked = value;
}
}
public LayerType TypeOfLayer
{
get
{
return layerType;
}
}
public float Depth
{
get
{
return depth;
}
set
{
if (visible && !locked)
{
depth = value;
modified = true;
}
}
}
public int CellWidth
{
get
{
return cellW;
for (int i=row1; i<=row2; i++)
for (int j=column1; j<=column2; j++)
SetElement(i, j, elementValue[Rnd.Next(elementValue.Length)]);
modified = true;
}
public void ChangeElements(int elementValue1, int elementValue2)
{
for (int i = 0; i < matrix.RowCount; i++)
for (int j = 0; j < matrix.ColumnCount; j++)
if ((int)matrix.GetElement(i, j) == elementValue1)
matrix.SetElement(i, j, elementValue2);
}
public void ExchangeElements(int elementValue1, int elementValue2)
{
System.Collections.ArrayList List1 = new System.Collections.ArrayList();
System.Collections.ArrayList List2 = new System.Collections.ArrayList();
for (int i = 0; i < matrix.RowCount; i++)
for (int j = 0; j < matrix.ColumnCount; j++)
{
if ((int)matrix.GetElement(i, j) == elementValue1)
{
List1.Add(i);
List1.Add(j);
}
if ((int)matrix.GetElement(i, j) == elementValue2)
{
List2.Add(i);
List2.Add(j);
}
}
for (int i = 0; i < List1.Count; i+=2)
matrix.SetElement((int)(List1[i]), (int)(List1[i+1]), elementValue2);
for (int i = 0; i < List2.Count; i+=2)
matrix.SetElement((int)(List2[i]), (int)(List2[i+1]), elementValue1);
}
}
}