[広告]

2014年10月23日木曜日

Crafty 一巡り

Crafty の使用にあたって


Javascriptでゲームを作りたいと思い、Crafty というフレームワークを使用しました。
今後、このフレームワークを使いたいという人の参考になればと思い、開発の経緯ほかをまとめました。

ちなみに、できあがってゲームはこちらです。
カピバラ・キッドの脱出(ブラウザ版)

Crafty選定の理由

極端に凝ったゲームを作る気はさらさらなかったので、軽量でシンプルな構造で、最低限の機能が揃っているフレームワークを探しました。

http://html5gameengine.com/
とか
http://buildnewgames.com/game-engine-comparison/
とか
http://matome.naver.jp/odai/2134682461157875201
とか
http://www.jsbreakouts.org/
とかを参考にしながら、Craftyを選定し、とりあえずやってみようということにしました。

まずはチュートリアル

普通なら、まずはドキュメントとを読んで言いたいところなのですが、Craftyのドキュメントはこのありさまです。
http://craftyjs.com/documentation/

2014年10月現在では「Here would be information about...」の連発になっていて、まったく役に立ちません。

そこで、Craftyの開発は、


を参考に進めることになります。

何はともあれチュートリアルをやってみて感触をつかみます。

チュートリアルは英語ですがそれほど難しいことは書いていませんし、Javascriptが読めればどうにかなります。
ざっくりとやってみるだけなら半日もかかりません。
そして、この段階では深いことは考えず、ざっくりとやってみるだけで十分です。

ここで挫折するなら、Craftyの使用は諦めたほうがいい、という話になります。

なお、このブログ記事の続きを読む前にチュートリアルに目を通しておくことをお勧めします。

シーンの設計

要件はすでに決まっているものとして話を進めます。
(Craftyの機能の把握ができんていないのに要件を決められるのかといった、卵が先か鶏が先かの話になるのですが、それはそれで別の機会に)

Crartyは、シーン(画面)という単位でアプリケーションを管理します。
スタート画面、ヘルプ画面、ハイスコア画面、プレイ画面、ゲームオーバー画面、といった画面の一つ一つがシーンとなり、これらのシーンが集まって、一つのゲームになります。

なので、コードを書く前に、いくつのシーンが必要になるのか、目算を立てておく必要があります。

ざっくり考えて
  • 起動直後、リソースのロード中に表示する「ちょっと待っててね」の画面
  • タイトル画面
  • ヘルプ画面
  • 設定画面
  • ハイスコア画面
  • スタート画面
  • プレイ画面
  • 1ステージクリア後の、次のステージへのつなぎ画面
  • ゲームオーバー画面
といったあたりを準備することになります。

これらの構成をどうするか、しっかり構想を練ってから、チュートリアルでいうところの、scenes.jsを書くことになります。

コンポーネントの設計

Craftyはコンポーネント指向の設計になっています。
クラスを作ったりインスタンスを作ったりすることはないので注意してください。

ゲームのキャラクターや障害物はコンポーネントとして作成することになります。
これについては、チュートリアルをこなしていれば、それほど難しいものではないでしょう。
いわゆる普通のプログラミングです。

ここで分からないところがあれば、APIドキュメントやCraftyのソースにあたることになります。

最終的にアプリケーションを書き上げるには、めんどうですが、ざっくりとAPIドキュメント全体に目を通すことになってしまうでしょう。
APIドキュメントは上から読むと頭が痛くなるので、Eventsコンポーネントや2Dコンポーネントのあたりから眺めるのをお勧めします。
一番上のCoreは、ある程度Craftyに慣れてからでないと、何を書いているのかよく分かりません。

ゲームの構成

ここまで来たら、後はgame.jsを書いてゲーム全体をまとめるだけです。
これも普通のプログラミングですので、開発経験があればそれほど困ったことにはならないでしょう。

モバイル対応について(画面)

ソースコードを見れば分かるのですが、Craftyは、モバイル環境にも対応しています。
モバイル環境だと、自動的にHTMLデータにメタ情報をセットして、ビューポートの調整までやってくれます。
(ただし、バグっぽい現象があるので後述します)

