История Лошарика

Предисловие


imageБыл вечер четверга, когда нам с коллегой spiff пришла в голову идея написать OpenSource игру на прогрессирующем в наше время HTML5, как говорится, from scratch и just for fun.

Так как мы работаем в области системного программирования и опыта разработки web-приложений у нас было совсем немного, было решено реализовать достаточно простой клон, всемирно известной и популярной игры для первых телефонов Nokia — RapidRoll.

Спустя неделю мы выпустили первый стабильный релиз, и готовы поделиться первым полученным опытом.



Шаг 1. Дизайн-документ


Любая игра, которая завоюет мир, на наш взгляд, должна начинаться с дизайн-документа. Обычно он состоит из следующих пунктов:
  • схема игры (игровой процесс, цель игры и что мешает её достижению);
  • интерфейс (основные элементы графики);
  • игровая механика (устройство игрового мира, физика, взаимодействие объектов и т.д.);
  • программные алгоритмы (высокоуровневое описание основных игровых алгоритмов);
  • звуки и музыка;
  • игровой мир (игровые персонажи и их взаимодействие);
  • участники и сроки;

Смысл игры заключается в удержании Лошарика в видимой части экрана, путем его перемещения по поднимающимся платформам, предотвращая его падение или уход за верхнюю границу экрана.

Ссылка на дизайн-документ

Шаг 2. Архитектура


Архитектура приложения отображена на следующей диаграмме:

image

Как видно, диаграмма напоминает модель MVC. Имеются следующие основные классы:
  • Game. Основной класс игры, отвечает за взаимодействие остальных классов;
  • GameModel. Модель игры, отвечает за формирование игровых сцен;
  • KeyboardController. Один из возможных контроллеров для управления игрой с клавиатуры;
  • HTML5View. Одно из возможных представлений игры. Отрисовывает сцены, создаваемые моделью на HTML5 canvas, на основе ресурсов.
  • Resources. Хранит ресурсы игры, такие как звуки, спрайты и т.д.

Имплементация скелета архитектуры представлена ниже:

  function GameModel() {
  this.setView = function(v) { view = v; };
  this.getView = function() { return view; };

  this.moveLeft = function() { ... };
  this.moveRight = function() { ... };

  this.next = function() {
    // build next/new frame
    ...
    view.update(); //update view            
  };
}

function HTML5View(canvas) {
  var model;
  this.setModel = function(m) { model = m; };
  this.getModel = function() { return model; };

  var context = canvas.getContext("2d");

  this.update = function() {
    // clear view
    ...
    // drawing model to canvas
    ...
  };
}

function KeyboardController() {
  var model = null;

  this.setModel = function(m) { model = m; };
  this.getModel = function() { return model; };

  this.keydown = function(event) {
    if (event.keyCode == LEFT_KEY_CODE) {
      model.moveLeft();
    } else if (event.keyCode == RIGHT_KEY_CODE) {
      model.moveRight();
    }
  };
  document.onkeydown = this.keydown;
}

function Game(model, view, ctrl) {
  view.setModel(model);
  ctrl.setModel(model);
  model.setView(view);

  this.run = function() {
    setInterval(model.next, 1000 / DEFAULT_FPS);
  };
}

function main(canvas) {
  var view = new HTML5View(canvas);
  var model = new GameModel();
  var ctrl = new KeyboardController();

  var game = new Game(model, view, ctrl);

  game.run();
}


Шаг 3. Первый прототип


Спустя сутки был получен первый рабочий прототип, размером в 150 строк кода на HTML5.

image

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

Основной “фишкой” прототипа стал алгоритм генерации платформ — новые платформы генерируются после уничтожения самой верхней и это обеспечивает возможность динамического изменения количества платформ на экране и расстояния между ними. Код алгоритма представлен ниже:

var generatePlatforms = function() {
  do {
    var type = (Math.random() 0.6) ? PLATFORM_TYPE.SOLID :
      PLATFORM_TYPE.KILLER;
    var baseline = pCounter > 0 ? platforms[pCounter - 1].y : 0;
    platforms[pCounter++] = {
      x: Math.floor(Math.random() * (WIDTH - DEFAULT_PLATFORM_WIDTH)),
      y: Math.floor(baseline + (Math.random() *
        (DEFAULT_MAX_PLATFORM_INTERVAL - DEFAULT_MIN_PLATFORM_INTERVAL + 1)) +
        DEFAULT_MIN_PLATFORM_INTERVAL),
      w: DEFAULT_PLATFORM_WIDTH,
      h: DEFAULT_PLATFORM_HEIGHT,
      type: type
    };
  } while (platforms[pCounter - 1].y < (HEIGHT + DEFAULT_PLATFORM_HEIGHT));
};


