起業しました

twitterでは少しtweetしていましたが、2017年7月に株式会社アートマンを設立しました。

https://atman.co.jp/

設立に際して、多くの方にご相談、お支えをいただきまして、実現することができました。大変ありがとうございました。

会社に関してはHPを見ていただいても分かるかと思いますが、個が輝く少数精鋭なチームで最高をやっていくという感じの理念にしました。

これに関してはまぁ自戒もありまして、社会に介在していると何かと我慢があったり同調圧力に屈したりするものですが、そういった中でも個性は隠さずにしたいよね、と。もちろん、個性の主張だけでは中々生きていかれないのも事実ですが、人の面白さやクリエイターとしての力量もまた個性から発せられるものであり、魅力が現れるものです。 (アートマンという言葉自体は、"我"であって"個性"とはまた別ですが、少し拡大解釈と言うことで。)

最高ってなに?という部分ですが、まずは自分たちの信じたものということになります。また、受託開発であればその内容の中で自分たちが提供できる最高点、というところでしょう。抽象的ではありますが、なにぶんコアな事業をバッツリ固定していないので、最高、という表現にしました。

お仕事の依頼, 寿司会へのお誘い, 働きたいなどの奇特なお問い合せは以下へお願いします。

創業よもやま話

そういえば改めて公表はしてませんでしたが、2017年に入ってから個人事業半分, Make It Real Inc.での勤務半分のような働き方をしてました。

実は4〜5月には設立の目標を立てていましたが、色々が起こり、社会の洗礼を浴び、幾度か心が折れていたんですが、やはり受託開発にしてもプロダクト開発にしてもやはり法人格があるないでは差があるなぁと感じ、法人化に踏み切りました。

起業するのももちろん初めてでしたが、書類に関するほとんどはその辺の会社設立サービス経由で行政書士さんに全ておまかせしたのでだいぶ楽だったと思います。一番大変だったのは事務所の賃借と、次点で名刺とWebページの制作(ヒマがなくて)かもしれません。

事務所は最後まで悩んだんですが、

  • 自宅マンション: 登記不可
  • シェアオフィス: 1人だと安いけど増えると普通に高い
  • 知り合いのオフィス間借り: どれだけ関係性が強いか&自由度があるかを鑑みるとウーン
  • 個別の事務所: 高いし設備がないけど自由と広さはある

という中から最終的に自由と広さを取った感じでした。高いと行っても秋葉原徒歩5分で2桁万円程度なのでまぁ許容範囲かなと思っております。何より城があるというのがいいですね。とりあえずPS4とテレビだけはちゃんと置いておきました。

Webページは、WebGL背景チャンスかと思ったんですが、いつまでも出せなくなりそうなので一旦ざっくり作って公開しちゃいました。暇を見つけてちゃんと作り直したいと思ってます。思ってはいます。

改めて文章で書いてみると、エンジニアをやってるわりには全然技術を話題に上げてないですね。自分自身が特定の領域や言語だけではなく、クライアントもサーバもやるようになって、やっぱり色々作れるのが面白いな、という風に最近は思っているのが強いからかもしれません。

所謂器用貧乏というやつではありますが、発生する問題の中にはめちゃくちゃクリティカルなものはありつつも基本的に問題解決の連続とそこに取れる解決手段の引き出しの量だとおもっており、鍛錬を怠らなければ何とかエンジニアとして生きていけるかなぁ、というのが最近の生存戦略だったりします。

エンジニアとして生きていけなくなったら珈琲とカレーとビールのお店をやる予定です。


まだまだ未熟者ですが、今後ともどうぞよろしくお願いいたします。

2017年 脱macOSの旅

※失敗談です。

2017年と言いつつ、去年辺りから開発環境をWindows上に作れないかと考えている。

その意思がすごく高いわけではないが

  • 基本リモートワークなのでデスクトップがメイン
  • 新しいMacbook Proに対してスペックもハードも魅力を感じない
  • Unity + Visual Studioを使う機会が増えた(ただこれに関してはProject Riderがリリースされたらそっちに移る可能性が非常に高い)
  • Bash on Ubuntu on Windowsが気になる
  • ゲーミングPCがある

辺りが琴線だ。

ギョームや趣味のフォーカスが最近だと

という感じなので、Swiftだけはロックインを免れないものの、他の開発に関してはWindowsでも行けるんじゃないかという希望がある。

Linuxは?という疑問を持たれるかもしれないが、Unity, Adobe周りのソフトを考えると候補にはならない。

大まかな構成要素

という感じ。

要するにBoWにはshellをやってもらって、あとはWindowsで全てまかなう想定。

macOSでは、

をやる。やらざるを得ない。脱macOSは失敗しました。

Docker for Windows

最近はもう全てコンテナなのでDockerだけあれば生きていける。

Docker for Macのosxfsのようにクッソ遅いようなこともなかった(ただこういう病状もあるらしい)

Bash on Ubuntu on Windows

2016/10辺りには普通にshellとして使う分にはいいんじゃないかと思っていたが、やっぱり安定してない。

