2023年3月18日土曜日

お名前.comのVPSでFTPを設定する話

お名前.comのVPSにFTPを設定するのに非常に苦労したので解説する。

経緯 
  • VPSのOSをUbuntu 20.04でクリーンインストール 
  • vsftpsをインストール 
  • ufwで20, 21ポートを開放 
  • クライアントからVPSへアクセスするとエラーになる 

 ufwのステータスは以下。 
# sudo ufw status
ポートが正しく開放されているのに接続できない。お名前.comのVPSのQ&Aにもどこにも情報が載っておらず 途方に暮れていたが、お名前.comの設定周りをしらみつぶしに見ていたらIPセキュリティの項目を発見。

2022年3月19日土曜日

AngularでUndo Redo 機能を実装する

AngularでUndo Redo機能を実装する、と題して複数のポストをこちらに投稿していたのだけれど、コード表示が上手に行えなかったのでZenn.devに以下投稿した。

2018年3月18日日曜日

グルグル回転している長方形(Rectangle)と線(Ray)の当たり判定

3Dで回転しているキューブとRayの当たり判定の背後にある数式がいまいちよく分からなかったのでJavaScriptのCanvasを使って図解してみた。2Dも3Dもテストする軸(Axis)の数が変わるだけで内容は一緒。コードは以下より取得してもらいたい。
JSFiddle
GitHub(上に同じ)

数式の背後にある考えは以下。
Ray Box Intersection

以下図解。
右下のRay OriginがRayの起点になっている。directionでRayの方向を指定している。
const rayCast = new Ray(origin=new Vector(400, 400), direction=new Vector(-0.8, convertYUp(1)));

左上のオレンジと黄色の線で囲われている領域がくるくる回転しているRectangleとなる。
const obb = new RotatingRectangle(pos=new Vector(150, 200), size=new Vector(80, 50));

右下のRay Originから左上のRectangleに伸びている線(Vector)はRayとRectangleの距離を算出するためのものなので、投げかけているRayとは別物なので注意してほしい。
const vectorFromOriginOfRayToCenterOfRect = obb.pos.subtract(rayCast.origin);

当たっているとき(左下に「YES!」と出る)


当たっていないとき(左下に「NO Intersection」と出る)


この数式の肝はSlab(オレンジと黄色の線)とRayがいつ(Time)交わるかなので、Timeを算出しないといけない。Timeを求める式は以下。
Time = (Axis上に投影されたRay OriginとRectangle間の距離 +/- Rectangleのサイズ) / Axis上に投影されたRay Directionの長さ
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;

Timeを求める元々の式は以下。
+/- Rectangleのサイズ = Axis上に投影されたRay OriginとRectangle間の距離 + Time * Axis上に投影されたRay Directionの長さ
Slabの位置を求める式からTimeを求める式に変換すれば前述の式になる。

Dot Product
投影されたほにゃららの長さを求めるにはDot Productを使用すればよい。


~まとめ~
同一軸上に投影すれば後は単純な長さの比較になるのでTimeが求まると理解するまで悶々と苦しんだけど、今はスッキリ。こんな投げやりな解説で伝わるかははなはだ心もとないけれど、誰かの理解の一助になれば幸い。分からないことありましたらコメントください。

2016年11月9日水曜日

async、await、CancellationTokenについて

職場でasync、await、CancellationTokenの実装方法についてまとめたので同じ内容をまとめておく。
※サンプルコードはWeb API 2を想定している。またDBアクセス部分はDapperを使用している。

この記事は下記リンクの要約なのでasync, awaitについての詳細は下記のリンクを参照してもらいたい。日本語翻訳されていないので英語。
Asynchronous Programming with async and await (C#)

使うべき場所
下記のような状況ではWebサーバのパフォーマンス向上が見込めるので積極的に使ったほうが良い。
  • DBでの処理(問い合わせ、実行など)
  • DB接続のオープン処理
  • ファイルIOなど
async, awaitの非同期処理で知っておくべきことは「awaitされている処理も呼び出し元のThreadと同一のThreadで処理される」ということにある。なぜならasync, awaitキーワードは呼び出されたメソッドを呼び出し元のThread上で必要に応じて適時処理するだけであり、新規にThreadを作成するわけではないからだ。
Threads(詳細説明)

つまりDBやファイルIOなどの別プロセスの結果待ちが発生する処理などでは非常に有用ではあるけれど、非同期処理になることを期待して単純に処理を二分しても実際には同一のThread上で二つの処理が適時行われるだけなので意味がないということ。ところでWebサーバのCPUリソースを多量に必要とするような処理の場合はasync, awaitとTask.Runを組み合わせると良い。Task.RunはThreadpoolへ処理を投げるのでパフォーマンス向上が見込める。

async, awaitのWeb APIサンプル
// 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;
}
  • A、リクエストのキャンセルを考慮してCancellationTokenを最終パラメータに指定する
    時間のかかる処理などはリクエストがキャンセルされた場合に処理を中止できるのが望ましいのでCancellationTokenを実装すると良い
  • B、非同期処理の開始
    AsyncのSuffixは非同期処理のネーミングコンベンションなので関数名につけるようしたほうが良い
  • C、Bが非同期処理されている間にCalculateSalaryが実行される
  • D、Bの非同期処理の結果待ち
// 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));
    }
}
  • A、DB接続のオープンを非同期で行う
  • B、Dapperで非同期問い合わせを行う
  • C、cancellationTokenを渡すためにCommandDefinitionが必要
async, awaitの動作解説図
下記リンク先にある解説図を見ると一連の動きが良く分かるので是非参照してもらいたい。
What Happens in an Async Method(詳細説明)

まとめ1
ここまででasync, awaitキーワードの使い方が動作原理を含めて理解できたと思う。使い方を誤らなければ簡易な記述で容易にパフォーマンス向上が見込めるので使える状況では積極的に使っていくべきだとは思うけれど、ともすると処理フローは複雑化しやすく、関連する箇所全体で非同期処理を念頭に置いた実装にしなければならないので使いどころは慎重に見極めないといけない。


非同期リクエストのクライアントからのキャンセル方法
こここらは非同期リクエストをクライアントからキャンセルする方法を解説する。
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();
    }
});
ページ遷移時に非同期リクエストが処理されている場合は明示的にリクエストをabortしないとキャンセルされない。また重たい処理などを実行する場合はキャンセル処理を実装しておくと不要な処理の実行を防ぐことによってパフォーマンス向上が見込めるので(エンドユーザがレスポンスを待たずにページ遷移してしまったとき用などに)積極的にキャンセル処理は実装したほうが良いだろう。

