2013年02月27日

要出典?

 ネットにて、ある新聞記事を読んでいてふと思った。あ、この文章ってWikipediaだったら「要出典」 タグがつくんじゃなかろうか…と。

 まぁ、こんな事を思ってしまう時点でだいぶネットという世界に毒されている小生ではある。そして、よくWikipediaは情報源としての信頼性に欠けるという意見も聞く。実際、「中立性」や「正確性」については誰でも編集ができる時点でWikipediaは劣るというのは自然な考え方である。だが、その「中立性」や「正確性」を『検証する』という点においては、世に言う「信頼出来る情報ソース」と比してもWikipediaは勝ることはあっても劣ることはないのではなかろうか。

 そう思ってしまったら、少しは調べてみないと気がすまないのが小生の性分である。とりあえず、Wikipediaにあるガイドラインを流し読み。ふむ、検証が可能なようにという目線で見れば、このガイドラインは(確実に実行されているのであれば)確かに有用なように思える。

 ならば、とそのガイドラインが頭に残っている状態で、とりあえずネットニュースの記事を読んでみた。

 むぅ。いかん。これ、ほぼ全文に「要出典」がつきそうな勢いだ。というか、海外メディアの『〇〇によると』的な表記以外でこれがつかないのは、記事の最後あたり蛇足として載っている記者の感想的な部分くらいだろう。まぁ、その部分は間違いなく「独自研究」のタグがついてしまうわけだが。

 いや、ほぼリアルタイムの記事に出展なんざつけようがないのも事実だろう。それはそれで、仕方ないのかもしれない。また、記事の検証性についての考え方が根本的に違う…というか、検証される事を全く考えていない世界だから、同じ土俵に乗せるというのは酷というものであろう。だが、小生が書いているこのページのような、それこそ独自研究レベルで個人の「考え方」を発表する場であれば検証性など論ずる必要もないだろうが、ニュースなどのような物はもうちょっと検証性を求められてもいいのではなかろうかという気もする。というか、ここまで出展となる物が明らかにされていないということは、もしかしたら記事についての検証される可能性を意図的に消しているのではないかとすら勘ぐりたくなってしまうくらいである。まぁ、ぶっちゃけ「誰2」タグや「独自研究」タグがついてしまうような、言葉を濁した内容や不明確な言葉、明らかに主観的な文言や内容が内容が記事中に散見されるあたり、この手のニュースとて検証性という意味においては小生の書き散らす駄文と大差ないのかもしれない。

 何はともあれ、この目線は面白いかもしれない。少なくとも、何かを疑う時にこの「要出典」の考え方は、非常に有効である気がしてきた。あるいは、何かが「疑われずにすむため」の検証にも使えるかもしれない。

 で…調子に乗ってネットニュースを見ながら「要出典」と頭のなかで繰り返していたら、何かを思い出した。あぁ…そうだ。あれだ。アンサイクロペディアの「要出典」だ。現状の新聞記事って、たぶんあのページと同じくらいの「要出典」タグがつくんじゃないかなぁ。
posted by 管理人 at 23:48| Comment(0) | 雑談 | このブログの読者になる | 更新情報をチェックする

2013年02月09日

Haxe + NME 5回目 テキストフィールド、お前もか!!

 ボードゲームのヘクスマップには、記録用にヘクスの番号がついている物があった。あれを表示してみるのもいいかもしれない。というか、どのみちマップのステータスやら、移動距離やら、敵味方の脅威度やらといった情報を、デバッグのために表示させる必要がある。なら、早いうちに簡単に表示させるルーチンを作っておくべきかもしれない。

 というわけで、早速追加。適当なテキストフィールドを作って、描画と同じような感じでループを回して、ループの内側で必要な文字列を書き込んでBitmapにdrawしてやれば良い。座標は、縦方向は六角形の上端に書くとしたら、1個前の縦座標に合わせれば良い。横方向はというと、中心点に合わせて適当に中央合わせしてやれば簡単な事だ。そう…簡単な事のはずである。

 が…
