четверг, 11 августа 2011 г.

Портируем 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.

Комментариев нет:

Отправить комментарий