今のInside PreviewのStableビルドだとCtrl+CでSIGINTが通らないのでプロセスを殺せない。

あと一度zshを入れたら何が悪かったのか分からないが何もできなくなった。

macOSからWindowsへ移行する課題と現状の選択

フォント

はいでました〜〜〜〜マッキントッシュ様でました〜〜〜〜〜〜〜って感じだとは思うが、マジでWindowsのフォント見てると気が狂いそうになるので重要な課題。

解決策としては

が一択だと思われる。

仕組みに関してはググって欲しいが、多少の不安定さ、縦書きの対応度、適用されない箇所もあると言うことを鑑みると悩ましいが、適用しないアプリを選ぶこともできるので回避はできる。

ただし、フロントエンドエンジニア的な観点だとWindowsともmacOSとも似つかないレンダリング結果になるので、承知の上で挑んだ方が良い。普段使いのChromeには適用しておき、確認用のChromeには適用しないなどを行えばよさそうだ。

キーボードとIME

macOSで一番いいのが、かな・英数でのIME切り替え(US配列なのでKarabinerで左右コマンド単体タイプにしてる)が、これをWindowsでやるのが若干面倒。

AutoHotkeyで頑張るか、AppleK Pro for 10を使うかどっちかだとおもう。

めんどくさいので後者にしてるが、MMO等のオンラインゲームでnProtect/GameGuardなどのソフトに弾かれることが多いので(まぁこれはAutoHotkeyもそうだが)ある程度諦めるしかない。

terminal

色々試したけどConEmuが良かった。

フォントがキレイ、コミュニティも活発、情報も多い。

Finder

カラム表示おじさんを貫き通してきたが諦めた。諦めるほど人間は強くなれる。

ランチャ

色々あるけどもうCortanaでいいんじゃないかなって思い始めてる。別にCortanaも悪いわけではない。

TimeMachine

1日1回朝方、Windowsの標準バックアップをする。

ほか

所感

開発はもうずっとIntelliJでやってるので、コアな開発に関しては余りコンテキストスイッチがなく移行できる。

BoWが安定してくれれば使い物になりそう。

この記事は、macOS Sierraで書きました。

おまけ

VMWareの上でマックのようなものが動く夢を見たんですが

  • Intel VTのおかげか想像以上に速い
  • マルチモニタはできない
  • iCloud, AirDrop関係の機能が一切使えない
  • FPSが余りでない(30-60を行き来する感じ)

という妄想が見えました。

Swiftを使ったRx入門の話をしてきた

先日、技術顧問先の企業様にてRx入門の話をしてきました。

その時の資料をせっかくなので公開しておきます。

サンプルコード: https://gist.github.com/dameleon/9fc3178aaba1f695455234af40730a03

ページ数, 文字数共におおく、中々資料だけでは内容を理解しきれないとは思いますが、Rxに関するベーシックな概念は一通り触れているので、何かの参考にして頂ければと思います。

需要があればこの内容についていつでも講演します。

あ、あとツッコミがあれば随時ツッコんでいただけるとすぐに修正します。

制作秘話とか

Rx入門関連の資料として、実践的に入るのか、概念から体系的に入るのかを悩んでいたんですが、自分がRxに入門〜実践できるまではひたすらコードを書いていて、後々考えたときにやはり概念から入った方が良かったなという気持ちがあったので、わりと後者に寄せた資料になりました。

人によっては好みもあるので一概には言えないですが、Rxは概念が多いので初心者〜中級者くらいの間には役立てるかな、という気持ちです。

実際自分自身もまだまだRxを使った設計に悩むことが多いですが、また機会があれば次は実践的な内容で話をしてみようと思ってます。

転職しました

f:id:dameleon:20151203154035j:plain

どうも、帰属意識のなさです。

TKG(たまごかけごはん転職のご報告ご飯) Advent Calendar 2015の3日目のエントリです。

ご報告が遅れてしまいましたが、2015年10月中旬をもちましてDeNA Co., Ltd.を退職し、Make It Real Inc.に転職しました。

DeNAには、通算2年10ヶ月所属していました。意外と長く?勤めましたね。

DeNAで何でやってたの

f:id:dameleon:20151203144945j:plain

入社時の扱いとしてはウェブフロントエンドやる人(ウェブフロントエンドエンジニアというジョブがそもそも無くてクリエイターという扱い)でしたが、共通開発グループに所属している時が多かったので、なんやかんやでバックエンドもやったりインフラ云々の話をしてみたり、時には新規開発したりガッツリ運用したりで本当に色々なことをやりました。いわゆる技術的雑用みたいなポジションだったと思います。

f:id:dameleon:20151203145617j:plain

関わったタイトルってどのくらいあるんだろうと思ったんですが、細かいのも含めると十数タイトルくらいありました。多分社内でも多いほうではないでしょうか。

また、共通開発チームでは某Flashミドルウェアのメンテナや、リリース前にピヨピヨしちゃったタイトルの改善提案やパフォーマンスチューニング指示を行ったりしていました。
とくに某ビッグIPタイトルでの実績は、僕のここ2年の集大成と言っても過言ではないかもしれません。

