Статьи

Light Speed ​​iOS Apps: Падстероиды 2

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

Набор инструментов iOS поставляется с богатым набором элементов управления, кнопок, списков, элементов управления датой и так далее. Но это игра, и для этой игры мы хотим иметь что-то вроде джойстика. К сожалению, нет встроенного элемента управления джойстика, поэтому мы собираемся создать новый вид под названием «CircleControlView», который действует как джойстик. И мы будем использовать два из этих элементов управления, один для запуска лазера, а другой для перемещения корабля.

Создание CircleControlView

Создание нового класса CircleControlView начинается с использования опции «New> File» в папке Padsteroids, как показано на рисунке 1. Это точно такой же процесс, который мы использовали для создания представления GameSurface.

Падстероиды 2 Рисунок 1

фигура 1

Как и в случае с представлением GameSurface, следующие шаги — выбрать, что мы хотим создать класс Objective-C и что он должен происходить из UIView. Затем нам нужно назвать его CircleControlView, как показано на рисунке 2.

Падстероиды 2 Рисунок 2

фигура 2

Если вы создали класс CircleControlView правильно, вы должны увидеть новые CircleControlView.h и CircleControlView.m добавленные в ваш проект, как вы можете видеть на рисунке 3.

Падстероиды 2 Рисунок 3

Рисунок 3

Следующим шагом является копание в файл CircleControlView.h и добавление некоторых переменных-членов и двух CircleControlView.h доступа, как показано в листинге 1.

Листинг 1. CircleControlView.h

 #import <UIKit/UIKit.h> @interface CircleControlView : UIView { BOOL initializedCenter; CGPoint centerPoint; } - (double)getAngle; - (double)getDistance; @end 

initializedCenter является логическим значением, которое указывает, был ли центр инициализирован для элемента управления. И centerPoint хранит текущее местоположение джойстика в представлении. Два getAngle доступа getAngle и getDistance возвращают угол джойстика от центра в градусах и расстояние от центра в процентах от общего доступного расстояния. Например, элемент управления может находиться на нулевом градусе и нулевом расстоянии, что означает, что он находится в центре. Или это может быть на 25 градусов на расстоянии 50%, что означает, что он был перемещен вверх и вправо на полпути между центром контроля и периметром круга.

После того как определение класса полностью настроено, пришло время изменить класс CircleControlView.m для реализации этого нового интерфейса. Новый код для инициализации и отрисовки элемента управления приведен в листинге 2.

Листинг 2. Инициализация CircleControlView.m и рисование

 #import "CircleControlView.h" @implementation CircleControlView - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { initializedCenter = NO; } return self; } - (void)drawRect:(CGRect)rect { if ( initializedCenter == NO ) { centerPoint.x = self.bounds.size.width / 2; centerPoint.y = self.bounds.size.height / 2; initializedCenter = YES; } CGContextRef context = UIGraphicsGetCurrentContext(); CGContextSetShouldAntialias( context, true ); CGContextSetFillColorWithColor(context, [UIColor blueColor].CGColor); CGContextSetAlpha( context, 0.2 ); CGContextFillEllipseInRect( context, CGRectInset( self.bounds, 5, 5 ) ); CGContextSetAlpha( context, 0.7 ); CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor); CGContextSetLineWidth(context, 2.0); CGContextStrokeEllipseInRect( context, CGRectInset( self.bounds, 5, 5 ) ); CGContextSetAlpha( context, 0.5 ); CGRect ballRect; ballRect.origin.x = centerPoint.x - 20; ballRect.origin.y = centerPoint.y - 20; ballRect.size.width = 40; ballRect.size.height = 40; CGContextSetFillColorWithColor(context, [UIColor blueColor].CGColor); CGContextSetLineWidth(context, 1.0); CGContextFillEllipseInRect( context, ballRect ); } 

Код для рисования — самая интересная часть. Он начинается с инициализации центральной точки, если это первый раз, когда рисуется элемент управления. Он использует рисунок, чтобы нарисовать заполненный круг, затем границу круга и, наконец, контрольный шар в его текущем местоположении. Не стесняйтесь играть с цветами и размерами и тому подобное, часть удовольствия от разработки собственных элементов управления заключается в том, что вы полностью контролируете внешний вид и ощущения.

Следующим шагом является добавление интерактивности пользователя в элемент управления с использованием методов, показанных в листинге 3.

Листинг 3. Элемент управления взаимодействием CircleControlView.m

 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { centerPoint.x = self.bounds.size.width / 2; centerPoint.y = self.bounds.size.height / 2; [self setNeedsDisplay]; } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { centerPoint = [[touches anyObject] locationInView: self]; [self setNeedsDisplay]; } 

