Шайбу вбросим в iOS восемь
Прежде чем создавать казуальную игру для iOS, хорошо бы ответить на вопрос: — А зачем? Вариантов три: Срубить денег; Порадовать родственников; Хрен его знает, но мысль жжет организм изнутри.
Думаю, в ближайшие годы, правильный ответ — третий.А, не буду спорить и учить — расскажу, как я делаю приложения.Гуру разработки молча нажимают плюс и уходят в сторону. Остальные следуют за мной, чтобы вспомнить школу и настольный хоккей.И да, уникальность топика, что в каждом предложении слова начинаются разными буквами.В статье девять картинок и пол-минуты видео.
1. ИдеяОт неё зависит выбор инструментов. Мои идеи, как пользователи смартфонов — незамысловатые. Потому — никаких лишних инструментов для разработки, кроме библиотеки воспроизведения звуков. Ресурсы заимствую из сети. Мелодии, дизайн, изображения, примеры кода. Все уже создано до нас. Авторское право — как в хоккее или футболе. Допустим, Месси придумал новый финт. Вы можете его скопировать, не отчисляя автору денег.Ладно. В качестве примера игры я выбрал настольный хоккей.Очень личное 80-ые годы. На мех-мате эта забава была популярна. Мы играли в умывалке ФДС-6. Похвастаюсь звездным моментом — толпа народу соревновалась в ночь. Я зашел, дождался очереди, победил местного чемпиона 10–0 и скромно удалился. Если честно — у меня два брата и шестнадцать лет азартной практики.
2. Начало проекта Рисунок 2. В Xcode завожу новый проект.Кстати, у меня толстые пальцы. Значит, для игры подходит лишь iPad режим. Это существенно облегчает работу. Кроме того, запрещаю вращение экрана. Только портрет. Долой пейзажи, это хоккей, а не балет.Выбираю название Hockey 2015. Названия из мира спорта — прибыльны. Насчет денег, я, конечно, слукавил. Хочется, чтобы будущий опус принес удовлетворение как душевное, так и материальное. Я уже писал здесь о своей игре Biathlon 2014. Каждый год — почти тысяча долларов навара. Покупают в дни чемпионата мира, олимпиады, кубковых этапов. Кто? Норвежцы, германцы, чехи, французы, русские, итальянцы.Думаю, мой будущий хоккей ждет что-то похожее всего лишь из-за названия.
Выбираю иконку. Не так важно, как имя. Главное — чтобы самому нравилось.Рисунок 3. Кто угадает, что это за хоккеист, тому- приз 1 доллар.
В результате, получается заготовка будущей игры, которая отображает ярко-серый экран. Проект имеет два основных файла, которые я могу редактировать.
ViewController.xib ViewController.m А не буду, плохой тон. Хороший тон не полениться и создать другой класс вида ViewController. Например, с именем PlayViewController.Рисунок 4. Добавляю новый контроллер PlayViewController.
Проект имеет два новых файла, которые я всегда могу редактировать.
PlayViewController.xib PlayViewController.m 3. Статичные изображения Неподвижные изображения размещаю с помощью редактора в файле PlayViewController.xib.Вначале их надо создать, украсть, позаимствовать. Фотографии хоккейного поля, игроков, вратарей мне прислал Milfgard. В Мосигре их есть. За что я без спросу разместил именной лейбл в центре поля. Шайба — из сети. Фотографии лиц хоккеистов — nhl.com. Кнопки — от дизайнеров Зептолаб. Хоккейные звуки — из приложения Ice Rage.Теперь можно разместить изображения в редакторе xib-файлов. Это просто — все картинки отображается элементом UIImageView.Рисунок 5. Хоккейное поле и табло в редакторе Xcode.
Если в дальнейшем потребуется анимация статично заданных элементов — не беда. Каждому элементу можно присвоить имя. В этом случае элемент можно программно двигать, гасить, трансформировать, что угодно.
Пример, объявляем элемент scoreBoard (черное табло на картинке) в файле PlayViewController.m
IBOutlet UIImageView *scoreBoard; Ключевое слово IBOutlet означает, что в редакторе XIB любому элементу можно присвоить идентификатор scoreBoard.Присваиваю мышкой в редакторе.Теперь я могу сдвинуть картинку глубоко наверх, чтобы оно не загораживало хоккейное поле.
scoreBoard.center = CGPointMake (384, -1000); Напоминаю, 384 — центр экрана iPad по ширине, -1000 — что-то вне устройства. После исполнения команды табло улетит вверх за границы экрана.Если требуется двигать группу картинок — их надо объединить. Завести элемент типа UIView, чтобы переместить в него группу картинок и надписей.Рисунок 6. Итак, я освободил поле от лишних элементов.
Теперь разместим здесь хоккеистов, чтобы научить их двигаться.
4. Трансформируемые изображения Размещаем одного хоккеиста программно в файле PlayViewController.m UIImageView *player = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@«player_1.png»]]; float rPlayer = 150.0; player.frame = CGRectMake (0, 0, rPlayer, rPlayer); player.center = CGPointMake (100, 500); [self.view addSubview: player]; Рисунок 7. На корте появился хоккеист.
В дальнейшем я его буду перемещать и вращать. Вот так.
player.center = CGPointMake (xnew, ynew); // перемещаю в точку (xnew=100, ynew=440) player.transform = CGAffineTransformMakeRotation (alpha); // кручу хоккеиста на угол (alpha=2.0) в радианах Рисунок 8. Хоккеист уехал в другое место и развернулся.
Хоккеистов много, а я один. Размещаем все 12 игроков с помощью массива.
NSMutableArray *players;
players = [[NSMutableArray alloc] init]; shadows = [[NSMutableArray alloc] init]; for (int k=0; k<12; k++) { UIImageView *p = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"player_1"]]; p.frame = CGRectMake(0, 0, cellDx, cellDx); float x = xp[k]; float y = yp[k]; p.center = CGPointMake(x, y); p.transform = CGAffineTransformMakeRotation(ap[k]); [players addObject:p]; p = [players objectAtIndex:k]; // так можем получить доступ к любому из 12 хоккеистов [self.view addSubview:p]; } 5. Численные методы в хоккее Хоккей, как жизнь, зависит от времени.Необходимо завести счетчик временем. Событие, которое программа будет вызывать 3000 раз за минуту. NSTimer *pauseTimer;
time = 0; deltaTime = 1.0/50.0; pauseTimer = [NSTimer scheduledTimerWithTimeInterval: deltaTime target: self selector:@selector (timerFunction) userInfo: nil repeats: YES];
— (void) timerFunction { // мы здесь бываем 50 раз в секунду time = time + deltaTime; [self renderPuck]; [self renderPlayers]; [self puckMoving: deltaTime]; } Итак, все что мне осталось — расписать функцию puckMoving. В ней проверю столкновение шайбы и хоккеистов. Математически шайба задается окружностью радиусом 21 пиксел. Каждый хоккеист есть две окружности — побольше (радиус 25 пикселов) тело. Поменьше (радиус 5 пикселов) — клюшка.
Функция ниже, комментарии внутри.
puckMoving for (int k=1; k<33; k++) { if ( [self checkCollision:i With:k] ) { [self resolve:i With:k]; } }
-(int) checkCollision:(int) k1 With:(int) k2 { float d2 = [self distance2: k1 With: k2]; float dr = rp2[k1] + rp2[k2]; return (d2 < dr*dr ? 1 : 0); }
-(float) distance2:(int) k1 With:(int) k2 { float dxx = xp2[k1] — xp2[k2]; float dyy = yp2[k1] — yp2[k2]; return dxx*dxx + dyy*dyy; }
-(void) resolve:(int) k1 With:(int) k2 { float x1 = xp2[k1]; float y1 = yp2[k1]; float x2 = xp2[k2]; float y2 = yp2[k2]; float u2 = up2[k2]; float v2 = vp2[k2]; float u1 = up2[k1]; float v1 = vp2[k1]; Vector *b1Velocity = [Vector alloc]; [b1Velocity initX: u1 initY: v1]; Vector *b2Velocity = [Vector alloc]; [b2Velocity initX: u2 initY: v2 ]; float b1Mass = mp2[k1]; float b2Mass = mp2[k2]; Vector *vv = [Vector alloc]; [vv initX: x1-x2 initY: y1-y2]; float distance = [vv magnitude]; float min_distance = rp2[k1] + rp2[k2]; if (distance < min_distance) { [vv mulScalar: ((0.1+min_distance-distance)/(distance)) ]; x1 += vv.x; y1 += vv.y; xp2[k1] = x1; yp2[k1] = y1; } Vector *lineOfSight = [Vector alloc]; [lineOfSight initX:x1-x2 initY:y1-y2]; Vector *v1Prime = [b1Velocity vectorProjectionOnto:lineOfSight]; Vector *v2Prime = [b2Velocity vectorProjectionOnto:lineOfSight]; Vector *v1Prime2 = [Vector alloc]; [v1Prime2 copyVector:v2Prime]; [v1Prime2 mulScalar:(2*b2Mass)]; [v1Prime2 addVector:[v1Prime getMulScalar:(b1Mass - b2Mass)] ]; [v1Prime2 mulScalar:(1.0/(b1Mass + b2Mass))]; Vector *v2Prime2 = [Vector alloc]; [v2Prime2 copyVector:v1Prime]; [v2Prime2 mulScalar:(2*b1Mass)]; [v2Prime2 subVector: [v2Prime getMulScalar:(b1Mass - b2Mass)] ]; [v2Prime2 mulScalar:(1.0/(b1Mass + b2Mass))]; [v1Prime2 subVector:(v1Prime)]; [v2Prime2 subVector:(v2Prime)]; [b1Velocity addVector:v1Prime2]; [b2Velocity addVector:v2Prime2]; float a = 0.999; up2[k1] = a*b1Velocity.x + (1.0-a)*b2Velocity.x; vp2[k1] = a*b1Velocity.y + (1.0-a)*b2Velocity.y; a = 1.0 - a; // NSLog(@"new speed %f", hypotf(u, v)) ); } Внутри функции используется класс Vectorкласс Vector #import "Vector.h"
@implementation Vector @synthesize x, y;
-(void) initX:(float) setX initY:(float) setY { x = setX; y = setY; }
-(void) copyVector:(Vector*) v{ x = v.x; y = v.y; }
-(void) addVector:(Vector*) v { x += v.x; y += v.y; } -(void) subVector:(Vector*) v { x -= v.x; y -= v.y; } -(void) mulScalar:(float) f { x *= f; y *= f; }
-(float) magnitude { return sqrt (x*x + y*y); } -(float) magnitude2 { return x*x + y*y; }
-(Vector *)getMulScalar:(float) f { Vector *v = [Vector alloc]; [v initX: x*f initY: y*f]; return v; }
-(float) scalarProjectionOnto:(Vector*) v { return (x* v.x + y*v.y)/ [v magnitude]; }
-(Vector *) vectorProjectionOnto:(Vector*) v { Vector *res = [v getUnitVector]; [res mulScalar: [self scalarProjectionOnto: v]]; return res; } -(Vector *) getUnitVector { float len = [ self magnitude]; Vector *res = [Vector alloc]; [res initX: x initY: y]; if (len>0) { len = 1.0/len; [res mulScalar: len]; } return res; } @end При численном моделировании движения шайбы есть одна хитрость. 50 раз в секунду не хватает для точности моделирования. Я применяю элементарный трюк — удесятеряю число вызовов функции puckMoving, соответственно разбивая временной шаг на десять. — (void) timerFunction { // мы здесь бываем 50 раз в секунду time = time + deltaTime; [self renderPuck]; [self renderPlayers]; for (int k=0; k<10; k++) [self puckMoving:deltaTime/10.0]; } Смотрим, что получилось
[embedded content]
6. Статистика знает все Русские и американцы любят статистику. Поэтому я организовал турнир шести лучших команд мира.Реальные игроки с nhl.com, голы, шайбы, очки, все звезды турнира.7. Тестирование Apple допускает 1000 тестеров на стадии ревизии приложения.Не заходите, реклама У кого есть iPad — шлите Apple ID, можете играть прямо завтра. Сегодня у меня хоккей, извините.
Спасибо, что дочитали.