imageВ нашем дворе всегда играли в «Мордобойку». А в Москве, оказалось, её называют «Дуля».

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

Ба-ац! Бу-ум! Йо-пт…

Вытерев следы чужой крови на руках, я сел за клавиатуру и твердо решил. Сейчас! Или никогда. И написал игру под iOS — встречайте Мордобойку, берегите руки, Сеня.

Как я моделировал движение мяча и веревки, грабил уже краденное, оптимизировал код под iPhone 4S, вспоминал (и тут же забывал) уравнения Лагранжа второго рода в обобщенных координатах, экспроприировал картинки и звуки Му — читайте в нашем журнале.

Итак, Мордобойка была..., кстати, а что такое Мордобойка?

Кратко о правилах


Мяч болтается на длинной веревке. Второй конец веревки закреплен на верхней точке столба. Столб высотой не менее 6 метров. Игроки бьют по мячу руками (как в волейболе) и пытаются закрутить веревку вокруг столба — каждый в свою сторону. На чужую половину заходить нельзя. Зацепы, двойные удары запрещены.

Моделирование движения сферического маятника


Почему-то я считал, что движение сферического маятника очень легко алгоритмизировать. Расписываем силы, действующие на маятник. Сила тяжести и реакция натяжения нити.

// X, Y, Z - координаты мяча, где Y - высота над уровнем моря, она же вертикальное смещение по экрану смартфона, X - горизонт смартфона
// L - длина нити
// R -расстояние от мяча до точки крепления нити (0,0,0)
R = sqrt(X*X+Y*Y+Z*Z);
// g - ускорение свободного падения в пикселах в секунду за секунду. Типичное значение для Земли 9.8 м/с^2, для экрана смартфона 400 п/с^2
// G - сила тяжести
// N - реакция нити
// U, V, W - скорости точки, VB - абсолютное значение скорости
VB = sqrt(U*U+V*V+W*W);
G=g;
N=g*Y/R+VB*VB/R;
// нить мягкая, то есть мнется легко
if (N<0) N=0;


Вычисляем скорости по каждой оси, исходя из второго закона Ньютона.

// dt - шаг по времени, в моем случае dt=0.025 сек, что соответствует 40 кадрам в секунду
U += -dt*N*X/R;
W += -dt*N*Z/R;
V += dt*(G - N*Y/R);

И находим новые значения координат мяча.
X += dt*U;
Y += dt*V;
Z += dt*W;

В верхней полусфере реакция нити может быть отрицательной, что означает режим свободного падения. Соответственно, во всех случаях, когда R<L, реакцию веревки обнуляем.

Все просто, не так ли? Однако бытовой точности моей численной схеме не хватило. К 10-15 секунде игры накапливались бешеные ошибки вычислений. Схема разностная. А это залет, воин.

Я стал хитрить. Рисовал сцену по-прежнему 40 раз в секунду, а вычисления делал в 10 раз чаще, соответсвенно уменьшив шаг интегрирования с 0.025 до 0.0025. Стало лучше, но ненадолго.

Поменял float на double. Стало чуть лучше, но нет. В 100 раз? Нет, Карл. Путь в никуда.

На этом первый день моего программирования завершился, параллельно я завел два проекта Reel the Rope и Face Off в Apple Store, стащил картинки мяча и веревки у Зептолабова, звуки из приложений VolleyBeach и BasketBall, и выбрал главного героя, которого надо победить. В первом варианте это был Болванчик, стыренный из статьи на Хабре, опубликованной в этот день. Я ленив, что было под рукой — то и использовал.

image
Рисунок 1. Главный соперник Болванчик

Забыл, чей блог, поэтому благодарю авторов идеи заочно.

Песок и небо вытащил из первой Angry Birds и сразу сотворил две заставки для старых iPhone 960 на 640, и для новых 1136 на 640.

image
Рисунок 2. Заставка в игре Мордобойка

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

Изображение мяча я решил поменять — все таки Зептолабов известный чувак, может и заяву в органы накатать. Я, как бывший чемпион Челябинска-70 по волейболу, знаю толк в мячах и поэтому только Mikasa.

Набрав запрос на картинки мячей Микаса, я долго хохотал, упав под стол.

Оставляю на ваш суд, какая картинка первична.

image

День второй


Проснувшись утром и плотно позавтракав, я уж было собрался пообедать. Как вдруг позвонил Владяс. У Владяса голова — круглая как шар, и я понял: только сферические координаты.

