// Braid (http://www.xbox.com/ja-JP/games/b/braidxboxlivearcade/) // が面白かったので何も考えずSTGにしてみた。 // 緑弾の扱いとか大失敗した感があるが後悔していない。 package { import flash.display.Bitmap; import flash.display.Sprite; import flash.events.Event; import flash.events.KeyboardEvent; import flash.geom.Point; import flash.geom.Rectangle; import net.hires.debug.Stats; [SWF(width=465,height=465,frameRate=60,backgroundColor=0x111111)] public class Ponytail extends Sprite { private var step:uint = 0; private var step2:uint = 0; private var deadStep:uint = 0xFFFFFFFF; private var canvas:Canvas = new Canvas(400, 465); private var rewindHint:RewindHint = new RewindHint(); private var fighter:Fighter = new Fighter(200, 420); private var waves:Array = [new Wave1(), new Wave2(), new Wave3()]; private var waveIndex:uint = 0; private var bulletList:BulletList; /** * おコンストラクタ */ public function Ponytail() { var bounds:Rectangle = canvas.bitmapData.rect.clone(); bounds.inflate(4, 4); bulletList = new BulletList(bounds); addChild(canvas); var stats:Stats = new Stats(); stats.x = canvas.width; addChild(stats); rewindHint.x = canvas.width - rewindHint.width >> 1; rewindHint.y = canvas.height - rewindHint.height >> 1; rewindHint.visible = false; addChild(rewindHint); addEventListener(Event.ENTER_FRAME, enterFrame); stage.addEventListener(KeyboardEvent.KEY_DOWN, KeyboardManager.keyDown); stage.addEventListener(KeyboardEvent.KEY_UP, KeyboardManager.keyUp); } /** * フレームごとの処理 */ private function enterFrame(event:Event):void { var i:uint; var forward:Boolean = false; if (KeyboardManager.keyStates[88]) { if (step > 0) { step--; canvas.rollback(); } else { canvas.clear(); } } else if (step - deadStep > 30) { rewindHint.visible = true; return; } else { step++; canvas.clear(); forward = true; } rewindHint.visible = false; step2++; if (forward) { canvas.sepia = Math.max(0, canvas.sepia - 0.02); } else { canvas.sepia = Math.min(1, canvas.sepia + 0.002); } var currentWave:IWave = waves[waveIndex % waves.length]; currentWave.getBullets(step, step2, bulletList); if (deadStep > step) { if (forward) { fighter.update(canvas.bitmapData.rect, step); deadStep = 0xFFFFFFFF; var hitRect:Rectangle = new Rectangle(fighter.getX(step) - 4, fighter.getY(step) - 4, 8, 8); for (i=0; i<bulletList.count; i++) { if (hitRect.contains(bulletList.listX[i], bulletList.listY[i])) { deadStep = step; break; } } } canvas.blit(fighter.bitmap, fighter.getX(step), fighter.getY(step)); } for (i=0; i<bulletList.count; i++) { canvas.blit( bulletList.listLocked[i] ? BulletVisual.locked : BulletVisual.normal, bulletList.listX[i], bulletList.listY[i] ); } if (step > 100 && deadStep > step && bulletList.count == 0) { fighter.reset(step); step = 0; step2 = 0; waveIndex++; } } } } import flash.display.BitmapData; import flash.geom.Point; import flash.geom.Rectangle; import flash.events.KeyboardEvent; import flash.ui.Keyboard; import flash.geom.ColorTransform; import flash.geom.Matrix; import flash.display.Bitmap; import flash.filters.ColorMatrixFilter; import flash.text.TextField; import flash.text.TextFormat; import flash.text.TextFieldAutoSize; class BulletList { private var bounds:Rectangle; public var listX:Vector.<Number> = new Vector.<Number>(0, false); public var listY:Vector.<Number> = new Vector.<Number>(0, false); public var listLocked:Vector.<Boolean> = new Vector.<Boolean>(0, false); public var count:uint = 0; public function BulletList(bounds:Rectangle) { this.bounds = bounds; } public function reset():void { count = 0; } public function addBullet(x:Number, y:Number, locked:Boolean=false):void { if (bounds.contains(x, y)) { listX[count] = x; listY[count] = y; listLocked[count] = locked; count++; } } } interface IWave { function getBullets(step1:uint, step2:uint, list:BulletList):void; } class Wave1 implements IWave { public function getBullets(step:uint, step2:uint, list:BulletList):void { list.reset(); for (var i:uint=0; i<12; i++) { var elapsed:int = step - i * 12; if (elapsed > 0) { for (var j:uint=0; j<30; j++) { var angle:Number = j / 30 * Math.PI * 2 - i * 0.021; list.addBullet(Math.cos(angle) * elapsed + 200, Math.sin(angle) * elapsed + 100); } } } } } class Wave2 implements IWave { public function getBullets(step:uint, step2:uint, list:BulletList):void { list.reset(); var elapsed:int; for (var i:uint=0; i<60; i++) { elapsed = step - i * 6; if (elapsed > 0) { for (var j:uint=0; j<10; j++) { var angle:Number = j / 18 * Math.PI * 2; list.addBullet(Math.cos(angle) * elapsed + 200, Math.sin(angle) * elapsed + 100); } } } elapsed = step - 59 * 6; if (elapsed > 0) { for (j=0; j<400 * 17 / 18; j++) { angle = (j / 400 + 8 / 18) * Math.PI * 2; list.addBullet(Math.cos(angle) * elapsed + 200, Math.sin(angle) * elapsed + 100); } } } } class Wave3 implements IWave { public function getBullets(step:uint, step2:uint, list:BulletList):void { list.reset(); var elapsed:int; var i:uint, j:uint, angle:Number; for (i=0; i<60; i++) { elapsed = step - i * 8; if (elapsed > 0) { for (j=0; j<20; j++) { angle = j / 20 * Math.PI * 2 + i * 0.02; list.addBullet(Math.cos(angle) * elapsed + 180, Math.sin(angle) * elapsed + 100); } } } for (i=0; i<60; i++) { elapsed = step2 - i * 8; if (elapsed > 0) { for (j=0; j<20; j++) { angle = j / 20 * Math.PI * 2 + i * -0.02; list.addBullet(Math.cos(angle) * elapsed + 180, Math.sin(angle) * elapsed + 100, true); } } } } } class Fighter { private var historyX:Array; private var historyY:Array; public const bitmap:BitmapData = new BitmapData(16, 16, false, 0xFF0000); public function Fighter(x:Number, y:Number):void { historyX = [x]; historyY = [y]; } public function update(bounds:Rectangle, step:uint):void { var newX:Number = historyX[step - 1]; var newY:Number = historyY[step - 1]; if (KeyboardManager.keyStates[Keyboard.RIGHT]) { newX += 2; } if (KeyboardManager.keyStates[Keyboard.LEFT]) { newX -= 2; } if (KeyboardManager.keyStates[Keyboard.DOWN]) { newY += 2; } if (KeyboardManager.keyStates[Keyboard.UP]) { newY -= 2; } newX = Math.max(bounds.left, Math.min(bounds.right, newX)); newY = Math.max(bounds.top, Math.min(bounds.bottom, newY)); historyX[step] = newX; historyY[step] = newY; } public function getX(step:uint):Number { return historyX[step]; } public function getY(step:uint):Number { return historyY[step]; } public function reset(step:uint):void { historyX[0] = historyX[step]; historyY[0] = historyY[step]; } } class BulletVisual extends BitmapData { public static const normal:BulletVisual = new BulletVisual(0x0000FF, 0x00FFFF); public static const locked:BulletVisual = new BulletVisual(0x008000, 0x00FF00); public function BulletVisual(edgeColor:uint, frameColor:uint) { super(8, 8, false, edgeColor); fillRect(new Rectangle(1, 1, 6, 6), frameColor); fillRect(new Rectangle(2, 2, 4, 4), 0xFFFFFF); } } class Canvas extends Bitmap { private var bmp:BitmapData; private var tmp:BitmapData; private var darken:ColorTransform; private var shrink:Matrix; private var dest:Point = new Point(); private var _sepia:Number = 0; private var rgb2yuv:ColorMatrixFilter = new ColorMatrixFilter([ +0.299, +0.587, +0.114, 0, 0, -0.169, -0.331, +0.500, 0, 128, +0.500, -0.419, -0.081, 0, 128, 0, 0, 0, 1, 0 ]); private var yuv2rgb:ColorMatrixFilter = new ColorMatrixFilter([ 1, +0.000, +1.402, 0, 128 * -1.402, 1, -0.344, -0.714, 0, 128 * (0.344 + 0.714), 1, +1.772, +0.000, 0, 128 * -1.772, 0, 0, 0, 1, 0 ]); public function get sepia():Number { return _sepia; } public function set sepia(value:Number):void { if (_sepia != value) { _sepia = value; if (value) { var r:Number = 1 - value; var matrix:Array = rgb2yuv.matrix.slice(); matrix[5] *= r; matrix[6] *= r; matrix[7] *= r; matrix[10] *= r; matrix[11] *= r; matrix[12] *= r; matrix[9] += value * -0.091 * 128; matrix[14] += value * +0.056 * 128; filters = [new ColorMatrixFilter(matrix), yuv2rgb]; } else { filters = null; } } } public function Canvas(width:int, height:int):void { super(bmp = new BitmapData(width, height, false, 0)); tmp = bmp.clone(); darken = new ColorTransform(0.85, 0.85, 0.85); shrink = new Matrix(); shrink.translate(width * -0.5, height * -0.5); shrink.scale(1.05, 1.05); shrink.translate(width * 0.5, height * 0.5); } public function clear():void { bmp.fillRect(bmp.rect, 0x000000); } public function rollback():void { tmp.copyPixels(bmp, bmp.rect, bmp.rect.topLeft); bmp.draw(tmp, shrink, darken); } public function blit(bitmap:BitmapData, x:Number, y:Number):void { dest.x = x - (bitmap.width >> 1); dest.y = y - (bitmap.height >> 1); bmp.copyPixels(bitmap, bitmap.rect, dest); } } class RewindHint extends TextField { public function RewindHint() { defaultTextFormat = new TextFormat("_typewriter", 30, 0xFFFFFF, true); text = "PRESS [X]"; autoSize = TextFieldAutoSize.LEFT; selectable = false; } } class KeyboardManager { public static const keyStates:Object = {}; public static function keyDown(event:KeyboardEvent):void { keyStates[event.keyCode] = true; } public static function keyUp(event:KeyboardEvent):void { keyStates[event.keyCode] = false; } } Ponytail (何も考えずBraidをSTGにしてみた)