…また…はまった…。
 Bitmap.drawが上手く行かないのは4回目で書いたとおりであるから、同じ方法で逃げるとして…「TextFieldAutoSize.CENTER」がHTML5で機能してくれない。早い話、文字のセンタリングをしてくれないのである。

 あぁ…TextField、お前もか…。もし仮にNMEに「Brute」というクラスがあったとして、そのクラスの挙動が出力する環境によって異なるバグを持っていたとしたら、かのガイウス・ユリウス・カエサルでなくとも、誰もがこう言うだろう。「Et tu, Brute?」 すなわち「ブルータス、お前もか」、と。

 …はい、はい。わかりました。便利機能に頼っちゃいけないってことですね。…こうなると、もうそろそろ諦観の域である。とりあえず問題無さそうな左詰め、つまり「TextField.autoSize = TextFieldAutoSize.LEFT」にして、文字列をセットしたうえでTextFieldの幅を見てx座標を調整しましょうか。


// 文字を描画する
private function drawString(target:BitmapData):Void
{
// 文字列のフォーマットなどを指定…あんまり細かい事はしない方が安全っぽいので、フォントサイズだけ決める
var textFormat:TextFormat = new TextFormat();
textFormat.size = ySize / 2;
// 描画元になるテキストフィールドを設定。NMEを信じてはいけない。なるべくシンプルな設定で。
var textField:TextField = new TextField();
textField.defaultTextFormat = textFormat;
textField.autoSize = TextFieldAutoSize.LEFT; // うかつにCENTERとか使おうとするとNMEはコケる模様

var m:Matrix = new Matrix();
// Matrixを指定しないと、Flashで位置が動かない!!
// Shape.xとShape.yを指定しないと、HTML5で位置が動かない!!
for (y in 0 ... mapMax) {
m.ty = y * ySize - ySize;
textField.y = m.ty;
for (x in 0 ... mapMax) {
if ((x + y) % 2 == oddEven) {
// 文字列を代入。
textField.text = drawStringFunc(x, y);
// widthを元に横位置を調整。
m.tx = x * xSize - textField.width / 2;
textField.x = m.tx;
target.draw(textField, m);
}
}
}
}


 これだけ色々出てくると、クロスプラットフォーム云々はまだまだ夢なのかなぁ、と思ってしまうのだが…否!! この程度の工夫で何とかなるなら、十分クロスプラットフォームと言っていいのではなかろうか。ゲームを作ったとして、3Dがグリグリ動くとかでもない限り、中核部分はプラットフォームに左右されない物だ。つまり表示系だけちょこちょこっと気をつけつつ、なるべく基本的な描画方法で実装さえしてしまえば、あとは何とかなるはずだ…と思いたい。思うことにする。いや、別にクロスプラットフォームにこだわってる訳じゃないので、どっかで捨てるかもしれないけどさ。

 何はともあれ。とりあえず現時点でこんな感じのコードに。ヘクスの描画部分だけ微妙に一般化してみたが、これならクラス全体ももうちょっと一般化した方が良さそうかな、とも思う次第である。

package ;
import nme.display.Bitmap;
import nme.display.BitmapData;
import nme.display.Shape;
import nme.display.Sprite;
import nme.events.MouseEvent;
import nme.geom.Matrix;
import nme.text.TextField;
import nme.text.TextFieldAutoSize;
import nme.text.TextFormat;

/**
* ...
* @author kani-miso
*/

