プログラミング兼読書日記

プログラミングをしていて、はまってしまった事を中心に記事にしています。

Exel VBA 最低限のまとめ

この記事ではちょっとデータの整理をexcelでやらなければならないことがあったので、最低限VBAを用いて、これがあればデータを操作できるというものをまとめておきます。

まず、VBAについて他の言語と特に違うことは

  • プログラムの予約後、変数名のlowerCase、upperCaseの区別はない
  • 構文の書き方がちょっと古い
  • 配列の作成の仕方でインデックスの最大値を指定するため、c言語などと比べると1個多くかんじる

ということです。

以下、最低限必要なことは

  1. 変数の宣言、代入
  2. 数、文字列の型
  3. 数、文字列の四則演算、変換
  4. 比較演算子
  5. If、For構文
  6. 配列
  7. Excelのセルの値の参照と書き込み

1.変数の宣言、代入

宣言:Dim 変数名 As 変数型

代入:Dim 変数名=代入する値

宣言&代入:Dim 変数名 As 変数型: 変数名 = 代入する値

2.数、文字列の型

数について

整数32bit:Integer

整数64bit:Long

float:Single

double:Double

文字列はString型

3.数、文字列の四則演算、変換

数について

+、-、*、/は通常の四則演算

商は\、余りはMod、べき乗は^

文字列から数:Val関数

文字列について

+、&はともに文字列の結合

数から文字列:CStr関数

4.比較演算子

大小は通常の>、>=、<、<=が使える

等しい、等しくないはそれぞれ=、<>

5.If、For構文

If 条件式1 Then
    条件式1を満たした場合の処理
ElseIf 条件式2 Then
    条件式2を満たした場合の処理
Else
    条件式1と条件式2を満たさなかった場合の処理
End If
For カウンタ変数 = 初期値 To 最終値 (Step 毎回の増分)
    処理
Next

for文でbreakするにはExit For

Do While 条件式
    条件式を満たすまで繰り返す処理
Loop

While文でbreakするにはExit Do

6.配列

Dim 変数名(個数) As 変数型

Dim array(5) As Integerならarray(0)からarray(5)までの6個が使用可能

7.Excelのセルの値の参照と書き込み

セルの値以下で参照

WorkSheets("SheetName").Range("A1").Value

WorkSheets("SheetName").Cells(1,1).Value 'indexのはじめは1

Valueのところを他のプロパティ名で参照可能

WorkSheets("SheetName").Activateを呼べば、WorkSheetsの部分は省略可能

書き込みはふつうに代入すればよい

 

GoogleChartのChartEditorクラスの使い方

今回は、Google ChartのChartEditorクラスの使い方について書きたいと思います。このクラスを用いるとチャートの設定変更ダイアログを簡単に作成できます。

 

設定を変更後、ChartEditorクラスから設定済みChartWrapperクラスを取得して使えばいいのですが、ここで注意点があります。注意することは ChartEditorクラスのgetChartWrapperメソッドは当該クラスに設定したeventListenerの関数中でしか正常に機能しないこ とです。

 

まずは、簡単なサンプルです。チャートを編集ボタンを押すとチャートの設定を変更できます。

 

 

以下ソース。ポイントはChartWrapperクラスを毎回ChartEditorから取得するのではなく、globalオブジェクトとして保持していること。

<script type="text/javascript" src="http://www.d3js.org/d3.v3.min.js"></script>
  <script type="text/javascript" src="https://www.google.com/jsapi"></script>
  <div id="samplesimple">
    <button id="editbuttonsimple">チャートを編集</button>
    <div id="vis_divsimple" style="height: 400px; width: 600px;"></div>
  </div>
  <script type="text/javascript">
  var wrapper;
  var chartEditor;
  function initialize(){
    google.load('visualization', '1.0', {packages: ['charteditor'],callback:loadEditor});
    //チャートを編集ボタンを押したらダイアログを開く
    d3.select("#editbuttonsimple").on("click",function(){
         chartEditor.openDialog(wrapper, {});
    });
  
  }
  function loadEditor(){
    chartEditor = new google.visualization.ChartEditor();
    //データテーブル作成
    var chartdata=[["A",30],["B",20],["C",49],["D",60]];
    var dataTable = new google.visualization.DataTable();
    dataTable.addColumn('string', 'countory');
    dataTable.addColumn('number', 'population');
    dataTable.addRows(chartdata);
    wrapper = new google.visualization.ChartWrapper({
      'chartType':'LineChart',
      'dataTable':dataTable,
      'options': {'title': "population"}
    });
    google.visualization.events.addListener(chartEditor, 'ok', showdataCallByChartEditor);
    showdata();
  }
  //ChartEditorのeventListenerで呼ばれる場合と違う場合で分ける
  function showdataCallByChartEditor(){
    //wrapperを保存
    wrapper=chartEditor.getChartWrapper();
    wrapper.draw(document.getElementById('vis_divsimple'));
  }
  function showdata(){
    wrapper.draw(document.getElementById('vis_divsimple'));
  }
  initialize();
  </script>

