東京工業大学 ロボット技術研究会

東京工業大学の公認サークル「ロボット技術研究会」のブログです。 当サークルの日々の活動の様子を皆さんにお伝えしていきます。たくさんの人に気軽に読んでもらえると嬉しいです。
新歓特設ページ        ロボット技術研究会 HP        ロボット技術研究会 twitter公式アカウント

2016年01月

「ロボット技術研究会」通称「ロ技研」は、その名前の通りロボットの制作や研究はもとより、電子工作や機械工作、プログラミングなどの幅広い分野にわたるものつくり活動を行っています。

カテゴリ一覧: loading

JSでスライドを作るツールを作った話

おひさしぶりです、phi16です。
今回第9回rogyゼミで「もふもふ」というタイトルで発表させていただきました。内容はゲームなどに用いられる「morph」についてです。
資料は ここ にあります。Enterで進めます。本来は画像や動画などがかなり入っていたのですが抜いてあります。
その時に作ってしまったJSでプレゼンもどきを動かすプログラムについて、なかなか放置するにはもったいないので解説等を書いておこうと思った次第です。
JSのプログラムではあるんですけど関数型的知識をかなり利用しているので多少理解が難しいかもしれません。
そのプログラムは ここ にあります。これを用いて書いた、上のスライドの内部コードが これ です。

何故作ってしまったのか

今回の発表ではまさに「morph : 変形」についての話で、ゲーム・アニメなどでの変形表現やモーション、動きなどについて実例とともに解説しました。
実例を出す方法として、自由に「動き」を表現するにはさすがに既存プレゼンツールでは厳しいということと、だからといって自由にプログラムを書いていては大変すぎるということで、JSでEDSLのような形式で記述し、そのままブラウザ上で表示できるようなものがいいと考え実際に実装した次第です。
結果的にはスライド自体は良いものができたと思っていますが、内部EDSLの表記がかなり残念になっているので設計的には失敗だと感じていますが、後述。

基礎

0. Style

まず、私のJSの書き方について。
このコードには、いわゆる「クラス」が存在しません。全てただのObjectとして管理しています。
が、それは何でも入っているというわけではなく、(人間によるチェックとして)全て同じ「型」を持ちます。
元々JSは関数が第一級オブジェクトなのでオブジェクトにクロージャとして生やせば簡単にオブジェクト指向ができます。
全く問題がありません。privateもpublicも自由に作れます。pに予め別オブジェクトを入れておけば継承もできます。
残念なのはブラウザのデバッガで型が表示されないくらいです。thisの闇にとらわれるよりマシです。
オブジェクト指向の本質はカプセル化だと思っています。そういう意味でこういう二次元座標はオブジェクト指向に「向いていない」のだと思ったりもしますが別の話です。
要は、オブジェクトとクロージャを全力で使っていくということです。

1. Color

簡単な例として内部で使っているColor「オブジェクト」について見ていきます。
これは色と色を補間できるようになっています。最初に「コンストラクタ」としてt=0の色、次に「第二コンストラクタ」としてt=1の色を与えるとtを受け取ってCSS的色を返すファンクタ(C++用語)を得ます。
関数だけですがちゃんとそれっぽく動作するのです。クロージャは神。

2. Easing

スライドの最初の方で言っているEasingですが、もちろんスライド自体でも大量に使われています。
Easing「オブジェクト」として利用しているのは、Ease.easingの戻り値です。これはEasingに利用する関数を渡すとそれに沿って動かしてくれるようなものです。
最初にtを渡すのはアニメーション部分との整合性のためです。
valはそのまま[0,1]の値、ファンクタとして呼び出すと線形補間を行います。
メンバのquadはEase-in Quadな補間関数です。これはEasingオブジェクトではありません。
reflect()は「その後に続く命令をIn-OutのInとして解釈」するEasingオブジェクトを返します。
最終行はEase-inOut Quintの[1,2]補間なので1.5というわけです。