で、なんで辞めたの?

f:id:dameleon:20151203145528j:plain

もっと矢面に立ってモノを作りたかったこと、クライアントもウェブだけにとどまらずネイティブもガッツリ作っていきたい。加えてサーバ、インフラも業務知識として得たいと思った時に今より良い環境はあるなと感じたのが大きいです。

要するに、何でも自分の手で作りたくなっちゃったんですね。もとい、DeNA(のサービス開発側)で活躍してるエンジニアはこういうタイプの人が多く、それに憧れたと言うのもあります。

細かい話は飲みに連れてってくれたら話しさせて頂きます。まぁでもそんなにネガティブなことはないです。たぶん。

ちなむと辞めるタイミングに関しては、社内でも自分の名前が売れてきたこと、上司にもかわいがってもらえ始めたこと、新規開発に関わり始めたことなど鑑みると若干微妙だったかなーとは思ってはいました。上司各位にもリソース調整などで本当にお世話になりました。

で、これからどうするの?

f:id:dameleon:20151203152100j:plain

Make It Real Inc.で、何でもやらせてもらうつもりです。

@typester, @soh335の2人が本当に強いエンジニアなので、そこに入っていって大丈夫かなぁと不安はありましたが入社以来まぁなんとか楽しくやってます。

既にもういくつかリリースを終えていて、

  • オフィスの表札を100均で売ってるものだけで作る
  • 自分のデスクをIKEAで買ってきて、ヤスリ掛けから組み立てまでする

という実績を残しました。

エンジニアリングっぽいことでは、iOS, Androidのネイティブアプリ開発を最近は業務にしています。

「もうWebはやらないんですか?」と聞かれますが、そんなことはないです。この数年で培ったもの、とくにWebブラウザ上でのパフォーマンスの話は価値があると思っていて、これからも情報は拾っていきますしお仕事があればブラウザ本来の力を引き出して見せますよ、という気持ちはあります。

Make It Real Inc.では、"技術コンサルティング事業"も請け負っておりますのでお困りの方はぜひご連絡ください。(営業)

個人での活動と今後

f:id:dameleon:20151203153956j:plain

個人では、スマートフォン向けのゲームを作っていきます。

エンジニアリングで関わらせて頂いているタイトルが1本、楽しい仲間と1から全て作っているのが1本、の合計2本をまずは作ります。

ゲームはやるのも楽しいですが、作る方が数千倍楽して辛くて、楽しいです。

DeNAでは、1からまるっと自分が関わらせていただいたタイトルはなかったので、自分の力でどこまで面白いものが作れるのかにチャレンジしていきます。


一応置いておきますが、Wishlistはこちらです。何かバラのビールがあんまりなかったので一緒にのみに言ってくれるとかでもいいんですよ!

明日のTKG Advent Calendar 2015は@S_Shimotoriさんです!

f:id:dameleon:20151203154121j:plain

Paw.jsというのを書いたのと今から始めるマルチタッチイベント処理

どうも、連載予定は絶対に次回を書かないでおなじみの僕です。

Paw.js is 何

マルチタッチに対応してない、いわゆるfastclickを実現するものやtapイベントを発行するライブラリ、やたら機能が多くかつ特にtouchmoveでのイベント過多で処理落ちしかねないライブラリが多かったので、現実的に使うであろう範囲でめちゃ軽なやつが欲しかったので作りました。

Paw.js

あなたがもしモバイルWeb開発者で60fpsを出さなければ死ぬのであれば使えばいいと思いますし、そんな必要が無いもしくはフリックやジェスチャも取りたいと言うのであればHammer.jsとか使えばいいんじゃないでしょうか。


  • シンプル, 小さい, 速い
  • touchevent, pointereventのマルチタッチ対応
  • tap, doubletap, pressの3つのカスタムイベントを発行
  • fastclick機能

です。Webサイトやアプリケーションで現実的かつ恒常的に使用されうる機能に絞りました。もし需要があるならフリック系も作ろうとは思いますが現状Hammer.jsで十分でしょう。ってかみんなWeb上でジェスチャとか使うんですかね。
また、スワイプイベントでよく見られる"指が動き終わってからイベントが発火"するというのがありますが、指に付いてこないで動かし終わってから描画が追いつくという気持ち悪い状態になりますのでそういうのはやらないためにも実装してません。逆に言うとそういうのは普通に自分でやった方が軽く気持ちよく動かせます。

カスタムイベントを使用(非搭載ブラウザは普通のイベント)を使ってますので、標準APIはもちろんjQueryでも$('div').on('tap', fn)のようにイベントを拾うことが出来ます。もちろんBackboneとかその辺でも使えるようになってます。

ちなみに Paw は犬猫などの足という意味で肉球を思い出させ、ライブラリ制作者も使用者にも癒やしを与える効果があります。

相変わらずUI周りのテストは書きづらくまだ手を付けてないのですが、一通りの端末ではテストしましたのでご興味があればお使いください。

※通常状態だとdocumentにtouchmoveべた貼りするのでchromeにperformance warningだされますが、一部のAndroidさんがいきなりtouchcancel呼んだりしていきなり別の要素でtouchmoveされたりする挙動で何のタッチか分からなくなる問題のため現状は仕方ありません。