次に表示するデータも変更できる場合です。

以下にサンプルを示します。セレクトボックスから誰に関するデータかを選択、及びチャートを編集ボタンを押すとチャートを編集できます。

欅坂46メンバー間の写真統計(11月、12月分)

写真を撮ったメンバー:
 

以下ソース。

<script type="text/javascript" src="http://www.d3js.org/d3.v3.min.js"></script>
  <script type="text/javascript" src="https://www.google.com/jsapi"></script>
  <div id="sample">
    <h2>欅坂46メンバー間の写真統計(11月、12月分)</h2>
    <button id="editbutton">チャートを編集</button>
    写真を撮ったメンバー:
    <select id="nameselect"></select>
    <div id="vis_div" style="height: 400px; width: 600px;"></div>
  </div>
  <script type="text/javascript">
var namelist=["石森 虹花","今泉 佑唯","上村 莉菜","尾関 梨香","織田 奈那","小池 美波","小林 由依","齋藤 冬優花","佐藤 詩織","志田 愛佳","菅井 友香","鈴本 美愉","長沢 菜々香","土生 瑞穂","原田 葵","平手 友梨奈","守屋 茜","米谷 奈々未","渡辺 梨加","渡邉 理佐","長濱 ねる"];
var matrix=[
  [0,0,2,0,2,8,1,1,1,1,1,1,0,1,1,2,1,0,0,5,0],
  [0,0,0,0,2,1,4,9,0,0,2,5,1,0,1,10,1,0,1,2,1],
  [12,5,0,34,9,7,11,5,7,8,14,9,5,23,21,5,18,6,5,13,4],
  [1,0,4,0,0,0,1,3,2,0,0,2,1,3,2,1,3,0,2,1,3],
  [0,1,0,0,0,4,0,0,0,5,0,0,6,1,1,1,7,1,2,3,0],
  [2,0,4,0,4,0,1,0,0,0,1,0,3,0,2,0,0,1,0,1,0],
  [0,1,2,0,3,4,0,1,0,1,1,0,1,1,6,2,3,1,0,6,0],
  [2,31,4,8,4,5,3,0,2,1,16,12,8,3,4,8,17,1,0,4,2],
  [0,1,1,2,1,0,0,1,0,1,5,0,0,4,2,0,0,0,1,0,14],
  [0,0,1,2,2,0,0,0,0,0,0,0,0,1,1,0,2,0,4,2,0],
  [3,6,8,5,3,5,1,13,7,3,0,4,10,17,3,2,4,2,5,2,1],
  [0,2,2,0,1,0,0,1,0,0,1,0,0,0,1,21,1,0,1,0,0],
  [1,2,2,3,9,6,1,3,0,2,6,3,0,1,1,2,8,5,9,1,0],
  [0,0,2,1,1,1,0,0,1,1,6,1,0,0,0,2,0,1,3,0,0],
  [0,0,3,0,2,2,3,0,0,1,0,1,1,1,0,2,7,0,0,3,0],
  [2,6,1,0,1,0,0,0,0,2,0,16,0,5,3,0,0,0,4,0,1],
  [1,4,9,2,7,1,2,7,0,2,3,0,7,5,10,3,0,3,8,7,0],
  [1,0,1,0,2,4,1,0,0,0,1,2,4,2,1,2,3,0,1,1,0],
  [0,0,0,1,1,0,0,1,1,4,2,0,8,5,1,1,2,0,0,0,2],
  [2,3,2,0,5,2,6,1,0,2,2,2,0,0,7,0,1,0,0,0,0],  
  [0,0,0,2,0,1,0,0,6,0,0,0,0,0,0,0,0,0,2,0,0]];
