+(AppStore *) Доведи меня домой
Сразу хочу признаться, идею этого приложения я позаимстовал в одной flash-игрушке, ровно как и саму идею заимствовать идеи во флэш-играх — как то прочел в одном блоге сетования товарища на тему того, что какие-то нехорошие люди под кальку переделали его флэш-игру на iOS и срубили нехило деньжат. Но я не такой подлый коварный, поэтому свое приложение я сделал бесплатным, не смотря на то, что на его создание ушло почти три месяца. Почему я решил делать именно «пьяницу» (рабочее название)? В оригинальной игре нужно было удерживать от падения нестабильного персонажа, двигая мышку вправо-влево. Мне показалась очень заманчивой идея воспроизвести это же, но только с использованием акселерометра (устройство, с помощью которого iPhone знает свое расплолжение в пространстве) — идеально укладывается в концепцию iOS-приложения. К тому же с моего предыдущего приложения, в котором акселерометр использовался весьма примитивно, у меня было сильное желание более тщательно изучить приемы работы с акселерометром. Ну и плюс еще добавил несколько фич, например, бутылки на дороге, на которых человек может поскользнуться — их нужно отшвыривать. Поэтому изучить пришлось гораздо больше, и сейчас я попробую это изложить.
Для начала традиционно ссылка на приложение TakeMeHome avialable on the AppStore.
Ну и скриншотик:
И так. Начну, пожалуй, с OpenGL, ибо пришлось его использовать. До сей поры я боялся к нему подступиться, так как считал это уделом крутых game-девелоперов, пишущих 3d-движки, но при отрисовке сцены столкнулся с проблемами нехватки ресурсов, а также производительности процессора arm, которым комплектуются iPhone-гаджеты. Стены домов являются зацикленными, то есть через какое-то время текстура повторяется. Изначально я хотел реализовать это с помощью растровой графики, то есть, имея картнику png, которая из себя представляет сцену с перспективой, добавляем UIImageView, а далее масштабируем его по таймеру (для этого достаточно просто задать новый размер через метод setFrame) , создавая тем самым эффект движения. Когда картинка уменьшается на 50%, возвращаем его в исходный масштаб и таким образом зацикливаем. Когда я уже реализовал данный подход, решил посмотреть, сколько же памяти нужно приложению, ибо закралось подозрение, что картинка разрмером 2500×900 с 32-х разрядным цветом скушает немало памяти. Так и оказалось — 20 мегабайт мне показалось многовато для такого незамысловатого приложения. Было решено искать другие способы. Первым способом стало использование Quartz — это графическая система унаследованная от Mac OS X. С ее помощью можно рисовать, например, примитивы. На самом деле, в отличие от того же Windows GUI, там нет, например, такой функции, как DrawRectangle (отрисовать прямоугольник). Там все основано на путях — кривых Безье (Bezier Path). К слову сказать, сейчас вообще почти вся векторная графика реализована на кривых Безье. Для своей задачи мне было достаточно примитива всего одного типа — закрашенного в определенный цвет четырехугольника. Идея такая: переводим растровое изображение в модель — набор точек, соединив которые получаем исходное изображение. Для создания модели пришлось накидать небольшое приложеньеце под Windows.
Вот пример реализации отрисовки сцены с помощью QuartzCore:
-(void) drawWalls: (CGContextRef) ctx { float sh[8] = {0, 0, 0, 0, 0, 0, 0, 0}; BOOL inframe; for (int i = 0; i < SHAPE_COUNT * 1; i++) { for(int j=0;j<8;j++) sh[j] = (float)curr_model[i][j]; for(int j=0;j<8;j++) { if (j%2==0) sh[j] -= currentFrame.origin.x; else sh[j] -= currentFrame.origin.y; } inframe = NO; for(int j = 0; j<4;j++) if (sh[j * 2] > 0 && sh[j * 2] < gadget_width && sh[j * 2 + 1] > 0 && sh[j * 2 + 1] < gadget_height) { inframe = YES; NSLog(@"inframe = YES"); break; } //Отрисовка закрашенного прямоугольника if (inframe) { CGContextMoveToPoint(ctx, sh[0], sh[1]); CGContextAddLineToPoint(ctx, sh[2], sh[3]); CGContextAddLineToPoint(ctx, sh[4], sh[5]); CGContextAddLineToPoint(ctx, sh[6], sh[7]); CGContextAddLineToPoint(ctx, sh[0], sh[1]); CGContextSetFillColorWithColor(ctx, [self colorFromRGB : model[i][8]]); CGContextFillPath(ctx); } } } - (void)drawRect:(CGRect)rect { // Получаем текущий графический контекст CGContextRef ctx = UIGraphicsGetCurrentContext(); // Масштабируем модель (упущено) [self scaleModel]; // Рисуем [self drawWalls :ctx]; }
Идея простая: храним модель в виде массива точек, на каждом такте игры масштабируем модель, бежим по массиву через четыре точки, соединяем их кривой, закрашиваем путь. Сделал, запустил, круто пашет… в симуляторе. Запустил на девайсе — ужас. Тормоз несусветный. FPS просто никакой, плюс все приложение дико тормозит. К тому же сам девайс начинает греться, в общем кошмар, понимаю, что Quartz не подходит для этих целей. Его удел — всякие там менюшки, кнопочки, в общем нечто не динамичное. Даже реализовав отрисовку только тех элементов, которые попадают на экран (if (inframe)…), картина не изменилась.
Что же делать, второй подход тоже не сработал? Немного погуглив нашел способ использовать PDF. Не буду долго расписывать мытарства с пдф, суть в том, что я перегнал растровое изображение сцены в PDF и попробовал с ним поэкспериментировать. Для отображения пдф-документов в CocoaFramework существует набор классов. Чтобы не заморачиваться, можно использовать самую внешнюю обертку — компонент UIWebView. Мне он не подошел, потому что не ресайзил мой пдф до нужных мне размеров, да и вообще не адекватно себя вел. И так, третий подход тоже не прокатил. Оставался только OpenGL.
Я как мог до последнего оттягивал знакомство с OpenGL, хотя знаю о его существованиии еще с конца 90-х годов двадцатого века, но, почитав теорию и пару-тройку туториалов (вот один из них — весьма неплохая подборка тутроиалов), понял, что черт вовсе не так уж страшен, как его малюют. В iOS используется OpenGL ES (Open Graphics Library for Embedded Systems) — немного упрощенная версия библиотеки для встраиваемых систем. OpenGL использует графический чип, за счет чего осуществляется акселерация.
Самое главное, что нужно понять в OpenGL, это система координат (она трехмерная) и принципы задания моделей. Для первого знакомства хватит. Начало системы координат физически находится в центре экрана, то есть точка с координатами x=0, y=0, z=0 находится как бы за наблюдателем уже. Оси X и Y направлены так же, как и в двумерной системе, ось Z направлена из глубины экрана в сторону наблюдателя. В OpenGL есть три основных примитива — точка, отрезок и треугольник. Модель задается массивами. Массив может содержать как точки, так, например и цвета. Причем массивы используются преимущественно одномерные. Например, элементы с нулевого по восьмой (всего девять) содержат три точки треугольника (по три координаты на точку), а девятый элемент содержит RGBA-цвет треугольника. Поскольку у меня уже была реализована модель сцены в псевдо-3d, я решил и далее обходиться без третьего измерения. Тем не менее, координату Z пришлось задействовать — эффект движения сцены как раз и реализуется за счет декремента Z-кооринаты. Таким образом сцена удаляется от наблюдателя. Когда она достигает определенной дистанции по оси Z, модель снова возвращается в исходную точку и анимация зацикливается. У меня это в итоге выглядит так:
- (void)layoutSubviews { [EAGLContext setCurrentContext:context]; [self destroyFramebuffer]; [self createFramebuffer]; [self drawView]; } - (BOOL)createFramebuffer { glGenFramebuffersOES(1, &viewFramebuffer); glGenRenderbuffersOES(1, &viewRenderbuffer); glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer); glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer); [context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(CAEAGLLayer*)self.layer]; glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer); glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth); glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight); if (USE_DEPTH_BUFFER) { glGenRenderbuffersOES(1, &depthRenderbuffer); glBindRenderbufferOES(GL_RENDERBUFFER_OES, depthRenderbuffer); glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES, backingWidth, backingHeight); glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthRenderbuffer); } if(glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) { NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES)); return NO; } return YES; } - (void)setupView { const GLfloat zNear = 0.1, zFar = 1000.0, fieldOfView = 60.0; GLfloat size; glEnable(GL_DEPTH_TEST); glMatrixMode(GL_PROJECTION); size = zNear * tanf(DEGREES_TO_RADIANS(fieldOfView) / 2.0); // This give us the size of the iPhone display CGRect rect = self.bounds; glFrustumf(-size, size, -size / (rect.size.width / rect.size.height), size / (rect.size.width / rect.size.height), zNear, zFar); glViewport(0, 0, rect.size.width, rect.size.height); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); } - (void)destroyFramebuffer { glDeleteFramebuffersOES(1, &viewFramebuffer); viewFramebuffer = 0; glDeleteRenderbuffersOES(1, &viewRenderbuffer); viewRenderbuffer = 0; if(depthRenderbuffer) { glDeleteRenderbuffersOES(1, &depthRenderbuffer); depthRenderbuffer = 0; } } - (void)dealloc { [self stopAnimation]; if ([EAGLContext currentContext] == context) { [EAGLContext setCurrentContext:nil]; } [context release]; [super dealloc]; } - (void)drawView { [EAGLContext setCurrentContext:context]; glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer); glViewport(0, 0, backingWidth, backingHeight); glClearColor(0.0f, 0.0f, 0.0f, 0.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glMatrixMode(GL_MODELVIEW); if (inProgress) if (current_z < final_z + 3) { current_x = init_x; current_y = init_y; current_z = init_z; step_z = 0.15; } else { current_z-=step_z; current_x-=step_x; current_y-=step_y; } current_x = init_x - (-(final_x - init_x) * (-(current_z - init_z)/-(final_z - init_z))); current_y = init_y - (-(final_y - init_y) * (-(current_z - init_z)/-(final_z - init_z))); step_z += scale_step; GLfloat scale = 1; GLfloat zc = -1.0; glEnableClientState(GL_VERTEX_ARRAY); for (int i=0; i<MODEL_COUNT * 2;i++) { zc = -1.0 + (model[i][11] * (-current_z )* 0.005); squareVertices[0] = model[i][6] * scale; squareVertices[1] = model[i][7] * scale; squareVertices[2] = zc; squareVertices[3] = model[i][4] * scale; squareVertices[4] = model[i][5] * scale; squareVertices[5] = zc; squareVertices[6] = model[i][0] * scale; squareVertices[7] = model[i][1] * scale; squareVertices[8] = zc; squareVertices[9] = model[i][2] * scale; squareVertices[10] = model[i][3] * scale; squareVertices[11] = zc; glLoadIdentity(); glColor4f(model[i][8], model[i][9], model[i][10], 1.0); glTranslatef(current_x, current_y, current_z); glVertexPointer(3, GL_FLOAT, 0, &squareVertices[0]); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } glDisableClientState(GL_VERTEX_ARRAY); glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer); [context presentRenderbuffer:GL_RENDERBUFFER_OES];
Вот такие дела. Единственное, что омрачало мое первое знакомство с OpenGL ES, так это то, что в нем не поддерживается antialiasing (сглаживание) для отрисовки линий и треугольников. Очень много искал на эту тему и экспериментировал, в частности много советов мне дали на популярном ресурсе stackoverflow, но времени убил на это много, поэтому решил оставить до следующего раза. К тому же в iOS4 уже есть поддержка antialiasing, так что не факт, что следующий раз когда-нить будет. Также не забываем подключить к проекту соответствующий фреймворк (OpenGLES), иначе компилятор будет ругаться.
Следует заметить, что различные transcitions (повороты, перемещения, масштабирования UIView и CALayer) также акселерируются, но уже без участия программиста (behind the scene). Это оказалось весьма кстати, так как большинство других элементов игры я реализовал именно через transitions. Например, облака на заднем плане, появляющиеся на дороге бутылки, их отшвыривание, разлетающиися элементы меню, и т.д. Да собственно, и перемещения самого человека сделано также через transitions. На следующей иллюстрации видно, что из себя представляет игра — это набор UIView, наложенных друг на друга.
Каждый такой уровень очень удобно реализовывать в виде потомка класса UIView.
Вот, к примеру, разворот человека. Имеем класс ManView наследник UIView. Все, что связано с человеком, реализовано в этом классе. Имеется базовый угол — на который он в данный момент отклонен, от него строятся все остальные элементы (туловище, ноги, руки, голова):
-(void) rotateMan { if (body_angle == prev_body_angle) return; float x_offs = 240 + rotationSchwanker; if (!inProgress) x_offs = 240; float r_angle = (body_angle - prev_body_angle ) * M_PI / 180.0; CGAffineTransform tr = body.transform; tr = CGAffineTransformRotate(tr, r_angle); body.transform = tr; tr = legs.transform; tr = CGAffineTransformRotate(tr, r_angle); legs.transform = tr; tr = leftArm.transform; tr = CGAffineTransformRotate(tr, r_angle); leftArm.transform = tr; tr = rightArm.transform; tr = CGAffineTransformRotate(tr, r_angle); rightArm.transform = tr; [self posManParts : x_offs]; if (prev_body_angle==-1&&body_angle==0) leftArmCenter = leftArm.center;//origLeftArmFrame = leftArm.frame; prev_body_angle = body_angle; }
Очень важное замечание, второй параметр функции CGAffineTransformRotate — это не новый угол, а изменение относительно предыдущего положения. Для этого и пришлось вводить переменную prev_body_angle — это instance variable класса.
При реализации облаков нашел для себя простой способ анимации. Решил я декорировать сцену проплывающими облаками. Но как-то лениво было писать обработчик таймера, смещать вручную облако, смотреть, не ушло ли оно за край экрана, перемещать на начало… Использовал стандартную анимацию. Задаем координаты объекта в начале анимации и в конце, задаем длительность анимации и запустили ее(анимацию) — система все сделает сама. Правда, возник один нюансик — нужно было отследить окончание анимации, чтоб запустить ее заново. Для этого нужно объявить объект делегатом анимации и передать селектор окончания (callback)
-(void)animationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context { //Зацикливаем анимацию [self posShape:cloud1 :-40 - rnd(20) :20 + rnd(10)]; [UIView beginAnimations:nil context:NULL]; [UIView setAnimationDelegate :self]; [UIView setAnimationDidStopSelector :@selector (animationDidStop:finished:context:)]; [UIView setAnimationDuration:25+ rnd(5)]; [self posShape:cloud1 :self.frame.size.width :self.frame.origin.y]; [UIView commitAnimations]; } - (void)startAnimation { [UIView beginAnimations:nil context:NULL]; [UIView setAnimationDelegate :self]; [UIView setAnimationDidStopSelector :@selector (animationDidStop:finished:context:)]; [UIView setAnimationDuration:30]; [self posShape:cloud1 :self.frame.size.width :self.frame.origin.y]; [UIView commitAnimations]; }
Вот такая премудрость. Она мне также понадобилась для анимации отшвыривания бутылки и еще кой-чего.
По поводу зацикливания… Встала необходимость зациклить звук (мелодию). Оказалось, что сделать это с помощью системных функций и компонента, который я использовал в своих предыдущих приложениях, задача не очень тривиальная. Погуглив, нашел неплохое решение, основанное на фреймворке AVFoundation — компонент AVAudioPlayer. В частности, с помощью него стало возможным беспроблемно выставлять уровень громкости для разных звуков.
Для удоства работы со звуком я накидал отдельный класс.
AVAudioPlayer* createSysSnd(NSString * snd_name) { NSString *soundFilePath = [[NSBundle mainBundle] pathForResource: snd_name ofType: @"wav"]; NSURL *fileURL = [[NSURL alloc] initFileURLWithPath: soundFilePath]; AVAudioPlayer *snd = [[AVAudioPlayer alloc] initWithContentsOfURL: fileURL error: nil]; [fileURL release]; [snd_name release]; return snd; } -(id) initAudio { // sounds - массив объектов класса AVAudioPlayer sounds[WAV_BELCH] = createSysSnd(@"belch"); sounds[WAV_BELCH].volume =0.4; // выставляем громкость. 1 - громко, 0 -тихо ... } // проигрывание петель -(void) playLoop :(int) snd { if(sounds[snd]==nil) return; if (!settings.withSounds) return; // если numberOfLoops = -1, звук будет проигрываться бесконечно. sounds[snd].numberOfLoops = -1; sounds[snd].currentTime = 0; [sounds[snd] play]; } -(void) playSound :(int) snd { if( sounds[snd]==nil) return; sounds[snd].currentTime = 0; currentSound = snd; [sounds[snd] play]; } // Полезная функция. Запускает одиночную вибрацию гаджета - (void)vibrate { AudioServicesPlaySystemSound (kSystemSoundID_Vibrate); }
Решил сделать так, чтоб при падении человека гаджет начинал вибировать. Это делается просто, как показано выше (метод vibrate). При реализации звукового сопровождения также столнкулся с необходимостью отслеживать окончание воспроизведения звука (например, чтобы звук глотания жидкости не пересекался с пением). Собственно, поэтому и пришлось использовать AVAudioPlayer. Для этого нужно чтобы класс, в котором используются объекты AVAudioPlayer был делегатом AVAudioPlayerDelegate.
@interface AudioEngine : NSObject (AVAudioPlayerDelegate) { }
Таким образом остается только реализовать метод
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *) player successfully: (BOOL)flag { }
и отлавливать окончание проигрывания звука.
Самое важное, ради чего я взялся за это приложение — работа с акселерометром. Взаимодействие приложения с данным устройством осуществляется через делегат UIAccelerometerDelegate. Делаем главный контроллер приложения делегатом акселерометра
@interface takemehomeViewController : UIViewController (UIAccelerometerDelegate){ }
, говорим системе, что наш контроллер — делегат акселерометра
- (void)viewDidLoad { [super viewDidLoad]; [[UIAccelerometer sharedAccelerometer] setDelegate:self]; }
и реализуем методы этого делегата. Собственно, он всего один
- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration { // Get the current device angle float xx = -[acceleration x]; float yy = [acceleration y]; float zz = [acceleration z]; float angle = atan2(yy, xx); float angle2 = atan2(yy, zz); float angle3 = atan2(xx, zz); float value = (angle * FILTERFACTOR) + (previousValue * (1.0 - FILTERFACTOR)); previousValue = value; [sceneView setAngles:angle :angle2 :angle3]; }
акселерометр — вещь весьма чувствительная, поэтому сглаживаем колебания по средствам
#define FILTERFACTOR 0.1
Не буду вдаваться в физику измерения ускорения, в официальном документе все хорошо рассказано, скажу лишь, что значения x, y и z означают ни координаты устройства в прострастве, и не углы поворота. Вот неплохая статья на хабре, ее я и брал за основу.
Ну и еще из мелочей… Впервые использовал компонент UITextView — для ввода имени пользователя с виртуальной клавиатуры (для таблицы рекордов). Узнал, как сделать ограничение максимального количества символов. Какого-то специального свойства для этого не существует, нужно отлавливать событие нажатия softkey, и сканировать длину введенной строки. Для этого класс, обрабатывающий события UIView должен быть делегатом UITextFieldDelegate и реализовывать метод:
- (BOOL)textField:(UITextField *)tf shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string{ return !(tf.text.length >= MAX_LENGTH && range.length == 0) }
Ну хорошо, ограничить-ограничили, но ведь можно ввести строку 111111111111111, а можно и ЖЖЖЖЖЖЖЖЖЖЖЖЖЖЖЖЖЖ, поскольку шрифт не моноширинный, строка при отрисовке может вылезти за рамки фрейма, и будет не красиво — я бы даже сказал отстойно. Проблему помог решить самый банальный класс NSString — оказывается, он имеет на борту обширный инструментарий для отрисовки.
UIFont *font = [UIFont systemFontOfSize:12.0f]; [score.name drawAtPoint:CGPointMake(16.0f + (img.size.width + 14) * j, -500.0f + 21 * i + 39) forWidth: img.size.width - 30 withFont:font lineBreakMode: UILineBreakModeTailTruncation];
При таком раскладе, если строчка не влезает в указанные рамки, она образается на манер ЖЖЖЖЖЖЖ…
Проверяя приложения на утечки был приятно удивлен, что оных почти не оказалось — я думал, будет дыра на дыре. Единственная утечка была в каком-то блоке GeneralBlock-3584, стек вызовов которой уходил корнями в AudioToolbox Framework. Причем, утечка возникала только при отладке на девайсе, так что запускать приложение для отладки из под симулятора или на девайсе — все таки есть разница (в одном из предыдущих постов я предполагал обратное). Погуглив, узнал, что, оказывается у инструментов случаются ложные срабатывания. На том успокоившись, залил бинарник на модерацию. Мое предположение подтвердили специалисты Apple, не найдя никаких утечек и разместив 6 января приложение в AppStore. Кстати, в этот раз приложение достаточно быстро было промодерировано — всего два дня. Возможно, это связано с новогодними каникулами.
Не хотелось в новый год вступать с психологическим грузом неопубликованного приложения, разработка которого велась на протяжении двух с половиной месяцев, поэтому я пошел на хитрость — подготовил описание, скриншоты, и т.д 31-го декабря, а бинарник залил 4-го))
В первый день было зафиксировано 139 скачиваний. Потом, конечно, статистика пошла плавно на убыль. Сейчас уже на протяжение недели она держится на уровне 50 скачиваний в сутки.
Приложение находится в топе в двух категориях бесплатных приложений. Посмотрим, что будет дальше.
PS. Аццкий WP-Syntax некорректно отображает треугольные скобки <>, поэтому примеры кода местами криво выглядят. Постараюсь к следующему разу разобраться.
Очень интересные статьи у тебя получаются. По предыдущим темам попытался приложение выложить в AppStore, почти получилось, но им не понравилось))) так что буду тоже что-нибудь пробовать делаь.
У меня вопрос, как можно получать прибыль с таких приложений? Я должен помимо того чтобы придумать какое-то приложение, еще и быть зарегистрирован как, хотябы, Индивидуальный предприниматель что ли?
Спасибо.
Прибыль с бесплатных приложений можно получать как минимум двумя способами:
1. Спустя какое-то время выложить обновление с какой-нить платной фичей.
2. Участвовать в сети iAd (контекстная реклама в приложениях).
По второму пункту собираюсь в скором времени попробовать, только есть один нюанс — это возможно только в iOS 4 и выше. То есть придется покупать iPhone4, а может лучше iPad.
Ну и конечно же остается вариант написать супер полезное и оригинальное приложение, и продавать его.
Про индивидуального предпринимателя… я нигде не слыхал, что нужно им становиться. Просто Эппл переводит заработанные деньги на твой счет каждый квартал. Пока доходы не большие, никому ты не интересен, я так считаю.
Хорошо, ты в одной статье описывал то, как ты звонил в налоговую, для того чтобы не списывали 30% американского налога.
А как ты думаешь, Apple или американская налоговая, отсылает информацию в нашу налоговую о том что у тебя идет доход?
Просто если по нашему законодательству действовать, мне кажется что такую прибыль надо декларировать. А как физическое лицо этого не возможно сделать.
Правда если прибыль будет небольшая, к тебе и никаких претензий не будет. Тебя даже не заметят. Ну а если там пойдет хороший доход, тогда могут быть претензии к тому что это нелегальный доход.
Вот я и хочу это где-нибудь узнать, как правильно это сделать?
Признаюсь честно, юридический аспект этого дела я пока что глубинно не изучал в силу отсутствия времени, но он меня тоже интересует. Могу лишь сказать, что по итогам моего первого квартала, доход составил 70% от моих продаж. 30% отошли Эпплу как комиссия за предоставление своей площадки. Американского налога не взималось.
По поводу американской налоговой. Да, я звонил, но куда сообщить этот номер EIN я так и не нашел. Говорят, раньше он был в iTunes Connect как часть интерфейса, но я все перерыл и не нашел. Уже не раз встречалось упоминание, что сейчас EIN уже не актуален.
Я бы пока не стал заморачиваться с ИП.
Ок. Буду пробовать!)