2012-04-01

テスト

テスト

2011-12-14

ゼルダSSプレイしながらKinectと体感型ゲームインターフェースについて考えた

ゼルダSSを少しだけやった。ものすごい完成度で、Wiiはこのゲームのために作られたんじゃ、と考えてしまうほどだった。
それだけに発売時期が悔やまれる。自分はWiiもってたから構わないけど、ハードごと買わせるには現状のWiiがオワコンすぎて心惹かれない。これがMH3の数カ月後とかだったら、と思わなくもない。

で、一応OSSKinectドライバいじってたりする自分がゼルダをデザインするならどういうインターフェースをデザインするかな、と考えて妄想してみた。
図かけないので文字でのみ説明する。

現時点の僕が考えた理想のインターフェース

結論から言う。

「短焦点で上半身トラッキングに特化したKinect」 + 「両手Wiiヌンチャク」

これが最強。以下理由。

Kinectの現状の問題点

Kinectはデバイスフリーを志向したデバイスだが、デバイスをもたないことに起因する大きな問題がある。

離散的なイベントの発火を検知しづらい

たとえば僕が最初に買ったスノボのゲームのメニュー画面では、右、左、決定を、手を左右に突き出すか、上に伸ばすかで姿勢を検知し、確認のためにディレイが入る。そのひとつのシーケンスがおよそ1秒から1.5秒ほどかかる。これはかなりのストレスだ。
Kinectは手や足の移動量、カメラからの連続的な距離の変化には強いが、on/offなイベントの検知には決定的に向かない。よって、これを補助するハンディなデバイスが必要となる。

カメラ範囲内でしか移動ができず、角度も固定される

Kinectカメラの照射角の物理的な制限により、移動範囲が固定されるため、「移動」という表現ができない。
現在のKinectのゲームも、たとえばスノボだとか、川下りだとか、ボクシングだとか、そういうものは「イベントが向こうからやってくる」ことを前提にしていて、ゲームデザインとしてプレイヤーが空間的に動くことを拒否している。
角度に関してはHMDを使うことで解決できるが、HMDは比較的廉価な(とはいっても5万)HMZ-T1というデバイスがあるものの、基本的には現在一般的なデバイスではない。動くことを前提にする以上、頭に重いものを載せたくない気持ちもある。

立ちっぱなし

連続プレイにはきつい

Wii ヌンチャクを組み合わせることの利点

  • on/offが確実に検知できる
  • アナログスティックで移動を表現できる
  • 右手と左手が自由に動かせる
  • 握ってしまえばKinectの検知を邪魔しない
  • 加速度センサの履歴でKinectのトラッキング精度が低下したときに補完ができる(きがする)
ヌンチャクのボタン配置

ヌンチャクとはいっても普通のWiiヌンチャクではなくて、それぞれに右手と左手に対応したイベントを割り振る。

右手には親指で押すA/Bボタン(YES/NOのメタファ)と、背面にRトリガー。背面ボタンは指ごとに感度センサをつけてもいいかもしれない。
左手は移動を表現するアナログスティックと、左手のイベントに対応するためのLトリガー。たとえば「押している間は盾を構えるボタン」とか。

アナログスティックを用意する以上、移動、足の動きは不要。ということで上半身だけのトラッキングが求められる。Kinectの仕様上、全身の位置がそれぞれに補完しあっているのだが、上半身だけに特化した検出器が必要になる。短焦点であれば、大きな空間も必要なくなる。(短焦点にチューニングされたKinectが発売されるということは知っている)

座位で上半身のみのトラッキングなら、ゲームする姿勢としてはそこまで辛くはない。Wiiも長時間しようと思えば出来た。
右手のリモコンにあたるインターフェースは無線モジュールとバッテリを備えないといけないため大きくなるが、許容範囲だろう。

このインターフェースの問題点

MSと任天堂がそれぞれの特許を使わないといけない。
Kinectにもデバイスフリーの哲学があるので、ヌンチャクのようなデバイスを使いたがるとは思いにくい。
こういうデバイスを実現したがるとしたら任天堂の側だが、Kinectを使うにはMSの特許が必要。


ということで実現可能性はすごい低いので、妄想乙、というわけでした。

2011-11-28

1996年、「僕達のインターネット」を指咥えて見ていた小学生の話 あるいは「真性引きこもり」という現代に生きながらえる呪詛について

先に申し上げておくと、この文章には教訓も教養もなければ、オチもない。
このテーマを書く限りどうしても意識は散漫になるから、筆が出た順番に書き置く。