var id=0;
var chartEditor=null;
var dataTable=null;
var wrapper;
var titlesuffix="写真を撮った人:";
//セレクトボックスを変更した際に呼ばれる
//idをセレクトボックスで選択した値に基づいて変更し、
//dataTableを更新し、chartを更新して表示する
function getSelected(obj){
  id = Number(obj.options[obj.selectedIndex].value);
  setdata();
  showdata();
}
//matrixとidに基づいてデータテーブルdataTableを作成
function setdata(){
  var chartdata=[];
  for(var i=0;i<matrix.length;i++){
    if(matrix[id][i]!=0){
	  chartdata.push([namelist[i],matrix[id][i]]);
    }
  }
  chartdata.sort(function(a,b){return b[1]-a[1];});
  dataTable = new google.visualization.DataTable();
  dataTable.addColumn('string', '写真を撮られた人');
  dataTable.addColumn('number', '写真を撮られた回数');
  dataTable.addRows(chartdata);
}
//各種初期化		  
function initialize(){
  //button の初期化
  d3.select("#editbutton").on("click",changeEditor);
  //namelistに基づいてセレクトボックスを初期化
  d3.select("#nameselect").selectAll("option")
    .data(namelist)
    .enter()
    .append("option")
    .attr("value",function(d,i){return ""+i;})
    .text(function(d,i){return d;});
 d3.select("#nameselect").on("change",function(){getSelected(this);});
  //google chart関連オブジェクトの初期化
  google.load('visualization', '1.0', {packages: ['charteditor'],callback:loadEditor});
  function loadEditor(){
    chartEditor = new google.visualization.ChartEditor();
    setdata();
    wrapper = new google.visualization.ChartWrapper({
      'chartType':'PieChart',
      'dataTable':dataTable,
      'options': {'title': titlesuffix+namelist[id],
  		  'vAxis':{title:'写真を撮られた回数'},
  		  'hAxis':{title:'写真を撮られた人'}
  		 }
    });
    google.visualization.events.addListener(chartEditor, 'ok', showdataCallByChartEditor);
    showdata();
  }
}
//editボタンから呼ばれる
function changeEditor(){
  chartEditor.openDialog(wrapper, {});
}
function showdataCallByChartEditor(){
  wrapper=chartEditor.getChartWrapper();
  wrapper.draw(document.getElementById('vis_div'));
}
function showdata(){
  wrapper.setDataTable(dataTable);
  wrapper.setOption("title",titlesuffix+namelist[id]);
  wrapper.setOption("animation",null);
  wrapper.draw(document.getElementById('vis_div'));
}		  
initialize();    
  </script>

d3.jsを用いた連続アニメーション

d3ではtransitionを用いて簡単にアニメーションを実現できます。しかし、d3を用いてデータに基づく連続したアニメーションを実装することは面倒です。ここでは、連続アニメーションを若干汚い方法で実装します。

 

アニメーションするオブジェクトが一つの場合:

  1. 各時間に対応するオブジェクトの属性を持った配列を作成する。
  2. オブジェクトを次の属性に変化させる再帰的な関数animateを作成する。

2.に関しては、d3のeachを用いて、animateの中でアニメーション完了後に呼ばれる関数をセットする。セットした関数の中でanimateを呼び出し、再帰的にアニメーションを実現させる。

 

以下にアニメーションのサンプルとソースを記載しておきます。

ソース:

<div id="sample"><button id="startbutton">start</button>
<div>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<svg id="canpas" width="300" height="300"> </svg></div>
<script>// <![CDATA[
//初期化
  initialize();
  function initialize(){
    //ボタンの初期化。
    var startbutton = d3.select("#startbutton");
    startbutton.attr("disabled",null).on("click",startAnimation);
    //svgにcircle追加
    var svg = d3.select("#canpas");
    svg.append("circle")
       .attr({id:"circle0",
             cx:100,
             cy:100,
             r:10});
  }
  //startbuttonから呼ばれる
  function startAnimation(){
    //startbuttonを押せないようにする
    var startbutton = d3.select("#startbutton");
    startbutton.attr("disabled","disabled");
    //circleの位置の初期化
    var svg = d3.select("#canpas");
    circle=svg.select("#circle0")
      .attr({cx:100,
             cy:100,
             r:10});
    //順番に、半径、x座標、y座標のデータ
    var datas=[{r:5,x:10,y:100},
               {r:10,x:40,y:58},
               {r:20,x:100,y:200},
               {r:14,x:20,y:90},
               {r:10,x:100,y:100}];
    //どこまでアニメーションしたかを表すインデックス
    var index=0;
    //以下でanimateを再帰的に呼び出す
    animate();
    function animate(){
      if(index == datas.length){
        startbutton.attr("disabled",null);
        return;
      }
      var d=datas[index];
      index+=1;
      circle.transition()
        .duration(1000)
        .attr("r",d.r)
        .attr("cx",d.x)
        .attr("cy",d.y)
        .each("end",animate);
    }
  }
// ]]></script>
</div>

アニメーションするオブジェクトが複数の場合:

  1. 各時間に対応するオブジェクトすべての属性を要素としてもつ配列を作成する。
  2. すべてのオブジェクトを次の属性に変化させる再帰的な関数animateを作成する。

2.に関して、上記のオブジェクトが一つの場合と違う点はすべてのオブジェクトがアニメーションを終えた時にanimate関数を呼びださなければならない点です。d3では残念ながら、selectAllなどで複数に分割したすべてのオブジェクトのアニメーションが終了した時点で呼ばれる関数をセットするものはありません。そこで、d3のeach関数が何回呼ばれたかを記録しておき、オブジェクトの数だけ呼ばれたらanimate関数を呼び出すようにしました。

 