画面の横幅についてはCraftyが対応してくれるからよしとして、問題は縦幅になります。
ソースコード上では320px × 480px を基本に考えて設計するのが無難かな、といった印象を受けました。
ただし、アドレスバーやステータスバーがあるのでブラウザの実効画面これより上下に小さなものになります。
私はこちらのサイトの数値などを参考にしましたが、日本でメジャーなiPohneを基準に考えると、実際に使える画面サイズは320px × 444pxくらいと考えるのが無難です。

なお、HTML5ではスマートフォン等の画面の回転を抑制する方法はありません。
回転しても大丈夫なように設計するか、ユーザーが自分でどうにかするだろうということで、スパッと割り切るか、のいずれかになります。

Retina対応を考えると、コード上の設計は横幅320pxでも、画像データ等は横幅640pxを前提に準備しないと、画面上でぼやっとした感じになるので気をつけてください。

なお、Craftyではゲーム画面は実行時は<div id="cr-stage"></div>内部に置かれるので、cssで
#cr-stage {
    text-align: center;
    padding-left: 0;
    padding-right: 0;
    margin-left: auto;
    margin-right: auto;
    display: block;
}
と書いておけば、ゲーム画面をセンタリングすることができます。

モバイル対応について(音)

こちらは絶望的な状況です。
Craftyに問題があるのではなく、スマホ側の問題です。
こちらの記事が参考になります。
iOS/Android で HTML5 の audio/video を任意のタイミングで再生する方法

ユーザーが何らかのアクションを起こさない限りは audio/video を使用できず、さらにプレロードも制約されます。
とてもではありませんが、簡潔に実用的なコードを書ける状況ではありません。

オーディオスプライトを扱うライブラリを別途用意するか、マニフェストを用意してHTML5アプリケーションにしてしまうか、潔く音は諦めるか、することになります。
HTML5アプリケーションにすると、それはそれで面倒なメッセージが画面に表示されることになるので、私は今回は音は諦めることにしました。

モバイル対応について(入力)

Craftyのソースを調べてみて分かったのですが、タッチパネルからの入力は、Craftyではマウス入力のイベントに自動的に読み替えられます。
タッチすると、MouseDown、離すとMouseUp、動かすとMouseMoveのイベントが発生します。

なので、Clickも含め、これらのイベントに対応するようにコードを書いておけばそれでOKです。

バグについて

Craftyでプログラミングをしていて困ったのはモバイル対応の部分でした。
特に音の部分については散々悩まされたあげく、「あきらめる」という対応をとらざるを得ませんでした。
将来のバージョンでモバイル対応のいいサウンドライブラリが用意されるのを待つのみです。

ところでもう一つ、モバイル開発の画面まわりで、どうもCraftyのバグらしいものが見つかっています。

モバイル環境だとCraftyが自動的にビューポート制御のメタデータをHTMLデータに組み込んでくれることは上で説明しました。
この部分の制御で、Craftyはデスクトップだとcssで <div id="cr-stage"></div> に postition:relative を設定しているのですが、モバイルでは「敢えて」これを設定していません。

で、これを信用してモバイル環境で開発すると、見事に背景とゲーム画面の位置がずれてしまいます。
下手にcssで調整しようとすると、今度はタッチイベントの検出位置がずれてしまい、使い物になりません。

要はこの「敢えて」がどうもバグっぽいのです。

Crafty 0.6.2 の場合、12,188行目から始まる if ブロックがこれに該当します。
12,209行目の「else {」が余計なのでこれと、12,215行目の「}」を削除し、モバイルかどうかに関わらず「elem.position = "relative";」以下の実質4行が生きるように書き換えると問題は解決します。

ただし、この件については私は公式フォーラム等に問い合わせたわけではありません。
次のバージョンアップでまだこの「else」が残っていたら、本格的に問い合わせをしてみようと考えています。

2014年10月21日火曜日

カピバラ・キッドの脱出 ブラウザバージョン

iPhoneでも遊べます

先日、Android向けに開発したパズルゲーム、「カピバラ・キッドの脱出」のブラウザ版ができました。

こちらのスクリーンショットをクリックしてください(リンク先は別サイトです)。

カピバラ・キッドの脱出


ブラウザ・バージョンですので、Android はもちろんのこと、iPhoneや、普通のPC、Macでもプレイすることができます。

背景

カピバラのキッドは遊んでいたところ、仲間からはぐれてしまいました。
気がついたら、周りはクロカイマン(ワニ)、アナコンダ、ワシ、ジャガー、ピューマといった恐い動物だらけです。