真性引き篭もり: 普通の女子大生は、Google+で「日本一」になんかなっちゃいない。
「ネットの片隅に咲くドクダミの花の匂い」 - シロクマの屑籠
ぼくは、「死んだ」と言われるインターネットの海で「古き良き」インターネットを見つけ、憧れ、羨ましく思った - opitziuのブログ
かつて、俺たちはインターネットだった - G.A.W.

真性引きこもりという「呪い」の再来

真性さん、僕はハンドルネームの方の半角さんと呼んでたんだが、まあそれはどうでもいい。
あの半角さんが、ブロゴスフィアに再び現れたことに心踊ったのは僕だけじゃなかったのは各所での反応でみてとれる。あのカリスマ性は隠そうにも隠せない。
半角さんの「呪詛」の放つ熱量にあてられて心が昂るのは、半角さんがブログを書いていた当時を知る人なら自然なことだろう。半角さんはどう捉えても「毒」として扱われるであろう存在なのだが、その衝動は人を酩酊させる何かがある。毒だと知りながらも恋焦がれてしまう何か。あのセンチメンタルな罵倒、あらゆる欺瞞を許せず糾弾する姿勢に、僕は痺れずにはいられない。

僕が半角さんを知ったのは、2007年、はてなのホットエントリーに入ってきた彼の記事でだった。受験中にも関わらず、僕は半角さんの過去ログのほとんどを読んだ。ただただ圧倒的な呪いがそこにあって、僕はそれに夢中になった。現状を是とせず、敵がどんなに強大であろうと狂ったように罵倒を撒き散らし、また自身の矛盾にさえ自覚的に身を焼く半角さんは、とにかくかっこよかったんだ。

僕はたぶん、半角さんになりたかったんだと思う。許せないものをそのまま許せないと言える、狂人のような在り様。衝動がそのまま言葉の暴力となり、人を憤慨させ、感動させずにはいられない、その姿。

「インターネットの彼ら」に憧れていた小学生

僕について軽く申し上げておくと、小学生低学年から中学校の頭にかけて(1996-2001)はインターネットに入り浸り、上の世代の「僕らのインターネット」に憧れつつ、しかし参加する権利が与えられなかった人間だ。当時のネットは今以上に、消防、厨房は氏ねという雰囲気が強かった。2ch以前からある確たるそれは僕を萎縮させ、年齢を隠してそういうコミュニティに参加する技術は持たなかったから、遠くから眺めてお兄さん、お姉さんたちが何か面白いことをやってる、という印象を強く抱いていた。テキストサイトの黎明期をみていたから、特にその思いは強いのだろう。
中学生になると親にエロサイトをみてたのがバレてパソコンを没収され、以降2007年に自分のPCを手に入れるまでインターネットは軽く覗く程度になったしまったが…。

僕がインターネットのコミュニティに「復帰」した2008年のTwitterは、アーリーアダプターの遊び場、ながれゆくノマドの一時的な逗留所で、今でも僕が知るかぎり一番魅力的なコミュニティだった。端的に自己紹介すると、秋葉原事件の日に、僕はリナカフェにいて、安否確認されていた人間だ。そのぐらいの立ち位置のユーザー、っていうのがどの程度通じるかわからないが、まあお察し下さいという感じ。

僕はTwitterによって、インターネットのコミュニティによって救済された人間だった。2chとニコニコの匿名文化では、当時大学のあらゆるサークルからフェードアウトした僕の孤独感を癒すことはなかった。2chに入り浸ってはいたが、匿名性の海に埋没していく自己が許せなかった。エゴが強い人間なのだと思う。
Twitterはコミュニケーションの救済装置だ。あらゆるものが数値化されていく。発言数、フォロワー数、発言へのFavorite数… コミュニケーションが数値化され、またその実が伴っていた。それに充足を覚えるメンタリティが歪んでいるかどうかはさておき、僕は自分を救ってくれた「新しいインターネット」を否定することができなかった。僕は半角さんのように新しいインターネットに絶望出来なかったんだ。

僕はTwitterのコミュニティの新参の一番下っ端で、昔、指を咥えてみていたインターネット古参たる彼らに酒をおごってもらったりする一方、ひたすらRSSリーダーに張り付いて最新の記事をあさり、また昔のアーカイブを漁り喪ったものを取り戻そうとした。テキストサイトの墓地を巡り、Flashのもう誰にも更新されない倉庫をめぐった。kanoseさんやotsuneさんのブックマークをなぞり、知識としてモヒカン論争などを知った。