Web API 2のバグ対応
Web API 2のバグで非同期処理中にAbortするとOperationCanceledExceptionが発生するのでそれを抑制する必要がある。下記コードをGlobal.asax.csに追加してほしい。
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());
    });
}

まとめ2
Single Page Appなどで多量のAjaxリクエストを行っている最中にエンドユーザがページを離れてしまうとサーバサイドで行っている処理はすべて無意味になってしまうので、そのようなときのためにもキャンセル処理を組み込んでおくと良いだろう。

2016年9月15日木曜日

ユニットテストでlog4netのログを出力する方法

いまさらな内容だけど備忘録としてユニットテストでlog4netのログを出力する方法を記しておく。

static constructorで下記設定を行っておけばユニットテストの結果Windowの下にログが出力される。

[Subject("Test")]
public class TestSpec
{
        static TestSpec()
        {
                // MEMO : ユニットテストでlog4netを使用するための設定
                var consoleAppender = new log4net.Appender.ConsoleAppender { Layout = new SimpleLayout() };
                BasicConfigurator.Configure(consoleAppender);
        }

        // 以下省略
}

2015年9月2日水曜日

d3.jsでチャートを作る パイチャート

前回に引き続き今回もd3.jsでのチャートをまとめておく。今回はパイチャートについて。

前々回はこちら
d3.jsでチャートを作る ラインチャートとバーチャートを2つのy軸上に描画する
前回はこちら
d3.jsでチャートを作る ツールチップ



SFiddleはこちら

弧上に描画するためのarc()とパイ上に配置するためのpie()を用意する。それと配色用のcolor。
 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();

データをpie()にかませて配置用のデータへと変換する。
var container = svg.selectAll('g')
                      .data(pie(data))
                      .enter()
                      .append('g');

弧を描画する。
// 色をつけて弧を描画する
  container.append('path')
              .style("fill", function(d, i) { return color(i); })
              .attr('d', arc);

arc.centroid()を使用すると真ん中の座標が簡単に取得できる。
// 真ん中に文字を描画する
  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; });

弧の外側に文字を描画するには三角関数を使用して座標を算出する。またtext-anchorを角度によって変更している。
// 外側に文字を描画する
  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; });

d3.jsでチャートを作る ツールチップ

前回に引き続き今回もd3.jsでのチャートをまとめておく。今回はおもにツールチップ的なものについて。ツールチップ的な部分以外は前回のものとあまり大差ないため省略するので詳細は前回を参照してもらいたい。

前回はこちら
d3.jsでチャートを作る ラインチャートとバーチャートを2つのy軸上に描画する
次回はこちら
d3.jsでチャートを作る パイチャート



JSFiddleはこちら

X軸。今回はtimeを使用している。またrange()の指定は少しずらすために30からとなっている。
// 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);

ツールチップ。
// ツールチップ
  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' });

オーバーレイとツールチップの表示場所の指定。チャート上のどこにマウスがあっても表示したいのでオーバーレイはチャート全体を覆っている。mousemove()の冒頭でマウスの現在座標からその位置よりも左側に位置するデータをbisector()で取得している。それだけだとマウスが二つのデータ間にある場合、必ず左側のものが選ばれてしまうのでマウスとデータ間の距離を比較してツールチップを表示するべきデータを取得している。このx.invert()の使用方法はtime()でしか使用できないので注意。ordinal(), linear()の場合(linear()は違うのかも。調査していないので不明)はデータの幅とrange()、マウスの座標から当該のデータを探し出す必要がある(詳細は下記参考を参照してもらいたい)。
// オーバーレイ
  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));
      }
  }

参考
Inversion with ordinal scale

d3.jsでチャートを作る ラインチャートとバーチャートを2つのy軸上に描画する

d3.js周りを調査して、簡易なチャートをいくつか作ったので後々のためにまとめておく。今回は2つのy軸上にラインチャートとバーチャートをプロットする。

他のチャートはこちら
d3.jsでチャートを作る ツールチップ
d3.jsでチャートを作る パイチャート



JSFiddleに動作するサンプルをあげてあるので詳細はそちらを参考にしてもらいたいが、簡単に解説しておく。なおd3.jsはdata, enterの仕組みが理解できていないと一見しても意味不明なコードになるのでチュートリアル的なものをどこかで一読されることをお勧めする。

JSFiddleはこちら

データを用意する。
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 }
  ];

svg要素を用意する。
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 + ')');

X軸。ここでscaleにはordinal()を使用している。他に線形のlinear()と時系列用のtime()が使用できる。
// 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)";
        });

一つ目のY軸。domain()に0から売り上げと予想の最大値を指定している。またnice()でよさげにtickを調整してもらう。range()がheightから0になっているのはSVGの(0, 0)は左上をさすため。
// 一つ目の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('万円');

二つ目のY軸。右端にtransform.translateで移動する。また横線を引くためにd3が追加してくれたtickのg要素があるのでそこに横線用のline要素を追加して描画している。
// 二つ目の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('%');

ラインチャート。二つ目のY軸で高さを算出している。
// ラインチャート。売り上げと予想の割合を表示する
  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);

バーチャート。一つ目のY軸で高さを算出している。またX軸のscaleでrangeRoundPoints()を指定しているおかげでちょうど良い感じの座標が取得できている。ここらへんは色々いじってためしてもらいたい。
// バーチャート。売り上げと予想を表示する
  // 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);

2015年3月12日木曜日

libgdxで2つ目のゲームを作ったので宣伝

スマホ用のゲームを作りました。

・作ったゲーム
スーパーホッケー

Get it on Google Play


・感想(ゲーム開発全般)
昨年中にこのゲームはリリースする予定だったのが、やる気の低下やらDragon Age Inquisitionやら、さらに延期したMy締め切りである一月中という目標も低下したやる気ではかなわず、結局Android版ができたのは二月末。毎月簡単なゲームをポンポンリリースしていこうかと思っていたけど、いやー、大変すわ、これは。おもにやる気の部分で。結局このゲーム用に凝ったものを作る気持ちが出てこなかったのでスーパーバレーボールから画像を流用しまくってお茶を濁すことに。なんだろ、多分これ作っても見返りほとんど無いしなぁ、って思いがゲーム作りたいって気持ちより強くなっちゃってやる気がでてこなかったんだろうな。次回はもっと新しいことにチャレンジしよう。じゃないと面白くないね。