以下にアニメーションのサンプルとソースを記載しておきます。

ソース:

<div id="sample1">
    <button id="startbutton1">start</button>
    <div>
      <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
      <svg id="canpas1" width="300" height="300">
      </svg>
    </div>
    <script>
  //順番に、半径、x座標、y座標のデータ
  var datas=[[{r:5,cx:10,cy:100},
	       {r:10,cx:40,cy:58},
	       {r:20,cx:100,cy:200},
	       {r:14,cx:120,cy:90}
	       ],
	       [{r:14,cx:20,cy:90},
	       {r:5,cx:80,cy:100},
	       {r:10,cx:40,cy:58},
	       {r:20,cx:100,cy:200}
	       ],
	       [{r:20,cx:100,cy:200},
	       {r:14,cx:20,cy:90},
	       {r:5,cx:80,cy:100},
	       {r:10,cx:50,cy:58}
	       ],
	       [{r:10,cx:40,cy:58},
	       {r:20,cx:100,cy:200},
	       {r:14,cx:40,cy:90},
	       {r:5,cx:90,cy:80}
	       ],
	       [{r:5,cx:60,cy:100},
	       {r:10,cx:40,cy:78},
	       {r:20,cx:100,cy:200},
	       {r:14,cx:50,cy:90}
	       ]];    
  //初期化
  initialize1();
  function initialize1(){
    //ボタンの初期化。
    var startbutton = d3.select("#startbutton1");
    startbutton.attr("disabled",null);
    startbutton.on("click",startAnimation1);
  //circleの位置の初期化
    var svg = d3.select("#canpas1");
    var circles=svg.selectAll("circle").data(datas[0])
       .enter()
       .append("circle")
       .attr("r",(d)=>d.r)
       .attr("cx",(d)=>d.cx)
       .attr("cy",(d)=>d.cy)

  }
  //startbuttonから呼ばれる
  function startAnimation1(){
    //startbuttonを押せないようにする
    var startbutton = d3.select("#startbutton1");
    startbutton.attr("disabled","disabled");
    //circleの位置の初期化
    var svg = d3.select("#canpas1");
    var circles=svg.selectAll("circle")
       .data(datas[0])
       .attr("r",(d)=>d.r)
       .attr("cx",(d)=>d.cx)
       .attr("cy",(d)=>d.cy)
    //どこまでアニメーションしたかを表すインデックス
    var index=1;
    //何番目の点までセットしたendfunctionが呼ばれたか
    var endIndex=0;
    //以下でanimateを再帰的に呼び出す
    animate();
    function animate(){	     
      if(index == datas.length){
	startbutton.attr("disabled",null);
	return;
      }
      var ds=datas[index];
      index+=1;
      circles.data(ds)
	.transition()
	.duration(1000)
	.attr("r",(d)=>d.r)
	.attr("cx",(d)=>d.cx)
	.attr("cy",(d)=>d.cy)
	.each("end",function(){
	  endIndex++;
	  if(endIndex==ds.length){
	    endIndex=0;
	    animate();
	  }
	});
    }
  }
    </script>

ゲームシーケンス遷移

 今回はいつも使っている簡単なシーケンス遷移の実装方法を紹介します。

 以下に特徴を示します。

  1. 仮想的な木構造であり、実行時には1つのリストになる
  2. 木の葉ノードGameLeafクラスと木の内部ノードGameNodeクラス(GameLeafを継承している)からなる
  3. 実行時にはGameNodeは1つのGameNodeまたは1つのGameLeafをもち、GameLeafのupdate、renderが毎フレーム呼ばれる
  4. 子ノードは親ノードがもつリソースにアクセスできる

 仮想的なシーケンス構造が下のようになっているとすると、

シーケンス構造
RootNode-|-TitleLeaf
         |-SelectStageLeaf
         |-PlayNode-------|-LoadLeaf
                          |-PlayLeaf
                          |-PauseLeaf
                          |-ClearLeaf
                          |-GameOverLeaf

 実行時にはRootNode-TitleLeafやRootNode-PlayNode-PlayLeafのようにGameNode継承クラス-GameNode継承クラス...-GameLeaf継承クラスという構造になります。以下では仮想的な子である場合を"仮想的な子"、実際に生成されている子ノードは"子"と表現しています。

 まずはGameLeafとGameNodeの実装

