Quantcast
Channel: itouhiroはてなブログ
Viewing all articles
Browse latest Browse all 107

ActionScript3.0でRPG(ドラクエふう)のマップを作成

$
0
0

RPG(ドラクエふう)のマップをFlashで作成する。

f:id:itouhiro:20130519002137p:plain


マップ部品は「First Seed Material」http://www.tekepon.net/fsmの提供フリー素材「マップチップ」「キャラクターチップ」を使用させていただく。

注意書き

「このソフトは、REFMAPが配布しているフリー画像素材を使用してます。このソフト内で使用されている画像を、このゲームを遊ぶ以外の用途には使用しないで下さい」

しかしこのマップチップは「RPGツクール2000」で使うのが主な用途で、それ以外の用途で使うには工夫が必要だ。私はツクール使わずFlashでRPG作るつもりなので、
16px x 16px のチップを並べて作られた1枚のBMP画像を、使いやすくしてみる。


透過

透過設定がしてないので、透過処理をする

  • Photoshopで読み込み
  • colorModeをRGBに変更
  • layer名をdoubleClickして、Layer名を'Background'から'Layer 0'に変更。
  • 透過色をMagicWandで選択。そのとき Contiguous(隣接) オプションのチェックを外せば、透過色をすべて選択できる。
  • 削除
  • png形式で保存。PNG-8でも、PNG-24(alpha含めると実質PNG-32)でもよい。

f:id:itouhiro:20130517231541p:plain


グリッド線をひいて、番号をつけておく

Photoshopの自動化スクリプトJSXを使い、16pxごとにグリッド線をひいて、番号を表示する。この画像を見ながらチップを配置する。

f:id:itouhiro:20130519002610p:plain


line16x16px.jsx

//線の太さ
lineWidth = 1;

//線の間隔
pxBetweenX = 16;
pxBetweenY = 16;

//foreground color: 以下のdrawLine()で使う線の色
theColor = new SolidColor()
theColor.rgb.hexValue = 'FFFFFF';

cTID = function(s){ return charIDToTypeID(s); };
sTID = function(s){ return stringIDToTypeID(s); };
//線を引く関数はコピペさせていただいた
//http://forums.adobe.com/thread/803261
function drawLine( startXY, endXY, width ) {
  var desc = new ActionDescriptor();
  var lineDesc = new ActionDescriptor();
  var startDesc = new ActionDescriptor();
  startDesc.putUnitDouble( cTID('Hrzn'), cTID('#Pxl'), startXY[0] );
  startDesc.putUnitDouble( cTID('Vrtc'), cTID('#Pxl'), startXY[1] );
  lineDesc.putObject( cTID('Strt'), cTID('Pnt '), startDesc );
  var endDesc = new ActionDescriptor();
  endDesc.putUnitDouble( cTID('Hrzn'), cTID('#Pxl'), endXY[0] );
  endDesc.putUnitDouble( cTID('Vrtc'), cTID('#Pxl'), endXY[1] );
  lineDesc.putObject( cTID('End '), cTID('Pnt '), endDesc );
  lineDesc.putUnitDouble( cTID('Wdth'), cTID('#Pxl'), width );
  desc.putObject( cTID('Shp '), cTID('Ln  '), lineDesc );
  //desc.putBoolean( cTID('AntA'), true );
  desc.putBoolean( cTID('AntA'), false );
  executeAction( cTID('Draw'), desc, DialogModes.NO );
};

// 幅・高さを調べる
//perferences.rulerUnits = Units.PIXELS;
width = activeDocument.width;
height = activeDocument.height;

// color mode変更
activeDocument.changeMode(ChangeMode.RGB)

// layer追加
theLayerSet = activeDocument.layerSets.add()
theLayer = theLayerSet.artLayers.add()

// foreground color指定
app.foregroundColor = theColor;

// 線を引く
y = 0;
for (i=0; i<height; i+=pxBetweenY){
  drawLine([0,y], [width,y], lineWidth);
  y += pxBetweenY;
}
x = 0;
for (i=0; i<width; i+=pxBetweenX){
  drawLine([x,0], [x,height], lineWidth);
  x += pxBetweenX;
}