Шаг 4. Второй прототип


Во втором прототипе был пересмотрен алгоритм проверки попадания на платформы и переписан на метод проверки пересечения отрезков. Сначала на основе предыдущего Лошарика строится новый, с учетом предполагаемого перемещения в пространстве. Координаты нового, текущего Лошарика и проверяемой платформы передаются в функцию, которая ищет точки пересечения P1 и P2 и при их нахождении возвращает true, с учетом этого, принимается решение — присоединять Лошарика к платформе или нет.

image

Позже функция была оптимизирована и выполнялась только для тех платформ, которые могут попасть в пересечение.

В результате, на свет появился такой вот код:

var isRollCrossPlatform = function(platform, rollPrev, rollNext) {
  // we should to check only platforms between prev roll and next roll
  if (platform.y <= rollPrev.y || platform.y > rollNext.y + rollNext.h) return false;

  var s1 = { x1: platform.x, y1: platform.y,
  x2: platform.x + platform.w, y2: platform.y };
  var s2 = { 
    x1: rollPrev.x, y1: rollPrev.y + rollPrev.h - worldSpeed, 
    x2: rollNext.x, y2: rollNext.y + rollNext.h 
  };
  var s3 = { 
    x1: rollPrev.x + rollPrev.w, y1: rollPrev.y + rollPrev.h - worldSpeed, 
    x2: rollNext.x + rollNext.w, y2: rollNext.y + rollNext.h 
  };
	
  var zn1 = (s2.y2 - s2.y1) * (s1.x2 - s1.x1) -
    (s2.x2 - s2.x1) * (s1.y2 - s1.y1);
  var zn2 = (s3.y2 - s3.y1) * (s1.x2 - s1.x1) -
    (s3.x2 - s3.x1) * (s1.y2 - s1.y1);

  if (Math.abs(zn1) < Math.EPS &&
    Math.abs(zn2) < Math.EPS) return false;

  var ch11 = (s2.x2 - s2.x1) * (s1.y1 - s2.y1) -
    (s2.y2 - s2.y1) * (s1.x1 - s2.x1);
  var ch21 = (s1.x2 - s1.x1) * (s1.y1 - s2.y1) -
    (s1.y2 - s1.y1) * (s1.x1 - s2.x1);
  if ((ch11/zn1 <= 1.0 && ch11/zn1 >= 0.0) &&
     (ch21/zn1 <= 1.0 && ch21/zn1 >= 0.0)) {
    return true;
  } 
	
  var ch12 = (s3.x2 - s3.x1) * (s1.y1 - s3.y1) -
    (s3.y2 - s3.y1) * (s1.x1 - s3.x1);
  var ch22 = (s1.x2 - s1.x1) * (s1.y1 - s3.y1) -
    (s1.y2 - s1.y1) * (s1.x1 - s3.x1);
  if ((ch12/zn2 <= 1.0 && ch12/zn2 >= 0.0) &&
     (ch22/zn2 <= 1.0 && ch22/zn2 >= 0.0)) {
    return true;
  }
	
  return false;
};


Во второй прототип были добавлены различные типы платформ: движущиеся, убивающие и таящие и возможность перемещения за боковые границы экрана.

Шаг 4. Третий прототип


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

image

Шаг 5. Релиз!


Прошла почти неделя с начала проекта и у нас уже был полностью функциональный рабочий прототип, которому до релиза оставалось изменить лишь один класс — HTML5View и добавить немного дополнительных возможностей:
  • Фон мира заменен на полноценный рельеф, двигающийся равномерно платформам;
  • Таблица рекордов;
  • Кнопка “Мне нравится” для интеграции с ВКонтакте;
  • Реализована минимальная анимация для Лошарика в виде двух спрайтов (“Лошарик двигается влево”, “Лошарик двигается вправо”);
  • Добавлен счетчик FPS;

Вся отрисовка графики в виде canvas фигур, была заменена на отрисовку спрайтов (изображений).

context.fillRect(x, y, width, height) -> context.drawImage(roll, x, y, width, height)