いい加減覚えるマルチタッチイベント処理

タッチイベント周りはマルチタッチ ウェブ開発 - HTML5ROCKSを呼んでいただければ大体ご理解いただけると思いますが、今回は要約して説明していきます。

TouchEvent と PointerEvent

え?IEの話はどうでもいい?まぁまぁ落ち着いてください。昨今はSurface等のデバイスもありますし覚えておいて損はありません。

TouchEventはtouches, targetTouches, changedTouchesというTouchListオブジェクトが付与されたイベントデータと考えればよいと思います。このTouchListにはそれぞれ以下のようなTouchオブジェクトが入っています。

  • touches: 現在画面上にある全ての指のデータ
  • targetTouches: 現在のDOM要素上にある指のリスト
  • changedTouches: 現在のイベントに関与している指のリスト

JavaScriptからハンドリングする場合、touchesは2本以上の指でのジェスチャ、targetTouches&changedTouchesはタップやスワイプなどの1つの指を扱う場合が多いです。

変わってPointerEventはTouchListオブジェクトのようなものは備えておらず、1つのイベントで1つのポインターを扱うというようになります。(TouchEventでいうとchangedTouchesに入ったTouchオブジェクトを常に1つ受け取るような感覚です)

識別子

タッチイベントを扱う上(特にマルチタッチ)で重要なのが識別子です。TouchEventであればTouchオブジェクト、PointerEventであればイベントのパラメータに必ず識別子が付いているので、その識別子を元にイベントを扱っていくと良いでしょう。

TouchEventの識別子

TouchEventの識別子は、touches, targetTouches, changedTouchesのTouchListオブジェクト内に入ったTouchオブジェクトのプロパティidentifierに付与されています。

// ex. 識別子を取る
document.addEventListener('touchstart', function(ev) {
    // 最初のTouchオブジェクトを取る
    var touch = ev.touches[0];     

    // 識別子
    console.log(touch.identifier);
});

識別子で判断することで、以下のように別の場所が他の指でタッチされた場合の処理を書くことが出来ます。

var lastTouchId = null;

document.addEventListener('touchstart', function(ev) {
    var touch = ev.changedTouches[0];

    lastTouchId = touch.identifier;
});

doucment.addEventListener('touchmove', function() {
    var touch = ev.changedTouches[0];    

    // 一番最後にタッチされ始めた指以外は処理しない
    if (lastTouchId !== touch.identifier) {
        return;
    }
});

Note.

例ではtouch, changedTouchesの最初のTouchオブジェクトのみ扱ってますが、複数のTouchオブジェクトが存在する可能性があるためfor等で列挙して処理するほうが良いでしょう。

PointerEventの識別子

PointerEventは通常のUIEventにポインター情報のパラメータが追加で付与されたようなオブジェクトとなっています。

document.addEventListener('pointerdown', function(ev) {
    // 識別子 
    ev.pointerId
});

上でも書きましたがTouchEventでいうchangedTouchesのTouchオブジェクトがイベントパラメータとして入ってくるような状態になるので、pointerIdプロパティを元にタッチごとの判定を行います。

touchmove, pointermove, mousemove (+ scroll)で無理をしない

若干マルチタッチとは離れますが、上記のイベントの中で特にCSSの変更やレイアウトプロパティへのアクセスを行うべきではありません。

これはブラウザによるスタイルの再計算や再描画が高頻度で走ることによるブラウザの全体的なパフォーマンスの落ち込みを防止するためですが、昨今流行りのパララックスエフェクト等ではガッツリと使われている現状が残念でなりません。(まぁPCブラウザならそんなに気にすることもありませんがね…)

こういった場合は、以下のようにイベントと描画のロジックを切り離し、描画の頻度を下げることで回避できたりします。

//// touchmoveごとにスタイルを変更したい(適当バージョン)

var fps = 30; // 30fpsに制限
var frameTime = 1000 / fps;
var isAnimated = false;
var lastTouchInfo = null;
var element = document.getElementById('some-element');

document.addEventListener('touchstart', function(ev) {
    // 最初のデータを入れてアニメーションを開始
    lastTouchInfo = ev.changedTouches[0];
    isAnimated = true;
    animation();
});

document.addEventListener('touchmove', function(ev) {
    // touchmoveイベントの中ではデータの更新だけする
    lastTouchInfo = ev.changedTouches[0];
});

document.addEventListener('touchend', function() {
    // アニメーションの終了
    isAnimated = false;
    lastTouchInfo = null;
});

document.addEventListener('touchcancel', function() {
    // アニメーションの終了
    isAnimated = false;
    lastTouchInfo = null;
});

function animation() {
    // isAnimatedフラグが立ってなかったら終了
    if (!isAnimated) {
        return; 
    }

    // 最後のタッチイベントからデータを取得
    var x = lastTouchInfo.pageX;
    var y = lastTouchInfo.pageY;

    // 何かのスタイルを変更したりする
    element.style.top = y + 'px';
    element.style.left = x + 'px';

    // 次のanimationを登録
    setTimeout(animation, frameTime);
}