設計としては、元々のEase-inな基本関数群を固定してEasingを加工したいという目的の為に、reflectやmirror、invなどの「新しいEasingオブジェクトを返すメンバ」を持たせて自由に関数を組み合わせられるようになっています。
特にIn-OutっていうのはInとInの180度回転をくっつけたものに他ならないので。わざわざ用意するよりも組み合わせとして書けるようにするあたり関数型思想っぽいですかね。
実装ですが、これは単純には実現できるものではありません。quintに渡す値はメンバ関数で好きにいじれるのですが、その結果はいじることができないので「片方を[0.0.5]にする」みたいなことがやりにくいです。つまりquintそのものを線形補間オブジェクトにする前にどうにかしてやる必要があります。
そこで内部的には「継続」を保持しています。分かる人ならもうわかるかもですが。
Ease.easingの第二引数として継続を取り、quintの結果は勝手に継続を通します。Lensにつながるものを感じる。
結果的にEasingオブジェクトのメンバ関数内で「基本関数を通す前」と「基本関数を通した後」に好きに関数を与えられるのでreflectも実装が簡単にできるというわけです。

また、連続的複数Easingのために、indexという関数を持っています。これは位置、全体量、1つに用いる幅を指定することで位置をずらした時にEasingもずれる便利なやつです。
これによって最初の「もふもふ → morph」やEasing一覧などのアニメーションが簡単に描けます。こういうコンビネータ的発想よい。

実装読んでわかるタイプの人はそのほうが速いですね。どうだかわかんないですけど。
関数型やるとわかるようになる気がします。

3. Graphics-1

ふつう、描画するときに座標を指定すると思うのです。
ですが、基本的にこのコードは描画時に座標の直指定を行いません。
その代わりに、「平行移動命令」が別で用意されています。その中で描画すると平行移動された状態になるのです。
同様に「回転命令」「拡大縮小命令」が用意されています。circleの半径は本質的には不要なのですが一応ある。
この方式の便利なところは、「全ての位置が相対的に表示される」ということです。何らかの絵を描く命令組み合わせを頑張って作ったとして、それをちょっとでも移動などしようと思ったら全ての命令の全ての座標を書き換えるしかありません。
この相対方式であれば、外側をg.translateで囲むだけです。
これによってプレゼンツールに「いつでも好きな場所のサムネを表示できる」みたいな機能を一瞬で実装できました。

ちなみに描画は全部HTML5 Canvasで出来ています。video要素を簡単に取り込めるのすごい楽だった。
g.translateはsave → translate → restoreするだけだし。かんたん。

4. Graphics-2

もう一つ面白い特徴として、「全ての描画部品は統一」されています。具体的には
を等しく持ちます。
これによってオブジェクトの色からの分離ができ、構築に時間が掛かるポリゴンデータなどを適切にキャッシュすることが容易になります。
clipではそのオブジェクトの形状でクリッピングを行い、外に描画しないようにできます。これもサムネ機能で使われています。
要は全部書き方がおなじなのです。

特に文字描画についてもそうで、普通はfill(brown)とするところ、stroke(1)(brown)とやれば幅1の線で枠がでます。
ちなみに文字描画はg.text("ぽよ").left.fill(brown)みたいな書き方をします。
原点の指定方法が9種類用意されていて、位置指定に役立つのです。個人的に便利。

なおHTML5 Canvasには文字をPathにする方法が存在しません。なのでopentype.jsでPathを取得して自分で曲線を書いています。
この時最大最小を計算できるのでleftUp~rightDownを適切にoffset付きで計算できるわけです。

アニメーション

1. 設計思想

まず、スライドの状態はfloat1個。
アニメーションを1つ増やすとその状態が1増える。その増え方はアニメーションにかかる時間に依存。
EDSLとしてはアニメーションのタイミングと描画を綺麗に共存できるのが目標。

2. 書き方

pがアニメーション全体の管理を行っているオブジェクトです。
p.in(duration,function(t){f})で、その場所に1イベントを発生させます。
p.inOut(d1,d2,function(t){f})で、その場所と、内部のfが完了した時点に1イベントずつ発生させます。
tは今いくら経ったのかを表すEasingオブジェクトです。durationはアニメーションする時間。
p.inOutのときは0 → 1 → 0のようにtが変化します。
p.outはただ単にtが[1,0]になるだけです。
p.inをネストさせることで連鎖的なアニメーションを書くことができます。
任意のタイミングでp.draw(f)を行ってGraphics系関数を呼ぶようにできます。

3. 内部処理

