2012年05月21日

9leapのHTML5ゲームコンテスト

HTML5のゲーム用フレームワークに enchant.js と言うのを見付けた。

iPhone/Android向けなのだがこれの開発元がゲームコンテストを 開いていて数百の投稿が有る。

iPhone と Android に対応していれば enchant.js 使ってなくも良いらしいので参加してみたいなー。

Android の動作確認ってどうしよう。 中華PADはサクっと壊れちゃったし。
 

Posted by kotemaru at 09:12  |Comments(0)TrackBack(0) | JavaScript

2012年05月11日

html5のDOMとCanvasの描画性能

「ビー玉ころころ」でDOM(imgタグ)とCanvasを使った場合の 描画速度に差があるのか気になったので調べてみた。

こんなサイトを見付けた。

Flash, HTML5(DOM), HTML5(Canvas) の比較を行っている。

Flash圧勝なのは予想通りだけど今回知りたいのは DOM vs Canvas。
で、PCでの結果を見ると Canvas 圧勝。

テストコードが公開されていたのでiPodTouch4で試してみた。

テストコード:指定した個数の矩形画像が下に流れ落ちる。

500個の画像を処理させてみた。

DOMの結果: 12fps

Canvasの結果: 26fps

となり、Canvasの方が2倍くらい早い結果となった。

PC用のテストコードなのでテスト方法が妥当か疑問の余地があるが まあ納得の行く結果だと思う。

ゲームの様な性能の要求される描画ではCanvasを使えって事だな。
 

Posted by kotemaru at 14:15  |Comments(0)TrackBack(0) | JavaScript , iPhone

2012年05月08日

iPhone用ゲーム「ビー玉ころころ」公開

「iPhoneのsafariから差速度センサを使う」 のビー玉を転がすサンプルを拡張してWebアプリでゲームを作ってみました。

iPhone/iPodTouch専用です。iOS5でしか確認していないのですが Android でも動きそうな気がするので動作報告等して頂けると ありがたいです。

一応、「マーブル・マッドネス」を目指したのですがマップが平面なので だいぶ違う感じのものになってしましました。

それでも思いのほかスムーズに動作していて遊べる物になっています。 効果音はどうやっても安定しないので今回は諦めました。

マップエディタも作ったのでユーザ向けにも開放してあります。 Googleアカウントが有れば独自のマップが作れますのでぜひ作って見て下さい。



実装について:

jQuery はさすがに重いと思ったので裸の JavaScript で実装しています。

画面は Canvas を使わず img タグを動かしています。 描画性能的にどちらが良いのかはまだ良く分からないのですが、 次は Canvas を使って試して見ようかと思っています。

以外に面倒だったのが当り判定で、ビー玉同士が当って弾かれる方向などは 三角関数が必要で学生時代に頭を戻して何とか違和感の無いようにしました。

マップやスコアの保存はGAEを使っています。 単純なデータの保存先として使う分には課金が発生することは無さそうです。
 

Posted by kotemaru at 11:40  |Comments(2)TrackBack(0) | JavaScript , iPhone

2012年05月07日

datastore-indexes.xmlでSingle Indexは制御できないのかね?

GAEの課金が大きくなったその対策の一つとして余計な index を作らないと言うのが有るのだが Single index はデフォルトで作られちゃうので各項目に設定しなければならない。

Slim3ならアノテーションでやって貰えるけど低レベルAPI だとソースの setProperty() を setUnindexedProperty() に変更しないといけない。

で、これって datastore-indexes.xml で出来ないのかと思って調べたがどうやら 出来ないらしい。

つーか、良く考えたら datastore-indexes.xml のちゃんとした仕様って 読んだこと無いわ orz
しかも、ググってもなかなか出て来ない。 結局 SDK の中に DTD を発見。

<!ELEMENT datastore-indexes (datastore-index)*> <!ATTLIST datastore-indexes autoGenerate (true|false) #REQUIRED> <!ELEMENT datastore-index (property)*> <!ATTLIST datastore-index ancestor CDATA #REQUIRED kind CDATA #REQUIRED> <!ELEMENT property (#PCDATA)> <!ATTLIST property direction (asc|desc) #REQUIRED name CDATA #REQUIRED>

これを見る限り Composite index の設定しか無さげ。

対策検討中にたまたま見付けたのが以下のページ

うーん、逃げ道無し。

ちなみに single index 全部無くすとどのくらい効果があるかと言うと 項目数11個で Write ops が 22 -> 2 になる。