実はマルチタッチのデータを上手く扱う方法はこのくらいです。
が、実際にそのデータを扱い始めるとまた別の難しい問題(指が動いた距離、角度など)が出てきますので、その辺りは頑張ってみてください。

幸せになりたいソーシャルゲーム系Webフロントエンドエンジニアが本気で考える HTML GUI ツール第一回

何か dis られっぱなしなのも癪に障るので、現在のソーシャルゲームでの Web フロントエンド開発を振り返りつつ、今後のソリューションやツールとしての HTML GUI ツールの可能性を本気で考えてみようの第1回。


簡単な自己紹介をするが、僕はソシャゲ開発に携わって3年になる。その前はソシャゲではなかったが俗に言われるガラケーから PC サイトまで多岐にわたる Web 開発に携わっていた。

ソシャゲの新規開発〜運用には5本、運用のみには3本のタイトルに関わった。幸か不幸か新規開発のチームに多く呼んでいただいて、自分で言うのも何だがその第一線で戦ってきたつもりだ。

ソシャゲの Web フロントエンド開発と言ってみたものの、そのワークフローや担当領域は所属会社によってかなり違うのではないかと思われる。

というのは、1社目では PSD のスライスや微調整からマークアップ, JavaScript までほぼ1人やっていたが、2社目の今ではほとんど JavaScript にしか触れなくなった。

それも踏まえつつ、昨今のソシャゲ Web フロントエンド開発では大体各社とも以下のような課題があるのではないかと思っている。

"スマートフォンへシフトしたことによるデザインのリッチ化に伴って、 HTML マークアップCSS スタイリング工数の肥大化と伝統工芸化"

要するに、職人しか到達できない世界になってしまっていないだろうか?ということだ。

仮に、あなた自身やチームが上記の問題を抱えていないのであれば、ここから先を見る必要は無いし、どういうワークフローと人材で作業をしているのか是非教えてもらいたい。


HTML と CSS の表現力は年を追う毎にリッチなものへ進化してきた。そして、 Web ソーシャルゲームもその一途を辿っている。

例を挙げればキリがないが、グランブルーファンタジー, スカイロック, ダンジョンポッパー, ドリランドまおゆう, グリマス, ガールフレンド(仮), Lord of Knights とか適当に思いつく感じでそんなところだろうか。

上記の中でも例外はあるが、ガラケー時代から続く Mobile Web の系譜を受け継ぎつつも、いわゆるスマートフォンアプリを強く意識して Web サイトを制作するといったのが昨今の Web ソーシャルゲームだ。(すべてがすべてそうなっていると言うことではないし、そうなっていないからと言ってどうということではない)

こういったゲームのスタイルがどうやって構築されているか、というと

  • 職人によって1つ1つネストされた div
  • 高度に構築された CSS拡張メタ言語

というような、知らない他人は手の付けようがないくらいの物体でできているはずだ。(あ、一応補足はしておくがその仕事自体を否定しているわけではないし、無駄だとも言っていない。僕も職人の1人ではあると思っているし、その力がなければできないのは確かだと思う。)

しかし、否定はしていないが、そんなマークアップとスタイリングはやりたい作業だろうか?

来る日も来る日も div ッターン div ッターン div ッターン とキーボードを叩き続けるのが僕らが望んだ未来だっただろうか?

絶対配置だらけの要素の px, % を整えるために生まれてきたんだろうか?

少なくとも、僕はそう思っていないし、自分だけでなくチームにとってももっと良いやり方を模索しなければいけない時期に来ていると考える。

僕ら Mobile Web は Flash という希望を失ってしまったし、昨今は Unity や Unreal Engine などのゲーム開発環境が Web へ進出してきている中で、 よりコンテンツの制作に集中できる 環境というものを目指すべきなのではないだろうか。


幸い、前述したとおりに Web ゲーム開発ではセマンティックなマークアップが求められない。(いや本来はすべきなんだろうが、ゲーム画面はそもそも文章でない場合は多いのでセマンティックを考えてたら SAN 値が上がって下がって死ぬ)

追記 2014/05/12

ブクマでご指摘頂いたとおり、 SAN 値が上がって → SAN 値が下がって の誤りでした

そもそも要素の配置やスタイリングは今も絶対配置や :before, :after だらけでできている。要するに、力でごり押ししても運用上破綻しなければ勝ちだ。

そう、運用上破綻しなければ、今すぐにでもすべての Web ソシャゲフロントエンドエンジニアがエディタを捨てて GUI で画面を作ってもいいのだ。

むしろ、画面を作る必要すらなくなるかもしれない。デザイナーがやればいいのだから。そうなったら仕事がなくなるし最高だ。霞でも食って生活していけばいい。


眠いのと怒りが冷めてきて筆の進みが遅くなったので一旦切るが、そんなツールの要件定義を次のエントリで行っていきたいと思う。

FlashCC for HTML5 による fla ファイルの書き出し(基本編)