この書き方自体は非常に直感的だと思っています。ネストさえどうにかなれば。
が、直感的であるということはコンピュータにとって辛いということです。実際このままで目的機能を果たせるとは思えないですよね?
内部では結構面白い処理を行っています。具体的には関数を事前実行してイベントを全部調べるようになっています。

アニメーションに用いるpというオブジェクトは、実はRenderのコンストラクタの引数の関数の引数です。
つまり、本来はここに管理オブジェクトのpがくるはずだけど、そうでないpを入れることもできるということです。Visitorパターンに近いものです。
そして、前処理として、pに「イベント検出オブジェクト」を渡します。これはクロージャになっていて、p.in(d,f)が呼ばれるたびに内部のリストに必要アニメーション時間を記録していきます。
ついでにイベント数カウントも増やしたり、引数として与えるべきEasingオブジェクトの開始時間を記録します。
gはnullだけどp.draw内でしか呼べないようになっているのでだいじょうぶ。
そうすると全てのイベントのタイミングを把握できます。あとはこれに沿って唯一の状態floatを動かしていけばもはやdurationを無視しても良いのです。
こういう「コードの再解釈を行う」という意味でEDSLになっているのです。

実際に描画する際には前処理で検出したイベントタイムラインを平行して追跡して、
「inの引数関数の引数として渡すべきEasingオブジェクト」を追跡によって得られた「Easingオブジェクトの開始時間」にもとづいて計算し与えることで目的の動作を得ることができます。

ちなみに前処理のときにvideoやimageのロードも行うようになっています。通常実行の際は保存しといたDOMを読みだして操作できます。
あとinOutがなければ多分Easingオブジェクトの開始時間を保持する必要はなかったですね。本来はinOutをもっと使うと考えていたので作ったのですが結局全くつかってないです。後述。

4. 状態管理

このプレゼンツール、左/右キーで前と先に移動ができます。が、最初は右に移動できないです。
内部はブラウザの遷移と同じようにhistoryとfutureを記録してあって、進んだことでhistoryが増え、過去に戻れるようになっています。
またそれらは時間がかかるイベントですが、複数回要求があっても全てキューに保持してあるので適切に動作させることができます。
ちなみにキューに要素があるとイベントの速度が0.1秒になるので遷移が速くなります。戻るだけなのに毎回アニメーションついたら邪魔ですし。
その辺はsetTimeoutでいい感じに実装しました。やるだけなんだけど実装量が重いタイプ。

ちなみにこうなっている元の理由は状態をfloat1個じゃなくて分岐もできるようにしようと思っていたからで、過去に戻った状態でEnterですすめると未来が消えます。

その他

p.global

普通のゲームとかでもなんですが、位置を1px単位で調節したいというニーズはかなりあります。
実際スライド書いていて0.142とか0.045とかのいい感じの値を求めるのが大変でした。
そこで、「毎フレーム処理の評価をしなおしている」「ブラウザには便利なデバッグツールがついてる」ことから、デバッグツール内で変数書き換えできればいいなとおもいまして。
書き方は、決めたい定数値の部分にp.global(name,value)って書くだけです。
そうするとvalueを初期値としてnameの名前でObjectに登録されて、ブラウザからはrender.global(name)でアクセスできます。
実装は自明。

でも結局手で書きなおしたほうが慣れてるあたり速かったりしました。

p.configure

Canvasはデフォルトでは左上(0,0)で右下が(w,h)です。でもアニメーションとかするには中心が(0,0)、単位は1なのがありがたいです。
というわけで、それを常に(-r/2,-0.5)から(r/2,0.5)にしたかったのです。でもデフォルトにするには微妙なので自分で書いたほうがいい気がするのですね。
でもg.translateやg.scaleはp.draw内でしか許されていません。なのでアニメーションを描くたびに書きなおすことになります。つらい。
ということでつくったのがp.configureという機能で、
と書くことで、なんとそれ以降の描画実行がsave()の場所で行われるようになります。まぁまさしく求めていたものなのですが。見た目すごい綺麗じゃないですか。
限定継続っぽさを感じるけどちょっと違うのかな。まぁ。

実装には必見の価値があります。うん。

タイムラインの闇関数

下のバーにでてる円の位置を計算してくれる関数です。
クリックした場所を検出するために逆関数をつくるはめになりました :
もっと綺麗ななにかがあった気がしなくもないです・・・