・感想(libgdx周り)
libgdxでの開発は大分なれた。次回はfontやらsoundあたりをしっかりと組み込んでみようかな、と思案中。

2015年2月19日木曜日

.NETプログラム実行時にDLLの参照エラーになったときようのツール

実行時にDLLが見つからずにエラーになるのはままあることで原因を特定するのはさほど難しくないけれど面倒な作業である。とくにライブラリで参照しているDLLと呼び出し元のプロジェクトで参照しているDLLに差が出ることは、Nugetでパッケージを取得することが増えた昨今はよく起こる。

ということが今の職場でよく起きてそれを調べる用のツールを作ったので公開する。

Missing Reference Finder
https://github.com/yooontheearth/missing-reference-finder



使い方はいたって簡単。Pathにbin等のフォルダを指定してcheckボタンを押下するだけ。指定のフォルダかGACにないものはMISSING扱いになる。

2014年9月28日日曜日

libgdxでスマホ用のゲームを作っての感想

今年に入ってからの仕事で調べた物を社内のWikiにまとめるという作業がとても多いのでこちらのブログに書くモチベーションがまったくあがらず放置しまくっていたけれど久しぶりに仕事とは離れたネタができたので更新。

前々から何らかのゲームを作りたいなとAndroid用にAndEngineをいじくってみたりHtml5用にEaselJSで遊んでみたりしてみたけどどうにもしっくりこない。そんなこんなで月日は流れたけれど今年の夏に一念発起して二ヶ月に一本ぐらいのペースでゲームをリリースしていこうと決めた。

で、八月の頭からゲーム開発を余暇を使ってしてきたんだけど先ほどつつがなく一本目のリリース(2014/11/06につつがなくApp Storeへのリリースも完了)が終わったので宣伝やら何やらも兼ねて楽しかったり大変だったりしたことをまとめたのが以下。今回は主に開発環境周りで。

・作ったゲーム
宣伝です。

Get it on Google Play



余談:「バレーボール ゲーム」で調べたらあんまり出てこなかったので油断して「Slime Volleyball」って名前にして作ってたらまんま同名の10万回ぐらいダウンロードされてるタイトルがあって内容も似てたのでとりあえず「Super Volleyball」にしてお茶を濁す。


・フレームワーク
libgdx
AndEngineとかEaselJSをいじってみて思ったんだけど同じ製品をAndroid用、Html用、iOS用とか作り分けるのはやっぱりめんどい。他プラットフォーム用に製品を作るのはロジック部分とかほぼほぼ転用できるので開発は思いのほか楽なんだけどそれでもメンテナンスを考えるとめちゃめんどい。リソースが自分ひとりで開発からグラフィック、リリースとかまで全部やるのにこれはかなりの重労働になるのでうまいこと他プラットフォーム用のコードを生成してくれるフレームワークがないかなぁと思っていて見つけたのがこれ。

Unityも考えたけどlibgdxはフリーだし必要そうな機能も満たしてたのでこちらへ。javaで開発できるのもいいね。C#でもできるっぽいけどC++とか今更触りたくないよ。

・libgdxの感想
さくさく開発できた。libgdxはビルドをGradleに任せてるんだけどそのお陰か私のメインIDEであるIntelliJ Ideaでのセットアップもすぐに完了し即コーディングに入れたのはベリーグッド。グッジョブだ。Box2dとかも初期設定で参照するようにできるし簡単だった。ただねGradleのことが良く分かってなかった(今現在もポワポワしてる)ので何らかのサードパーティのライブラリを追加するのは結構骨が折れたしこれからも骨が折れそう。

・コーディング周りの感想
scene2dという形でScreen、Actor、Actionとか画面の要素をうまいことまとめて使えるようにしてくれてるけどまだ自分の中でしっくりきてないしまだまだこなれてない。それなので模索しながら開発しつつ今現在もまだモヤモヤしたまま。けれど可能性は十分あると感じた。やっぱController的なレイヤーをしっかりと作ってView的な要素ともっと明確にわけたほうがスコアとか画面のステートとかがごちゃごちゃせずにすっきりするだろうなという感じ。

・フォント
これは今もってどうしたほうが良いのか謎。今回みたいにほとんどテキスト表示するものが無いものはあんまりこだわる必要もなくデフォルトのを使えばよいかなぁと思った。ちょっと凝った部分は画像を文字として使うことになるし。多言語化とか考えたら文字を画像にするのとかご法度だろうけど。

・ツール類
Texture Packerの存在を途中まで認識しておらず最初のころは自前で複数の画像を一枚の画像につめてその画像内の座標を指定して表示用に抜き取ったりという狂気の沙汰を続けていたのだけれどTexture Packerさんにご足労いただいてからはその自死一歩手前から引き返すことができた。とても便利。

さらにPhysics Body Editorなるめちゃんこ優秀なツールもあるのでBox2dを使う方は目を通されるとよいかと。

あとは2D Particle Editorとかもある。未使用なのでまだいまいち把握していないけれどエフェクトを派手にするためにも次のゲームでは使ってみようと考えている。

・マネタイズ
libgdxを選んだ理由にAdMobとかとの統合のチュートリアルがちゃんとあるというのも大きなウェートを占める。少し苦労したけれど無事にAdMobを実装し広告が出せるようになった。いずれはiOS版も出そうとは考えているけれどそっちはまだあまり調べていないので少し謎だけど一応問題なくできそう。

・グラフィック
Ink Scapeを使用。グラフィックの勉強をちゃんとしたこともないしPhotoShopで適当に画像をいじったりとかしかしたことなかったけれどInk Scapeは優秀なのでネットに転がってるチュートリアルを参考にいくつかサンプルを作ったりしてたら大分慣れてきてほぼほぼ満足いくキャラとか背景とかが作れた。まだ隠された機能がたくさんあるようなので精進あるのみ。非常に使いやすいのでお勧め。フリーだし。

・サウンド
なし。スマホゲームでサウンドはいらんかなと思ったのでばっさりと。サウンドやらBGMの作り方とかまったくの門外漢なので。今後も予定無し。

・iOS
Androidアプリは今までもいくつか作ったことがあったので勝手が分かってたけどiOSのほうは全然分からないので現在勉強中。アイコンとかでさえどっから手をつければよいのかがまったく分からん。iAdを組み込むならなおさら。
下記に追記。