いわゆるソーシャルゲームのゲーム画面で、FlashCC の HTML5 書き出しを使って演出部分を制作し、アプリケーション側との繋ぎ込みを行った際のメモ。

どのゲームかはあんまり大きい声で言えない?ので、もし知りたければ twitter で聞いてもらえば。


今回は基本中の基本で、そもそも FlashCC がどういう形の CreateJS のコードを書き出すのかを細かく書いている。全3回くらいでお送りする予定です。

サンプルコードと言ってもただ MC を数個配置しただけだが一応 Github においてある。

サンプルでは test.fla というファイルを作り、とりあえず4つの各種インスタンス(ムービークリップ、ボタン、グラフィック、ビットマップ)を配置する。(今回は描画部分のみにフォーカスするのでサウンドについては省略)

また、合わせてCreateJSの公式ドキュメントにも一通り目を通す、もしくは都度検索していくことを奨める。大事なことはほとんど書いてあった。

ドキュメントとパブリッシュ設定

後のエントリで書くが、Retina 対応をするためにコンテンツの制作サイズは w640 x h832 とした。実際の表示サイズは w320 x h416(iPhone4 iOS6のブラウザサイズ)になる。

以下、パブリッシュ設定の説明

タイムラインをループ
ステージのタイムラインをループさせるかどうか。デフォルトはループ。

HTMLをパブリッシュ
fla の単体再生が可能な html ファイルをパブリッシュするかどうか。

アセット書き出しオプション
画像、サウンド、CreateJS の各アセットの書き出しパスを指定できる。CreateJS は下記のホストのライブラリにチェックを入れている場合は書き出されない。

JavaScript 名前空間
シンボル、画像、CreateJSの各オブジェクトの名前空間をそれぞれ設定できる。
シンボル: Flash で作成した各シンボルのオブジェクト
イメージ: ID (ファイル名 or リンケージ) をキーにした image のインスタンス
CreateJS: CreateJSの各クラスオブジェクト
が入ると思っていればよい。

