Портируем html5 игру на Android

    Это продолжение моей прошлой статьи "Создаем html5 мини-бродилку на CraftyJS". Я подумал, сейчас так много возможностей относительно просто портировать любое html5 приложение на мобильную платформы, почему бы не попробовать?

    image

    Ниже, то что из этого вышло. Внимательно читаем вывод!

    Что нам потребуется

    • PhoneGap и окружение для работы с ним (инструкция по установке)
    • Проект из предидущей статьи
    • Крайне желательно наличие android телефона

    Задача


    Нужно портировать игру бродилку на android, сделать краткий вывод статистики и управление по средствам акселерометра.

    Предварительная подготовка


    С начала, выслушав критику к прошлой статье, я убрал из index.html множественные вызовы js файлов, оставив только главные библиотеки. Для этого я подключил библиотеку requirejs. Так же я сразу подключил phonegap.js и немного изменил верстку, вот как теперь все это выглядит:

    /index.html
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
            "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
      <script type="text/javascript" src="js/require.js"></script>
      <script type="text/javascript" src="js/phonegap.js"></script>
      <script type="text/javascript" src="js/jquery.js"></script>
      <script type="text/javascript" src="js/crafty.js"></script>
      <script type="text/javascript" src="js/game.js"></script>
      <link rel="stylesheet" href="css/game.css" type="text/css" media="screen" charset="utf-8">
      <title>Simpe RPG</title>
    </head>
    <body>
      <div id="cr-stage"></div>
      <div id="sidebar">
        <div id="level">
          Level: 
          <span>1</span>
        </div>
        <div id="score">
          Score: 
          <span>0</span>
        </div>
      </div>
    </body>
    </html>
    

    /css/game.css
    body, html { margin:0; padding: 0; overflow:hidden; font-family:Arial; font-size:20px; background-color: #000; }
    #cr-stage { color:white; float:left; }
    #sidebar { top: 0; left: 0; width: 150px; height: 100px; position: absolute; color:white;}
    #sidebar div { margin: 10px 5px; text-align: center; }
    

    /js/game.js
    var Settings = {
      width: 480, // ширина игрового поля
      height: 320, // высота
      poligon: 16, // размер полигона 16x16
      level: 1, // текущий уровень
      flower_count: 0 // цветков на уровне
    };
    
    var AllScripts = [
      // objects
      'js/objects/flower',
      'js/objects/bush',
      'js/objects/grass',
      'js/objects/unit',
      'js/objects/fourway_accel',
      'js/objects/player',
      'js/objects/fourway_ai',
      'js/objects/monster',
      // scenes
      'js/scenes/loading',
      'js/scenes/main',
      'js/scenes/win',
      'js/scenes/lose'
    ];
    
    require(AllScripts, function() {
      require.ready(function() {
        Crafty.init(Settings.width, Settings.height); // создаем игровое поле
    
        // подгружаем спрайт
        Crafty.sprite(Settings.poligon, "images/sprite.png", {
            grass1: [0,0],
            grass2: [1,0],
            grass3: [2,0],
            grass4: [3,0],
            flower: [0,1],
            bush1: [0,2],
            bush2: [1,2],
            player: [0,3],
            monster: [0,4]
        });
    
        // запускаем первую сцену
        Crafty.scene("loading");
      });
    });
    

    Обратите внимание, что я так же изменил width и height в соответствие с разрешением мобильного телефона.

    Акселерометр


    Теперь давайте займемся управлением, для это изменим /js/objects/player.js заменив компонент Fourway на FourwayAccel, а так же вызов this.fourway(1) на this.fourway_accel(1). Дальше, нам нужно создать этот самый компонент, вот он:

    Crafty.c("FourwayAccel", { 
      _speed: 3,
      _touch_element: null,
          
      init: function() {
        this._movement= { x: 0, y: 0};
        
        this.accels = {};
        
        this.accels['left'] = false;
        this.accels['right'] = false;
        this.accels['top'] = false;
        this.accels['bottom'] = false;
      },
    
      fourway_accel: function(speed) {
        var self = this;
        
        self._speed = speed;
        
        self.bind('Acceleration', function(acceleration) {
          if (acceleration.y < -2) this.start_or_stop_move('left');
          if (acceleration.y > 2) this.start_or_stop_move('right');
          if (acceleration.x < -2) this.start_or_stop_move('top');
          if (acceleration.x > 2) this.start_or_stop_move('bottom');
        });
    
        
        self.bind("EnterFrame",function() {
          if (self.disableControls) return;
        
          if(self._movement.x !== 0) {
            self.x += self._movement.x;
            self.trigger('Moved', {x: self.x - self._movement.x, y: self.y});
          }
          if(self._movement.y !== 0) {
            self.y += self._movement.y;
            self.trigger('Moved', {x: self.x, y: self.y - self._movement.y});
          }
        });
          
        return self;
      },
      
      start_or_stop_move: function(move_type) {
        var move_speed = this.get_speed(move_type);
        
        if (this.accels[move_type]) {
          // stop move
          this._movement.x = Math.round((this._movement.x - move_speed.x)*1000)/1000;
          this._movement.y = Math.round((this._movement.y - move_speed.y)*1000)/1000;
          
          this.accels[move_type] = false;
        } else {
          // start move
          this.accels[move_type] = true;
          
          this._movement.x = Math.round((this._movement.x + move_speed.x)*1000)/1000;
          this._movement.y = Math.round((this._movement.y + move_speed.y)*1000)/1000;
        }
        
        this.trigger('NewDirection', this._movement);
      },
      
      get_speed: function(key_id) {
        switch (key_id) {
          case 'top':
            return {x: 0, y: -this._speed};
          case 'left':
            return {x: -this._speed, y: 0};
          case 'right':
            return {x: this._speed, y: 0};
          case 'bottom':
            return {x: 0, y: this._speed};
        }
      }
    });
    

    После вызова метода fourway_accel, мы начинаем слушать событие «Acceleration», которое мы создадим чуть позже. Данное событие передает нам данные о наклоне (x,y,z). Нас тут интересует только x и y. Для упрощения, я проверяю достаточно большой уровень наклона, меньше -2 или больше 2.
    Как только наклон достиг определенного градуса, вызывается функция «start_or_stop_move», которой передается направление наклона. Данная функция, в зависимости от скорости задает направление движения игрока, которое потом отрисовывается в событие «EnterFrame».

    Дальше нам нужно создать сам генератор события «Acceleration», для этого добавим следующий код в /js/game.js:
    var watchID = null;
    
    function stopWatch() {
      if (watchID) {
        navigator.accelerometer.clearWatch(watchID);
        watchID = null;
      }
    }
    
    function startWatch() {
      var options = { frequency: 200 };
      watchID = navigator.accelerometer.watchAcceleration(onSuccess, onError, options);
      // с помощью этого куска, можно дебажить акселерометр в хроме
      // window.addEventListener('deviceorientation', function(event) {
      //   Crafty.trigger("Acceleration", {x: event.beta, y: event.alpha, z: event.gamma})
      // }, false);
    }
    
    function onSuccess(acceleration) {
      Crafty.trigger("Acceleration", acceleration)
    }
    
    
    function onError() {
      console.log('error!');
    }
    

    Более подробно, о работе с акселерометром в phonegap, можно прочесть в документации.
    Теперь, нам нужно вызвать startWatch() в сцене /js/scenes/main.js, а так же stopWatch() в сценах win.js и lose.js

    Непосредственный запуск на телефоне


    Итак, будем считать что вы уже сделал все, что описано в документации. Нужно немного подправить AndroidManifest.xml, добавив в секцию activity строчку: android:screenOrientation=«landscape». Это необходимо для того, что бы ориентация экрана всегда была альбомной.
    Приводим AndroidrpgActivity.java к такому виду:

    package com.phonegap.simplerpg;
    
    import android.os.Bundle;
    import android.view.WindowManager;
    import com.phonegap.*;
    
    public class AndroidrpgActivity extends DroidGap {
        /** Called when the activity is first created. */
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            
            getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
            getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
            
            super.loadUrl("file:///android_asset/www/index.html");
        }
    }
    

    Думаю переменная FLAG_FULLSCREEN говорит сама за себя.

    Результат и вывод


    Вот, что у меня в итоге получилось (извините за качество):



    Работает? О да! Доволен ли я? О нет!

    Дело в том, что толи я криворукий, толи лыжи не едут, но приложение получилось крайне тормазнутым. Результат больше похож на пошаговую стратегию, чем на Action. И дело тут, я думаю, все же в лыжах, ответ наверное очевиден. PhoneGap — отличная библиотека для tumblr читалок и прочих новостных ридеров, но для игрушек лучше использовать нативный для android Java.

    Исходники, как обычно на GitHub.
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 24

      +3
      Ну не удивительно. Вообще мне не понятна тенденция использовать браузер и HTML для всего — он ведь на порядки медленнее и отзывчивость всегда в нем будет хуже, чем в нативной версии… На телефоне отзывчивость — одно из самых важных показателей, так что тут нет смысла экономить на разработке…
        +1
        так происходит, когда у web-разработчика появляется острое желание написать что то для своего телефона, к сожалению, ни к чему хорошему это, пока, не приводит
          –1
          Конечно. И не может привести. Это все полумеры, которые ну ооочень редко работают.
          +2
          со временем движки браузеров ускоряются и сам браузер становится средой для запуска приложений (да в общем то оно уже сейчас среда для запуска приложений), т.е. в идеале 1 раз пишем прогу на html5 и оно чудесным образом работает на любом устройстве и на любой операционке где есть браузер — удобно же!
          но в реальности конечно не всё так радужно
            +3
            Да никогда такого не будет. Браузер стал быстрее в N раз, а железяки в 2N раз.
              0
              если предположить, что js будет компилится, а DOM операции (и прочие) будут очень быстры (вообще для игр лучше Canvas или вообще webgl), то скорости вполне будет хватать для казульных игрушек, коими завалены всевозможные маркеты — и как итог, не придется писать на всяких SDK для разных платформ, а надо будет написать всего 1 раз под браузер и работать будет везде и сразу
                +1
                Да не будет. А если будет, то не скоро. Тут дело не в скорости, а в отзывчивости в первую очередь.
                Браузер — очень жирная прослойка между системой, которая внутри себя имеет еще несколько прослоек и обмен данными между ними занимает время — появляются задержки и комфорт уже далеко не такой же.
                  0
                  ну кстати я заметил что отзывчивость нормальная, если я вас правильно понимаю, в дебаг режиме, показатели акселерометра достаточно шустро на экран выводились, тормоза именно в отрисовке Canvas
                    0
                    Я не читал предыдущую статью, потому спрошу здесь: какие методы канваса и как вы используете?
                    +2
                    ну вот AngryBirds вполне не плохо играются в браузере, конечно и ресурсов отжирают немерено и на телефоне я бы не рискнул играть =) но ведь когда то игры и на ассемблере писали, а щас юзают движки готовые и результат тот же самый — скорость теряется из-за тонны прослоек между железом и конечным продуктом, зато скорость разработки и абстрагирование от железа и иногда программной платформы
                      0
                      Где? да десктопе? А тут кто-то про десктопы говорит?

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

                      А тут железо — драйвер — браузер — джава-скрипт.
                        0
                        > Все топовые игры по прежнему почти напрямую с железом общаются.
                        да я ж про топовые и не говорю, и что значит почти? API звука, API графики, API физики и т.д. + фреймворк/движек — это напрямую с железом? это всё те же самые прослойки, просто работают они быстрее чем JS в браузере
                        я не в коей мере не призываю писать игры на JS (пока что это больше just4fun, чем реальный кроссплатформенный инструмент), я просто говорю о том что это удобно в плане переносимости между платформами/архитектурами и если бы исполнялось быстро, то почему не писать на js?
                        или вот еще пример — ChromeOS — ось+браузер, теперь представим что Google наваяли супер-пупер JS компилятор, который делает из кучи html+js байткод, который напрямую скармливается процессору, в итоге любая страничка открытая в браузере может быть полноценной переносимой платформонезависимой игрой, просто среда исполнения это не голая операционка, а браузер, который и есть операционка в обычном случае =)
                        мечты… мечты…
                        зы: на прошлой работе писал ПО как раз на html5, в итоге скорость разработки уменьшилась в разы (по сравнению написания этого же например на сях), смена/изменения интерфейса занимали считанные минуты/часы/дни, а скорость выполнения всех удовлетворяла (http://www.youtube.com/watch?v=4jSif-4CNWw — вырвки из-за слабости рабочий машинки, не осилила ворочить интерфейс 1440х900 и одновременно писать и сжимать видео)
                          –1
                          Да. Там прослойка последняя в виде тоненькой обертки над DirectDraw.
                      0
                      я согласен с SovGVD по поводу казуальных игр. яркий тому пример — огрмное кол-во таких игр на флеше. html просто заменит его и всё.
              0
              А почему не захотели оставить в вебе, адоптировать под другие разрешения и сделать контролы по тачу?
                +1
                неинтересно, цель была: узнать что то новое
                  0
                  разве не интересней было написать нативную аппу и узнать намного больше нового?
                  +3
                  в вебе так же тормозило бы. Если приложение тормозит просто в телефонном браузере, то в фонегапе будет тормозить ровно столько же. Потому что фонегап не портирует приложение, а просто «оборачивает» его.
                  0
                  отлично. продолжайте в том же духе
                    +1
                    А что говорит профилирование? Где узкое место?
                      +3
                      Еще до прочтения догадался о чем и какой будет вывод :) Чудес не бывает.

                      Улыбнуло в тексте "по средствам акселерометра", не могу не обратить Ваше внимание на соответствующую статью на хабре :)
                        0
                        Недавно на htmlbook проходил конкурс по созданию HTML5 игры, можно попробовать портировать любую из финальных игр :) Не забудьте предупредить разработчиков :)
                          0
                          Так это же не порт, вы просто обернули странички и браузер в приложение. С таким же успехом можно просто открыть игру в браузере, за одно посмотрели бы на скорость, прежде чем что то делать.

                          И да, оно будет тормозным.
                          Лучше напишите порт игры для андроида, а не для браузера.
                            0
                            Спасибо за статью, только что попробовал запустить свой прототип астеройдов. Работает! Скрипт показывает 30-50 фпс. Но на глаз их меньше… Единственное я заметил что клик как то заторможенно воспринимается — не приятно.

                            Only users with full accounts can post comments. Log in, please.