class HexBase extends Sprite
{
static public var ODD_TYPE = 1;
static public var EVEN_TYPE = 0;

// 定数
var mapMax = 21; // マップの縦横最大値
var hexSize = 40; // ヘックスの直径
// 偶数系か奇数系か
var oddEven = 0;
// ヘックスの縦横サイズ(全体で何度も使うのでここで定義)
var xSize:Int;
var ySize:Int;
// マウス位置のヘックスを示すためのカーソル
var cursor:Shape;
// 描画用のbitmapセット
var bitmapData:BitmapData;
var bitmap:Bitmap;

// クラス初期化
public function new()
{
super();
// Hexの縦幅、横幅の計算
xSize = Std.int(1 + hexSize * 3 / 4 / 8) * 8; // 8の倍数にする
ySize = Std.int(xSize / 3 * Math.sqrt(3));

// 表示位置を少しずらす
x = 10;
y = 10;

// bitmapを背景色つきで作成
bitmapData = new BitmapData(xSize * (mapMax - 1), ySize * (mapMax - 1), 0xFFEEFFEE);
bitmap = new Bitmap(bitmapData);
addChild(bitmap);

// bitmapに描画
draw(bitmapData);
drawString(bitmapData);

// カーソルを準備してマウスが動いたら動かす
initCursor();
addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
}

// 描画ルーチン
private function draw(target:BitmapData
, centerCircleColor:Int = 0xFF0000
, hexLineColor:Int = 0xFF0000
, rangeLine:Int = -1
):Void
{

// 六角形1個分を描画するためのShape
var shape:Shape = new Shape();

// 中心円を描く
if (centerCircleColor >= 0) {
shape.graphics.lineStyle(1, centerCircleColor);
shape.graphics.drawCircle(0, 0, 4);
}

// Hexの枠線を描く
if (hexLineColor >= 0) {
var octantXSize:Int = xSize >> 3;
shape.graphics.lineStyle(1, hexLineColor);
shape.graphics.moveTo(-5 * octantXSize, 0);
shape.graphics.lineTo(-3 * octantXSize, -ySize);
shape.graphics.lineTo( 3 * octantXSize, -ySize);
shape.graphics.lineTo( 5 * octantXSize, 0);
}

// 四角線を描く
if (rangeLine >= 0) {
var halfYSize:Int = ySize >> 1;
var halfXSize:Int = xSize >> 1;
shape.graphics.lineStyle(1, 0x0000FF, 0.6);
shape.graphics.moveTo(-halfXSize, halfYSize);
shape.graphics.lineTo(-halfXSize, -halfYSize);
shape.graphics.lineTo( halfXSize, -halfYSize);
shape.graphics.lineTo( halfXSize, halfYSize);
shape.graphics.lineTo(-halfXSize, halfYSize);
}

// 出来上がったShapeを、位置をずらして描画
// Matrixを指定しないと、Flashで位置が動かない!!
// Shape.xとShape.yを指定しないと、HTML5で位置が動かない!!
var m:Matrix = new Matrix();
for (y in 0 ... mapMax) {
shape.y = y * ySize;
m.ty = shape.y;
for (x in 0 ... mapMax) {
if ((x + y) % 2 == oddEven) {
shape.x = x * xSize;
m.tx = shape.x;
target.draw(shape, m);
}
}
}
}

// 文字を描画する
private function drawString(target:BitmapData):Void
{
// 文字列のフォーマットなどを指定…あんまり細かい事はしない方が安全っぽいので、フォントサイズだけ決める
var textFormat:TextFormat = new TextFormat();
textFormat.size = ySize / 2;
// 描画元になるテキストフィールドを設定。NMEを信じてはいけない。なるべくシンプルな設定で。
var textField:TextField = new TextField();
textField.defaultTextFormat = textFormat;
textField.autoSize = TextFieldAutoSize.LEFT; // うかつにCENTERとか使おうとするとNMEはコケる模様

var m:Matrix = new Matrix();
// Matrixを指定しないと、Flashで位置が動かない!!
// Shape.xとShape.yを指定しないと、HTML5で位置が動かない!!
for (y in 0 ... mapMax) {
m.ty = y * ySize - ySize;
textField.y = m.ty;
for (x in 0 ... mapMax) {
if ((x + y) % 2 == oddEven) {
// 文字列を代入。
textField.text = drawStringFunc(x, y);
// widthを元に横位置を調整。
m.tx = x * xSize - textField.width / 2;
textField.x = m.tx;
target.draw(textField, m);
}
}
}
}

// Hex番号を文字列で返す関数
private function drawStringFunc(x:Int, y:Int):String
{
//return( ("0" + Std.string(x)) + " " + ("0" + Std.string(y)).substr(1,2) );
return( ("0" + Std.string(x)).substr(-2) + " " + ("0" + Std.string(y + 1 >> 1)).substr(-2) );
}

// カーソル初期化
private function initCursor(thickness:Float = 2
, cursorColor:Int = 0x00FF00
, cursorAlpha:Float = 0.7
, radius:Float = 7
):Void
{
// Shapeを初期化して、適当なサイズの丸を中心に描画
cursor = new Shape();
cursor.graphics.lineStyle(2, cursorColor, cursorAlpha);
cursor.graphics.drawCircle(0, 0, radius);
addChild(cursor);
}

// カーソル移動ルーチン
private function onMouseMove(evt:MouseEvent):Void
{
// 青マス上での座標を得る
var x:Int = Std.int((evt.localX + xSize / 2 ) / xSize);
var y:Int = Std.int((evt.localY + ySize / 2 ) / ySize);
// ヘックス上で有効な値ならカーソルを動かす
cursor.x = x * xSize;
if ((x + y) % 2 == oddEven) {
cursor.y = y * ySize;
} else {
var mod:Int = Std.int(evt.localY + ySize / 2) % Std.int(ySize) - Std.int(ySize / 2);
if (mod > 0 ) {
cursor.y = (y + 1) * ySize;
} else {
cursor.y = (y - 1) * ySize;
}
}
}

}