キッドはまだ子どもなので泣くばかりで何もできません。
あなたが手助けしてキッドを脱出させてあげてください。

遊び方

「箱入り娘」と呼ばれる古典的なスライディング・ブロック・パズルです。
空いているマスをうまく使ってブロックをスライドさせ、カピバラ・キッドのブロックを真ん中、一番下の出口まで誘導してください。

ブロックはPCならマウスのドラッグで、スマートフォンやタブレットならタッチ&スライドで動かせます。
ブロックは空いているマスにしか動かせません。

遊び方は単純ですが、レベル4,5になってくると大人でも苦戦するくらいの難しさですよ。


2014年7月5日土曜日

WEBスクレイパー開発記録(5) lxmlで試行錯誤

基本方針

とにかくurlwatchでうまくいかなかった部分ができるようになったスクリプトをPythonで書いてしまおうというのが目標です。

  • 文字化けせずに、日本語のWEBサイトを適切に扱えること
  • フィルタ機能があること
  • 再実行を繰り返してフィルタ機能のデバッグができること(ダウンロードした元ファイルが残っていること)

具体的にはこの3点でしょうか。これに加えてパフォーマンスがよければ言うことなしです。

urlwatchのソースを参考に、ファイルをWEBからとってくる部分と、ファイルを比較する部分に分けて考えていくことにしました。

そして、ファイルをWEBからとってくる部分については、ダウンロードした元ファイルを残すという方針があるわけですから、こちらは比較的簡単でした。

urlwatchを見習って、URLをハッシュ化したものをファイル名にすることにしました。

URLそのままですと、ファイル名に使えない文字が入ってしまう可能性があります。ファイル名に使えない文字というのはOSによって異なったりするので、変に置換すると収拾がつかなくなったりしますので、ハッシュ値のほうが取り回しやすいだろうと考えました。

HTMLパーサの採用

次にフィルタ部分をどうするかを考えました。

ありがちなのは、例えば、「あなたは○人目のお客様です」とか「今日のお天気は晴れです」とかいった、更新をチェックする意味はないけれども、毎回のように更新されている部分をHTMLから取り外す処理です。

これは正規表現でやるにはかなり無理があります。

やはり正攻法で、HTMLをパースして、XPath式か、CSSセレクタのようなもので、読み取る範囲や削除する範囲を指定する方向で考えたほうがよいようです。

問題になるのは、動物園のWEBサイトがどれくらい正当なHTMLで記述されているかという部分です。

こればっかりはやってみないと分かりません。

とりあえず、WEBを検索して、2つのパーサが候補になりました。

一つはlxml。とにかく高速なのが売りです。

もう一つはbeautifulsoup。こちらはエラー含みのHTMLの処理が得意です。

安直に考えて、まずはlxmlで試して、どうしてもうまくいかなかったら、例えばそのサイトだけbeautifulsoupで処理すればとても効率がよさそうです。

というわけで、lxmlを使うところから始めたのでした。

lxmlを使っての迷走

lxmlを試すにあたって、まずは札幌市円山動物園のWEBサイトにアクセスしました。北から順、というか、都道府県コードでいくとどうしてもこの動物園がトップにきますので。

ここのWEBサイトはxhtmlでutf-8で書かれています。HTML-lintを通したところ、「53個のエラーがありました。このHTMLは 78点です。」という結果がでました。まずまずの出来ではないでしょうか。

で、これをlxml.htmlでパースすると、とりあえず、無事読み込んでくれているようです。

抽出したい部分をXPath式で指定すると、うまくいきます。

これはいけそうだというので、続いて旭山動物園。こちらはいきなりフレームレイアウトで文字コードはShift_JIS。これだけ人気の動物園でも、WEBサイトはこんなものなのですね。

どうもホームページビルダーを使っているようなのですが、WEBサイト担当の方とか、業務量的に大変ではないでしょうか。

こちらもlxml.htmlでパースしてみると、うまくいっているようです。

lxmlは、文字コードはHTML内に記述された、charsetを見てくれているようです。

と、ここで問題発生です。

出力が短すぎ。どうもHTMLを最後まで読み込んでくれていないらしいことが分かりました。

何もエラーなど出ていなかったはずなのですが。