まとめ
専門学校以来10数年ぶりのゲーム作成で面白かった。libgdxはさくさく作れるので良い。動作テストもDesktopのプロジェクトを含めておけばPCですぐに確認できるし。これがAndroidのEmulatorだけだったら遅すぎて発狂すると思う。



追記 2014/11/07
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してから一週間ということを考えれば早いほうなのかな、と。

いずれAdMobの組み込み方とか、iOS版のリリース方法とかまとめたいと考えているけれどとりあえず年内にもう一本簡単なゲームをリリースしたいのでそれが終わってから気が向いたら書くかも。

2014年4月10日木曜日

html5のcanvasにレーダーチャートを描画する

レーダーチャートの話が仕事中に出て自前で作るかどこかのコンポーネントを購入するかという話になり結局購入することになったのだけれども、デモ用にちゃちゃっとレーダーチャートを実装したのがあるので公開しておく。

デモはこちら

実装周り
// 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();
drawRader()の冒頭でviewModel.points()と取得しているのはKnockoutjs用のViewModelが内部的に保持しているObservableArrayだ(Knockoutjsを知らない人は配列のようなものと考えてもらって構わない)。描画の処理は単純で配列のアイテム数で360度を割ってその角度ごとに軸線を描画し、前後の軸線上の点と点を結んでいくだけ。

2014年1月4日土曜日

html5のcanvasに描画した曲線をヒットテストする

昨年はWPFを使ってGUIをグリグリ動かすアプリ開発にどっぷりはまっていたので今回は久しぶりにJavaScriptをいじりたくなり表題のものをガリガリ作った。

デモはこちら
githubはこちら(デモと差は皆無)

赤い四角が始点と終点だ。オレンジの四角が制御点になる。すべてdraggableなので任意に動かしてもらいたい。線上の点は曲線を直線に分割している点だ。曲線をクリックすると緑色になる。

曲線描画 on canvas
html5のcanvas上で曲線を描画するのは至極簡単だ。context.bezierCurveToまたはcontext.quadraticCurveToを呼び出せば良い。前者は制御点が2つのcubic bezierで後者は名称そのままの制御点1つのquadratic bezierだ。今回のデモではcubic bezierのほうを使用している。

曲線ヒットテスト on canvas
しかしあたり判定になると手段が提供されていないので自前でやる必要がある。以下任意のクリックした点が曲線上に位置しているかを計算する方法である。

1、曲線上の任意の点の接線の傾きを計算し曲線上の近傍点の接線の傾きと比較する
2、1の差が許容範囲内ならば2点は直線に近いと判断する
3、1の差が許容範囲外ならば2点間は曲がっているので2点間の中央で分割し再度1の判定を再帰的に行う
4、1~3までを繰り返し曲線を点で分割する
5、4で得られた点群とクリックされた点が直線上に位置するかを計算する
6、5で直線上に存在すれば曲線がクリックされたとする
7、5で直線上に存在しないならば曲線はクリックされていないとする

上記の計算に必要なものを解説していく。

曲線上の任意の点の計算方法
Cubic bezier curveの式を使うと簡単に任意の点を取得できる。Cubic bezier curveの式は以下。
b(t) = p0*(1-t)^3 + p1*3t(1-t)^2 + p2*3(1-t)t^2 + p3*t^3
p0は始点、p3が終点、p1、p2はそれぞれ制御点の1と2だ。これに時間であるtを0~1の間で指定すると曲線上の任意の点が取得できる。

曲線上の任意の点の接線の傾きの計算方法
これもCubic bezier curveの式の導関数を使えば簡単に計算できる。式は以下。
b'(t) = 3(1-t)^2(p1-p0) + 6(1-t)t(p2-p1) + 3t^2(p3-p2)
p0~p3とtの説明は前述したものと同じだ。

上2つの式はWikipediaを見てもらったほうが分かりやすいと思う。

任意の点が2点間の線上に存在するかの計算方法
任意の点Pが線AB上に存在するならば距離AB=距離AP+距離PBが成り立つ。

コード解説
ここから実際にいくつかコードを見ていこう。

まずは曲線上の任意の点を取得するコード。pointsは始点、制御点1、制御点2、終点の配列だ。
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);
};

ついで曲線上の任意の点の接線の傾きを取得するコード。ここもpointsは始点、制御点1、制御点2、終点の配列だ。
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;
};

曲線を直線に分割していくコード。mDiffやdDiffのしきい値をどこに設定するかで分割する大きさが変わってくるので上手いこと調整してもらいたい。
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));
};

任意の点が2点間の線上に存在するかをチェックするコード。これも許容値を変更することで当たり判定を厳しくしたりゆるくしたりできるので上手いこと調整してもらいたい。
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;
};

ざーっと解説してきたけれどCubic Bezierの公式さえ分かっていればさほど難しくないと思う。フレームワークから提供される曲線のあたり判定などは多量の曲線が存在する場合にボトルネックになったりもするので自前で計算できるよう公式を理解しておいても損はないだろう。処理実行の最適化などはまったく考慮していないので使用メモリだったり処理時間などを勘案して計算処理をキャッシュするなどは実際のコードベースでは行ったほうがよいと思われる。

2013年8月8日木曜日

UbuntuにGitをセットアップしてWindowsから接続する

自前のGitサーバが欲しくなり設定したのでその方法をまとめる。今回の環境は以下。

・Gitサーバ
Ubuntu 12.04.2 LTS

・Windowsクライアント
Windows 7 64bit


Ubuntu認証用のSSHキーを用意する
WindowsでSSHキーを用意するにはPuTTYgenを使ったりする方法もあるけれど、git bashからもさくりと作れるので今回はそちらで。git bashをまだインストールしていない場合はGit for Windowsをインストールしよう。

インストールできたら「Git Bash」があるのでそちらを叩いて起動する。初期作業位置はc:\Users\your nameかc:\Users\your name\Documentsなどになっているはずである。さっそくSSHキーを下記のコマンドから生成する。
※your nameは自分のユーザ名で置き換えてもらいたい。

$ ssh-keygen