abstract public class GameLeaf {
  private GameNode parent;               //親ノード
  abstract public void init();           //初期化処理
  abstract public GameLeaf update();     //更新処理。戻り値は基本的には次以降フレームで実行するGameLeaf継承クラス。変更しない場合はthisを返すようにする。
  abstract public void render();         //描画処理
  abstract public void destroy();        //終了処理
  abstract public String getID();        //自分を識別する文字列を返す。
  public void setParent(GameNode node){
    parent=node;
  }
  public GameNode getParent(){           //getParentで親ノードを取得し、親のリソースにアクセスする
    return parent;
  }
}
abstract public class GameNode extends GameLeaf{
  private GameLeaf child;//子ノード
  abstract public GameLeaf createFirstChild();//ノードが作られたと同時に作成する子ノードを返す。初期化もここでする。
  abstract public boolean isMyChild(GameLeaf c);//このノードの仮想てきな子ノードかどうか
                                                //GameNode継承クラスは上の2つとgetID、destroyを実装する必要がある。
  @Override
  public void init() {//子を生成する。
    child=createFirstChild();
    child.setParent(this);
    child.init();
  }
  @Override
  public GameLeaf update() {      //ノードの処理を行う。戻り値によって処理をかえる。
    GameLeaf next=child.update();
    if(next==child)return this;   //child.updateの戻り値がchildと等しい場合なにもしない。
    else if(next==null){          //戻り値がnullの場合は再帰的にノードが破棄され終了
      child.destroy();
      child=null;
      return null;
    }else if(isMyChild(next)){                        
      if(isMyChild(next)){        //child.updateの戻り値がchildと等しくないが自分の仮想的な子の場合はchildを破棄し、
        child.destroy();          //nextに変更。自分自身を戻り値として返すので親ノードではなにもしない。
        child=null;
        child=next;
        child.setParent(this);
        child.init();
        return this;
      }else{                      //child.updateの戻り値がchildと等しくない場合はchildを破棄し、nextを親に投げる。        
        child.destroy();          //自分自身を戻り値として返さないのでこのノードは親ノードで破棄される。
        child=null;               //nextが子である親ノードにたどりつくまで、同じ処理が行われる。
        return next;
      }
    }
  }
  @Override
  public void render() {
    child.render();
  }   
}

 C++の場合はdestroyをデストラクタにし、destroy呼び出しをdeleteに変更すればいいです。

 わかりにくいのはGameNodeのcreateFirstChild、updateだと思います。createFirstChildはGameNodeのinit内で呼ばれるので、init終了時には末端のGameLeafの生成、初期化処理まで終わっています。updateではシーケンスの遷移を処理しています。updateの戻り値は

  1. thisポインタ
  2. null
  3. 同じ親に属するGameLeafまたはGameNode継承クラス
  4. さらに上の親ノードの仮想的な子

 1の場合は何もしません。2が返されたら順次クラスを破棄して終了します。3の場合は元の子ノードを破棄してupdateの戻り値を子ノードとしてセットします。自分自身をnewして返すなら再初期化することになります。4の場合は戻り値が仮想的な子ノードである親ノードに出会うまで再帰的にノードが破棄されていき、親ノードにであったら子ノードとしてセットされる。有効な戻り値は例で示すシーケンス構造で考えると、ClearLeafがTitleLeafを返すのは有効ですが、TitleLeafがPlayLeafを返すのは無効です。

 実際には、描画に必要なものをrenderの引数にもたせたり、経過時間をupdateの引数に持たせたりしています。

 GameLeaf、GameNodeを継承したクラスを実装し、根ノードをRootNodeとすると、ゲームループを実装する場合、下のように使います。

int fps=60;
GameLeaf rootNode=new RootNode();//RootNodeは根ノードとする。
rootNode.init();
rootNode.render();
while(true){//ゲームループ
  long ms=System.currentTimeMillis();
  if(rootNode.update()==null){
    break;
  }
  rootNode.render();
  long waitTime=System.currentTimeMillis()-1000/fps;
  //ほかの処理などここで
  //fpsを固定させるため待つ
  if(waitTime>0){
    try{
      Thread.sleep(waitTime);
    }catch(Exception e){
    }
  }
}
rootNode.destroy();

 以下ではよく使う構造で実装してみます。[...]は...が実装されていると考えてください。

 GameLeaf、GameNode継承クラスの実装方法はシーケンスがしたのようになっているとすると、

シーケンス構造
RootNode-|-TitleLeaf
         |-SelectStageLeaf
         |-PlayNode-------|-LoadLeaf
                          |-PlayLeaf
                          |-PauseLeaf
                          |-ClearLeaf
                          |-GameOverLeaf
