/** * Tetris3D * 昔WCANで使ったネタをちょっといじった * http://blog.eternitydesign.net/archives/2009/05/tetris3d.html * * 180秒で何個ブロックを消せるか * 対象ブロックは最下段の縦横5個 * (斜めは判定ないです) * * * ・操作方法 * * Spaceでゲーム開始 * * I : ブロックを奥へ移動(Z+) * M : ブロック手前へ移動(Z-) * L : ブロックを右へ移動(X+) * J : ブロックを左へ移動(X-) * K : 早く落とす * * コツはちょい押しで移動 * 散らかったら、一度ブロック使って掃除しちゃうのもあり * */ package { import com.bit101.components.PushButton; import flash.display.StageAlign; import flash.display.StageScaleMode; import flash.events.Event; import flash.events.KeyboardEvent; import flash.events.MouseEvent; import flash.events.TimerEvent; import flash.geom.Vector3D; import flash.net.URLRequest; import flash.net.navigateToURL; import flash.text.TextField; import flash.text.TextFieldAutoSize; import flash.text.TextFormat; import flash.utils.Timer; import flash.utils.escapeMultiByte; import jiglib.physics.PhysicsSystem; import jiglib.physics.RigidBody; import jiglib.plugin.papervision3d.Papervision3DPhysics; import org.papervision3d.cameras.Camera3D; import org.papervision3d.lights.PointLight3D; import org.papervision3d.materials.shadematerials.FlatShadeMaterial; import org.papervision3d.materials.utils.MaterialsList; import org.papervision3d.objects.DisplayObject3D; import org.papervision3d.render.QuadrantRenderEngine; import org.papervision3d.view.BasicView; import org.papervision3d.view.Viewport3D; [SWF(backgroundColor="#2e3036", frameRate="30", width="465", height="465")] public class Tetris3d extends BasicView { private var birdView:Viewport3D; private var birdViewCamera:Camera3D; private var tetrisStage:DisplayObject3D; //private var tetrisCtrl:DO3DController; private var physics:Papervision3DPhysics; private var ground:RigidBody; private var light:PointLight3D; private var tetrisBlock:TetrisBlock; private var dCubeNoText:TextField; private var textFormat:TextFormat; private var timer:Timer; private var completeText:TextField; private var countDown:TextField; private const GAME_TIME:uint = 180; public function Tetris3d() { super(stage.stageWidth, stage.stageHeight, true, false); addEventListener(Event.ADDED_TO_STAGE, init); } private function init(e:Event):void { removeEventListener(Event.ADDED_TO_STAGE, init); stage.scaleMode = StageScaleMode.NO_SCALE; stage.align = StageAlign.TOP_LEFT; // 俯瞰のため birdView = new Viewport3D(100, 100); birdViewCamera = new Camera3D(); birdViewCamera.rotationX = 90; birdViewCamera.y = 800; birdViewCamera.z = 0; addChild(birdView); tetrisStage = new DisplayObject3D(); scene.addChild(tetrisStage); physics = new Papervision3DPhysics(tetrisStage, 5); physics.engine.setGravity(new Vector3D(0, -2, 0)); light = new PointLight3D(true, true); light.x = 0; light.y = 400; light.z = -300; camera.y = 450; camera.z = -500; var materiaList:MaterialsList = new MaterialsList(); materiaList.addMaterial(new FlatShadeMaterial(light, 0xCCCCCC), "all"); // 床生成 ground = physics.createCube(materiaList, 200, 200, 5); ground.movable = false; ground.friction = 0.2; ground.restitution = 0.8; textFormat = new TextFormat("_sans", 10, 0xAAAAAA); textFormat.letterSpacing = 1; dCubeNoText = new TextField(); dCubeNoText.autoSize = TextFieldAutoSize.LEFT; dCubeNoText.defaultTextFormat = textFormat; dCubeNoText.x = 30; dCubeNoText.y = 300; addChild(dCubeNoText); countDown = new TextField(); countDown.autoSize = TextFieldAutoSize.LEFT; countDown.defaultTextFormat = textFormat; countDown.text = "残り" + GAME_TIME + "秒"; countDown.x = 30; countDown.y = 330; addChild(countDown); //renderer = new QuadrantRenderEngine(QuadrantRenderEngine.ALL_FILTERS); stage.addEventListener(KeyboardEvent.KEY_DOWN, start); } private function start(e:KeyboardEvent):void { trace("start"); if(e.keyCode == 32) { stage.removeEventListener(KeyboardEvent.KEY_DOWN, start); startRendering(); timer = new Timer(1000, GAME_TIME); timer.addEventListener(TimerEvent.TIMER, timerEvent); timer.addEventListener(TimerEvent.TIMER_COMPLETE, timerComplete); timer.start(); tetrisBlock = new TetrisBlock(physics, tetrisStage); tetrisBlock.addEventListener("FALL_COMPLETE", checkFall) tetrisBlock.createBlock(); stage.addEventListener(KeyboardEvent.KEY_DOWN, tetrisBlock.blockKeyEvent); } } private function checkFall(e:Event):void { trace("checkFall"); tetrisBlock.createBlock(); dCubeNoText.text = "消したCubeの数 : " + tetrisBlock.totalDestroyCube; } private function timerEvent(e:TimerEvent):void { countDown.text = "残り" + (GAME_TIME - timer.currentCount) + "秒"; } private function timerComplete(e:TimerEvent):void { trace("Timer Complete!!!!"); stopRendering(); textFormat = new TextFormat("_sans", 28, 0xff0000); textFormat.letterSpacing = 1; var tweetBtn:PushButton = new PushButton(this, 10, 10, "Tweet Result", tweetBtnClickEvent); tweetBtn.x = (stage.stageWidth - tweetBtn.width) * 0.5; tweetBtn.y = (stage.stageHeight - tweetBtn.height) * 0.5 + 35; completeText = new TextField(); completeText.autoSize = TextFieldAutoSize.LEFT; completeText.defaultTextFormat = textFormat; completeText.text = "GAME OVER"; completeText.x = (stage.stageWidth - completeText.width) * 0.5; completeText.y = (stage.stageHeight - completeText.height) * 0.5; addChild(completeText); } private function tweetBtnClickEvent(e:MouseEvent):void { navigateToURL(new URLRequest("http://twitter.com/home/?status=" + escapeMultiByte("#tetris3d 消したCubeの数 : " + tetrisBlock.totalDestroyCube + " http://wonderfl.net/c/wLvS/"))); } protected override function onRenderTick(e:Event = null):void { PhysicsSystem.getInstance().integrate(0.2); renderer.renderScene(scene, camera, viewport); renderer.renderScene(scene, birdViewCamera, birdView); } } } import com.adobe.utils.ArrayUtil; import flash.display.Sprite; import flash.events.Event; import flash.events.KeyboardEvent; import flash.geom.Vector3D; import jiglib.geometry.*; import jiglib.math.*; import jiglib.physics.*; import jiglib.physics.constraint.*; import jiglib.plugin.papervision3d.*; import org.papervision3d.core.proto.DisplayObjectContainer3D; import org.papervision3d.lights.PointLight3D; import org.papervision3d.materials.shadematerials.*; import org.papervision3d.materials.utils.MaterialsList; import org.papervision3d.objects.primitives.Cube; class TetrisBlock extends Sprite { private var physics:Papervision3DPhysics; private var target:DisplayObjectContainer3D; private var light:PointLight3D; private var cubeML:MaterialsList; private var ckFlg:Number; private var ckAry:Array = new Array(); private var jboxAry:Array; private var blockAry:Array; private var posAry:Array; private var massNum:Number = 5; public var totalDestroyCube:Number = 0; public function TetrisBlock(pp:Papervision3DPhysics, scene:DisplayObjectContainer3D) { physics = pp; target = scene; jboxAry = new Array(); blockAry = new Array(); posAry = new Array(-40, 0, 40); light = new PointLight3D(true, true); light.x = 0; light.y = 400; light.z = -300; } public function createBlock():void { var num:Number = MathUtil.decimalRandomRange(0, 3); trace("num : " + num); switch(num) { case 1: createBlock3(); break; case 2: createBlock2(); break; case 3: createBlock4(); break; default: createBlock1(); } //createBlockDebug2(); } private function createBlock1():void { cubeML = new MaterialsList(); cubeML.addMaterial(new FlatShadeMaterial(light, 0xFF9912), "all"); blockAry = []; ckFlg = 0; var xPos:Number = posAry[MathUtil.decimalRandomRange(0, 2)]; var zPos:Number = posAry[MathUtil.decimalRandomRange(0, 2)]; trace(zPos); for(var i:uint = 0; i < 4; i++) { blockAry[i] = createCube(cubeML, 40, 40, 40); blockAry[i].x = xPos; blockAry[i].y = 400 + (40 * i); blockAry[i].z = zPos; ckAry[i] = false; } addEventListener(Event.ENTER_FRAME, checkFallEnd); } private function createBlock2():void { cubeML = new MaterialsList(); cubeML.addMaterial(new FlatShadeMaterial(light, 0x0DB8FF), "all"); blockAry = []; ckAry = []; ckFlg = 0; var xPos:Number = posAry[MathUtil.decimalRandomRange(0, 2)]; var zPos:Number = posAry[MathUtil.decimalRandomRange(0, 2)]; trace(zPos); blockAry[0] = createCube(cubeML, 40, 40, 40); blockAry[0].x = xPos; blockAry[0].y = 400; blockAry[0].z = zPos; ckAry[0] = false; blockAry[1] = createCube(cubeML, 40, 40, 40); blockAry[1].x = xPos - 40; blockAry[1].y = 400; blockAry[1].z = zPos; ckAry[1] = false; blockAry[2] = createCube(cubeML, 40, 40, 40); blockAry[2].x = xPos + 40; blockAry[2].y = 400; blockAry[2].z = zPos; ckAry[2] = false; addEventListener(Event.ENTER_FRAME, checkFallEnd); } private function createBlock3():void { cubeML = new MaterialsList(); cubeML.addMaterial(new FlatShadeMaterial(light, 0x0AFF26), "all"); blockAry = []; ckAry = []; ckFlg = 0; var xPos:Number = posAry[MathUtil.decimalRandomRange(0, 2)]; var zPos:Number = posAry[MathUtil.decimalRandomRange(0, 2)]; trace(zPos); blockAry[0] = createCube(cubeML, 40, 40, 40); blockAry[0].x = xPos; blockAry[0].y = 400; blockAry[0].z = zPos; ckAry[0] = false; blockAry[1] = createCube(cubeML, 40, 40, 40); blockAry[1].x = xPos - 40; blockAry[1].y = 400; blockAry[1].z = zPos; ckAry[1] = false; blockAry[2] = createCube(cubeML, 40, 40, 40); blockAry[2].x = xPos; blockAry[2].y = 400; blockAry[2].z = zPos + 40; ckAry[2] = false; blockAry[3] = createCube(cubeML, 40, 40, 40); blockAry[3].x = xPos - 40; blockAry[3].y = 400; blockAry[3].z = zPos + 40; ckAry[3] = false; addEventListener(Event.ENTER_FRAME, checkFallEnd); } private function createBlock4():void { cubeML = new MaterialsList(); cubeML.addMaterial(new FlatShadeMaterial(light, 0xFF0CF5), "all"); blockAry = []; ckAry = []; ckFlg = 0; var xPos:Number = posAry[MathUtil.decimalRandomRange(0, 2)]; var zPos:Number = posAry[MathUtil.decimalRandomRange(0, 2)]; trace(zPos); blockAry[0] = createCube(cubeML, 40, 40, 40); blockAry[0].x = xPos; blockAry[0].y = 400; blockAry[0].z = zPos; ckAry[0] = false; blockAry[1] = createCube(cubeML, 40, 40, 40); blockAry[1].x = xPos + 40; blockAry[1].y = 400; blockAry[1].z = zPos; ckAry[1] = false; blockAry[2] = createCube(cubeML, 40, 40, 40); blockAry[2].x = xPos; blockAry[2].y = 400; blockAry[2].z = zPos + 40; ckAry[2] = false; addEventListener(Event.ENTER_FRAME, checkFallEnd); } //--------------------------------------- // X軸方向5個Cube //--------------------------------------- private function createBlockDebug():void { cubeML = new MaterialsList(); cubeML.addMaterial(new FlatShadeMaterial(light, 0xFF0E0B), "all"); blockAry = []; ckAry = []; ckFlg = 0; var xPos:Number = 0; var zPos:Number = posAry[MathUtil.decimalRandomRange(0, 2)]; trace(zPos); blockAry[0] = createCube(cubeML, 40, 40, 40); blockAry[0].x = xPos; blockAry[0].y = 400; blockAry[0].z = zPos; ckAry[0] = false; blockAry[1] = createCube(cubeML, 40, 40, 40); blockAry[1].x = xPos - 40; blockAry[1].y = 400; blockAry[1].z = zPos; ckAry[1] = false; blockAry[2] = createCube(cubeML, 40, 40, 40); blockAry[2].x = xPos + 40; blockAry[2].y = 400; blockAry[2].z = zPos; ckAry[2] = false; blockAry[3] = createCube(cubeML, 40, 40, 40); blockAry[3].x = xPos - 80; blockAry[3].y = 400; blockAry[3].z = zPos; ckAry[3] = false; blockAry[4] = createCube(cubeML, 40, 40, 40); blockAry[4].x = xPos + 80; blockAry[4].y = 400; blockAry[4].z = zPos; ckAry[4] = false; addEventListener(Event.ENTER_FRAME, checkFallEnd); } //--------------------------------------- // Z軸方向5個Cube //--------------------------------------- private function createBlockDebug2():void { cubeML = new MaterialsList(); cubeML.addMaterial(new FlatShadeMaterial(light, 0xFF0E0B), "all"); blockAry = []; ckAry = []; ckFlg = 0; var xPos:Number = posAry[MathUtil.decimalRandomRange(0, 2)]; var zPos:Number = 0; trace(zPos); blockAry[0] = createCube(cubeML, 40, 40, 40); blockAry[0].x = xPos; blockAry[0].y = 400; blockAry[0].z = zPos; ckAry[0] = false; blockAry[1] = createCube(cubeML, 40, 40, 40); blockAry[1].x = xPos; blockAry[1].y = 400; blockAry[1].z = zPos - 40; ckAry[1] = false; blockAry[2] = createCube(cubeML, 40, 40, 40); blockAry[2].x = xPos; blockAry[2].y = 400; blockAry[2].z = zPos + 40; ckAry[2] = false; blockAry[3] = createCube(cubeML, 40, 40, 40); blockAry[3].x = xPos; blockAry[3].y = 400; blockAry[3].z = zPos - 80; ckAry[3] = false; blockAry[4] = createCube(cubeML, 40, 40, 40); blockAry[4].x = xPos; blockAry[4].y = 400; blockAry[4].z = zPos + 80; ckAry[4] = false; addEventListener(Event.ENTER_FRAME, checkFallEnd); } //--------------------------------------- // ブロック移動用キーイベント //--------------------------------------- public function blockKeyEvent(e:KeyboardEvent):void { trace("keyDownHandler2: " + e.keyCode); switch(e.keyCode) { case 73: // i addWorldForceZ(40); break; case 77: // m addWorldForceZ(-40); break; case 76: // l addWorldForceX(40); break; case 74: // j addWorldForceX(-40); break; case 75: // k addWorldForceY(-90); break; } } private function addWorldForceX(n:Number):void { for(var i:uint = 0; i < blockAry.length; i++) { blockAry[i].addWorldForce(new Vector3D(n, 0, 0), blockAry[i].currentState.position); } } private function addWorldForceZ(n:Number):void { for(var i:uint = 0; i < blockAry.length; i++) { blockAry[i].addWorldForce(new Vector3D(0, 0, n), blockAry[i].currentState.position); } } private function addWorldForceY(n:Number):void { for(var i:uint = 0; i < blockAry.length; i++) { blockAry[i].addWorldForce(new Vector3D(0, n, 0), blockAry[i].currentState.position); } } private function addWorldTorqueY(n:Number):void { for(var i:uint = 0; i < blockAry.length; i++) { blockAry[i].addWorldTorque(new Vector3D(0, n, 0)); } } public function createCube(materials:MaterialsList, width:Number=500, depth:Number=500, height:Number=500, segmentsS:int=1, segmentsT:int=1, segmentsH:int=1, insideFaces:int=0, excludeFaces:int=0):RigidBody { var cube:Cube = new Cube(materials, width, depth, height, segmentsS, segmentsT, segmentsH, insideFaces, excludeFaces); target.addChild(cube); var jbox:JBox = new JBox(new Pv3dMesh(cube), width, depth, height); jbox.restitution = 0; // Default = 0.2 jbox.friction = 1; // Default = 0.5 physics.addBody(jbox); jboxAry.push(jbox); return jbox; } public function destroyCube(n:Number):void { target.removeChild(physics.getMesh(jboxAry[n])); physics.removeBody(jboxAry[n]); jboxAry[n] = null; } //--------------------------------------- // 一番新しいブロックのCubeすべての停止を判定 //--------------------------------------- private function checkFallEnd(e:Event):void { ckAry = []; for(var i:uint = 0; i < blockAry.length; i++) { if(blockAry[i].currentState.linVelocity.x == 0 && blockAry[i].currentState.linVelocity.y == 0 && blockAry[i].currentState.linVelocity.z == 0) { ckAry[i] = true; } if(blockAry[i].y < -200) { ckAry[i] = true; } } for(i = 0; i < blockAry.length; i++) { if(ckAry[i] == true) { ckFlg++; } } if(ckFlg >= blockAry.length) { trace("MOVE COMPLETE!!!"); removeEventListener(Event.ENTER_FRAME, checkFallEnd); checkAllCube(); setAllCubeActive(); dispatchEvent(new Event("FALL_COMPLETE")); } ckFlg = 0; } //--------------------------------------- // 消去対象のCubeの精査と消去 // なんかもっといいコード誰か考えて!! //--------------------------------------- private function checkAllCube():void { var destroyCubeAry:Array = new Array(); var lowestAry:Array = new Array(); var destroyCubeNo:Number = 0; var xStart:Number = -100; var zStart:Number = -100; // フラグの初期化 var destroyCubeFlhAry:Array = new Array(); for(var i:uint = 0; i < jboxAry.length; i++) { destroyCubeFlhAry[i] = false; } // マス用配列の初期化 for(var j:Number = 0; j < massNum; j++) { var tmpAry:Array = new Array(); for(var k:Number = 0; k < massNum; k++) { tmpAry[k] = null; //lowestAry[j][k] = false; } lowestAry[j] = tmpAry; } // まずは全Cubeから最下段にあるCubeを検索し、指定されたマス内にあるCubeを2次元配列に格納 for(i = 0; i < jboxAry.length; i++) { if(jboxAry[i] != null) { // X軸Z軸で5個並んでるところのチェック(一番下の段だけ) if(jboxAry[i].y <= 40 && jboxAry[i].y >= 0) { for(j = 0; j < massNum; j++) { if(jboxAry[i].x >= xStart && jboxAry[i].x <= (xStart + 40)) { zStart = -100; for(k = 0; k < massNum; k++) { if(jboxAry[i].z >= zStart && jboxAry[i].z <= (zStart + 40)) { lowestAry[j][k] = i; } zStart += 40; } } xStart += 40; } xStart = -100; } // 下に落ちてるCubeを削除 if(jboxAry[i].y < -100) { trace("jboxAry.y : " + jboxAry[i].y); destroyCube(i); } } } // 上記で作成された2次元配列から5個揃っている列をX、Z軸双方からサーチして削除対象用配列に格納 for(j = 0; j < massNum; j++) { var chkFlg:Number = 0; for(k = 0; k < massNum; k++) { if(lowestAry[j][k] != null) { chkFlg++; } } // Z軸方向にサーチして5個揃ってたら削除対象配列にpush if(chkFlg == 5) { for(k = 0; k < massNum; k++) { destroyCubeAry.push(lowestAry[j][k]); } } chkFlg = 0; } for(k = 0; k < massNum; k++) { chkFlg = 0; for(j = 0; j < massNum; j++) { if(lowestAry[j][k] != null) { chkFlg++; } } // X軸方向にサーチして5個揃ってたら削除対象配列にpush if(chkFlg == 5) { for(j = 0; j < massNum; j++) { destroyCubeAry.push(lowestAry[j][k]); } } chkFlg = 0; } // 重複要素の削除 destroyCubeAry = ArrayUtil.createUniqueCopy(destroyCubeAry); // ここで配列に入ってるものを削除 for(i = 0; i < destroyCubeAry.length; i++) { //trace("destroyCubeAry.y : " + destroyCubeAry[i].y); totalDestroyCube++; destroyCube(destroyCubeAry[i]); } } //--------------------------------------- // 残ってるjBoxをすべてActiveに //--------------------------------------- private function setAllCubeActive():void { for(var i:uint = 0; i < jboxAry.length; i++) { if(jboxAry[i] != null) { jboxAry[i].setActive(); } } } } class MathUtil { public static function decimalRandomRange(min:Number, max:Number, digit:uint = 0):Number { var randomNum:Number = Math.random() * (max - min) + min; return decimalRound(randomNum, digit); } public static function decimalRound(i:Number , digit:uint):Number { return Math.round(i * Math.pow(10 , digit)) / Math.pow(10 , digit); } } Tetris3D