lxmlの公式サイトを見ても、lxml.htmlの部分については、エラーハンドリングの記述がありません。

エラーがあるなら例外を吐いて止まってくれればいいのに、何も言わずに結果を切り詰めるなんて...

ちなみに、同じHTMLをbeautifulsoupでパースすると、文字化けします。が、これは文字コードを適切に指定すればうまくいきました。どうもbeautifulsoupは、HTML内のcharsetを見てくれていないようです。

文字コードの部分だけ解決すれば、beautifulsoupで最後までパースできているようなのですが、こちらはかなり遅いということが分かりました。

さて、困ったのは

  • lxml.htmlが適切なエラーを出力してくれないので、自動でbeautifulsoupに処理を移す策が立てられない
  • beautifulsoupで処理しようとすると、あらかじめ文字コードが分かっていないといけない

といった2点で、これらについてはとりあえず、手動。すなわち、旭山動物園のサイトを処理する際には、beautifulsoupを使用して、あらかじめShift_JISという文字コードを与えてやるという対応にしたのでした。

それで先に進めるわけですから。

ここから先は同様で、まずlxml.htmlでパースをしてみて、うまくいかないようなら、beautifulsoupで試す、の繰り返しで、それぞれの動物園ごとの設定ファイルを書いていったのでした。

200サイトもあるので、この作業だけで3日くらいかかったのですけれど。

2014年6月6日金曜日

ウェブスクレイパー開発記録(4) PythonでGO!

urlwatch採用

チェック対象のページ数からして、wgetとdiffでやっていくのが効率的だろうという考えには達しました。

しかしもちろん、手でwgetやdiffを動かすわけにはいきません。

wgetだけなら、あるいはdiffの実行までなら手動操縦もありえます、が、diffの結果のチェックまで目視でやるには無理があります。

少なくともdiffの結果は一つのテキストファイルにまとまっていてほしいものです。

それともう一つ、diffにかける前に、チェック対象外の部分を削除する方法も検討する必要があります。

このあたりの実験的なコード作成には、深く考えることなくPythonを選びました。

自分にとって一番手っ取り早いという理由です。

そして、チェック対象のページが決まっていて、Pythonを使うとなると、wgetすら必要はありません。

Pythonのurllib2を使うと、HTMLのダウンロードができてしまうからです。

しかし実は、そこまでやる必要すらないだろうと思っていました。

urlwatchというPythonスクリプトを見つけてあったからです。

https://thp.io/2008/urlwatch/

HTMLの取得からdiffの適用まですべて自動で作業してくれる優れものです。

手でフィルターを書けば、diffにかける前にHTMLデータの不要な部分を削除しておくこともできます。

これで、動物園・水族館WEBサイトのウォッチが現実のものになる、と思ったものでした。

実際のところ、これでもがんばればどうにかなるレベルではありました。このブログ内で動物園・水族館のWEBサイトの更新状況を報告していた時は、このurlwatchを使用していたのでした。

しかし、urlwatchにはurlwatchなりの問題がありました。

urlwatchを使っていて困ったこと

urlwatchはすばらしいソリューションを私に提供してくれました。

けれども、十分といえる域には達していなかったのです。

まずurlwatchの挙動について、簡単に説明しておきます。

  1. 監視対象のurlのリストを作ってurlwatchに投入すると、urlwatchはその対象のHTMLファイルをサーバーから取得します。
  2. 初期設定のままだとurlwatchはそのHTMLファイルをlynxに食わせてプレーンなテキストファイルにします。
  3. さらに、ユーザーが設定したフィルタを通してそのテキストファイルを加工します。
  4. フィルタ後の結果と、前回の実行結果であるキャッシュファイルをdiffで比較し、比較結果を出力します。
  5. 今回のフィルタ後の結果でキャッシュファイルを上書きし、次回以降の実行に備えます。

lynxを通した結果に対してdiffを適用することで、無関係なjavascriptの変更やcssの変更を気にする必要がなくなり、WEBサイト上の意味のある文言だけを比較できるわけです。

相手がセマンティックなWEBサイトならこれで十分ではありませんか。

しかし、動物園・水族館はそのような相手ではありませんでした。

まず、urlwatchは必ずしも日本語を正しく扱うことができません。UTF-8しかまともに扱えません。