RootNode:根ノード
TitleLeaf:タイトルを表示する。スタートボタンでステージセレクト画面へ遷移する。
SelectStageLeaf:ステージセレクト画面を表示
PlayNode:ゲームに必要なリソースを持つNode
LoadLeaf:ロード画面を表示しつつ、PlayNodeのリソースをロードする。
PlayLeaf:ゲームプレイ中
PauseLeaf:ポーズ画面表示
GameOverLeaf:ゲームオーバー画面表示。スタートボダンでタイトル画面へ
ClearLeaf:クリア画面表示。スタートボタンでタイトル画面へ

 RootNode.java

public class RootNode extends GameNode{
  public static String id="RootNode";//すべてのGameLeaf、GameNodeクラスに静的文字列idを持たせる
  @Override
  public GameLeaf createFirstChild(){
    return new TitleLeaf();
  }
  @Override
  public boolean isMyChild(GameLeaf leaf){
    String id=leaf.getID();
    return id.equals(TitleLeaf.id)||id.equals(SelectStageLeaf.id)||id.equals(PlayNode.id);
  }
  @Override
  public void destroy(){
  }
  @Override
  public String getId(){
    return id;
  }
}

 TitleLeaf.java

public class TitleLeaf extends GameLeaf{
  public static String id="TitleLeaf";
  @Override
  public void init(){
  }
  @Override
  public GameLeaf update(){
    if([スタートボタンを押した]){
      return new StageSelectLeaf();
    }
    return this;
  }
  @Override
  public void render(){
    [ここでタイトルの表示]
  }
  @Override
  public void destroy(){
  }
}

 StageSelectLeaf.java

public class StageSelectLeaf extends GameLeaf{
  public static String id="StageSelectLeaf";
  private int stageNumber;   //ステージのナンバー
  @Override
  public void init(){
  }
  @Override
  public GameLeaf update(){
    if([方向キーを押した]){
      [stageNumberの変更処理]
    }
    if([スタートボタンを押した]){
      return new PlayNode(stageNumber);
    }
    return this;
  }
  @Override
  public void render(){
    [ステージセレクト画面表示]
  }
  @Override
  public void destroy(){
  }
}

 PlayNode.java

public class PlayNode extends GameNode{
  public static String id="PlayNode";
  private int stageNumber;
  public PlayNode(int stageNumber){
    this.stageNumber=stageNumber;
  }
  @Override
  public GameLeaf createFirstChild(){
    return new LoadLeaf();
  }
  @Override
  public boolean isMyChild(GameLeaf leaf){
    String id=leaf.getID();
    return id.equals(PlayLeaf.id)||id.equals(LoadLeaf.id)||id.equals(ClearLeaf.id)||id.equals(GameOverLeaf.id);
  }
  @Override
  public void destroy(){
  }
  @Override
  public String getId(){
    return id;
  }
  public void load(){          //各種リソースのロードを行う。LoadLeafから呼び出す
  }
  public void updateGameObjects(){   //ゲームオブジェクトの更新を行う。PlayLeafのupdateから呼び出す
  }
  public void renderGameObjects(){   //ゲームオブジェクトの描画を行う。PlayLeafのrenderから呼び出す
  }
}

 LoadLeaf.java

public class LoadLeaf extends GameLeaf{
  public static String id="LoadLeaf";
  @Override
  public void init(){
    PlayNode node=(PlayNode)getParent();
    [スレッドを作成し、別スレッドでPlayNodeのloadを呼び出す]
  }
  @Override
  public GameLeaf update(){
    if([ロードが完了した]){
      return new PlayLeaf();
    }
    return this;
  }
  @Override
  public void render(){
    [ロード画面の表示]
  }
  @Override
  public void destroy(){
    [リソースの解放処理]
  }
}

 PlayLeaf.java

public class PlayLeaf extends GameLeaf{
  public static String id="PlayLeaf";
  @Override
  public void init(){
    
  }
  @Override
  public GameLeaf update(){
    PlayNode node=(PlayNode)getParent();
    node.updateGameObjects();
    if([ポーズボタンを押した]){
      return new PauseLeaf();
    }
    if([クリアした]){
      return new ClearLeaf();
    }else if([ゲームオーバーした]){
      return new GameOverLeaf();
    }
    return this;
  }
  @Override
  public void render(){
    PlayNode node=(PlayNode)getParent();
    node.renderGameObjects();
  }
  @Override
  public void destroy(){
  }
}

 ClearLeaf.java

public class ClearLeaf extends GameLeaf{
  public static String id="ClearLeaf";
  @Override
  public void init(){
    
  }
  @Override
  public GameLeaf update(){//PlayNodeのupdateGameObjectsを呼び出さないのでゲームはとまったまま
    if([スタートボタンを押した]){
      return new TitleLeaf();
    }
    return this;
  }
  @Override
  public void render(){       //通常のプレイ画面の上にClearの文字列を表示する
    PlayNode node=(PlayNode)getParent();
    node.renderGameObjects();
    [Clear文字列の描画]
  }
  @Override
  public void destroy(){
  }
}

 他のクラスは省略します。RootNodeを生成し、ゲームループ内で実行していくと、まずはタイトル画面が表示されます。スタートボタンを押すとステージセレクト画面が表示されます。ステージセレクト画面でステージを選択し、スタートボタンを押すとロード画面が表示され、ロードが開始されます。ロードが完了したらゲーム開始となります。ゲームクリアでプレイ画面の上に"Clear"文字列が表示され、スタートボタンを押すとタイトル画面に戻ります。