Бросился к клавиатуре и уничтожил все декартовое, все что написал в первый день. Именно не забил комментариями, а грубо стер. Нах. Никогда не храните старые лыжи на балконе. Это вам любой пожарный скажет. Итак, вводим два переменных угла и оставляем в качестве третей переменной радиус-вектор R.

image

Уравнения стали проще, но подводные камни — острее. Задание удара по мячу — это просто песня. Всего лишь
U_FI = hit_force;

Просто присваиваем константу величине скорости по углу фи. Не то что в декартовой системе!, где 1) вычисляешь вектор направления веревки, 2) вычисляешь к нему вектор нормали, 3) присваиваешь пропорционально три значения силы удара трем компонентам вектора скорости. Да. Но.
Опытные диф-гемщики уже хихикают надо мной, предвкушая ад формул прямолинейного движения в сферической системе координат. А я наступил в этот ад. Прошел его и больше не хочу. Плюс проблемы вычислений на полюсах — отдельная тема. Все удобства сферы оказались иллюзией.

В итоге, я отказался от углов и, изрядно ошеломленный радианами, вернулся к Декарту. Прости меня, брат Декарт. Декарт простил. И познакомил со своим другом Лагранжем. А Лагранж не подкачал, знаете. Избавил меня от проблем численной схемы, предложив на каждом шаге обнулять дисбаланс энергии. Мы помним, что полная энергия системы в отсутствии сил диссипации постоянна. Она состоит из кинетической и потенциальной энергии.
E = EKIN+EPOT;
EKIN = VB*VB/2;
EPOT= g*Y;

И в потенциальном поле силы тяжести энергия замкнутой системы не меняется.
Что это значит? На каждом шаге вычисляем новые скорости и координаты мяча согласно формулам, приведенным выше.
Затем считаем полную энергию системы на новом счетном шаге. Вычисляем дисбаланс и поправляем новые скорости, чтобы схема была консервативна по энергии.
// E - полная энергия системы на предыдущем шаге по времени
EKIN = U*U+W*W+V*V;
EPOT= 2*g*dt*V;
E2 = EKIN+EPOT; // новая энергия на следующем счетном шаге
 temp = sqrt(E/E2);
 U *= temp;
 V *= temp;
 W *= temp;
 


И все залетало! 40 кадров в секунду, double снова был заменен обратно на float, никаких лагов и ляпов не обнаружено на реальных смартфонах. Да, одним из требований была быстрая работа приложения на старом iPhone 4S. Также у меня есть iPod Touch 5(6?), который еще медленнее, но прекрасно живет с iOS 9.0.2. И знаете, айподик чудесно справился с расчетами.

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

Картинки были доработаны и даже поменяны.
Главный герой довольно смешно раскачивался и подвывал, но бил по мячу исключительно головой. Я решил заменить его на что-нибудь с руками. Руки — это проблема художников. Венера Милосская подтвердит. В интернете подходящих спрайтов не нашел, пришлось взять с полки отечественного Вини-Пуха, лапы которого легко крутить и анимировать.
image
Рисунок 3. Вини-Пух состоит из шести спрайтов — 4 лапы, глаза и все остальное.

Пух довольно бодро прыгал, но в пейзаж Angry Birds не вписывался.

День третий



Продолжение следует.

Короткое видео для нетерпеливых

Комментарии (4)


  1. mickvav
    15.10.2015 15:54

    Гугл на метод Рунге-Кутты 4-го порядка.


  1. GamePad64
    15.10.2015 19:01
    +1

    Именно не забил комментариями, а грубо стер.

    А ещё лучше использовать систему контроля версий. Тогда можно всегда с лёгким сердцем стирать всё, что не нужно сейчас.


    1. PapaBubaDiop
      15.10.2015 20:36
      +1

      Разумеется, но после первых набросков приходит вселенское осознание проекта и необходим полный рефакторинг. В результате я вернулся к декартовым координатам, переписал код с нуля в третий раз и он стал в 5 раз меньше, чище и надежнее. Все оказалось настолько просто, что я запел песню.
      И еще.
      Идея разбить статью на несколько частей оказалась неудачной, видимо надо все сразу, уберу ка я первую часть.


      1. stychos
        16.10.2015 00:55
        +3

        Да, статья внезапно прекратилась, с нетерпением хотел дочитать до конца, а не тут-то было =) Но всё равно интересно!