tag:blogger.com,1999:blog-64347452373553175912024-03-14T07:16:15.678+09:00マツオソフトウェアブログソフトウェア開発に関連することを発信していますYoo Matsuohttp://www.blogger.com/profile/09732121529027523391noreply@blogger.comBlogger144125tag:blogger.com,1999:blog-6434745237355317591.post-48650496959315951432023-03-18T09:47:00.002+09:002023-03-18T09:48:30.195+09:00お名前.comのVPSでFTPを設定する話お名前.comのVPSにFTPを設定するのに非常に苦労したので解説する。<div><br /></div><div><b>経緯 </b></div><div><ul style="text-align: left;"><li>VPSのOSをUbuntu 20.04でクリーンインストール </li><li>vsftpsをインストール </li><li>ufwで20, 21ポートを開放 </li><li>クライアントからVPSへアクセスするとエラーになる </li></ul></div><div><br /></div><div> ufwのステータスは以下。 <div># sudo ufw status
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNCxMds-l9k-2Xz6xzneEZ9GO21R90lAaOTpHhPUauUsy_8g8mNJ7d-l32cHrwEalfXbmL6HmH6-136Kuiqvfa1sY18gEPe4IZk3UAjWgUt8VsNPIYsxchLkfmjn5OHn5tC63R9fLBUNbo5nSh-AmtL1Ro5TRUgRO9Oiu0IqDiIWim8aZiQL_O5cx8/s496/ufw.png" style="display: block; padding: 1em 0px; text-align: center;"><img border="0" data-original-height="374" data-original-width="496" height="301" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNCxMds-l9k-2Xz6xzneEZ9GO21R90lAaOTpHhPUauUsy_8g8mNJ7d-l32cHrwEalfXbmL6HmH6-136Kuiqvfa1sY18gEPe4IZk3UAjWgUt8VsNPIYsxchLkfmjn5OHn5tC63R9fLBUNbo5nSh-AmtL1Ro5TRUgRO9Oiu0IqDiIWim8aZiQL_O5cx8/w400-h301/ufw.png" width="400" /></a><div class="separator" style="clear: both;">ポートが正しく開放されているのに接続できない。お名前.comのVPSのQ&Aにもどこにも情報が載っておらず 途方に暮れていたが、お名前.comの設定周りをしらみつぶしに見ていたらIPセキュリティの項目を発見。</div></div></div></div><div class="separator" style="clear: both;"><br /></div><span><a name='more'></a></span><div class="separator" style="clear: both;">以下設定方法。</div><div class="separator" style="clear: both;"><ul style="text-align: left;"><li>下図のように、左側のIPセキュリティを選択</li><li>上部のセキュリティーグループ一覧の「設定を追加」を押下</li></ul></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgvorSMA6lBjugGXR-4sHQliVjFhbUmybhn9yQy07epUvXrp_N65Op8m2IhuwrOIvgpCDvdE1kCWEQAjJETGm27W2WC6AHhJZLERGl2RU8kbLwC72evTOjSPBq7NXdXqNSP-LMAQQ9kGULxUIKce6mLvBHWO2T6cBEf6D0mATx9UFxy4kHVz6mDgceV/s1186/onamae%20group.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1186" data-original-width="1183" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgvorSMA6lBjugGXR-4sHQliVjFhbUmybhn9yQy07epUvXrp_N65Op8m2IhuwrOIvgpCDvdE1kCWEQAjJETGm27W2WC6AHhJZLERGl2RU8kbLwC72evTOjSPBq7NXdXqNSP-LMAQQ9kGULxUIKce6mLvBHWO2T6cBEf6D0mATx9UFxy4kHVz6mDgceV/w399-h400/onamae%20group.png" width="399" /></a><div class="separator" style="clear: both; text-align: center;"><br /></div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;"><span><!--more--></span>下図のようにFTP passive modeをインバウンドルールに追加。グループ名は任意。ここではFTP_Passive</div><div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgvorSMA6lBjugGXR-4sHQliVjFhbUmybhn9yQy07epUvXrp_N65Op8m2IhuwrOIvgpCDvdE1kCWEQAjJETGm27W2WC6AHhJZLERGl2RU8kbLwC72evTOjSPBq7NXdXqNSP-LMAQQ9kGULxUIKce6mLvBHWO2T6cBEf6D0mATx9UFxy4kHVz6mDgceV/s1186/onamae%20group.png" style="margin-left: 1em; margin-right: 1em;"></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj30hY7TU1yBCjIHxTXtlPboSYxq2gpxkeDpSpBvg81A6vCJ--MLC13NCy6lKw1rNnp3WfYj_lMD7PqqHjhCYtaYaU6VKQNgbdPPsSSbypD-lJQmyIbZlqRnODR1GKsKw34Aq6anr28Xi5IbkzZXx36V3mgAyOPLtcHES1gaZ80WH8gKawzbuK249vS/s637/onamae%20security.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="529" data-original-width="637" height="332" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj30hY7TU1yBCjIHxTXtlPboSYxq2gpxkeDpSpBvg81A6vCJ--MLC13NCy6lKw1rNnp3WfYj_lMD7PqqHjhCYtaYaU6VKQNgbdPPsSSbypD-lJQmyIbZlqRnODR1GKsKw34Aq6anr28Xi5IbkzZXx36V3mgAyOPLtcHES1gaZ80WH8gKawzbuK249vS/w400-h332/onamae%20security.png" width="400" /></a></div></div><div><br /></div><div><br /></div><span><!--more--></span>下図のように左側のサーバー一覧→対象のサーバーの詳細を押下<br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjfpzLdI80o756pLb473pfeCjtRQKFHnRCFsSpJAqPs2NyiTTrcebV2OWVpDb_KDGtJDX42y7gb4EseDkYlfUOZLYlRXSyOhJrWApqVigobx2BacqyTq9RHWzeLGeufipzK2yUdTWZKS7wiSzTVoexljlwRrn5w_ejYnvhIVs298fNbZ1cH54e0Qqwo/s1185/server%20list.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="444" data-original-width="1185" height="150" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjfpzLdI80o756pLb473pfeCjtRQKFHnRCFsSpJAqPs2NyiTTrcebV2OWVpDb_KDGtJDX42y7gb4EseDkYlfUOZLYlRXSyOhJrWApqVigobx2BacqyTq9RHWzeLGeufipzK2yUdTWZKS7wiSzTVoexljlwRrn5w_ejYnvhIVs298fNbZ1cH54e0Qqwo/w400-h150/server%20list.png" width="400" /></a></div><div><br /></div><div><br /></div><span><!--more--></span>下図のIPセキュリティの個所にFTPと先ほど作ったFTP_Passiveを追加<br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjYg4PWu1uQzhYcAlMVGoep44SzURE_aRVJcfFitTr5c1JfaCW6r9TqpsTr33PVF7F3F3aixn-bx_Obkkt3ZLuJd4IR3ZgWxNwUwNAgS_2xsYaClicSzrZrCQHJw2Vphvyihnim-TVXCS2As9kbvbhZTQohU-0BsuAz51kZlXRv89RoySoDra0gwYDN/s1167/server%20setting.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="278" data-original-width="1167" height="95" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjYg4PWu1uQzhYcAlMVGoep44SzURE_aRVJcfFitTr5c1JfaCW6r9TqpsTr33PVF7F3F3aixn-bx_Obkkt3ZLuJd4IR3ZgWxNwUwNAgS_2xsYaClicSzrZrCQHJw2Vphvyihnim-TVXCS2As9kbvbhZTQohU-0BsuAz51kZlXRv89RoySoDra0gwYDN/w400-h95/server%20setting.png" width="400" /></a></div><div class="separator" style="clear: both; text-align: center;"><br /></div><div class="separator" style="clear: both; text-align: center;"><br /></div><div class="separator" style="clear: both; text-align: center;"><br /></div><b><span><!--more--></span>結論</b><div>お名前.com側でVPSの外側にさらにファイアウォールを設定しているようでそちらに設定をしないといけなかった。そんな説明はどこにもないので設定を探すのに非常に苦労したという話。</div>Yoo Matsuohttp://www.blogger.com/profile/09732121529027523391noreply@blogger.com0tag:blogger.com,1999:blog-6434745237355317591.post-24522599088093258472022-03-19T08:08:00.005+09:002022-03-19T08:08:52.572+09:00AngularでUndo Redo 機能を実装する<p>AngularでUndo Redo機能を実装する、と題して複数のポストをこちらに投稿していたのだけれど、コード表示が上手に行えなかったので<a href='Zenn.dev'>Zenn.dev</a>に以下投稿した。</p>
<ul>
<li><a href='https://zenn.dev/yookunka/articles/5fd85705c144e8'>Undo Redo機能をAngularで実装する 1</a></li>
<li><a href='https://zenn.dev/yookunka/articles/79b1889c5ef2e1'>Undo Redo機能をAngularで実装する 2</a></li>
<li><a href='https://zenn.dev/yookunka/articles/46389e7052c46b'>Undo Redo機能をAngularで実装する 3</a></li>
</ul>
Yoo Matsuohttp://www.blogger.com/profile/09732121529027523391noreply@blogger.com0tag:blogger.com,1999:blog-6434745237355317591.post-43815757776700572972018-03-18T21:27:00.000+09:002018-08-28T16:11:31.096+09:00グルグル回転している長方形(Rectangle)と線(Ray)の当たり判定3Dで回転しているキューブとRayの当たり判定の背後にある数式がいまいちよく分からなかったのでJavaScriptのCanvasを使って図解してみた。2Dも3Dもテストする軸(Axis)の数が変わるだけで内容は一緒。コードは以下より取得してもらいたい。<br />
<a href="https://jsfiddle.net/hmfs7ksh/9/" target="_blank">JSFiddle</a><br />
<a href="https://github.com/yooontheearth/math-visual-examples/tree/master/raycast" target="_blank">GitHub(上に同じ)</a><br />
<br />
数式の背後にある考えは以下。<br />
<a href="https://www.siggraph.org/education/materials/HyperGraph/raytrace/rtinter3.htm" target="_blank">Ray Box Intersection</a><br />
<br />
以下図解。<br />
右下のRay OriginがRayの起点になっている。directionでRayの方向を指定している。<br />
<pre class="js" name="code">const rayCast = new Ray(origin=new Vector(400, 400), direction=new Vector(-0.8, convertYUp(1)));
</pre><br />
左上のオレンジと黄色の線で囲われている領域がくるくる回転しているRectangleとなる。<br />
<pre class="js" name="code">const obb = new RotatingRectangle(pos=new Vector(150, 200), size=new Vector(80, 50));
</pre><br />
右下のRay Originから左上のRectangleに伸びている線(Vector)はRayとRectangleの距離を算出するためのものなので、投げかけているRayとは別物なので注意してほしい。<br />
<pre class="js" name="code">const vectorFromOriginOfRayToCenterOfRect = obb.pos.subtract(rayCast.origin);
</pre><br />
当たっているとき(左下に「YES!」と出る)<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgRNAnJm3i0Qhl5cHXFAs7UTKsBQlA1lZldshKz1eO8djCc_WjndZza9R5XZ7Wmi5A2J5vRia1cBQf28YeMybYycbGjKuSdd1xrSgrpcwQw_CbS9rmvpYnuvxI5WZlhbb7ue_j7ur6zZDI/s1600/intersected.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgRNAnJm3i0Qhl5cHXFAs7UTKsBQlA1lZldshKz1eO8djCc_WjndZza9R5XZ7Wmi5A2J5vRia1cBQf28YeMybYycbGjKuSdd1xrSgrpcwQw_CbS9rmvpYnuvxI5WZlhbb7ue_j7ur6zZDI/s1600/intersected.png" /></a><br />
<br />
当たっていないとき(左下に「NO Intersection」と出る)<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizNTvk_xcndvD16nxzOWv_BQCVbPH0zZnr7cdv4zp96A17BfJZFvKAaOyKCw87997CGWwSpD2vE8DRpi0mmqGp1AlrmBiA73BLOjHy2bBj7YFxN44S4mGrId8ay-8mwBJX18RsfDJUa5Y/s1600/no+intersection.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizNTvk_xcndvD16nxzOWv_BQCVbPH0zZnr7cdv4zp96A17BfJZFvKAaOyKCw87997CGWwSpD2vE8DRpi0mmqGp1AlrmBiA73BLOjHy2bBj7YFxN44S4mGrId8ay-8mwBJX18RsfDJUa5Y/s1600/no+intersection.png" /></a><br />
<br />
この数式の肝はSlab(オレンジと黄色の線)とRayがいつ(Time)交わるかなので、Timeを算出しないといけない。Timeを求める式は以下。<br />
<b>Time = (Axis上に投影されたRay OriginとRectangle間の距離 +/- Rectangleのサイズ) / Axis上に投影されたRay Directionの長さ</b><br />
<pre class="js" name="code">const t1 = (lengthBetweenOriginOfRayAndCenterOfRectProjectedOnXAxis + obb.size.x) / lengthOfDirectionOfRayProjectedOnXAxis;
const t2 = (lengthBetweenOriginOfRayAndCenterOfRectProjectedOnXAxis - obb.size.x) / lengthOfDirectionOfRayProjectedOnXAxis;
const t3 = (lengthBetweenOriginOfRayAndCenterOfRectProjectedOnYAxis + obb.size.y) / lengthOfDirectionOfRayProjectedOnYAxis;
const t4 = (lengthBetweenOriginOfRayAndCenterOfRectProjectedOnYAxis - obb.size.y) / lengthOfDirectionOfRayProjectedOnYAxis;
</pre><br />
Timeを求める元々の式は以下。<br />
<b> +/- Rectangleのサイズ = Axis上に投影されたRay OriginとRectangle間の距離 + Time * Axis上に投影されたRay Directionの長さ</b> <br />
Slabの位置を求める式からTimeを求める式に変換すれば前述の式になる。<br />
<br />
Dot Product<br />
投影されたほにゃららの長さを求めるには<a href="https://ja.wikipedia.org/wiki/%E3%83%89%E3%83%83%E3%83%88%E7%A9%8D">Dot Product</a>を使用すればよい。<br />
<br />
<br />
<b>~まとめ~</b><br />
同一軸上に投影すれば後は単純な長さの比較になるのでTimeが求まると理解するまで悶々と苦しんだけど、今はスッキリ。こんな投げやりな解説で伝わるかははなはだ心もとないけれど、誰かの理解の一助になれば幸い。分からないことありましたらコメントください。Yoo Matsuohttp://www.blogger.com/profile/09732121529027523391noreply@blogger.com0tag:blogger.com,1999:blog-6434745237355317591.post-35564352134570296252016-11-09T16:04:00.005+09:002016-11-09T16:04:53.561+09:00async、await、CancellationTokenについて職場でasync、await、CancellationTokenの実装方法についてまとめたので同じ内容をまとめておく。<br />
※サンプルコードはWeb API 2を想定している。またDBアクセス部分はDapperを使用している。<br />
<br />
この記事は下記リンクの要約なのでasync, awaitについての詳細は下記のリンクを参照してもらいたい。日本語翻訳されていないので英語。<br />
<a href="https://msdn.microsoft.com/en-us/library/mt674882.aspx">Asynchronous Programming with async and await (C#)</a><br />
<br />
<b>使うべき場所</b><br />
下記のような状況ではWebサーバのパフォーマンス向上が見込めるので積極的に使ったほうが良い。<br />
<ul><li>DBでの処理(問い合わせ、実行など)</li>
<li>DB接続のオープン処理</li>
<li>ファイルIOなど</li>
</ul>async, awaitの非同期処理で知っておくべきことは<b>「awaitされている処理も呼び出し元のThreadと同一のThreadで処理される」</b>ということにある。なぜならasync, awaitキーワードは呼び出されたメソッドを呼び出し元のThread上で必要に応じて適時処理するだけであり、新規にThreadを作成するわけではないからだ。<br />
<a href="https://msdn.microsoft.com/en-us/library/mt674882.aspx#Anchor_4">Threads(詳細説明)</a><br />
<br />
つまりDBやファイルIOなどの別プロセスの結果待ちが発生する処理などでは非常に有用ではあるけれど、非同期処理になることを期待して単純に処理を二分しても実際には同一のThread上で二つの処理が適時行われるだけなので意味がないということ。ところでWebサーバのCPUリソースを多量に必要とするような処理の場合はasync, awaitとTask.Runを組み合わせると良い。Task.RunはThreadpoolへ処理を投げるのでパフォーマンス向上が見込める。<br />
<br />
<b>async, awaitのWeb APIサンプル</b><br />
<pre class="c#" name="code">// TestController.cs
public async Task<TestDto> GetTest(int id, CancellationToken cancellationToken){ // A
var dto = new TestDto();
var data = TestDataFacade.GetDataAsync(id, cancellationToken); // B
dto.Salary = CalculateSalary(); // C
dto.Data = await data; // D
return dto;
}
</pre><ul><li><b>A</b>、リクエストのキャンセルを考慮してCancellationTokenを最終パラメータに指定する<br />
時間のかかる処理などはリクエストがキャンセルされた場合に処理を中止できるのが望ましいのでCancellationTokenを実装すると良い</li>
<li><b>B</b>、非同期処理の開始<br />
AsyncのSuffixは非同期処理のネーミングコンベンションなので関数名につけるようしたほうが良い</li>
<li><b>C</b>、Bが非同期処理されている間にCalculateSalaryが実行される</li>
<li><b>D</b>、Bの非同期処理の結果待ち</li>
</ul><pre class="c#" name="code">// TestDataFacade.cs
public async Task<IEnumerable<TestItem>> GetDataAsync(int id, CancellationToken cancellationToken){
using (var con = _connectionProvider.GetEditableConnection()){
await con.OpenAsync(cancellationToken); // A
return await con.QueryAsync<TestItem>( // B
new CommandDefinition( // C
"select * from Tests where id=@id", new { id }, cancellationToken: cancellationToken));
}
}
</pre><ul><li><b>A</b>、DB接続のオープンを非同期で行う</li>
<li><b>B</b>、Dapperで非同期問い合わせを行う</li>
<li><b>C</b>、cancellationTokenを渡すためにCommandDefinitionが必要</li>
</ul><b>async, awaitの動作解説図</b><br />
下記リンク先にある解説図を見ると一連の動きが良く分かるので是非参照してもらいたい。<br />
<a href="https://msdn.microsoft.com/en-us/library/mt674882.aspx#Anchor_2">What Happens in an Async Method(詳細説明)</a><br />
<br />
<b>まとめ1</b><br />
ここまででasync, awaitキーワードの使い方が動作原理を含めて理解できたと思う。使い方を誤らなければ簡易な記述で容易にパフォーマンス向上が見込めるので使える状況では積極的に使っていくべきだとは思うけれど、ともすると処理フローは複雑化しやすく、関連する箇所全体で非同期処理を念頭に置いた実装にしなければならないので使いどころは慎重に見極めないといけない。<br />
<br />
<br />
<b>非同期リクエストのクライアントからのキャンセル方法</b><br />
こここらは非同期リクエストをクライアントからキャンセルする方法を解説する。<br />
<pre class="js" name="code">var currentXHR = $.ajax({
url: "/api/Test/GetTest/1",
type: "GET"
});
currentXHR.done(function (data) {
// 取得したデータで何かする
}).fail(function (jqXHR, textStatus, errorThrown) {
if (errorThrown === "abort") {
alert('処理を中断しました。');
} else {
alert('処理中にエラーが発生しました。エラー内容:' + errorThrown);
}
}).always(function () {
currentXHR = null;
});
// MEMO : 非同期処理中にページ遷移した場合はリクエストをキャンセルする
$(window).unload(function() {
if (currentXHR) {
currentXHR.abort();
}
});
// MEMO : 非同期処理中にキャンセルボタンを押下した場合はリクエストをキャンセルする
$('button.cancel').click(function() {
if (currentXHR) {
currentXHR.abort();
}
});
</pre>ページ遷移時に非同期リクエストが処理されている場合は明示的にリクエストをabortしないとキャンセルされない。また重たい処理などを実行する場合はキャンセル処理を実装しておくと不要な処理の実行を防ぐことによってパフォーマンス向上が見込めるので(エンドユーザがレスポンスを待たずにページ遷移してしまったとき用などに)積極的にキャンセル処理は実装したほうが良いだろう。<br />
<br />
<b>Web API 2のバグ対応</b><br />
Web API 2のバグで非同期処理中にAbortするとOperationCanceledExceptionが発生するのでそれを抑制する必要がある。下記コードをGlobal.asax.csに追加してほしい。<br />
<pre class="c#" name="code">class CancelledTaskBugWorkaroundMessageHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
// キャンセルされた場合はエラー内容を空っぽにして送り返す
if (cancellationToken.IsCancellationRequested)
{
return new HttpResponseMessage(HttpStatusCode.InternalServerError);
}
return response;
}
}
protected void Application_Start(object sender, EventArgs e) {
GlobalConfiguration.Configure(config => {
// ...省略...
// abort処理で発生する例外出力を抑制するための処理
config.MessageHandlers.Add(new CancelledTaskBugWorkaroundMessageHandler());
});
}
</pre><br />
<b>まとめ2</b><br />
Single Page Appなどで多量のAjaxリクエストを行っている最中にエンドユーザがページを離れてしまうとサーバサイドで行っている処理はすべて無意味になってしまうので、そのようなときのためにもキャンセル処理を組み込んでおくと良いだろう。Yoo Matsuohttp://www.blogger.com/profile/09732121529027523391noreply@blogger.com0tag:blogger.com,1999:blog-6434745237355317591.post-29743726649288752112016-09-15T22:11:00.002+09:002016-09-15T22:11:58.903+09:00ユニットテストでlog4netのログを出力する方法いまさらな内容だけど備忘録としてユニットテストでlog4netのログを出力する方法を記しておく。<br />
<br />
static constructorで下記設定を行っておけばユニットテストの結果Windowの下にログが出力される。<br />
<br />
<pre name='code' class='c#'>[Subject("Test")]
public class TestSpec
{
static TestSpec()
{
// MEMO : ユニットテストでlog4netを使用するための設定
var consoleAppender = new log4net.Appender.ConsoleAppender { Layout = new SimpleLayout() };
BasicConfigurator.Configure(consoleAppender);
}
// 以下省略
}
</pre>Yoo Matsuohttp://www.blogger.com/profile/09732121529027523391noreply@blogger.com0tag:blogger.com,1999:blog-6434745237355317591.post-3156862451096496592015-09-02T22:10:00.000+09:002015-09-02T22:10:00.770+09:00d3.jsでチャートを作る パイチャート前回に引き続き今回もd3.jsでのチャートをまとめておく。今回はパイチャートについて。<br />
<br />
前々回はこちら<br />
<a href="http://my-clip-devdiary.blogspot.jp/2015/09/d3jsy.html">d3.jsでチャートを作る ラインチャートとバーチャートを2つのy軸上に描画する</a><br />
前回はこちら<br />
<a href="http://my-clip-devdiary.blogspot.jp/2015/09/d3js.html">d3.jsでチャートを作る ツールチップ</a><br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiA_jUb6cmNPN_W9Xh8dVmmhTAN3TjQp54hWp6k4uaNITiMo1F-O4zGA_0_cYgSD2rN64NkYRgY-XgUt5KEEzjUPCoI54Kuo8PczVdIpWoSj3znZMR9scfuGE5IVIp15Kw3JlZFfGYjnR0/s1600/023.jpg" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiA_jUb6cmNPN_W9Xh8dVmmhTAN3TjQp54hWp6k4uaNITiMo1F-O4zGA_0_cYgSD2rN64NkYRgY-XgUt5KEEzjUPCoI54Kuo8PczVdIpWoSj3znZMR9scfuGE5IVIp15Kw3JlZFfGYjnR0/s320/023.jpg" /></a><br />
<br />
<a href="https://jsfiddle.net/jjd4ya6e/1/">SFiddleはこちら</a><br />
<br />
弧上に描画するためのarc()とパイ上に配置するためのpie()を用意する。それと配色用のcolor。<br />
<pre class='javascript' name='code'> var arc = d3.svg.arc()
.innerRadius(50)
.outerRadius(radius);
var pie = d3.layout.pie()
.value(function (d) { return d.sales; })
.sort(null) // ソートはしない
.startAngle(-Math.PI / 3) // -60度から
.endAngle(Math.PI / 3); // 60度まで
var color = d3.scale.category10();
</pre><br />
データをpie()にかませて配置用のデータへと変換する。<br />
<pre class='javascript' name='code'>var container = svg.selectAll('g')
.data(pie(data))
.enter()
.append('g');
</pre><br />
弧を描画する。<br />
<pre class='javascript' name='code'>// 色をつけて弧を描画する
container.append('path')
.style("fill", function(d, i) { return color(i); })
.attr('d', arc);
</pre><br />
arc.centroid()を使用すると真ん中の座標が簡単に取得できる。<br />
<pre class='javascript' name='code'>// 真ん中に文字を描画する
container.append('text')
.attr('class', 'label value')
.attr('transform', function(d, i) {
return 'translate(' + arc.centroid(d) + ')';
})
.attr('text-anchor', 'middle')
.text(function (d, i) { return d.value; });
</pre><br />
弧の外側に文字を描画するには三角関数を使用して座標を算出する。またtext-anchorを角度によって変更している。<br />
<pre class='javascript' name='code'>// 外側に文字を描画する
container.append('text')
.attr('class', 'label name')
.attr('transform', function(d, i) {
// 弧の外側を取得。パイチャートでは90度(Math.PI/2)の位置が0度計算になっているので注意。それなのでxは-する。yはSVGだと向きが逆になるので+する
var labelR = radius + 20
, x = labelR * Math.cos((d.endAngle - d.startAngle) / 2 + d.startAngle - Math.PI/2)
, y = -labelR * Math.sin((d.endAngle - d.startAngle) / 2 + d.startAngle + Math.PI/2);
return 'translate(' + x + ','+ y + ')';
})
.attr("text-anchor", function(d) {
var angle = (d.endAngle + d.startAngle) / 2;
return 0 < angle && angle < Math.PI ? "start" : "end";
})
.style("fill", function(d, i) { return color(i); })
.text(function(d, i) { return data[i].busho; });
</pre>Yoo Matsuohttp://www.blogger.com/profile/09732121529027523391noreply@blogger.com0tag:blogger.com,1999:blog-6434745237355317591.post-40110707457864676322015-09-02T21:54:00.003+09:002015-09-02T22:11:06.534+09:00d3.jsでチャートを作る ツールチップ前回に引き続き今回もd3.jsでのチャートをまとめておく。今回はおもにツールチップ的なものについて。ツールチップ的な部分以外は前回のものとあまり大差ないため省略するので詳細は前回を参照してもらいたい。<br />
<br />
前回はこちら<br />
<a href="http://my-clip-devdiary.blogspot.jp/2015/09/d3jsy.html">d3.jsでチャートを作る ラインチャートとバーチャートを2つのy軸上に描画する</a><br />
次回はこちら<br />
<a href='http://my-clip-devdiary.blogspot.jp/2015/09/d3js_2.html'>d3.jsでチャートを作る パイチャート</a><br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiv-x9oA3k3YUGxQ-XCFPwk1fFTM5LHpq6u37pG2FAASOTCwZAcImGN6UrOORTnTJem8h-_KturPVEbBoRC792u1LpgEMq_h5z98fC13k-rX5v1ozcNXlDmIu-n7PigaPZq6RB7-qsn6mE/s1600/026.jpg" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiv-x9oA3k3YUGxQ-XCFPwk1fFTM5LHpq6u37pG2FAASOTCwZAcImGN6UrOORTnTJem8h-_KturPVEbBoRC792u1LpgEMq_h5z98fC13k-rX5v1ozcNXlDmIu-n7PigaPZq6RB7-qsn6mE/s320/026.jpg" /></a><br />
<br />
<a href="https://jsfiddle.net/q68mzm1q/">JSFiddleはこちら</a><br />
<br />
X軸。今回はtimeを使用している。またrange()の指定は少しずらすために30からとなっている。<br />
<pre class='javascript' name='code'>// x軸は日付。y軸にくっつけたくなかったので30からはじめている
var x = d3.time.scale().nice()
.domain(d3.extent(data, function (d) { return d.date; }))
.range([30, width]);
var yearMonthFormat = d3.time.format("%Y/%m");
var xAxis = d3.svg.axis().scale(x)
.orient('bottom')
.tickFormat(yearMonthFormat);
svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0, ' + height + ')')
.call(xAxis);
</pre><br />
ツールチップ。<br />
<pre class='javascript' name='code'>// ツールチップ
var focus = svg.append('g')
.attr('class', 'focus');
focus.append('rect')
.attr({ x: -10, y: -25, width: 105, height: 50 });
// circle
focus.append('circle')
.attr('class', 'profit')
.attr({ r: 5, cy: -15 });
focus.append('circle')
.attr('class', 'sales')
.attr({ r: 5 });
focus.append('circle')
.attr('class', 'expense')
.attr({ r: 5, cy: 15 });
// text
focus.append('text')
.attr('class', 'profit')
.style('text-anchor', 'end')
.attr({ x: 90, y: -15, dy: '.35em' });
focus.append('text')
.attr('class', 'sales')
.style('text-anchor', 'end')
.attr({ x: 90, dy: '.35em' });
focus.append('text')
.attr('class', 'expense')
.style('text-anchor', 'end')
.attr({ x: 90, y: 15, dy: '.35em' });
</pre><br />
オーバーレイとツールチップの表示場所の指定。チャート上のどこにマウスがあっても表示したいのでオーバーレイはチャート全体を覆っている。mousemove()の冒頭でマウスの現在座標からその位置よりも左側に位置するデータをbisector()で取得している。それだけだとマウスが二つのデータ間にある場合、必ず左側のものが選ばれてしまうのでマウスとデータ間の距離を比較してツールチップを表示するべきデータを取得している。このx.invert()の使用方法はtime()でしか使用できないので注意。ordinal(), linear()の場合(linear()は違うのかも。調査していないので不明)はデータの幅とrange()、マウスの座標から当該のデータを探し出す必要がある(詳細は下記参考を参照してもらいたい)。<br />
<pre class='javascript' name='code'>// オーバーレイ
svg.append('rect')
.attr('class', 'overlay')
.attr({ width: width, height: height })
.on('mouseover', function () { focus.style('display', 'block'); })
.on('mouseout', function () { focus.style('display', 'none'); })
.on('mousemove', mousemove);
var bisectDate = d3.bisector(function (d) { return d.date; }).left
, formatValue = d3.format(",.2f")
, formatCurrency = function (d) { return formatValue(d) + '億円'; };
function mousemove() {
var x0 = x.invert(d3.mouse(this)[0])
, i = bisectDate(data, x0, 1);
if (i < data.length) {
var d0 = data[i - 1],
d1 = data[i],
d = x0 - d0.date > d1.date - x0 ? d1 : d0; // 一番近いデータを取得
focus.attr('transform', 'translate(' + (x(d.date)) + ',' + y(d.sales) + ')');
focus.select('text.profit').text(formatCurrency(d.profit));
focus.select('text.sales').text(formatCurrency(d.sales));
focus.select('text.expense').text(formatCurrency(d.sales - d.profit));
}
}
</pre><br />
参考<br />
<a href="http://stackoverflow.com/questions/20758373/inversion-with-ordinal-scale">Inversion with ordinal scale</a>Yoo Matsuohttp://www.blogger.com/profile/09732121529027523391noreply@blogger.com0tag:blogger.com,1999:blog-6434745237355317591.post-79037288009152721092015-09-02T21:24:00.003+09:002015-09-02T22:11:53.496+09:00d3.jsでチャートを作る ラインチャートとバーチャートを2つのy軸上に描画する<a href="http://d3js.org/">d3.js</a>周りを調査して、簡易なチャートをいくつか作ったので後々のためにまとめておく。今回は2つのy軸上にラインチャートとバーチャートをプロットする。<br />
<br />
他のチャートはこちら<br />
<a href="http://my-clip-devdiary.blogspot.jp/2015/09/d3js.html">d3.jsでチャートを作る ツールチップ</a><br />
<a href='http://my-clip-devdiary.blogspot.jp/2015/09/d3js_2.html'>d3.jsでチャートを作る パイチャート</a><br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhdb1X7plHHhJbI1N5Wxnd1ih6A9gSDh8UMf_cwtdBvnjwPiwpcn8nch2zDqjU0Xsd36tVp4fCBs-omZhPWqwscZ1RhSSlRXRF6oB20Dcrl7corjbe-TTVwerEJLch4OyRm1LSoTRSCOg8/s1600/025.jpg" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhdb1X7plHHhJbI1N5Wxnd1ih6A9gSDh8UMf_cwtdBvnjwPiwpcn8nch2zDqjU0Xsd36tVp4fCBs-omZhPWqwscZ1RhSSlRXRF6oB20Dcrl7corjbe-TTVwerEJLch4OyRm1LSoTRSCOg8/s320/025.jpg" /></a><br />
<br />
JSFiddleに動作するサンプルをあげてあるので詳細はそちらを参考にしてもらいたいが、簡単に解説しておく。なおd3.jsはdata, enterの仕組みが理解できていないと一見しても意味不明なコードになるのでチュートリアル的なものをどこかで一読されることをお勧めする。<br />
<br />
<b><a href="https://jsfiddle.net/vr3uz3ay/">JSFiddleはこちら</a></b><br />
<br />
データを用意する。<br />
<pre class='javascript' name='code'>var data = [
{ name:'Branch A', sales: 5400, forecast: 7000 },
{ name:'Branch B', sales: 2800, forecast: 4500 },
{ name:'Branch C', sales: 3600, forecast: 3300 },
{ name:'Branch D', sales: 1700, forecast: 4700 },
{ name:'Branch E', sales: 2200, forecast: 5500 }
];
</pre><br />
svg要素を用意する。<br />
<pre class='javascript' name='code'>var margin = { top: 50, right: 100, bottom: 40, left: 40 }
, width = 800 - margin.left - margin.right
, height = 300 - margin.top - margin.bottom
, svg = d3.select('#item-container').append('div').append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
</pre><br />
X軸。ここでscaleにはordinal()を使用している。他に線形のlinear()と時系列用のtime()が使用できる。<br />
<pre class='javascript' name='code'>// x軸は文字列なのでordinalにするのとrangeRoundPointsでちょうど良い場所を取得する
var x = d3.scale.ordinal()
.domain(data.map(function (d) { return d.name; }))
.rangeRoundPoints([0, width], 0.5);
var xAxis = d3.svg.axis().scale(x)
.orient('bottom');
svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0, ' + height + ')')
.call(xAxis);
// x軸のTickの文字を斜めにする
svg.selectAll(".x text")
.attr("transform", function (d) {
return "translate(" + this.getBBox().height * -2 + "," + this.getBBox().height + ")rotate(-45)";
});
</pre><br />
一つ目のY軸。domain()に0から売り上げと予想の最大値を指定している。またnice()でよさげにtickを調整してもらう。range()がheightから0になっているのはSVGの(0, 0)は左上をさすため。<br />
<pre class='javascript' name='code'>// 一つ目のy軸。売り上げと予想から最大値を取得する
var y = d3.scale.linear().nice()
.domain([0, d3.max(data, function (d) { return Math.max(d.sales, d.forecast); })])
.range([height, 0]);
var yAxis = d3.svg.axis().scale(y)
.orient('left')
.ticks(5);
var gy = svg.append('g')
.attr('class', 'y axis')
.call(yAxis);
// テキストを表示する
gy.append('text')
.attr('transform', 'rotate(-90)')
.attr('y', 16)
.style('text-anchor', 'end')
.text('万円');
</pre><br />
二つ目のY軸。右端にtransform.translateで移動する。また横線を引くためにd3が追加してくれたtickのg要素があるのでそこに横線用のline要素を追加して描画している。<br />
<pre class='javascript' name='code'>// 二つ目のy軸。売り上げと予想の割合を表示する
var y2 = d3.scale.linear().nice()
.domain([0, 150])
.range([height, 0]);
var yAxis2 = d3.svg.axis().scale(y2)
.orient('right');
var gy2 = svg.append('g')
.attr('transform', 'translate(' + width + ',0)')
.attr('class', 'y2 axis')
.call(yAxis2);
// 横線を描画する
gy2.selectAll('g').filter(function(d) { return d; })
.append('line')
.attr('x1', -width)
.attr('x2', 0)
.classed('minor', true);
// テキストを表示する
gy2.append('text')
.attr('transform', 'rotate(-90)')
.attr('y', -10)
.style('text-anchor', 'end')
.text('%');
</pre><br />
ラインチャート。二つ目のY軸で高さを算出している。<br />
<pre class='javascript' name='code'>// ラインチャート。売り上げと予想の割合を表示する
var linePlaceHolder = svg.append('g');
var percentLine = d3.svg.line()
.x(function (d) { return x(d.name); })
.y(function (d) { return y2((d.sales / d.forecast) * 100); });
linePlaceHolder.append("path")
.datum(data)
.attr("class", "line percent")
.attr("d", percentLine);
</pre><br />
バーチャート。一つ目のY軸で高さを算出している。またX軸のscaleでrangeRoundPoints()を指定しているおかげでちょうど良い感じの座標が取得できている。ここらへんは色々いじってためしてもらいたい。<br />
<pre class='javascript' name='code'>// バーチャート。売り上げと予想を表示する
// bars
var bar = svg.selectAll('.bar')
.data(data)
.enter()
.append('g')
.attr('class', 'bar')
.attr('transform', function(d) { return 'translate(' + (x.rangeBand() / 2 + x(d.name)) + ',0)'; });
// 売り上げ
bar.append('rect')
.attr('class', 'sales')
.attr('x', -10)
.attr('y', function (d) { return y(d.sales); })
.attr('height', function (d) { return height - y(d.sales); })
.attr('width', 10);
// 予想
bar.append('rect')
.attr('class', 'forecast')
.attr('y', function (d) { return y(d.forecast); })
.attr('height', function (d) { return height - y(d.forecast); })
.attr('width', 10);
</pre><br />
Yoo Matsuohttp://www.blogger.com/profile/09732121529027523391noreply@blogger.com0tag:blogger.com,1999:blog-6434745237355317591.post-67601282043197086732015-03-12T15:26:00.002+09:002015-03-12T15:26:47.250+09:00libgdxで2つ目のゲームを作ったので宣伝スマホ用のゲームを作りました。<br />
<br />
<b>・作ったゲーム</b><br />
<b>スーパーホッケー</b><br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEild8fRifGTYXL5Z7aYpqcOdTMw1H9Vw_G1sGKTbCKytQoTwPvi9GtrpacoMq-limphCssFDOwIQ2sjNgoV4-o0wq4DpLg7LY5ud0qhELd5Lvl_tgWwRR5aE230bl3QqeaDJ9Etai9HR04/s1600/feature.png" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEild8fRifGTYXL5Z7aYpqcOdTMw1H9Vw_G1sGKTbCKytQoTwPvi9GtrpacoMq-limphCssFDOwIQ2sjNgoV4-o0wq4DpLg7LY5ud0qhELd5Lvl_tgWwRR5aE230bl3QqeaDJ9Etai9HR04/s320/feature.png" /></a><br />
<a href="https://play.google.com/store/apps/details?id=com.matsuosoftware.game.sh.android"><img alt="Get it on Google Play" src="https://developer.android.com/images/brand/en_generic_rgb_wo_45.png" /></a><br />
<a href="https://itunes.apple.com/us/app/super-slime-hockey/id973437369?ls=1&mt=8"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjFO02Y9WKlVbwQikuB0qdhfSxWjHmG5T8Y2yaWOSFrSoYImuiQni7D8FSo05Lr2iDfQ0OzhxsgJPa01Oqw1kds572d8hX-vKytwpJWVcQ6fBsLhKngjvPXj5y66QTwMjckaeIOdjH8qwk/s320/iosbadge.png" /></a><br />
<br />
・感想(ゲーム開発全般)<br />
昨年中にこのゲームはリリースする予定だったのが、やる気の低下やらDragon Age Inquisitionやら、さらに延期したMy締め切りである一月中という目標も低下したやる気ではかなわず、結局Android版ができたのは二月末。毎月簡単なゲームをポンポンリリースしていこうかと思っていたけど、いやー、大変すわ、これは。おもにやる気の部分で。結局このゲーム用に凝ったものを作る気持ちが出てこなかったのでスーパーバレーボールから画像を流用しまくってお茶を濁すことに。なんだろ、多分これ作っても見返りほとんど無いしなぁ、って思いがゲーム作りたいって気持ちより強くなっちゃってやる気がでてこなかったんだろうな。次回はもっと新しいことにチャレンジしよう。じゃないと面白くないね。<br />
<br />
・感想(libgdx周り)<br />
libgdxでの開発は大分なれた。次回はfontやらsoundあたりをしっかりと組み込んでみようかな、と思案中。Yoo Matsuohttp://www.blogger.com/profile/09732121529027523391noreply@blogger.com0tag:blogger.com,1999:blog-6434745237355317591.post-26992170814955580972015-02-19T10:40:00.000+09:002015-02-19T10:40:31.489+09:00.NETプログラム実行時にDLLの参照エラーになったときようのツール実行時にDLLが見つからずにエラーになるのはままあることで原因を特定するのはさほど難しくないけれど面倒な作業である。とくにライブラリで参照しているDLLと呼び出し元のプロジェクトで参照しているDLLに差が出ることは、Nugetでパッケージを取得することが増えた昨今はよく起こる。<br />
<br />
ということが今の職場でよく起きてそれを調べる用のツールを作ったので公開する。<br />
<br />
Missing Reference Finder<br />
<a href='https://github.com/yooontheearth/missing-reference-finder'>https://github.com/yooontheearth/missing-reference-finder</a><br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUbU7JQUFQImVY2_mS5kx7Tz3LZgxkvFklaL6I3bqpL-ZWMP_GPWz__BQN4BF0mgfmSKcztENydrYlO8O5ZYTGbGyfaKPf2pu3A4GxlcYJ1UADZcxXdgS_Q7Ah1qItyEBDCau5USMAMV4/s1600/022.jpg" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUbU7JQUFQImVY2_mS5kx7Tz3LZgxkvFklaL6I3bqpL-ZWMP_GPWz__BQN4BF0mgfmSKcztENydrYlO8O5ZYTGbGyfaKPf2pu3A4GxlcYJ1UADZcxXdgS_Q7Ah1qItyEBDCau5USMAMV4/s320/022.jpg" /></a><br />
<br />
使い方はいたって簡単。Pathにbin等のフォルダを指定してcheckボタンを押下するだけ。指定のフォルダかGACにないものはMISSING扱いになる。Yoo Matsuohttp://www.blogger.com/profile/09732121529027523391noreply@blogger.com0tag:blogger.com,1999:blog-6434745237355317591.post-75392917274510320642014-09-28T18:58:00.000+09:002014-11-07T21:18:46.812+09:00libgdxでスマホ用のゲームを作っての感想今年に入ってからの仕事で調べた物を社内のWikiにまとめるという作業がとても多いのでこちらのブログに書くモチベーションがまったくあがらず放置しまくっていたけれど久しぶりに仕事とは離れたネタができたので更新。<br />
<br />
前々から何らかのゲームを作りたいなとAndroid用にAndEngineをいじくってみたりHtml5用にEaselJSで遊んでみたりしてみたけどどうにもしっくりこない。そんなこんなで月日は流れたけれど今年の夏に一念発起して二ヶ月に一本ぐらいのペースでゲームをリリースしていこうと決めた。<br />
<br />
で、八月の頭からゲーム開発を余暇を使ってしてきたんだけど先ほどつつがなく一本目のリリース(2014/11/06につつがなくApp Storeへのリリースも完了)が終わったので宣伝やら何やらも兼ねて楽しかったり大変だったりしたことをまとめたのが以下。今回は主に開発環境周りで。<br />
<br />
<b>・作ったゲーム</b><br />
宣伝です。<br />
<img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiyYeTKpIXGSgZDvXUuycRmAtl8rT1vI1dmblxm9-iOv_J2LCPtwbqwCIvn4LkIV8mkjfqe7pF6c5ImYHbTlWCJ253P1KzOO1FHA8osLO6Ymayyym_2q4n5iPBiDztxSFsozvo7YtIoHas/s320/feature.png" /><br />
<a href="https://play.google.com/store/apps/details?id=com.matsuosoftware.game.sv.android"><img alt="Get it on Google Play" src="https://developer.android.com/images/brand/en_generic_rgb_wo_45.png" /></a><br />
<a href="https://itunes.apple.com/us/app/super-volleyball/id935173036?ls=1&mt=8" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhpndrfUmzHmbJ9DR6aZ5EQbOL2krhX8df0dK69xqUVIog8Fr-jb-yfeI68unxMg1hFx45c6WcInA3oQ-KlVSSPvlSAxbE5Ao8vkH-xT-OczldU5IY5ZUfCjI6DnpQJc0_NsrY3k9yXnfs/s1600/iosbadge.png" /></a><br />
<br />
<br />
余談:「バレーボール ゲーム」で調べたらあんまり出てこなかったので油断して「Slime Volleyball」って名前にして作ってたらまんま同名の10万回ぐらいダウンロードされてるタイトルがあって内容も似てたのでとりあえず「Super Volleyball」にしてお茶を濁す。<br />
<br />
<br />
<b>・フレームワーク</b><br />
<b><a href="http://libgdx.badlogicgames.com/">libgdx</a></b><br />
AndEngineとかEaselJSをいじってみて思ったんだけど同じ製品をAndroid用、Html用、iOS用とか作り分けるのはやっぱりめんどい。他プラットフォーム用に製品を作るのはロジック部分とかほぼほぼ転用できるので開発は思いのほか楽なんだけどそれでもメンテナンスを考えるとめちゃめんどい。リソースが自分ひとりで開発からグラフィック、リリースとかまで全部やるのにこれはかなりの重労働になるのでうまいこと他プラットフォーム用のコードを生成してくれるフレームワークがないかなぁと思っていて見つけたのがこれ。<br />
<br />
<a href="http://unity3d.com/">Unity</a>も考えたけどlibgdxはフリーだし必要そうな機能も満たしてたのでこちらへ。javaで開発できるのもいいね。C#でもできるっぽいけどC++とか今更触りたくないよ。<br />
<br />
<b>・libgdxの感想</b><br />
さくさく開発できた。libgdxはビルドを<a href="http://www.gradle.org/">Gradle</a>に任せてるんだけどそのお陰か私のメインIDEであるIntelliJ Ideaでのセットアップもすぐに完了し即コーディングに入れたのはベリーグッド。グッジョブだ。Box2dとかも初期設定で参照するようにできるし簡単だった。ただねGradleのことが良く分かってなかった(今現在もポワポワしてる)ので何らかのサードパーティのライブラリを追加するのは結構骨が折れたしこれからも骨が折れそう。<br />
<br />
<b>・コーディング周りの感想</b><br />
scene2dという形でScreen、Actor、Actionとか画面の要素をうまいことまとめて使えるようにしてくれてるけどまだ自分の中でしっくりきてないしまだまだこなれてない。それなので模索しながら開発しつつ今現在もまだモヤモヤしたまま。けれど可能性は十分あると感じた。やっぱController的なレイヤーをしっかりと作ってView的な要素ともっと明確にわけたほうがスコアとか画面のステートとかがごちゃごちゃせずにすっきりするだろうなという感じ。<br />
<br />
<b>・フォント</b><br />
これは今もってどうしたほうが良いのか謎。今回みたいにほとんどテキスト表示するものが無いものはあんまりこだわる必要もなくデフォルトのを使えばよいかなぁと思った。ちょっと凝った部分は画像を文字として使うことになるし。多言語化とか考えたら文字を画像にするのとかご法度だろうけど。<br />
<br />
<b>・ツール類</b><br />
Texture Packerの存在を途中まで認識しておらず最初のころは自前で複数の画像を一枚の画像につめてその画像内の座標を指定して表示用に抜き取ったりという狂気の沙汰を続けていたのだけれどTexture Packerさんにご足労いただいてからはその自死一歩手前から引き返すことができた。とても便利。<br />
<br />
さらに<a href="http://www.aurelienribon.com/blog/projects/physics-body-editor/">Physics Body Editor</a>なるめちゃんこ優秀なツールもあるのでBox2dを使う方は目を通されるとよいかと。<br />
<br />
あとは2D Particle Editorとかもある。未使用なのでまだいまいち把握していないけれどエフェクトを派手にするためにも次のゲームでは使ってみようと考えている。<br />
<br />
<b>・マネタイズ</b><br />
libgdxを選んだ理由にAdMobとかとの統合のチュートリアルがちゃんとあるというのも大きなウェートを占める。少し苦労したけれど無事にAdMobを実装し広告が出せるようになった。いずれはiOS版も出そうとは考えているけれどそっちはまだあまり調べていないので少し謎だけど一応問題なくできそう。<br />
<br />
<b>・グラフィック</b><br />
Ink Scapeを使用。グラフィックの勉強をちゃんとしたこともないしPhotoShopで適当に画像をいじったりとかしかしたことなかったけれどInk Scapeは優秀なのでネットに転がってるチュートリアルを参考にいくつかサンプルを作ったりしてたら大分慣れてきてほぼほぼ満足いくキャラとか背景とかが作れた。まだ隠された機能がたくさんあるようなので精進あるのみ。非常に使いやすいのでお勧め。フリーだし。<br />
<br />
<b>・サウンド</b><br />
なし。スマホゲームでサウンドはいらんかなと思ったのでばっさりと。サウンドやらBGMの作り方とかまったくの門外漢なので。今後も予定無し。<br />
<br />
<b>・iOS</b><br />
Androidアプリは今までもいくつか作ったことがあったので勝手が分かってたけどiOSのほうは全然分からないので現在勉強中。アイコンとかでさえどっから手をつければよいのかがまったく分からん。iAdを組み込むならなおさら。<br />
下記に追記。<br />
<br />
<br />
<b>まとめ</b><br />
専門学校以来10数年ぶりのゲーム作成で面白かった。libgdxはさくさく作れるので良い。動作テストもDesktopのプロジェクトを含めておけばPCですぐに確認できるし。これがAndroidのEmulatorだけだったら遅すぎて発狂すると思う。<br />
<br />
<br />
<br />
<b>追記 2014/11/07</b><br />
App Reviewが昨日承認されて晴れてApp Storeにリリースできた。iOSアプリを作ったことがなかったのに加えてlibgdxでiOS版をリリースするチュートリアルがEclipseメインで書かれているものが多くIntellij ideaを使ってのリリースはかなり大変だった。またAppleのチュートリアルもXCodeを使って開発することを想定してのものなので、libgdx+intelij ideaというiOSアプリ開発環境的に邪道感満載の環境で手動設定するべき値がどれなのかを調べるのには骨が折れた。iAdのほうがマネタイズ的にAdMobよりも断然おいしいという比較記事を読んでいたのでiAdを組み込みたかったのだけれどそこにたどり着くまでにかなり試行錯誤を繰り返しすぎて発狂しそうだったのでそこはすっぱりとあきらめてrobovm-ios-bindings/admobでさくりと実装した。それとApp Reviewはなんやかんやとやっぱり時間を食う。それでもSubmitしてから一週間ということを考えれば早いほうなのかな、と。<br />
<br />
いずれAdMobの組み込み方とか、iOS版のリリース方法とかまとめたいと考えているけれどとりあえず年内にもう一本簡単なゲームをリリースしたいのでそれが終わってから気が向いたら書くかも。Yoo Matsuohttp://www.blogger.com/profile/09732121529027523391noreply@blogger.com0tag:blogger.com,1999:blog-6434745237355317591.post-60370638467636610152014-04-10T13:20:00.000+09:002014-04-10T13:20:35.382+09:00html5のcanvasにレーダーチャートを描画するレーダーチャートの話が仕事中に出て自前で作るかどこかのコンポーネントを購入するかという話になり結局購入することになったのだけれども、デモ用にちゃちゃっとレーダーチャートを実装したのがあるので公開しておく。<br />
<br />
<a href="http://jsfiddle.net/YcUFp/3/">デモはこちら</a><br />
<br />
実装周り<br />
<pre class='js' name='code'>// Knockoutjs周りは省略
var canvas = document.getElementById("canvas")
, c = canvas.getContext("2d");
function drawRader(){
var points = viewModel.points()
, eachRad = (Math.PI*2) / points.length
, i
, accumRad = Math.PI/2
, radius = 150
, center = 200
, sin
, cos
, endOfAxisPt
, firstPt
, prevPt
, currentPt;
c.clearRect(0,0,400,400);
for(i = 0; i < points.length; i ++){
c.strokeStyle = "#ffa500";
c.beginPath();
sin = Math.sin(accumRad);
cos = Math.cos(accumRad);
endOfAxisPt = {
x: center + (cos * radius),
y: center - (sin * radius)
};
accumRad += eachRad;
c.moveTo(center, center);
c.lineTo(endOfAxisPt.x, endOfAxisPt.y);
c.stroke();
c.fillStyle = "#00A0E9";
c.fillText(points[i].text(),
endOfAxisPt.x - 10,
endOfAxisPt.y - (sin * 20));
c.strokeStyle = "#00A0E9";
c.beginPath();
currentPt = {
x: center + (cos * (radius * points[i].ratio())),
y: center - (sin * (radius * points[i].ratio()))
};
if(prevPt){
c.moveTo(prevPt.x, prevPt.y);
c.lineTo(currentPt.x, currentPt.y);
}
else
firstPt = currentPt;
prevPt = currentPt;
c.stroke();
}
c.beginPath();
c.moveTo(prevPt.x, prevPt.y);
c.lineTo(firstPt.x, firstPt.y);
c.stroke();
}
drawRader();
</pre>
drawRader()の冒頭でviewModel.points()と取得しているのはKnockoutjs用のViewModelが内部的に保持しているObservableArrayだ(Knockoutjsを知らない人は配列のようなものと考えてもらって構わない)。描画の処理は単純で配列のアイテム数で360度を割ってその角度ごとに軸線を描画し、前後の軸線上の点と点を結んでいくだけ。Yoo Matsuohttp://www.blogger.com/profile/09732121529027523391noreply@blogger.com0tag:blogger.com,1999:blog-6434745237355317591.post-16540659860530783652014-01-04T17:31:00.001+09:002014-01-04T17:31:29.663+09:00html5のcanvasに描画した曲線をヒットテストする昨年はWPFを使ってGUIをグリグリ動かすアプリ開発にどっぷりはまっていたので今回は久しぶりにJavaScriptをいじりたくなり表題のものをガリガリ作った。<br />
<br />
<a href="http://jsfiddle.net/JcQPL/1/">デモはこちら</a><br />
<a href="https://github.com/yooontheearth/html5-canvas-bezier-hittest">githubはこちら</a>(デモと差は皆無)<br />
<br />
赤い四角が始点と終点だ。オレンジの四角が制御点になる。すべてdraggableなので任意に動かしてもらいたい。線上の点は曲線を直線に分割している点だ。曲線をクリックすると緑色になる。<br />
<br />
<b>曲線描画 on canvas</b><br />
html5のcanvas上で曲線を描画するのは至極簡単だ。context.bezierCurveToまたはcontext.quadraticCurveToを呼び出せば良い。前者は制御点が2つのcubic bezierで後者は名称そのままの制御点1つのquadratic bezierだ。今回のデモではcubic bezierのほうを使用している。<br />
<br />
<b>曲線ヒットテスト on canvas</b><br />
しかしあたり判定になると手段が提供されていないので自前でやる必要がある。以下任意のクリックした点が曲線上に位置しているかを計算する方法である。<br />
<br />
1、曲線上の任意の点の接線の傾きを計算し曲線上の近傍点の接線の傾きと比較する<br />
2、1の差が許容範囲内ならば2点は直線に近いと判断する<br />
3、1の差が許容範囲外ならば2点間は曲がっているので2点間の中央で分割し再度1の判定を再帰的に行う<br />
4、1~3までを繰り返し曲線を点で分割する<br />
5、4で得られた点群とクリックされた点が直線上に位置するかを計算する<br />
6、5で直線上に存在すれば曲線がクリックされたとする<br />
7、5で直線上に存在しないならば曲線はクリックされていないとする<br />
<br />
上記の計算に必要なものを解説していく。<br />
<br />
<b>曲線上の任意の点の計算方法</b><br />
Cubic bezier curveの式を使うと簡単に任意の点を取得できる。Cubic bezier curveの式は以下。<br />
<pre>b(t) = p0*(1-t)^3 + p1*3t(1-t)^2 + p2*3(1-t)t^2 + p3*t^3
</pre>p0は始点、p3が終点、p1、p2はそれぞれ制御点の1と2だ。これに時間であるtを0~1の間で指定すると曲線上の任意の点が取得できる。<br />
<br />
<b>曲線上の任意の点の接線の傾きの計算方法</b><br />
これもCubic bezier curveの式の導関数を使えば簡単に計算できる。式は以下。<br />
<pre>b'(t) = 3(1-t)^2(p1-p0) + 6(1-t)t(p2-p1) + 3t^2(p3-p2)
</pre>p0~p3とtの説明は前述したものと同じだ。<br />
<br />
上2つの式は<a href="http://en.wikipedia.org/wiki/B%C3%A9zier_curve">Wikipedia</a>を見てもらったほうが分かりやすいと思う。<br />
<br />
<b>任意の点が2点間の線上に存在するかの計算方法</b><br />
任意の点Pが線AB上に存在するならば距離AB=距離AP+距離PBが成り立つ。<br />
<br />
<b>コード解説</b><br />
ここから実際にいくつかコードを見ていこう。<br />
<br />
まずは曲線上の任意の点を取得するコード。pointsは始点、制御点1、制御点2、終点の配列だ。<br />
<pre name='code' class='js'>function b0(t) { return Math.pow(1 - t, 3); };
function b1(t) { return t * Math.pow(1 - t, 2) * 3; };
function b2(t) { return (1 - t) * Math.pow(t, 2) * 3; };
function b3(t) { return Math.pow(t, 3); };
function getPointOnBezier(points, t){
var x = points[0].x * b0(t) + points[1].x * b1(t) + points[2].x * b2(t) + points[3].x * b3(t)
, y = points[0].y * b0(t) + points[1].y * b1(t) + points[2].y * b2(t) + points[3].y * b3(t);
return new Point(x, y);
};
</pre><br />
ついで曲線上の任意の点の接線の傾きを取得するコード。ここもpointsは始点、制御点1、制御点2、終点の配列だ。<br />
<pre name='code' class='js'>function bd0(t) { return 3 * Math.pow(1-t, 2); };
function bd1(t) { return 6 * (1-t) * t; };
function bd2(t) { return 3 * Math.pow(t, 2); };
function getSlopeOfTangentLine(points, t){
var x = bd0(t) * (points[1].x-points[0].x) + bd1(t) * (points[2].x-points[1].x) + bd2(t) * (points[3].x-points[2].x)
, y = bd0(t) * (points[1].y-points[0].y) + bd1(t) * (points[2].y-points[1].y) + bd2(t) * (points[3].y-points[2].y);
return y/x;
};
</pre><br />
曲線を直線に分割していくコード。mDiffやdDiffのしきい値をどこに設定するかで分割する大きさが変わってくるので上手いこと調整してもらいたい。<br />
<pre name='code' class='js'>function calculateDividingPoints(){
var points = []
, bi = bezierInfo
, i;
for(i = 0.1; i <= 1.0; i += 0.1){
divideCurveRecursively(bi, points, i-0.1, i);
}
points.push(bi.points[bi.points.length-1]);
bi.dividingPoints = points;
};
function divideCurveRecursively(bi, points, t1, t2){
var prevD = getSlopeOfTangentLine(bi.points, t1)
, currentD = getSlopeOfTangentLine(bi.points, t2)
, dDiff = Math.abs(Math.abs(prevD)-Math.abs(currentD))
, mDiff = t2-t1
, middleT = t1 + mDiff / 2;
if(mDiff > 0.05 && (sign(prevD) !== sign(currentD) || dDiff > 0.2)){
divideCurveRecursively(bi, points, t1, middleT);
divideCurveRecursively(bi, points, middleT, t2);
}
else
points.push(getPointOnBezier(bi.points, t1));
};
</pre><br />
任意の点が2点間の線上に存在するかをチェックするコード。これも許容値を変更することで当たり判定を厳しくしたりゆるくしたりできるので上手いこと調整してもらいたい。<br />
<pre name='code' class='js'>function onStraightLine(pt){
var bi = bezierInfo
, i;
for(i = 1; i < bi.dividingPoints.length; i ++){
var p1 = bi.dividingPoints[i-1]
, p2 = bi.dividingPoints[i]
, trueDistance = calculateDistance(p1, p2)
, testDistance1 = calculateDistance(p1, pt)
, testDistance2 = calculateDistance(pt, p2);
if(Math.abs(trueDistance - (testDistance1 + testDistance2)) < 0.2)
return true;
}
return false;
};
</pre>
<br />
ざーっと解説してきたけれどCubic Bezierの公式さえ分かっていればさほど難しくないと思う。フレームワークから提供される曲線のあたり判定などは多量の曲線が存在する場合にボトルネックになったりもするので自前で計算できるよう公式を理解しておいても損はないだろう。処理実行の最適化などはまったく考慮していないので使用メモリだったり処理時間などを勘案して計算処理をキャッシュするなどは実際のコードベースでは行ったほうがよいと思われる。Yoo Matsuohttp://www.blogger.com/profile/09732121529027523391noreply@blogger.com0tag:blogger.com,1999:blog-6434745237355317591.post-11568394382226469612013-08-08T15:41:00.003+09:002018-08-25T22:54:33.132+09:00UbuntuにGitをセットアップしてWindowsから接続する自前のGitサーバが欲しくなり設定したのでその方法をまとめる。今回の環境は以下。<br />
<br />
・Gitサーバ<br />
Ubuntu 12.04.2 LTS<br />
<br />
・Windowsクライアント<br />
Windows 7 64bit<br />
<br />
<br />
<b>Ubuntu認証用のSSHキーを用意する</b><br />
WindowsでSSHキーを用意するにはPuTTYgenを使ったりする方法もあるけれど、git bashからもさくりと作れるので今回はそちらで。git bashをまだインストールしていない場合は<a href="http://msysgit.github.io/">Git for Windows</a>をインストールしよう。<br />
<br />
インストールできたら「Git Bash」があるのでそちらを叩いて起動する。初期作業位置はc:\Users\your nameかc:\Users\your name\Documentsなどになっているはずである。さっそくSSHキーを下記のコマンドから生成する。<br />
※your nameは自分のユーザ名で置き換えてもらいたい。<br />
<br />
<div class="command">$ ssh-keygen</div><br />
そうすると以下の内容が出力されるので順々に必要な項目をタイプする。<br />
<div class="command">Generating public/private rsa key pair.<br />
Enter file in which to save the key (/c/users/your name/.ssh/id_rsa):<br />
Enter passphrase (empty for no passphrase):<br />
Enter same passphrase again:<br />
Vera vera vera vera ....</div><br />
/c/users/your name/.ssh/id_rsaの場所はgit sshから参照されるので名前と場所を変更しないように。エンターキーでpassphraseの入力に移ろう。passphraseは入力しなくてもかまわないがセキュリティの観点から入力するのが望ましい。<br />
<br />
さて、c:\Users\your name\.sshを参照するとid_rsaとid_rsa.pubができているはずだ。とりあえずWindowsのセットアップはここまでにしてGitサーバをたてる。<br />
<br />
<br />
<b>Gitサーバをたてる</b><br />
サーバセットアップまでは以下をそのまま参考してもらいたい。<br />
<a href="http://git-scm.com/book/en/Git-on-the-Server-Setting-Up-the-Server">4.4 Git on the Server - Setting Up the Server</a><br />
<br />
一応手順を訳すとこうなる。<br />
<br />
1、サーバにGitをインストールする<br />
<div class="command">$ apt-get install git</div><br />
2、'git'ユーザを作り、そのユーザ用の.sshディレクトリを作る<br />
<div class="command">$ sudo adduser git<br />
$ su git<br />
$ cd<br />
$ mkdir .ssh</div><br />
3、Windowsで作ったid_rsa.pubを/home/git/に持ってくる<br />
コピーするなりFTPで渡すなりしてもらいたい。<br />
<br />
4、(サーバに複数ユーザいる場合は他のユーザのホームディレクトリに作らないように注意!)gitユーザのhomeの.sshにauthorized_keysファイルを作る<br />
<div class="command">$ cat id_rsa.pub >> /home/git/.ssh/authorized_keys</div><br />
.sshとauthorized_keysの権限は以下のようにしておく必要があるので注意してほしい。<br />
$ chmod 700 .ssh<br />
$ chmod 600 .ssh/authorized_keys<br />
<br />
authorized_keysを作った後はid_rsa.pubは不要なので削除する<br />
<div class="command">$ rm id_rsa.pub</div><br />
ちなみに複数ユーザを追加する場合はユーザ分のid_rsa.pubをどんどんauthorized_keysに追加していくだけでよい。<br />
<br />
この時点でWindowsからgitサーバにSSHでログインできるはずなので、Windowsに戻ってGit Bashから以下のコマンドを試してもらいたい。<br />
<div class="command">$ ssh git@your domain.com</div>※your domain.comは自分のドメイン名に変更してもらいたい。<br />
<br />
ここでPermission deniedされる場合はSSHのキーがへぼっている可能性があるのでキー作成からやり直して再度authorized_keysにpublic key(id_rsa.pub)を追加してもらいたい。<br />
<br />
5、Git用の作業ディレクトリを作る<br />
それぞれのリポジトリ用に空のディレクトリを用意する必要がある。git initを--bareオプションで行う。<br />
<div class="command">$ cd /opt/git<br />
$ mkdir project.git<br />
$ cd project.git<br />
$ git --bare init</div>必要に応じてどんどんディレクトリを増やしていこう。<br />
<br />
project.gitのオーナーがgitになっていることを確認しよう。もしもなっていない場合は以下コマンドで変更しておこう。<br />
<div class="command">$ chown -R git:git project.git</div><br />
<b>いざcommit</b><br />
さてこれで準備がととのったので再度WindowsにもどりGit Bashから下記のコマンドを実行してみよう。<br />
<div class="command">$ cd myproject<br />
$ git init<br />
$ git add .<br />
$ git commit -m 'initial commit'<br />
$ git remote add origin git@your domain.com:/opt/git/project.git<br />
$ git push origin master</div><br />
これで他の端末にcloneすることもできるようになったので、他の端末から下記のコマンドを実行してみよう。<br />
<div class="command">$ git clone git@your domain.com:/opt/git/project.git<br />
$ cd project<br />
$ vim README<br />
$ git commit -am 'fix for the README file'<br />
$ git push origin master</div><br />
<br />
<b>まとめ</b><br />
SSH周りにはまるとアホみたいに時間がかかるけれど、それさえなければ一時間もかからずにGitサーバを用意してリポジトリをどんどん追加できるはずだ。SSH周りの認証がうまくいかない場合は下手に色々といじくるよりも手順を最初からやり直したほうが早いことが往々にしてあるのでそれを試してみるとよいだろう。Yoo Matsuohttp://www.blogger.com/profile/09732121529027523391noreply@blogger.com0tag:blogger.com,1999:blog-6434745237355317591.post-51122293145635938192013-07-20T00:41:00.001+09:002013-07-20T00:42:29.707+09:00ホワイトIT企業で働くとこんな感じブラック企業の代名詞としてよくあがるIT業界。IT土方(土方の方々に失礼なのではと思わないでもない)なんていわれる職場もあるようでひどいところは本当にひどいようだけれども、今の現場に入って数ヶ月、超絶ホワイトっぷりがすごいので書いてみる。<br />
<br />
<br />
<b>概要</b><br />
今の案件にはお客さん(A社)の自社開発の企業向けソリューション(α)にGUI開発者として参画している。で、このプロジェクト、営業、開発、保守、運用なんかも含めるとざっと80人規模になる。もしかしたら知らない場所にも人がいるかもしれないけれど、同じフロアでαに確実に専任で携わっていると知れている人だけで80人ぐらいいる。α自体はVer1がすでにリリースされていて複数の大企業に納入されて元気に(?かどうか知らないけど)稼動している状態。<br />
<br />
<br />
<b>体制説明</b><br />
色々とトライアンドエラーを繰り返しながら柔軟に体制を変えているようなので以前はまた違ったようだけれども、今は大体こんな感じで回しているようだ。内実はよく分からないので数字は適当。<br />
<br />
・現行開発/サーバサイド(3~5人ぐらい)<br />
Ver2の開発でサーバとかデータ周りをいじくり回してる人達。<br />
<br />
・現行開発/フロントエンド(3~5人ぐらい)<br />
Ver2の開発でGUIとかクライアントアプリ周りをいじくり回してる人達。<br />
<br />
・プロト開発/サーバサイド(5人ぐらい)<br />
Ver3に向けてのプロト開発でサーバとかデータ周りをいじくり回してる人達。<br />
<br />
・プロト開発/フロントエンド(10人ぐらい)<br />
Ver3に向けてのプロト開発でGUIとかクライアントアプリ周りをいじくり回してる人達。GUIの凝ったアプリなのでここに開発リソースがたくさん割かれてる。私がいるのもここ。<br />
<br />
・プロト設計(5人ぐらい)<br />
Ver3に向けての設計を進める人達。<br />
<br />
・営業/コンサル(10人ぐらい)<br />
αを実際のお客さん達に売り込みに行く人達。「αを使ったらこんなに効率よくなりますよー」とか甘言を弄する人々。日本全国飛び回ってるっぽい。<br />
<br />
・保守運用(5~7人ぐらい)<br />
良く分からんけど多分保守とかやってる人達だと思う。<br />
<br />
・テスト(5~7人ぐらい)<br />
テストケースとか作ってるっぽい人達。これも良く分からん。<br />
<br />
ここまではパートナー企業から来てる人達の内訳。それプラス以下。<br />
<br />
・A社社員(20人ぐらい)<br />
パートナーの取りまとめを行う人達。設計の最終決定や、スケジュール管理、アーキテクチャ選定、価格交渉などなど最終的な責任を持つ人達。<br />
<br />
なんか人数合わないけどざっとこんぐらい居る。<br />
<br />
<br />
<b>なぜホワイトなのか</b><br />
で、ここからはいかにホワイトなのかを語る。<br />
<br />
・優秀<br />
基本的にみんな優秀。A社社員は言わずもがな、パートナー企業から来てる人達も優秀。とくにコアメンバーはかなりスキルが高いと思われる。で、まれにあまり仕事のできない人が紛れ込んでくることがあるけれども、そういう人達はさくっと契約が打ち切られるのでいつの間にかいなくなっている。<br />
<br />
・体制とか運用がしっかりしている<br />
BTS(Bug Tracking System)の活用なんて今じゃどこでもやっていることだけど、A社ほど有効活用できているところは少ない。チケットの意味付け(課題、TODO、実装、レビュー、etc)はしっかりしているし、チケットとバージョンコントロールもちゃんと紐付けがされている(とくにレビューとの紐付けがかなり良くできている)。何よりWikiの充実っぷりがすごく、そのWikiも日々更新され、Wiki上でのコメントのやりとりも活発。意識の高い人が多いのだと思う。<br />
<br />
・レビューがすごい<br />
コードレビューが頻繁に行われている。コミットされたコードに対してはA社社員に限らず、パートナーの開発者もガシガシ行って指摘チケットが飛び交っているし、そこで見つけた事例が新しければすぐにWiki化されるというなんとも良くできた仕組み。ここでやべーこいつ、と思われた人は契約カットの憂き目に会うと思われる。またコードだけではなく設計やらテストやらもガンガンレビューされている模様。<br />
<br />
・体調不良に優しい<br />
A社社員は言わずもがな、パートナーでも体調不良で長期で休んでも契約がきられない(とりあえず家庭の事情で長期で休んでいる人がいるけれど契約はきられていない模様)。A社社員に療養が必要となれば積極的に休暇を薦めてフォローしあう体制をすぐさま作ってる感じ。よく知らんけど。<br />
<br />
・スケジュールがきつくない<br />
9-5時勤務で余裕で回る感じ。なのでいつも定時あがり。ただ、実はA社社員はかなり大変なようで終電間際まで作業している人もちらほらいるらしい。それでも休暇なんかはとりやすいらしく、みなさんガシガシ取得している。パートナーも設計の人達とかは大変そうだ。<br />
<br />
・ピリピリしてない<br />
納期のある請負案件だったりすると期日が迫ってくるにつれピリピリしはじめて、嫌な職場になると怒鳴るような人員がいるところもあるけれど、今の現場ではみんなある種のんびりとしている。もちろん自社開発のプロトとはいえスケジュールはあるのでそれに向かって進めているのだけれども、よりよいものを作ろう的なスタンスなのでスケジュールは柔軟である。多分そのせいもあってか余裕があるように見受けられる。これだけでもこの現場のホワイトさが分かろうというもの。<br />
<br />
・嫌らしい人がいない<br />
性格の悪い人がいない。A社の社員はみんなシュッとしてて紳士的である。A社自体も大企業でありA社の社風的なものもあると思うけど、とりあえず一緒に仕事する人達(A社社員に限らず)は感じの良い人達が多い。<br />
<br />
・技術的に結構チャレンジング<br />
新しい技術を上手いこと使ってよいもの作っていこうぜ!的なスタンスだし、技術的にかなり高度なことをやっていたりもするので知識欲を満たせる。<br />
<br />
<br />
とまぁこれぞザ・ホワイト企業だな、と数ヶ月働いてみての実感。飽き性なので小金がたまったら働くのをやめて、しばらく自分のやりたい開発とか調べ物をしてからまた請負仕事をする、みたいな感じで色々な職場を見てきたけれど、A社ほどのホワイトっぷりはいまだかつて見たことが無いレベル。ブラックと叫ばれて久しいIT業界だけど、いるところにはホワイトな企業も生息しているというお話。Yoo Matsuohttp://www.blogger.com/profile/09732121529027523391noreply@blogger.com0tag:blogger.com,1999:blog-6434745237355317591.post-46378998985575748712013-03-22T11:32:00.000+09:002013-03-22T11:32:21.239+09:00磯野ー!2Dゲーム開発しようぜー! Android編 その4長々と続いてきたカツオと中島のゲーム開発も今回で終了だ。今回はTextとEntityModifier周りを解説する。前回までは以下。<br />
<br />
<a href="http://my-clip-devdiary.blogspot.jp/2013/03/dhtml5.html">磯野ー!2Dゲーム開発しようぜー! HTML5編 その1</a><br />
<a href="http://my-clip-devdiary.blogspot.jp/2013/03/dhtml5_15.html">磯野ー!2Dゲーム開発しようぜー! HTML5編 その2</a><br />
<a href="http://my-clip-devdiary.blogspot.jp/2013/03/dhtml5_16.html">磯野ー!2Dゲーム開発しようぜー! HTML5編 その3</a><br />
<a href="http://my-clip-devdiary.blogspot.jp/2013/03/dandroid_17.html">磯野ー!2Dゲーム開発しようぜー! Android編 その1</a><br />
<a href="http://my-clip-devdiary.blogspot.jp/2013/03/dandroid.html">磯野ー!2Dゲーム開発しようぜー! Android編 その2</a><br />
<a href="http://my-clip-devdiary.blogspot.jp/2013/03/dandroid_21.html">磯野ー!2Dゲーム開発しようぜー! Android編 その3</a><br />
<br />
<br />
<b>Text</b><br />
<pre class='c#' name='code'>private Font _font;
@Override
protected void onCreateResources() {
_font = FontFactory.create(this.getFontManager(), this.getTextureManager(), 256, 256, Typeface.create(Typeface.DEFAULT, Typeface.BOLD), 24, Color.WHITE_ARGB_PACKED_INT);
_font.load();
}
@Override
protected Scene onCreateScene() {
_scene = new Scene();
Text text = new Text(100, 10, _font, "Click on the characters!", this.getVertexBufferObjectManager());
text.setColor(new Color(0, 0, 0)); // 色ごとにFontを用意するのは難儀なので、WhiteでFontを読み込み、後から色を設定する
_scene.attachChild(text);
}
</pre>文字を表示するのは一見簡単そうなのだけど柔軟さに欠けるので中々に面倒くさい。第一にフォント読み込み時に色を指定するので文字色を後から任意に変更できない。また色毎にフォントを読み込むのは難儀だ。それなので白色フォントで読み込んだ後にEntityの色変更メソッドを通して任意の色へと設定している。第二にフォント読み込み時の領域確保に指定する値の最適値が判然としない。文字の大きさと文字数から判断するしかないのだけど直感的でないので良く分からない。多量の文字を表示するなら大きくなければならないし、かといって無駄に領域を確保するとメモリを無駄に圧迫するのでやっかいな部分だ。最後にこれはバグなのだけど、コンストラクタで指定した文字列よりも長い文字列をsetTextで後から設定すると例外が発生する。そういう状況が起こりえる場合の回避策はコンストラクタで長い文字列を渡しておくしかないだろう。<br />
<br />
フォントを指定して読み込む方法は下記を参照してほしい。<br />
<a href="https://github.com/nicolasgramlich/AndEngineExamples/blob/GLES2/src/org/andengine/examples/CustomFontExample.java">src/org/andengine/examples/CustomFontExample.java</a><br />
<br />
<br />
<b>EntityModifier</b><br />
<pre class='c#' name='code'>final ButtonSprite ship = new ButtonSprite(50, 50, _shipRegion.getTextureRegion(0), this.getVertexBufferObjectManager());
final AnimatedSprite shipAnimation = new AnimatedSprite(10, 10, _shipRegion, this.getVertexBufferObjectManager());
shipAnimation.animate(50);
shipAnimation.setPosition(50, 50); // コンストラクタで指定しているけれど、このコードがないとRotationの挙動がおかしい
ship.setOnClickListener( new ButtonSprite.OnClickListener(){
@Override
public void onClick(ButtonSprite pButtonSprite, float pTouchAreaLocalX, float pTouchAreaLocalY) {
_scene.detachChild(pButtonSprite);
shipAnimation.setRotation(0);
SequenceEntityModifier shipModifier =
new SequenceEntityModifier(
new IEntityModifier.IEntityModifierListener() {
@Override
public void onModifierStarted(IModifier<ientity> pModifier, IEntity pItem) {
}
@Override
public void onModifierFinished(final IModifier<ientity> pEntityModifier, final IEntity pEntity) {
MainActivity.this.mEngine.runOnUpdateThread(new Runnable() {
@Override
public void run() {
_scene.attachChild(ship);
_scene.detachChild(shipAnimation);
pEntity.unregisterEntityModifier((IEntityModifier)pEntityModifier);
}
});
}
},
new RotationByModifier(0.5f, 90)
,new MoveModifier(1.0f, 50, CAMERA_WIDTH+50, 50, 50, EaseExponentialOut.getInstance())
,new RotationByModifier(0.1f, 180)
,new MoveModifier(1.0f, CAMERA_WIDTH+50, 50, 50, 50, EaseExponentialOut.getInstance())
,new RotationByModifier(0.5f, 90)
);
shipAnimation.registerEntityModifier(shipModifier);
_scene.attachChild(shipAnimation);
}
});
_scene.registerTouchArea(ship);
_scene.attachChild(ship);
</pre>EntityModifier周りだけを抜き取ると余計にわけが分からなくなりそうなのでそこらへんをごっそりと持ってきているけれど、注目して欲しいのは10行目から。SequenceEntityModifierを指定すると順番にEntityの値を変更していくことができる。12行目から27行目までは開始と終了のイベントハンドラーでそれ以降が値の変更を記述している。Tweenjsと同様の働きをする。同時に2つ以上の値を変更したい場合は下記のようにParallelEntityModifierを使用する。<br />
<pre class='c#' name='code'>// 省略
new RotationByModifier(0.5f, 90),
new ParallelEntityModifier(
new ScaleModifier(3, 0.5f, 5),
new RotationByModifier(3, 90)
),
new RotationByModifier(0.5f, 90),
// 省略
</pre>またTweenjsと同様にどのように値を変更させるかEase関数を使って指定ができる。Ease関数の種類は下記を参照してほしい。<br />
<a href="https://github.com/nicolasgramlich/AndEngine/tree/GLES2/src/org/andengine/util/modifier/ease">AndEngine / src / org / andengine / util / modifier / ease /</a><br />
実際に挙動を見るのが一番早いのでAndEngineExamplesをローカルに設定してEase関数周りのサンプルを動作させてみるのが一番良いだろう。その際にはExtensionsも必要なので忘れずに取得しよう。詳しくは<a href="https://github.com/nicolasgramlich/AndEngine">AndEngine</a>のReadMeを参照してほしい。<br />
<br />
EntityModifierの開始、終了のイベントハンドラーでUI要素をいじくる場合は注意してほしい。左記2つの関数はワーカスレッドから呼び出されるのでUI要素をいじくるとアプリがクラッシュする。それなので17行目のようにmEngine.runOnUpdateThreadでUIスレッドを呼び出して処理しよう。<br />
<br />
EntityModifierのざっくりとした説明は以上になるけれど、今回EntityModifierをいじくりまわしていていくつか不明な点があった。1つ目は、EnityModifierを使いまわせないかとEnityModifier.reset()をいじくりまわしてみたけれど上手くいかなかったこと。2つ目は、unregisterEntityModifierの使いどころ。Entityが不要なEntityModifierをいつまでも参照しているのが嫌だったので一応呼んでいるけれど、AndEngineのサンプルコードを参考にしてもunregisterEntityModifierを呼んでいるものがなかった。AndEngineのコードを追っかけていけば分かることなのだろうけれど現状はどうするのが一番良いのかは不明。<br />
<br />
<br />
これでAndEngineの解説を終わりとする。AndEngineの基本的な使い方がどういったものか理解できたかと思う。今回は実装しなかったけれどもちろんゲームのメインループにあたる機能や、あたり判定の便利機能などなどゲーム開発に必要な機能は大体そろっている。そういった今回解説しなかった機能はAndEngineExamplesで詳説されているのでそちらを参照してほしい。Yoo Matsuohttp://www.blogger.com/profile/09732121529027523391noreply@blogger.com0tag:blogger.com,1999:blog-6434745237355317591.post-65118348523337474582013-03-21T17:28:00.002+09:002013-03-21T17:28:25.706+09:00磯野ー!2Dゲーム開発しようぜー! Android編 その3今回は画像周りを解説する。前回までは以下。<br />
<br />
<a href="http://my-clip-devdiary.blogspot.jp/2013/03/dhtml5.html">磯野ー!2Dゲーム開発しようぜー! HTML5編 その1</a><br />
<a href="http://my-clip-devdiary.blogspot.jp/2013/03/dhtml5_15.html">磯野ー!2Dゲーム開発しようぜー! HTML5編 その2</a><br />
<a href="http://my-clip-devdiary.blogspot.jp/2013/03/dhtml5_16.html">磯野ー!2Dゲーム開発しようぜー! HTML5編 その3</a><br />
<a href="http://my-clip-devdiary.blogspot.jp/2013/03/dandroid_17.html">磯野ー!2Dゲーム開発しようぜー! Android編 その1</a><br />
<a href="http://my-clip-devdiary.blogspot.jp/2013/03/dandroid.html">磯野ー!2Dゲーム開発しようぜー! Android編 その2</a><br />
<br />
<br />
<b>BitmapTextureAtlas</b><br />
AndEngineで画像を扱う場合はまずBitmapTextureAtlasでサイズを指定し画像のための領域を作り、そこに実際の画像をどしどし読み込む形になる。コードにすると以下のような形。<br />
<pre name='code' class='c#'> private BitmapTextureAtlas _charactersAtlas;
private TiledTextureRegion _shipRegion;
private TiledTextureRegion _squareRegion;
@Override
protected void onCreateResources() {
BitmapTextureAtlasTextureRegionFactory.setAssetBasePath("gfx/");
_charactersAtlas = new BitmapTextureAtlas(this.getTextureManager(), 1024, 256); // 指定のサイズで領域を作る
_shipRegion = BitmapTextureAtlasTextureRegionFactory.createTiledFromAsset(_charactersAtlas, this, "BlueShip.png", 0, 0, 6, 1);
_squareRegion = BitmapTextureAtlasTextureRegionFactory.createTiledFromAsset(_charactersAtlas, this, "WalkingSquare.png", 0, 100, 5, 1);
_charactersAtlas.load();
}
</pre>BitmapTextureAtlasのコンストラクタで指定する数値は2のべき乗である必要がある。つまり2,4,8,16,32,64,128,256...というわけだ。上記のコードが実行されると下図のような様子になる。<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgEcDL26Srm1UOpqAEWDxgu57Etmc4ohQDmHj8kUcBbB3vvIMjFq6oi_mGkkaUgMcUV9KWblS3bB9RmiqaKE99TooDNbBg9q5KPc4QXsxw9m26dxoqRBRwcEnVjDW3A_PneOHvNtxBD_YQ/s1600/atlas-sample.png" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgEcDL26Srm1UOpqAEWDxgu57Etmc4ohQDmHj8kUcBbB3vvIMjFq6oi_mGkkaUgMcUV9KWblS3bB9RmiqaKE99TooDNbBg9q5KPc4QXsxw9m26dxoqRBRwcEnVjDW3A_PneOHvNtxBD_YQ/s320/atlas-sample.png" /></a><br />
<br />
BlueShip.pngは600*100なので幅512では収まらないので幅1024になっているし、WalkingSquare.pngとあわせての必要な高さが230になっているので高さは256となっている。そのため画像のように無為な領域が多量に発生してしまっている。無駄なメモリ使用を減らすためにも画像はすき間無くつめられるようなサイズを意識して用意するとよいだろう。ちなみにBitmapTextureAtlasにあまりにも大きい領域を指定するとアプリがクラッシュすることもあるので注意しよう。4096などを指定すると危うそうだ。<br />
<br />
<br />
<b>TiledTextureRegion</b><br />
BitmapTextureAtlasのほかにもここではTiledTextureRegionを使ってスプライトシートアニメーションを読み込むことを明示している。下記のコードで船のアニメーションが簡単に実現できる。<br />
<pre name='code' class='c#'>final AnimatedSprite shipAnimation = new AnimatedSprite(10, 10, _shipRegion, this.getVertexBufferObjectManager());
shipAnimation.animate(50); //アニメーション速度
_scene.attachChild(shipAnimation);
</pre>また下記のようにTiledTextureRegionから指定のフレームを使ってスプライトオブジェクトを作ることも可能。<br />
<pre name='code' class='c#'>final Sprite ship = new Sprite(50, 50, _shipRegion.getTextureRegion(0), this.getVertexBufferObjectManager());
</pre><br />
<br />
<b>TextureRegion</b><br />
単純に画像をただ表示するだけならば下記のようにすればよい。<br />
<pre name='code' class='c#'> private BitmapTextureAtlas _backgroundAtlas;
private TextureRegion _backgroundRegion;
@Override
protected void onCreateResources() {
BitmapTextureAtlasTextureRegionFactory.setAssetBasePath("gfx/");
_backgroundAtlas = new BitmapTextureAtlas(this.getTextureManager(), 512, 512);
_backgroundRegion = BitmapTextureAtlasTextureRegionFactory.createFromAsset(_backgroundAtlas, this, "Background02.png", 0, 0);
_backgroundAtlas.load();
}
@Override
protected Scene onCreateScene() {
_scene = new Scene();
Sprite bg = new Sprite(0, 0, _backgroundRegion, this.getVertexBufferObjectManager());
bg.setScaleCenter(0, 0);
bg.setScale(CAMERA_WIDTH/400f, CAMERA_HEIGHT/300f);
_scene.attachChild(bg);
}
</pre><br />
BitmapTextureAtlas周りが理解できればAndEngineの画像周りはしごくシンプルだ。次回はEntityModifier周りを解説してこのシリーズも終了となる。Yoo Matsuohttp://www.blogger.com/profile/09732121529027523391noreply@blogger.com0tag:blogger.com,1999:blog-6434745237355317591.post-67003770911746036152013-03-19T21:32:00.000+09:002013-03-19T21:32:35.846+09:00磯野ー!2Dゲーム開発しようぜー! Android編 その2今回からはAndEngineを使ってのゲーム開発を解説する。前回までは以下。<br />
<br />
<a href="http://my-clip-devdiary.blogspot.jp/2013/03/dhtml5.html">磯野ー!2Dゲーム開発しようぜー! HTML5編 その1</a><br />
<a href="http://my-clip-devdiary.blogspot.jp/2013/03/dhtml5_15.html">磯野ー!2Dゲーム開発しようぜー! HTML5編 その2</a><br />
<a href="http://my-clip-devdiary.blogspot.jp/2013/03/dhtml5_16.html">磯野ー!2Dゲーム開発しようぜー! HTML5編 その3</a><br />
<a href="http://my-clip-devdiary.blogspot.jp/2013/03/dandroid_17.html">磯野ー!2Dゲーム開発しようぜー! Android編 その1</a><br />
<br />
<br />
<b>サンプルコード</b><br />
今回使用するコードは<a href="https://github.com/yooontheearth/andengine-sample">https://github.com/yooontheearth/andengine-sample</a>から取得できる。<br />
<br />
<br />
<b>概要</b><br />
AndEngineはOpenGLベースのゲームライブラリで現在はバージョンGLES2となっている。AndEngine周りのチュートリアルを探すと大体前バージョンであるGLES1が見つかると思うけれど、GLES2になって仕様が若干変わっているので注意してもらいたい。AndEngine GLES2のコードは以下。<br />
<a href="https://github.com/nicolasgramlich/AndEngine">https://github.com/nicolasgramlich/AndEngine</a><br />
<br />
<br />
<b>セットアップ</b><br />
Eclipseのセットアップ方法はよくあるので今回はIntellij IDEAでのセットアップ方法を解説する。今回使用するIDEAのバージョンは12.04のCEだ。<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQnVnlMhK54-hAvJ6o-PlgphKAqpSCpa4Omg9ATuVGhAjtdw4Z1lqwC7iNgO2naVSiBXwVFuo8Q9AbI3d793Kw6xI-6N4gnkZUmSSJl-0QFcczUznfZKJyg5xKTQZdiWGzKVg5V_whJHM/s1600/1.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQnVnlMhK54-hAvJ6o-PlgphKAqpSCpa4Omg9ATuVGhAjtdw4Z1lqwC7iNgO2naVSiBXwVFuo8Q9AbI3d793Kw6xI-6N4gnkZUmSSJl-0QFcczUznfZKJyg5xKTQZdiWGzKVg5V_whJHM/s320/1.png" /></a><br />
アプリケーションの新規作成の図。Project SDKを4.2としているけれど、AndEngine GLES2は2.2以降で動作する(らしい)。ちなみにちぬあたりMEGAはAndroid 2.3の実機にインストールしたところ問題なく動作した。<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgYaMYoB6wRcApVCkzOe1d9TF4kJ2VswuhXgNiVbam07WXF8plXKA-1wRvvd2J7ajCFprFaQzNPrN7JPzFUJQIefeRPC2bwZsDB5qS1W1mJl2qUubRaul-tqL9dP1g7XtbvcpkeKOECGqw/s1600/2.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgYaMYoB6wRcApVCkzOe1d9TF4kJ2VswuhXgNiVbam07WXF8plXKA-1wRvvd2J7ajCFprFaQzNPrN7JPzFUJQIefeRPC2bwZsDB5qS1W1mJl2qUubRaul-tqL9dP1g7XtbvcpkeKOECGqw/s320/2.png" /></a><br />
AndEngineを追加する準備の図。File→Project Structure→Modulesを選択→上部の緑の十字をクリック→New Moduleを選択する。<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjmSs6Ls82Sp_VkDihakh1EdbY5X9GVOTbe83Id8f11YxIZL32Wrak0myeHWJbZL4l6XEZ3xJQpsKtglmAF7Wre60l-Ht-rDH8MYl5zpjwIGyPc8ajANtegwewBFMqoiniSeWPCaMxyl8Y/s1600/3.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjmSs6Ls82Sp_VkDihakh1EdbY5X9GVOTbe83Id8f11YxIZL32Wrak0myeHWJbZL4l6XEZ3xJQpsKtglmAF7Wre60l-Ht-rDH8MYl5zpjwIGyPc8ajANtegwewBFMqoiniSeWPCaMxyl8Y/s320/3.png" /></a><br />
AndEngineを追加するの図。Library Moduleを選択→Content rootにダウンロードしたAndEngineのフォルダを選択して(<span style="color: red;">※Package nameは「org.andengine」と入力するように</span>)Finishをクリックする。<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhEhU0e0lqwkiu3k3N83TjNJ6t4OhiYvs6WTJ6MfFwdy-N96pPTJXrPkCFEDBybc8FCMOCQRRZVMh67RAJfdIJ7AcpbKh3qP6enB_ZVaWJyYW3PotTEoxPzYqxse_gdjBouJaou8Rfhd1E/s1600/4.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhEhU0e0lqwkiu3k3N83TjNJ6t4OhiYvs6WTJ6MfFwdy-N96pPTJXrPkCFEDBybc8FCMOCQRRZVMh67RAJfdIJ7AcpbKh3qP6enB_ZVaWJyYW3PotTEoxPzYqxse_gdjBouJaou8Rfhd1E/s320/4.png" /></a><br />
AndEngineを確認の図。Module SDKは4.2のこと。AndEngineは3.0以降のConstantをいくつか参照しているので最新のSDKを参照しないとコンパイルエラーになる。<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjCSjZXM63Va3x5RYUSdPXSNeJJiJP6qr5tqrusK2-WR_WKX6iu8tf5Zac5m9GaiMDxkDtq5SFMpnW5tsgUQtmHW-_61BgoruE24nlBVLDbZFihl80t4CZk8O79g-Wc22QMSwS9m8Ch9fU/s1600/5.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjCSjZXM63Va3x5RYUSdPXSNeJJiJP6qr5tqrusK2-WR_WKX6iu8tf5Zac5m9GaiMDxkDtq5SFMpnW5tsgUQtmHW-_61BgoruE24nlBVLDbZFihl80t4CZk8O79g-Wc22QMSwS9m8Ch9fU/s320/5.png" /></a><br />
AndEngineを参照するの図。andengine sampleを選択→Dependenciesタブ→右の緑の十字をクリック→Module Dependencyを選択→AndEngineを選択する。AndEngineの参照ができたらExportにチェックしておこう。<br />
<br />
<br />
では早速コードを見ていこう。<br />
<br />
<b>src/com.matsuosoftware.game.andengine_sample/MainActivity.java</b><br />
<pre class="c#" name="code">public class MainActivity extends SimpleBaseGameActivity {
public static final int CAMERA_WIDTH = 480;
public static final int CAMERA_HEIGHT = 320;
private Camera _camera;
@Override
public EngineOptions onCreateEngineOptions() {
_camera = new Camera(0, 0, CAMERA_WIDTH, CAMERA_HEIGHT);
return new EngineOptions(true, ScreenOrientation.LANDSCAPE_SENSOR, new RatioResolutionPolicy(CAMERA_WIDTH, CAMERA_HEIGHT), _camera);
}
@Override
protected void onCreateResources() {
BitmapTextureAtlasTextureRegionFactory.setAssetBasePath("gfx/");
// 画像などのResourceを読み込む
}
@Override
protected Scene onCreateScene() {
_scene = new Scene();
_scene.setTouchAreaBindingOnActionDownEnabled(true);
// this.mEngine.registerUpdateHandler()に登録するオブジェクトがいわゆるゲームのメインループになる
// _sceneに画像をぺたぺた貼り付けていく
return _scene;
}
}
</pre>上記はもっともシンプルな構成のActivityだ。ちなみに上記を実行しても何も表示されない。AndEngineはSimpleBaseGameActivityから派生したActivityひとつだけで動作する。開発者はSceneオブジェクトにぺたぺたと画像を貼り付けつつ、this.mEngine.registerUpdateHandler(Obj)で登録したObjのonUpdateが定期的に呼ばれるのでそこで当たり判定などを行いつつ開発をすすめていく。ただ基本は上記の3つのメソッドで、エンジンを作り、リソースを読み込み、シーンを使って描画するという流れだ。<br />
<br />
<br />
次回以降は上記のコードにドシドシ肉付けを行っていく。Yoo Matsuohttp://www.blogger.com/profile/09732121529027523391noreply@blogger.com0tag:blogger.com,1999:blog-6434745237355317591.post-27496889612286254982013-03-17T09:00:00.000+09:002013-03-17T09:00:05.082+09:00磯野ー!2Dゲーム開発しようぜー! Android編 その1前回まではHTML5での2dゲーム開発を解説したが今回からはAndroidでのゲーム開発を解説する。「次回はAndEngineを」と述べたけれど気が変わったのでAndroid編初回の今回は前回までに作ったHTML5ベースのゲームをWebViewでラッピングし、さもAndroidネイティブで作られたゲームかのように動作させる方法を解説する。前回までのシリーズは以下。<br />
<br />
<a href="http://my-clip-devdiary.blogspot.jp/2013/03/dhtml5.html">磯野ー!2Dゲーム開発しようぜー! HTML5編 その1</a><br />
<a href="http://my-clip-devdiary.blogspot.jp/2013/03/dhtml5_15.html">磯野ー!2Dゲーム開発しようぜー! HTML5編 その2</a><br />
<a href="http://my-clip-devdiary.blogspot.jp/2013/03/dhtml5_16.html">磯野ー!2Dゲーム開発しようぜー! HTML5編 その3</a><br />
<br />
<br />
<b>ゲーム概要</b><br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiL5QMjaaXOajrwdw2S0GBSUU7mg9BraWDKe_qDHedTKukTEX2b0gmBbE2rg4P3ageFzuQNdVBs0lMzov2gXLd8nt3Ww0TYVpCluk2VYFXbNpX9nPReAspFsZmQJ7wadclwrfqf2kcrLX0/s1600/1.png" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiL5QMjaaXOajrwdw2S0GBSUU7mg9BraWDKe_qDHedTKukTEX2b0gmBbE2rg4P3ageFzuQNdVBs0lMzov2gXLd8nt3Ww0TYVpCluk2VYFXbNpX9nPReAspFsZmQJ7wadclwrfqf2kcrLX0/s320/1.png" /></a><br />
Androidローカル(assetsフォルダ)に配置したindex.html、画像、JavaScriptファイルなどをWebViewに読み込んでいる。ゲームの内容は前回までとまったく同じ。<br />
※index.html等々はローカルから読み込んでいるけれど、jQuery、EaselJSなどのライブラリは前回までのコードそのままにCDN(Content Delivery Network)を利用しているのでインターネット経由で取得している。オフラインでも動作可能にするにはこれらの外部ライブラリもローカルに配置しそちらを参照するようにすればよい。<br />
<br />
<br />
<b>サンプルコード</b><br />
今回のWebViewのサンプルコードは<a href="https://github.com/yooontheearth/html5-game-webview-sample">https://github.com/yooontheearth/html5-game-webview-sample</a>から取得できる。ではさっくりと見ていこう。<br />
<br />
<br />
<b>src/com.matsuosoftware.game.webview_sample/MainActivity</b><br />
<pre name='code' class='c#'>public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.main);
WebView webview = (WebView)findViewById(R.id.webview);
webview.getSettings().setJavaScriptEnabled(true);
webview.loadUrl("file:///android_asset/index.html");
}
}
</pre>いたってシンプルである。が、順に見ていこう。<br />
・6、7行目、フルスクリーンで表示するためのフラグを設定する<br />
・11行目、JavaScriptを有効にする<br />
・12行目、assetsフォルダからファイルを読み込む。file:///android_assetはassetsフォルダへの特別なファイルパス<br />
<br />
ここまででゲームはさもAndroidネイティブアプリかのように起動するのだけれども、見てくれをよくするためにもindex.htmlのbodyタグに以下のスタイル設定が必要となる。<br />
<br />
<body style="margin: 0; padding: 0"><br />
<br />
とまぁ、これでブラウザで動作させたときと同様のHTML5のゲームがAndroidでラッピングできたわけだけれども、WebViewにはこれだけではなくAndroidからJavaScriptを操作したり値を渡したりその逆もまた可能になっているのでJavaScript上の値をAndroid上で永続化したり復元したりもできるようになっている。詳しくは以下を参照してもらいたい。<br />
<br />
・AndroidとJavaScriptとのやりとりは以下のリンクからaddJavascriptInterface()周りを参照してもらいたい。<br />
<a href="http://developer.android.com/guide/webapps/webview.html">Building Web Apps in WebView</a><br />
<br />
・JavaScriptのエラーハンドルなどは以下のリンクからsetWebChromeClient、setWebViewClient周りを参照してもらいたい。<br />
<a href="http://developer.android.com/reference/android/webkit/WebView.html">WebView</a><br />
<br />
<br />
<b>結論</b><br />
HTML5のコードに手を加えずともWebViewでラッピングしてそのままAndroidアプリとしてリリースすることもできるし、Android用の特別な処理を実装したい場合はAndroidと双方向でやりとりできることも理解できたかと思う。<br />
<br />
ただ、HTML5をそのまま使えるというわけで各プラットフォームごとに開発を行わなくてもよいかも!?と明るい展望を一瞬みせてくれたWebViewではあったけれど、その実、実行速度はひっじょうに遅い!!!通常だと50または60FPS平均のものが10FPS以下ほどへと速度が落ちてしまうので大体のゲームはゲームとして成り立たなくなると思われる。通常のアプリ類であればPhoneGapやTitaniumがプラットフォームフリーへの一応の答えになると思うけれど、ゲームはどうなんだろう。以前調べたときはあまりよさげなのはなかった。<br />
<br />
<br />
というわけでAndroidの能力を搾り出すためにも次回からはAndEngineの使い方を解説していく。Yoo Matsuohttp://www.blogger.com/profile/09732121529027523391noreply@blogger.com0tag:blogger.com,1999:blog-6434745237355317591.post-36454908622485916382013-03-16T09:00:00.000+09:002013-03-16T09:00:03.201+09:00磯野ー!2Dゲーム開発しようぜー! HTML5編 その3磯野と中島のゲーム開発も今回で3回目。HTML5編は今回で終了だ。ちなみに今回もめがねと坊主頭の出番はない。前回までの分は以下。<br />
<br />
<a href="http://my-clip-devdiary.blogspot.jp/2013/03/dhtml5.html">磯野ー!2Dゲーム開発しようぜー! HTML5編 その1</a><br />
<a href="http://my-clip-devdiary.blogspot.jp/2013/03/dhtml5_15.html">磯野ー!2Dゲーム開発しようぜー! HTML5編 その2</a><br />
<br />
<b>coffeescripts/platform.coffee</b><br />
<pre class="js" name="code">class GamePlatform
constructor: (@stage, width, height, contentManager)->
# 背景の用意
background = new createjs.Bitmap contentManager.background
background.setTransform 0, 0, width/400.0, height/300.0 # 背景画像は400*300でCanvasサイズと合わないので拡大する
@stage.addChild background
# 文字列表示
text = new createjs.Text "Click on the characters!", "bold 24px Meiryo", "#000"
text.x = (width/2)-(text.getMeasuredWidth()/2)
text.y = 10
# text.align = 'right' # align指定はこの方法で
@stage.addChild text
# スプライトシートの設定
shipSpriteSheet = new createjs.SpriteSheet
images: [contentManager.ship]
frames:
width:100 # 1フレームの幅を指定
height:100 # 1フレームの高さを指定
regX:50 # 中心点(拡大縮小、回転、移動用の)を指定
regY:50 #
animations:
move:
frames: [0, 1, 2, 3, 4, 5] # アニメーションフレームの指定
frequency: 10 # フレームごとの速度
# スプライトアニメーションの設定
shipAnimation = new createjs.BitmapAnimation shipSpriteSheet
shipAnimation.x = shipAnimation.y = 100
# スプライトシートから指定のフレームの画像を抜き取ってBitmapオブジェクトを作成する
ship = new createjs.Bitmap createjs.SpriteSheetUtils.extractFrame shipSpriteSheet, 0
ship.regX = ship.regY = 50 # 中心点をスプライトアニメーションに合わせる
ship.x = ship.y = 100
ship.onClick = =>
shipAnimation.rotation = 90
@stage.addChild shipAnimation
shipAnimation.gotoAndPlay "move"
@stage.removeChild ship
# オブジェクトアニメーションを設定する
createjs.Tween.get(shipAnimation)
.to({x:width+100, scaleX:2, scaleY:2}, 1000, createjs.Ease.cubicIn)
.to({rotation:270})
.to({x:100, scaleX:1, scaleY:1}, 1000, createjs.Ease.cubicOut)
.to({rotation:0}, 500)
.wait(500)
.call =>
@stage.addChild ship
@stage.removeChild shipAnimation
shipAnimation.stop()
@stage.addChild ship
sqSpriteSheet = new createjs.SpriteSheet
images: [contentManager.square]
frames:
width:100
height:130
regX:50
regY:65
animations:
walk:
frames: [0, 1, 2, 3, 4]
frequency: 20
sqAnimation = new createjs.BitmapAnimation sqSpriteSheet
sqAnimation.x = 100
sqAnimation.y = 250
sq = new createjs.Bitmap createjs.SpriteSheetUtils.extractFrame sqSpriteSheet, 0
sq.regX = 50
sq.regY = 65
sq.x = 100
sq.y = 250
sq.onClick = =>
@stage.addChild sqAnimation
sqAnimation.gotoAndPlay "walk"
@stage.removeChild sq
createjs.Tween.get(sqAnimation)
.to({x:width+100}, 1000, createjs.Ease.quadInOut)
.to({scaleX:-1}) # Y軸を中心にフリップする
.to({x:100}, 1000, createjs.Ease.bounceOut)
.to({scaleX:1})
.wait(500)
.call =>
@stage.addChild sq
@stage.removeChild sqAnimation
sqAnimation.stop()
@stage.addChild sq
# Containerを使用してオブジェクトをまとめて管理したりもできる
# container = new createjs.Container()
# player = new createjs.Bitmap somethingImage
# container.addChild player
# player2 = new createjs.Bitmap somethingImage2
# container.addChild player2
# @stage.addChild container
@fps = new createjs.Text "0 fps", "bold 14px Arial", "#000"
@fps.x = @fps.y = 10
@stage.addChild @fps
startGame: ->
createjs.Ticker.userRAF = true
createjs.Ticker.setFPS 60
createjs.Ticker.addListener this
tick: =>
#
# キー入力にあわせたキャラクタの動作やあたり判定などをここで行う
#
@fps.text = (Math.round createjs.Ticker.getMeasuredFPS())+" fps"
@stage.update()
window.GamePlatform = GamePlatform
</pre>・4行目、画像からBitmapオブジェクトを生成する<br />
・5行目、setTransformは移動、スケール、回転、傾き、中心点をまとめて指定できる便利メソッド。ここではスケールまでしか指定していない<br />
・6行目、Zオーダーという概念はなく後からstageに追加されたものほど上にくる。Zオーダーの指定がしたい場合は自分で実装しよう<br />
・9行目、テキスト生成はこのとおり<br />
・12行目、alignの指定などもおこなえる<br />
・16行目、スプライトシートの設定を行う。画像の指定は複数可能。そちらの詳細は<a href="http://www.createjs.com/Docs/EaselJS/classes/SpriteSheet.html">SpriteSheet Class</a>を参照してほしい<br />
・21行目、アクションの中心点を設定する。意味が不明な場合はXY両方の値を0に変更して実行してみよう。宇宙船の回転のアニメーションの部分で意味が理解できるはずだ<br />
・23行目、BitmapAnimationで使用するアニメーションの設定を行う。ここの設定方法は複数あるのでこちらも<a href="http://www.createjs.com/Docs/EaselJS/classes/SpriteSheet.html">SpriteSheet Class</a>を参照してほしい<br />
・28行目、BitmapAnimationオブジェクトを生成する。16行目で行ったスプライトシートのアニメーション設定をこのBitmapAnimationオブジェクトを通して使用する<br />
・31行目、スプライトシートから指定のフレームを抜き取ってBitmapオブジェクトを生成する<br />
・34行目、クリックイベントをハンドルする。これもEaselJSの大きな功績のひとつだ<br />
・37行目、指定のアニメーションを実行する<br />
・40行目、TweenJSを使用してオブジェクトアニメーションを設定する。指定のオブジェクトのプロパティ値を指定の時間と動かし方で変更できる<br />
・46行目、TweenJSのアニメーションの終わりにコールバックを設定する。そのコールバックで宇宙船のアニメーションをstageからはずしたり宇宙船のアニメーションを止めたりする<br />
・77行目、画像をフリップさせる場合はscaleXまたはscaleYに-1を設定する<br />
・87行目、Containerを活用するとオブジェクトのグルーピングも楽に行える<br />
・98行目、Tickerの設定あれこれ。詳細は<a href="http://www.createjs.com/Docs/EaselJS/classes/Ticker.html">Ticker Class</a>を参照してほしい<br />
・102行目、今回のサンプルではあたり判定やらキー操作でのプレイヤーキャラクターの動作などは実装しなかったけれどそういった実装はtick()で行うようになる<br />
・106行目、FPS(Frame Per Second)を表示する<br />
<br />
<br />
ざーっと説明したけれども大体EaselJSを使ってのゲーム開発がどういうものか理解できただろうか。前回にも述べたけれどTicker(に登録したオブジェクトのtick())とStageという概念が基礎にあるだけでその上で行うゲーム開発は通常のゲーム開発とさほど変わらない。というわけでこれまでの解説を通してEaselJS、TweenJS、PreloadJSの便利さとか開発のしやすさが伝わったら幸いだ。<br />
<br />
<br />
次回からはAndEngineの解説を行っていく。Yoo Matsuohttp://www.blogger.com/profile/09732121529027523391noreply@blogger.com0tag:blogger.com,1999:blog-6434745237355317591.post-29848347849014715942013-03-15T09:00:00.000+09:002013-03-16T19:56:41.361+09:00磯野ー!2Dゲーム開発しようぜー! HTML5編 その2今回はHTML5でのゲーム開発を解説するシリーズの第二回である。前回は以下。<br />
<br />
<a href="http://my-clip-devdiary.blogspot.jp/2013/03/dhtml5.html">磯野ー!2Dゲーム開発しようぜー! HTML5編 その1</a><br />
<br />
今回からコードを使って解説していく。内容はCoffeeScriptをもとに説明するけれど、javascriptsフォルダ内に対応するJavaScriptファイルがあるのでCoffeeScriptになじみがない方はそちらを参照してほしい。<br />
<br />
<b>index.html</b><br />
<pre class="js" name="code"><body>
<canvas id="c" width="480" height="320"></canvas>
</body>
</pre>なぜかCSSからのサイズ指定が行えないのでCanvasタグに直接widthとheightを指定している。<br />
<br />
<br />
<b>coffeescripts/contentManager.coffee</b><br />
<pre class="js" name="code">class ContentManager
manifest:[
{src:'images/Background02.png', id:'background'},
{src:'images/BlueShip.png', id:'ship'},
{src:'images/WalkingSquare.png', id:'square'}
]
constructor: (@stage, width, height, @downloadCompleteCallback)->
@preload = new createjs.PreloadJS false # useXHR=false ローカルからファイルを読み込むのでimageタグを強要する
# xhrはCross Origin Resource Sharingを許可しない
@preload.onError = @handleElementError
@preload.onFileLoad = @handleElementLoad
@downloadProgress = new createjs.Text "-- %", "bold 18px Arial", "#fff"
@downloadProgress.x = (width / 2)
@downloadProgress.y = (height / 2)
@elementLoadedCount = 0
startDownload: ->
@preload.loadManifest @manifest
@stage.addChild @downloadProgress
createjs.Ticker.addListener this
createjs.Ticker.setInterval 50
handleElementError: (e)=>
alert "画像読み込み失敗 : #{e.src}"
handleElementLoad: (e)=>
@[e.id] = e.result
@elementLoadedCount += 1
if @elementLoadedCount is @manifest.length
@stage.removeChild @downloadProgress
createjs.Ticker.removeListener this
@downloadCompleteCallback()
tick: =>
@downloadProgress.text = Math.round((@elementLoadedCount/@manifest.length) * 100) + " %"
@stage.update()
window.ContentManager = ContentManager
</pre>何はともあれ画像がないとはじまらないのでPreloadJSを使用して画像を読み込む。<br />
・2行目、読み込む対象画像へのパスを指定する。ここで指定しているidが11行目のonFileLoadイベントの引数e.idとして渡ってくる<br />
・8行目、PreloadJSのコンストラクタにuseXHR=falseを指定している。この引数はPreloadJSにXHRではなくimageタグでの画像取得を行わせるための設定。今回のサンプルはローカルファイルからの読み込みなのでXHRでの取得ができない<br />
・10行目、画像取得時にエラーが発生した場合のコールバックを設定する<br />
・11行目、各画像取得完了時のコールバックを設定する<br />
・17行目、PreloadJSに画像の読み込みを指示する<br />
・19行目、createjs.Tickerのイベントリスナーを登録する。ここで登録したオブジェクトのtickメソッドが定期的に呼び出される。このtickメソッドがいわゆるゲーム開発におけるメインループとなる<br />
・24行目、取得の完了した画像のidをプロパティ名として画像内容を設定する。このidは2行目で設定していたものと同一<br />
・28行目、すべての画像の取得が完了したのでTickerのイベントリスナーを忘れずにはずしておく<br />
・32行目、stage.update()で画面のリフレッシュを行う。stageについては後述する<br />
<br />
とざっくりとだがPreloadJSの機能説明をした。簡単に言うと画像の取得が楽、という一言に尽きる。ちなみにPreloadJSを使わずに画像取得をする場合は以下のようになる。<br />
<pre class="js" name="code">var image = new Image();
image.onload = function() {
context.drawImage(image, 10, 10);
};
image.src = "http://hoge/test.jpg";
</pre>上記のコードを汎用的に使いやすい形にするとPreloadJSになるのが理解できるかと思う。<br />
<br />
<br />
<b>coffeescripts/index.coffee</b><br />
<pre class="js" name="code">$(document).ready ()->
isCanvasSupported = ->
elem = document.createElement('canvas')
return !!(elem.getContext && elem.getContext('2d'))
unless isCanvasSupported()
return alert "あなたのブラウザはCanvasが使えないです"
canvas = $('#c')[0]
stage = new createjs.Stage canvas
contentManager = new ContentManager stage, canvas.width, canvas.height, ->
platform = new GamePlatform(stage, canvas.width, canvas.height, this)
platform.startGame()
contentManager.startDownload()
</pre>使用しているブラウザがCanvasをサポートしているかチェックしてから先ほどのContentManagerで画像の取得を行っている。全画像取得完了時のコールバックでGamePlatform(後述)をインスタンス化しゲーム本編へと移行する。<br />
・9行目、Stageをインスタンス化している。BitmapオブジェクトやBitmapAnimationオブジェクトなどの描画したいオブジェクトをstage.addChild(Obj)で追加する。stage.update()で追加されたオブジェクトのリストをコンストラクタで指定されたCanvasへと描画する。<br />
<br />
EaselJSではTickerとStageが開発の基礎となる。<br />
<br />
長くなったので続きは<a href="http://my-clip-devdiary.blogspot.jp/2013/03/dhtml5_16.html">次回</a>。Yoo Matsuohttp://www.blogger.com/profile/09732121529027523391noreply@blogger.com0tag:blogger.com,1999:blog-6434745237355317591.post-63916714292368173482013-03-14T16:59:00.002+09:002013-03-16T19:56:22.479+09:00磯野ー!2Dゲーム開発しようぜー! HTML5編 その1<a href="http://www.chinu-kakariduri.com/Home/AtariGame">ちぬあたりMEGA</a>の<a href="https://play.google.com/store/apps/details?id=com.chinukakariduri.game.chinuatari">Android版</a>もリリースし、HTML5とAndroidの2Dゲーム開発が何となく分かったのでこれから数回に分けて解説していく。最初の数回はHTML5(というよりもeaseljs)で残りはAndroidの解説になる。AndroidはAndEngineの使い方がメインとなる。ちなみに中島も磯野も出てはこないのであしからず。<br />
<br />
今回のシリーズで使用する画像は下記から拝借している。商用利用する場合は連絡が必要ということなのでその意向がある方はリンク先の指示に従っていただきたい。<br />
<a href="http://www.xnadevelopment.com/sprites/">XNA Sprites</a><br />
<br />
<br />
<b>ゲーム内容</b><br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgfLmUIm0kGtOlmg8Vg9eAm6jT6WRmDfcOd6KxrtQlQdd1nE71j0ecpdux7vDY7Ie6LRINx7CA1Di2RjhC4M_Iq5ZnphpYr4HmWyAMlGFPzJT5YBMIGUIebNLnnSWRuCtlTsrj081mPyYA/s1600/html5+game+sample.png" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgfLmUIm0kGtOlmg8Vg9eAm6jT6WRmDfcOd6KxrtQlQdd1nE71j0ecpdux7vDY7Ie6LRINx7CA1Di2RjhC4M_Iq5ZnphpYr4HmWyAMlGFPzJT5YBMIGUIebNLnnSWRuCtlTsrj081mPyYA/s320/html5+game+sample.png" /></a><br />
いたってシンプルな2dゲームである。キャラクターをクリックすると右端に消えた後に戻ってくるという内容だ。ちなみにゲームの対象年齢は2~3歳児ぐらいとなっている。<br />
<br />
<br />
<b>サンプルコード</b><br />
今回のHTML5のソースコードは<a href="https://github.com/yooontheearth/html5-game-sample">https://github.com/yooontheearth/html5-game-sample</a>から取得できる。後述する設定を行うことによってindex.htmlから動作確認できるはずだ。javascriptsフォルダ内のJavaScriptファイルはすべてcoffeescriptsフォルダ内のCoffeeScriptファイルから生成されている。<br />
<br />
<br />
<b>設定</b><br />
今回はサーバをたてずにローカルからindex.htmlを起動するのでChromeにいろいろと設定が必要になる(IEとFireFoxは使用していないので不明)。Chromeの初期設定ではローカルからファイルを読み込んだ場合にほかの読み込んだローカルファイルをいじくるとセキュリティエラーが発生するようになっている。なのでそれを回避するために下記のフラグを設定する必要がある。<br />
<a href="http://peter.sh/experiments/chromium-command-line-switches/#allow-file-access-from-files">--allow-file-access-from-files</a><br />
<span style="color: red;">※セキュリティ上たいへんよろしくないのでテスト中などの場合にのみ使用するように</span><br />
<br />
上記のフラグとともにChromeを起動する方法は下記。これはUbuntuの場合。<br />
<div class="command">$ chromium-browser --allow-file-access-from-files</div>ちなみに上記のコマンドでChromeを立ち上げる前にすでにChromeのProcessが起動していたりするとフラグの設定が有効にならないのでUbuntuを再起動することをおすすめする。そのほかのWindowsやOS X、Androidなんかの起動方法は下記を参照してほしい。<br />
<a href="http://www.chromium.org/developers/how-tos/run-chromium-with-flags">Run Chromium with flags</a><br />
<br />
どうやってもうまくいかん、という人はApacheでもなんでもよいので適当にWebサーバを立ち上げてhttp://localhost/index.htmlとかしてもらっても今回のサンプルは動作するはずなのでそちらからアプローチしてもらってもかまわない。<br />
<br />
<br />
<b>ライブラリ</b><br />
今回はHTML5編の第一回ということで今回のシリーズで使用するライブラリの紹介をする。<br />
・<a href="http://www.createjs.com/#!/EaselJS">EaselJS</a><br />
ゲームのメインループや画像オブジェクトクラス、スプライトシート・アニメーションクラス、テキストクラスなどなど便利な機能盛り沢山のライブラリでHTML5の2dゲーム開発の核となる。各クラスの詳細な情報は<a href="http://www.createjs.com/Docs/EaselJS/modules/EaselJS.html">EaselJS Document</a>を参照してほしい。<br />
<br />
・<a href="http://www.createjs.com/#!/TweenJS">TweenJS</a><br />
オブジェクトのアニメーションや動作を直感的に記述できるライブラリ。メソッドチェイン形式で振る舞いを記述できるので大変便利。<a href="http://www.createjs.com/#!/TweenJS/demos">デモ</a>でどのようなビルトインの動きがあるか参照してほしい。<br />
<br />
・<a href="http://www.createjs.com/#!/PreloadJS">PreloadJS</a><br />
画像などを読み込むのに使用する。<br />
<br />
上記のライブラリ群に<a href="http://www.createjs.com/#!/SoundJS">SoundJS</a>というライブラリを追加すると<a href="http://www.createjs.com/#!/CreateJS">CreateJS</a>というライブラリスイートになる。が、今回はサウンド周りは実装しないのでCreateJSではなく個別に参照している。<br />
<br />
<br />
<a href="http://my-clip-devdiary.blogspot.jp/2013/03/dhtml5_15.html">次回</a>は前述のサンプルコードを使って色々と解説していく。Yoo Matsuohttp://www.blogger.com/profile/09732121529027523391noreply@blogger.com0tag:blogger.com,1999:blog-6434745237355317591.post-69071201161272188342012-11-24T06:23:00.001+09:002012-11-24T06:23:49.365+09:00MSpecをReSharperと一緒に使うための設定 on Visual Studio 2010<a href="https://github.com/machine/machine.specifications">Machine Specification(MSpec)</a>はBDDスタイルのテストフレームワークで、BDDスタイルの開発をするなら至極便利。そのMSpecもReSharperと組み合わせてVisual Studio 2010上で簡易にテスト実行できるようにしておかないとありがたみが失せるので設定方法を紹介しておく。<br />
<br />
MSpecのReSharperに対する設定が完了すると、下図のようにVSのテストファイルの左側にポケモンボールのようなアイコンがテスト毎に表示されるようになるし、メニューからReSharper -> Unit Tests -> Run Unit Testsでテストを実行できるようにもなる。<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjkcuMGQa6SP3A4GmA3JIrFNxIoNLFztJp_3ed-F8OaRMkvktLf6ZIWPUIVroFywKnIv7DnvcXDjysxe1Wqyku_1sZ-7FEHHlQkuY5W-VespWFlHxK2XSD2d5u6dFITmDmLgwKRCZ5KgkA/s1600/mspec.jpg" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="213" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjkcuMGQa6SP3A4GmA3JIrFNxIoNLFztJp_3ed-F8OaRMkvktLf6ZIWPUIVroFywKnIv7DnvcXDjysxe1Wqyku_1sZ-7FEHHlQkuY5W-VespWFlHxK2XSD2d5u6dFITmDmLgwKRCZ5KgkA/s320/mspec.jpg" /></a></div><br />
実はNuGetからもMSpecの取得は可能なのでそちらから取得してもかまわない。今回は<a href="https://github.com/machine/machine.specifications">GitHubから取得したmachine/machine.specifications</a>を使用する。適当にcloneするなりzipファイルをダウンロードして展開してもらいたい。フォルダが展開できたら直下にあるbuild.cmd(Releaseビルドしたい場合はbuild-release.cmd)を実行しよう。実行するとDOS窓が起動し、うにょうにょとビルドが始まり、テストが実行され、しばらくすると完了する。するとBuildフォルダができているので、そこからDebugかReleaseを自分の好みにあわせて選択してもらいたい。使用するだけならばReleaseでよいだろう。<br />
<br />
で、そのDebugかReleaseフォルダ内にあるInstallReshaperRunner%%.batを実行する。%%の部分は自分のReShaperのVersionに置き換えてもらいたい。実行すると下記フォルダにMachine.Specifications.dll、Machine.Specifications.ReSharperRunner.6.0.dllとそれぞれpdbファイルがコピーされる。ここではv6.0のbatファイルを叩いたとする。<br />
<br />
%appdata%\JetBrains\ReSharper\v6.0\vs10.0\Plugins<br />
<br />
NuGetから取得した場合は前述の2つのDLLを上記のパスに自分でコピーすればよい。コピーする際にフォルダが無い場合は自分で作ろう。<br />
<br />
コピーできたらVSを再起動しよう。再起動後は上図のポケモンボールがテストファイルの左側に表示されているはずだ。<br />
<br />
一点注意が必要なのが、JetBrains配下にコピーしたMachine.Spedifications.DLLとVSで参照しているMachine.Spedifications.DLLのバージョンなどが違うとどうやらReSharperのほうでMSpecのプラグインを認識してくれないようなので同じファイルを使用するようにしよう。新しい環境を構築する際にMSpecをNuGetで取得したり、JetBrainsのほうへは古いファイルをコピーしたりなどを行ってしまいMSpecのテストが中々認識されなくて苦労したことがあるので注意してもらいたい。Yoo Matsuohttp://www.blogger.com/profile/09732121529027523391noreply@blogger.com0tag:blogger.com,1999:blog-6434745237355317591.post-89902349124974934952012-09-12T15:30:00.001+09:002012-09-12T15:30:22.705+09:00Cross-Origin Resource Sharing(CORS)で外部ドメインからリソースを取得する今回は<a href="http://www.w3.org/TR/cors/">Cross-Origin Resource Sharing(CORS)</a>で外部ドメインからリソースを取得するお話。実際にどんな用途があるかというとGoogle Analyticsなんかで埋め込むスクリプトは埋め込む先のページのドメイン(仮にwww.matsuo-software.comとする)から違うドメイン(GAではwww.google-analytics.com)へ情報を飛ばしたりする(注1)のでそういう場合に必要となる。<br />
<span style="color: red;">注1:GAのコードを調べたわけではないので実際には不明です。</span><br />
<br />
ではなぜJavascriptから外部ドメインへのアクセスにCORSが必要かというと、ブラウザにはセキュリティのために<a href="http://www.w3.org/Security/wiki/Same_Origin_Policy">Same Origin Policy</a>というのが適用されている。そのPolicyは外部ドメインからのリソースに訳の分からんことをされて悪意のあるデータをPOSTされたりするのを未然に防ぐためにある。で、その防止策の一環で外部ドメインへ向けてのJavascriptを使った単純なAjaxリクエストを行ってもブラウザレベルでアクセスを拒否されてしまうので、その制限をかいくぐるのにCORSの設定が必要となるわけだ。<br />
<br />
<br />
<b>サンプル</b><br />
例によってサンプルはNode.js。<a href="https://github.com/yooontheearth/cors-sample">https://github.com/yooontheearth/cors-sample</a>からコードは取得してもらいたい。アプリの設定まわりは<a href="http://my-clip-devdiary.blogspot.jp/2012/08/backbonejs.html">Backbone.jsでクライントスクリプトをガリガリ組むためのお手軽チュートリアル</a>を参照してもらいたい。<br />
<br />
<div class="command">NODE_PATH=/usr/local/lib/node_modules coffee app</div>でNode.jsを起動してからcorssample.htmlをブラウザで開き「Hi Mr. Yoo!」と表示されれば正常に動作している。<br />
<br />
CORSでGETをするのは特に何の問題もなくできるはずなので、今回は少しひっかかるところのあるPOSTを解説している。<br />
<br />
<br />
<b>解説</b><br />
今回のサンプルはいたってシンプル。見るべきところは13行目のretrieveNameと25行目のapp.post '/mrnobody'の部分のみ。<br />
<pre class="js" name="code">express = require 'express'
qs = require 'querystring'
app = express.createServer()
app.configure ->
app.use express.logger('tiny')
app.use express.bodyParser()
app.use express.methodOverride()
app.use app.router
app.use express.static(__dirname + '/')
app.use express.errorHandler({ dumpExceptions: true, showStack: true })
retrieveName = (req, callback) ->
name = req.body.name
if name?
callback name
else
data = ""
req.on "data", (chunk)-> data += chunk
req.on "end", ->
parsedData = qs.parse data
name = parsedData.name
callback name
app.post '/mrnobody', (req, res) ->
retrieveName req, (name) ->
res.header "Access-Control-Allow-Origin", '*'
res.header "Access-Control-Max-Age", '86400' # 1 day
res.header 'Access-Control-Allow-Methods', 'POST,OPTIONS'
res.header "Access-Control-Allow-Headers", "X-Requested-With"
res.send "Hi Mr. #{name}!"
app.listen 3000
console.log "Express server listening on port #{app.address().port} in #{app.settings.env} mode"
</pre>retrieveNameはそのままPOSTされたデータ(名前)を取得している。req.body.nameがNullだったら、という処理の理由は後述する。/mrnobodyのリクエストハンドラでCORSに必要なヘッダー情報を設定している。ブラウザはここで設定しているレスポンスのヘッダー情報をもとにCORSが許可されているかの判断を行う。Access-Control-Allow-Originに*を指定するとすべてのドメインからのアクセスを許可することになる。特定のドメインのみ許可したい場合は「http://www.matsuo-software.com」などを指定すればよい。またAccess-Control-Allow-Methods、Access-Control-Allow-Headersでそれぞれ許可するHTTPメソッド、ヘッダーを指定している。許可された以外のアクセスが行われた場合はアクセス拒否されるという寸法だ。それらの許可内容をAccess-Control-Max-Ageで指定した秒数だけブラウザの方でキャッシュされる。<br />
<br />
これだけでサーバサイドのCORSの設定は完了だ。後はクライアントから通常のAjaxリクエストと同じように処理を行えば正常動作するはずだ。リクエスト時にエラーが出る場合はjQuery.support.cors = true;やcrossDomain:trueの設定をサンプルのcorssample.htmlを参考に同様に処理してもらいたい。<br />
<br />
ただこのままで動くのはFirefoxやChromeだけのモダンなブラウザのみ。<br />
<br />
<br />
<b>INTERNET EXPLORER</b><br />
Yes, IE! そう、IE。Web開発者の鬼門、IE。CORSでも堂々のお邪魔虫っぷりを十二分に発揮してくれる。本当にいつもいつも鬱陶しいことこの上ない。<br />
<br />
IE10未満はXmlHttpRequestでのCORSを許可しておらず、CORSのためにはそれようのオブジェクトを使用する必要がある。XDomainRequestがそれにあたる。それなのでIEの場合はXDomainRequestを使用するようにしなければならない。<br />
<br />
というわけで下記ライブラリでXDomainRequestを使ったAjaxリクエストをさくっと行う。<br />
<a href="https://github.com/Malvolio/ie.xhr">Malvolio / ie.xhr</a><br />
<br />
呼び出しはこんな形になる。<br />
<pre class="js" name="code">$.ajax('http://localhost:3000/mrnobody', {
data: { name: 'Yoo' },
type: 'post',
xhr: window.IEXMLHttpRequest || jQuery.ajaxSettings.xhr,
crossDomain: true,
success: function (data, textStatus, jqXHR) {
$result.text(data);
},
error: function (jqXHR, textStatus, errorThrown) {
$result.text('Failed to load data.' + errorThrown);
}
});
</pre><br />
で、これで終わりかと思いきやさすがはIE。さらに鬼門が用意してあってデータをポストしてるのにContet-typeをtext/plainとしてリクエストする。そのため通常ならばapplication/x-www-urlencodedでリクエストされた場合はポスト内容の解析を行ってくれるサーバサイドフレームワークがtext/plainなため内容解析を行わず、前述のreq.body.nameがNullになるというような事態を惹起するのである。そこでretrieveNameで行っているようなリクエストから生のポストデータを取得してkey=valueのペアになっている値を独自に解析する必要があるというわけである。<br />
<br />
ここまでやって、やっとIEでもCORSができるようになった。IEでのCORSの詳しい制限事項は<a href="http://blogs.msdn.com/b/ieinternals/archive/2010/05/13/xdomainrequest-restrictions-limitations-and-workarounds.aspx">XDomainRequest - Restrictions, Limitations and Workarounds</a>を参照してもらいたい。<br />
Yoo Matsuohttp://www.blogger.com/profile/09732121529027523391noreply@blogger.com0tag:blogger.com,1999:blog-6434745237355317591.post-18693918918654032042012-08-31T12:32:00.002+09:002012-08-31T12:32:59.600+09:00ASP.NET MVCのキャッシュについてあれこれ一年半前ぐらいにASP.NET MVC 3のキャッシュ周りについて調べまくったので今更ながらまとめておく。一年以上前の話なのであやふやな部分も少々あるけれどそこはご愛嬌ということで話半分に読んで欲しい。<br />
<br />
で、キャッシュについて。キャッシュを有効活用できれば一番手っ取り早くサーバのスループットをあげることができるわけで、もちろんASP.NET MVC 3にもキャッシュ機能はある。<br />
<br />
<br />
<b>OutputCacheAttribute</b><br />
<a href="http://msdn.microsoft.com/en-us/library/system.web.mvc.outputcacheattribute(v=vs.108).aspx">OutputCacheAttribute</a>がそれにあたる。この属性をControllerかActionに付与することによってそれらの<b>出力結果</b>をキャッシュしてくれる。使用方法は下記のような感じ。<br />
<pre class='c#' name='code'>[OutputCache( Duration = 5, VaryByParam = "fish;angler", VaryByHeader = "X-Requested-With" )]
public class ChinuController : Controller{
}
</pre>Durationで何秒間キャッシュするのかを指定。VaryByParamやVaryByHeaderの指定でQueryStringのパラメータとかヘッダーごとにキャッシュを分けることが可能となっている。その他にもいくつか設定できる項目があるみたいだけれど使ったことがないのでよく分からない。<br />
<br />
で、この属性を使う上で一つ注意が必要なのが、さきほども強調表示してあるけれどこの属性は<b>出力結果</b>をキャッシュするようになっている。つまりこの属性を使用するとControllerのアクションは当たり前だけどViewのレンダリングもスキップされることになる。<br />
<br />
で、それの何が問題なの?ってのは次の場合。<br />
<br />
<br />
<b>ドーナツキャッシュ</b><br />
ログインユーザに「ようこそYooさん」のようなメッセージを表示する会員制のサイトがあったとする。そういう場合にOutputCacheだとディモールト(非常に)都合がよろしくない。というのもOutputCacheは出力結果をキャッシュするので、Yooさんの内容がキャッシュされた状態でSasukeさん(うちの猫)がそのページにアクセスすると「ようこそYooさん」と表示されてしまうのである。じゃぁそういう場合はどうするの?ってのでドーナツキャッシュという手法がある。下図のようにある一箇所だけを除外してキャッシュするのでドーナツキャッシュと呼ばれる。<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiIvbb0kfq5o_PcLcu_UfSh98e9ZB27X-w4yWAd35t9KyGNKu4p_ecGn_0EUk-JaeBwr0hRSkmMffOrXVHwfZcgy6M5jedsvHIeAxy6ywIiHSTKDjgzAdAzrMKVpEFIczpq2unB-Pz99Ko/s1600/Donut-cache.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="219" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiIvbb0kfq5o_PcLcu_UfSh98e9ZB27X-w4yWAd35t9KyGNKu4p_ecGn_0EUk-JaeBwr0hRSkmMffOrXVHwfZcgy6M5jedsvHIeAxy6ywIiHSTKDjgzAdAzrMKVpEFIczpq2unB-Pz99Ko/s320/Donut-cache.png" /></a></div><br />
その除外された箇所の描画用コールバックを用意しておくとそれが後から呼ばれるという寸法だ。で、詳細は下記を参照して欲しい。<br />
<a href="http://haacked.com/archive/2008/11/05/donut-caching-in-asp.net-mvc.aspx">Donut Caching in ASP.NET MVC</a><br />
<br />
うん、冒頭のUPDATEを読んでびっくりだと思うけど<b>この機能はASP.NET MVC 3には組み込まれてない</b>。キャッシュについて調べてたときも上記のサイトの説明を飛ばしてソースコードから入ったので、実装したら動かなくてちゃんと読み直したらびっくりしたね。F***って思ったよ。<br />
<br />
で、困ったなーということで自前のキャッシュフィルターを作ることにした。<br />
<br />
<br />
<b>ResultCache</b><br />
要するに出力結果をキャッシュされちゃうと柔軟性に欠けるというわけで、それならControllerのアクションの結果(ActionResult)だけをキャッシュして、そのキャッシュしといたActionResultをViewに渡して描画するようにすれば大体の問題は解決できるので下記のようなアクションフィルターを作ろうと思ったら既に作っている人(<a href="http://www.codeproject.com/Articles/33431/ASP-NET-MVC-Result-Cache#xx3871648xx">ASP.NET MVC Result Cache</a>)がいたのでそれを拝借した。<br />
<pre name='code' class='c#'>public class ResultCacheAttribute : ActionFilterAttribute
{
public ResultCacheAttribute()
{
Duration = 1200; // 20 mins
}
public string CacheKey{ get; private set; }
public CacheDependency Dependency { get; set; }
private CacheItemPriority _priority = CacheItemPriority.Default;
public CacheItemPriority Priority
{
get { return _priority; }
set { _priority = value; }
}
public int Duration{ get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var url = filterContext.HttpContext.Request.Url.PathAndQuery;
this.CacheKey = "ResultCache-" + url;
if (filterContext.HttpContext.Cache[this.CacheKey] != null)
{
filterContext.Result = (ActionResult)filterContext.HttpContext.Cache[this.CacheKey];
}
base.OnActionExecuting(filterContext);
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
filterContext.Controller.ViewData["CachedStamp"] = DateTime.Now;
filterContext.HttpContext.Cache.Add(this.CacheKey, filterContext.Result, Dependency, DateTime.Now.AddSeconds(Duration), System.Web.Caching.Cache.NoSlidingExpiration, Priority, null);
base.OnActionExecuted(filterContext);
}
}
</pre>リクエストされたURLをキャッシュのキーにしてActionResultを保持しておき、再度同じURLがリクエストされた場合はキャッシュからActionResultを取得してfilterContextのResultに渡すというだけの至極シンプルなフィルター。OnActionExecutingを見て不思議に思うかもしれないけれど、実はfilterContextのResultにActionResultが設定されるとControllerのアクションは呼び出されないのだ。<br />
<br />
<br />
というわけで、サーバリソース節約のためにもキャッシュはガンガン使っていくべきなので機会があれば快適なWebを実現するためにもドシドシ有効にしていただきたい。ただ一点注意が必要なのはなんでもかんでもキャッシュしていると予期せぬ出力結果になることがままあるのでキャッシュが有効になっている箇所の周りを開発する場合はその箇所だけをテストするのではなく、複数人からのリクエストを想定したテストなどを行わないと本番環境にリリースしてから痛い目にあうことがあるのでくれぐれも注意して欲しい。かく言う私もチヌかかり釣りMEGAのスマホ版をリリースしたさいに、ControllerのアクションでPC版とスマホ版のViewを動的に切り替えていたのでActionResultがキャッシュされてしまい予期せぬ動作になりとても焦った。ただその問題は前述のアクションフィルターにスマホ用のキャッシュキーを追加することによってサクッと事なきを得たので良かったけれども。Yoo Matsuohttp://www.blogger.com/profile/09732121529027523391noreply@blogger.com0