2D物理エンジンAPE

 今回はいつも使っている2D物理エンジンを紹介します。APE(ACTION SCRIPT PHYSICS ENGINE)です。以下のような特徴があります。

  1. 使える形状は四角と円のみ
  2. 四角は回転できるが、トルクには対応していない
  3. 円は回転物体として取り扱うこともできる
  4. ConstraintはAngularConstraint(javaでは未実装)とSpringConstraintが使える
  5. 空間分割処理はしていない
  6. ActionScriptC++Javaバージョンがある
  7. MITライセンスなので改変して、商用に使ってもいい

 機能は少なく、ちょっとした処理に用いるのに適しています。ソースコードがとてもシンプルにできているので、使用目的に応じてカスタマイズしています。例えば、衝突判定のみ有効で衝突処理はしないものや、衝突情報の追加などです。

 以下に簡単な使い方を示します

//APEngineの初期化
APEngine.init((double)1/3);
APEngine.setCollisionResponseMode(APEngine.SELECTIVE);
//ここから物体の追加
RectangleParticle rp= new RectangleParticle (0,0,20,20,0,true,1.0,0,0);//x,y,width,height,rotation,isFixed,mass,elasticity,friction
CircleParticle cp=new CircleParticle(0,10,5,fales,1.0,0,0)//x,y,radius,isFixed,mass,elasticity,friction
APEngine.addParticle(rp);
APEngine.addParticle(cp);
.
.
.
//以下は毎フレームよび出す
APEngine.step();

 詳しい使い方はサイトからダウンロードできるデモのソースを見てください。

 ソースの構成について少し説明しておきます。APEngine、CollisionDetector、CollisionResolverはstaticメソッドで構成されています。APEngineのstepメソッド下からCollisionDetectorのtestメソッド呼ばれ衝突判定を行い、衝突してたらさらに、CollisionDetectorのtestメソッドからCollisionResolverのresolveParticleParticleが呼ばれ衝突処理を行います。なので、物体の基底クラス AbstractParticleにプロパティを増やし、APEngineのstep、CollisionDetectorのtest、CollisionResolverのresolveParticleParticleあたりを改変すれば、たいていのことはできます。

Androidマルチタッチの検出方法

 Androidマルチタッチの検出方法についてまとめておこうと思います。検索した結果、古い情報しかなく、はまりやすい点が多いように感じました。はじめに注意すべき点をあげておきます。

  1. 1つのMotionEventにはMotionEventのタイプ(ACTION_DOWN、ACTION_UPなど)とタッチしているすべての点に対応する情報が含まれている
  2. android.support.v4.view.MotionEventCompatを使ってMotionEventの情報を取得した方がよい

 1.について、はじめMotionEventには1つの点しか含まれていないと勘違いしていました。実際はMotionEventのタイプがACTION_MOVEだとすると、MotionEventには実際に動いたものだけでなく、すべてのタッチしている点が含まれています。

 2.について、マルチタッチで検索すると、MotionEvent.ACTION_POINTER_ID_MASKなどを用いて取得しているものが多いです。しかし、これは現在deprecatedになっています。また、MotionEventCompatを使うほうがわかりやすいです。

以下で詳しい説明に入ります。

OnTouchListenerの実装、セット

 OnTouchListenerはpublic boolean onTouch(View v, MotionEvent event)を実装する必要があります。

public class TestTouchListener implements OnTouchListener{
  public boolean onTouch(View v, MotionEvent event){
          //ここにタッチされたときの処理
  }
}

 あとはViewにセット

view.setOnTouchListener(testTouchListener);

 もちろん実際はOnTouchListenerはActivityやViewに実装する場合が多いです。

マウスイベントの種類(アクション)

アクションタイプ呼び出しタイミング
MouseEvent.ACTION_DOWN 画面をタッチした(はじめの1点のみ)
MouseEvent.ACTION_POINTER_DOWN 画面をタッチした(2点以降)
MouseEvent.ACTION_UP タッチした指を離した(最後の1点のみ)
MouseEvent.ACTION_POINTER_UP タッチした指を離した(最後の1点以外)
MouseEvent.ACTION_MOVE タッチした指を動かした

 他にもアクションタイプはありますが省略します。