posted by 管理人 at 16:01| Comment(0) | Haxe | このブログの読者になる | 更新情報をチェックする

Haxe + NME 4回目 ハチの巣にしてやられる

 映画やら漫画やらで、「ハチの巣にしてやるぜ」的なセリフを時々見かける。多数の銃弾を頼りに、相手に対して「穴だらけにしてやる」という意味で使われるこのセリフだが、たいていは言った人がその場あるいはその後に命を落としているような気がする。ってことは、基本的に悪役の吐くべきセリフなのだろうか。

 さて、先のヘクスマップだが、確かに形状はハチの巣になっているものの、多数の銃弾的な物で穴を開けたわけではなく、どちらかというと丁寧に上から順に六角形の3辺を描画している感じである。しかも、わざわざShapeにハチの巣をひと通り描画して、それをBitmapDataに写し取っている。

 どうせShapeに描画してからBitmapDataに転送するなら、1ヘクス分だけShapeに描画しといて、それを座標を変えつつBitmapDataにコピーしてった方がアルゴリズム的にもすっきりするんじゃなかろうか…と、ふと思いついた。つまり、六角形の3辺と中心円、そして青マスの部分を単体で描画したShapeを用意して、青マスの単位で縦横にずらして複写すればいい。1個分のShapeを銃弾に見立てて、形容としても文字通りの意味でも「ハチの巣にしてやるぜ」的な指向で「ハチの巣」を描画しようという寸法である。

 この方法の長所は、境界線や中心円を描く、描かないのオプションが発生した時にif文が発生する回数が少なくなる事。例えば100×100ヘクスを描画するとして、それぞれ10,000回の繰り返しの中にif文を入れて描くか描かないかを判断するというのは、それなりに演算時間を食うはずである。これを、あらかじめ描画したShapeのコピーで処理すれば、たったの1回で済むというわけだ。うん、何だかお得な気がしてきた。

 では…とやってみた。
 …はまった…。
 結論から言う。NMEのBitmap.draw()は、かなりのクセモノだ。いや、HTML5への出力時にSpriteからのDrawが出来ないバグは前から知っていたが、それ以外にも環境によって動作が違うっぽい。

 具体的には、HTML5への出力だとdrawするShapeの位置は「Shape.x Shape.y」によって動く。Flashへの出力だとBitmap.drawの第二引数にMatrixを指定して描画位置を決めるため、Matrixが無いと「Shape.x Shape.y」の値に関わらずBitmapの原点に描画される。そして、再度HTML5に戻ると、このMatrixでの位置指定はなぜか無視されてしまい、Matrixで位置を指定してもBitmapの原点に描画される。

 NMEは基本的にActionScript3のラッパーであると思われる。故に、Flash側での動作が正しいような気がしなくもない。が…何はともあれ、同じソースなのに環境によって差があるというのは困った物である。

 ん〜。どうしよう…。

 待てよ。
  • 「Flashの時はMatrixのみ使用される」
  • 「HTML5の時はShape.x Shape.yのみ使用される」