Это довольно стандартные методы пользовательского интерфейса; touchesEnd и touchesEnd . iOS отправляет эти события, когда игрок касается элемента управления и перемещается. Когда они заканчивают касание, touchesEnded метод touchesEnded . В этом случае мы используем метод touchesMoved чтобы отслеживать большие пальцы пользователей при перемещении джойстика. И метод touchesEnded для возврата джойстика в центральное положение. В листинге 4 показаны методы доступа для класса.

Листинг 4. Средства доступа CircleControlView.m.

 - (double)getDistance { double d = 0.0; CGPoint circCenter; circCenter.x = self.bounds.size.width / 2; circCenter.y = self.bounds.size.height / 2; double d1 = ( circCenter.x - centerPoint.x ) * ( circCenter.x - centerPoint.x ); double d2 = ( circCenter.y - centerPoint.y ) * ( circCenter.y - centerPoint.y ); d = sqrt( d1 + d2 ); d = d / (double) ( self.bounds.size.width / 2 ); return d; } - (double)getAngle { double angle = 0.0; CGPoint circCenter; circCenter.x = self.bounds.size.width / 2; circCenter.y = self.bounds.size.height / 2; if ( circCenter.x == centerPoint.x && circCenter.y == centerPoint.y ) { angle = 0.0; } else if ( circCenter.x == centerPoint.x && centerPoint.y < circCenter.y ) { angle = 0.0; } else if ( circCenter.x == centerPoint.x && centerPoint.y > circCenter.y ) { angle = 180.0; } else if ( centerPoint.x < circCenter.x && centerPoint.y == circCenter.y ) { angle = 270.0; } else if ( centerPoint.x > circCenter.x && centerPoint.y == circCenter.y ) { angle = 90.0; } else { double p1 = abs( centerPoint.x - circCenter.x ); double p2 = abs( centerPoint.y - circCenter.y ); angle = atan( p1 / p2 ) * 57.29577951; if ( centerPoint.x > circCenter.x && centerPoint.y > circCenter.y ) { angle = 180 - angle; } else if ( centerPoint.x < circCenter.x && centerPoint.y > circCenter.y ) { angle = 180 + angle; } else if ( centerPoint.x < circCenter.x && centerPoint.y < circCenter.y ) { angle = 360 - angle; } } while ( angle > 360.0 ) { angle -= 360.0; } while ( angle < 0 ) { angle += 360.0; } return angle; } - (void)dealloc { [super dealloc]; } @end 

Эти методы выглядят хитро, но на самом деле это просто математика, чтобы получить угол и расстояние от текущей контрольной точки, где пользователь поместил большой палец в центр элемента управления. Метод getDistance использует теорему Пифагора для получения расстояния и возвращает его в процентах. И метод getAngle выполняет много триггерной работы, чтобы определить угол для данного квадранта.

С разработанными элементами управления пришло время обновить класс GameSurface, чтобы мы могли перемещать корабль.

Обновление GameSurface

Мы начнем с простого перемещения корабля, а затем перейдем к стрельбе из лазера в этом уроке. Чтобы переместить корабль, нам нужно добавить метод moveShip, как показано в листинге 5.

Листинг 5. Обновленный GameSurface.h

 #import <UIKit/UIKit.h> @interface GameSurface : UIView { CGPoint shipLocation; double shipDirection; } - (void)moveShip:(float)distance angle:(float)angle; @end 

Затем нам нужно реализовать метод, добавив его в файл GameSurface.m как показано в листинге 6.

Листинг 6. Перемещение в GameSurface.m

 - (void)moveShip:(float)distance angle:(float)angle { shipLocation.x -= ( distance * 3.0 ) * cos((angle+90.0) * 0.0174532925); shipLocation.y -= ( distance * 3.0 ) * sin((angle+90.0) * 0.0174532925); shipDirection = angle; [self setNeedsDisplay]; } 

Код для moveShip изменяет местоположение корабля, изменяя его координаты x и y на значение, рассчитанное по углу и значениям пройденного расстояния. Он также хранит текущий угол корабля, чтобы при его рендеринге он указывал вправо. направление.

После внесения этих изменений пора добавить ссылки на игровую поверхность и два элемента управления кругом в PadsteroidsViewController.h

Листинг 7. PadsteroidsViewController.h

 #import <UIKit/UIKit.h> #import "CircleControlView.h" #import "GameSurface.h" @interface PadsteroidsViewController : UIViewController { IBOutlet CircleControlView *motionControl; IBOutlet CircleControlView *fireControl; IBOutlet GameSurface *gameSurface; } @end 

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

Обновление PadsteroidsViewController.xib

Большинство изменений в коде, которые нам нужны для перемещения корабля, были сделаны, поэтому теперь пришло время добавить CircleControlViews для управления движением и огнем в интерфейс, определенный в PadsteroidsViewController.xib .

