Этот мануал написан для тех, кто хочет заниматься разработкой игры, не вникая в тонкости работы API фреймворков для iOS.
Информацию можно использовать для написания обвязки, прототипа или для понимания принципов работы.
Предполагается, что читатель знает, как создать проект приложения для iOS в Xcode и прилинковать фреймворки.
Так же стоит отметить, что использование ресурсов графического интефейса (*.xib или *.storyboard) не рассматривается.
Весь код актуален для iOS версии 5.0.0 и выше и всей линейки устройств, если не отмечено иначе. Модель менеджмента памяти - ARC.
OpenGL
Что бы не усложнять себе работу инициализацией OpenGL, настройкой графического контекста и прочей низкоуровневой рутиной, я использую GLKit. Он оборачивает инициализацию контекста, библиотеки, фрейм и рендербуферов. Так же предоставляет коллбэк для рисования в main loop’е. Тем самым, вся вышеупомянутая рутина сводится к написанию пары классов и нескольких строк кода. К коду и перейдём.
Нам необходимы два класса:
MainView, наследуемый от GLKView;
MainViewController, наследуемый от GLKViewController и реализующий протоколы GLKViewDelegate и GLKViewControllerDelegate.
MainView.h
@interface MainView : GLKView
@end
Объявление и тело класса пока что оставим пустым. Позже он нам понадобится для обработки жестов.
(2) Выбор версии графического API устанавливается флагами:
kEAGLRenderingAPIOpenGLES1;
kEAGLRenderingAPIOpenGLES2;
kEAGLRenderingAPIOpenGLES3.
(3) Инициализация color render буфера. Для стареньких устройств с небольшим объёмом видеопамяти можно устанавливать флаг формата GLKViewDrawableColorFormatRGB565. С iOS 7 становится доступен новый флаг GLKViewDrawableColorFormatSRGBA8888 позволяющий оперировать графическими данными в формате sRGB.
(4) Инициализация буфера глубины. Его можно отключить, или установить 16 битным, что тоже очень хорошо подходит для стареньких устройств.
Эти два метода просто разделяют графику и логику. В -glkViewControllerUpdate: я использую ограничитель на определённое количество раз в секунду.
Там где нужно узнать время, я использую CACurrentMediaTime(). В треде на stackoverflow есть обсуждение производительности нескольких методов получения времени и CACurrentMediaTime лидирует. Для игр это важно, ведь время определяется по несколько десятков раз за один проход игрового цикла.
По хорошему, lastUpdateTime должна быть свойством класса и инициализироваться текущим временем перед первым апдейтом, что бы не использовать тернарный оператор по 30 раз в секунду.
Суть этого таймера в том, что даже если игра подвисла на несколько секунд, впоследствии выполнятся все апдейты за время простоя.
Финальным аккордом в настройке OpenGL является установка MainViewController в качестве root для главного окна. Для этого в AppDelegate изменим метод -application:didFinishLaunchingWithOptions:
У UIKit'а есть несколько способов обработки событий. На мой взгляди самый простой это использование UIGestureRecognizer.
Этот API позволяет нам создать класс-обработчик события, который дёрнет наш коллбек в нужный момент и снабдит всеми необходимыми данными о событии.
Следующие события можно обрабатывать без слёз:
Работа с ресурсами в Xcode реализована без учета нужды постоянно изменять содержимое проекта. Существует метод борьбы и с этим недугом. Что бы этот способ менеджмента ресурсов сработал, необходимо соблюсти одно правило - все ресурсы объеденены в одной папке.
Добавляем папку с ресурсами в проект как папку (на рисунке папка "data"):
Для таргета создадим Run Script после линковки с библиотеками и фремворками:
Код скрипта:
touch -cm ${SRCROOT}/data
{SRCROOT} - путь к папке, в которой лежит *.xcodeproj.
Суть скрипта в изменении даты последнего изменения папки. Xcode при билде перезаливает в бандл ресурсы, только при увеличении даты последнего изменения ресурса.
Так как у папки эта дата меняется крайне редко, таким образом мы обеспечим обновление ресурсов при каждой сборке.
Однако, доступ к ресурсам, лежащим таким образом, через
Фреймворк CoreFoundation предоставляет нам несколько способов создания сокетного клиента. Самый простой, на мой взгляд, это использование CFStreamCreatePairWithSocketToHost(). На вход отдаём адрес хоста и порт, на выходе имеем NSInputStream и NSOutputStream, минуя весь деревянный API berkley сокетов.
Дальнейшее описание работы со стримами можно почитать в доке.
Если вы решили реализовать обработку событий стримов через RunLoop, логично было бы сделать обработку сетевого ввода/вывода в отдельном потоке. Реализовывается это не совсем тривиально.
Код инициализации изменится на следующий: