ばかおもちゃ本店:Youtube twitter:@sashimizakana Amazon.co.jpアソシエイト

2013年12月21日土曜日

AngularJSのscopeの継承とかを理解する

まえおき

本稿はAngularJS Startup AdventCalendar2013の21日目です。
20日目:AngularJSをTypeScriptで書くときのあれこれ - Qiita [キータ]

scopeについて

scopeはAngularJSでビュー側に値を渡すときに利用するサービス。
たぶん知ってると思う。これがビューとコントローラーがデータをやりとりする。
で、今回はちょっとAngularJSをさわり始めると公式ドキュメントなんかあちこちに出てくる、新しいscopeが作られるっていう部分について書く。
ディレクティブなんかのAPIを読んでると出てくる。このディレクティブは新しいscopeを作りますって、あれってなんなのか。

scopeの生成と継承

scopeは一番上位に位置するrootScopeの下に階層化されて作られていく。
scopeはJavaScriptのレキシカルスコープみたいにブロックを持ち、親子関係を持つ。
なのだけど、少しだけjavaScriptのスコープとは違う部分がある。ここがちょっとだけわかりにくい。

一般的にディレクティブを作るときにscopeを作ることが多いので、それを例に取って説明する。
ディレクティブの作成時にscopeの生成には、false(作らない:デフォルト)、true(作る)、オブジェクト(親を継承せず作る)ってのがある。
一応比較対象としてfalseの作らないやつからfiddleに例を作った。

AngularJS Scope-1 - JSFiddle

これは見たまんまでディレクティブ内でいきなり親スコープのプロパティを読んでるけど、問題なく表示されているし更新も出来る。念のために書いておくと、これはサンプルなのでそういうことをしているけど、普通にこんなことをするとモジュール間の結合度が上がりすぎて最終的に動かないプログラムを作ることになるのでやってはいけない。
ダメ絶対。

普通にやる場合はlinkの第三引数(attrs)からタグに設定された属性の中身を読みとってscope.$watchで受け取ったりする。
なんでそう書いてないのかというとコードを短くしたいってことと、この後のサンプルの意味が分かりづらくなるから。

というわけで、trueを設定した場合。

AngularJS Scope-2 - JSFiddle

trueとfalseが変わっただけだけど、子+を押すと親+は変更されず、以後はまったく独立してカウントされるようになる(なので先に親+から押してください)。これがAngularJSのscopeがJavaScriptのスコープと違う部分で、つまり親を継承して子スコープを作った場合は、親の値は参照可能だけど上書きすることはできないということ。
上書きしようとすると変数名などはそのままに、別の値として作成されてしまう。
これを知らないで居ると全く意味不明な挙動に見えるときがある。

たとえばng-includeなどでもこんな風にscopeは作成される。そのため、たとえば親のscopeの値をそのまま利用して、かつng-include内部でちょこっと数値を変えるようなものを書いてしまうと、親の方で値を操作しているうちは良いけど、ng-include以下で操作すると急に親から値が読めないなどということが起きたりする。

で、最後がscopeにobjectを指定した場合。

AngularJS Scope-3 - JSFiddle

これは親を継承しないので、親の値は全く参照することが出来ない。
つまり動きません。お使いのPCは正常です。

ただこのobjectに親から継承したいものを設定することができる。ここではすべては詳しく書かないけど、親のscopeの変数を変更可能な状態で受け取ることとか、子のDOM内にのみ入れることとか、親のcontextで実行させるとか(ng-click="hoge()"みたいなやつのこと)、ができる。

値を受け取る場合はプロパティ名が子scopeでプロパティ名にしたい名前にして、その値を"=自分のDOMの属性名"にする。つまり<div hoge="***">みたいな場合、scope:{myValue:"=hoge"}というようなこと。
ちなみに子scope内で属性名と同じ名前を使うときは属性名は省略可能。
{hoge:"="}ってな感じ。

いちおうサンプル。

AngularJS Scope-4 - JSFiddle

まとめ

具体的にどういうときに使うんだって言う例を作っておきたかったけど、時間がなかった。実際にバリバリ書いてると絶対に分かってないとダメな局面があったはずなんだけど、なかなかちゃんと再現するコードを書くのはむずかしい。
でもjQuery拡張とかをディレクティブに放り込んでAngularJS対応にしてたりとか、複数のディレクティブをタグに付けまくったりしたりすると、なんか値が上手く変わらんとか思うことがあった。でもこのあたりを理解してからは問題なくなった。

scope作るときに実際どれ使えば良いかっていうので言うと、普通に使うだけならオブジェクトを渡して必要なプロパティだけ共有するのがおすすめ。安全だしミスを起こさないので良いと思う。

余談

$watchで値を受け取るとき、受け取った値がオブジェクトとか配列とかだと参照渡しされているのでscope:trueに設定してあってもそのオブジェクトのプロパティへの変更とかは通常通り行われて親scope側の値に影響する。
まあでも関数の引数と同じ。

2013年12月15日日曜日

AngularJSでゲームをつくる「艦隊くりっかー」

まえおき

これはAngularJS Startup AdventCalendar の15日目の記事です。
14日目:AngularJSとAngularJSモジュールでi18nを実現する(2) - Qiita [キータ]

本稿ではAngularJSでゲームを作ったときに気づいたこととかを元に、どんな感じでアプリケーションを構成するかをざっくり書きます。実際のゲームを作る上でのTipsとかってよりは、基礎的なディレクティブとかサービスの使い方中心ですが、ゲームの実例も多少書くのできっとどう分けるかを理解できるはず。で、それが把握できればゲームをどう作るかもわかるはず。
一応ある程度AngularJSの基礎用語くらいは聞いたことあるくらいの人対象です。
それのどこがスタートアップなんだとか気にしない。泣かない。
ちなみにAngularJSはゲームを作るのに適したフレームワークではないです。
もっとも数値を操作するだけのゲームならAngularJSでも十分です。
そういうわけでAngularJSやろうぜ!

自己紹介

ばかおもちゃ本店という名前でニコニコ動画に電子工作とかの動画をあげています。
全自動スカートめくり機とか、念力スカートめくり機とか、そういうのを作りました。人様に言いづらい。

そして本稿で説明する艦隊くりっかーという二次創作ではない(重要)ゲームを作りました。

艦隊くりっかー?

艦くり - Kantai Clicker

CookieClickerのクローンです。
本当は更に艦隊これくしょんの二次創作のつもりでしたが、ゲームは禁止されているので、
キャラクター要素を削って良くわからない艦隊これくしょん便乗商品のようなものになった。
数値を増やすために数値を増やすという超単純なゲームです。

(本文と関係のない画像です)

AngularJSアプリの大まかな構成

AngularJSのアプリケーションは、ざっくり以下の様な構成になる。

directive
ディレクティブ。HTML内に埋め込んで表示を制御する。値の実際の入出力を処理する。
配列渡されたらフォーマットしてリスト出したり、入力値を値にセットする。

controller
コントローラー。ng-controllerで呼ぶとそのタグの階層以下のscopeを管理できる。
scopeってのはAngularJSでビューと値をやりとりするときのオブジェクトのこと。

service
サービス。値を保存したり読み込んだり、それに伴う通信したり。
もちろん値への操作も入れちゃって構わない。いわゆるモデル。

filtter
フィルター。リストの内容を変えたり値を整形したりするディレクティブの補助的なもの。
今回使ってないのでもうこれっきり出てきません。

つまりAngularJSはHTMLに利用するコントローラーとか含めてdirectiveでテンプレートを書いて、
controllerがそのdirectiveとserviceの間の値のやり取りをscopeってのを使ってやる。
なるほど、かんたん。

各部の詳細とか

以下、詳細というか使ってる時に思ったこととか、注意点みたいなのをまとめる。

controller
AngularJSの公式のサンプルなんかを見て、モジュールを分けることを意識しないで書くとファットコントローラーになって手に負えない状況になりがち。見た目は全部ディレクティブ、値の操作はサービスでする。
ここで値を直接変更してたり、見た目に関連するフラグの変更とかやってるならたぶん間違ってる。
コントローラー間での値の受け渡しとかで悩んでたりするなら考え方が根本的に違う可能性が高い。

directive
雑に言うと、この中でならjQueryとか素で使ってもOK。
なのでjQueryの拡張とかをそのまま使うときもこの中に閉じ込める。
directiveのcompileの第一引数とかlinkの第二引数とかはディレクティブがつけてある要素そのものなのだけど、これはもともとjQueryオブジェクトの形で渡される。
ちなみにAngularJSよりjQueryを先に読み込んでないとjqLiteという機能縮小版になる。
(たしかfindとかなかったりremoveが無かったりして不便なときが多い)

余談だけど、linkのサンプルコードで第一引数のscopeに$マークがないのは、ここは名前で引数が決定されるわけじゃなくて、普通に順番で引数が決定するからだとか何とか。
ECMA3で機械的に挿入されたコードのみで$の利用が許可されるとか言ってるのと関係有るかも(ないかも)。


書き始めると終われないので飛ばしていくけど、directiveはscope周りが一番面倒なので注意深く勉強するといいかも。
親の値をそのまま変更可能だったり(scopeが未設定かfalse)、親の値が上書きのみ別の値になるか指定したもののみ上書きも可(scopeがtrueかオブジェクトで指定)とかある。
ちなみに普通の引数と同じくオブジェクトの場合は参照で渡されるので、falseでもオブジェクトのプロパティの変更のみは出来たりするので混乱の元になる。
自分の作ったディレクティブにng-hideとかを組み合わせて使うつもりが無いなら、スコープは常に作ったほうが事故ることが少ないと思う。
逆に言うと、親スコープ内で使うつもりなら、スコープをそもそも使わないか、親スコープと絶対にかぶらないような名前で利用しない限り危険。うっかり名前かぶったりするとミスに気付きづらくて泣く。

service
複数画面のアプリケーションの場合もserviceはシングルトンなので、データは共有される。
これ重要。
angular.('モジュール名',[依存モジュール...]).factory('ディレクティブ名',[利用サービス...,function(){
    return サービスのオブジェクト;
}]);
みたいな感じで作るわけだけど、もちろん、
var MyService = function(){
    this.initialize.apply(this,arguments);
}
MyService.prototype = {
    initialize:function()...
}
return MyService;
みたいにクラス渡して、コントローラー側で、
['$scope','MyService',function($scope,MyService){
    var svc = new MyService();
}];
てな感じで使ってもいい。
私はこういう感じのクラスにデータ取得するメソッドとキャッシュとか持たせて使ったりしてる。
var MyService = function(){
    this.initialize.apply(this,arguments);
}

MyService.prototype = {
    initialize:function()...
}

MyService.cache = {};

MyService.getData = function(id){
    if(this.cache[id]){
        //キャッシュにあればそれを返す
        return this.cache[id];
    }

    //なければバックエンドとかからデータ持ってくる
    var svc = new MyService(バックエンドから持ってきたデータ);
    this.cache[id] = svc;
    return svc;
}
return MyService;
でMyService.getData(4);みたいな感じで呼び出す。
factoryが呼ばれるのは最初の一回だけなので、複数画面とかならどの画面を最初に開いても、データが既に読み込まれてたらキャッシュを呼ぶし、そうでないならバックエンドからデータを持ってくることになる。
一覧と詳細のリストを持ってて、詳細を開くと追加データを読んでキャッシュするとかそういう使い方に便利。有効期限とかキャッシュの強制削除とかそういうのもいるかも。

小休止

何回、controller、directive、serviceって繰り返すんだよクソっ! って気になってきたかと思いますので本文とは特に関係ない挿絵を御覧ください(あと一回繰り返します)。

艦隊くりっかーでの全体の構成

敵のパラメーターと説明、施設のパラメーターと設定なんかはjsonに書いてある。

controller
データの保存とかロードをやってるだけ。
上記のjsonのデータを呼び出すserviceとかクッキーの入出力serviceとかの実行。でその値をscopeに入れるだけ。

directive
ディレクティブとして作ったのは、体力とか経験値のバーと船の部分、施設の部分。
今見直すと、施設の部分はディレクティブとして分ける必要性が薄いし、船の部分も含めてビジネスロジックまで書いててまずい。
体力とか経験値のバーは与えられた数値で要素の長さを変えてバーっぽく見せてるだけ。
船の数値のやつは、$rootScopeのイベントを見張って攻撃されたりクリックしたら渡された数値を表示するだけ。
この辺後述するけど、$watchをどこでも使うよりイベントのがいい感じの時も多い。

service
ディレクティブと同じく分離がうまく言ってない箇所があって構成が説明しづらい。
本来だとユーザー関連と敵関連と施設関連を分離してそれぞれがクッキーへの入出力サービスに依存してるような感じにすべきだったと思う。で、クッキーへの入出力モジュールがロード時にデータをキャッシュしたり、一定時間おきにデータの変更を確認して保存すれば良かった。
ちなみにAngularJSのクッキーモジュールはブラウザ閉じるまで以外の有効期限を設定できないので、
クッキーへの書き出しとか読み込みとかは自前で作った。

だいたい終わった

というあたりで大まかな構造はだいたい語れたので満足した。
つうかそんな複雑な構造してない。
あとその他開発中に思ったことを列挙。

・クッキーすげえ壊れる

クッキーは壊れる。ポッケに入れて膝に矢を受けたら真っ二つ。
結構長いデータをJSONにしてbase64にしただけっていう横着なやり方だからか結構壊れた報告を受けた。
受けたから、テキストで保存できるようにしたのだけど、根本的に直す方法は良く分からない。
確認した限りでは全部真っ白になるわけではなくて、後半データが切れるみたいな壊れ方が大半だったので、容量に余裕があるなら一つ前の状態も保存して、壊れてたら前のデータ参照みたいなやり方ならまだ被害は少ないかもしれない。

・イベント利用($emit,$on)のすすめ

普通AngularJS的には、各ディレクティブ内で見た目とかが変わるときは依存性として渡してあるモデルを$watchしてその状況に応じて変更するみたいなやりかたが常道っぽい。
ただこれだと、っていうかゲームのようなDOMの見た目変更が複数条件で決定されかつ複雑なときには、ディレクティブに片っ端からサービス渡したりすることになって現実的じゃない。
しかも今回は小規模なので問題になってないけど、私がやっちゃったみたいにディレクティブ内でサービスのデータ変えたりするともう目も当てられない。
どこで何が変わっているのか、どこに何が依存しているのかの把握がむずかしくなる。
でっかいオブジェクトなんかをまるごと$watchするのがあちこちにあるとこれもまた怖い。
そのへん、イベントは投げたところと受け取るところがはっきりするので、ある程度統制を取りやすい気がする。

自データサービスは敵に攻撃されたことをイベントで受け取って、引数の攻撃力に応じて自分の艦数を減らして、
もし0になったら敗北イベントを発生させる。
敵サービス側は攻撃状態にある間は一定時間おきに攻撃イベントを発生させて、プレイヤー側の攻撃、またはクリックのイベントを受けて自分の艦数を減らして、もし0になったら敗北イベントを発生させて攻撃状態を解除、あるいはプレイヤーの敗北イベントが発生しても攻撃状態を解除する。

というような感じ。

・ぐるぐる回る

そもそも上記のように$watchを多用してオブジェクトとかを監視しまくる構成になると、把握が難しいならまだしも最悪の場合はループするし、そうでなくても無駄な呼び出しが増える傾向にある。
これは大変に厄い。
最初のうち何も考えずに$watchを活用しまくって適当に書いていると、なんか知らないけどこのプロパティ変えただけで、関係ない処理が山ほど流れるなんてことになったことがあった。
filterなんかとくにそうなりやすい。console.logで出力してみると何故かちょこっと動かすたびにフィルタ動いてるみたいになる。
オブジェクトのプロパティも監視対象にできるのであんまり大きなオブジェクト全体を見張らないで、必要な部分だけにすべきなのだと思う。
(わざわざ第三引数にtrue渡さないとプロパティを含めたオブジェクト全体は見張らないし、もともとそういう想定なのだと思う)

まとめ

個人的な印象で言うと、アクションゲームみたいな動きの多いものでなければ、AngularJSでゲームを作るのは結構簡単で楽しいのではないかと思いました。$watch周りとか$scope周りはややこしいこともあって、混乱することもありますが、その辺をある程度整理したり、こういうことはすべきではないということが分かれば、本当に楽に書けて良いと思います。
時間と場が残っていればこのAdventCalendar内で、scopeあたりについても書ければいいなと思います。

宣伝

艦くりは今年年末から来年初くらいまでの間にアップデートを予定しています。
ちなみに私が作ったゲームやおもちゃその他や、それらの制作動画のお知らせはこのブログ(RSS)か@sashimizakanaに掲載しています。
君もばかおもちゃの最新情報をチェックだ!

2013年12月11日水曜日

安いTEGRA NOTE7で液タブみたいにお絵描きしてみる

TEGRA NOTE7を買った。
TEGRA NOTE7は最近はやりの7インチタブレットの一種で、最大の特徴としてはTEGRA4というグラフィック描画にバッキバキに特化したコアを搭載していることで、値段は¥25,800とNEXUS7よりちょい安いくらい。もっともメモリや液晶の解像度なんかでは一段下のスペックになっている。ではなぜTEGRA NOTE7を買ったかというと、それでもGPUの性能のおかげで各種ベンチマークなどでは完全にTEGRA NOTE7が買っていることと、その高い画像処理能力を使って、標準で筆圧対応のスタイラスがついているからだ。
といっても筆圧感知の方式自体は一般的なものと変わらず接地面積の多寡で認識しているようだが、感度が非常に高いためペン先をスリムに作れ、かつ形状の変化をさせなくても利用できるというのが一番のセールスポイントなのではないかと思う。
(現状の一般的なスタイラスは先端に丸いゴム球がついていてこれが潰れることで接地面積を変えるというものが多く、普通に絵を描く向きにはそのゴム感が使いにくいとされることも多い)

というわけで、本稿ではそのスタイラスでお絵かきをすることに絞ってレビューをする。

外観

サイズはNEXUS7と似たようなもの。フロントの上下(左右?)にスピーカーがあって音が良い。

裏面。おしゃれというよりはいかにもビデオカードメーカーっぽいガッチリ感。

スタイラスは本体に収納されてて、引き出すとランチャーが起動してお絵かきソフトを選べる。

ペンは細めなので筆圧をかけるタイプの人は疲れるかも。個人的には見た目よりは描きやすいと思う。

アプリ

スタイラスを使うためにTegraDrawというアプリとWriteというアプリが付属する。
Writeはメモ書き用のソフトなのでここでは省略する。
で、TegraDrawなのだけど、たしかにペンの書き心地は良い。入りも抜きも私の好きな筆圧だけで大きく線の太さが変わるのでなかなか気持ちよく描ける。ただ、このソフトは少なくとも現段階ではお絵かきに使うにはあまりにも足りない。

TegraDrawで適当に落書き中。

なにも私はレイヤーが無いとかそういう高度な機能がないと不平を言っているわけではない。このソフトで使える色は上の写真に表示されているものですべてなのだ。それならそれで色をいい感じに選んでくれればまだどうにかなりそうなものなのに、選ばれている色も一昔前のofficeみたいな原色とその彩度を落としたようなパレットで、およそ絵を描きたくなるような色ではない。
ペンの感触としてはかなり良いだけに大変残念だが、今の段階ではあまり使い物にはならない。

で、じゃあこれは使いものにならないのか、というとそんなことはない。
前述の通り、筆圧感知方式は一般的なものと変わらないため、いくつかのアプリで試してみたところ、まともに筆圧を感知してくれた。接地面積が普通のスタイラスより小さいことが問題にならないかと心配だったが、特にわからなかった。使い比べてみると違うのか、あるいはそういう部分はハードウェアの内部でソフトウェアに渡される前に処理されているのか、詳しくは分からない。

使ってみたアプリと、描いてみた絵

TegraDrawは色も選べないんですよ! とか言った直後に出すのは心苦しいんですが、このソフトは色は全く使えません。グレースケールだけです。それも普通に使う分には三色のみ。拡大・縮小等々お絵かきソフトとして必要な機能はほぼない。
でも、このソフトはペンの描き心地が最高で大好き。




無料版もあるけど、気に入ったので有料版を買った。

SketchBook Pro

たぶんAndroidのお絵かきソフトとしては一番完成度が高いであろうやつ。
レイヤーとか拡大縮小とか、必要そうな機能は全部入り。
ペンもものすごく精緻で追従性が高く書かれる感じで、そのあたりは私の下手さが余計出るのであまり好きではない。機能が多すぎるのもちょっと疲れるなと思う。ただばかおもちゃ本店の専属イラストレーターこと私の妻はもともとグラフィックソフトに親しんでいる人なので、簡単に使いこなしていて気に入ったようだ。
以下妻が走り書きしたもの。



色を塗るのには良いのでZenBrushで描いたものに乗算で色を乗せるとかでもいいかも。

当然私の絵である。

というわけで、液タブを買う金はないし、iPadですらまだ高いし、ついでに早くて安いAndroidタブレットが欲しいという人にはかなりおすすめできるのではないかと思う。お金がある人はCintiqとかiPad+Bluetooth接続系のスタイラスとか買えばいいかと思います(使ったことないけど)。



ZOTAC NVIDIA TEGRA NOTE 7 [Androidタブレット] ZT-TN701-10J
ZOTAC
売り上げランキング: 4,490
実際NEXUS7より安いし、デザインとかGoogle製だとかにこだわらないなら絵とか関係なくても大変おすすめ。

2013年12月9日月曜日

本当にカップラーメンが更に旨くなる魔法の粉なのか試した


すこし前にネットニュースで話題になっていた味源のカップラーメンが更に旨くなる魔法の粉を買った。
この商品は名前の通り、俺達の大好きなカップ麺にふりかけると更に旨くなるとかというもので、魔法がかかってるらしい。魔法、粉、ブラック(ペッパー)と聞くといろいろな感覚が覚醒してしまうようなものの利用が疑われるが使われてません。原材料を見る感じ、魔法の正体はさば節とかかつお節とか一般的な調味料っぽい。

というわけで安心したので食べよう。



ドサァ。
分量は小さじ1~3。

はい、かけすぎた。
コショウ効きすぎになって何がなんだかわからない。

このあと、色々な袋麺とかカップ麺とかうどんとかに試して見たけど、うどんを除いていい感じに魚介系の味に変わるのでなかなか良かった(うどんはなんかどこかに味が消えて良くわからない感じになった)。
もっとも味そのものが強いので、色々なラーメンで変化が楽しめるというよりは、入れ過ぎると何に入れても同じ味に落ち着く傾向が強い。そのため最近はある程度ラーメンを食べたあとで、最後のほうで小さじ半分くらいを入れて元の味とのミックスを楽しむ感じになった。

ただその同じ味ってのがなかなか悪くないので、まずいカップ麺にあたってがっかりみたいなときには、これをふりかけてやればある程度美味しくなるので、そういう使い方もあるかなと思う。




レッドペッパー味もためしたい。