Первый шаг — открыть файл XIB и выбрать Identity Inspector из меню «View> Utilities». Внизу внизу панели находится набор доступных объектов для добавления в представление, как показано на рисунке 4.

Падстероиды 2 Рисунок 4

Рисунок 4

Прокрутите немного вниз, и вы найдете объект View. Перетащите объект View на главный экран, и он появится в интерфейсе. Оттуда измените класс в верхней части панели с UIView на CircleControlView, как показано на рисунке 5.

Падстероиды 2 Рисунок 5

Рисунок 5

Теперь нам нужно изменить размер представления соответствующим образом. Перейдите в инспектор размеров и установите размеры, показанные на рисунке 6.

Падстероиды 2 Рисунок 6

Рисунок 6

Теперь перейдите к инспектору атрибутов и измените режим на «Перерисовать», «Фон — на прозрачный цвет» и убедитесь, что установлен флажок «Взаимодействие с пользователем», как показано на рисунке 7.

Падстероиды 2 Рисунок 7

Рисунок 7

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

Падстероиды 2 Рисунок 8

Рисунок 8

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

Следующий шаг — связать их с PadsteroidsViewController, который считается «владельцем файла». Первым шагом является щелчок правой кнопкой мыши на CircleControlViewer с левой стороны. Вы должны увидеть всплывающее окно, как показано на рисунке 9.

Падстероиды 2 Рисунок 9

Рисунок 9

Оттуда, нажмите на маленькую букву «О» справа от нового источника ссылок и перетащите его в заполнитель «Владелец файла». Вы должны увидеть что-то вроде рисунка 10.

Падстероиды 2 Рисунок 10

Рисунок 10

Оттуда отпустите кнопку мыши, и она должна вызвать другое всплывающее меню, которое дает список возможных точек подключения, как показано на рисунке 11.

Падстероиды 2 Рисунок 11

Рисунок 11

Для управления огнем выберите пункт fireControl. Повторите процесс для управления движением и подключите его к точке motionControl . Вам также необходимо подключить игровую поверхность к элементу gameSurface, как показано на рисунке 12.

Падстероиды 2 Рисунок 12

Рисунок 12

Это может быть действительно чуждо в первый раз, когда вы это делаете. Конструктор интерфейса XCode отличается от любого другого построителя пользовательского интерфейса, который я использовал в прошлом. Но как только вы привыкаете к этому, это начинает иметь смысл.

Завершение изменений кода для движения

Последний шаг для PadsteroidsViewController.m управления движением — это внести небольшие изменения в файл PadsteroidsViewController.m как показано в листинге 8.

Листинг 8. Обновление PadsteroidsViewController.m

 #import "PadsteroidsViewController.h" @implementation PadsteroidsViewController - (void)dealloc { [super dealloc]; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; } - (void)viewDidLoad { [super viewDidLoad]; [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:@selector(gameUpdate:) userInfo:nil repeats:YES]; } - (void)gameUpdate:(NSTimer*)theTimer { if ( [motionControl getDistance] > 0 ) { [gameSurface moveShip:[motionControl getDistance] angle:[motionControl getAngle]]; } } - (void)viewDidUnload { [super viewDidUnload]; } - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { return YES; } @end 

Здесь мы создаем таймер в функции viewDidLoad , а затем viewDidLoad на этот таймер в новом методе gameUpdate . Метод gameUpdate просматривает текущую позицию motionControl . Если расстояние больше нуля, это означает, что пользователь отодвинул элемент управления от центральной точки, указывая, что он хочет переместить корабль. Затем он вызывает метод moveShip для перемещения корабля с учетом угла и расстояния.

Если все подключено правильно, вы должны увидеть что-то похожее на то, что показано на рисунке 13 при запуске приложения.

Падстероиды 2 Рисунок 13

Рисунок 13

И, перемещая управление движением, вы сможете перемещать корабль, как вы можете видеть на рисунке 14.

Падстероиды 2 Рисунок 14

Рисунок 14

Хорошо! Теперь у нас есть игра, которая на самом деле начинает ощущаться как игра. Но чтобы воплотить все это в жизнь, нам нужно уметь делать что-то большее, чем двигаться, нам нужно уметь стрелять.

Добавление лазера

Какой была бы игра без какого-либо способа взорвать вещи? Для этого мы собираемся добавить лазер на космический корабль и подключить его к управлению огнем, чтобы игрок мог стрелять и стрелять в любом направлении, которое им нравится. В листинге 9 показаны дополнения, которые мы должны внести в файл GameSurface.h .