失敗

タイムラインと描画を混ぜるコンセプトはいいとおもうんですけど実現方法が間違っていた気しかしないです。
inOutを使いたいシチュエーションはいっぱいあったけど諸々の制限で使えなかったり。
ネストを防ぐためにわざわざp.preserve.inというイベントを発生させない機能を追加したり。
これについては「スライド1枚」という単位を状態に入れておくべきだったかなといまは考えていますが。

ちなみに私的には「ネストは全く問題ない」派なのですが、今回のイベント管理は本質的にネストするべきではない記述なのでどうにかするべきだったという印象です。
問題は変数のスコープなんですよね。ただこの辺は考えてもあまり解決しないような気がしなくもないです。

あとこれは関係ない話なのですが、自分は完全に理解しているんですけど動作原理を書くのがすごく難しいですね。
関数の引数の関数の引数の関数の引数みたいなことをたまにやるのですけどちょっといろいろと厳しいものを感じます。
でもきっと関数とかクロージャと戯れていれば理解できるとおもうので がんばってください(丸投げ)。

まとめ

HTML5 Canvasを用いてアニメーションが書き放題なプレゼンツールを製作しました。ブラウザ上でものを作るのはデバッガツールが完璧に揃ってるのですごいやりやすいです。
設計的にはEasingやGraphicsなどの最終形にはかなり満足していて、その辺りの知見を得ることができました。
アニメーション管理についてはまだまだ至らない点もありますが、JS上でEDSLのような形式で諸々描けるという点は結構おもしろいとおもっています。
またコード内でクロージャを「乱用」している感じもありますが、むしろこういうのは積極的に使っていいと思っているので、もっと広まっていってほしいと考えています。

あと線形は、ダメです。

2016年部長就任挨拶

こんばんは。
昨日の選挙で部長になったまーぼうです。
普段はゲームの進捗報告をしたり、GAME^3について記事を書いたりしていますが、今回は部長に就任したという事で、就任挨拶をします。

少し、自分の話をしたいと思います。
僕は「大学に入ったらゲームを作ってみたい」というよくある理由で大学入学直後にロボット技術研究会に入りました。そして、RPG(正確にはSRPGというジャンル)を作るぞ、と意気込んでからまだ完成できてないです。
この事から分かるとおり、僕はゲーム制作については全くの初心者です。しかも、大学に入る前に機械工作をしたり、電子工作したり、プログラミングをしたりした経験は殆どゼロでした。ものつくりについては完全に初心者です。これは今でも変わっていないでしょう。
そのような人から見ても、「この人は凄い事をしているんだなぁ」と思えるような高い技術を持った人がこのサークルにはたくさんいます。自分の作りたいものについて、高い技術を持った人達が議論しているのもよくある光景です。そして、私はそのような人達に色々教えてもらって、ゲームをのんびりと作っています。

このようにに、このサークルには様々な人が所属しています。議論する相手や教えてくれる人がたくさんいます。そのような環境は貴重なもので、この事がこのサークルの1番の魅力だと僕は思っています。今後もその環境を残していき、なおかつそれをより充実させることができるよう努力していきたいと思います。

では、1年間よろしくお願いします。
 

コミケも終わったので落ち着いて進捗しましょう

あけおめ!

というわけで一月ぶりです。らりおです。

コミケ行ってきました。一般参加です。
いや、本当はサークル参加したかったんですけどね。
ゲーム作ってるはずだったのに、いつの間にかライブラリ作ってました
とはいえそれ自体は嘗て幾度となく経験したことなので今更ですが、 何度も進捗が後退するようだとさすがに悲しいので、 ばばーん!とリリースしてしまうことにしました。
今回はそのライブラリと、実装に使った言語の話です。

続きを読む
ギャラリー
  • ABUロボコン結果報告
  • スマホから部屋の電気をつけてみた
  • MakerFaireTokyo2017に出展します
  • MakerFaireTokyo2017に出展します
  • MakerFaireTokyo2017に出展します
  • MakerFaireTokyo2017に出展します
  • MakerFaireTokyo2017に出展します
  • MakerFaireTokyo2017に出展します
  • たのしいロボット帝国 製作物紹介
記事検索
最新コメント