. Продукты: продукты зависят от свободного места на складе, поэтому сначала обнаруживаем все здания с готовыми продуктами, затем выясняем, хватает ли для них всех места на складе. В случае нехватки требуется отсортировать все продукты по стоимости продажи, выбрать самые дорогие, посчитать их количество. В случае если места на складе достаточно для всех ресурсов, также считаем их количество. Далее полученное число сравнивается с заданным условием.
. Население: население зависит от ограничения уровня, поэтому сначала обнаруживаем все здания с готовыми ресурсами, затем выясняем, хватает ли для них всех места. В случае нехватки последовательно просматриваются, считаются здания, выясняется количество получаемого с них населения и так до момента, пока население не превысит ограничение. В случае если места достаточно для всех ресурсов, также считаем их количество. Далее полученное число сравнивается с заданным условием.
Если все три компонента выполняют условие, на панели появляется кнопка запуска окна. Кнопка пульсирует, привлекая внимание игрока. Анимация реализована с использование игрового движка Cocos2d-iphone.
Появление кнопки в правом нижнем углу отображено на рисунке 6:
Рис. 6. Отображение кнопки запуска окна
Второй задачей является отрисовка содержимого таблицы в окне. Она состоит из двух частей: организация ячейки, то есть компоновка одинаковых ресурсов, и работа с функционалом таблицы.
Ячейка состоит из изображения ресурса и его количества. Требуется отсортировать все продукты по типу, сгруппировать их, таким образом определяя, каково их количество для каждого типа. Кроме того, пользуясь файловой системой проекта нужно подобрать соответствующее изображение. Для населения и денег всегда берется одна и та же картинка, а для продуктов информация о нужном изображении хранится в одной из переменных класса Resource.m. Так как данный способ создания ячейки может быть использован еще в каких-либо таблицах с наградой, специально был создан класс RewardNode.m.
После создания ячейки нужно организовать работу таблицы. Таблица создается на основе такого механизма как CCScrollView, поэтому чтобы получить доступ к некоторым методам этого класса, нужно наследовать основной класс CollectAll.m от скролла. CCScrollView класс принадлежит игровому движку cocos2d-iphone, который используется в приложении. Так как функционал данного механизма все еще находится в разработке и он является лишь контейнером для ячеек, для его правильной работы следует:
. Добавить кнопки для прокрутки в обе стороны. Используются такие
данные как размер страницы прокрутки, размер всего контейнера и размер одной
ячейки, чтобы вручную указать, насколько сдвинется страница прокрутки
относительно всего его контейнера. Пример метода для прокрутки влево:
- (void)actionScrollLeft:(id)sender
{* rewardContainerScroll = (CCScrollView*)[_buildernodeWithName:@"rewardContainerScroll"];cellWidth = rewardContainerScroll.pageSize.width;
CGPoint pt = rewardContainerScroll.contentOffset;.x += cellWidth;.x = roundf(pt.x / (float)cellWidth);(pt.x > 0)
{.x = 0;
}.x *= cellWidth;
[rewardContainerScroll setContentOffset:pt animated:YES];
}
. Установить размер контейнера, для того чтобы при пролистывании ячейки оказывались в границах предоставленной области
. Указать, как будет вести себя прокрутка при клике не по кнопкам, а при перетягивании из любой точки
. Отцентровать положение ячеек в контейнере, если их количество меньше, чем вмещается на все поле прокрутки
В результате получаем контейнер CCScrollView с ячейками, изображенный на рисунке 7:
Рис. 7. Таблица с прокруткой
А также окно:
Рис. 8. Окно модуля «Собрать все»
После визуальной части компонента происходит работа над третьей задачей: алгоритмом сбора ресурсов. После нажатия на кнопку сбора все ресурсы должны правильно изменить систему атрибутов приложения и запустить ряд анимаций.
. Деньги: происходит добавление полученного количества к атрибуту money из класса GameStat.m, а также метка над зданием изменяется с изображения готового ресурса на картинку с изготавливающимся продуктом.
. Продукты: все собранные продукты кладутся на склад, причем одинаковые ресурсы складываются группами по 20 штук, метка над зданием меняется на метку свободного здания. Кроме того, запускается анимация машинки, которая отвозит ресурс на склад. Над машинкой указывается, какой ресурс она везет и его количество.
. Население: происходит добавление полученного количества к атрибуту population. Метка над зданием меняется на метку изготовления. Кроме того, учитывается такой вариант сбора, когда с определенного здания собирается лишь часть населения, поэтому в метке над зданием остается висеть еще n населения.
После нажатия кнопки сбора должно измениться значение денежного атрибута через класс GameStat.m, а также увеличиться счетчик сбора всех ресурсов за раз, так как количество использований данного механизма сбора влияет на получение пользователем игрового достижения, за которое дается награда. Кроме того, при сборе ресурса с одиночного здания запускается звуковое сопровождение и на карте появляется изображение полученных денег и опыта, но при сборе множества продуктов такое количество анимации и работы с графикой может замедлить работу приложения, поэтому нужно изменить алгоритм появления анимаций и звуков. Данные алгоритмы являются частью игрового менеджера - DropItemManager.m, отвечающего за получение ресурсов, появления их на карте.
Классы, используемые в работе компонента:
Класс, контролирующий появление кнопки на игровой панели и ее анимацию.
|
MainPanel |
|
- onShowCollectAllButton |
|
- actionCollectAll |
|
- pulseButton |
|
Game |
|
- collectAllWith: |
|
- canCollectAllWith: |
|
- canCollectBuilding: |
|
- collectBuilding: |
Реализация некоторых методов класса Game:
- (BOOL)canCollectBuilding:(Building *)building alert:(BOOL)alert
{
NSString *type = building.typeName;
int value = [building inCountToCollect];([type isEqualToString:@"house"])
{
value -= building.dropedValue;
int population = [_gameStat valueForAttribute:ATTR_POPULATION];
int dropedPopulation = [self.map.dropItemManager totalValueOfItemsWithType:DROP_ITEM_ATTRIBUTE name:@"population"];
int happiness = [_gameStat valueForAttribute:ATTR_HAPPINESS];
int v = (population + value + dropedPopulation) - happiness;(((v > 0) && (v == value)) || (value - v) < 0 || ![_gameStat canAddValue:(value - abs(v)) forAttribute:ATTR_POPULATION])
{
if (alert)
{
[Game showNotEnoughHapinessAlertForBuilding:building];
}
return NO;
}
}
else if ([type isEqualToString:@"entertainment"])
{
if (![_gameStat canAddValue:value forAttribute:ATTR_MONEY1])
{
if (alert)
{
[Game showCantCompleteAlert];
}
return NO;
}
}YES;
}
(void)collectBuilding:(Building*)building
{
NSString *type = building.typeName;*attribute = nil;([type isEqualToString:BUILDING_TYPE_HOUSE])
{
attribute = ATTR_POPULATION;
}
else
{
attribute = ATTR_MONEY1;
[[Counters instance] updateWithType:TYPE_COLLECT_TIMES subType:@"money1" addValue:1];
}(attribute != nil)
{
NSMutableArray *dropItems = [NSMutableArray arrayWithCapacity:3];value = [building inCountToCollect];
if ([[building typeName] isEqualToString:BUILDING_TYPE_HOUSE])
{
value -= building.dropedValue;
int population = [_gameStat valueForAttribute:ATTR_POPULATION];
int happiness = [_gameStat valueForAttribute:ATTR_HAPPINESS];
int dropedPopulation = [self.map.dropItemManager totalValueOfItemsWithType:DROP_ITEM_ATTRIBUTE name:@"population"];
int v = - happiness + (population + value + dropedPopulation);(v > 0)
{
value -= v;
building.dropedValue += value;
}
else
building.dropedValue = 0;
}([_gameStat canAddValue:value forAttribute:attribute])
{
DropItem *item = [[[DropItem alloc] initWithMap:building.map attribute:attribute count:value] autorelease];
[dropItems addObject:item];
}exp = [building valueForParam:@"in_exp"];
if (exp != 0)
{
DropItem *xpItem = [[[DropItem alloc] initWithMap:building.map attribute:ATTR_EXP count:exp] autorelease];
[dropItems addObject:xpItem];*bonus = [Bonus bonusByName:@"bonus_collect"];
[dropItems addObjectsFromArray:[bonus apply]];([Game game].collectAllMode) // do not show drop when CollectAll
[building.map.dropItemManager applyInvisibleDrops:dropItems];
[building.map.dropItemManager dropItems:dropItems atObject:building];
}
[building afterCollect];
[[Counters instance] addValue:1 forCounter:@"count_collect_times"];
}
Класс, отвечающий за выпадение на карту полученных ресурсов и их
автоматический сбор с карты. В данном случае процесс происходит в режиме
silent, то есть без звука и визуальной отображения.
|
DropItemManager |
|
- dropItems: |
|
- collectItem: |
|
- applyInvisibleDrop: |
Синглтон-класс, отвечающий за получение данных о текущем состоянии
какого-либо атрибута и его изменение.
|
GameStat |
|
- setValue: |
|
- canAddValue: |
|
- valueForAttribute: |
|
RewardNode |
|
- buildContentWithTouchPriority: |
|
- setIconWithName: |
|
- setLabelText: |
Класс, отвечающий за создание ячейки в таблице.
|
CollectAll |
Выполняемое действие |
|
|
- buildProfitNode |
Организация окна, таблицы. Запуск алгоритма сбора. |
|
|
- countItemsForProfit |
|
|
|
- onCollectAll |
|
|
|
- xpForContract: |
Подсчет выходных атрибутов при сборе: опыт, население, продукты. Суммирование элементов для отрисовки в ячейке таблицы. |
|
|
- xpForAllContracts: |
|
|
|
- peopleForHouse: |
|
|
|
- peopleForAllHouses: |
|
|
|
- sumContractsFor: |
|
|
|
- actionScrollLeft: |
Работа с прокруткой. |
|
|
- actionScrollRight: |
|
|
|
- updateButtons |
|
|
|
- compareContractsCost: |
Сортировка ресурсов по цене. |
Проверка условий для появления кнопки запуска окна. |
|
+ resultFactoryBuildings |
|
|
|
+ canCollectHouse: |
Проверка возможности сбора. |
Реализация некоторых методов класса CollectAll:
NSInteger compareContractsCost(Building* building1, Building* building2, void *context){*contract1 = ((FactoryBuilding*)building1).contractInProgress;
Contract *contract2 = ((FactoryBuilding*)building2).contractInProgress;*res1 = [Resource resourceByCode:[contract1 outResource]];
Resource *res2 = [Resource resourceByCode:[contract2 outResource]];(res1.sellMoney1 != res2.sellMoney1)
{
return -(res1.sellMoney1 - res2.sellMoney1);
}
return 0;
}
(int)xpForContract:(FactoryBuilding*)factoryBuilding
{
Contract *contract = factoryBuilding.contractInProgress;
return contract.outXP;
}
(int)xpForAllContracts:(NSArray*)buildingsWithContracts
{
int allXp = 0;(int i = 0; i < buildingsWithContracts.count; i++)
{
allXp += [self xpForContract:(FactoryBuilding*)[buildingsWithContracts objectAtIndex:i]];
}allXp;
}
+ (NSArray*)resultFactoryBuildings
{
NSMutableArray *result = [NSMutableArray array];
NSMutableArray *buildingsWithReadyContract = [NSMutableArray array];
int freeWarehouseSpace = [[Game game].resourcesManager freeSpace];*map = [Game game].map;
for (Building *building in [map allBuildings]) {*type = building.typeName;([type isEqualToString:@"factory"])([(FactoryBuilding*)building canCollectContract])
{
[buildingsWithReadyContract addObject:building];
}(buildingsWithReadyContract.count > freeWarehouseSpace) // if there is no so much space in warehouse
{
[buildingsWithReadyContract sortUsingFunction:compareContractsCost context:nil];(int i = 0; i < freeWarehouseSpace; i++)
{
[result addObject:[buildingsWithReadyContract objectAtIndex:i]];
}
}
else
{
[result addObjectsFromArray:buildingsWithReadyContract];
}result; // buildings, which contracts can be added to warehouse
}
2.3 Описание тестов и оценка качества программного продукта
программный игровой компьютерный визуальный
Игровой продукт написан для планшетов Apple iPad, поэтому тестирование проводилось на всех устройствах этой линейки, так как каждый из них использует различную версию платформы iOS.
Требуется проверить несколько аспектов работы компонента:
. Производительность приложения во время работы компонента: основным показателем является такая характеристика как кадровая частота, или FPS(Frame Per Second) планшета. Под кадровой частотой понимается частота, генерируемая самой игрой в зависимости от ресурсов устройства и необходимости передачи движений разной интенсивности. Так как сбор сразу нескольких ресурсов запускает много анимаций, кадровая частота может уменьшаться. Во время тестирования FPS не опускался ниже среднего показателя 25, что является неплохим показателем.
. Работа алгоритма: правильность удовлетворения заданным условиям
появления кнопки, подсчета зданий, готовых для сбора ресурсов, изменения
игровых атрибутов. Компонент тестировался на различных наборах зданий с
ресурсами, ошибок не выявлено.
По итогам прохождения производственно-технологической практики я ознакомилась с платформой iOS, API Cocoa, языком программирования Objective-C и его парадигмами, а также с игровым движком Cocos2d-iphone. Кроме того, был проведен анализ экономической составляющей игры, совместная работа с отделом тестирования позволила выявить слабые места и ошибки приложения, и в итоге разработан компонент программного продукта, удовлетворяющий заявленным требованиям:
. Визуальная часть представлена окном с интуитивно понятным интерфейсом
. Механизм сбора функционирует корректно
. Производительность игры остается на высоком уровне
Использование данного программного компонента призвано упростить игровой процесс пользователя, организовать и собрать в одном месте множественную информацию о текущих доступных ресурсах, а также ускорить прогресс игрока.
Дальнейшая разработка может быть продолжена в следующих направлениях:
. Реализация возможности выбора, какие ресурсы собирать, а какие оставить в готовом состоянии
. Логгирование на сервер изменения денежного атрибута
. Оптимизация анимации с целью ее добавления без потери производительности
. Оптимизация кода для уменьшения используемого объема памяти
1. Хиллегаас, А. Objective-C. Программирование для iOs и MacOs. - М: Питер, 2010. - 304 с.
. Кочан, С. Изучаем iOS программирование. - М: Орейли, 2012. - 428 с.
. Иттерхейм, С. Изучаем создание игр с использованием Cocos2d. - М: Апресс, 2011 - 218 с.