そうすると以下の内容が出力されるので順々に必要な項目をタイプする。
Generating public/private rsa key pair.
Enter file in which to save the key (/c/users/your name/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Vera vera vera vera ....

/c/users/your name/.ssh/id_rsaの場所はgit sshから参照されるので名前と場所を変更しないように。エンターキーでpassphraseの入力に移ろう。passphraseは入力しなくてもかまわないがセキュリティの観点から入力するのが望ましい。

さて、c:\Users\your name\.sshを参照するとid_rsaとid_rsa.pubができているはずだ。とりあえずWindowsのセットアップはここまでにしてGitサーバをたてる。


Gitサーバをたてる
サーバセットアップまでは以下をそのまま参考してもらいたい。
4.4 Git on the Server - Setting Up the Server

一応手順を訳すとこうなる。

1、サーバにGitをインストールする
$ apt-get install git

2、'git'ユーザを作り、そのユーザ用の.sshディレクトリを作る
$ sudo adduser git
$ su git
$ cd
$ mkdir .ssh

3、Windowsで作ったid_rsa.pubを/home/git/に持ってくる
コピーするなりFTPで渡すなりしてもらいたい。

4、(サーバに複数ユーザいる場合は他のユーザのホームディレクトリに作らないように注意!)gitユーザのhomeの.sshにauthorized_keysファイルを作る
$ cat id_rsa.pub >> /home/git/.ssh/authorized_keys

.sshとauthorized_keysの権限は以下のようにしておく必要があるので注意してほしい。
$ chmod 700 .ssh
$ chmod 600 .ssh/authorized_keys

authorized_keysを作った後はid_rsa.pubは不要なので削除する
$ rm id_rsa.pub

ちなみに複数ユーザを追加する場合はユーザ分のid_rsa.pubをどんどんauthorized_keysに追加していくだけでよい。

この時点でWindowsからgitサーバにSSHでログインできるはずなので、Windowsに戻ってGit Bashから以下のコマンドを試してもらいたい。
$ ssh git@your domain.com
※your domain.comは自分のドメイン名に変更してもらいたい。

ここでPermission deniedされる場合はSSHのキーがへぼっている可能性があるのでキー作成からやり直して再度authorized_keysにpublic key(id_rsa.pub)を追加してもらいたい。

5、Git用の作業ディレクトリを作る
それぞれのリポジトリ用に空のディレクトリを用意する必要がある。git initを--bareオプションで行う。
$ cd /opt/git
$ mkdir project.git
$ cd project.git
$ git --bare init
必要に応じてどんどんディレクトリを増やしていこう。

project.gitのオーナーがgitになっていることを確認しよう。もしもなっていない場合は以下コマンドで変更しておこう。
$ chown -R git:git project.git

いざcommit
さてこれで準備がととのったので再度WindowsにもどりGit Bashから下記のコマンドを実行してみよう。
$ cd myproject
$ git init
$ git add .
$ git commit -m 'initial commit'
$ git remote add origin git@your domain.com:/opt/git/project.git
$ git push origin master

これで他の端末にcloneすることもできるようになったので、他の端末から下記のコマンドを実行してみよう。
$ git clone git@your domain.com:/opt/git/project.git
$ cd project
$ vim README
$ git commit -am 'fix for the README file'
$ git push origin master


まとめ
SSH周りにはまるとアホみたいに時間がかかるけれど、それさえなければ一時間もかからずにGitサーバを用意してリポジトリをどんどん追加できるはずだ。SSH周りの認証がうまくいかない場合は下手に色々といじくるよりも手順を最初からやり直したほうが早いことが往々にしてあるのでそれを試してみるとよいだろう。

2013年7月20日土曜日

ホワイトIT企業で働くとこんな感じ

ブラック企業の代名詞としてよくあがるIT業界。IT土方(土方の方々に失礼なのではと思わないでもない)なんていわれる職場もあるようでひどいところは本当にひどいようだけれども、今の現場に入って数ヶ月、超絶ホワイトっぷりがすごいので書いてみる。


概要
今の案件にはお客さん(A社)の自社開発の企業向けソリューション(α)にGUI開発者として参画している。で、このプロジェクト、営業、開発、保守、運用なんかも含めるとざっと80人規模になる。もしかしたら知らない場所にも人がいるかもしれないけれど、同じフロアでαに確実に専任で携わっていると知れている人だけで80人ぐらいいる。α自体はVer1がすでにリリースされていて複数の大企業に納入されて元気に(?かどうか知らないけど)稼動している状態。


体制説明
色々とトライアンドエラーを繰り返しながら柔軟に体制を変えているようなので以前はまた違ったようだけれども、今は大体こんな感じで回しているようだ。内実はよく分からないので数字は適当。

・現行開発/サーバサイド(3~5人ぐらい)
Ver2の開発でサーバとかデータ周りをいじくり回してる人達。

・現行開発/フロントエンド(3~5人ぐらい)
Ver2の開発でGUIとかクライアントアプリ周りをいじくり回してる人達。

・プロト開発/サーバサイド(5人ぐらい)
Ver3に向けてのプロト開発でサーバとかデータ周りをいじくり回してる人達。

・プロト開発/フロントエンド(10人ぐらい)
Ver3に向けてのプロト開発でGUIとかクライアントアプリ周りをいじくり回してる人達。GUIの凝ったアプリなのでここに開発リソースがたくさん割かれてる。私がいるのもここ。

・プロト設計(5人ぐらい)
Ver3に向けての設計を進める人達。

・営業/コンサル(10人ぐらい)
αを実際のお客さん達に売り込みに行く人達。「αを使ったらこんなに効率よくなりますよー」とか甘言を弄する人々。日本全国飛び回ってるっぽい。

・保守運用(5~7人ぐらい)
良く分からんけど多分保守とかやってる人達だと思う。

・テスト(5~7人ぐらい)
テストケースとか作ってるっぽい人達。これも良く分からん。

ここまではパートナー企業から来てる人達の内訳。それプラス以下。

・A社社員(20人ぐらい)
パートナーの取りまとめを行う人達。設計の最終決定や、スケジュール管理、アーキテクチャ選定、価格交渉などなど最終的な責任を持つ人達。

なんか人数合わないけどざっとこんぐらい居る。


なぜホワイトなのか
で、ここからはいかにホワイトなのかを語る。

・優秀
基本的にみんな優秀。A社社員は言わずもがな、パートナー企業から来てる人達も優秀。とくにコアメンバーはかなりスキルが高いと思われる。で、まれにあまり仕事のできない人が紛れ込んでくることがあるけれども、そういう人達はさくっと契約が打ち切られるのでいつの間にかいなくなっている。

・体制とか運用がしっかりしている
BTS(Bug Tracking System)の活用なんて今じゃどこでもやっていることだけど、A社ほど有効活用できているところは少ない。チケットの意味付け(課題、TODO、実装、レビュー、etc)はしっかりしているし、チケットとバージョンコントロールもちゃんと紐付けがされている(とくにレビューとの紐付けがかなり良くできている)。何よりWikiの充実っぷりがすごく、そのWikiも日々更新され、Wiki上でのコメントのやりとりも活発。意識の高い人が多いのだと思う。

・レビューがすごい
コードレビューが頻繁に行われている。コミットされたコードに対してはA社社員に限らず、パートナーの開発者もガシガシ行って指摘チケットが飛び交っているし、そこで見つけた事例が新しければすぐにWiki化されるというなんとも良くできた仕組み。ここでやべーこいつ、と思われた人は契約カットの憂き目に会うと思われる。またコードだけではなく設計やらテストやらもガンガンレビューされている模様。

・体調不良に優しい
A社社員は言わずもがな、パートナーでも体調不良で長期で休んでも契約がきられない(とりあえず家庭の事情で長期で休んでいる人がいるけれど契約はきられていない模様)。A社社員に療養が必要となれば積極的に休暇を薦めてフォローしあう体制をすぐさま作ってる感じ。よく知らんけど。

・スケジュールがきつくない
9-5時勤務で余裕で回る感じ。なのでいつも定時あがり。ただ、実はA社社員はかなり大変なようで終電間際まで作業している人もちらほらいるらしい。それでも休暇なんかはとりやすいらしく、みなさんガシガシ取得している。パートナーも設計の人達とかは大変そうだ。

・ピリピリしてない
納期のある請負案件だったりすると期日が迫ってくるにつれピリピリしはじめて、嫌な職場になると怒鳴るような人員がいるところもあるけれど、今の現場ではみんなある種のんびりとしている。もちろん自社開発のプロトとはいえスケジュールはあるのでそれに向かって進めているのだけれども、よりよいものを作ろう的なスタンスなのでスケジュールは柔軟である。多分そのせいもあってか余裕があるように見受けられる。これだけでもこの現場のホワイトさが分かろうというもの。

・嫌らしい人がいない
性格の悪い人がいない。A社の社員はみんなシュッとしてて紳士的である。A社自体も大企業でありA社の社風的なものもあると思うけど、とりあえず一緒に仕事する人達(A社社員に限らず)は感じの良い人達が多い。

・技術的に結構チャレンジング
新しい技術を上手いこと使ってよいもの作っていこうぜ!的なスタンスだし、技術的にかなり高度なことをやっていたりもするので知識欲を満たせる。


とまぁこれぞザ・ホワイト企業だな、と数ヶ月働いてみての実感。飽き性なので小金がたまったら働くのをやめて、しばらく自分のやりたい開発とか調べ物をしてからまた請負仕事をする、みたいな感じで色々な職場を見てきたけれど、A社ほどのホワイトっぷりはいまだかつて見たことが無いレベル。ブラックと叫ばれて久しいIT業界だけど、いるところにはホワイトな企業も生息しているというお話。

2013年3月22日金曜日

磯野ー!2Dゲーム開発しようぜー! Android編 その4

長々と続いてきたカツオと中島のゲーム開発も今回で終了だ。今回はTextとEntityModifier周りを解説する。前回までは以下。

磯野ー!2Dゲーム開発しようぜー! HTML5編 その1
磯野ー!2Dゲーム開発しようぜー! HTML5編 その2
磯野ー!2Dゲーム開発しようぜー! HTML5編 その3
磯野ー!2Dゲーム開発しようぜー! Android編 その1
磯野ー!2Dゲーム開発しようぜー! Android編 その2
磯野ー!2Dゲーム開発しようぜー! Android編 その3


Text
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);
    }