ということは…。

 なんだ、両方書いときゃいいんじゃん。つまり、こんな感じ。

// 出来上がったShapeを、位置をずらして描画
// Matrixを指定しないと、Flashで位置が動かない!!
// Shape.xとShape.yを指定しないと、HTML5で位置が動かない!!
var m:Matrix = new Matrix();
for (y in 0 ... mapMax) {
shape.y = y * ySize;
m.ty = shape.y;
for (x in 0 ... mapMax) {
if ((x + y) % 2 == 0) {
shape.x = x * xSize;
m.tx = shape.x;
target.draw(shape, m);
}
}
}


 というわけで、今回のNMEでの注意点。

nme.display.Bitmap.draw(Shape, Matrix)を使う時、
  • HTML5に出力する時は、Shapeの x y で位置が決まる
  • Flashに出力する時は Matrix で位置が決まる。
  • 両者の環境で同じ描画をするときには、 x y を入れた上で、同じ値を Matrix に入れておくと何とかなる。
あぁ…我ながらなんて馬鹿な実装してるんだろうなぁ、と思いつつも…プログラムなんて物は、最終的に正しく動けば勝利なのです。うん。

 しかし…何だかこう…「ハチの巣にしてやるぜ」とほざいた物の、いつの間にか倒されて「どうしてこんな…」とか思っている悪役の気分である。
posted by 管理人 at 15:00| Comment(0) | Haxe | このブログの読者になる | 更新情報をチェックする

Haxe + NME 3回目 ハチの巣

 前回の記事にて、ヘクスマップの描画アルゴリズムを考えてみた。では、これをNMEで実装したらどうなるか、と考えてみた。そんなに難しい話ではないはずである。

HexAlgo02.png

 まずは、青線のようなマス目を想定し、縦横の座標を足して偶数になる時に「中心」を描画する。あとは、上の図の A-C、C-D、D-F の直線を3本引けば良い。六角形のどっか3本を引いておけば、端っこ以外はきちんと六角形になるはずである。

 問題となるのは、それぞれの寸法比率であろうか。六角形の対角線(外接円の直径)に対して、青線の縦横の比率とか、それに対してのA、C、D、Fの位置関係を考えなければならない。また、B、Eは、それぞれA-C、D-Fの中点となるはずである。

 とりあえず、縦方向は比較的気楽に考えられそうだ。というのも、青線の縦の長さが決まってしまえば、中心から見てCやDの縦方向の位置は、青線の縦の長さと同じになるからである。ぶっちゃけ考える必要なんて無い。B、Eの位置も、この縦の長さの半分だ。問題はない。

 じゃぁ、横方向。これはというと…微妙に面倒である。どうやれば解けるか。これを解くためには、ある物を思い出す必要がある。

 その「ある物」とは、思春期…具体的には中学生くらいの頃、誰にでも覚えがある物。幾度と無く苦悩し、時に間違え、なかなかうまく行かない体験をし、それ故に時としてうまくいった時には喜び、でもその全てが大人たちの手のひらの上でしかなく、そして大人になる頃にはほとんどの人が忘れ去ってしまった…そんな苦い思い出のつきまとう物である。

 つまりは、というと…
「三平方の定理」
 …である。

 えぇ、やりましたとも。何年ぶりだか何十年ぶりだか知らないけれど、大人気もなく紙に下手な六角形を描いて、補助線と称して三角形だのなんだのを描き入れて、やっとのことで計算しましたともさ。微妙に間違ってると恥ずかしいので、途中計算は省略するとして、結論だけ。

 六角形の対角線の長さを「1」とすると、青線の横 = 3 / 4。

 以下、青線の横の長さをLとすると
