В последнее время обратил внимание на ролики программ, в которых реализован так называемый псевдо 3D эффект: когда картинка приложения изменяется в зависимости от положения пользователя относительно телефона. Или телефона относительно пользователя: смотря с какой стороны вы находитесь :). Для достижения этого эффекта можно использовать либо сенсоры либо отслеживать положение глаз пользователя (т.н. head tracking). Второй способ несколько сложнее, хотя даёт более правдоподобный результат.
В качестве эксперимента мы решили попробовать сделать такой 3Д фон в программе Deluxe Moon Pro (В версии на маркете пока этот эффект не реализован!).
Вот пример того, что у нас получилось:
Итак начнем.
Для того, чтобы достичь 3Д эффекта необходимо изображение разбить на «слои» и в зависимости от показаний акселерометра смещать каждый слой на определённую величину.
Казалось бы всё просто, но есть несколько проблем.
- Дрожание рук вызывает мерцание картинки
- Телефон держится под наклоном. Поэтому, при запуске программы лучше считать, что в этом положении взгляд пользователя был направлен по нормали к плоскости телефона, а все изменения сенсоров отсчитывать от этого начального значения.
Таким образом, общий подход к решению данной задачи такой:
Разбить фон на неско
- лько слоёв, которые будут двигаться друг относительно друга. Это может быть текст, фоновые изображения или же специальные элементы созданные для придания «глубины»
- Подписаться на события акселерометра.
- При изменении акселерометра:
- Подкоректировать показатели сенсоров относительно начальных значений.
- Сгладить колебания от дрожания рук
- Сдвигать каждый слой на свою величину.
- Сделать сенсоры опциональными: не всем пользователям может понравится такой сюрприз.
Итак, в нашем случае у нас есть изображение фона, + 2 слоя звезд, + слои различных элементов экрана.
Для нужд фильтрации устанавливаем некоторые константы:
#define kUpdateFrequency 240.0 //частота обновления фильтра
#define kCutoffFrequency 5.0 //частота фильтрации
#define kAccDataScale 2.2 //масштаб данных
При запуске программы мы инициализируем низкочастотный фильтр для того чтобы убрать дребезжание фона, вызванное дрожанием рук.
про него можно почитать вот здесь
и скачать пример реализации у Apple здесь
-(void)awakeFromNib {
filter = [[LowpassFilter alloc] initWithSampleRate:kUpdateFrequency/10 cutoffFrequency:kCutoffFrequency]; //собственно фильтр низких частот
if ([Options instance].useSensors) {
[self performSelector:@selector(startAcc) withObject:nil afterDelay:4]; // стартуем с запаздыванием чтобы программа успела запуститься и заполнить нормальные начальные значения акселерометра
}
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didGoToBG:) name:UIApplicationDidEnterBackgroundNotification object:nil]; //программа ушла в фон
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didResume:) name:UIApplicationDidBecomeActiveNotification object:nil]; //Программа стала активна
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(useSensorsChanged) name:@"useSensors" object:nil]; //подписываемся на изменения опции
}
Реакцию на изменение опции по вкл/выкл 3д эффекта фона и на сворачивание программы в фон описывать думаю не стоит, там все банально
Теперь рассмотрим функцию которая запускает всю эту красоту
P.S. Используется ARC потому, никаких retain или release
-(void)startAcc {
@try {
if (!acc) {
startY = 100; //это стартовое положение сенсоров по Y чтобы не приходилось постоянно держать устройство в горизонтальном положении
acc = [UIAccelerometer sharedAccelerometer];
acc.updateInterval = 1/kUpdateFrequency;
acc.delegate = self;
}
}
@catch (NSException *exception) {
NSLog(@"startAcc %@",exception);
}
}
Чтобы остановить все это дело
-(void)stopAcc {
@try {
if (acc) {
acc.delegate = nil;
acc = nil;
[self resetState];
}
}
@catch (NSException *exception) {
NSLog(@"stopAcc %@",exception);
}
}
Дальше нам остается только ловить событие обновления акселерометра
- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration {
@try {
[filter addAcceleration:acceleration]; //фильтруем данные чтобы избежать «тряски» фона
double x = filter.x*kAccDataScale; //масштабируем данные чтобы увеличить частоту
double y = filter.y*kAccDataScale;
if (startY == 100) {
startY = acceleration.y*kAccDataScale; // запоминаем начальные данные по Y
}
// убираем лишнее по X
if (x > 1) {
x = 1;
}
// учитываем начальное значение
y = (y - startY);
// убираем лишнее по Y
if (y > 1) {
y = 1;
}
//Сдвигаем необходимые элементы
[self setOffsetElementForX:x Y:y];
}
@catch (NSException *exception) {
NSLog(@"accelerometer %@",exception);
}
}
@end
И, наконец, в функции сдвига мы передвигаем разные слои на разную фиксированную величину. Поэтому, верхние элементы двигаются с меньшей скоростью чем фон что и создает эффект 3Д. При этом, все элементы должны сдвигаться в сторону поворота устройства.
-(void)setOffsetElementForX:(double)x Y:(double)y {
@try {
allEll.transform = CGAffineTransformMakeTranslation(-8.5*x, 11.5*(y - startY));
movingButtonsView.transform = CGAffineTransformMakeTranslation(-8.5*x, 11.5*(y - startY));
arcView.transform = CGAffineTransformMakeTranslation(-8.5*x, 11.5*(y - startY));
bgView.transform = CGAffineTransformMakeTranslation(-17*x, 23*(y - startY));
bgStar1View.transform = CGAffineTransformMakeTranslation(-13*x, 18*(y - startY));
bgStar2View.transform = CGAffineTransformMakeTranslation(-10*x, 13*(y - startY));
allEll2.transform = CGAffineTransformMakeTranslation(-17*x, 21*(y - startY));
}
@catch (NSException *exception) {
NSLog(@"offsetElementForX %@",exception);
}
}
Эти 4 простых шага позволят сделать программы привлекательнее, интереснее и инновационее. 21-й век всё-таки. Помните, что эффектный и стильный дизайн может положительно повлиять на оценку вашего приложения ревьюверами, а также выделить их из сотен тысяч конкурентов. Мы в Lifeware Solutions (http://www.lifewaresolutions.com/) активно занимаемся исследованиями и будем рады, если наш опыт будет вам полезен. Буду очень благодарен за ваши отзывы.
Автор: VasKravchuk