文字を表示するのは一見簡単そうなのだけど柔軟さに欠けるので中々に面倒くさい。第一にフォント読み込み時に色を指定するので文字色を後から任意に変更できない。また色毎にフォントを読み込むのは難儀だ。それなので白色フォントで読み込んだ後にEntityの色変更メソッドを通して任意の色へと設定している。第二にフォント読み込み時の領域確保に指定する値の最適値が判然としない。文字の大きさと文字数から判断するしかないのだけど直感的でないので良く分からない。多量の文字を表示するなら大きくなければならないし、かといって無駄に領域を確保するとメモリを無駄に圧迫するのでやっかいな部分だ。最後にこれはバグなのだけど、コンストラクタで指定した文字列よりも長い文字列をsetTextで後から設定すると例外が発生する。そういう状況が起こりえる場合の回避策はコンストラクタで長い文字列を渡しておくしかないだろう。

フォントを指定して読み込む方法は下記を参照してほしい。
src/org/andengine/examples/CustomFontExample.java


EntityModifier
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);
EntityModifier周りだけを抜き取ると余計にわけが分からなくなりそうなのでそこらへんをごっそりと持ってきているけれど、注目して欲しいのは10行目から。SequenceEntityModifierを指定すると順番にEntityの値を変更していくことができる。12行目から27行目までは開始と終了のイベントハンドラーでそれ以降が値の変更を記述している。Tweenjsと同様の働きをする。同時に2つ以上の値を変更したい場合は下記のようにParallelEntityModifierを使用する。
// 省略
new RotationByModifier(0.5f, 90),
new ParallelEntityModifier(
 new ScaleModifier(3, 0.5f, 5),
 new RotationByModifier(3, 90)
),
new RotationByModifier(0.5f, 90),
// 省略
またTweenjsと同様にどのように値を変更させるかEase関数を使って指定ができる。Ease関数の種類は下記を参照してほしい。
AndEngine / src / org / andengine / util / modifier / ease /
実際に挙動を見るのが一番早いのでAndEngineExamplesをローカルに設定してEase関数周りのサンプルを動作させてみるのが一番良いだろう。その際にはExtensionsも必要なので忘れずに取得しよう。詳しくはAndEngineのReadMeを参照してほしい。

EntityModifierの開始、終了のイベントハンドラーでUI要素をいじくる場合は注意してほしい。左記2つの関数はワーカスレッドから呼び出されるのでUI要素をいじくるとアプリがクラッシュする。それなので17行目のようにmEngine.runOnUpdateThreadでUIスレッドを呼び出して処理しよう。