Значение счетчика FPS было решено получать на основе последних 10 фреймов и общего времени их отрисовки. В конечном счете код счетчика выглядит так:

// draw current fps
var fps = (frameCounter / totalTime) * 1000.0;
context.fillText(fps.toFixed(2) + " fps", 4, HEIGHT - 6);

// calc FPS
if (frameCounter > FPS_REFRESH_INTERVAL) { frameCounter = 0; totalTime  = 0; }
frameCounter++;
var currentTime = new Date().getTime();
totalTime += (currentTime - lastTime);
lastTime = currentTime;


Кроме того, в релизе проработаны коэффициенты физики игры — гравитация, ускорение по X для более уверенного управления и изменены моменты переключения сложности (уровни) для более продолжительной игры.

image

Статистика


За одну неделю был получен рабочий релиз Лошарика, содержащий 1130 строк кода:
  • 163 на PHP для серверной части;
  • 50 на CSS для меню и кнопок;
  • 71 на HTML5 для отображения;
  • 846 на JS для движка игры.

Планы на Лошарика 2.0


  • Узнаваемый логотип;
  • Новые бонусы (парашют, ядро, бронежилет, ...);
  • Улучшенная физика и производительность;
  • Монстры;
  • Новые типы платформ (деревянные/хрупкие, ...);
  • Полностью измененный дизайн и графика;
  • Звуки (HTML5 audio);
  • Социальная составляющая (Twitter / FB / Google+);
  • Сборка для Google web-store (Chrome app);
  • Сборки для: Andriod app / WAC app / IPhone app (WebGL implementation);

Вместо заключения


Нам хотелось бы сделать небольшое объявление. Как видно, Лошарику для завоевания мира требуется наличие в команде хорошего дизайнера. Если у кого-то возникнет желание поучаствовать в интересном OpenSource проекте и прокачать свои навыки, милости просим — можете оставлять комментарии или обращаться к spiff.

P.S. Еще раз ссылка на ПОИГРАТЬ!
P.P.S. Лошарик на Googlecode.

UPDATE: спасибо хабрапользователю qmax за патч, добавляющий вращение Лошарику!

Средняя зарплата в IT

113 000 ₽/мес.
Средняя зарплата по всем IT-специализациям на основании 10 037 анкет, за 2-ое пол. 2020 года Узнать свою зарплату
Реклама
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее

Комментарии 64

    +17
    Восхищаюсь людьми, которые for fun делают серьёзные вещи. For free. Куда опустошить кошельки?
      +12
      Ого! Не думал, что нам придется заводить кошелек :) Пофиксим :)
        +2
        Во первых, опыт и профессиональный рост (отсюда и деньги ;), во вторых удовольствие от процесса и понимания того, что ты делаешь что-то хорошее для людей :)
          +4
          Создали кошелек, чрезе PayPall. Его можно найти на главной старнице проекта (кнопка Donate): code.google.com/p/losharik/
          ответить
          +1
          Механика падения странная. Вроде ускорение должно быть плавным — а заметно резкое ускорение в момент срыва с платформы, и более плавное движение дальше.
          Зацикленность через край мира — так и задумано?
          Шарик сбоку может пройти сквозь платформу. Так и задумано?
          Пробел увеличивает вдвое предел fps? :)
          На краю шарик может стоять в воздухе :) надо «зону отрыва» уменьшить с квадрата до 1/3 в центре, пожалуй.
            +1
            Opera browser? :)
              0
              Да, он самый. В hardware accelerated build максимальную производительность на intel карте ноута — 1400 fps.
              На nvidia к 3000 подобралось, дальше помирать стал слишком быстро :D
                0
                Opera пока не тестировалась, но о проблемах в ней мы уже осведомлены. Будем пилить :)
                  0
                  А какие проблемы? Что вообще по пробелу-энтеру должо происходить? :)
                    0
                    В том то и дело, что ничего :) Опера очень странно себя ведет.
                      0
                      Ну, fps 60 мне нравится :)
            +3
            Не ребята, мячик должен крутится при движении и иметь тенденции к скатыванию с края платформы.
            Но в целом уже неплохо.
              0
              Пока он соскальзывает — не хватает анимации качения
                +2
                Скатывания конечно надо делать. А вот чтоб шарик крутился (а у нас есть другие идеи по этому поводу), нужен дизайнер. Его и ищем.

                Вообще в будущем хотим сделать мячик в виде шарика с водой с характерной физикой. Будет заметно веселее.
                  0
                  Шарик с водой — это уже отдельный подвиг.

                  А вот вращение вполне можно сделать средствами css3.

                  Ну или переписать всё на gamejs и получить полноценные многофункциональные спрайты.

                    0
                    Пардон, я тормоз.
                    css3 не в тему, конечно же.

                      0
                      А gamejs поддерживат HTML5? Мы просто новички в этом :)
                        0
                        gamejs.org/
                        ""«GameJs was born in late 2009 as a set of functions wrapping the raw HTML canvas.context.draw* functions. It quickly became obvious that the canvas, as specified by the w3c, is a good fit for the PyGame abstraction.»""
                          +1
                          Кстати, а что у него с лицензией? Так и не нашёл ни на сайте, ни на гитхабе.
                  +4
                  Прикрутил вращение средствами канваса.
                  Выглядит воттак: qmax.academ.org/~qmax/losharik/src/

                  Аксонометрия при этом херится, поэтому шарик пришлось перерисовать, а блики накладывать отдельно.
                    0
                    Ха! Отлично :)

                    Я так понимаю, мы земляки? (судя по домену)
                      0
                      Да и по профилю если все из одной деревни :))
                        0
                        Вот только «академгородок» в профиле не выставляется :)
                      0
                      Залил в стабильную ветку, только цвет изменил немного. Спасибо!
                    +4
                    Добавить бы при высоком падении эффект небольшого отскока, как это было бы с обычным мячиком :)
                      0
                      Ребят, а откуда ограничение в 30 fps :)? У меня на 30 fps в Safari загрузка CPU составляет где-то 6%, я думаю, можно вполне до 60 fps поднять, чтобы оно выглядело более плавно (ибо на 30 fps у меня лично создается ощущение, что оно тормозит, хотя ресурсов компьютера не занято почти никаких при этом :))
                        0
                        На самом деле для такого плана геймплея 30 fps достаточно. Однако не могу не согласиться, что мир двигается как-бы прерывисто (хотя это слабо заметно). Мы работаем на этой проблемой. Как показывает профайлер, основная работа выполнятся при отрисофке фона. Ее и надо оптимизировать. Если не поможет — переишем на WebGL. В любом случае, к выпуску на телефонах, все будет летать :)
                          0
                          Попробуйте оперу — там fps удваивается по пробелу до довольно больших величин.
                          +1
                          И какую-нибудь анимацию или хотябы надпись добавить при пропадании жизни.
                          А то пауза совершенно непонятна.
                            +1
                            Можно исходники на Github, пожалуйста?
                              0
                              Зачем? Мы хостим проект на гуглокоде. Причем тоже используем Git.
                                +1
                                1. Гитхаб социален (например, можно фолловить людей/проекты, комментировать коммиты/исходники)
                                2. Гитхаб вроде популярнее гуглокода
                                3. Субъективно, у гитхаба более приятная веб-морда (и классные Issues)
                                4. Простое форканье (fork) и пулл-реквесты

                                По-моему достаточно причин.
                                  0
                                  А не объясните, хотя бы по одному пункту в чем профит от него?
                                    0
                                    Практически всё js-сообщество на GitHub. Я понимаю, что это не особо аргумент, но лично я из-за него и перешёл с GC на GH.
                                      0
                                      Не, ну окей, гитхаб социален, популярен, js-овцы там тусуются, простое форканье, сайт красивый. А вот тут наступает время величайшего аргумента в мире: и чо?
                                        0
                                        Величайший аргумент: «чо и чо?». Вот и поговорили.
                                        А по теме — ну выложил автор исходники на ГуглоКод. И чо? Всё-равно на ГуглоКоде они почти никому не нужны.
                              +10
                              А помните старую одноименную игру?
                              image
                                +2
                                Конечно помним :) Наш лошарик — это реинкарнация.
                                  0
                                  Был когда-то архиватор LZH под DOS, создавал архивы с расширением .lzh; его также называли «лошарик». Первым делом подумал, что статья про развитие этого архиватора :)
                                0
                                самое грустное в этой игре — что у неё есть только «You lose». Она никогда не скажет You win :(
                                  0
                                  А почему бы и нет? Можно говорить «You win!» тому, кто прошел в таблицу рекордов. Запишем в концепт :)
                                    +2
                                    В тетрис тоже нельзя выиграть
                                      0
                                      Ситуацию спасёт социализация — и возможность «обыграть» кого-нибудь из друзей/соседей.
                                      +2
                                      Спасибо, что убили почти час моего драгоценного времени. :)
                                        +8
                                        Пжлст! Мы убили на это неделю :)
                                        0
                                        Ура, я сделал бесконечные жизни. Правда, когда подменяю значение очков на что-то вроде 99999, то они не сохраняются в базу даже прямым POST запросом. У Вас есть какая-то защита или хабраэффект имеет место?
                                          +1
                                          Так вот кто мещает другим играть! Вы думаете нужо дофига ума, чтобы открыть js-код в firebug или еще в чем либо? Или подменить REFER и отправить POST запрос?

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

                                            Вот если бы была какая-нибудь real-time защита с зеркалированием и проверкой каждого шага на сервере, тогда взломать игру было бы сложней, но и реализация тоже будет неслабой.
                                              0
                                              Ну, обфусцирование кода тоже не помешало бы
                                                0
                                                Не думаю, что это решит проблему — всегда можно скачать исходники и запустить их, а подделать REFERER не составляет труда, к сожалению.
                                                  0
                                                  Обфускация спасёт от таких же любопытных как я. Если моя цель — не взломать игру, а поразвлечься, то желание расшифровывать игру отпадёт при виде такого когда. А в запросах можно использовать некий hash, который с каждым разом (запросом) будет генерироваться разный. Ну тут уж Вам решать
                                                0
                                                Причем сперва там даже HTML теги не экранировались хочу заметить (И да, я тоже пробовал ковырять турнирную таблицу. Сперва напрямую, потом там, видимо, защиту по Referer поставили, но Firebug-ом это обходится)
                                            +3
                                            Пусть тут полежит — Simple game with HTML5 Canvas в пяти частях, и кодом на GitHub.
                                              +1
                                              Ребята, вы молодцы! И молодцы, что сделали это открытым проектом. Плюсы вам в карму и большое спасибо!

                                              По поводу WebGL. Если хотите делать прямо из HTML порт под iOS при помощи PhoneGap или Titanium Mobile, то WebGL на ней, к сожалению, работать не будет. Насколько знаю, поддержка WebGL появилась в iOS5, но только для рекламы iAd. Если я, опять же, не ошибаюсь, PhoneGap использует iOS-овский webkit. Хотя, последнее надо уточнить.
                                                0
                                                А вот вам ещё идея для разнообразия:
                                                Сделать модель шарика инерционной (с массой и разным откликом на управление), а некотрые платформы превращающими шарик в разные материалы (металлический, деревянный, бумажный).
                                                  +1
                                                  Дайте угадаю, вы играли в Ballance?
                                                    +1
                                                    Угадали :)

                                                    Вот бы сейчас на айпады её…
                                                • НЛО прилетело и опубликовало эту надпись здесь
                                                    +1
                                                    Сосздали кошелек, чрезе PayPall. Его можно найти на главной старнице проекта (кнопка Donate): code.google.com/p/losharik/
                                                    0
                                                    Все круто! Спасибо вам за игру и за процесс. Но хотел бы добавить. Вот я зашел со смартфона, его браузер открыл игру на все окно, все заработало после кнопки плей, но управлять я этим не смог. Идея 1: сверстать поверх игры(только для моб.тел) два элемента и накинуть на них функции управления. Идея 2: Использовать html5 accelerometer api (не уверен, не сталкивался).

                                                    P.S. Когда хабр научится открывать все ссылки вне своего домена в новом окне, а?
                                                      0
                                                      Для смартфонов будет версия 2.0. Будет поддержка акселерометра. Мы сделаем отдельные сборки для Андродиа, айФона и Тизена.
                                                      0
                                                      Помню игру, «Лошарик спасает галактику».
                                                      Собачка из воздушных шариков в космосе прыгала по другим шарикам. Сложная игра) висла на 83 уровне. Перевод был от Фаргуса, пиратский…

                                                      Вот этот диск, до сих пор дома лежит:
                                                      www.nestor.minsk.by/kg/1999/28/kg92825.html
                                                        0
                                                        Я придумал подлянку для следующих уровней — стоит себе плаформа, прыгаешь на нее, и когда лошарик к ней приближается, она начинает с небольшой скоростью сваливать в сторону.

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

                                                      Самое читаемое