// forked from 5ivestar's ActionScript で弾幕ゲーをこしらえてみた // Arrow Key で移動 // Shift で低速移動 // 今のところ避けることしかできません // なんであたるかなぁ package { import flash.display.Loader; import flash.display.Sprite; import flash.display.BitmapData; import flash.events.Event; import flash.events.MouseEvent; import flash.events.KeyboardEvent; import flash.geom.Rectangle; import flash.geom.Point; import flash.net.URLRequest; import flash.text.TextField; import flash.text.TextFormat; import flash.ui.Keyboard; import flash.system.LoaderContext; import flash.system.Security; import net.hires.debug.Stats; [SWF(backgroundColor="#262626", frameRate="60")] public class Game extends Sprite { private var eField:BulletField; private var pField:BulletField; private var player:Player; private var enemy:Enemy; private var status:TextField; private var t:uint; private var hit:uint; private var loader:Loader; private var _aa : AvoidanceAlgorithm; public function Game() { stage.align = "TL"; stage.scaleMode = "noScale"; Security.loadPolicyFile("http://5ivestar.org/misc/crossdomain.xml"); loader = new Loader(); loader.contentLoaderInfo.addEventListener("complete", loadingComplete); loader.load(new URLRequest("http://5ivestar.org/misc/bullets.png"), new LoaderContext(true)); pField = new BulletField(380, 445); pField.x = 10, pField.y = 10; addChild(pField); eField = new BulletField(380, 445); eField.x = 10, eField.y = 10; addChild(eField); var frame:Sprite = new Sprite(); frame.graphics.beginFill(0x000000); frame.graphics.drawRect(0, 0, 465, 465); frame.graphics.drawRect(10, 10, 380, 445); addChild(frame); var stats:Stats = new Stats(); stats.x = 395; stats.y = 10; frame.addChild(stats); status = new TextField(); status.defaultTextFormat = new TextFormat("_sans", 9); status.autoSize = "left"; status.textColor = 0xffffff; status.x = 395; status.y = 110; frame.addChild(status); _aa = new AvoidanceAlgorithm(eField); } public function loadingComplete(e:Event):void { var source:BitmapData = new BitmapData(55, 28, true, 0x00000000); source.draw(loader); var bmp1:BitmapData = new BitmapData(12, 12, true, 0x00000000); bmp1.copyPixels(source, new Rectangle(0, 0, 12, 12), new Point()); Player.loadImage(bmp1); var bmp2:BitmapData = new BitmapData(15, 15, true, 0x00000000); bmp2.copyPixels(source, new Rectangle(12, 0, 15, 15), new Point()); GrainBullet.loadImage(bmp2); var bmp3:BitmapData = new BitmapData(28, 28, true, 0x00000000); bmp3.copyPixels(source, new Rectangle(27, 0, 28, 28), new Point()); BigBall.loadImage(bmp3); var s:Sprite = new Sprite(); s.graphics.beginFill(0x000000, 0.5); s.graphics.drawRect(10, 10, 380, 445); addChild(s); var tf:TextField = new TextField(); tf.defaultTextFormat = new TextFormat("_sans", 18); tf.autoSize = "left"; tf.textColor = 0xffffff; tf.text = "click start"; tf.x = 200 - tf.width / 2; tf.y = 230 - tf.height / 2; s.addChild(tf); s.addEventListener(MouseEvent.MOUSE_UP, function(e:MouseEvent):void { removeChild(s); stage.focus = stage; start(); }); } public function start():void { t = 0; hit = 0; eField.clear(); pField.clear(); player = new Player(185, 390); pField.addBullet(player); stage.addEventListener(KeyboardEvent.KEY_DOWN, player.keyDown); stage.addEventListener(KeyboardEvent.KEY_UP, player.keyUp); enemy = new Enemy(185, 75); eField.addBullet(enemy); Bullet.player = player; Bullet.enemy = enemy; addEventListener(Event.ENTER_FRAME, update); } private var _dicision : int = 0; public function update(e:Event):void { t++; if(t % 6 == 0){ _dicision = _aa.calcAvoidanceAtRoot(player.x, player.y, 1); dispatchEvent(new KeyboardEvent(KeyboardEvent.KEY_DOWN, true, false, 0, 16)); dispatchEvent(new KeyboardEvent(KeyboardEvent.KEY_UP, true, false, 0, 39)); dispatchEvent(new KeyboardEvent(KeyboardEvent.KEY_UP, true, false, 0, 37)); dispatchEvent(new KeyboardEvent(KeyboardEvent.KEY_UP, true, false, 0, 40)); dispatchEvent(new KeyboardEvent(KeyboardEvent.KEY_UP, true, false, 0, 38)); if(_dicision == 1 || _dicision == 5 || _dicision == 8){ dispatchEvent(new KeyboardEvent(KeyboardEvent.KEY_DOWN, true, false, 0, 39)); } if(_dicision == 3 || _dicision == 6 || _dicision == 7){ dispatchEvent(new KeyboardEvent(KeyboardEvent.KEY_DOWN, true, false, 0, 37)); } if(_dicision == 2 || _dicision == 5 || _dicision == 6){ dispatchEvent(new KeyboardEvent(KeyboardEvent.KEY_DOWN, true, false, 0, 40)); } if(_dicision == 4 || _dicision == 7 || _dicision == 8){ dispatchEvent(new KeyboardEvent(KeyboardEvent.KEY_DOWN, true, false, 0, 38)); } } pField.update(); var h:uint = eField.update(); if (h) player.hit(); if (player.dead) finish(); hit += h; var s:String = ["BULLETS: " + eField.bullets.length.toString(), "HIT: " + hit.toString(), "LIFE: "].join("\n"); for (var i:int = 0; i < player.life; i++) { s += "★"; } status.text = s; } public function finish():void { removeEventListener(Event.ENTER_FRAME, update); stage.removeEventListener(KeyboardEvent.KEY_DOWN, player.keyDown); stage.removeEventListener(KeyboardEvent.KEY_UP, player.keyUp); var s:Sprite = new Sprite(); s.graphics.beginFill(0x000000, 0.5); s.graphics.drawRect(10, 10, 380, 445); addChild(s); var tf:TextField = new TextField(); tf.defaultTextFormat = new TextFormat("_sans", 18); tf.autoSize = "left"; tf.textColor = 0xffffff; tf.text = "retry"; tf.x = 200 - tf.width / 2; tf.y = 230 - tf.height / 2; s.addChild(tf); s.addEventListener(MouseEvent.MOUSE_UP, function(e:MouseEvent):void { removeChild(s); stage.focus = stage; start() }); } } } import flash.display.Bitmap; import flash.display.Sprite; // 弾のコンテナ class BulletField extends Bitmap { public var fieldWidth:Number; public var fieldHeight:Number; public var bullets:Vector.<Bullet>; public function BulletField(w:Number, h:Number) { fieldWidth = w; fieldHeight = h; bullets = new Vector.<Bullet>(); var bmp:BitmapData = new BitmapData(w, h, true, 0x00000000); super(bmp); } public function addBullet(bullet:Bullet):void { bullet.field = this; bullets.push(bullet); } public function update():uint { bitmapData.lock(); bitmapData.fillRect(bitmapData.rect, 0x00000000); var hit:uint = 0; var n:uint = bullets.length; while (n--) { var bullet:Bullet = bullets[n]; bullet.update(); bullet.draw(); if (bullet.hitTest()) hit++; if (bullet.dead) { bullets.splice(n, 1); } } bitmapData.unlock(); return hit; } public function clear():void { bullets.splice(0, bullets.length); } } import flash.display.Bitmap; import flash.display.BitmapData; import flash.geom.Matrix; // BulletField 上に表示されるオブジェクトの基底クラス // 動きを変更するには update // 当たり判定を変更するには hitTest // をそれぞれオーバーライドする class Bullet { public static var player:Player; public static var enemy:Enemy; public var dead:Boolean = false; public var x:Number = 0; public var y:Number = 0; protected var direction:Number; public var field:BulletField; public var vx : Number = 0.0; public var vy : Number = 0.0; public var r2 : Number = 0.0; public function Bullet(x0:Number, y0:Number, d:Number) { x = x0, y = y0; direction = d; } public function update():void { } public function hitTest():Boolean { return false; } public function draw():void { } public function nWay(bullet:Class, n:int, direction:Number, range:Number, f:BulletField = null):void { if (n > 1) { for (var i:int = 0; i < n; i++) { (f || field).addBullet(new bullet(x, y, direction + range * (i/(n-1)-0.5))); } } else if (n == 1) { (f || field).addBullet(new bullet(x, y, direction)); } } } import flash.display.Shape; import flash.events.Event; import flash.events.KeyboardEvent; import flash.geom.ColorTransform; import flash.geom.Point; // 自機 class Player extends Bullet { public static var images:Vector.<BitmapData>; private var image:BitmapData; public var life:int = 4; private var t:uint; private var damaged:uint; private var slow:Boolean = false; private var left:Boolean = false; private var up:Boolean = false; private var right:Boolean = false; private var down:Boolean = false; private var alpha:Number = 1; private var p:Point; public static function loadImage(bmp:BitmapData):void { images = new Vector.<BitmapData>(); images.push(bmp); var bmp1:BitmapData = bmp.clone(); bmp1.colorTransform(bmp.rect, new ColorTransform(1.0, 1.0, 1.0, 0.8)); images.push(bmp1); var bmp2:BitmapData = bmp.clone(); bmp2.colorTransform(bmp.rect, new ColorTransform(1.0, 1.0, 1.0, 0.2)); images.push(bmp2); } public function Player(x0:Number = 0, y0:Number = 0) { super(x0, y0, 0); image = images[0]; p = new Point(); } override public function update():void { t++; var vx:Number = 0; var vy:Number = 0; if (left && !right) vx = -1; if (!left && right) vx = 1; if (up && !down) vy = -1; if (!up && down) vy = 1; if (slow) { x += vx; y += vy; } else { x += vx * 3; y += vy * 3; } x = Math.max(0, x); x = Math.min(field.fieldWidth, x); y = Math.max(0, y); y = Math.min(field.fieldHeight, y); if (damaged) { var n:int = t - damaged; if (n % 8 < 4) { image = images[2]; } else { image = images[1]; } if (n > 75) { image = images[0]; damaged = 0; } } } override public function draw():void { p.x = x - image.width / 2; p.y = y - image.height / 2; field.bitmapData.copyPixels(image, image.rect, p, null, null, true); } public function hit():void { if (!damaged) { damaged = t; life--; if (!life) { dead = true; } } } public function keyDown(e:KeyboardEvent):void { switch (e.keyCode) { case 16: slow = true; break; case 37: left = true; break; case 38: up = true; break; case 39: right = true; break; case 40: down = true; break; } } public function keyUp(e:KeyboardEvent):void { switch (e.keyCode) { case 16: slow = false; break; case 37: left = false; break; case 38: up = false; break; case 39: right = false; break; case 40: down = false; break; } } } // 敵機 class Enemy extends Bullet { private var t:uint; public function Enemy(x0:Number = 0, y0:Number = 0) { super(x0, y0, 0); r2 = 4; } override public function update():void { t++; var d:Number = Math.atan2(y - player.y, player.x - x); if (t < 100) { if (!(t % 5)) nWay(GrainBullet1, 30, Math.PI * (t % 360) / 180, Math.PI * 2); } else if (t > 250 && t < 1050) { if (!(t % 25)) nWay(GrainBullet1, 31, d, Math.PI * 4/3); if (!(t % 20)) nWay(BigBall1, 10, -Math.PI * (t % 16) / 8, Math.PI * 2); } else if (t > 1250 && t < 2050) { if (!(t % 10)) nWay(GrainBullet1, 20, Math.PI * (t % 90) / 45, Math.PI * 2); if (!(t % 10)) nWay(GrainBullet1, 20, -Math.PI * (t % 90) / 45, Math.PI * 2); if (!(t % 50)) nWay(BigBall1, 11, d, Math.PI * 4/3); } else if (t > 2200 && t < 2800) { if (!(t % 250)) nWay(GrainBullet2, 20, 0, Math.PI * 2); } } } // Bullet に弾のイメージを管理する機能を付与したクラス // AS3 では静的プロパティは継承されないため // イメージ毎にこうしたクラスを作る必要がある // 粒弾 class GrainBullet extends Bullet { private static var images:Vector.<BitmapData>; private static var p:Point; public var image:BitmapData; public static function loadImage(source:BitmapData):void { images = new Vector.<BitmapData>(); var s:Number = source.width; for (var i:int; i < 360; i++) { var bmp:BitmapData = new BitmapData(s, s, true, 0x00000000); var d:Number = i / 180 * Math.PI; var m:Matrix = new Matrix(); m.translate(-s/2, -s/2); m.rotate(-d); m.translate(s/2, s/2); bmp.draw(source, m, null, null, null, true); images.push(bmp); } p = new Point(); } public function GrainBullet(x0:Number, y0:Number, d:Number) { super(x0, y0, d); r2 = 9; updateImage(); } override public function draw():void { p.x = x - image.width / 2; p.y = y - image.height / 2; field.bitmapData.copyPixels(image, image.rect, p, null, null, true); } override public function hitTest():Boolean { return Math.sqrt(Math.pow(player.x - x, 2) + Math.pow(player.y - y, 2)) < 3; } protected function updateImage():void { var l:int = images.length; var i:int = direction / Math.PI * l * 0.5; image = images[((i %= l) < 0) ? i + l : i]; } } // 大玉 class BigBall extends Bullet { private static var image:BitmapData; private static var p:Point; public static function loadImage(bmp:BitmapData):void { image = bmp; p = new Point() } public function BigBall(x0:Number, y0:Number, d:Number) { super(x0, y0, d); r2 = 100; } override public function draw():void { p.x = x - image.width / 2; p.y = y - image.height / 2; field.bitmapData.copyPixels(image, image.rect, p, null, null, true); } override public function hitTest():Boolean { return Math.sqrt(Math.pow(player.x - x, 2) + Math.pow(player.y - y, 2)) < 10; } } // 等速直進運動する弾 class GrainBullet1 extends GrainBullet { public function GrainBullet1(x0:Number, y0:Number, d:Number, v:Number = 1.5) { super(x0, y0, d); vx = Math.cos(direction) * v; vy = -Math.sin(direction) * v; } override public function update():void { x += vx; y += vy; if (x < -10 || x > field.fieldWidth + 10 || y < -10 || y > field.fieldHeight + 10) { dead = true; } } } class BigBall1 extends BigBall { public function BigBall1(x0:Number, y0:Number, d:Number, v:Number = 1) { super(x0, y0, d); vx = Math.cos(direction) * v; vy = -Math.sin(direction) * v; } override public function update():void { x += vx; y += vy; if (x < -10 || x > field.fieldWidth + 10 || y < -10 || y > field.fieldHeight + 10) { dead = true; } } } // 敵機の周りを回った後はじける class GrainBullet2 extends GrainBullet { private var t:uint = 0; public function GrainBullet2(x0:Number, y0:Number, d:Number) { super(x0, y0, d); } override public function update():void { t++; if (t < 300) { direction += 0.06 - t * 0.0002; updateImage(); x += Math.cos(direction); y -= Math.sin(direction); } else { nWay(GrainBullet3, 50, 0, Math.PI * 2); dead = true; } } } // はじけた後落ちていく class GrainBullet3 extends GrainBullet { private var t:uint = 0; public function GrainBullet3(x0:Number, y0:Number, d:Number) { super(x0, y0, d); vx = Math.cos(direction) * 2.5; vy = -Math.sin(direction) * 2.5; } override public function update():void { t++; if (t < 100) { vx *= 0.99; vy *= 0.99; x += vx; y += vy; } else { vy += 0.01; x += vx; y += vy; direction = Math.atan2(-vy, vx); updateImage(); if (x < -10 || x > field.fieldWidth + 10 || y < -10 || y > field.fieldHeight + 10) { dead = true; } } } } import flash.text.TextField; class AvoidanceAlgorithm { private var _bf : BulletField; public function AvoidanceAlgorithm(bf : BulletField) { _bf = bf; } // 低速移動オンリー public static const PTN_MOVE : Array = [ [0, 0], [1, 0], [0, 1], [-1, 0], [0, -1], [1, 1], [-1, 1], [-1, -1], [1, -1] ]; private const W : int = 380; private const H : int = 245; // 決定間隔 private const INTERVAL : int = 7; // 回避アルゴリズム // @param depth 読むパスの深さ public function calcAvoidanceAtRoot(x : Number, y : Number, depth : int) : int { var i : int; var t : Number; var mincs : Array = new Array(PTN_MOVE.length); // 壁への衝突 for(i = 0;i < PTN_MOVE.length;i++){ var minc : Number = Number.MAX_VALUE; if(PTN_MOVE[i][0] > 0){ t = (W - x) / PTN_MOVE[i][0]; if(t < minc)minc = t; }else if(PTN_MOVE[i][0] < 0){ t = (x) / -PTN_MOVE[i][0]; if(t < minc)minc = t; } // あんまり上に行かないように入れ知恵 if(PTN_MOVE[i][1] > 0){ t = (H - (y - 100)) / PTN_MOVE[i][1]; if(t < minc)minc = t; }else if(PTN_MOVE[i][1] < 0){ t = (y - 100) / -PTN_MOVE[i][1]; if(t < minc)minc = t; } mincs[i] = minc; } // 弾への衝突 for each(var b : Bullet in _bf.bullets){ var rxx : Number = b.x - x; var rxy : Number = b.y - y; for(i = 0;i < PTN_MOVE.length;i++){ var rvx : Number = b.vx - PTN_MOVE[i][0]; var rvy : Number = b.vy - PTN_MOVE[i][1]; t = mint(rxx, rxy, rvx, rvy); if(t > 0){ var d2 : Number = mind2(rxx, rxy, rvx, rvy, t); if(d2 < b.r2){ t -= Math.sqrt((b.r2 - d2) / (rvx * rvx + rvy * rvy)); if(t < mincs[i])mincs[i] = t; } } } } var mc : Number; var maxminc : Number = 0.0; var maxmini : int = Math.random() * PTN_MOVE.length; // 詰みの防止 for(i = 0;i < PTN_MOVE.length;i++){ var maxt : int = int(mincs[i]); if(maxt <= INTERVAL)continue; if(depth == 0){ mc = mincs[i] + calcAvoidanceAtLeaf( x + PTN_MOVE[i][0] * maxt, y + PTN_MOVE[i][1] * maxt, maxt ); }else{ mc = mincs[i] + calcAvoidanceAtNode( x + PTN_MOVE[i][0] * maxt, y + PTN_MOVE[i][1] * maxt, maxt, depth - 1 ); } if(mc > maxminc){ maxminc = mc; maxmini = i; } } return maxmini; } private function calcAvoidanceAtNode(x : Number, y : Number, delay : int, depth : int) : Number { var i : int; var t : Number; var mincs : Array = new Array(PTN_MOVE.length); // 壁への衝突 for(i = 0;i < PTN_MOVE.length;i++){ var minc : Number = Number.MAX_VALUE; if(PTN_MOVE[i][0] > 0){ t = (W - x) / PTN_MOVE[i][0]; if(t < minc)minc = t; }else if(PTN_MOVE[i][0] < 0){ t = (x) / -PTN_MOVE[i][0]; if(t < minc)minc = t; } // あんまり上に行かないように入れ知恵 if(PTN_MOVE[i][1] > 0){ t = (H - (y - 100)) / PTN_MOVE[i][1]; if(t < minc)minc = t; }else if(PTN_MOVE[i][1] < 0){ t = (y - 100) / -PTN_MOVE[i][1]; if(t < minc)minc = t; } mincs[i] = minc; } // 弾への衝突 for each(var b : Bullet in _bf.bullets){ var rxx : Number = b.x - x + b.vx * delay; var rxy : Number = b.y - y + b.vy * delay; for(i = 0;i < PTN_MOVE.length;i++){ var rvx : Number = b.vx - PTN_MOVE[i][0]; var rvy : Number = b.vy - PTN_MOVE[i][1]; t = mint(rxx, rxy, rvx, rvy); if(t > 0){ var d2 : Number = mind2(rxx, rxy, rvx, rvy, t); if(d2 < b.r2){ t -= Math.sqrt((b.r2 - d2) / (rvx * rvx + rvy * rvy)); if(t < mincs[i])mincs[i] = t; } } } } var mc : Number; var maxminc : Number = 0.0; for(i = 0;i < PTN_MOVE.length;i++){ var maxt : int = int(mincs[i]); if(maxt <= INTERVAL)continue; if(depth == 0){ mc = mincs[i] + calcAvoidanceAtLeaf( x + PTN_MOVE[i][0] * maxt, y + PTN_MOVE[i][1] * maxt, delay + maxt ); }else{ mc = mincs[i] + calcAvoidanceAtNode( x + PTN_MOVE[i][0] * maxt, y + PTN_MOVE[i][1] * maxt, delay + maxt, depth - 1 ); } if(mc > maxminc)maxminc = mc; } return maxminc; } private function calcAvoidanceAtLeaf(x : Number, y : Number, delay : int) : Number { var i : int; var t : Number; var minc : Number = Number.MAX_VALUE; // 壁への衝突 for(i = 0;i < PTN_MOVE.length;i++){ if(PTN_MOVE[i][0] > 0){ t = (W - x) / PTN_MOVE[i][0]; if(t < minc)minc = t; }else if(PTN_MOVE[i][0] < 0){ t = (x) / -PTN_MOVE[i][0]; if(t < minc)minc = t; } // あんまり上に行かないように入れ知恵 if(PTN_MOVE[i][1] > 0){ t = (H - (y - 100)) / PTN_MOVE[i][1]; if(t < minc)minc = t; }else if(PTN_MOVE[i][1] < 0){ t = (y - 100) / -PTN_MOVE[i][1]; if(t < minc)minc = t; } } // 弾への衝突 for each(var b : Bullet in _bf.bullets){ var rxx : Number = b.x - x + b.vx * delay; var rxy : Number = b.y - y + b.vy * delay; for(i = 0;i < PTN_MOVE.length;i++){ var rvx : Number = b.vx - PTN_MOVE[i][0]; var rvy : Number = b.vy - PTN_MOVE[i][1]; t = mint(rxx, rxy, rvx, rvy); if(t > 0){ var d2 : Number = mind2(rxx, rxy, rvx, rvy, t); if(d2 < b.r2){ t -= Math.sqrt((b.r2 - d2) / (rvx * rvx + rvy * rvy)); if(t < minc)minc = t; } } } } return minc; } private function mint(x : Number, y : Number, vx : Number, vy : Number) : Number { if(vx * vx + vy * vy < 0.01){ return 0.0; } return -(x * vx + y * vy) / (vx * vx + vy * vy); } private function mind2(x : Number, y : Number, vx : Number, vy : Number, t : Number) : Number { var xx : Number = x + vx * t; var yy : Number = y + vy * t; return xx * xx + yy * yy; } } ActionScript で弾幕ゲーをこしらえてみた をズルしてみた