EntityModifierのざっくりとした説明は以上になるけれど、今回EntityModifierをいじくりまわしていていくつか不明な点があった。1つ目は、EnityModifierを使いまわせないかとEnityModifier.reset()をいじくりまわしてみたけれど上手くいかなかったこと。2つ目は、unregisterEntityModifierの使いどころ。Entityが不要なEntityModifierをいつまでも参照しているのが嫌だったので一応呼んでいるけれど、AndEngineのサンプルコードを参考にしてもunregisterEntityModifierを呼んでいるものがなかった。AndEngineのコードを追っかけていけば分かることなのだろうけれど現状はどうするのが一番良いのかは不明。


これでAndEngineの解説を終わりとする。AndEngineの基本的な使い方がどういったものか理解できたかと思う。今回は実装しなかったけれどもちろんゲームのメインループにあたる機能や、あたり判定の便利機能などなどゲーム開発に必要な機能は大体そろっている。そういった今回解説しなかった機能はAndEngineExamplesで詳説されているのでそちらを参照してほしい。

2013年3月21日木曜日

磯野ー!2Dゲーム開発しようぜー! Android編 その3

今回は画像周りを解説する。前回までは以下。

磯野ー!2Dゲーム開発しようぜー! HTML5編 その1
磯野ー!2Dゲーム開発しようぜー! HTML5編 その2
磯野ー!2Dゲーム開発しようぜー! HTML5編 その3
磯野ー!2Dゲーム開発しようぜー! Android編 その1
磯野ー!2Dゲーム開発しようぜー! Android編 その2


BitmapTextureAtlas
AndEngineで画像を扱う場合はまずBitmapTextureAtlasでサイズを指定し画像のための領域を作り、そこに実際の画像をどしどし読み込む形になる。コードにすると以下のような形。
    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();
    }
BitmapTextureAtlasのコンストラクタで指定する数値は2のべき乗である必要がある。つまり2,4,8,16,32,64,128,256...というわけだ。上記のコードが実行されると下図のような様子になる。



BlueShip.pngは600*100なので幅512では収まらないので幅1024になっているし、WalkingSquare.pngとあわせての必要な高さが230になっているので高さは256となっている。そのため画像のように無為な領域が多量に発生してしまっている。無駄なメモリ使用を減らすためにも画像はすき間無くつめられるようなサイズを意識して用意するとよいだろう。ちなみにBitmapTextureAtlasにあまりにも大きい領域を指定するとアプリがクラッシュすることもあるので注意しよう。4096などを指定すると危うそうだ。


TiledTextureRegion
BitmapTextureAtlasのほかにもここではTiledTextureRegionを使ってスプライトシートアニメーションを読み込むことを明示している。下記のコードで船のアニメーションが簡単に実現できる。
final AnimatedSprite shipAnimation = new AnimatedSprite(10, 10, _shipRegion, this.getVertexBufferObjectManager());
shipAnimation.animate(50); //アニメーション速度
_scene.attachChild(shipAnimation);
また下記のようにTiledTextureRegionから指定のフレームを使ってスプライトオブジェクトを作ることも可能。
final Sprite ship = new Sprite(50, 50, _shipRegion.getTextureRegion(0), this.getVertexBufferObjectManager());


TextureRegion
単純に画像をただ表示するだけならば下記のようにすればよい。
    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);
    }

BitmapTextureAtlas周りが理解できればAndEngineの画像周りはしごくシンプルだ。次回はEntityModifier周りを解説してこのシリーズも終了となる。

2013年3月19日火曜日

磯野ー!2Dゲーム開発しようぜー! Android編 その2

今回からはAndEngineを使ってのゲーム開発を解説する。前回までは以下。

磯野ー!2Dゲーム開発しようぜー! HTML5編 その1
磯野ー!2Dゲーム開発しようぜー! HTML5編 その2
磯野ー!2Dゲーム開発しようぜー! HTML5編 その3
磯野ー!2Dゲーム開発しようぜー! Android編 その1


サンプルコード
今回使用するコードはhttps://github.com/yooontheearth/andengine-sampleから取得できる。


概要
AndEngineはOpenGLベースのゲームライブラリで現在はバージョンGLES2となっている。AndEngine周りのチュートリアルを探すと大体前バージョンであるGLES1が見つかると思うけれど、GLES2になって仕様が若干変わっているので注意してもらいたい。AndEngine GLES2のコードは以下。
https://github.com/nicolasgramlich/AndEngine


セットアップ
Eclipseのセットアップ方法はよくあるので今回はIntellij IDEAでのセットアップ方法を解説する。今回使用するIDEAのバージョンは12.04のCEだ。


アプリケーションの新規作成の図。Project SDKを4.2としているけれど、AndEngine GLES2は2.2以降で動作する(らしい)。ちなみにちぬあたりMEGAはAndroid 2.3の実機にインストールしたところ問題なく動作した。


AndEngineを追加する準備の図。File→Project Structure→Modulesを選択→上部の緑の十字をクリック→New Moduleを選択する。


AndEngineを追加するの図。Library Moduleを選択→Content rootにダウンロードしたAndEngineのフォルダを選択して(※Package nameは「org.andengine」と入力するように)Finishをクリックする。


AndEngineを確認の図。Module SDKは4.2のこと。AndEngineは3.0以降のConstantをいくつか参照しているので最新のSDKを参照しないとコンパイルエラーになる。


AndEngineを参照するの図。andengine sampleを選択→Dependenciesタブ→右の緑の十字をクリック→Module Dependencyを選択→AndEngineを選択する。AndEngineの参照ができたらExportにチェックしておこう。


では早速コードを見ていこう。

src/com.matsuosoftware.game.andengine_sample/MainActivity.java
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;
    }
}
上記はもっともシンプルな構成のActivityだ。ちなみに上記を実行しても何も表示されない。AndEngineはSimpleBaseGameActivityから派生したActivityひとつだけで動作する。開発者はSceneオブジェクトにぺたぺたと画像を貼り付けつつ、this.mEngine.registerUpdateHandler(Obj)で登録したObjのonUpdateが定期的に呼ばれるのでそこで当たり判定などを行いつつ開発をすすめていく。ただ基本は上記の3つのメソッドで、エンジンを作り、リソースを読み込み、シーンを使って描画するという流れだ。


次回以降は上記のコードにドシドシ肉付けを行っていく。

2013年3月17日日曜日

磯野ー!2Dゲーム開発しようぜー! Android編 その1

