Резюме
Я работаю над довольно простой 2D-оборонной игрой для iOS.
До сих пор я использовал Core Graphics исключительно для обработки рендеринга. В приложении нет файлов изображений (пока). Я испытываю некоторые существенные проблемы с производительностью, делая относительно простой рисунок, и я ищу идеи относительно того, как я могу это исправить, не переходя в OpenGL.
Настройка игры
На высоком уровне у меня есть класс Board, который является подклассом UIView
, для представления игрового поля. Все остальные объекты в игре (башни, ползунки, оружие, взрывы и т.д.) Также являются подклассами UIView
и добавляются в качестве подзонов к Совету при их создании.
Я сохраняю состояние игры полностью отдельно от свойств вида внутри объектов, и каждое состояние объекта обновляется в основном игровом цикле (срабатывает NSTimer
на 60-240 Гц, в зависимости от настройки скорости игры). Игра полностью воспроизводится без рисования, обновления или анимации представлений.
Я обрабатываю обновления с помощью таймера CADisplayLink
с частотой обновления (60 Гц), которая вызывает setNeedsDisplay
на объектах платы, которые должны обновлять свои свойства в зависимости от изменений состояния игры. Все объекты на доске переопределяют drawRect:
, чтобы нарисовать некоторые довольно простые 2D-фигуры в пределах их рамки. Поэтому, когда оружие, например, анимируется, оно будет перерисовываться на основе нового состояния оружия.
Проблемы с производительностью
Тестирование на iPhone 5 с примерно двумя десятками игровых объектов на борту, частота кадров значительно падает ниже 60 FPS (целевая частота кадров), обычно в диапазоне 10-20 FPS. С большим количеством действий на экране, он идет вниз отсюда. А на iPhone 4 все еще хуже.
Используя инструменты, я определил, что на фактическое обновление состояния игры затрачивается примерно 5% времени процессора, и подавляющее большинство из них направлено на рендеринг. В частности, функция CGContextDrawPath
(которая, по моему мнению, является местом растеризации векторных путей), занимает огромное количество процессорного времени. Подробнее см. Снимок экрана "Инструменты" внизу.
Из некоторых исследований, посвященных StackOverflow и другим сайтам, похоже, что Core Graphics просто не соответствует задаче для того, что мне нужно. По-видимому, поглаживание векторных дорожек чрезвычайно дорого (особенно при рисовании объектов, которые не являются непрозрачными и имеют некоторое альфа-значение < 1,0). Я почти уверен, что OpenGL решит мои проблемы, но это довольно низкий уровень, и я не очень рад, что должен его использовать - похоже, что это не обязательно для того, что я здесь делаю.
Вопрос
Есть ли какие-либо оптимизации, на которые я должен обратить внимание, чтобы попытаться получить гладкую 60 FPS из Core Graphics?
Некоторые идеи...
Кто-то предположил, что я рассматриваю возможность рисования всех моих объектов на одном CALayer
вместо того, чтобы каждый объект сам по себе CALayer
, но я не уверен, что это поможет на основе того, что демонстрирует инструменты.
Лично у меня есть теория, использующая CGAffineTransforms
для выполнения моей анимации (т.е. один раз рисует форму объекта в drawRect:
один раз, а затем выполняет преобразования для перемещения/поворота/изменения размера своего слоя в последующих кадрах). моя проблема, поскольку они основаны непосредственно на OpenGL. Но я не думаю, что было бы легче сделать это, чем просто использовать OpenGL.
Пример кода
Чтобы вы почувствовали уровень рисования, который я делаю, вот пример реализации drawRect:
для одного из моих объектов оружия ( "луч", выпущенный из башни).
Примечание: этот луч может быть "перенацелен", и он пересекает всю доску, поэтому для простоты его рама имеет те же размеры, что и плата. Однако большинство других объектов на доске имеют свой кадр, установленный на наименьший описанный прямоугольник.
- (void)drawRect:(CGRect)rect
{
CGContextRef c = UIGraphicsGetCurrentContext();
// Draw beam
CGContextSetStrokeColorWithColor(c, [UIColor greenColor].CGColor);
CGContextSetLineWidth(c, self.width);
CGContextMoveToPoint(c, self.origin.x, self.origin.y);
CGPoint vector = [TDBoard vectorFromPoint:self.origin toPoint:self.destination];
double magnitude = sqrt(pow(self.board.frame.size.width, 2) + pow(self.board.frame.size.height, 2));
CGContextAddLineToPoint(c, self.origin.x+magnitude*vector.x, self.origin.y+magnitude*vector.y);
CGContextStrokePath(c);
}
Запуск инструментов
Посмотрите на инструменты, после чего игра продлится некоторое время:
Класс TDGreenBeam
имеет точную реализацию drawRect:
, показанную выше в разделе Sample Code.