マルチタッチに対応した実装方法

 まず注意する点はMouseEventには画面に触れているすべての点が含まれているということです。ここは要注意です。では実際にどのようにMouseEventから情報を取得するのかを見ていきます。まずはコードをみてください。

public boolean onTouch(View v, MotionEvent event){
  int action = MotionEventCompat.getActionMasked(event);//アクションタイプの取得
  int count = MotionEventCompat.getPointerCount(event);//含まれる点の個数を取得
  for(int i=0;i<count;i++){
    int id = MotionEventCompat.getPointerId(event,i);//i番目ののpointIdを取得
    float x = MotionEventCompat.getX(event,i);//i番目のx座標を取得
    float y = MotionEventCompat.getY(event,i);//i番目のy座標を取得
    switch(action){
    case MotionEvent.ACTION_DOWN:
      //タッチした1点めの処理 countは必ず1のはず
	  break;
    case MotionEvent.ACTION_POINTER_DOWN:
      if(i == MotionEventCompat.getActionIndex(event)){
        //はじめて画面にタッチした点の処理
      }else{
        //すでに画面にタッチしている点の処理
      }
      break;
    case MotionEvent.ACTION_UP:
      //指を離した最後の点の処理 countは必ず1のはず
      break;
    case MotionEvent.ACTION_POINTER_UP:
      if(i == MotionEventCompat.getActionIndex(event)){
        //指を離した点の処理
      }else{
        //すでに画面にタッチしている点の処理
      }
      break;
    case MotionEvent.ACTION_MOVE:
      if(i = MotionEventCompat.getActionIndex(event)){
        //指を動かした点の処理
      }else{
        //すでに画面にタッチしている点の処理
      }
      break;
  }
}

 全パターン処理できるように実装しています。switchしてforした方が無駄な処理がないのですが、見にくいので上のように実装しました。MouseEventの情報はMouseEventCompat経由で取得しています。MouseEventCompatには以下のstatic関数があります。

関数名説明
int getActionMasked(MotionEvent e) アクションタイプ取得
int getPointerCount(MotionEvent e) タッチしている点の数を取得
int getActionIndex(MotionEvent e) 実際にアクションした点のインデックス取得
int getPointerId(MotionEvent e,int index); index番目の点のPointerId取得
float getX(MotionEvent e,int index); index番目の点のx座標取得
float getY(MotionEvent e,int index); index番目の点のy座標取得

 点の追跡はPointerIdを用いて行います。PointerIdが前のMouseEventで得たPointerIdと等しいとすると、同じ指に対応する点であると判別することができます。もしなにかアクションした点だけに興味があるなら以下のように実装します。

public boolean onTouch(View v, MotionEvent event){
  int action = MotionEventCompat.getActionMasked(event);//アクションタイプの取得
  int index = MotionEventCompat.getActionIndex(event);//アクションした点のindex取得
  int id = MotionEventCompat.getPointerId(event,index);//pointIdを取得
  float x = MotionEventCompat.getX(event,index);//x座標を取得
  float y = MotionEventCompat.getY(event,index);//y座標を取得
  switch(action){
    case MotionEvent.ACTION_DOWN:
    case MotionEvent.ACTION_POINTER_DOWN:
     //タッチした点の処理
      break;
    case MotionEvent.ACTION_UP:
    case MotionEvent.ACTION_POINTER_UP:
      //指を離した点の処理
      break;
    case MotionEvent.ACTION_MOVE:
      //指を動かした点の処理
      break;
  }
}

文字化け注意

 先日、php、html、データベース、jdbcドライバを使っていて文字化けで苦しみました。なんで、チェックする項目をまとめて置きたい思います。環境はubuntu12.10でファイルの文字コードはすべてutf-8の場合です。

1.jdbcドライバの文字エンコーディング設定

 urlをjdbc:mysql:///[database name]?characterEncoding=utf8とする

2.データベースの文字コードの設定

 データベースの文字コードをチェックする

3.htmlのcharacter-set

 以下のようにContent-Typeを設定

<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">

4.phpからexecを用いて、標準出力を利用する場合の環境変数LANGの設定

 環境変数が渡されないので

exec("export LANG=ja_JP.UTF-8;[command]",$output,$result);

 とすることで標準出力から得た$outputの文字コードutf-8になる

5.phpからデータベースへ接続する場合の文字エンコーディング設定

 mysqlの場合

mysql_set_charset('utf-8');

 でMySQLのクライアント側の文字コードまで設定する

以上これだけ設定すれば、うまくいきました。phpでは  mbstring.http_input,mbstring.http_output,mbstring.internal_encoding

などの設定がありますが、そこは変更しませんでした。このへんはよくわからないので、よく解説してあるサイトを貼って置きます。

http://hain.jp/index.php/tech-j/2007/02/13/p125