Shift_JISやEUC-JPエンコーディングのサイトは文字化けしてしまいます。

文字化けしながらも新旧比較はできるので、サイトが更新されたということと、大体の更新箇所はわかるので、ブラウザでそのサイトを開いて実際の変更箇所を目視確認するという作業が必要になります。

動物園・水族館が日本語文字コードの問題について深く考えているはずもなく、普通にShift_JISやEUC-JPエンコーディングのサイトがあります。

そしてもう一つの大きな問題。

それは、urlwatchのセールスポイントの一つであるフィルタの使い方が難しいことです。状況によっては使い物になりません。

フィルタはlynxを通した後のテキストデータに対して適用されます。

フィルタが使えるには、この時点で文字化けしていないという前提が必要になります。これで半数近くのサイトではフィルタが機能しないことになりました。

さらに文字化けしていなかったとして、どのようなテキストデータが出力されているのか、ハッキングなしに確認する方法がないため、フィルタの書きようがありません。

対象のサイトをlynxで開いて実際に見てみるくらいしか手がありません。

そしてさらにやっかいなことに、そのフィルタが正しく動作しているかテストする方法が見当たりません。

urlwatchを動作させてしまうと、たとえフィルタが間違って書かれていたとしても、その間違ったフィルタを通した結果がキャッシュに書きこまれてしまいます。原本はどこにも残りません。

フィルタの間違いに気づいて修正したとしても、再実行することができないのです。

せめて原本がどこかに残っていてほしいものなのですが、urlwatchの仕様上、フィルタを通した後のファイルしか残されません。

また、動物園・水族館のサイトはセマンティックなものであるとは限りません。正規表現でのフィルタ作成すら困難です。親切に表示された今日のお天気をどうやって正規表現で切り抜けというのでしょうか。

というわけで、urlwatchをベースに、自分なりのツールを開発する必要性に迫られたのでした。

ちなみに、urlwatchで200のサイトを監視していたところ、次のような時間がかかっていました。

大体動物園・水族館が一日の業務を終えるのは午後6時くらいだろうと目途をつけていました。もちろん実際には、動物園・水族館の主業務はWEBサイトの更新ではありませんから、とっても遅い時間に残業してブログとか書いていらっしゃる職員さんがたくさんいることも今ではわかっています。が、こちらもこちらの作業時間の都合上、午後6時くらいというのが一つのタイムリミットでした。

で、午後6時を過ぎたあたりでurlwatchを走らせます。200のうち大体50から80のサイトで更新がかかっていることが分かります。

明らかに無関係な「あなたは○人目のお客様です」のような更新内容を除き、それらのサイトを一つ一つブラウザで開いて目視確認し、変更箇所を手でテキストエディタに入力していきます。

北海道から鹿児島まで、全データをチェックすると作業完了が早くて午後8時、遅い日は午後10時手前までかかりました。

ということは1回のチェックの作業に2時間から4時間をかけていたことになります。

これで給料がもらえるならまだしも。あるいは給料がもらえたとしても連日午後6時から10時まで拘束、かつ集中して作業というのはちょっときつすぎます。

urlwatchで監視を続けるのは非現実的と言わざるをえませんでした。

ウェブスクレイパー開発記録(3) サイトの更新チェックはどうやる?

道具の選択

動物園の基本DBが出来上がって、次は情報のメンテナンスのためWEBサイトの更新チェックの方法を検討することになりました。

このころには、動物園のWEBサイトの更新情報をブログのコンテンツにしてしまおうという下心が芽生え始めていました。

さて、動物園・水族館の数は200以上。この時点では更新頻度不明です。

中にはRSSを出力している園もありますが、一部の園だけで出力してても...といった感じです。

GoogleでWEBサイトの更新チェック方法を調べたところ、大きく分類して

  • ブラウザのプラグイン
  • 専用ソフト
  • 更新チェックのWEBサービス

が見つかりました。

また、腹案として、wgetでWEBサイトの情報をダウンロードしてきてdiffをかけてしまえばいいのではないか、という考えもありました。何せ200サイトです。導入がひと手間だけれども、wgetが一番現実的ではないかとその時は思っていました。

AlertBox試用

まず試したのはFirefoxのアドオンで、AlertBoxというものです。

https://addons.mozilla.org/ja/firefox/addon/alertbox/