2009年頃、「ミームの死骸」のはっしゅさんに煽られてブログを書き始めたのだが、僕は半角さんほど、世界にも、インターネットにも絶望出来なかったようで、粘着の相手するのに疲れて、無難な技術記事ばかり書くようになってしまった。技術記事は誰も不幸にしないので良い。
インターネットには二種類の人間がいて、無視され続けるか、炎上し続けるかだそうだが、僕はどうも後者の素養があるらしく、最初は人の耳目を集めるのもまんざらでもなかったので、僕の思う「かくあるべき」から外れた現状には声を挙げ問題提起(炎上ともいう)をしていたのだが、すぐに飽きてしまった。でもそこに至る気持ちってのは、たぶん半角さんのように自分の正義を信じて狂ってみたかったんだと思う。


インターネット人とモバイル人

今で言う非モテだとかスクールカーストの最底辺の人間達が、安息の場を得た場所がインターネットだった。そうじゃない人もいただろうが、声が大きいのはそういうやつらだった。だってやつらはインターネットを失うと、もう生きる場所がないんだから、必死にもなる。
それらをぶち壊して、お約束も何も無視して侵入してくるのが数の力で攻めてくるのが新世代のモバイルの人間たちだ。スマホによってi-modeezwebの垣根が取り払われ、彼らがインターネットという「僕らの遊び場」に解き放たられたのはつい最近の出来事だが、mixiなどのサービスに関しては先行して彼の進出ははじまっていた。彼らは自治厨というにも生ぬるい。彼らには彼らの厳然たるルールがあり、相容れない人間を容赦無く攻撃する。日本人らしい無言の圧力によって、人を殺す。そこでインターネット的なルールを主張すれば、キチガイのレッテルを貼られる。Twitterをはじめるまでの僕がそうだった。
そして似非原さんが言及していたように、相対的に立場が上がっていたインターネット人達はリア充の真似事をしていたわけで、彼らはサブカルメンヘラ女子を伴って新しいサービスへと逃げまわるのだ。

結局、アーリーアダプター達のノマドとしての振る舞いは、モバイルという異文化圏から逃れる形の逃避に過ぎない。mixiやニコ動は占拠されてしまったし、Twitterもどうかな、完璧に棲み分けてるようで、昔の面白かった人たちがリアルの人間関係のせいで黙ってしまったりしているのが現状だ。

あらゆるものが分解されてフラットになる時代

これは単に持論なのだけど、はてな村もまた、Twitterによって分解され、再構築されている。もはやサービス形態に依存する閉じたブロゴスフィアなど存在しない。全てはTwitterで貼られるテキストリンクとして、フラットな存在になってしまったんだ。はてなブックマークもそこにコミュニティへの帰属性はもはや存在しない。多様化しすぎたはてなはそこに語るべき「はてな」を失ってしまった。ツールはツールでしかないという建前が現実になった。

現代においては、ブロガーあるいは言論人って生き物は、切り売りされる自己、切り売りされるテキストってのに自覚的にならざるを得ない。
そこに文脈もなく、人格も存在しない。誰かの同意と承認と敵意の対象として消費されていく。パラグラフ単位でTumblrの海へ流れだしていく。

今あえてインターネットという総体を語ること

「俺たちのインターネット」は死んだけど、今あえてインターネットという幻想の総体を語り、挑み続けるのはすげーロックでかっこいいんだけど、報われないよね。ってことをid:murashitと酒のみながら話していたのは一年ほど前だったか。
僕なんかが言及したところで半角さんは気にもとめずに、留めたとしても数語の罵倒で済まされてしまうのだろうが、これは僕の一方的な尊敬の念である。僕は、半角さんに、どうしようもなく憧れていた。

まあそういうことを、僕は酒が入ったり考えこんだりしてしまうと、ちょっとばかし「意識が高く」なってしまうので、こんな感じでブログでガス抜きしている。それだけ。

2011-11-10

ウェブソケットでネトゲ作るよ〜 途中報告

解説しながら作る、と言ってた気がするんだけど、ドキュメント書くの飽きてがっーと作ってたら出来てしまった。
とりあえずGithubにおいた mizchi/ws-netgame - GitHub

localhost以外の環境で動くかは、まだ確認してない。以下のTODOが終わったらさくら鯖あたりに置いてテストしてみる。

TODO:

  • HP残量表示
  • WebSocket用のデータシリアライズをもうちょい頑張る
  • 共有データの切り出し(ObjectGroup)
  • セーブ機能
  • マップチップ表示
  • マニュアル書く

実装済み

  • 複数人プレイ
  • isometricのビュー

これはこのコードではじめて実装したもの。というかクオータビューの実装って案外簡単で

  to_ism : (x,y)->
    [cx,cy] = @cam
    [
     320-cx+(x+y)/2
     240-cy+(x-y)/4
    ]

