読者です 読者をやめる 読者になる 読者になる

転職しました

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 ファイルの書き出し(基本編)

javascript CreateJS FlashCC

いわゆるソーシャルゲームのゲーム画面で、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>

基本編はここまで。

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

100万円が欲しくてisucon2013予選に参加したけど惨敗した話 #isucon

isucon

表題の通り。

予選に参加できなかった某bobpp(優しいからリンクはしない)を甲子園(本戦)につれてくと強い約束をしましたが、結果完敗しました。

初参戦のisuconで思ったより何もできないのがショックで思わず当日の夜は焼き肉食いながらくだをまいたりしてたんですが、

そもそもフロントエンドエンジニア(笑)ですしみたいな言い訳とか言うと @hokaccha さんとかから椅子が飛んできそうなので自重しつつ

バックエンドの素人がどう考えて何を行ったのかをログにしておきます。

ちなみに参加した(するはずだった)メンバーとしては

  • damele0n(フロントエンド、バックエンドちょっと分かるくらい)
  • junqi(バックエンドアプリケーションエンジニア)
  • bobpp(バックエンドアプリケーションエンジニア)

という構成。ちげーよってなったらmentionください。

前日

最初から2人で予選を戦うことは分かっていたので、みんな大好き渋谷グリフォンで3人で作戦を練る

  • 使用言語はPerl(まぁPerlが一番慣れてるよね[ぼくは書けませんが]みたいなノリと勢い)
  • アプリを junqi 、インフラを damele0n でいいんじゃない。知らんけど。
  • とりあえずベンチ走らせてアクセスログ見ようか
  • 多分何かしらのKVSは必要になるよね。アプリの状況見ながら Redis か memcached かな。
  • HS(HandlerSocket) どうよ?ビルドめんどくさいよねー、アプリも全部リプレイスできる余裕あるかな。
  • 無難なところからやろう(クソクエリ潰す、インデックス貼る)
  • 構成は nginx をフロントでアプリを Starlet で
  • 任意のタイミングで再起動して耐えられるって、いきなり再起動かかったりするのかな…?
  • 某社内のキャッシュ機構をパクリ影響を受けたらいいんじゃないか
  • Template Toolkit使ってりゅううとか無いよね(もちろん無かった)
  • 時間に余裕できたら NYTProf でプロファイリング

インフラエンジニアなんてやったこともない私はその日28時くらいまでChefレシピをまねごとしながら作ったり、nginx の基本的な使い方を勉強したり、MySQLのチューニングなどをせっせと勉強したりしていました。

当日

当日はjunqiの家で戦いに挑んだのですが、開始前にいきなりWifiがつながらない問題が発生。僕は最後までLTEのテザリングで参戦することに。

以下、時系列

10時

AMIからインスタンス作るところはスムーズに進んだけど、あれ ssh 繋がらなくねとか言ってたら Security Groups の設定ミスっててポート空いてねーfxxkーーーとか言ってたら30分くらいになってた。

とりあえずアプリを見てgit化。nopasteかーとか言いながら Slowlog 吐くようにして一旦計測回してみる。

11時

アクセスログも見つつ、 /recent/N で飛ぶクエリが重いのは察したのでインデックス貼って様子見。

同時にキャッシュ機構入れ始める。ちなみに最後まで MySQL の memcached plugin だってこと気づいてませんでしたq^

この辺りでスコア3600台。7位くらいには入ってた気がして浮かれてました。

MySQLを適当にチューニングぶっ壊し始める。

12時

軽くつまめるとの理由で寿司の出前を検討し始める。

アプリ側は Markdown を外部プロセスに回してるところを Text::Markdown でリプレイス

バックエンドは静的ファイルを nginx で返してるからそれ直すかーという感じに。

50分くらいに初めて本番計測をしたら、3100ぐらいに落ち込んで???ってなってた。

13時

寿司の出前を頼む。おもわず笑顔がこぼれるが裏で12時台の本番計測が落ちた理由を悩む

そのあと、適当に寿司食いながら作業

14時

12時台に決めた改修が完了

でも全くスコアがでない。testでも出なくなった。3100とか。

つーかこれって毎回DBリセットされるからインデックス元に戻るんじゃない?->戻ってた

でinitのスクリプトでインデックスを貼るようにした。

しかし、ここでもスコアが伸びす。3600台を出れない。

15時

2人で改修方針に悩む。もう Redis 化する余裕もなさそうなので、junqiの発想に任せて改修を行う。(他力本願)

とりあえず Starlet 化してみる。一切スコア変わらなくて手首を切りたくなる。

16時

プロセス数とか色々ゴニョゴニョしては計測。さほど効果のある改修はなし。

俺たちの junqi の改修が完了!さぁいこう本戦へ!いざ100万円!!->initの60sec制限でコケる

17時

沈黙。淡々とisuconのメンバーページをリロードして、ガンガンスコアに差が付いていくのを見ていた。

50分頃、気を利かせた junqi が蛍の光を流し始める。

俺たち頑張ったよな!な!みたいな謎なテンションになる。甲子園の地区予選で落ちる高校球児はこんな気持ちなんだろうか。

18時

敗因

色々敗因はあれど、経験と知識不足へ全て収束すると思う。

あえて挙げるとすれば

  • 必要なログをカジュアルに見れるようにしなかった。しかも後半ログ見なくなってた
  • アプリの性質をちゃんと理解して、どういうチューニングにするべきかをきちんと最初に話さなかった
  • (主にインフラで)行った改修と計測をきちんと対にして、行動と結果を明示的にすべきだった(なにやったか忘れたりしたので)
  • やっぱ3人は必要だと思った(スーパーハッカーなら2人でいいかもしらんけど)
  • 寿司をつまむ時間さえ惜しむべきだった

おわりに

すごい苦しかったし悔しいけど、nginxとかchefとかsupervisordとか色々学べたのは大きかった。また一人で色々できるようになった気がする。

でも本当に悔しいので来年こそ本戦に行きたい。病気の祖父のために100万円が必要なんです。

開催に尽力された KAYAC, LINE のみなさん本当にありがとうございました。いい勉強になりました。

大人のYAPC登壇の告知

yapcasia 大人のYAPC

YAPC::Asia Tokyo 2013が来場予定数1000人突破したなか、非モテの僕にもやっとチャンスが回ってきました。

大人のYAPC - YAPC for Grown-upsにて、一般発表枠にお誘いいただきましたので登壇させていただきます!!

大人のYAPC?

YAPC本編とは一線を画した非公式イベント、YAPCを一通り遊び終わったあとの大人たちが夜な夜な集まり濃密なアダルトテクニックを語り合う場です。アダルトテクニック。

と、適当なことを言うと怒られそうなので一応告知サイトもご覧ください。

要するに、エロい話が大好きな大人の子供が真面目にエンジニアリングでエロを考える会です。(適当)

とまぁ、注意点ですが公式でもあるとおり

  • 一部(性的な内容を含め)過激な内容が含まれる可能性があります。十八歳以下の入場は堅くお断りします
  • Twitter等を含むインターネット上での発表内容に関連する情報の開示を行う場合は必ず登壇者の同意のもと行うようにしてください。もし登壇者が発表内容を公にしないよう求めた場合はそれに従ってください

です。大人の男の子たちなら問題ないですね。

ナニを話すの

昨年、某社AdventCalendarでちょっとした事故を起こしましたが

前回はあくまでスマートフォン向けの軽量かつ実用的なプロダクトとしたところを、今回は今注目のセンサーデバイスを用いるように改修し、ご紹介させていただきます。

当社比2倍以上のぷるんぷるんをお約束します。


イベントへの参加募集は9月から公式サイトにて行われます。

5分のLT枠はまだまだ募集中のようなので、変態エンジニア紳士各位はぜひぜひ@yusukebeさんまでReplyを。

また、YAPC::Asia Tokyo 2013のチケット販売は11日(日)までです。お買い逃した方、少しでも興味のある方はお早めに

僕は社内でチケットが配られてるのを知らず、自費で参加となりました!本当にありがとうございました!!

イベントを企画していただいた、@lestrrat氏・@yusukebe氏、また参加へのキッカケとなった@mackee_wに感謝します。ありがとうございます。

Gitアプリの決定版!SourceTree for windowsがやってきた! ヤァ!ヤァ!ヤァ!

git SourceTree

街頭アンケート「非エンジニアのWindows環境でgitが運用出来ましたか

  • 40%: Github for Windowsを入れたが、日々何らかのトラブルが発生した
  • 30%: TortoiseGitを入れようとしたが、面倒くさすぎてやめた
  • 20%: 上記の情報を知っていたので、他のアプリを探したがどれもクソ完成度がイマイチだった
  • 9%: 非エンジニアはsvnもしくは共有フォルダでファイルを管理してもらい、自分でpushするようになった
  • 1%: 全員Macで開発するようになった

2013.3僕調べ

そんなすべての環境設定おじさんに朗報です!

Macで人気を博したgitアプリ、某法人利用率99%(僕調べ)の"Atlassian SourceTree"がついにWindowsへ登場!!

これで環境設定おじさんの手間も省けるし、誰にも殺意を向けずにすみますね!!やったね!!!

SourceTree for Windowsことはじめ

ダウンロードはこちらから

インストール

ダウンロードしたインストーラーを起動してインストールを進めて行きましょう。

過去に Git for Windows や msysGit をインストール指定なければ、途中で下記のような確認画面が表示されると思います。

f:id:dameleon:20130321184036j:plain

要するに、Git本体(Gitコマンドの実行ファイル)をどうするの?ということなのですが

多くの場合は特に気にせず一番上の選択肢"Download an embedded version of Git for SourceTree alone to use"を選択して大丈夫です。

(過去、または現在TortoiseGitなどでGitを使用しており、現在の環境を引き継ぎたい場合には"Browse to the location of Git on your system"のほうがいいかもしれません。[自動で選択されるかも])

Git本体のインストールを行う(or 既にインストール済みである)と、下記のような画面が表示されます。

f:id:dameleon:20130321184043j:plain

Full Name, Email addressを入力します。

その下のチェックボックスは、通常入れたままで問題ありませんが、気になる場合はプロジェクトの管理者やプログラマに確認するといいでしょう。

"I agree to the SourceTree license agreement"は、規約に同意出来ればチェックを入れ、"Next"をクリックして進みます。

f:id:dameleon:20130321184054j:plain

AtlassianのBitbucket, Stashを使用している場合は、アカウントのサインアップができます。使用してなければ"Finish"でインストール終了です。

f:id:dameleon:20130321184059j:plain

鍵を作る

インストール完了!よし使うぞ!と思いきや、Gitは優しくありません。優しいわけがありません。

まずは、Gitとサーバが通信を行う際の認証情報として必要な「鍵」を作っていきます。

  • Tools > Create or Import SSH Keysを選択
  • PuTTY Key Generatorが起動するので、"Generate"をクリック

f:id:dameleon:20130321184103j:plain

  • 画面上の案内に従って、"Key"の部分でマウスを動かすことにより鍵を生成していきます

f:id:dameleon:20130321184106j:plain

  • 鍵の生成が完了しました。

      "Key Comment"にPCや自分の名前など分かりやすいものを入力します
      "Key passphrase", "Confirm passphrase"に好きなパスワードを入力します
    

(一応ボカしとこう)

f:id:dameleon:20130321184109j:plain

  • "Save private key"をクリックして、「秘密鍵」と呼ばれるものを保存します

      これは誰に見せてはダメな鍵です。見られたら死にます。
      また、無くしてもダメです。死にます。
      特に決まりはありませんが、"id_rsa.ppk"や"名前_PC名.ppk"というファイル名にしておくといいでしょう。
      別に決まりはないので好きでいいです。
    
  • "Public key for pasting into OpenSSH authorized_keys file:"の下のテキストボックスに表示されている文字列の「公開鍵」を保存します

      一旦、メモ帳などに貼り付けて保存すると良いです。
      こちらも特に決まりはないですが、"id_rsa.pub"や"名前_PC名.pub"などがオススメです。好きにしてください。
    
  • 続いて鍵を登録します。PuTTY Key Generatorを閉じて、SourceTreeに戻ったらTools > Launch SSH Agent(pageant)を選択します

  • 先ほど保存した.ppkファイルを選択すると、passphraseを入力するように言われるので入力します

  • これで鍵の登録は完了です


以上で、GitとSourceTreeの環境設定は完了です。

Gitを使ったプロジェクトに参加する際は、「公開鍵」をサーバへ登録する必要があるので、その際はプロジェクト管理者やプログラマに.pubのファイルを渡せば作業をしてくれるでしょう。

簡単な使い方は、また後日。