実際には全部消せないけどケースバイケースで効果絶大な場合もありそう。
 
Posted by kotemaru at 09:25  |Comments(0)TrackBack(0) | GAE/J

2012年04月29日

実行中のJavaScriptパス名の取得

備忘録。

汎用的なJavaScriptライブラリを書いていると呼び出し元の HTMLファイルのパスに依存しないJavaScriptファイルのパスが 欲しくなる事がある。 (リソースとか依存JS/CSSとか)

通常の方法は無さげなのだがなんとかならないかと思って 調べてみたらこんなのが出て来た。

実行時点で document ツリーの最後に有る script タグが実行中の JS だろって事らしい。
スゲー乱暴な気がするがとりあえず試してみる。

function absPath(path) { if (!(path.match(/^\//) || path.match(/^https?:/i))) { var scripts = document.getElementsByTagName("script"); path = (scripts[scripts.length-1].src).replace(/[^\/]*$/,path); } return path; } alert(absPath("resource.path"));

ちゃんと http://localhost/js/resource.path が返って来ました。
ブラウザ依存な気がしたのでググたら既に調べた人がいて大体OKらしいです。

つー訳でこれは「有り」って事にしたいと思います。
 

Posted by kotemaru at 16:55  |Comments(0)TrackBack(0) | JavaScript , メモ

2012年04月21日

iPhoneのsafariで効果音を鳴らす

差速度センサのビー玉が思いの他気持良く動くのでゲームっぽく手を入れている。

で、効果音が欲しいと思い調べたのだがこれが想像以上に厄介だった。

基本的には HTML5 の audio タグでやるわけだが iPhone には特殊な制限が有る。

  • ユーザのアクションから起動しないと音声データを読み込めない。
    (※参考1サイトではonloadで読込できるとしているがiOS5では出来なかった)
  • 同時に1つの音声データしか保持/再生できない。

結局、解決策としてはこうなった。

  • 複数の効果音を1つの音声データにまとめて一部を再生する。
  • 読み込みは起動時に何らかのユーザアクションをさせるしかない。
効果音が重なると前のが途切れちゃうけど仕方が無い。 これが現状のiPhoneの限界。

整理してライブラリにまとめて見た。
ソース:

/** * iPhone用効果音ライブラリ。 * - iPhone の制限を回避して任意のタイミングで効果音を鳴らすライブラリ。 * - iPhone の safari には audio タグに以下の制限がある。 * - ユーザのアクションから起動しないと音声データを読み込めない。 * - 同時に1つの音声データしか保持できない。 * - 解決方 * - 複数の効果音を1つの音声データにまとめて一部を再生する。 * - 読み込みは起動時に何らかのユーザアクションをさせるしかない。 * 使用例: xxxx.addEventListener("click", function(){ // 何かのタップで iPhoneSound.load("all.mp3", { // 音の名前 開始秒 終了秒 bootup : {s:0.0, e:1.55, }, shotdown : {s:1.9, e:2.48, }, click : {s:3.293, e:3.8, }, }, function(){ onload(); }); ); function onload() { // ロード後は何時でも呼べる。 iPhoneSound.play("bootup"); } * * @author kotemaru@kotemru.org */ function iPhoneSound(){}; (function(Class){ /** * 音声データの読み込み。 * @param url 音声データのURL * @param parts 部分定義 {効果音名:{s:開始秒,e:終了秒},…} * @param callback 読込完了ハンドラ */ Class.load = function(url, parts, callback) { Class.audio = new Audio(url); Class.parts = parts; Class.audio.addEventListener("loadedmetadata", callback); Class.audio.load(); } /** * 効果音の再生。 * @param name 効果音の名前 */ Class.play = function(name){ var part = Class.parts[name]; playPart(part.s, part.e, 1.0); } function playPart(s,e,v){ Class.audio.pause(); Class.audio.currentTime = s; Class.audio.volume = v; Class.endTime = e; Class.audio.play(); setTimeout(autoStop, (e-s)*1000); } function autoStop(){ if (Class.audio.paused) return; if (Class.audio.currentTime >= Class.endTime) { Class.audio.pause(); } else { setTimeout(autoStop, 10); } } })(iPhoneSound);

ちなみに再生の開始位置は指定出来るが終了位置は指定出来ない。 終了位置に来たらJavaScriptから停止する仕掛けを入れている。

それと volume は指定しても効かなかったのであらかじめ音量のバランスを調整しておく必要がある。

iPhoneのブラウザでアクションゲームはなかなか厳しいのー。

P.S. 遅いサーバに音声ファイルが有ると変な場所を再生したりして動作が不安定になった。 ちょっと実用的では無いかも。
 

Posted by kotemaru at 16:48  |Comments(0)TrackBack(0) | iPhone , JavaScript

2012年04月18日

iPhoneのsafariから差速度センサを使う

iOS4以降はsafariのJavaScriptから差速度センサの入力が取れるらしいので試してみた。

大雑把な使い方はこれだけ。

window.addEventListener("devicemotion", function(ev){ var g = ev.accelerationIncludingGravity; // == {x,y} 傾き var a = ev.acceleration; // == {x,y,z} 加速度 }, true);

第3パラメータの true はイベント優先度。 リアルタイム性が必要な時は指定した方が良い。

y軸は画面系の座標と±が逆になる。

値を数字で表示して見てもつまらないので画面上でビー玉を転がしてみた。

サンプル:ビー玉が転がるだけ

結構リアルに転がってますw
反応も思いの他良くこれならちょっとしたゲームくらい作れそうです。

ソース:

<html> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8"> <meta content="minimum-scale=1.0, width=device-width, maximum-scale=1.0, name="viewport"> <meta name="format-detection" content="telephone=no"> <meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-status-bar-style" content="black"> <script> function onLoad() { RollingMarble.instance = new RollingMarble(); // 加速度センサ イベント登録 window.addEventListener("devicemotion", function(ev){ RollingMarble.instance.marble.accele(ev.accelerationIncludingGravity); }, true); // PCデバック用キー入力 イベント登録 document.onkeydown = function(ev){ var g = {x:0, y:0}; switch(ev.keyCode) { case 37: g={x:-0.5, y:0}; break; //← case 38: g={x:0, y:0.5}; break; //↑ case 39: g={x:0.5, y:0}; break; //→ case 40: g={x:0, y:-0.5}; break; //↓ default: } RollingMarble.instance.marble.accele(g); }; RollingMarble.instance.start(); } function RollingMarble() { this.instance = null; this.marble = new Marble("marble"); this.stage = new Stage("stage"); } (function(Class) { Class.prototype.start = function() { setTimeout(ticker, 25); } function ticker() { Class.instance.marble.action(); setTimeout(ticker, 25); } })(RollingMarble); function Marble(id) { this.img = document.getElementById(id); this.w = this.img.width; this.h = this.img.height; this.img.style.position = "absolute"; this.reset(); } (function(Class) { Class.prototype.reset = function() { this.isDropping = false; this.x = Stage.W/2; this.y = Stage.H/2; this.gx = 0; this.gy = 0; this.img.width = this.w; this.img.style.left = Math.floor(this.x-this.w/2)+"px"; this.img.style.top = Math.floor(this.y-this.h/2)+"px"; } Class.prototype.accele = function(grav) { this.gx += grav.x/2; this.gy -= grav.y/2; // 上下は逆 } Class.prototype.action = function() { if (this.isDropping) return this.dropping(); with (this) { x += gx; y += gy; if (x < 0) drop(-w/2, y); else if (x > Stage.W) drop(Stage.W+w/2, y); if (y < 0) drop(x, -h/2); else if (y > Stage.H) drop(x, Stage.H+h/2); img.style.left = Math.floor(x-w/2)+"px"; img.style.top = Math.floor(y-h/2)+"px"; } } Class.prototype.drop = function(x, y) { this.x = x; this.y = y; this.isDropping = true; } Class.prototype.dropping = function() { with (this) { img.width *= 0.9; img.style.left = Math.floor(x-(img.width/2))+"px"; img.style.top = Math.floor(y-(img.width/2))+"px"; if (img.width < 5) reset(); } } })(Marble); function Stage(id) { this.div = document.getElementById(id); this.div.style.width = Stage.W+"px"; this.div.style.height = Stage.H+"px"; this.div.style.left = "60px"; this.div.style.top = "60px"; this.div.style.position = "absolute"; this.div.style.backgroundColor = "white"; }; (function(Class) { Class.W = 600; Class.H = 600; })(Stage); </script> <style> body{ background: black; } </style> </head> <body onload="onLoad()"> <div id="stage"> <img id="marble" src="blue-ball.png" /> </div> </body> </html>
 
Posted by kotemaru at 14:28  |Comments(0)TrackBack(0) | iPhone , JavaScript