青線の縦 = L × √3 / 3
点AのX座標 = 中心のX座標 - L × 5 / 8
点AのX座標 = 中心のX座標 - L × 4 / 8 = 中心のX座標 - L / 2
点CのX座標 = 中心のX座標 - L × 3 / 8
点DのX座標 = 中心のX座標 + L × 3 / 8
点EのX座標 = 中心のX座標 + L × 4 / 8 = 中心のX座標 + L / 2
点FのX座標 = 中心のX座標 + L × 5 / 8
となる。

 ふむ。青線の長さを8の倍数にさえしておけば、横方向は基本的に整数で済む感じなので、ヘックスごとにビットマップを貼り付けても大丈夫な雰囲気かな。縦方向は小数になるのは仕方ないので、若干の誤差がでるけれど整数に丸めてしまうのがいいと思われる。

 そんなわけで、早速ガリガリとコードを書いてみた。線を3本しか描かない方法だと、上下左右の端の部分がきっちり六角形にならない。ので、端面処理は…ぶっちゃけ面倒なので省略して、真ん中の美味しい部分だけビットマップに描画している。


package ;
import nme.display.Bitmap;
import nme.display.BitmapData;
import nme.display.Shape;
import nme.display.Sprite;
import nme.events.MouseEvent;

/**
* ...
* @author kani-miso
*/

class HexBase extends Sprite
{
// 定数
var mapMax = 21; // マップの縦横最大値
var hexSize = 40; // ヘックスの直径
// ヘックスの縦横サイズ(全体で何度も使う)
var xSize:Int;
var ySize:Int;
// マウス位置のヘックスを示すためのカーソル
var cursor:Shape;
// 描画用のShape
var shape:Shape;
// 描画用のbitmapセット
var bitmapData:BitmapData;
var bitmap:Bitmap;

public function new()
{
super();
// Hexの縦幅、横幅の計算
xSize = Std.int(1 + hexSize * 3 / 4 / 8) * 8; // 8の倍数にする
ySize = Std.int(xSize / 3 * Math.sqrt(3));

// 表示位置を少しずらす
x = 40;
y = 40;

// 描画用のShapeを準備
shape = new Shape();
//addChild(shape);

// 背景色
shape.graphics.beginFill(0xEEFFEE);
shape.graphics.drawRect(0, 0, xSize * (mapMax - 1), ySize * (mapMax - 1) );
shape.graphics.endFill();

// 描画
drawHex(); // 六角形の描画
drawLine(); // 青マスの描画

// bitmapに描画
bitmapData = new BitmapData(xSize * (mapMax - 1), ySize * (mapMax - 1));
bitmapData.draw(shape);
bitmap = new Bitmap(bitmapData);
addChild(bitmap);

// カーソルを準備してマウスが動いたら動かす
initCursor();
addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
}

private function drawHex():Void
{
// 線の色
shape.graphics.lineStyle(1, 0xFF0000);
var x:Int;
var y:Int;
var x3:Int = 3 * xSize >> 3;
var x5:Int = 5 * xSize >> 3;
for (x in 0 ... mapMax) {
// ループ内でのHexの中心点のX座標
var calcX:Int = x * xSize;
// ループ内でのHexの左上線、上線、右上線の端点X座標計算
var linePoint1:Int = calcX - x5;
var linePoint2:Int = calcX - x3;
var linePoint3:Int = calcX + x3;
var linePoint4:Int = calcX + x5;

for (y in 0 ... mapMax) {
if ((x + y) % 2 == 0) {
// ループ内でのY座標と、1個前のY座標を計算
var calcY:Float = y * ySize;
var calcBeforY:Float = (y - 1) * ySize;

// 中心円を描画
shape.graphics.drawCircle(calcX, calcY, 4);

// Hexの上側の線を描画
shape.graphics.moveTo(linePoint1, calcY);
shape.graphics.lineTo(linePoint2, calcBeforY);
shape.graphics.lineTo(linePoint3, calcBeforY);
shape.graphics.lineTo(linePoint4, calcY);
}
}
}
}

private function drawLine():Void
{
// 縦幅、横幅の計算
var xMax:Int = xSize * mapMax;
var yMax:Int = ySize * mapMax;
var calcX:Int;
var calcY:Int;

// 線の色
shape.graphics.lineStyle(1, 0x0000FF, 0.6);

// 描画(縦横の最大値が異なるならループを2回に分ける
var i:Int;
for (i in 0 ... mapMax) {
// 縦線
calcX = i * xSize + (xSize >> 1);
shape.graphics.moveTo(calcX, 0);
shape.graphics.lineTo(calcX, yMax);
// 横線
calcY = i * ySize + (ySize >> 1);
shape.graphics.moveTo(0, calcY);
shape.graphics.lineTo(xMax, calcY);
}

}

private function initCursor():Void
{
// Shapeを初期化して、適当なサイズの丸を中心に描画
cursor = new Shape();
cursor.graphics.lineStyle(2, 0x004400, 0.7);
cursor.graphics.drawCircle(0, 0, 8);
addChild(cursor);
}

private function onMouseMove(evt:MouseEvent):Void
{
// 青マス上での座標を得る
var x:Int = Std.int((evt.localX + xSize / 2 ) / xSize);
var y:Int = Std.int((evt.localY + ySize / 2 ) / ySize);
// ヘックス上で有効な値ならカーソルを動かす
cursor.x = x * xSize;
if ((x + y) % 2 == 0) {
cursor.y = y * ySize;
} else {
var mod:Int = Std.int(evt.localY + ySize / 2) % Std.int(ySize) - Std.int(ySize / 2);
if (mod > 0 ) {
cursor.y = (y + 1) * ySize;
} else {
cursor.y = (y - 1) * ySize;
}
}
}

}


 AS3と違って気をつける点はというと、今のところ…
  • for文が「 for ( i in 0 ... 100)」みたな書き方になる
  • 浮動小数から整数への型キャストが必須 「Std.int(小数値)」
  • HTML5の出力時、Sprite型からビットマップへdrawが出来ない。ShapeからならOK。
といったあたりであろうか。型キャストがプチ面倒なので、8で割る部分はビットシフト(「>> 3」)を使ってしまった。まぁ、たぶんキャストするより速度的にも早いんじゃないかな、と信じての使用である。

 さぁ…次は、何をしようか。マップの描画か、デバッグ用の数字の表示か、そんな辺りから始めてみようかな。
posted by 管理人 at 01:11| Comment(0) | 雑談 | このブログの読者になる | 更新情報をチェックする

2013年02月03日

ヘクスマップ

 コンピュータでシミュレーションゲーム的な物を作ろうとか考えた時に、最初に立ちふさがる壁の一つと言えばヘクスマップとか呼ばれる物ではなかろうか。二次元マップの癖に、移動方向がデジタル計算しやすい4でも8でもなく6方向だし、座標系が変なずれ方をしているからマップの配列での管理も面倒くさいし。ゲームの中身がどうこうではなく、「ヘクスマップ」という実装をしようとするだけで膨大なインターフェースだのライブラリだのを作らないといけない雰囲気がある。というか、あの六角形をどう扱ったらいいかわららないという、それだけの理由で挫折してしまうケースの方がぶっちゃけ多かろうと思われる。

 とはいえ、ふとそれっぽい物をちょっと作ってみたいなと思い立ってしまったので、少し考えてみようか。

 まず、座標系。プログラム内では「縦横」の二次元配列で考えるのが基本かと思われる。というか、それ以外で考えるのはちょっと小生にはムリ。というのも、プログラム内部での配列なんて物は、そもそも画面に出てこない。故に、頭のなかでイメージをしっかり持てるものでないとヤバイ事になる。だから管理用の配列を無理矢理六角形に合わせるくらいなら、画面に出てくる六角形の方を縦横系に無理矢理直す方がイメージしやすいはず。うまく理屈に出来ないけど、何だか小生のカンと経験がそう言っている。

 カンと経験はともかくとして。とりあえず自力でもいくつか表現方法を考えてみる。2〜3種類、もやもやしたイメージを持つ事は出来たけど、なんとなくどれも長所と短所がちょっとずつ見えて、判断つかず。

 じゃぁ…といって、ヘクスマップを普通の座標系に変換する方法をGoogle先生に色々相談してみる。小生が思いついた以外のスマートなアルゴリズムってないかしら。出来れば、楽に、スマートに実装できる物。要は楽していい思いがしたいわけであるが…そこまではムリかな。

 色々検索していると、何やら「お、これ使えるかな?」と思ったものを発見。小生がもやもやと考えていた物の一つが、ある程度まとまった形で載っているサイトがあった。ははは。似たようなことを考える人ってのは世の中に居るわけで、しかもそういう人ってのは小生なんかよりずっと先に進んでるわけなんだなぁ。

 というわけで、その見つけた方法を採用。考え方はそんなに難しくない。段違いになっている部分を無理矢理1行と数える考え方を放棄して、段違いは別の行とする。で、段違い故にモノが無い場所は「使わない座標」と考える方法である。