@cam は 中心となるプレイヤーの座標

すべての座標にこの関数で変換した座標でレンダリングしなおせば一瞬で終わった。
もちろん二次元アフィン変換を一度手計算でやって確認したんだけど、よく考えたら当たり前な結果が出た。

実装したい

現状1プロセスで1リージョン。
複数インスタンスの立ち上げでリージョン移動とスケールアウトの実装。(セーブにはDBサーバーを一台)
Node v0.6の cruster で実装できる気がする。まだ0.4使ってるし.socket.io次第かなー。

2011-11-09

WebSocketでネトゲを作るよー クライアント:アニメーションループ

アニメーション、僕が昨日、こんにちは!ってやってたアレです
http://mizchi.hatenablog.com/entry/2011/11/08/144803

メインループの仕組み

setInterval , setTimeoutは使わず、requestAnimationFrame を使います。
バックグラウンドのときレンダリングしなかったり、最適化してくれます。

requestAnimationFrame < よーしおまえらー、アニメーションにsetInterval使うなよー - くろまほうさいきょうでんせつ

window.requestAnimationFrame = (function(){
	return window.requestAnimationFrame		||
		window.webkitRequestAnimationFrame	||
		window.mozRequestAnimationFrame		||
		window.oRequestAnimationFrame		||
		window.msRequestAnimationFrame		||
		function(callback, element){
			window.setTimeout(callback, 1000 / 60);
		};
})();


再帰呼び出しでループします。

function animationLoop(){
  render();
  requestAnimationFrame(animationLoop);
}
animationLoop();


で、このWindow.requestAnimationFrameなんですが、このままだ60FPS決め打ちなんですね。
このまま実装したとしても、高フレームレートを必要としないものも60FPSでんぶん回されます。バッテリにもよくないし、CPUにもやさしくないので、レートを落とします。

var cnt = 0;
function animationLoop(){
  cnt++;
  if (cnt%2) render();
  requestAnimationFrame(animationLoop);
}
animationLoop();

これで30FPSに。今回はこの設定で進めます。

Canvasによるアニメーション

canvasタグでcontextを取得します

// 初期化
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');


メインループから呼び出されるrender関数を定義します。

var render = function(){
  # バッファの消去
  ctx.clearRect(0,0,320,240);

  # ここから書く
  ctx.fillText("こんにちは!!",Math.random()*320, Math.random()*240 );
};

これが毎秒30回呼び出されてアニメーションにするわけですね。

2011-11-09

WebSocketでネトゲ作るよ〜 キーイベントの同期

今現在のサーバー側コード(の一部)

クライアント側(zappaを通した記述)

  @client '/index.js': ->
    window.soc = @connect()
    window.onkeydown = (e)->
      console.log "keydown"+e.keyCode
      soc.emit "keydown",code:e.keyCode

    window.onkeyup = (e)->
      console.log "keyup"+e.keyCode
      soc.emit "keyup", code:e.keyCode

キーコードを送ってる。正直ブラウザ依存あるのでfromCharCodeから変換したほうがいい気がしてるし、する。

サーバー(zappa)

  # 新しいユーザーのログイン
  @on connection: ->
    d "Connected: #{@id}"
    players[@id] = id:@id,keys:{}
    @emit 'connection',map:game.stage._map
    d "players:"+(k for k,v of players).join()

  # ログアウト処理
  @on disconnect: ->
    delete players[@id]
    d "Disconnected: #{@id}"
    d "players:"+(k for k,v of players).join()

  # キーコード受取り
  @on keydown: ->
    players[@id].keys[@data.code] = 1
    console.log @id+" push "+@data.code
    console.log players[@id].keys

  @on keyup: ->
    players[@id].keys[@data.code] = 0
    console.log @id+" left "+@data.code
    console.log players[@id].keys

d = console.log


サーバー側のロガー

Connected: 10335976501119295222
players:10335976501119295222
10335976501119295222 push 37
{ '37': 1 }
10335976501119295222 push 38
{ '37': 1, '38': 1 }
10335976501119295222 left 37
{ '37': 0, '38': 1 }
10335976501119295222 push 39
{ '37': 0, '38': 1, '39': 1 }
10335976501119295222 left 38
{ '37': 0, '38': 0, '39': 1 }
10335976501119295222 left 39
{ '37': 0, '38': 0, '39': 0 }
10335976501119295222 push 37
{ '37': 1, '38': 0, '39': 0 }


TODO: ユーザー名とセッションIDのヒモ付、まともなロガー