これにめぼしい動物園のWEBサイトを登録して、1日、2日と様子を見てみました。

少なくとも、動物園のWEBサイトの監視という目的がなければ、AlertBoxは優れたソフトウェアであることに違いありません。

WEBサイトのページの一部分の変化だけを追跡することもできます。この機能は重要で、たとえば親切な動物園の場合、本日のお天気情報がサイトに掲載されていたりしますので、こういった情報は取り除いてサイトをウォッチしないと、私にとっては意味なく毎日更新されているように見えてしまいます。

機能的には、これでだめならほかの専用ソフトや、更新チェックのWEBサービスはさらにだめだろうと感じさせるに十分なものでした。

で、十幾つかのサイトを監視したところで、どうも目的に合致していないらしいことが見えてきました。

WEBサイトが更新されていることはわかるのですが、どこが更新されたのかがわからない。AlertBoxには変化した部分を指摘する機能があるにも関わらずです。

なぜかというと、動物園・水族館側が、そういった用途を考慮していないからです。

延々と10ページ分くらいスクロールしないと全体を見られないようなページが普通にあったりします。その一部、たとえばイベント予定の日付だけが変わっていたとして、その部分が変更箇所として緑背景で強調されていたとしても、ちょっとやそっとでは見つけられないのです。結局そのページ全体を読むことになってしまいます。

新旧のページをならべて比較する機能も、こんな巨大なページを相手にしては無意味に近いです。

さらに致命的だったのが作業効率です。

意外と動物園・水族館のサイトは更新されていることが分かりました。

せいぜい週一回、イベント情報やニュースが更新されるくらいじゃないかなと予想していたのですが、熱心な園・館は毎日更新がかかります。

また一つの園・館につき、1ページだけをウォッチすれば済むという話ではありません。

結果を先に書いてしまうと、動物園・水族館200サイトをウォッチするには、1,000ページ以上を監視する必要があり、一日に更新されるサイト数は50、ページ数は100を超えていました。

1,000ページをAlertBoxに登録する手間がまず非現実的ですし、更新チェックのためのデータDLやレンダリングにかかる時間も論外です。さらにそこから更新点を目視確認する手間については上述のとおりです。

というわけで、AlertBoxで使い方を工夫すればどうにかなるというようなレベルの課題に取り組んでいるわけではないということがわかりました。

もちろん、専用ソフトや更新チェックWEBサイトも論外と思われました。

wget&diff

ならば、もう一つの腹案、すなわちwgetとdiffではどうなのでしょうか。

仕組みが動き始めてしまえば、作業効率の点ではAlertBox他よりは圧倒的によさそうです。

というわけで、まず安直に考えたのは、wgetをウェブスパイダーとして使い、動物園のサイトを毎日まるごとダウンロードし、diffをかけるというやり方でした。

wgetはこういった用途向けの便利な機能を持っています。

wgetが超絶便利なwebクローラー、クローリングツールだとは知らなかった・・・!

あたりが参考になるでしょうか。

で、まず最初の動物園のサイトに対して、wgetを実行してみました。

そうしたら時間のかかることかかること。

10年以上も前からWEBサイトを運営している動物園はざらです。そうなると一つのサイトのページ数が何百にもなっていたりするのです。

これをひたすらクローリングするわけですから、1分や2分では終わりません。200サイト全部やったら一日以上かかってしまうかもしれません。しかも、これは相手にとっても迷惑ですよね。

これを毎日とか毎週とか、定期的に実行しないといけないわけです。ネットワーク帯域の無駄遣いで、あまり格好のいいソリューションとはいえません。

スタートに手間はかかりますが、手動でチェック対象のページを絞り込んだほうがいいという結論にすぐに達しました。

それともう一つやっかいなのは、最近はCMSを利用している動物園・水族館が多数あるということです。

余談ですが、びっくりするほど有名な園でもサイトはホームページビルダーで作られていたりします。

で、話を戻すと、CMSを使っていると、例えばメニューを一か所変更すると、全ページに変更が反映されます。

diffにかけると、同じような結果が全ページ分出力されてしまうわけです。

AlertBox並とは言わないまでも、チェックする部分を限定する工夫が必要になります。

そのあたりも、何かのライブラリで解決できるに違いないということで、wget&diffを軸にツールを考えていくことにしました。