状況
とりあえず、左右キーによって「プレイヤー表示の反転(scaleXによる反転)」「弾の発射方向の反転」ができるようにした。キー入力用の関数内部に直接埋め込んでいるのは問題だが、まだプロトタイプだからそこはスルー。
つづいて、プレイヤーが移動できなくなる「埋まりこみ」を調査。やはり、ショットのタイミングで埋まっている模様。弾の発射位置を変えても起こったので、「カテゴリで指定してるのに当たって埋もれている」という症状ではないっぽい。
「埋もれているなら、掘り出せばいいじゃない」という思考パターンにより、「毎フレーム、少しだけpositionを上に移動させる」という処理を組み込んでみたが、状況は改善せず。むしろ、「浮いているのに動けない」という怪奇現象を発生させるだけだった。ちなみに、左右キーによってプレイヤの表示は反転しているので、キー入力を見てないわけではない。
どうも、「あるタイミングでプレイヤの位置計算が放棄されている」のではないかと思い、「そういえばSleepによってそれは起こりうるな」と思い至ったので、bWorldの初期化で「doSleep = false」にすると、今のところちゃんと移動できている。
ということで、「Box2Dで何かオブジェクトが動かせない状況があったら、Sleepを疑え」という結論で今回は終了しておく。Sleepのタイミングをどうやって見ているかがわかれば、「doSleep = false」以外の対処もあるかもしれない。
→Sleepは「別のコリジョンがぶつかるまで計算しない」というものなはずなので、そこらへんが鍵かも。
ここまで来たら、あとは本格的にアクションゲームの作成に入れる。まずはコードを整理して、きちんとクラスとして分離するところからかな。
コード
//mxmlc Main.as //author Show=O=Healer package { import Box2D.Dynamics.*; import Box2D.Collision.*; import Box2D.Collision.Shapes.*; import Box2D.Common.Math.*; import flash.events.Event; import flash.display.*; import flash.text.TextField; //Input import flash.ui.Keyboard; import flash.events.KeyboardEvent; //AA import flash.geom.*; [SWF(backgroundColor="#ffffff", width="350", height="200")] public class Main extends Sprite { private var m_world:b2World; private var m_physScale:Number = 10; static public const GRAVITY:Number = 10.0; //Player private var m_Player:b2Body; private var m_PlayerGraphic:Sprite; private var m_PlayerDir:Number = 1.0;//右向きなら1、左向きなら-1 //弾 private var m_BulletBd:b2BodyDef; private var m_Bullet:Array = new Array; //Input private var m_InputL:int = 0; private var m_InputR:int = 0; private var m_InputU:int = 0; private var m_InputD:int = 0; private var m_InputSpace:int = 0; static public const KEY_S:int = 83; //Param public static const FLOOR_W:Number = 400; public static const FLOOR_H:Number = 10; public static const FLOOR_Y:Number = 250; public static const BLOCK_W:Number = 40; public static const BLOCK_H:Number = 20; public static const BLOCK_X:Number = 300; public static const PLAYER_X:Number = 50; public static const PLAYER_Y:Number = 220; public static const PLAYER_R:Number = 10; public static const PLAYER_V:Number = 10; public static const BULLET_X:Number = 50; public static const BULLET_Y:Number = 220; public static const BULLET_R:Number = 5; public static const BULLET_V:Number = 200; //Category public static const CATEGORY_PLAYER:int = 0x0001; public static const CATEGORY_PLAYER_BULLET:int = 0x0002; public static const CATEGORY_TERRAIN:int = 0x0004; public static const CATEGORY_BLOCK:int = 0x0008; public function Main() { stage.scaleMode = "noScale"; stage.align = "TL"; var text:TextField = new TextField(); text.text = "CLICK TO START!!!"; text.x = text.y = 100; addChild(text); stage.addEventListener("click", function(event:Event):void { if(text.visible){ Init(); } text.visible = false; }); } //どうも、クリックでフォーカスを当てないとBox2Dがめりこむので、コンストラクタから分離 private function Init():void { {//Init Input stage.addEventListener(KeyboardEvent.KEY_DOWN, OnKeyDown); stage.addEventListener(KeyboardEvent.KEY_UP, OnKeyUp); } {//Init Box2D {//Common //AABB var worldAABB:b2AABB = new b2AABB(); worldAABB.minVertex.Set(-100.0, -100.0); worldAABB.maxVertex.Set(100.0, 100.0); //Gravity var g:b2Vec2 = new b2Vec2(0.0, GRAVITY); //World m_world = new b2World(worldAABB, g, false); } {//Floor //Shape var wallSd:b2BoxDef = new b2BoxDef(); { wallSd.extents.Set(FLOOR_W/2 / m_physScale, FLOOR_H/2 / m_physScale); // wallSd.localRotation = Math.random() * Math.PI / 8; wallSd.restitution = 0.9; wallSd.categoryBits = CATEGORY_TERRAIN; } //Body var wallBd:b2BodyDef = new b2BodyDef(); { wallBd.position.Set(FLOOR_W/2 / m_physScale, (FLOOR_Y + FLOOR_H/2) / m_physScale); wallBd.AddShape(wallSd); } //Create m_world.CreateBody(wallBd); } {//Player //collision { //Shape var playerSd:b2CircleDef = new b2CircleDef(); { playerSd.radius = PLAYER_R / m_physScale; // playerSd.localRotation = Math.random() * Math.PI / 8; playerSd.density = 2; playerSd.friction = 0.0; // playerSd.restitution = 0.0; playerSd.categoryBits = CATEGORY_PLAYER; playerSd.maskBits = CATEGORY_TERRAIN | CATEGORY_BLOCK; } //Body var playerBd:b2BodyDef = new b2BodyDef(); { playerBd.AddShape(playerSd); } //Graphic { m_PlayerGraphic = CreateAASprite("┏(^o^)┛\n ┛┓"); playerBd.userData = m_PlayerGraphic; } //Create playerBd.position.Set(PLAYER_X / m_physScale, PLAYER_Y / m_physScale); m_Player = m_world.CreateBody(playerBd); } } {//Bullet Init //Shape var ballSd:b2CircleDef = new b2CircleDef(); { ballSd.radius = BULLET_R / m_physScale; // ballSd.localRotation = Math.random() * Math.PI / 8; ballSd.density = 10; ballSd.friction = 0.2; ballSd.restitution = 0.9; ballSd.categoryBits = CATEGORY_PLAYER_BULLET; ballSd.maskBits = CATEGORY_TERRAIN | CATEGORY_BLOCK; } //Body m_BulletBd = new b2BodyDef(); { m_BulletBd.AddShape(ballSd); } //Graphic // { // m_BulletBd.userData = CreateAASprite("@"); // } //Create // for(var t:int = 0; t < 1; ++t){ // ballBd.position.Set((300 + t*0.1) / m_physScale / 2, (100 - t*20) / m_physScale); // m_world.CreateBody(ballBd); // } } {//Blocks Init var sd:b2BoxDef = new b2BoxDef(); sd.density = 1; sd.friction = 0.2; sd.extents.Set(BLOCK_W/2 / m_physScale, BLOCK_H/2 / m_physScale); sd.categoryBits = CATEGORY_BLOCK; var bd:b2BodyDef = new b2BodyDef(); bd.AddShape(sd); for (var i:int = 0; i < 10; i++) { bd.position.Set(BLOCK_X / m_physScale, (FLOOR_Y - BLOCK_H/2 - i*BLOCK_H) / m_physScale); m_world.CreateBody(bd); } } } //毎フレームUpdateを呼ぶ addEventListener("enterFrame", function(event:Event):void { Update(); }); } //in_textを(Bitmapによって)表示するSpriteを作成する private function CreateAASprite(in_text:String):Sprite{ //まずは文字列を普通に表示するものを作成 var text_field:TextField = new TextField(); { text_field.text = in_text; text_field.border = false; text_field.x = 0; text_field.y = 0; text_field.width = 100; text_field.height = 100; } //その内容をビットマップデータとして取り込む var bmp_data : BitmapData = new BitmapData( 100 , 100 , true , 0x00000000); { var matrix : Matrix = new Matrix(1,0,0,1,0,0); var color : ColorTransform = new ColorTransform(1,1,1,1,0,0,0,0); var rect : Rectangle = new Rectangle(0,0,400,300); bmp_data.draw(text_field, matrix, color, BlendMode.NORMAL, rect, true); } //取り込んだビットマップデータを表示 var bmp_obj:Bitmap = new Bitmap( bmp_data , PixelSnapping.AUTO , true); { bmp_obj.x = -text_field.textWidth*0.5; bmp_obj.y = -text_field.textHeight*0.5; } //さらにそれを表示するスプライトを作成して返す var sprite:Sprite = new Sprite(); { stage.addChild(sprite); sprite.addChild(bmp_obj); } return sprite; } //=Input= private function OnKeyDown(event:KeyboardEvent):void{ if(event.keyCode == Keyboard.LEFT){ m_InputL = 1; m_PlayerDir = -1.0; m_PlayerGraphic.scaleX = -1; } if(event.keyCode == Keyboard.RIGHT){ m_InputR = 1; m_PlayerDir = 1.0; m_PlayerGraphic.scaleX = 1; } if(event.keyCode == Keyboard.UP){ m_InputU = 1; } if(event.keyCode == Keyboard.DOWN){ m_InputD = 1; } if(event.keyCode == Keyboard.SPACE){ m_InputSpace = 1; } if(event.keyCode == KEY_S){ //ここで生成してしまう Shot(); } } private function OnKeyUp(event:KeyboardEvent):void{ if(event.keyCode == Keyboard.LEFT){ m_InputL = 0; } if(event.keyCode == Keyboard.RIGHT){ m_InputR = 0; } if(event.keyCode == Keyboard.UP){ m_InputU = 0; } if(event.keyCode == Keyboard.DOWN){ m_InputD = 0; } if(event.keyCode == Keyboard.SPACE){ m_InputSpace = 0; } } //=Shot= //弾の生成 private function Shot():void{ //Graphic {//毎回別のものを設定しないと、同じSpriteを動かしてしまうはず m_BulletBd.userData = CreateAASprite("o"); } //右なら1、左なら-1 var dir:Number = m_PlayerDir; //プレイヤ位置から発射 m_BulletBd.position.Set( m_Player.m_position.x + dir*20.0/m_physScale, m_Player.m_position.y-(PLAYER_R/m_physScale) ); //今のところ、右方向に一定速度で打ち出すのみ m_BulletBd.linearVelocity.Set(dir*BULLET_V / m_physScale, 0); //重力相殺を毎回行うので、記憶しておく(消失処理が追加で必要) m_Bullet.push(m_world.CreateBody(m_BulletBd)); } //=Update= public function Update():void { if(!m_world) { return; } {//player update //入力にしたがって動かす m_Player.m_linearVelocity.x = PLAYER_V * (m_InputR - m_InputL); if(m_InputU > 0){ m_Player.m_linearVelocity.y = -PLAYER_V; } } {//bullet update //重力を相殺するように力を加える m_Bullet.forEach( function(bullet:b2Body, index:int, arr:Array):void{ bullet.m_force.Add(b2Math.MulFV(bullet.GetMass(), new b2Vec2(0.0, -GRAVITY))); } ); } //Box2D Update m_world.Step(1.0 / 30.0, 10); // Render graphics.clear(); for (var bb:b2Body = m_world.m_bodyList; bb; bb = bb.m_next) { //primitive描画っぽいもの for (var s:b2Shape = bb.GetShapeList(); s != null; s = s.GetNext()) { DrawShape(s); } //Spriteへの位置の反映 if(bb.m_userData != null){ bb.m_userData.x = bb.m_position.x * m_physScale; bb.m_userData.y = bb.m_position.y * m_physScale; } } } //=Render= public function DrawShape(shape:b2Shape):void { //box if(shape.m_type == b2Shape.e_polyShape) { var drawBox:Function = function():void{ var poly:b2PolyShape = shape as b2PolyShape; var tV:b2Vec2 = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[i])); graphics.beginFill(0x999999, 1); graphics.lineStyle(1,0xffffff,1); graphics.moveTo(tV.x * m_physScale, tV.y * m_physScale); for (var i:int = 0; i < poly.m_vertexCount; ++i) { var v:b2Vec2 = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[i])); graphics.lineTo(v.x * m_physScale, v.y * m_physScale); } graphics.lineTo(tV.x * m_physScale, tV.y * m_physScale); graphics.endFill(); }; drawBox(); } /* //circle if(shape.m_type == b2Shape.e_circleShape) { var drawCircle:Function = function():void{ var circle:b2CircleShape = shape as b2CircleShape; var pos:b2Vec2 = circle.m_position; var r:Number = circle.m_radius; graphics.beginFill(0x999999, 1); graphics.lineStyle(1,0xaaaaaa,1); graphics.drawCircle(pos.x * m_physScale, pos.y * m_physScale, r * m_physScale); graphics.endFill(); }; drawCircle(); } //*/ } } }