(詳細)ホストのライブラリ
チェックを入れることで CreateJS ライブラリの JS ファイルを CDN(http://code.createjs.com) から呼ぶように html が変更される。
ちなみにどちらにせよ最新版ではないものが読まれるので注意

(詳細)非表示レイヤーを含める
その名の通り。デフォルトはチェック。チェックを外すと、非表示にしているレイヤーの内容物は、配置されなくなる。
しかし、前述の通り「その非表示レイヤーでしか使っていない画像も、ファイルは書き出される」ので注意。
非表示レイヤーの JavaScript コードは、チェックが外れていれば書き出されない。
ちなみに、ガイドレイヤーに書いたJavaScriptも書き出されない。

(詳細)シェイプをコンパクト化
デフォルトはチェック。
シェイプの描画命令を圧縮コードにするか展開するかの選択。特別な理由がない限りそのままでいいと思います。

(詳細)各フレームでの境界を取得
複数のフレームを持つムービークリップの1フレーム毎の描画範囲を書き出すかどうか。
トゥイーンや描画物の変化が起こった場合は、1フレーム毎に描画範囲は変わるため、あらかじめそれをFlashCC側で各フレーム毎全て取得しておくことができる。
通常は使用しないと思うが、CreateJS の機能 "SpriteSheetBuilder" (動的にスプライトシートを生成してキャッシュする機能)を使うときに取れると便利。

出力されるもの

fla を標準設定のままパブリッシュすると、以下のようなファイルが出力される。

$ tree
./
├── images        <----- fla内で使用しているビットマップ画像全て
│   └── bitmap_1.png
├── test.fla
├── test.html <------ flaを単体再生するための html
└── test.js       <----- 1つのステージ(fla ファイル)で、1つの JavaScript が生成される。いわゆるswfファイルのようなもの

JavaScript

実際に出力される JavaScript を部分部分取り出しながら、解説していく。

生成されるネームスペースと無名関数でのカプセル化

出力された JS ファイルは下記のような無名関数によってカプセル化される。

(function (lib, img, cjs) {
    // この中では、 img.hogehoge のように namespace を意識せずにアクセスできる
})(lib = lib||{}, images = images||{}, createjs = createjs||{});
// パブリッシュ設定の通りに、変数が宣言される
var lib, images, createjs;
注意
  • パブリッシュ設定でネームスペースに指定できるのは変数名のみとなり、オブジェクトのプロパティは設定できない
// # OK
// ネームスペース設定 lib_namespace 
(function (lib, img, cjs) {
})(lib_namespace = lib_namespace||{}, images = images||{}, createjs = createjs||{});
var lib_namespace, images, createjs; 

// # NG
// ネームスペース設定 lib.namespace 
(function (lib, img, cjs) {
})(lib.namespace = lib.namespace||{}, images = images||{}, createjs = createjs||{});
// var lib.namespace という宣言になってしまうので syntax error となる
// この行を削って、あらかじめ自分で宣言しておけば動くが…
var lib.namespace, images, createjs; 
  • 複数の flash ファイルで namespace が被っている場合、その複数の flash ファイル内で使用しているシンボルや画像の名前がぶつかって、あとから読み込んだ方に上書きされてしまう

複数ファイルを持つ場合は、createjs 以外の namespace はそれぞれの flash で分けておいた方が安全。もしくは、プロジェクト側で統一できるようにルールを決めること。

プロパティ

flash ファイルの設定情報やアセットの情報は lib.properties という変数に格納される。

上にも記載したが、シンボルの namespace が被っていると properties の情報も上書きされるので注意すること。

lib.properties = {
    width: 640,
    height: 832,
    fps: 24,
    color: "#FFFFFF",
    // manifest という配列に、flaファイル内で使用しているアセットの src と ID(画像名 or リンケージ名) が全て入る。
    // ファイルパスはパブリッシュ設定に準ずる
    manifest: [
        {src:"images/bitmap_1.png", id:"bitmap_1"}
    ]
};
シンボル

flash 内で使用しているシンボルは全てシンボル名 or リンケージ名で lib.{シンボル名} というようにオブジェクトとして登録される。

ルートのシンボルも1つのシンボルとして登録され、flash ファイル名がシンボル名として使用される。

各種類のシンボルは、以下のように CreateJS のクラスからインスタンス化される

createjs.MovieClip : ビットマップシンボル以外の、タイムラインが複数存在するシンボル

createjs.Container : ビットマップシンボル以外の、タイムラインが1フレームだけ存在するシンボル

createjs.Bitmap : ビットマップシンボル

各クラスの詳細は公式が一番詳しいので合わせて確認すること。

ルートシンボル
// test.fla というファイルなので lib.test がシンボル名になる
(lib.test = function(mode,startPosition,loop) {
    this.initialize(mode,startPosition,loop,{});

    // フレーム内の JavaScript は frame_{FRAME_NUM} というメンバ変数に格納される
    this.frame_0 = function() {
        this.stop();
    }

    // メンバ変数に登録されたフレーム内 JavaScript は、createjs.Tween で tween オブジェクト化され
    // その MC のタイムラインに addTween すると登録されてフレーム毎に呼ばれるようになる
    this.timeline.addTween(cjs.Tween.get(this).call(this.frame_0).wait(1));

    // インスタンス名を設定しないシンボルは this.instance, this.instance_1 のように後ろの数字がインクリメントされてメンバ変数になる
    // インスタンス名を設定した場合は、 this.{インスタンス名} という感じでメンバ変数になる
    // ビットマップシンボルのインスタンスを生成
    this.instance = new lib.bitmap_1();
    // 初期の配置位置を設定。他のインスタンスと x 座標の位置がずれているが
    // これはビットマップシンボルの座標基準が右上になっているため(他のシンボルは中央)
    this.instance.setTransform(220,710);

    // グラフィックシンボルのインスタンスを生成
    // グラフィックシンボルに対しては、"synched",0 の引数は固定
    // この指定があると、呼び出し側と子がタイムラインを同期するようになる
    this.instance_1 = new lib.graphic_1("synched",0);
    // 初期の配置位置を設定
    this.instance_1.setTransform(310,553);

    // ボタンシンボルのインスタンスを生成
    this.instance_2 = new lib.button_1();
    // 初期の配置位置を設定
    this.instance_2.setTransform(310,350);
    // CreateJSのButtonHelperという機能で、SWFのボタンっぽく動くようにタイムラインやイベントをよしなにしてくれる
    new cjs.ButtonHelper(this.instance_2, 0, 1, 1);

    // ムービークリップシンボルのインスタンスを生成
    this.instance_3 = new lib.movieclip_1();
    // 初期の配置位置を設定
    this.instance_3.setTransform(310,148);

    // addTween して、1フレーム目から表示されるようにインスタンスたちを登録する
    // 一見何の変哲もないが、state: [] と渡してる配列の先頭から重ね順が後ろになる
    this.timeline.addTween(cjs.Tween.get({}).to({state:[{t:this.instance_3},{t:this.instance_2},{t:this.instance_1},{t:this.instance}]}).wait(1));

}).prototype = p = new cjs.MovieClip();
// nominalbounds は、そのシンボルの初期の表示上の基準座標と大きさが設定される。どこかのフレームで座標や大きさが変化してもこの値は変わらない。
// ルートシンボルだけ、x, y の設定がよく分からないことになってるので調査中
p.nominalBounds = new cjs.Rectangle(340.1,441,414.1,767);
その他シンボル

各シンボルの命名は、リンケージに設定した名前もしくはシンボル名で lib.{symbol_name} のように出力される。

日本語のシンボル名を付けている場合、lib.シンボル1 のようになってしまうので注意すること。(動くけど)

// ビットマップのオブジェクト
(lib.bitmap_1 = function() {
    // 画像の実体は、manifest に登録された id で img というネームスペースに登録しているので (HTML の方に書いてある)
    // ここでは img.bitmap_1 として画像のデータを呼んでいる
    this.initialize(img.bitmap_1);
}).prototype = p = new cjs.Bitmap();
p.nominalBounds = new cjs.Rectangle(0,0,180,80);


// ムービークリップシンボルのオブジェクト
(lib.movieclip_1 = function() {
    this.initialize();

    // 表示するシェイプを登録している
    this.shape = new cjs.Shape();
    // シェイプを定義している。p() のメソッドに食わせている圧縮文字はパスやオブジェクトを描画するためのもの
    this.shape.graphics.f("#33CC00").s().p("AuDGPIAAsdIcHAAIAAMdg");

    // このMCはタイムラインを持たないので、単純に addChild される
    // MCを持っているシンボルは、上のルートシンボルのように addTween で配置される
    this.addChild(this.shape);
}).prototype = p = new cjs.Container();
// 基準点が中心なので、-([width|height] / 2) した点を基準座標としている
p.nominalBounds = new cjs.Rectangle(-90,-40,180,80);


// グラフィックシンボルのオブジェクト
(lib.graphic_1 = function() {
    this.initialize();

    // ムービークリップの方と一緒
    this.shape = new cjs.Shape();
    this.shape.graphics.f("#FFCC66").s().p("AuDGPIAAsdIcHAAIAAMdg");

    this.addChild(this.shape);
}).prototype = p = new cjs.Container();
p.nominalBounds = new cjs.Rectangle(-90,-40,180,80);


// ボタンシンボルのオブジェクト
(lib.button_1 = function() {
    this.initialize();

    // ムービークリップの方と一緒
    this.shape = new cjs.Shape();
    this.shape.graphics.f("#33CCFF").s().p("AuDGPIAAsdIcHAAIAAMdg");

    this.addChild(this.shape);
}).prototype = p = new cjs.Container();
p.nominalBounds = new cjs.Rectangle(-90,-40,180,80);
その他諸々
  • パブリッシュ時にもワーニングが出るが、CreateJS ではフレームが0からスタートすることに注意する。
  • 例えば、5フレーム目から表示されるシンボルも初期化は最初に行われる。「重いシンボルだけど、5フレーム目からだから関係ないか」などと思ってるとめっちゃ初期化走って死ぬので注意。
  • nonimalBoundsに設定される基準位置と大きさは、ガイドと非表示レイヤーに入っているものも含まれる。特にガイドはサイズを気にしなかったりする場合があるので注意。(パブリッシュ設定で非表示レイヤーはパブリッシュしなければ大きさに含まれません)

html

制作した Flash を単体再生するための html

これだけだと CSS もなにも効いていないので Flash でアプリケーション全部を作っているようなことがなければただの確認用のページである。

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>test</title>

<!-- CreateJS のコードが読み込まれる。パブリッシュ設定次第では、CreateJS の JavaScript もローカルに出力されるが、どのみちバージョンが古い -->
<script src="http://code.createjs.com/easeljs-0.7.0.min.js"></script>
<script src="http://code.createjs.com/tweenjs-0.5.0.min.js"></script>
<script src="http://code.createjs.com/movieclip-0.7.0.min.js"></script>
<script src="http://code.createjs.com/preloadjs-0.4.0.min.js"></script>
<!-- ここで test.js が読み込まれるので、var lib, images, createjs; のようにグローバルへ変数が宣言されていることになる -->
<script src="test.js"></script>

<script>
// この時点で泣きそうになるがまぁ察してほしい
var canvas, stage, exportRoot;

function init() {
   canvas = document.getElementById("canvas");
   // images という変数は test.js 下部で宣言されており、オブジェクトが入っているはずだが一応宣言しているのだと思われる
   images = images||{};

   // LoadQueue(PreloadJS)をインスタンス化
   var loader = new createjs.LoadQueue(false);
   // loader に各イベントを設定
   loader.addEventListener("fileload", handleFileLoad);
   loader.addEventListener("complete", handleComplete);
   // manifest を丸ごと渡すと全て読み込んでくれる
   loader.loadManifest(lib.properties.manifest);
}

function handleFileLoad(evt) {
   // ファイルが1つ読み込まれるたびに呼ばれる
   // images の変数に、manifest に書いてある id をキーにして、HTMLImageElement のインスタンスが保存される
   if (evt.item.type == "image") { images[evt.item.id] = evt.result; }
}

function handleComplete() {
   // lib.test (ルートシンボル) を初期化する
   exportRoot = new lib.test();

   // ステージを初期化して、配置
   stage = new createjs.Stage(canvas);
   stage.addChild(exportRoot);

   // ステージを手で1度アップデートしてるが意図が不明?
   // この時点で Canvas に最初の描画がされる
   stage.update();
   // mouseover, mouseout のイベントが、描画しているシンボルに適用されるようになる
   stage.enableMouseOver();

   // Ticker に FPS を設定して、tick イベントに stage 自身を登録することで、stage.update が設定した fps で呼ばれるようになる
   createjs.Ticker.setFPS(lib.properties.fps);
   createjs.Ticker.addEventListener("tick", stage);
}
</script>
</head>

<body onload="init();" style="background-color:#D4D4D4">
    <!-- 描画される canvas 。この HTML だけでは retina 対応はできない -->
    <canvas id="canvas" width="640" height="836" style="background-color:#FFFFFF"></canvas>
</body>
</html>

基本編はここまで。

次回は地獄編をお送りします。