// forked from 9re's Wonderfl Tank Game Tank Sample 1 // このコードを新着タンク一覧http://flash-games.wonderfl.net/tank/list/new // に表示させるにはinfinite-tank-entry // というタグをつけてください package { import flash.display.*; import flash.geom.*; import flash.events.*; import flash.filters.*; import flash.text.TextField; import flash.text.TextFieldAutoSize; import net.wonderfl.game.infinity_tank.development.*; import net.wonderfl.game.infinity_tank.core.*; import net.wonderfl.math.*; [SWF(backgroundColor="#000000")] /** * @author 9re */ public class Tank extends TankBase { /** ================================================== * constants * ================================================== */ /** -------------------------------------------------- * action constant */ private const INTERCEPT_ITERATION:Number = 10; private const INTERCEPT_ACCURACY:Number = 1; /** -------------------------------------------------- * draw & common constant */ private const DEBUG:Boolean = false; private const INTERCEPT:Boolean = true; private const BULLET_RENDERER_URL:String = "http://swf.wonderfl.net/swf/usercode/2/2a/2a1c/2a1c4faef5a34a6cb98cd005db878f576fe8d2e4.swf";//"http://swf.wonderfl.net/swf/usercode/0/04/046c/046cee45b4334c4f2dac8dfa7ec9ea2b2b2eb27d.swf"; private const STAGE_WIDTH :Number = 600; private const STAGE_HEIGHT:Number = 550; private const TANK_WIDTH :Number = 50; private const TANK_HEIGHT :Number = 30; private const TANK_RADIUS :Number = 25; private const GUN_WIDTH :Number = 24; private const GUN_HEIGHT :Number = 10; private const AFTERIMAGE_COLOR_TRANSFORM:ColorTransform = new ColorTransform(1, 1, 1, 1 - 1 / 4); private const TANK_BODY_GLOW_FILTER:GlowFilter = new GlowFilter( 0xffffff, 1, 8, 8, 0.2 ); /** ================================================== * variables * ================================================== */ /** -------------------------------------------------- * action variables */ private var moveForward:Boolean = true; private var intercept:Intercept; private var linearIntercept:Intercept; private var circularIntercept:Intercept; /** -------------------------------------------------- * draw & common variables */ public function get stageWidth():Number { return STAGE_WIDTH; }; public function get stageHeight():Number { return STAGE_HEIGHT }; public function get tankRadius():Number { return TANK_RADIUS }; private var tankWidth:Number; private var tankHeight:Number; private var tankBody:Sprite; private var gun:Sprite; private var previousStageBitmapData:BitmapData; private var drawTankMatrix:Matrix = new Matrix(); /** ================================================== * methods * ================================================== */ /** * */ public function Tank() { Wonderfl.disable_capture(); //Wonderfl.capture_delay(1); _bulletRenderer = BULLET_RENDERER_URL; } /** * */ //override protected function init():void { protected function init():void { //super._init(); __init(); // draw tankWidth = TANK_WIDTH; tankHeight = TANK_HEIGHT; createTankBody(); if ( DEBUG ) createDebugObject(); drawTankMatrix = new Matrix(); // action linearIntercept = new LinearIntercept( tkBattleScene, INTERCEPT_ITERATION, INTERCEPT_ACCURACY ); circularIntercept = new CircularIntercept( tkBattleScene, INTERCEPT_ITERATION, INTERCEPT_ACCURACY ); } /* 速度の測定に使用 private var maxLinearVelocity:Number = 0; private var maxAngularVelocity:Number = 0; //*/ /** * 常に攻撃 * * @return */ public function attackAction():int { var action:int = Command.DO_NOTHING; if ( INTERCEPT ) { // intercept if( Math.abs( enemyTank.angularVelocity ) <= 1 / 180 * Math.PI ) intercept = linearIntercept; else intercept = circularIntercept; intercept.calculate(); } var turnGunRight:Number; if( INTERCEPT ) turnGunRight = intercept.turnGunRight; else turnGunRight = Angle.normalizeRadianM180toP180( myTank.enemyTankBearingFromGunHeading ); // set fire if ( INTERCEPT ) /* if ( !tkBattleScene.bulletSpeed || ( Math.abs(turnGunRight) <= intercept.threshold && !intercept.targetBumpAgainstAWall ) )//*/ action += Command.FIRE; else action += Command.FIRE; //trace( Math.abs(turnGunRight) * 180 / Math.PI , intercept.threshold * 180 / Math.PI ); // set turn gun if( turnGunRight > 0 ) action += Command.GUN_TURN_RIGHT; else action += Command.GUN_TURN_LEFT; return action; } /** * 敵に対して ENEMY_BEARING_FROM_FLANKING 度に構える * * @return */ public function avoidAction():int { var action:int = Command.DO_NOTHING; action = antiGravityMoveAction(); return action; } private function antiGravityMoveAction():int { // enemyTank var myTankPosition:WVector2D = myTank.position; var enemyTankPosition:WVector2D = enemyTank.position; var enemyTankGravity:GravityPoint = new GravityPoint( enemyTankPosition.x, enemyTankPosition.y, -1000 ); var enemyTankForce:WVector2D = enemyTankGravity.force( myTankPosition ); var forceX:Number = enemyTankForce.x; var forceY:Number = enemyTankForce.y; // wall var topWallGravity:GravityPoint = new GravityPoint( myTankPosition.x, 0, -1000 ); var rightWallGravity:GravityPoint = new GravityPoint( stageWidth, myTankPosition.y, -1000 ); var bottomWallGravity:GravityPoint = new GravityPoint( myTankPosition.x, stageHeight, -1000 ); var leftWallGravity:GravityPoint = new GravityPoint( 0, myTankPosition.y, -1000 ); var topWallForce:WVector2D = topWallGravity.force( myTankPosition ); var rightWallForce:WVector2D = rightWallGravity.force( myTankPosition ); var bottomWallForce:WVector2D = bottomWallGravity.force( myTankPosition ); var leftWallForce:WVector2D = leftWallGravity.force( myTankPosition ); forceY += topWallForce.y; forceX += rightWallForce.x; forceY += bottomWallForce.y; forceX += leftWallForce.x; // bullet var enemyTankBullets:Vector.<BoundBox> = enemyTank.bullets; for( var i:int = 0; i < enemyTankBullets.length; i++ ) { var enemyTankBulletGravity:GravityPoint = new GravityPoint( enemyTankBullets[i].position.x, enemyTankBullets[i].position.y, -500 ); var enemyTankBulletForce:WVector2D = enemyTankBulletGravity.force( myTankPosition ); forceX += enemyTankBulletForce.x; forceY += enemyTankBulletForce.y; } return goTo( new WVector2D( myTankPosition.x - forceX, myTankPosition.y - forceY ) ); } private function goTo( destination:WVector2D ):int { var action:int = Command.DO_NOTHING; var myTankHeading:Number = myTank.heading; var myTankPosition:WVector2D = myTank.position; var destinationFromMyTank:WVector2D = destination.copy(); destinationFromMyTank.subtract(myTankPosition); var destinationBearingFromMyTank:Number = Math.atan2(destinationFromMyTank.y, destinationFromMyTank.x); destinationBearingFromMyTank = Angle.normalizeRadian0to360(destinationBearingFromMyTank); var destinationBearingFromMyTankHeading:Number = destinationBearingFromMyTank - myTankHeading; destinationBearingFromMyTankHeading = Angle.normalizeRadian0to360( destinationBearingFromMyTankHeading ); var turnRight:Number = Angle.normalizeRadianM180toP180( destinationBearingFromMyTankHeading ); if( Math.abs( turnRight ) < Angle.RADIAN_90_DEGREE ) { action += Command.TANK_MOVE_FORWARD; } else { action += Command.TANK_MOVE_BACKWARD; turnRight = Angle.normalizeRadianM180toP180( destinationBearingFromMyTankHeading + Angle.RADIAN_180_DEGREE ); } if( turnRight > Angle.RADIAN_0_DEGREE ) action += Command.TANK_TURN_RIGHT; else action += Command.TANK_TURN_LEFT; return action; } /** * 敵の銃弾に当たれば方向転換 */ override public function hit():void { moveForward = !moveForward; } /** ================================================== * draw * ================================================== */ /** * * @param tankBodyBitmapData */ //override public function drawStageBitmapData(stageBitmapData:BitmapData):void public function drawStageBitmapData(stageBitmapData:BitmapData):BitmapData { if( !DEBUG ) gun.rotation = _scene.myGunAngle * 180 / Math.PI; else gun.rotation = myTank.gunBearingFromHeading * 180 / Math.PI; // ---------- if ( previousStageBitmapData == null ) previousStageBitmapData = new BitmapData( stageBitmapData.width, stageBitmapData.height, true, 0 ); previousStageBitmapData.colorTransform(previousStageBitmapData.rect, AFTERIMAGE_COLOR_TRANSFORM ); // ----- drawTankMatrix.identity(); if( !DEBUG ) { drawTankMatrix.rotate( _scene.myTankAngle ); drawTankMatrix.translate( _scene.myTankPosition.x, _scene.myTankPosition.y ); } else { drawTankMatrix.rotate( myTank.heading ); drawTankMatrix.translate( myTank.position.x, myTank.position.y ); } previousStageBitmapData.draw(tankBody, drawTankMatrix, null, null, null, true); // ----- stageBitmapData.colorTransform( stageBitmapData.rect, CLEAR_COLOR_TRANSFORM ); stageBitmapData.draw(previousStageBitmapData); // ---------- if ( DEBUG ) { var debugBitmapData:BitmapData = new BitmapData( stageBitmapData.width, stageBitmapData.height, true, 0 ); debugBitmapData = drawDebugBitmapData( debugBitmapData ); stageBitmapData.draw(debugBitmapData); } // ----- return stageBitmapData; } /** * */ private function createTankBody():void { // draw tankBody tankBody = new Sprite(); // body var gradientMatrix:Matrix = new Matrix(); gradientMatrix.createGradientBox( TANK_WIDTH + 10, TANK_HEIGHT + 10, 0, -5 - ( TANK_WIDTH / 2 ), -5 - ( TANK_HEIGHT / 2 ) ); tankBody.graphics.lineStyle( 1, 0xffffff, 0.5, true ); tankBody.graphics.beginGradientFill( GradientType.RADIAL, new Array( 0x666666, 0x333333, 0x000000 ), new Array( 1, 1, 1 ), new Array( 0, 160, 255 ), gradientMatrix ); tankBody.graphics.drawRoundRect( -( TANK_WIDTH / 2 ), -( TANK_HEIGHT / 2 ), TANK_WIDTH, TANK_HEIGHT, 10, 10 ); // direction mark tankBody.graphics.beginFill( 0xffffff ); tankBody.graphics.moveTo( 3, 0 ); tankBody.graphics.lineTo( -3, 3 ); tankBody.graphics.lineTo( -3, -3 ); tankBody.graphics.endFill(); tankBody.filters = new Array( TANK_BODY_GLOW_FILTER ); // gun gun = new Sprite(); gun.graphics.beginFill( 0xffffff ); gun.graphics.drawEllipse( 9, -1, 2, 2 ); gun.graphics.endFill(); tankBody.addChild( gun ); } /** -------------------------------------------------- * debug */ private const DEBUG_POINT_RADIUS:Number = 30; private const DEBUG_AFTERIMAGE_COLOR_TRANSFORM:ColorTransform = new ColorTransform(1, 1, 1, 1 - 1 / 16); private var debugPointSprite:Sprite; private var debugTextField:TextField; private var debugMatrix:Matrix; private var previousDebugBitmapData:BitmapData; private function createDebugObject():void { debugPointSprite = new Sprite(); debugPointSprite.graphics.lineStyle( 1, 0x00ff00, 0.2 ); debugPointSprite.graphics.drawCircle( 0, 0, DEBUG_POINT_RADIUS ); debugPointSprite.graphics.moveTo( 0, -DEBUG_POINT_RADIUS ); debugPointSprite.graphics.lineTo( 0, DEBUG_POINT_RADIUS ); debugPointSprite.graphics.moveTo( -DEBUG_POINT_RADIUS, 0 ); debugPointSprite.graphics.lineTo( DEBUG_POINT_RADIUS, 0 ); debugTextField = new TextField(); debugTextField.textColor = 0xffffff; debugTextField.autoSize = TextFieldAutoSize.LEFT; debugMatrix = new Matrix(); } private function drawDebugBitmapData( debugBitmapData:BitmapData ):BitmapData { if ( intercept == null ) return debugBitmapData; if ( previousDebugBitmapData == null ) previousDebugBitmapData = new BitmapData( debugBitmapData.width, debugBitmapData.height, true, 0 ); previousDebugBitmapData.colorTransform(previousDebugBitmapData.rect, DEBUG_AFTERIMAGE_COLOR_TRANSFORM ); // ----- /* // myTank.position debugMatrix.identity(); debugMatrix.translate( myTank.position.x, myTank.position.y ); previousDebugBitmapData.draw( debugPointSprite, debugMatrix ); //*//* // enemyTank.position debugMatrix.identity(); debugMatrix.translate( enemyTank.position.x, enemyTank.position.y ); previousDebugBitmapData.draw( debugPointSprite, debugMatrix ); //*/ // intercept.impactPoint debugMatrix.identity(); debugMatrix.translate( intercept.impactPoint.x, intercept.impactPoint.y ); previousDebugBitmapData.draw( debugPointSprite, debugMatrix ); // ----- debugBitmapData.draw( previousDebugBitmapData ); // ----- debugTextField.text = "";/* debugTextField.appendText("myTank.position.x = " + myTank.position.x + "\n" ); debugTextField.appendText("myTank.position.y = " + myTank.position.y + "\n" ); debugTextField.appendText("myTank.positionFromPreviousPosition.x = " + myTank.positionFromPreviousPosition.x + "\n" ); debugTextField.appendText("myTank.positionFromPreviousPosition.y = " + myTank.positionFromPreviousPosition.y + "\n" ); debugTextField.appendText("myTank.velocity.x = " + myTank.velocity.x + "\n" ); debugTextField.appendText("myTank.velocity.y = " + myTank.velocity.y + "\n" ); debugTextField.appendText("myTank.velocity.length = " + myTank.velocity.length + "\n" ); debugTextField.appendText("myTank.angularVelocity = " + myTank.angularVelocity * 180 / Math.PI + "\n" ); debugTextField.appendText("enemyTank.position.x = " + enemyTank.position.x + "\n" ); debugTextField.appendText("enemyTank.position.y = " + enemyTank.position.y + "\n" ); debugTextField.appendText("enemyTank.positionFromPreviousPosition.x = " + enemyTank.positionFromPreviousPosition.x + "\n" ); debugTextField.appendText("enemyTank.positionFromPreviousPosition.y = " + enemyTank.positionFromPreviousPosition.y + "\n" ); debugTextField.appendText("enemyTank.velocity.x = " + enemyTank.velocity.x + "\n" ); debugTextField.appendText("enemyTank.velocity.y = " + enemyTank.velocity.y + "\n" ); debugTextField.appendText("enemyTank.velocity.length = " + enemyTank.velocity.length + "\n" ); debugTextField.appendText("enemyTank.angularVelocity = " + enemyTank.angularVelocity * 180 / Math.PI + "\n" ); debugTextField.appendText("tkBattleScene.bulletSpeed = " + tkBattleScene.bulletSpeed + "\n" ); debugTextField.appendText("intercept = " + intercept + "\n" );//*/ debugTextField.appendText("intercept.impactTime = " + intercept.impactTime + "\n" ); debugTextField.appendText("intercept.impactPoint.x = " + intercept.impactPoint.x + "\n" ); debugTextField.appendText("intercept.impactPoint.y = " + intercept.impactPoint.y + "\n" ); // ----- debugBitmapData.draw( debugTextField ); // ----- return debugBitmapData; } /** ================================================== * TKTankBase * ================================================== */ /** -------------------------------------------------- * draw constant */ protected const CLEAR_COLOR_TRANSFORM:ColorTransform = new ColorTransform( 1, 1, 1, 0 ); /** -------------------------------------------------- * action variable */ protected var tkBattleScene:TKBattleScene; protected var myTank:MyTank; protected var enemyTank:EnemyTank; private var battleMode:Boolean; /* public function get stageWidth():Number { return 0; } public function get stageHeight():Number { return 0; } protected function init():void { super._init(); tkBattleScene = new TKBattleScene( _scene, this, stageWidth, stageHeight ); } //*/ /** * */ protected function __init():void { tkBattleScene = new TKBattleScene( _scene, this, stageWidth, stageHeight, tankRadius ); myTank = tkBattleScene.myTank; enemyTank = tkBattleScene.enemyTank; } /** * * @return */ //override final public function action():int { override public function action():int { //if( !_world.battleMode ) if ( !battleMode ) battleMode = true; var action:int = Command.DO_NOTHING; if( _scene.enemyTankPosition == null ) return action; if( tkBattleScene == null ) _init(); tkBattleScene.run(); // ----- action += attackAction(); action += avoidAction(); return action; } /* public function fireAction():int { var action:int = Command.DO_NOTHING; return action; } public function turnTankAction():int { var action:int = Command.DO_NOTHING; return action; } public function moveTankAction():int { var action:int = Command.DO_NOTHING; return action; } public function turnGunAction():int { var action:int = Command.DO_NOTHING; return action; } //*/ /** -------------------------------------------------- * draw * -------------------------------------------------- */ /** * * @param bitmapData */ //override final public function draw(bitmapData:BitmapData):void override public function draw(bitmapData:BitmapData):void { if( tkBattleScene == null ) init(); //if( !_world.battleMode ) if( !battleMode ) tkBattleScene.run(); // ----- var stageBitmapData:BitmapData = new BitmapData( bitmapData.width, bitmapData.height, true, 0 ); stageBitmapData.draw( bitmapData ); stageBitmapData = drawStageBitmapData( stageBitmapData ); bitmapData.colorTransform( bitmapData.rect, CLEAR_COLOR_TRANSFORM ); bitmapData.draw( stageBitmapData ); } /* public function drawStageBitmapData(stageBitmapData:BitmapData):BitmapData { return null; } //*/ } } /** ================================================== * library * ================================================== */ import net.wonderfl.game.infinity_tank.development.*; import net.wonderfl.math.*; import flash.display.*; import flash.geom.*; /** * helper class */ class TKBattleScene { /* ================================================== * field * ================================================== */ private var battleScene:BattleScene; private var tank:TankBase; /* ================================================== * property * ================================================== */ /* -------------------------------------------------- * - */ private var _bulletSpeed:Number; public function get bulletSpeed():Number { return _bulletSpeed; } private var _robotRadius:Number; public function get robotRadius():Number { return _robotRadius; } /* -------------------------------------------------- * stage */ private var _stageWidth:Number; public function get stageWidth():Number { return _stageWidth; } private var _stageHeight:Number; public function get stageHeight():Number { return _stageHeight; } private var _stageCenter:WVector2D; public function get stageCenter():WVector2D { return _stageCenter; } private var _stageDiagonal:Number; public function get stageDiagonal():Number { return _stageDiagonal; } /* -------------------------------------------------- * tank */ private var _myTank:MyTank; public function get myTank():MyTank { return _myTank; } private var _enemyTank:EnemyTank; public function get enemyTank():EnemyTank { return _enemyTank; } /* -------------------------------------------------- * enemyTank property */ public function get enemyBulletCount():int { return battleScene.enemyBulletCount; } private var _enemyBullets:Vector.<BoundBox>; public function get enemyBullets():Vector.<BoundBox> { return _enemyBullets; } public function get enemyTankHeading():Number { var enemyTankAngle:Number; try { enemyTankAngle = angleRegularization( battleScene.enemyTankAngle ); } catch( e:Error ) { //trace( "error : get enemyTankPosition" ); } finally { return enemyTankAngle; } } public function get enemyTankAngularVelocity():Number { var enemyTankAngularVelocity:Number; try { enemyTankAngularVelocity = battleScene.enemyTankAngularVelocity; } catch( e:Error ) { //trace( "error : get enemyTankPosition" ); } finally { return enemyTankAngularVelocity; } } public function get enemyTankVelocity():WVector2D { var enemyTankVelocity:WVector2D = new WVector2D( 0, 0 ); try { enemyTankVelocity = battleScene.enemyTankLinearVelocity; } catch( e:Error ) { //trace( "error : get enemyTankPosition" ); } finally { return enemyTankVelocity; } } public function get enemyTankPosition():WVector2D { var enemyTankPosition:WVector2D = new WVector2D( 0, 0 ); try { enemyTankPosition = battleScene.enemyTankPosition; } catch( e:Error ) { //trace( "error : get enemyTankPosition" ); } finally { return enemyTankPosition; } } /* -------------------------------------------------- * myTank property */ public function get myBulletCount():int { return battleScene.myBulletCount; } private var _myBullets:Vector.<BoundBox>; public function get myBullets():Vector.<BoundBox> { return _myBullets; } public function get myGunBearingFromMyTankHeading():Number { return angleRegularization( battleScene.myGunAngle ); } public function get myGunAngularVelocity():Number { return battleScene.myGunAngularVelocity; } public function get myTankHeading() :Number{ return angleRegularization( battleScene.myTankAngle ); } public function get myTankAngularVelocity():Number { return battleScene.myTankAngularVelocity; } public function get myTankVelocity():WVector2D { var myTankVelocity:WVector2D = new WVector2D( 0, 0 ); try { myTankVelocity = battleScene.myTankLinearVelocity; } catch( e:Error ) { //trace( "error : get enemyTankPosition" ); } finally { return myTankVelocity; } } public function get myTankPosition():WVector2D { var myTankPosition:WVector2D = new WVector2D( 0, 0 ); try { myTankPosition = battleScene.myTankPosition; } catch( e:Error ) { //trace( "error : get enemyTankPosition" ); } finally { return myTankPosition; } } /* ================================================== * method * ================================================== */ public function TKBattleScene( battleScene:BattleScene, tank:TankBase, stageWidth:Number, stageHeight:Number, robotRadius:Number ):void { this.battleScene = battleScene; this.tank = tank; _stageWidth = stageWidth; _stageHeight = stageHeight; _stageCenter = new WVector2D( stageWidth / 2, stageHeight / 2 ); _stageDiagonal = Math.sqrt( stageWidth * stageWidth + stageHeight * stageHeight ); _robotRadius = robotRadius; _myTank = new MyTank( this ); _enemyTank = new EnemyTank( this ); } private var previousBulletPosition:WVector2D; public function run():void { var i:int; // enemyBullets _enemyBullets = new Vector.<BoundBox>( enemyBulletCount ); var enemyBullet:BoundBox = battleScene.enemyBulletList; for( i = 0; i < enemyBulletCount; i++ ) { _enemyBullets[i] = enemyBullet; if( enemyBullet.next != null ) enemyBullet = enemyBullet.next; } // myBullets _myBullets = new Vector.<BoundBox>( myBulletCount ); var myBullet:BoundBox = battleScene.myBulletList; for( i = 0; i < myBulletCount; i++ ) { _myBullets[i] = myBullet; if( myBullet.next != null ) myBullet = myBullet.next; } // tank update enemyTank.update(); //enemyTank の update が先 myTank.update(); //myTank は enemyTank の情報も内部で処理する // 後で修正する // bulletSpeed if ( !_bulletSpeed && battleScene.myBulletList ) { //_bulletSpeed = battleScene.myBulletList.linearVelocity.length; if ( previousBulletPosition == null ) { previousBulletPosition = battleScene.myBulletList.position; return; } var bulletVelocity:WVector2D = battleScene.myBulletList.position; bulletVelocity.subtract(previousBulletPosition); _bulletSpeed = bulletVelocity.length; //trace( _bulletSpeed, battleScene.myBulletList.linearVelocity.length ); } } public function nearWall( tankAndWallDistance:Number, position:WVector2D ):Boolean { if( position != null && ( position.x <= tankAndWallDistance || stageWidth - tankAndWallDistance <= position.x || position.y <= tankAndWallDistance || stageHeight - tankAndWallDistance <= position.y ) ) { return true; } return false; } private function angleRegularization( value:Number ):Number { value = value % ( 2 * Math.PI ); if( value < 0 ) value = ( 2 * Math.PI ) + value; return value; } } class Angle { public static const RADIAN_0_DEGREE:Number = 0; public static const RADIAN_90_DEGREE:Number = Math.PI / 2; public static const RADIAN_180_DEGREE:Number = Math.PI; public static const RADIAN_270_DEGREE:Number = Math.PI / 2 * 3; public static const RADIAN_360_DEGREE:Number = 2 * Math.PI; public static function normalizeRadian0to360( value:Number ):Number { value %= Angle.RADIAN_360_DEGREE; if( value < RADIAN_0_DEGREE ) value += Angle.RADIAN_360_DEGREE; return value; } public static function normalizeRadianM180toP180( value:Number ):Number { value = normalizeRadian0to360( value ); if( value > RADIAN_180_DEGREE ) value -= Angle.RADIAN_360_DEGREE; return value; } } class Intercept { /* ================================================== * field * ================================================== */ protected var tkBattleScene:TKBattleScene; protected var iteration:Number; protected var accuracy:Number; protected var target:ModelBase; protected var myTank:MyTank; /* ================================================== * property * ================================================== */ private var _turnGunRight:Number; public function get turnGunRight():Number { return _turnGunRight; } private var _impactTime:Number; public function get impactTime():Number { return _impactTime; } private var _impactPoint:WVector2D; public function get impactPoint():WVector2D { return _impactPoint; } private var _threshold:Number; public function get threshold():Number { return _threshold; } private var _targetBumpAgainstAWall:Boolean; public function get targetBumpAgainstAWall():Boolean { return _targetBumpAgainstAWall; } /* ================================================== * method * ================================================== */ public function Intercept( tkBattleScene:TKBattleScene, iteration:Number, accuracy:Number ) { this.tkBattleScene = tkBattleScene; this.iteration = iteration; this.accuracy = accuracy; this.myTank = tkBattleScene.myTank; this.target = tkBattleScene.enemyTank; } public function calculate():void { // impactTime _impactTime = getImpactTime(); // impactPoint _impactPoint = getTargetEstimatedPosition(impactTime); // targetPositionFromMyTank var myTankPosition:WVector2D = myTank.position; var targetPositionFromMyTank:WVector2D = impactPoint.copy(); targetPositionFromMyTank.subtract(myTankPosition); // goalBulletHeading var goalBulletHeading:Number = Math.atan2(targetPositionFromMyTank.y,targetPositionFromMyTank.x); goalBulletHeading = Angle.normalizeRadian0to360( goalBulletHeading ); // turnBulletHeading var myGunHeading:Number = myTank.gunHeading; _turnGunRight = goalBulletHeading - myGunHeading; _turnGunRight = Angle.normalizeRadianM180toP180( _turnGunRight ); // threshold _threshold = Math.atan( target.radius / targetPositionFromMyTank.length ); // targetBumpAgainstAWall if ( impactPoint.x < 0 || tkBattleScene.stageWidth < impactPoint.x || impactPoint.y < 0 || tkBattleScene.stageHeight < impactPoint.y ) _targetBumpAgainstAWall = true; else _targetBumpAgainstAWall = false; } private function getImpactTime():Number { var estimatedMaxImpactTime:Number = estimatedMaxImpactTime(); var time:Vector.<Number> = Vector.<Number>([ 0, estimatedMaxImpactTime ]); var distance:Vector.<Number> = Vector.<Number>([ estimatedDistanceFromBulletToTarget( time[0] ), estimatedDistanceFromBulletToTarget( time[1] ) ]); var iterationI:int = ( estimatedMaxImpactTime >= 5 ) ? estimatedMaxImpactTime : 5; var timeI:Vector.<Number> = new Vector.<Number>(iterationI); var distanceI:Vector.<Number> = new Vector.<Number>(iterationI); timeI[0] = time[0]; timeI[iterationI - 1] = time[1]; distanceI[0] = distance[0]; distanceI[iterationI - 1] = distance[1]; var j:int; // timeI and distanceI for ( j = 1; j < iterationI - 1; j++ ) { timeI[j] = time[0] + ( ( ( time[1] - time[0] ) / iterationI ) * j ); distanceI[j] = estimatedDistanceFromBulletToTarget( timeI[j] ); } // minDistanceIIndex var minDistanceIIndex:int = 0; for ( j = 1; j < iterationI; j++ ) { if( distanceI[minDistanceIIndex] > distanceI[j] ) minDistanceIIndex = j; } if ( estimatedDistanceFromBulletToTarget(timeI[minDistanceIIndex]) < myTank.radius ) return timeI[minDistanceIIndex]; /*trace(estimatedDistanceFromBulletToTarget(timeI[minDistanceIIndex])); if ( estimatedDistanceFromBulletToTarget(timeI[minDistanceIIndex]) < myTank.radius ) return timeI[minDistanceIIndex]; //*/ // impactTime var impactTime:Number; if( minDistanceIIndex == 0 ) impactTime = getImpactTimeSecant( timeI[minDistanceIIndex], timeI[minDistanceIIndex + 1] ); else if( minDistanceIIndex == iterationI - 1 ) impactTime = getImpactTimeSecant( timeI[minDistanceIIndex - 1], timeI[minDistanceIIndex] ); else { if( distanceI[minDistanceIIndex - 1] - distanceI[minDistanceIIndex] < distanceI[minDistanceIIndex + 1] - distanceI[minDistanceIIndex] ) impactTime = getImpactTimeSecant( timeI[minDistanceIIndex - 1], timeI[minDistanceIIndex] ); else impactTime = getImpactTimeSecant( timeI[minDistanceIIndex], timeI[minDistanceIIndex + 1] ); } /* if( impactTime != 0 ) return impactTime; //*///* if( estimatedDistanceFromBulletToTarget( impactTime ) < accuracy ) return impactTime; //*/ // minDistanceIIndex2 var minDistanceIIndex2:int = ( minDistanceIIndex > 1 ) ? 0 : minDistanceIIndex + 2; for ( j = 0; j < iterationI; j++ ) { if( minDistanceIIndex - 1 <= j && j <= minDistanceIIndex + 1 ) continue; if( distanceI[minDistanceIIndex2] > distanceI[j] ) minDistanceIIndex2 = j; } // impactTime2 var impactTime2:Number; if( minDistanceIIndex2 == 0 ) impactTime2 = getImpactTimeSecant( timeI[minDistanceIIndex2], timeI[minDistanceIIndex2 + 1] ); else if( minDistanceIIndex2 == iterationI - 1 ) impactTime2 = getImpactTimeSecant( timeI[minDistanceIIndex2 - 1], timeI[minDistanceIIndex2] ); else { if( distanceI[minDistanceIIndex2 - 1] - distanceI[minDistanceIIndex2] < distanceI[minDistanceIIndex2 + 1] - distanceI[minDistanceIIndex2] ) impactTime2 = getImpactTimeSecant( timeI[minDistanceIIndex2 - 1], timeI[minDistanceIIndex2] ); else impactTime2 = getImpactTimeSecant( timeI[minDistanceIIndex2], timeI[minDistanceIIndex2 + 1] ); } /* if( impactTime2 != 0 ) return impactTime2; //*///* if( estimatedDistanceFromBulletToTarget( impactTime2 ) < accuracy ) return impactTime2; //*/ if ( estimatedDistanceFromBulletToTarget(timeI[minDistanceIIndex]) < myTank.radius ) return timeI[minDistanceIIndex]; //trace(0, impactTime, estimatedDistanceFromBulletToTarget( impactTime )); //trace(1, impactTime2, estimatedDistanceFromBulletToTarget( impactTime2 )); return 0; } /** * 割線法で近似解を求める。 * 二分法とはさみうち法は、解く方程式の性質により区間[a,b]内に解を保持することができないので使用不可。 * ニュートン法は、解く方程式を微分した式の解を求める関数を用意するのが面倒。 * * @return */ private function getImpactTimeSecant( time0:Number, time1:Number ):Number { var distance0:Number = estimatedDistanceFromBulletToTarget( time0 ); for( var i:Number = 0; i < iteration; i++ ) { var distance1:Number = estimatedDistanceFromBulletToTarget( time1 ); var time2:Number = time1 - distance1 * ( time1 - time0 ) / ( distance1 - distance0 ); time0 = time1; distance0 = distance1; time1 = time2; if( distance0 < accuracy ) return time0; } //return 0; return time0; } private function estimatedMaxImpactTime():Number { // targetPositionFromMyTank var targetPositionFromMyTank:WVector2D = target.position; targetPositionFromMyTank.subtract( myTank.position ); // targetVelocity var targetVelocity:WVector2D = target.velocity; var targetSpeed:Number = targetVelocity.length; var bulletSpeed:Number = tkBattleScene.bulletSpeed; var time:Number = 0; if( bulletSpeed > targetSpeed ) { // T = distanceFromTargetPositionToMyTank / bulletSpeed time = targetPositionFromMyTank.length / bulletSpeed; // T * enemyTankSpeed + Tx * enemyTankSpeed = Tx * bulletSpeed time += Math.abs( ( time * targetSpeed ) / ( bulletSpeed - targetSpeed ) ); } // 改良の余地あり(弾が壁に当たるまでの時間にするなど) if ( bulletSpeed <= targetSpeed || time > tkBattleScene.stageDiagonal / bulletSpeed ) time = tkBattleScene.stageDiagonal / bulletSpeed; return time; } private function estimatedDistanceFromBulletToTarget(time:Number):Number { var bulletSpeed:Number = tkBattleScene.bulletSpeed; var myTankPosition:WVector2D = myTank.position; var estimatedDistanceFromBulletToTarget:WVector2D = getTargetEstimatedPosition(time); estimatedDistanceFromBulletToTarget.subtract( myTankPosition ); return Math.abs( ( estimatedDistanceFromBulletToTarget.length - myTank.radius ) - bulletSpeed * time ); } /* -------------------------------------------------- * abstract */ protected function getTargetEstimatedPosition(time:Number):WVector2D { return new WVector2D( 0, 0 ); } } class LinearIntercept extends Intercept { public function LinearIntercept( tkBattleScene:TKBattleScene, iteration:Number, accuracy:Number ) { super( tkBattleScene, iteration, accuracy ); } override protected function getTargetEstimatedPosition(time:Number):WVector2D { var targetPosition:WVector2D = target.position; var targetVelocity:WVector2D = target.velocity; var x:Number = targetPosition.x + targetVelocity.x * time; var y:Number = targetPosition.y + targetVelocity.y * time; return new WVector2D( x, y ); } } class CircularIntercept extends Intercept { public function CircularIntercept( tkBattleScene:TKBattleScene, iteration:Number, accuracy:Number ) { super( tkBattleScene, iteration, accuracy ); } // 式が微妙な感じなので暇があれば修正する override protected function getTargetEstimatedPosition(time:Number):WVector2D { var pointA:Point = new Point( target.getPosition(0).x, target.getPosition(0).y ); var pointB:Point = new Point( target.getPosition(1).x, target.getPosition(1).y ); var pointC:Point = new Point( target.getPosition(2).x, target.getPosition(2).y ); var centerPoint:Point = getCenterPoint( pointA, pointB, pointC ); var radius:Number = getRadius( centerPoint, pointA ); var pointAFromPointB:Point = pointA.subtract(pointB); var angularVelocity:Number = Math.asin( pointAFromPointB.length / ( 2 * radius ) ) * 2 ; if( target.angularVelocity < 0 ) { angularVelocity *= -1; radius *= -1; } //double angularVelocity = target.velocity().length() / radius; var targetBearingFromCircleCenter:Number = Angle.normalizeRadian0to360( Math.atan2( pointAFromPointB.y, pointAFromPointB.x ) + Angle.RADIAN_270_DEGREE ); var nextTargetHeading:Number = ( targetBearingFromCircleCenter + angularVelocity * time ) % Angle.RADIAN_360_DEGREE; var x:Number = pointA.x + radius * ( Math.cos(nextTargetHeading) - Math.cos(targetBearingFromCircleCenter) ); var y:Number = pointA.y + radius * ( Math.sin(nextTargetHeading) - Math.sin(targetBearingFromCircleCenter) ); return new WVector2D( x, y ); } } class ModelBase { /* ================================================== * field * ================================================== */ protected var tkBattleScene:TKBattleScene; /* ================================================== * property * ================================================== */ /* -------------------------------------------------- * */ protected var _radius:Number; public function get radius():Number { return _radius; } /* -------------------------------------------------- * basic */ protected var _positions:Vector.<WVector2D> = new Vector.<WVector2D>(10); public function getPosition( index:int ):WVector2D { return _positions[index].copy(); } protected var _position:WVector2D = new WVector2D( 0, 0 ); public function get position():WVector2D { return _position.copy(); } protected var _previousHeading:Number; public function get previousHeading():Number { return _previousHeading; } protected var _heading:Number; public function get heading():Number { return _heading; } protected var _velocity:WVector2D = new WVector2D( 0, 0 ); public function get velocity():WVector2D { return _positionFromPreviousPosition.copy();//_velocity.copy(); } protected var _angularVelocity:Number; public function get angularVelocity():Number { return _heading - _previousHeading;//_angularVelocity; } /* -------------------------------------------------- * */ protected var _positionFromPreviousPosition:WVector2D = new WVector2D( 0, 0 ); public function get positionFromPreviousPosition():WVector2D { return _positionFromPreviousPosition.copy(); } protected var _movedDirection:Number; public function get movedDirection():Number { return _movedDirection; } /* ================================================== * method * ================================================== */ public function ModelBase( tkBattleScene:TKBattleScene ):void { this.tkBattleScene = tkBattleScene; for( var i:uint = 0; i < _positions.length; i++ ) _positions[i] = new WVector2D( 0, 0 ); } public function update():void { // positions for( var i:uint = 0; i < _positions.length - 1; i++ ) _positions[_positions.length - ( 1 + i ) ] = _positions[_positions.length - ( 2 + i )]; _positions[0] = position; // positionFromPreviousPosition _positionFromPreviousPosition = position; _positionFromPreviousPosition.subtract( getPosition(1) ); // movedDirection _movedDirection = Math.atan2( velocity.y, velocity.x ); _movedDirection = Angle.normalizeRadian0to360( _movedDirection ); /* _movedDirection = Math.atan2( movedDistance.y, movedDistance.x ); _movedDirection = Angle.normalizeRadian0to360( _movedDirection ); //*/ } } class TankModel extends ModelBase { /* ================================================== * property * ================================================== */ /* -------------------------------------------------- * sub class calculate */ protected var _bullets:Vector.<BoundBox>; public function get bullets():Vector.<BoundBox> { return _bullets; } protected var _gunBearingFromHeading:Number; public function get gunBearingFromHeading():Number { return _gunBearingFromHeading; } /* -------------------------------------------------- * this class calculate */ protected var _gunHeading:Number; public function get gunHeading():Number { return _gunHeading; } protected var _bearingFromStageCenter:Number; public function get bearingFromStageCenter():Number { return _bearingFromStageCenter; } protected var _movedDirectionFromHeading:Number; public function get movedDirectionFromHeading():Number { return _movedDirectionFromHeading; } protected var _velocityFromHeading:Number; public function get velocityFromHeading():Number { return _velocityFromHeading; } /* ================================================== * method * ================================================== */ public function TankModel( tkBattleScene:TKBattleScene ):void { super( tkBattleScene ); _radius = tkBattleScene.robotRadius; } public override function update():void { super.update(); // gunHeading _gunHeading = heading + gunBearingFromHeading; _gunHeading = Angle.normalizeRadian0to360( _gunHeading ); // bearingFromStageCenter var stageCenter:WVector2D = tkBattleScene.stageCenter; var positionFromStageCenter:WVector2D = position; positionFromStageCenter.subtract(stageCenter); _bearingFromStageCenter = Math.atan2( positionFromStageCenter.y, positionFromStageCenter.x ); _bearingFromStageCenter = Angle.normalizeRadian0to360( _bearingFromStageCenter ); // movedDirectionFromHeading _movedDirectionFromHeading = movedDirection - heading; _movedDirectionFromHeading = Angle.normalizeRadian0to360( _movedDirectionFromHeading ); // velocityFromHeading _velocityFromHeading = ( movedDirectionFromHeading + Angle.RADIAN_90_DEGREE ) % Angle.RADIAN_360_DEGREE; if( 0 <= _velocityFromHeading && _velocityFromHeading < Math.PI ) _velocityFromHeading = velocity.length; else _velocityFromHeading = -velocity.length; } } class EnemyTank extends TankModel { /* ================================================== * method * ================================================== */ public function EnemyTank( tkBattleScene:TKBattleScene ):void { super( tkBattleScene ); } public override function update():void { _bullets = tkBattleScene.enemyBullets; _previousHeading = heading; _position = tkBattleScene.enemyTankPosition; _heading = tkBattleScene.enemyTankHeading; _velocity = tkBattleScene.enemyTankVelocity; _angularVelocity = tkBattleScene.enemyTankAngularVelocity; super.update(); } } class MyTank extends TankModel { /* ================================================== * field * ================================================== */ protected var enemyTank:EnemyTank; /* ================================================== * property * ================================================== */ /* -------------------------------------------------- * enemyTank */ protected var _enemyTankPositionFromPosition:WVector2D = new WVector2D( 0, 0 ); public function get enemyTankPositionFromPosition():WVector2D { return _enemyTankPositionFromPosition.copy(); } protected var _enemyTankBearingFromPosition:Number; public function get enemyTankBearingFromPosition():Number { return _enemyTankBearingFromPosition; } protected var _enemyTankBearingFromHeading:Number; public function get enemyTankBearingFromHeading():Number { return _enemyTankBearingFromHeading; } protected var _enemyTankBearingFromGunHeading:Number; public function get enemyTankBearingFromGunHeading():Number { return _enemyTankBearingFromGunHeading; } /* ================================================== * method * ================================================== */ public function MyTank( tkBattleScene:TKBattleScene ):void { super( tkBattleScene ); } override public function update():void { if( enemyTank == null ) enemyTank = tkBattleScene.enemyTank; _previousHeading = heading; _bullets = tkBattleScene.myBullets; _position = tkBattleScene.myTankPosition; _heading = tkBattleScene.myTankHeading; _velocity = tkBattleScene.myTankVelocity; _angularVelocity = tkBattleScene.myTankAngularVelocity; _gunBearingFromHeading = tkBattleScene.myGunBearingFromMyTankHeading; super.update(); // positionFromEnemyTankPosition _enemyTankPositionFromPosition = enemyTank.position; _enemyTankPositionFromPosition.subtract( position ); // enemyTankBearing _enemyTankBearingFromPosition = Math.atan2( enemyTankPositionFromPosition.y, enemyTankPositionFromPosition.x ); if( _enemyTankBearingFromPosition < 0 ) _enemyTankBearingFromPosition = ( _enemyTankBearingFromPosition + Angle.RADIAN_360_DEGREE ) % Angle.RADIAN_360_DEGREE; // enemyTankBearingFromGunHeading _enemyTankBearingFromGunHeading = enemyTankBearingFromPosition - gunHeading; _enemyTankBearingFromGunHeading = Angle.normalizeRadian0to360( _enemyTankBearingFromGunHeading ); // enemyTankBearingFromHeading _enemyTankBearingFromHeading = enemyTankBearingFromPosition - heading; _enemyTankBearingFromHeading = Angle.normalizeRadian0to360( _enemyTankBearingFromHeading ); } } class GravityPoint extends WVector2D { private var _power:Number; public function get power():Number { return _power; } public function force( point:WVector2D ):WVector2D { var positionFromPoint:WVector2D = copy(); positionFromPoint.subtract( point ); var forceLength:Number = power / Math.pow( positionFromPoint.length, 2 ); var angle:Number = Angle.normalizeRadianM180toP180(Math.PI/2 - Math.atan2(point.y - y, point.x - x)); var forceX:Number = Math.sin(angle) * forceLength; var forceY:Number = Math.cos(angle) * forceLength; return new WVector2D( forceX, forceY ); } public function GravityPoint( x:Number, y:Number, power:Number):void { super(x,y); _power = power; } } function getRadius( centerPoint:Point, pointA:Point ):Number { var pointAFromCenterPoint:Point = pointA.subtract(centerPoint); var radius:Number = pointAFromCenterPoint.length; return radius; } function getCenterPoint( pointA:Point, pointB:Point, pointC:Point ):Point { var lineSegmentABPerpendicularBisectorEquationAB:Point; var lineSegmentBCPerpendicularBisectorEquationAB:Point; var lineSegmentABPerpendicularBisectorSlope:Number = -1 / slope( pointA, pointB ); var lineSegmentBCPerpendicularBisectorSlope:Number = -1 / slope( pointB, pointC ); var lineABMiddlePoint:Point = middlePoint( pointA, pointB ); var lineBCMiddlePoint:Point = middlePoint( pointB, pointC ); var lineSegmentABPerpendicularBisectorB:Number = linearEquationsSolutionB( lineABMiddlePoint, lineSegmentABPerpendicularBisectorSlope ); var lineSegmentBCPerpendicularBisectorB:Number = linearEquationsSolutionB( lineBCMiddlePoint, lineSegmentBCPerpendicularBisectorSlope ); lineSegmentABPerpendicularBisectorEquationAB = new Point(lineSegmentABPerpendicularBisectorSlope, lineSegmentABPerpendicularBisectorB); lineSegmentBCPerpendicularBisectorEquationAB = new Point(lineSegmentBCPerpendicularBisectorSlope, lineSegmentBCPerpendicularBisectorB); var centerPoint:Point = simultaneousEquationsSolutionXY( lineSegmentABPerpendicularBisectorEquationAB, lineSegmentBCPerpendicularBisectorEquationAB ); return centerPoint; } function slope(point1:Point, point2:Point):Number { var x1:Number = point1.x; var y1:Number = point1.y; var x2:Number = point2.x; var y2:Number = point2.y; var a:Number = ( y2 - y1 ) / ( x2 - x1 ); return a; } function middlePoint(point1:Point, point2:Point):Point { var x1:Number = point1.x; var y1:Number = point1.y; var x2:Number = point2.x; var y2:Number = point2.y; var x:Number = ( x1 + x2 ) / 2; var y:Number = ( y1 + y2 ) / 2; return new Point(x, y); } function linearEquationsSolutionB(point:Point, a:Number):Number { var x:Number = point.x; var y:Number = point.y; var b:Number = y - a * x; return b; } function simultaneousEquationsSolutionXY(point1:Point, point2:Point):Point { var a1:Number = point1.x; var b1:Number = point1.y; var a2:Number = point2.x; var b2:Number = point2.y; var x:Number = ( b1 - b2 ) / ( a2 - a1 ); var y:Number = a1 * x + b1; return new Point(x,y); } TKTank