AS3でゲームを作成する その38 | Photoshop CC Tutorials
今回は敵キャラが壁をよけながらゴールを目指すプログラムを作成しました。
コンピュータ(敵)が壁やゴールの位置を判断し自動で動きます。

参考文献サイト:http://itpro.nikkeibp.co.jp/article/COLUMN/20070223/263174/?ST=develop

できあがりはこちらをクリック

実行結果
$ピック社長のブログ

Main.as
package 
{
/**
* ロボットを移動させる道を得るためのプログラム
*/
import flash.display.Sprite;
import flash.events.Event;
import flash.display.Loader;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.system.LoaderContext;
import flash.net.URLRequest;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.ui.Keyboard;

public class Main extends Sprite
{
private var loader_01:Loader;
private var loader_02:Loader;
private var source_01:BitmapData;
private var source_02:BitmapData;
private var output:BitmapData;
private var display:Display;

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
loader_01 = new Loader();
loader_01.contentLoaderInfo.addEventListener("complete", loadingComplete_01);
loader_01.load(new URLRequest("./bomberimage.png"), new LoaderContext(true));

// 画面の準備
output = new BitmapData(480, 480);
var bitmap:Bitmap = new Bitmap(output);
bitmap.x = 0;
bitmap.y = 0;
addChild(bitmap);
}

public function loadingComplete_01(e:Event):void
{
source_01 = new BitmapData(320, 128, true, 0x00000000);
source_01.draw(loader_01);
loadCharImg();
}

// キャラクター画像の読み込み
public function loadCharImg():void
{
loader_02 = new Loader();
loader_02.contentLoaderInfo.addEventListener("complete", loadingComplete_02);
loader_02.load(new URLRequest("./enemy.png"), new LoaderContext(true));
}

public function loadingComplete_02(e:Event):void
{
source_02 = new BitmapData(96, 256, true, 0x00000000);
source_02.draw(loader_02);
display = new Display(source_01, source_02);
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}

private function onEnterFrame(e:Event):void
{
display.render(output);
display.enemyMove();
}

}

}


Display.as
package 
{
/**
* 経路探索アルゴリズム
*/
import flash.display.BitmapData;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.media.Sound;
import flash.net.URLRequest;

public class Display
{
private const MAP_WIDTH:int = 15;/* マップのサイズ */
private const MAP_HEIGHT:int = 5;

private const ROBO_XPOS:int = 0;/* ロボットの位置 */
private const ROBO_YPOS:int = 2;

private const GOAL_XPOS:int = 14;/* ゴールの位置 */
private const GOAL_YPOS:int = 2;

private const ATTR_EMPTY:int = 0;/* マップの1マスぶんの内容 */
private const ATTR_SEARCH:int = 1;
private const ATTR_CLOSE:int = 2;
private const ATTR_WALL:int = 3;
private const ATTR_GOAL:int = 4;
private const ATTR_START:int = 5;
private const ATTR_PATH:int = 6;

/* マップ全体を示すmapfield配列 */
private var mapfield:Array = [MAP_HEIGHT];
/* 探索地点を集めるバッファ */
private const SEARCHQUE_LIMIT:int = 50;
private var searchque:Array = [SEARCHQUE_LIMIT];
private var searchque_head:int = 0;

// 敵キャラの初期位置
public var charX:int = ROBO_XPOS * 32;
public var charY:int = (ROBO_YPOS - 1) * 32;

private var tile:Vector.;
private var tile_char:Vector.;
private var charAnimTime:int = 0;

// 敵キャラを動かすために必要なノードを格納する
private var moveInfo:Array = new Array();

public var keyState:int = 1; // 敵キャラの向き

public function Display(source_01:BitmapData, source_02:BitmapData)
{
map_init();
root_search(ROBO_XPOS, ROBO_YPOS, GOAL_XPOS, GOAL_YPOS);

// コンピュータが動くために必要なノードをmoveInfo[]に格納する
for (var j:int = 0; j < MAP_WIDTH; j++ ) {
for (var i:int = 0; i < MAP_HEIGHT; i++ ) {
if (mapfield[i][j].attr == ATTR_PATH) {
moveInfo.push(mapfield[i][j]);
}
}
}

tile = new Vector.;
tile.length = 40;
tile.fixed = true;
tile_char = new Vector.;
tile_char.length = 12;
tile_char.fixed = true;

for (i = 0; i < 40; i++) {
tile[i] = new BitmapData(32, 32);
}

for (j = 0; j < 4; j++){
for (i = 0; i < 10;i++){
tile[j*10+i].copyPixels(source_01, new Rectangle(i*32, j*32, i*32+32, j*32+32), new Point(0, 0));
}
}

// キャラクター
for (i = 0; i < 12; i++) {
tile_char[i] = new BitmapData(32, 64);
}

var k:int = 0;
var l:int = 64;
for (i = 0; i < 12; i += 3) {
tile_char[i + 0].copyPixels(source_02, new Rectangle( 0, k, 32, l), new Point(0, 0));
tile_char[i + 1].copyPixels(source_02, new Rectangle(32, k, 64, l), new Point(0, 0));
tile_char[i + 2].copyPixels(source_02, new Rectangle(64, k, 96, l), new Point(0, 0));

k += 64;
l += 64;
}
}

/* マップの初期化 */
public function map_init():void
{
var j:int, i:int;

for (i = 0; i < MAP_HEIGHT; i++) {
mapfield[i] = new Array(MAP_WIDTH);
for (j = 0; j < MAP_WIDTH; j++) {
mapfield[i][j] = new t_node();
mapfield[i][j].pos.x = j;
mapfield[i][j].pos.y = i;
}
}

/* 障害物の設定 */
for (i = 0; i < 4; i++) {
mapfield[i][10].attr = ATTR_WALL;
mapfield[(MAP_HEIGHT - 1) - i][4].attr = ATTR_WALL;
}
}

/* -------------------------------------------------------- */
/* 2点間の距離を測る */
public function distance(pos1:t_point, pos2:t_point):int
{
var dx:int, dy:int, d:int;

dx = Math.abs(pos1.x - pos2.x);
dy = Math.abs(pos1.y - pos2.y);

d = ((dx < dy) ? dx : dy) / 2;
return dx + dy - d;
}

/* コストを計る */
public function poscost(pos:t_point, checkpos:t_point):int
{
var cost:int;

/* 2点間の距離をそのままコストに */
cost = distance(pos, checkpos);
return cost;
}

/* -------------------------------------------------------- */
/* キューに探索候補を追加 */
public function que_push(node:t_node):void
{
var tmpnode:t_node;
var i:int, j:int;

if (searchque_head >= SEARCHQUE_LIMIT)
return;
searchque[searchque_head] = node;
searchque_head++;

// コストの低い順でソートしておく
if (searchque_head <= 1)
return;
for (i = 0; i < searchque_head; i++) {
for (j = 1; j < searchque_head - i; j++) {
if (searchque[j - 1].cost < searchque[j].cost) {
tmpnode = searchque[j - 1];
searchque[j - 1] = searchque[j];
searchque[j] = tmpnode;
}
}
}
}

/* キューから探索候補を取り出す */
public function que_pop():t_node
{
if (searchque_head == 0)
return null;
searchque_head--;
return searchque[searchque_head];
}

/* -------------------------------------------------------- */
/* 探索されたパスをたどる */
public function path_check(goaly:int, goalx:int):void
{
var x:int, y:int, nextx:int, nexty:int;

nextx = mapfield[goaly][goalx].backpos.x;
nexty = mapfield[goaly][goalx].backpos.y;
do {
x = nextx;
y = nexty;
mapfield[y][x].attr = ATTR_PATH;
nextx = mapfield[y][x].backpos.x;
nexty = mapfield[y][x].backpos.y;
} while(mapfield[nexty][nextx].attr != ATTR_START);
}


/* -------------------------------------------------------- */
/* -------------------------------------------------------- */
/* 探索本体 */
public function root_search(startx:int, starty:int, goalx:int, goaly:int):void
{
/* 8方向を調べるための相対座標 */
var difpos:Array = [ { x: -1, y: -1 }, { x: 0, y: -1 }, { x:1, y: -1 },
{ x: -1, y: 0 }, { x: 1, y: 0 },
{ x: -1, y: 1 }, { x: 0, y: 1 }, { x:1, y: 1 } ];

var i:int, checkx:int, checky:int, cost:int, checkcount:int;
var checkxCopy:int, checkyCopy:int;
var nowpos:t_node;
searchque_head = 0;

// ゴールとスタートの設定
mapfield[starty][startx].attr = ATTR_START;
mapfield[starty][startx].cost = 200;
mapfield[goaly][goalx].attr = ATTR_GOAL;

// スタート位置をキューに入れる
que_push(mapfield[starty][startx]);

// checknodeがゴールに行くまでループ
do {
// 調べる位置をキューから取り出す
nowpos = que_pop();
if(nowpos == null) {
trace("ゴールに到達できません。\n");
return;
}

// 8方向調べる
checkcount = 0;
for (i = 0; i < 8; i++) {
checkxCopy = nowpos.pos.x + difpos[i].x;
checkyCopy = nowpos.pos.y + difpos[i].y;

// 範囲チェック
if ((checkxCopy < 0) || (checkxCopy >= MAP_WIDTH) || (checkyCopy < 0) || (checkyCopy >= MAP_HEIGHT))
continue;

checkx = checkxCopy;
checky = checkyCopy;

// ゴールだったら抜ける
if (mapfield[checky][checkx].attr == ATTR_GOAL) {
mapfield[checky][checkx].backpos.x = nowpos.pos.x;
mapfield[checky][checkx].backpos.y = nowpos.pos.y;
break;
}

// 壁や調査済みだったらその位置は使わない
if (mapfield[checky][checkx].attr != ATTR_EMPTY)
continue;

// コストを得る
cost = poscost(mapfield[checky][checkx].pos,
mapfield[goaly][goalx].pos);

// コストをそこに置いて次の調査へ
mapfield[checky][checkx].attr = ATTR_SEARCH;
mapfield[checky][checkx].cost = cost;
mapfield[checky][checkx].backpos.x = nowpos.pos.x;
mapfield[checky][checkx].backpos.y = nowpos.pos.y;
que_push(mapfield[checky][checkx]);
checkcount++;
}

// 1回も周囲を調べられなかったら,今後その位置は使わない
if ((!checkcount) && (nowpos.attr == ATTR_SEARCH))
nowpos.attr = ATTR_CLOSE;
if (nowpos.attr == ATTR_EMPTY)
nowpos.attr = ATTR_SEARCH;
}
while(mapfield[checky][checkx].attr != ATTR_GOAL);
path_check(goaly, goalx);
}

private var moveCnt:int = 0;
private var cnt:int = 0;
public function enemyMove():void
{
moveCnt++;

if (3 < moveCnt % 5) {
if (cnt >= 13) {
charX = ROBO_XPOS * 32;
charY = (ROBO_YPOS - 1) * 32;
cnt = 0;
}else {
charX = moveInfo[cnt].pos.x * 32;
charY = (moveInfo[cnt].pos.y - 1) * 32;
cnt++;
}
}
}

public function render(output:BitmapData):void
{
var n:int;
var rect:Rectangle = new Rectangle(0, 0, 32, 32);
var anime:int;
output.lock();
// 画面を塗りつぶす
output.fillRect(new Rectangle(0, 0, 465 , 465), 0xffffff);

for (var i:int = 0; i < MAP_HEIGHT; i++ ) {
for (var j:int = 0; j < MAP_WIDTH; j++) {
if (mapfield[i][j].attr == ATTR_WALL) {
output.copyPixels(tile[1], rect, new Point(j * 32, i * 32));
}else if (mapfield[i][j].attr == ATTR_GOAL) {
output.copyPixels(tile[39], rect, new Point(j * 32, i * 32));
}else {
output.copyPixels(tile[0], rect, new Point(j * 32, i * 32));
}
}
}

// キャラクターのアニメーション
if (charAnimTime++ > 100000) {
charAnimTime = 0;
}
anime = charAnimTime % 40;

switch(this.keyState) {
case 0: // 上
if (anime >= 0 && anime < 10) { output.copyPixels(tile_char[1], new Rectangle(0, 0, 32, 64), new Point(charX, charY), null, null, true); }
else if (anime >= 10 && anime < 20) { output.copyPixels(tile_char[0], new Rectangle(0, 0, 32, 64), new Point(charX, charY), null, null, true); }
else if (anime >= 20 && anime < 30) { output.copyPixels(tile_char[1], new Rectangle(0, 0, 32, 64), new Point(charX, charY), null, null, true); }
else if (anime >= 30 && anime < 40) { output.copyPixels(tile_char[2], new Rectangle(0, 0, 32, 64), new Point(charX, charY), null, null, true); }
break;

case 1: // 右
if (anime >= 0 && anime < 10) { output.copyPixels(tile_char[4], new Rectangle(0, 0, 32, 64), new Point(charX, charY), null, null, true); }
else if (anime >= 10 && anime < 20) { output.copyPixels(tile_char[3], new Rectangle(0, 0, 32, 64), new Point(charX, charY), null, null, true); }
else if (anime >= 20 && anime < 30) { output.copyPixels(tile_char[4], new Rectangle(0, 0, 32, 64), new Point(charX, charY), null, null, true); }
else if (anime >= 30 && anime < 40) { output.copyPixels(tile_char[5], new Rectangle(0, 0, 32, 64), new Point(charX, charY), null, null, true); }
break;

case 2: // 下
if (anime >= 0 && anime < 10) { output.copyPixels(tile_char[7], new Rectangle(0, 0, 32, 64), new Point(charX, charY), null, null, true); }
else if (anime >= 10 && anime < 20) { output.copyPixels(tile_char[6], new Rectangle(0, 0, 32, 64), new Point(charX, charY), null, null, true); }
else if (anime >= 20 && anime < 30) { output.copyPixels(tile_char[7], new Rectangle(0, 0, 32, 64), new Point(charX, charY), null, null, true); }
else if (anime >= 30 && anime < 40) { output.copyPixels(tile_char[8], new Rectangle(0, 0, 32, 64), new Point(charX, charY), null, null, true); }
break;
case 3: // 左
if (anime >= 0 && anime < 10) { output.copyPixels(tile_char[10], new Rectangle(0, 0, 32, 64), new Point(charX, charY), null, null, true); }
else if (anime >= 10 && anime < 20) { output.copyPixels(tile_char[9], new Rectangle(0, 0, 32, 64), new Point(charX, charY), null, null, true); }
else if (anime >= 20 && anime < 30) { output.copyPixels(tile_char[10], new Rectangle(0, 0, 32, 64), new Point(charX, charY), null, null, true); }
else if (anime >= 30 && anime < 40) { output.copyPixels(tile_char[11], new Rectangle(0, 0, 32, 64), new Point(charX, charY), null, null, true); }
break;
}

output.unlock();
}
}

}


t_node.as
package  
{
/**
* ノードクラス
*/
public class t_node
{
public var pos:t_point = new t_point(); /* 自分の場所 */
public var backpos:t_point = new t_point(); /* 探索を始めた場所 */
public var cost:int = 0; /* 現在位置のコスト */
public var attr:int = 0; /* その地点が探索済みかどうか */

public function t_node()
{

}

}

}


t_point.as
package  
{
/**
* x, y の2点間を示すクラス
*/
public class t_point
{
public var x:int, y:int;

public function t_point()
{

}

}

}