HexAlgo01.png

 赤線の六角形に対して、青線のようにマス目を入れる。内部管理はこのマス目に従えば、管理的にはすごく楽である。まぁ、メモリ的には半数が無駄になるわけだが、内部的には使わない部分を左詰めでもしてやれば良い。その辺は後でどうにでも出来る気がするので気にしない。

 で、こうすると、青のマス目の中心にヘックスの中心「○」がある物と、ヘックスの境界線「―」がある物の二種類が、市松模様で並んでいることがわかる。市松模様ということは、X座標とY座標を足して偶数(あるいは奇数)だとか、排他的論理和を取って末尾1ビットが0(あるいは1)だとか、そういう方法で「○」か「―」かが簡単に検出出来る事を意味する。あとは、マウスカーソルが乗った時などは「―」のマスだったら、上半分か下半分かを調べて上側なり下側なりのマスを指すようにしてやれば良い。

 距離の測り方についてはどうだろう。青マスの座標系でX方向、Y方向のそれぞれの絶対値(以下dX、dYとする)を取っておいて、dX≧dYならば単純にdXが距離になるし、そうでなければ ( dX + dY ) / 2 が移動距離になる。

 射撃時の射角のように、向きと範囲が絡む場合はどうなるか。具体的には、ある方向を向いている時に、その正面60度の範囲内に目標があるかどうかの判断である。これについても、上向きと下向きはdX≦dYの判断で出来そうだ。また、それ以外の4方向は青マス座標系での右上、左上、右下、左下で判断が出来る。境界線を含めるか含めないかは、不等号を「≦」にするか「<」にするかで対応出来よう。

 射線上の障害物などを判断するために、直線を引いた時の通過ヘクスを扱うならどうするか。これはちょっと悩んだが、青マス座標系で通過するマスを特定して、「―」のマスだけ無視すれば良さそうに思える。青マスでの通過部分はというと、二次元系ならいくらでもアルゴリズムが探せるだろう。

 移動についてはどうだろう。例えばダイクストラ法…要は起点から1マスずつしらみ潰しで最短距離を探して行く方法)を使う場合、普通の二次元座標であれば1マスについて縦横4マス、八方向移動が出来るなら8マス分の「隣接する」経路を考えるわけだ。ヘックスでの上下の隣は、青マス座標系で見ると2マス上下である故、ダイクストラ法を2次元系に特化したようなアルゴリズムはそのままでは使えない。とはいえ、「上下左右」ではなく、「2マス上、2マス下、右上、右下、左上、左下」の6マス分の経路を入れてやれば基本的な部分はそのままでも済みそうな予感がする。

 ふむ。何とかなりそうな気がしてきた。もうちょっと掘り下げて、Haxe + NME で何かつくってみようかな。

 余談ながら、ここでヘクスマップのアルゴリズムの参考としたサイトでは、小生の表記と比較すると90度回転した物となっていた。つまり、無視すべきマスは「―」ではなく「|」であり、ここで「縦方向」と称した物はそのサイトでは「横方向」であった。が、昔ながらのボードゲーム系ヘクスマップでは、参考サイトを90度横にした形の物が多かったと記憶している故、これを横にして焼き直してみた感じである。
posted by 管理人 at 13:43| Comment(0) | ソフトウェア | このブログの読者になる | 更新情報をチェックする

広告


この広告は60日以上更新がないブログに表示がされております。

以下のいずれかの方法で非表示にすることが可能です。

・記事の投稿、編集をおこなう
・マイブログの【設定】 > 【広告設定】 より、「60日間更新が無い場合」 の 「広告を表示しない」にチェックを入れて保存する。


×

この広告は1年以上新しい記事の投稿がないブログに表示されております。