Шайбу вбросим в iOS восемь

от автора

Прежде чем создавать казуальную игру для iOS, хорошо бы ответить на вопрос: — А зачем?
Вариантов три:

  • Срубить денег;
  • Порадовать родственников;
  • Хрен его знает, но мысль жжет организм изнутри.

image

Думаю, в ближайшие годы, правильный ответ — третий.
А, не буду спорить и учить — расскажу, как я делаю приложения.
Гуру разработки молча нажимают плюс и уходят в сторону. Остальные следуют за мной, чтобы вспомнить школу и настольный хоккей.
И да, уникальность топика, что в каждом предложении слова начинаются разными буквами.
В статье девять картинок и пол-минуты видео.

1. Идея

От неё зависит выбор инструментов. Мои идеи, как пользователи смартфонов — незамысловатые. Потому — никаких лишних инструментов для разработки, кроме библиотеки воспроизведения звуков. Ресурсы заимствую из сети. Мелодии, дизайн, изображения, примеры кода. Все уже создано до нас. Авторское право — как в хоккее или футболе. Допустим, Месси придумал новый финт. Вы можете его скопировать, не отчисляя автору денег.
Ладно. В качестве примера игры я выбрал настольный хоккей.

Очень личное

80-ые годы. На мех-мате эта забава была популярна. Мы играли в умывалке ФДС-6. Похвастаюсь звездным моментом — толпа народу соревновалась в ночь. Я зашел, дождался очереди, победил местного чемпиона 10-0 и скромно удалился. Если честно — у меня два брата и шестнадцать лет азартной практики.

2. Начало проекта

image
Рисунок 2. В Xcode завожу новый проект.

Кстати, у меня толстые пальцы. Значит, для игры подходит лишь iPad режим. Это существенно облегчает работу. Кроме того, запрещаю вращение экрана. Только портрет. Долой пейзажи, это хоккей, а не балет.
Выбираю название Hockey 2015. Названия из мира спорта — прибыльны. Насчет денег, я, конечно, слукавил. Хочется, чтобы будущий опус принес удовлетворение как душевное, так и материальное. Я уже писал здесь о своей игре Biathlon 2014. Каждый год — почти тысяча долларов навара. Покупают в дни чемпионата мира, олимпиады, кубковых этапов. Кто? Норвежцы, германцы, чехи, французы, русские, итальянцы.
Думаю, мой будущий хоккей ждет что-то похожее всего лишь из-за названия.

Выбираю иконку. Не так важно, как имя. Главное — чтобы самому нравилось.
image
Рисунок 3. Кто угадает, что это за хоккеист, тому- приз 1 доллар.

В результате, получается заготовка будущей игры, которая отображает ярко-серый экран. Проект имеет два основных файла, которые я могу редактировать.

  • ViewController.xib
  • ViewController.m

А не буду, плохой тон. Хороший тон не полениться и создать другой класс вида ViewController. Например, с именем PlayViewController.

image
Рисунок 4. Добавляю новый контроллер PlayViewController.

Проект имеет два новых файла, которые я всегда могу редактировать.

  • PlayViewController.xib
  • PlayViewController.m

3. Статичные изображения

Неподвижные изображения размещаю с помощью редактора в файле PlayViewController.xib.
Вначале их надо создать, украсть, позаимствовать. Фотографии хоккейного поля, игроков, вратарей мне прислал Milfgard. В Мосигре их есть. За что я без спросу разместил именной лейбл в центре поля. Шайба — из сети. Фотографии лиц хоккеистов — nhl.com. Кнопки — от дизайнеров Зептолаб. Хоккейные звуки — из приложения Ice Rage.
Теперь можно разместить изображения в редакторе xib-файлов. Это просто — все картинки отображается элементом UIImageView.

image
Рисунок 5. Хоккейное поле и табло в редакторе Xcode.

Если в дальнейшем потребуется анимация статично заданных элементов — не беда. Каждому элементу можно присвоить имя. В этом случае элемент можно программно двигать, гасить, трансформировать, что угодно.

Пример, объявляем элемент scoreBoard (черное табло на картинке) в файле PlayViewController.m

   IBOutlet UIImageView *scoreBoard; 

Ключевое слово IBOutlet означает, что в редакторе XIB любому элементу можно присвоить идентификатор scoreBoard.
Присваиваю мышкой в редакторе.

Теперь я могу сдвинуть картинку глубоко наверх, чтобы оно не загораживало хоккейное поле.

  scoreBoard.center = CGPointMake(384, -1000); 

Напоминаю, 384 — центр экрана iPad по ширине, -1000 — что-то вне устройства. После исполнения команды табло улетит вверх за границы экрана.

Если требуется двигать группу картинок — их надо объединить. Завести элемент типа UIView, чтобы переместить в него группу картинок и надписей.
image
Рисунок 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];  

image
Рисунок 7. На корте появился хоккеист.

В дальнейшем я его буду перемещать и вращать. Вот так.

    player.center = CGPointMake(xnew, ynew); // перемещаю в точку (xnew=100, ynew=440)     player.transform = CGAffineTransformMakeRotation(alpha); // кручу хоккеиста на угол (alpha=2.0) в радианах  

image
Рисунок 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]; }     

Смотрим, что получилось

6. Статистика знает все

Русские и американцы любят статистику. Поэтому я организовал турнир шести лучших команд мира.
Реальные игроки с nhl.com, голы, шайбы, очки, все звезды турнира.

7. Тестирование

Apple допускает 1000 тестеров на стадии ревизии приложения.

Не заходите, реклама

У кого есть iPad — шлите Apple ID, можете играть прямо завтра. Сегодня у меня хоккей, извините.

Спасибо, что дочитали.

ссылка на оригинал статьи http://habrahabr.ru/post/253423/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *