css3で表示中のサイトのDOM構造を3Dにして回転させるブックマークレット
- 自動で回転
javascript:(function(){var%20url='http://ndruger.lolipop.jp/hatena/20110919/css_3d_bookmarklet.js';var%20e=document.createElement('script');e.charset='UTF-8';e.src=url;document.getElementsByTagName('head')[0].appendChild(e);})();
- 手動で回転
javascript:(function(){var%20url='http://ndruger.lolipop.jp/hatena/20110919/css_3d_bookmarklet.js?mode=manual';var%20e=document.createElement('script');e.charset='UTF-8';e.src=url;document.getElementsByTagName('head')[0].appendChild(e);})();
説明
DOMツリー構造の子孫を辿ってcss transforms 3Dのtransform3Dを設定しています。子孫を辿るたびにz座標を増やしています。
ページの表示がDOMツリーでどのように実現されているのかが視覚的に分かるかもと思って作ったのですが、役立つケースは少なそうです。
iPhoneのブラウザ上で30FPS以上でヌルヌル動く3Dゲームを作ってみた
iPhoneでのCSS 3D Transforms利用の可能性を探るために、CSS 3D Transformsを使って3Dゲームを作ってみました。
javascriptによる3DゲームですがiPhoneのブラウザで結構スムーズに動きます。
デモページ
http://ndruger.lolipop.jp/hatena/20110705/css_3d_game/css_3d_game.htm
- 非対応環境の例
- Android
- 傾きがブラウザから取れないので動きません。
- iPod touch 第2世代 OS 4.2.1
- deviceorientationイベントが発火しないので動きません。devicemotionイベントは発火するのでそちらで回転の検出を実装するようにすれば動きそうですがまだしていません。
- Firefox
- CSS 3D Transformsに対応してないので動きません
- Android
ゲーム内容
- iPhoneを傾けて赤い立方体を転がらせて、ゴールである青い立方体まで移動させます
- 床にある穴に落ちるとゲームオーバーです
- ステージは2-4まであります
iPhoneのブラウザでの3Dゲーム作成の問題と解決
本ゲーム作成の動機です。
問題
手動でjavascriptでモデリングして3Dをcanvasに描画する場合でも、iPhoneのcanvasは描画する対象が多くなればすぐにFPSが下がるため、高いFPSを維持したまま全体を再描画するケース(視点移動など)に対応させることが難しいです。
CSS 3D Transformsによる解決
iPhoneはCSS 3D Transformsに対応しており、これを利用すると、ブラウザとその移植層の3D処理により高速な描画が実現できます。
例えば下記の例では、transformプロパティのtranslateを指定した300個のdiv要素の親を、フリックを使ってiPhoneでもスムーズに回転させることが出来ます。
CSS 3D Transformsを利用すれば、divを好きな角度に回転でき、位置もtranslate()を利用して再レイアウトなしで変更できます。
これにより、下記のdivを「ポリゴン」として利用して下記のようなことが実現できます。
- divを4個使って4角錐の実現(背景画像を利用すればdivを表示上3角形としても利用できます)。
- divを6個使って立方体の実現
また、上記の例のように、CSS 3D Transformsは祖先に適用させれば、その子孫全体が影響するため、3Dのモデルを扱いやすい形になっています(SceneJSのjsonツリーがDOMツリーになった感じです)。
よって、動かしたいグループには親要素を作って、それにCSS 3D Transformsを適応するとグループ全体が動きます。
本ゲームの実装説明
- 傾きの実装
- 端末の傾きによる赤いブロックの移動と回転
- ゲームオブジェクトの実装
- 上下左右の壁をそれぞれボックスとして作っています。1つのボックスは6つのdivから出来ています。
- 最初はシンプルに壁や床などもタイル単位でボックスにしてそれを敷き詰めようとしたのですが、動作が重くなったのでまとめてしまいました。
結果の考察
本ゲームにより、transformプロパティを指定しているdiv要素が少ない場合、transformの変更による3Dアニメーションは非常にスムーズに動くことが確認できました。
興味深いことに、CSS Animationでtransformプロパティを変更中に、メインループにて他の要素のtransformプロパティを変更すると、傾きの変更による表示が時々カクカクするようになりました。
そのため今回のゲームでは、青いブロックの回転もCSS Animationを使わず、メインループの中で回転するようにしています。
うまく使えばiPhoneのブラウザでスムーズに動く単純な3Dゲームがいろいろ作れそうです。ルーブックキューブとか。
iPhoneでヌルヌルキャラクターが動くゲームを作る方法
iPhoneのキャラクターがヌルヌル動いて、それらのキャラクターにタップ・フリックでいろいろ出来るアクションゲームの作成方法
- canvasを使ったり、left, topをいじるとキャラクターのスムーズでないため、css animations + css transformsのtranslate()でキャラクターを動かす
- 1) touchイベントのリスナーで、動いているキャラクターに対するアクションが実装できる場合
- touchイベントのリスナーを使う
- 2) touchイベントのリスナーで、動いているキャラクターに対するアクションが実装できない場合(例: 特定直線上の敵キャラクターへの攻撃等)
- 自分でキャラクターの座標を計算して、ヒットテストを行う
- css transformsの動きは、ベジェ曲線を利用しているので、ベジェ曲線の式を使えば、時刻tから現在位置が割り出せる
- transition-timing-functionごとの値も仕様に書いてあるので、その係数を利用する
- 問題は、css transformsの時刻tは、横軸上の値であり、ベジェ曲線では、横軸の値から縦軸の値を直接出すことはできない。
- そこで、ニュートン法を使ってt(時刻のtではない)を近似して、近似したtから縦軸上の値を算出する
- 後は縦軸上の値にtranslate()で渡したx,yの値をそれぞれかければ、現在時刻tのx,yの位置が計算できる
- 50%などで区切ると、それぞれの区間にtransition-timing-functionが適応されるので注意
- 自分でキャラクターの座標を計算して、ヒットテストを行う
iPhoneのブラウザでのアニメーション手法を比較
基本
- 拡大・縮小を避ける
- 下記の指定
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0;">
-
-
- iPhone4の解像度はiPhone3Gより縦・横に倍大きいが、width=device-widthを指定してもiPhone3Gと同じ320pxが返り、表示時に2倍に拡大されて描画される。style指定で見かけ上の拡大を避けれなくはないが、今回はしない。
- img.width / img.heighが画像の実サイズと異なる値を入れたり、canvasでの拡大をすると遅くなるのでしない
-
- タイマーはまとめる
- ゲームと同じように1つのループで行う。タイマーを増やすと遅くなるし、canvasならclearRect()が増える。
- 移動量は経過時間ベースで計算する
- 遅くなってスムーズさが落ちても、全体の完了時間は変わらないようにする。
- 少数の位置で描画しない
- 遅くなるため、CSSのtop, leftやdrawImage()の引数に小数を利用しない。整数化はMath.floor()より~~の方が速い。
比較
iPhone4(MC605J) OS 4.3で比較。
canvas
- キャラクターの数が少ない(5個)場合: 25FPS。スムーズ。"DOMからinline styleのleft/topを変更"よりFPSは低い。
- コンテンツ: http://ndruger.lolipop.jp/hatena/20110617/drawing_performance/drawing_performance.htm?type=canvas&number=5
- FPSの低さは、キャラクターが少ない時にclearRect()を全体に行う場合、clearRect()が占める時間の割合が大きいためと推測される。
- キャラクターの数が多い(80個)場合: 18FPS。ある程度カクカク。"DOMからinline styleのleft/topを変更"よりFPSが高い。
- 衝突判定: 移動時に、自分を移動させる座標で行う
- ヒットテスト: 自分で覚えている座標で行う
DOMからinline styleのleft/topを変更
CSS Animationでleft/topを利用
- キャラクターの数が少ない(5個)場合: FPS不明。スムーズ
- キャラクターの数が多い(80個)場合: FPS不明。カクカク(DOMからinline styleのleft/topを変更とほぼ同じ)
- コンテンツ: http://ndruger.lolipop.jp/hatena/20110617/drawing_performance/drawing_performance.htm?type=css_animation&number=80
- CSS Animationで行っても、left / topを変更するとブラウザの重いが実行されるため、パフォーマンスに大きな変化はない。下回りで位置の補完とか無茶なことをしてたら別だが、してないと推測。
- 衝突判定: CSS Animationとは別に行う必要がある。
- ヒットテスト: リスナーを使うか、getComputedStyle() or 自分で時間から位置を計算して自力で行う
- 他
- 現状inline styleに対してDOMを使って指定できない。下記のようにstyle要素を書き換えてがんばる
- ループしてから減速するなどを1つのルールで書けないので、webkitAnimationEndでルールをすげ替える必要があり、その時にアニメーションがわずかに止まる感じになる。アニメーション中にユーザー操作で動きを変えるときもこの問題が起こる。
- 結構複雑なことをすると、Android / iPhone3G / iPhone4で動作の違い(バグ?)が発生したりする。
- フレーム毎にjavascriptの関数が呼ばれので、FPSを計算できない
- 複数のアニメーションの同期が難しい
CSS Animationでtranslate()を利用
- キャラクターの数が少ない(5個)場合: FPS不明。非常にスムーズ
- キャラクターの数が多い(80個)場合: FPS不明。非常にスムーズ
- コンテンツ: http://ndruger.lolipop.jp/hatena/20110617/drawing_performance/drawing_performance.htm?type=css_animation&use_css_transform=true&number=80
- translate()はレイアウトに影響しないので、ブラウザ内の処理がほとんどなくなり、非常にスムーズな動きになる。
- 衝突判定: CSS Animationとは別に行う必要がある。
- ヒットテスト: リスナーを使うか、自分で時間から位置を計算して自力で行う
- 他
他のテクニック
canvasでclearRect()を全体にせず、個々のキャラクターの範囲に対して行う
- キャラクターの数が少ない(5個)場合: 42FPS。全画面clearRect()よりFPSが高い
- キャラクターの数が多い(80個)場合: 16FPS。全画面clearRect()よりFPSが低い
- コンテンツ: http://ndruger.lolipop.jp/hatena/20110617/drawing_performance/drawing_performance.htm?type=canvas&use_min_clearrect=true&number=80
- 書き換える領域が全体に近くなり、clearRect()の複数回の処理の方が悪影響になるため。
動いてない奴の描画を防ぐためdirty flag等を利用する
- styleやcanvasに変更など非常に重い処理の前に、動かす必要のある物か判定して、不要なら変更しない。
postMessage()によるsetZeroTimeout()の利用
- setZeroTimeout()の参照元
- 内部的にタイマーで実装してないようなので、プラットホームごとに使っているタイマーの精度より早く呼ばれる可能性がある。
- コンテンツ: http://ndruger.lolipop.jp/hatena/20110617/drawing_performance/drawing_performance.htm?type=canvas&use_set_zero_timeout=true&number=5
- 個人的にはループで使うものではないと思う。
適切な選択
- スムーズさを優先するならば、可能な限りCSS Animationでtranslate()を利用するが、やりたいことが制限にひっかかった場合、その制限に応じて、他の奴を選択する
javascriptを使わず、html + cssだけでクリックシューティングゲームを作ってみた
CSSで描かれたぐぬぬ画像などを見てたら、ふとhtml + CSSだけでアクションゲームが作れないかと思ったので作ってみました。
ページ移動すればいくらでも可能ですが、それだとつまらないので、1ページ内で作るという縛りもつけてます。
デモページ
http://ndruger.lolipop.jp//hatena/20110429/css_game/css_game.htm
Chrome / Safariの最新版で動きます。Firefox / IEだと動きません。
技術的な説明
アニメーションはCSS Animationを使うだけなので簡単ですが、問題は状態管理です。
アクションゲームを作るには、基本的に下記の要素が必要です。
- 1. ユーザー入力に対する状態の変更
- 2. 状態の保持
- 3. 状態に応じて見た目を変更
これらをhtml + CSSで実現する方法を考えます。
"1. ユーザー入力に対する状態の変更"を実装するには、:hover, :activeなどが思い浮かびますが、それらでは"2. 状態の保持"ができません。
よって、疑似クラスの状態変化が継続する、:visited, :focus, :checkedなどが使えそうです。
最初は、aタグのフラグメントジャンプ()を使って:visitedで状態を変えようとしたのですが、最近のブラウザは訪問履歴読み取り防止のため、:visitedで有効なプロパティがcolorなどに限られていて、ゲームに使うのは難しいと分かりました。
:focusは別な場所をクリックすると解除されるのであきらめました。
残ったのは:checkedですが、inputタグのtype=radio, checkboxならユーザー入力に応じて状態を記憶できるので、これを使います。
次に"3. 状態に応じて見た目を変更"の実現ですが、inputタグのtype=radio, checkboxを使った場合、その要素にどうやってキャラクター画像とマップさせるかが問題です。
- CSSには親・祖先セレクタがないので、input:checkedの親要素でbackground-imageプロパティを適応できない
- inputタグのtype=radio, checkboxはbackground-imageプロパティが反映されない
- inputタグはDHTMLで無理矢理付けない限り子要素を持てない -> 子要素で画像を表示するなどのテクニックができない
この問題を解決するために、CSS3のappearanceプロパティを使います。
使っているコンテンツを今まで見たことがないのですが、このプロパティを"-moz-appearance: button;-webkit-appearance: button;"のように指定すると、background-imageプロパティが反映されるようになります。CSS3の仕様ではこの挙動は明示されてません。
ここまでいけば後は簡単です。
アニメーションしている牛がクリックされて:checkedが有効になると、牛の取得アイコンとして適切なbackground-image, positionに変えてしまいます。
ついでにスコアを表示するため、type=radioの同じnameの要素を作って、ユーザーのクリックでそちらは:checkedが解除されるようにします。
そして、:checkedでレイアウトが変更され、スコアの画像がずれるようにすると、牛のクリックごとにスコアが上がるようにできます。
Documentに繋がってないDOMツリーから要素を探す場合querySelector()を使う
正確にはDocumentのroot elementが属するツリーのサブツリーでないDOMツリーから要素を探す場合。下記のようなケース。
- htmlの塊をテンプレートとして使いたいので、Documentに繋がってない要素にinnerHTMLでhtmlの塊を挿入する
- そのhtmlの塊の中に、属性の変更をしたい要素があるので、何らかの方法でその要素を取り出したい
- その時点でDocumentに繋がってないので、getElementById()等で取り出せない(DocumentFragmentはDocumentを継承してないのでそのようなIFはない)
- ツリーをjavascriptの世界で辿るのはやりたくない
querySelector()やquerySelectorAll()はNodeSelector IFのメソッドなので、DocumentFragment, Elementなどが継承している。
よって、これらを利用する。
var ele = document.createElement('div'); ele.innerHTML = '<div id="neko1">neko1<div id="neko2">neko2</div></div>'; console.log(ele.querySelector('#neko2'));
Stream APIを使ってAndroidのカメラの映像をjavascriptでリアルタイム顔認識(難航中)
こてさきAjax:Stream APIで、カメラの映像をWebSocket ライブ配信 - livedoor Blog(ブログ) http://blog.livedoor.jp/kotesaki/archives/1667452.html
上記のブログで、Stream APIを使ってカメラの映像をブラウザで取得できると知ったので、下記のjavascriptで顔認識ができるライブラリを使って、カメラの映像の顔認識を試してみた。
JavaScript+canvasで顔認識 - 開発な日々 http://hinata.in/blog/20100227231617.html
実行
- 1. http://my.opera.com/core/blog/2011/03/23/webcam-orientation-preview にあるOpera mobileのテクニカルプレビューをインストール
- 2. opera:configのSecurity Prefsの"Allow Camera To Canvas Copy"を一時的にチェックして保存
- 注意: そのままにしておくと、通常のサイトからカメラが覗けてしまうので、切り忘れに注意。
- 3. 作成したコンテンツ(http://ndruger.lolipop.jp/hatena/20110329/js_camera_face_detect/js_camera_face_detect.htm)を開くと、左にカメラの映像が表示され、5秒ごとに顔認識が行われて、結果が右に表示される
- 注意: 非常に重いため、Opera mobileが頻繁に落ちます。
結果
顔認識に成功すると、下記のように正しく検出される。
左がカメラの映像そのままで、右が顔認識により赤い枠を追加した表示。
ただ、顔認識に失敗したときに、非常に重たくなり、Opera mobileが落ちる。要分析と改善。
Stream APIの利用方法の検討
- カメラの映像に対してjavascriptでVRとかもしたいけど厳しそう。typed arrayでの改善度合いをチェックしたい。
- いっそwebsocketでサーバーに流して、サーバーがいろいろな処理をした結果を返すのもおもしろいかも。