前回まではHTML5での2dゲーム開発を解説したが今回からはAndroidでのゲーム開発を解説する。「次回はAndEngineを」と述べたけれど気が変わったのでAndroid編初回の今回は前回までに作ったHTML5ベースのゲームをWebViewでラッピングし、さもAndroidネイティブで作られたゲームかのように動作させる方法を解説する。前回までのシリーズは以下。

磯野ー!2Dゲーム開発しようぜー! HTML5編 その1
磯野ー!2Dゲーム開発しようぜー! HTML5編 その2
磯野ー!2Dゲーム開発しようぜー! HTML5編 その3


ゲーム概要

Androidローカル(assetsフォルダ)に配置したindex.html、画像、JavaScriptファイルなどをWebViewに読み込んでいる。ゲームの内容は前回までとまったく同じ。
※index.html等々はローカルから読み込んでいるけれど、jQuery、EaselJSなどのライブラリは前回までのコードそのままにCDN(Content Delivery Network)を利用しているのでインターネット経由で取得している。オフラインでも動作可能にするにはこれらの外部ライブラリもローカルに配置しそちらを参照するようにすればよい。


サンプルコード
今回のWebViewのサンプルコードはhttps://github.com/yooontheearth/html5-game-webview-sampleから取得できる。ではさっくりと見ていこう。


src/com.matsuosoftware.game.webview_sample/MainActivity
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");
    }
}
いたってシンプルである。が、順に見ていこう。
・6、7行目、フルスクリーンで表示するためのフラグを設定する
・11行目、JavaScriptを有効にする
・12行目、assetsフォルダからファイルを読み込む。file:///android_assetはassetsフォルダへの特別なファイルパス

ここまででゲームはさもAndroidネイティブアプリかのように起動するのだけれども、見てくれをよくするためにもindex.htmlのbodyタグに以下のスタイル設定が必要となる。

<body style="margin: 0; padding: 0">

とまぁ、これでブラウザで動作させたときと同様のHTML5のゲームがAndroidでラッピングできたわけだけれども、WebViewにはこれだけではなくAndroidからJavaScriptを操作したり値を渡したりその逆もまた可能になっているのでJavaScript上の値をAndroid上で永続化したり復元したりもできるようになっている。詳しくは以下を参照してもらいたい。

・AndroidとJavaScriptとのやりとりは以下のリンクからaddJavascriptInterface()周りを参照してもらいたい。
Building Web Apps in WebView

・JavaScriptのエラーハンドルなどは以下のリンクからsetWebChromeClient、setWebViewClient周りを参照してもらいたい。
WebView


結論
HTML5のコードに手を加えずともWebViewでラッピングしてそのままAndroidアプリとしてリリースすることもできるし、Android用の特別な処理を実装したい場合はAndroidと双方向でやりとりできることも理解できたかと思う。

ただ、HTML5をそのまま使えるというわけで各プラットフォームごとに開発を行わなくてもよいかも!?と明るい展望を一瞬みせてくれたWebViewではあったけれど、その実、実行速度はひっじょうに遅い!!!通常だと50または60FPS平均のものが10FPS以下ほどへと速度が落ちてしまうので大体のゲームはゲームとして成り立たなくなると思われる。通常のアプリ類であればPhoneGapやTitaniumがプラットフォームフリーへの一応の答えになると思うけれど、ゲームはどうなんだろう。以前調べたときはあまりよさげなのはなかった。


というわけでAndroidの能力を搾り出すためにも次回からはAndEngineの使い方を解説していく。

2013年3月16日土曜日

磯野ー!2Dゲーム開発しようぜー! HTML5編 その3

磯野と中島のゲーム開発も今回で3回目。HTML5編は今回で終了だ。ちなみに今回もめがねと坊主頭の出番はない。前回までの分は以下。

磯野ー!2Dゲーム開発しようぜー! HTML5編 その1
磯野ー!2Dゲーム開発しようぜー! HTML5編 その2

coffeescripts/platform.coffee
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
・4行目、画像からBitmapオブジェクトを生成する
・5行目、setTransformは移動、スケール、回転、傾き、中心点をまとめて指定できる便利メソッド。ここではスケールまでしか指定していない
・6行目、Zオーダーという概念はなく後からstageに追加されたものほど上にくる。Zオーダーの指定がしたい場合は自分で実装しよう
・9行目、テキスト生成はこのとおり
・12行目、alignの指定などもおこなえる
・16行目、スプライトシートの設定を行う。画像の指定は複数可能。そちらの詳細はSpriteSheet Classを参照してほしい
・21行目、アクションの中心点を設定する。意味が不明な場合はXY両方の値を0に変更して実行してみよう。宇宙船の回転のアニメーションの部分で意味が理解できるはずだ
・23行目、BitmapAnimationで使用するアニメーションの設定を行う。ここの設定方法は複数あるのでこちらもSpriteSheet Classを参照してほしい
・28行目、BitmapAnimationオブジェクトを生成する。16行目で行ったスプライトシートのアニメーション設定をこのBitmapAnimationオブジェクトを通して使用する
・31行目、スプライトシートから指定のフレームを抜き取ってBitmapオブジェクトを生成する
・34行目、クリックイベントをハンドルする。これもEaselJSの大きな功績のひとつだ
・37行目、指定のアニメーションを実行する
・40行目、TweenJSを使用してオブジェクトアニメーションを設定する。指定のオブジェクトのプロパティ値を指定の時間と動かし方で変更できる
・46行目、TweenJSのアニメーションの終わりにコールバックを設定する。そのコールバックで宇宙船のアニメーションをstageからはずしたり宇宙船のアニメーションを止めたりする
・77行目、画像をフリップさせる場合はscaleXまたはscaleYに-1を設定する
・87行目、Containerを活用するとオブジェクトのグルーピングも楽に行える
・98行目、Tickerの設定あれこれ。詳細はTicker Classを参照してほしい
・102行目、今回のサンプルではあたり判定やらキー操作でのプレイヤーキャラクターの動作などは実装しなかったけれどそういった実装はtick()で行うようになる
・106行目、FPS(Frame Per Second)を表示する


ざーっと説明したけれども大体EaselJSを使ってのゲーム開発がどういうものか理解できただろうか。前回にも述べたけれどTicker(に登録したオブジェクトのtick())とStageという概念が基礎にあるだけでその上で行うゲーム開発は通常のゲーム開発とさほど変わらない。というわけでこれまでの解説を通してEaselJS、TweenJS、PreloadJSの便利さとか開発のしやすさが伝わったら幸いだ。


次回からはAndEngineの解説を行っていく。