2013年02月09日 01:11

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」)を使ってしまった。まぁ、たぶんキャストするより速度的にも早いんじゃないかな、と信じての使用である。

 さぁ…次は、何をしようか。マップの描画か、デバッグ用の数字の表示か、そんな辺りから始めてみようかな。
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

※ブログオーナーが承認したコメントのみ表示されます。
×

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