// 文字を置く
function putText(num, x, y){
  theLayer = theLayerSet.artLayers.add();
  theLayer.kind = LayerKind.TEXT;
  theLayer.textItem.font = 'Courier New';
  theLayer.textItem.size = 9;
  theLayer.textItem.color = theColor;
  theLayer.textItem.antiAliasMethod = AntiAlias.NONE;
  theLayer.textItem.position = [x+7, y+9];
  str = '0123456789abcdefghijklmnopqrstuvwxyz'.charAt(num);
  theLayer.textItem.contents = str;
  theLayer.rasterize(RasterizeType.TEXTCONTENTS);
  if(num!=0) theLayer.merge();
};
for (i=0; i<height/pxBetweenY; i++){
  putText(i, 0, i * pxBetweenY);
}
for (i=1; i<width/pxBetweenX; i++){
  putText(i, i * pxBetweenX, 0);
}

Flash

16x16 pxの大きさは表示するには小さいので、3倍の 48x48px に拡大して貼ることにしたい。
Flash ActionScript3.0のcopyPixels()は等倍コピーなので、拡大するにはdraw()で拡大したBitmapDataを用意してからcopyPixels()すればよいようだ。


参考


この画像をswfにEmbed(埋め込み)して、表示する。
f:id:itouhiro:20130518112652p:plain

package 
{
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.display.PixelSnapping;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.geom.Matrix;
    import flash.geom.Point;
    import flash.geom.Rectangle;
    
    /**
     * ...
     * @author itouhiro
     */
    [SWF(width="480",height="480",backgroundColor="0x000000",frameRate="60")]
    public class Main extends Sprite 
    {
        //[Embed(source = "tekepon.net-fsm-road02_a.png", mimeType="image/png")] //←これでもよい
        [Embed(source = "tekepon.net-fsm-road02_a.png")]
        private var imgSrc:Class;
        
        public function Main():void 
        {
            if (stage) init();
            else addEventListener(Event.ADDED_TO_STAGE, init);
        }
        
        private function init(e:Event = null):void 
        {
            removeEventListener(Event.ADDED_TO_STAGE, init);
            // entry point
            
            var mapTipOriginal:Bitmap = new imgSrc();
            //BitmapDataの第4引数を0x00000000にしておかないと、copyPixels(,,,null,null,true)のときalpha部分が透過しないで白になる
            //   ref. http://www.kongregate.com/forums/4-game-programming/topics/250811
            var mapTip:BitmapData = new BitmapData(mapTipOriginal.width * 3, mapTipOriginal.height * 3, true, 0x00000000);
            
            //3倍 (16px→48px)に拡大してcopy
            var mtx:Matrix = new Matrix();
            mtx.scale(3, 3);
            mapTip.draw(mapTipOriginal, mtx);
            
            //背景用BitmapDataにマップチップの「草原」部分をcopy
            var bgSrc:BitmapData = new BitmapData(480, 480, false, 0x888888);
            var x:int; var y:int;
            for (y = 0; y < mapTip.height; y+=48) {
                for (x = 0; x < mapTip.width; x+=48) {
                    bgSrc.copyPixels(mapTip, new Rectangle(0,8*48,48,48), new Point(x, y));
                }
            }
            //「大樹」を配置します
            bgSrc.copyPixels(
                mapTip,
                new Rectangle(26 * 48, 11 * 48, 4 * 48, 5 * 48),
                new Point(2 * 48, 3 * 48),
                null,
                null,
                true);
            
            //BitmapDataを表示するためには、Bitmapが必要
            var bg:Bitmap = new Bitmap(bgSrc);
            addChild(bg);
        }
    }
}

キャラ

キャラチップは 24x32px なのか‥‥。
f:id:itouhiro:20130518111520p:plain


先ほどのjsxを少し変更して、24x32でグリッドひいて数字表示。

//線の太さ
lineWidth = 1;

//線の間隔
pxBetweenX = 24;
pxBetweenY = 32;

//foreground color: 以下のdrawLine()で使う線の色
theColor = new SolidColor()
theColor.rgb.red = 0;
theColor.rgb.green = 0;
theColor.rgb.blue = 0;

cTID = function(s){ return charIDToTypeID(s); };
sTID = function(s){ return stringIDToTypeID(s); };
//線を引く関数はコピペさせていただいた
//http://forums.adobe.com/thread/803261
function drawLine( startXY, endXY, width ) {
  var desc = new ActionDescriptor();
  var lineDesc = new ActionDescriptor();
  var startDesc = new ActionDescriptor();
  startDesc.putUnitDouble( cTID('Hrzn'), cTID('#Pxl'), startXY[0] );
  startDesc.putUnitDouble( cTID('Vrtc'), cTID('#Pxl'), startXY[1] );
  lineDesc.putObject( cTID('Strt'), cTID('Pnt '), startDesc );
  var endDesc = new ActionDescriptor();
  endDesc.putUnitDouble( cTID('Hrzn'), cTID('#Pxl'), endXY[0] );
  endDesc.putUnitDouble( cTID('Vrtc'), cTID('#Pxl'), endXY[1] );
  lineDesc.putObject( cTID('End '), cTID('Pnt '), endDesc );
  lineDesc.putUnitDouble( cTID('Wdth'), cTID('#Pxl'), width );
  desc.putObject( cTID('Shp '), cTID('Ln  '), lineDesc );
  //desc.putBoolean( cTID('AntA'), true );
  desc.putBoolean( cTID('AntA'), false );
  executeAction( cTID('Draw'), desc, DialogModes.NO );
};

// [効果 - 光彩]の関数もコピペさせていただいた
// http://stackoverflow.com/questions/7696212/how-do-you-modify-blending-options-with-a-photoshop-script
function addStyleGlow( R, G, B, blendingMode, opacity, spread, size ){
	var desc1 = new ActionDescriptor();
	var ref1 = new ActionReference();
	ref1.putProperty(cTID('Prpr'), cTID('Lefx'));
	ref1.putEnumerated(cTID('Lyr '), cTID('Ordn'), cTID('Trgt'));
	desc1.putReference(cTID('null'), ref1);
	var desc2 = new ActionDescriptor();
	desc2.putUnitDouble(cTID('Scl '), cTID('#Prc'), 100);
	// Glow color
	var desc4 = new ActionDescriptor();
	var rgb = new Array();
	desc4.putDouble(cTID('Rd  '), R);
	desc4.putDouble(cTID('Grn '), G);
	desc4.putDouble(cTID('Bl  '), B);
	// Blending mode of the effect
	var desc3 = new ActionDescriptor();
	desc3.putBoolean(cTID('enab'), true);
	desc3.putEnumerated( cTID('Md  '), cTID('BlnM'), cTID(blendingMode) );
	desc3.putObject(cTID('Clr '), sTID("RGBColor"), desc4);
	// Opacity
	desc3.putUnitDouble(cTID('Opct'), cTID('#Prc'), opacity);
	desc3.putEnumerated(cTID('GlwT'), cTID('BETE'), cTID('SfBL'));
	// Spread
	desc3.putUnitDouble(cTID('Ckmt'), cTID('#Pxl'), spread);
	// Size
	desc3.putUnitDouble(cTID('blur'), cTID('#Pxl'), size);
	// Noise
	desc3.putUnitDouble(cTID('Nose'), cTID('#Prc'), 0);
	// Quality: Jitter
	desc3.putUnitDouble(cTID('ShdN'), cTID('#Prc'), 0);
	desc3.putBoolean(cTID('AntA'), true);
	var desc5 = new ActionDescriptor();
	desc5.putString(cTID('Nm  '), "Linear");
  //desc5..putString(cTID('Nm  '), "\x90\xFC\x8C\x60" ); //Shift-JIS '線形'
	desc3.putObject(cTID('TrnS'), cTID('ShpC'), desc5);
	// Quality: Range
	desc3.putUnitDouble(cTID('Inpr'), cTID('#Prc'), 50);
	desc2.putObject(cTID('OrGl'), cTID('OrGl'), desc3);
	desc1.putObject(cTID('T   '), cTID('Lefx'), desc2);
	executeAction(cTID('setd'), desc1, DialogModes.NO);
};

// 幅・高さを調べる
//perferences.rulerUnits = Units.PIXELS;
width = activeDocument.width;
height = activeDocument.height;

// color mode変更
activeDocument.changeMode(ChangeMode.RGB)

// layer追加
theLayerSet = activeDocument.layerSets.add()
theLayer = theLayerSet.artLayers.add()

// foreground color指定
app.foregroundColor = theColor;

// 線を引く
y = 0;
for (i=0; i<height; i+=pxBetweenY){
  drawLine([0,y], [width,y], lineWidth);
  y += pxBetweenY;
}
x = 0;
for (i=0; i<width; i+=pxBetweenX){
  drawLine([x,0], [x,height], lineWidth);
  x += pxBetweenX;
}

// 文字を置く
function putText(num, x, y){
  theLayer = theLayerSet.artLayers.add();
  theLayer.kind = LayerKind.TEXT;
  theLayer.textItem.font = 'Courier New';
  theLayer.textItem.size = 9;
  theLayer.textItem.color = theColor;
  theLayer.textItem.position = [x, y];
  str = ('0' + num).slice(-2);
  theLayer.textItem.contents = str;
  theLayer.rasterize(RasterizeType.TEXTCONTENTS);
  if(num!=0) theLayer.merge();
};
for (i=0; i<height/pxBetweenY; i++){
  putText(i, 4, i * pxBetweenY + 9);
}
for (i=1; i<width/pxBetweenX; i++){
  putText(i, i * pxBetweenX + 4, 9);
}
addStyleGlow(255, 255, 255, 'Nrml', 100, 25, 5);

日本語PhotoshopCS6 (Locales\ja_JP\Support Files\tw10428.dat をrenameしてない場合)では最後の「光彩(外側)」(outer glow)がエラーになるが、別に問題はない。数字が見にくいので文字を縁取りしてるだけ。そのレイヤーを手動で「光彩(外側)」すればいい。


キャラをFlashに配置

マップチップは36進数(0-9a-z) 2桁で表すことにする。
キャラも配置してみた。

実際のFlash。一度クリックすると、カーソルキーで操作できる。当たり判定や重ね順はまだ作ってないので正しくない。


ソース
Main.as

package 
{
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.display.PixelSnapping;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.KeyboardEvent;
    import flash.geom.Matrix;
    import flash.geom.Point;
    import flash.geom.Rectangle;
    import flash.ui.Keyboard;
    
    /**
     * ...
     * @author itouhiro
     */
    [SWF(width="480",height="480",backgroundColor="0x1059FE",frameRate="30")]
    public class Main extends Sprite 
    {
        [Embed(source = "tekepon.net-fsm-road02_a.png")]
        private var MapSrcFile:Class;
        
        [Embed(source = "tekepon.net-fsm-chara01_a.png")]
        private var CharaSrcFile:Class;
        
        private var chara:Bitmap;
        private var charaTip:Array;
        private var count:int = 0;
        private var index:int = 0;

        public function Main():void 
        {
            if (stage) init();
            else addEventListener(Event.ADDED_TO_STAGE, init);
        }
        
        private function init(e:Event = null):void 
        {
            removeEventListener(Event.ADDED_TO_STAGE, init);
            // entry point
            
            var mapSrcOriginal:Bitmap = new MapSrcFile();
            //BitmapDataの第4引数を0x00000000にしておかないと、copyPixels(,,,null,null,true)のときalpha部分が透過しないで白になる
            //   ref. http://www.kongregate.com/forums/4-game-programming/topics/250811
            var mapSrc:BitmapData = new BitmapData(mapSrcOriginal.width * 2, mapSrcOriginal.height * 2, true, 0x00000000);
            
            //2倍 (16px→32px)に拡大してcopy
            var mtx:Matrix = new Matrix();
            mtx.scale(2, 2);
            mapSrc.draw(mapSrcOriginal, mtx);
            
            //背景用BitmapDataにマップチップをmapping
            var bgMap:Array = [];
            bgMap.push('-- -- -- -- -- -- -- -- f2 08 68 08 08 0c 1e'.split(' '));
            bgMap.push('-- -- -- -- -- -- l8 m8 c3 08 08 08 08 08 0f'.split(' '));
            bgMap.push('-- -- -- l8 m8 m8 08 08 c4 c3 08 08 08 g3 g3'.split(' '));
            bgMap.push('q8 r8 s8 f2 08 08 08 08 c4 c4 c2 08 e2 h7 d4'.split(' '));
            bgMap.push('-- -- -- f2 08 69 79 89 c5 c4 c2 08 e2 h7 d4'.split(' '));

            bgMap.push('-- -- -- f2 08 6b 7b 8b 08 c5 c2 08 e2 eb 08'.split(' '));
            bgMap.push('l8 m8 m8 f3 08 08 08 69 08 08 08 08 65 75 75'.split(' '));
            bgMap.push('f2 08 08 c4 f3 d3 d3 08 08 08 08 08 67 76 75'.split(' '));
            bgMap.push('f2 08 08 c5 c4 d4 d4 f3 08 08 08 08 08 67 77'.split(' '));
            bgMap.push('f2 08 08 08 c5 d5 d5 c4 f2 08 08 e1 d3 d3 d3'.split(' '));
            
            bgMap.push('f2 08 08 08 60 08 08 c4 c2 08 08 h2 h7 d4 d4'.split(' '));
            bgMap.push('f2 l6 e1 h6 08 08 08 c5 c2 08 08 h2 h7 d4 d4'.split(' '));
            bgMap.push('f2 m6 e2 h7 c3 08 08 08 08 08 08 08 eb 08 08'.split(' '));
            bgMap.push('f2 m6 e2 h7 c4 c3 08 08 08 08 08 08 08 08 08'.split(' '));
            bgMap.push('f2 m7 e2 h7 c4 c4 c3 08 08 08 08 08 08 08 08'.split(' '));
            
            var bgBoard:BitmapData = new BitmapData(480, 480, true, 0x00000000);
            var i:int; var j:int;
            var str36:String = '0123456789abcdefghijklmnopqrstuvwxyz';
            var sz:int = 32; //tip size
            var idxX:int;
            var idxY:int;
            for (i = 0; i < bgMap.length; i++) {
                for (j = 0; j < bgMap[i].length; j++) {
                    if (bgMap[i][j] === '--') continue;
                    idxX = str36.indexOf(bgMap[i][j].slice(0, 1));
                    idxY = str36.indexOf(bgMap[i][j].slice(1, 2));
                    //trace('bgMap[' + i + '][' + j + ']=' + bgMap[i][j] + ', idxY=' + idxY + ', idxX=', idxX );
                    bgBoard.copyPixels(mapSrc, new Rectangle(idxX*sz,idxY*sz,sz,sz), new Point(j*sz,i*sz), null, null, true);
                }
            }
            //bitmapDataを表示するためには、Bitmapが必要
            var bg:Bitmap = new Bitmap(bgBoard);
            bg.x = -12; bg.y = -8;
            addChild(bg);
            
            //背景 上層 (樹木など)
            bgMap = [];
            bgMap.push('-- -- -- -- -- -- -- -- -- -- -- of pf -- --'.split(' '));
            bgMap.push('-- -- -- -- -- -- -- -- -- -- -- -- -- p4 --'.split(' '));
            bgMap.push('-- -- -- t7 -- -- oe pe -- -- -- -- -- -- --'.split(' '));
            bgMap.push('-- -- -- -- -- -- of pf -- -- -- -- -- -- --'.split(' '));
            bgMap.push('-- -- -- -- -- -- -- -- -- -- -- -- -- -- --'.split(' '));
            
            bgMap.push('-- -- -- -- oe pe j9 -- -- -- -- -- -- -- --'.split(' '));
            bgMap.push('je ke -- -- of pf -- -- -- -- -- -- -- -- --'.split(' '));
            bgMap.push('jf kf -- -- -- -- -- -- -- -- -- -- -- -- --'.split(' '));
            bgMap.push('-- -- -- -- -- -- -- -- -- -- -- -- -- -- --'.split(' '));
            bgMap.push('-- -- -- -- -- -- -- -- -- -- -- -- -- -- --'.split(' '));
            
            bgMap.push('-- -- -- -- -- -- -- qb rb sb tb -- -- -- --'.split(' '));
            bgMap.push('-- -- -- -- -- -- -- qc rc sc tc -- -- -- --'.split(' '));
            bgMap.push('-- -- -- -- -- -- -- qd rd sd td -- -- -- ia'.split(' '));
            bgMap.push('-- -- -- -- -- -- -- qe re se te -- -- -- --'.split(' '));
            bgMap.push('-- -- -- -- -- -- -- qf rf sf tf -- -- -- --'.split(' '));
            var bgUpperBoard:BitmapData = new BitmapData(480, 480, true, 0x00000000);
            for (i = 0; i < bgMap.length; i++) {
                for (j = 0; j < bgMap[i].length; j++) {
                    if (bgMap[i][j] === '--') continue;
                    idxX = str36.indexOf(bgMap[i][j].slice(0, 1));
                    idxY = str36.indexOf(bgMap[i][j].slice(1, 2));
                    bgUpperBoard.copyPixels(mapSrc, new Rectangle(idxX*sz,idxY*sz,sz,sz), new Point(j*sz,i*sz), null, null, true);
                }
            }
            
            
            //キャラ
            var charaSrcOriginal:Bitmap = new CharaSrcFile();
            var charaSrc:BitmapData = new BitmapData(charaSrcOriginal.width * 2, charaSrcOriginal.height * 2, true, 0x00000000);
            mtx = new Matrix();
            mtx.scale(2, 2);
            charaSrc.draw(charaSrcOriginal, mtx);
            var szX:int = 24 * 2; //tip size X
            var szY:int = 32 * 2;
            //キャラクターチップを分割
            charaTip = [];
            for (y = 0; y <= charaSrc.height/szY; y++) {
                charaTip[y] = [];
                for (x = 0; x < charaSrc.width/szX; x++) {
                    charaTip[y][x] = new BitmapData(szX, szY, true, 0x00000000);
                    charaTip[y][x].copyPixels(charaSrc, new Rectangle(x * szX, y * szY, szX, szY), new Point(0, 0));
                }
            }
            //キャラを表示
            chara = new Bitmap(charaTip[2][3]);
            chara.x = 8 * sz - 12; chara.y = 7 * sz - 8;
            addChild(chara);
            stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler);
            
            //背景 上層
            var bgUpper:Bitmap = new Bitmap(bgUpperBoard);
            bgUpper.x = -12; bgUpper.y = -8;
            addChild(bgUpper);
        }
        
        private function keyDownHandler(e:KeyboardEvent):void {
            var pattern:Array = [0, 1, 2, 1]; //0→1→2→0→1だとヘン。0→1→2→1→0→1にする。
            var moveUnit:int = stage.frameRate / 8; //移動単位 px
            var charaKind:int = 3;
            count++;
            if (count % (stage.frameRate/6) === 0){
                index = (index + 1) % pattern.length;
            }
            if (e.keyCode === Keyboard.UP) {
                chara.bitmapData = charaTip[0][pattern[index]+charaKind];
                chara.y -= moveUnit;
            } else if (e.keyCode === Keyboard.RIGHT) {
                chara.bitmapData = charaTip[1][pattern[index]+charaKind];
                chara.x += moveUnit;
            } else if (e.keyCode === Keyboard.DOWN) {
                chara.bitmapData = charaTip[2][pattern[index]+charaKind];
                chara.y += moveUnit;
            } else if (e.keyCode === Keyboard.LEFT) {
                chara.bitmapData = charaTip[3][pattern[index]+charaKind];
                chara.x -= moveUnit;
            }
        }
    }
}

マップチップを数字見て配置するのがたいへんだ。配置ツール作らないと作業効率だ。

あと マップチップ配置に、http://www.tekepon.net/fsm/modules/refmap/index.php?mode=map&sort=released&cid=4の「プレビュー」で見ることのできる画像を参考にさせていただいたんだが、「プレビューにあるチップが、ダウンロード素材に含まれてないことがあるような‥‥。RPGツクール2000なら使えるのかな?





 


Viewing all articles
Browse latest Browse all 107

Latest Images

Trending Articles