Листинг 9. Новый GameSurface.h

 #import <UIKit/UIKit.h> @interface GameSurface : UIView { CGPoint shipLocation; double shipDirection; BOOL gunEnabled; double gunDirection; } - (void)moveShip:(float)distance angle:(float)angle; - (void)enableGun:(float)distance angle:(float)angle; - (void)disableGun; @end 

Дополнения составляют два новых метода disableGun и disableGun . Метод enableGun вызывается, когда пользователь переместил джойстик стрельбы большим пальцем. И disableGun вызывается, когда игрок выпустил джойстик стрельбы.

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

Листинг 10. Новый drawRect для GameSurface.m

 - (void)drawRect:(CGRect)rect { CGContextRef context = UIGraphicsGetCurrentContext(); CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor); CGContextTranslateCTM( context, ( self.bounds.size.width / 2 ) + shipLocation.x, ( self.bounds.size.height / 2 ) + shipLocation.y ); if ( gunEnabled ) { CGContextSaveGState(context); CGContextSetStrokeColorWithColor(context, [UIColor greenColor].CGColor); CGContextSetLineWidth(context, 5.0); CGPoint target; target.x = ( 1000.0 * cos((gunDirection+90.0) * 0.0174532925) ) * -1; target.y = ( 1000.0 * sin((gunDirection+90.0) * 0.0174532925) ) * -1; CGContextMoveToPoint(context, 0.0, 0.0); CGContextAddLineToPoint(context, target.x, target.y); CGContextStrokePath(context); CGContextRestoreGState(context); } CGContextSaveGState(context); CGContextRotateCTM(context, shipDirection * 0.0174532925 ); CGContextMoveToPoint(context, 0.0, -15.0); CGContextAddLineToPoint(context, 15.0, 30.0); CGContextAddLineToPoint(context, 0.0, 15.0); CGContextAddLineToPoint(context, -15.0, 30.0); CGContextAddLineToPoint(context, 0.0, -15.0); CGContextFillPath(context); CGContextRestoreGState(context); } 

Добавления означают добавление поддержки для оружия, когда оно включено, рисуя линию от корабля до хорошо расположенного экрана в направлении выстрела.

Методы enable и disable пистолета показаны в листинге 11.

Листинг 11. Методы оружия в GameSurface.m

 - (void)enableGun:(float)distance angle:(float)angle { gunEnabled = YES; gunDirection = angle; CGPoint laserStart, laserEnd; laserStart.x = ( self.bounds.size.width / 2 ) + shipLocation.x; laserStart.y = ( self.bounds.size.height / 2 ) + shipLocation.y; laserEnd.x = laserStart.x + ( 1000.0 * cos((gunDirection+90.0) * 0.0174532925) ) * -1; laserEnd.y = laserStart.y + ( 1000.0 * sin((gunDirection+90.0) * 0.0174532925) ) * -1; [self setNeedsDisplay]; } - (void)disableGun { gunEnabled = NO; [self setNeedsDisplay]; } 

Последнее изменение, которое нам нужно сделать, — это метод PasteroidsViewController.m в PasteroidsViewController.m . Новый код показан в листинге 12.

Листинг 12. Обновленное gameUpdate в PadsteroidsViewController.m

 - (void)gameUpdate:(NSTimer*)theTimer { if ( [motionControl getDistance] > 0 ) { [gameSurface moveShip:[motionControl getDistance] angle:[motionControl getAngle]]; } if ( [fireControl getDistance] > 0 ) { [gameSurface enableGun:[fireControl getDistance] angle:[fireControl getAngle]]; } else { [gameSurface disableGun]; } } 

Здесь мы добавляем мониторинг для представления fireControl а затем отправляем сообщение disableGun или disableGun на игровую поверхность в зависимости от ситуации. Если все пойдет по плану, вы сможете использовать левую ручку, чтобы запустить лазер, как показано на рисунке 15. На данный момент это начинает выглядеть и ощущаться как настоящая игра.

Падстероиды 2 Рисунок 15

Рисунок 15

Вывод

В этой части нашего урока есть чему поучиться. Вы узнаете, как обрабатывать прикосновения к представлению в интерфейсе. А также как подключить объекты в конструкторе интерфейса. А также, как создать таймер и использовать его, чтобы вести игру.

Благодаря способности двигаться и стрелять мы добавили немного интерактивности в нашу маленькую игру Padsteroids. Следующим шагом, который будет описан в заключительной части руководства, будет динамическое добавление некоторых видов астероидов к игровой поверхности, их перемещение в случайных направлениях со случайными скоростями и создание системы обнаружения столкновений, чтобы лазер мог взрываться. их из существования.

Эд : После того, как вы прочитали первую фазу падстероидов и вторую фазу падстероидов , не забывайте, что вы можете проверить или устранить неисправность своего кода, сравнивая его с проектом « Падстероиды» на GitHub .