前回のサンプルを発展させて、テトリス(もどき)を作ってみました。最低限の機能のみです。
このぐらいであればアルゴリズムを知っていれば1日くらいで出来そうな感じですね。細かい機能や操作性の調整をして商用レベルまでするとなると数週間は必要な気がしますが。
ちなみにブロックを壁に沿った状態では回転できないことがありますが仕様です。本来はその場で回転できない場合は位置をずらす処理が入りますが時間かかりそうなので今回は見送りで。あと、正方形ブロックの回転がおかしいのも仕様ですね。
また、本当はクラスを小分けにするべきだと思いますがこれも仕様です...がもっと上手くまとめられるのは確かですね。。。orz
ちなみに操作はキーボードの矢印キーで行います。
キーボードが反応しない場合はテトリスの部分をクリックしてください。
また、判定処理があまい為か段々と処理が重くなっていく仕様があります。。
Blockクラスのactivateメソッドにgraphics.clear();の記述を追記することで、上記問題を解消しました。ベクター描画特有の問題ですね。クリアしなければレイヤーが無限に増殖しちゃいますから、重くなって当然ですね。。。orz
指摘してくださった方ありがとうございました!
- テトリスサンプル ソースファイル
- テトリスのソースファイルです。基本ロジックのみですがお役に立てれば幸いです。
| ライセンス: | 非営利目的のみ使用可能です。部分的であれば商用も可能です。(というのもテトリス自体が他者の著作物ですので...。) |
|---|---|
| アプリケーション: | Adobe Flash CS4 |
| ActionScript バージョン: | 3.0 |
package
{
import flash.display.MovieClip;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.events.TimerEvent;
import flash.geom.Point;
import flash.text.TextField;
import flash.text.TextFieldAutoSize;
import flash.text.TextFormat;
import flash.ui.Keyboard;
import flash.utils.Timer;
/**
* ...
* @author Ks-Product.com
*/
public class Tetris extends Sprite
{
private const UP:String = "up";
private const DOWN:String = "down";
private const LEFT:String = "left";
private const RIGHT:String = "right";
private var axisX:Number;//blockの軸、基本的に固定
private var axisY:Number;
private var dropPosX:int = 4;
private var dropPosY:int = 0;
private var currentPosX:int;//落下中のブロックの(軸の)位置
private var currentPosY:int;
private var blocks:Object;
private var blockName:Array;
private var currentBlock:Object;
private var spriteArray:Array;
private var board:Array;//リアルタイムに更新されるボード
private var board2:Array;//ブロックが詰まれる度に更新されるボード
private var timer:Timer;//ブロック落下用タイマー
private var dropSpeed:Number;//ブロック落下速度(更新間隔)
public function Tetris()
{
initialize();
}
private function initialize():void
{
axisX = 0;
axisY = 0;
currentPosX = dropPosX;
currentPosY = dropPosY;
board = [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]];
board2 = [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]];
//ボードとマス目と同じ数だけスプライトを生成
spriteArray = [];
var offset:uint = 20;
var margin:uint = 1;
for (var i:uint = 0; i < board.length; i++) {
spriteArray[i] = [];
for (var j:uint = 0; j < board[i].length; j++ ) {
var b:Block = new Block();
b.x = j * (b.width + margin) + offset;
b.y = i * (b.height + margin) + offset;
addChild(b);
spriteArray[i][j] = b;
}
}
//ブロックを定義
blocks = { t: { color:1,array:[[0, 0], [0, 1], [ -1, 0], [1, 0]] },
key1: { color:2,array:[[0, 0], [0, 1], [ -1, 1], [1, 0]] },
key2: { color:3,array:[[0, 0], [0, 1], [ 1, 1], [-1, 0]] },
l1: { color:4,array:[[0, 0], [0, 1], [ 0, -1], [-1, -1]] },
l2: { color:5,array:[[0, 0], [0, 1], [ 0, -1], [1, -1]] },
square: { color:6,array:[[0, 0], [0, 1], [ 1, 1], [1, 0]] },
tetris:{color:7,array:[[0, 0], [0, 1], [0, 2], [0, -1]]}};
blockName = ["t", "key1", "key2", "l1", "l2", "square", "tetris"];
dropSpeed = 500;
timer = new Timer(dropSpeed);
timer.addEventListener(TimerEvent.TIMER, onTime);
timer.start();
stage.addEventListener(MouseEvent.CLICK, onClick);
stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDown);
callNext();
render();
}
private function keyDown(e:KeyboardEvent):void
{
switch(e.keyCode) {
case Keyboard.LEFT:
moveBlock(LEFT);
break;
case Keyboard.RIGHT:
moveBlock(RIGHT);
break;
case Keyboard.DOWN:
moveBlock(DOWN);
break;
case Keyboard.UP:
rotate(90);
break;
default:
return;
break;
}
render();
}
private function onTime(e:TimerEvent):void
{
if (!moveBlock(DOWN)) {
//着地したら・・・
//boardをboard2にコピー
for (var i:uint = 0; i < board.length; i++) {
for (var j:uint = 0; j < board[i].length; j++ ) {
board2[i][j] = board[i][j];
}
}
if (isGameOver()) {
//終了処理
timer.stop();
timer.removeEventListener(TimerEvent.TIMER, onTime);
stage.removeEventListener(KeyboardEvent.KEY_DOWN, keyDown);
var gameover:Sprite = new Sprite();
gameover.graphics.beginFill(0x000000,0.8);
gameover.graphics.drawRect(0,0,110,220);
gameover.graphics.endFill();
gameover.x = 20;
gameover.y = 20;
var txt:TextField = new TextField();
var f:TextFormat = new TextFormat();
f.size = 16;
f.color = 0xFFFFFF;
txt.defaultTextFormat = f;
txt.text = "Game Over";
txt.x = (gameover.width / 2) - (txt.textWidth / 2) - 5;
txt.y = (gameover.height / 2) - (txt.textHeight / 2) -5;
gameover.addChild(txt);
addChild(gameover)
return;
}else {
var deleteLineArray:Array = checkLine();
if (deleteLineArray.length) deleteLine(deleteLineArray);
callNext();
}
}
update();
}
/**
* 次のブロックを呼び出す
*/
private function callNext():void
{
var name:String = blockName[Math.floor(Math.random() * blockName.length)];
currentBlock = { color:blocks[name].color,array:[] };
for (var i:uint = 0; i < blocks[name].array.length; i++ ) {
currentBlock.array[i] = [];
for (var j:uint = 0; j < blocks[name].array[i].length; j++ ) {
currentBlock.array[i][j] = blocks[name].array[i][j];
}
}
currentPosX = dropPosX;
currentPosY = dropPosY;
}
/**
* 指定されたラインを削除する
* @param lines
*/
private function deleteLine(lines:Array):void
{
for (var i:uint = 0; i < lines.length; i++ ) {
//削除
for (var j:uint = 0; j < board2[lines[i]].length; j++) {
board2[lines[i]][j] = 0;
}
//スペース分落下
for (var k:int = lines[i]; k >= 0; k--) {
if (k) {
for (var k2:uint = 0; k2 < board2[k].length; k2++) {
board2[k][k2] = board2[k - 1][k2];
}
}else {
for (var k3:uint = 0; k3 < board2[k].length; k3++) {
board2[k][k3] = 0;
}
}
}
}
}
/**
* ゲームオーバーになったかを返す
* @return
*/
private function isGameOver():Boolean
{
for (var i:uint = 0; i < currentBlock.array.length; i++) {
var my = currentBlock.array[i][1];
if ((currentPosY - my) < 0) {
return true;
}
}
return false;
}
/**
* 削除可能なラインを探しその行数を返す。
* @return
*/
private function checkLine():Array
{
var isEmpty:Boolean;
var array:Array = [];
for (var i:uint = 0; i < board.length; i++) {
isEmpty = false;
for (var j:uint = 0; j < board[i].length; j++ ) {
if (!board2[i][j]) {
isEmpty = true;
}
}
if (!isEmpty) {
array.push(i);
}
}
return array;
}
private function onClick(e:MouseEvent):void
{
rotate(90);
render();
}
/**
* 指定された方向にブロックを1マス移動する
* @param direction
* @return 成功:移動後の座標オブジェクト、失敗:0が返る
*/
private function moveBlock(direction:String):Object
{
if (!canMove(direction)) {
return 0;
}
switch(direction) {
case DOWN:
currentPosY++;
break;
case LEFT:
currentPosX--;
break;
case RIGHT:
currentPosX++;
break;
default:
trace("無効な値です。\n func:moveBlock param:", direction);
return 0;
break;
}
return { x:currentPosX, y:currentPosY };
}
/**
* 指定された方向に移動できるか調べる
* @param direction
* @return
*/
private function canMove(direction:String):Boolean
{
var cx:int = currentPosX;
var cy:int = currentPosY;
switch(direction) {
case DOWN:
cy++;
break;
case LEFT:
cx--;
break;
case RIGHT:
cx++;
break;
default:
trace("無効な値です。\n func:canMove param:", direction);
return false;
break;
}
for (var i:uint = 0; i < currentBlock.array.length; i++) {
var mx = currentBlock.array[i][0];
var my = currentBlock.array[i][1];
if ((cy - my) >= 0) {
if (cx + mx < 0 || cx + mx > 9 || cy - my > 19 || board2[cy - my][cx + mx]) {
//ブロックが置かれている、または、ボードの外であれば
return false;
}
}
}
return true;
}
/**
* 画面及び必要な情報を更新する
* @param e
*/
public function update():void
{
render();
}
/**
* ブロックをレンダリングする
*/
private function render():void
{
var i:uint;
var j:uint;
//ボードをリセット
for ( i = 0; i < board.length; i++) {
for (j = 0; j < board[i].length; j++ ) {
board[i][j] = board2[i][j];
}
}
//ブロックの情報をボードに書き込む
for ( i = 0; i < currentBlock.array.length; i++) {
var mx = currentBlock.array[i][0];
var my = currentBlock.array[i][1];
if ((currentPosY - my) >= 0) {
//ボードの範囲外の場合は無視
board[currentPosY - my][currentPosX + mx] = currentBlock.color;
}
}
//ボードの情報をスプライトに反映
for ( i = 0; i < board.length; i++) {
for (j = 0; j < board[i].length; j++ ) {
spriteArray[i][j].activate(board[i][j]);
}
}
}
/**
* 指定された軸を中心に回転する
* @param angle
*/
public function rotate(angle:Number):void
{
var tmp:Array = [];
for (var n:uint = 0; n < currentBlock.array.length; n++) {
tmp[n] = [];
for (var m:uint = 0; m < currentBlock.array[n].length; m++) {
tmp[n][m] = currentBlock.array[n][m];
}
}
var r:Number = angle * Math.PI / 180;
//var cos:Number = Math.cos(r); //三角関数は誤差が出る為、下記の方法で誤差を吸収する。
//var sin:Number = Math.sin(r);
var cos:Number = Math.round( Math.cos(r) * 1000000000000000 ) / 1000000000000000;
var sin:Number = Math.round( Math.sin(r) * 1000000000000000 ) / 1000000000000000;
for (var i:uint = 0; i < tmp.length; i++) {
var tx:Number = tmp[i][0] - axisX;
var ty:Number = tmp[i][1] - axisY;
var nx = cos * tx - sin * ty;
var ny = sin * tx + cos * ty;
tmp[i][0] = nx + axisX;
tmp[i][1] = ny + axisY;
}
if (canRotate()) {
for (n = 0; n < currentBlock.array.length; n++) {
for (m = 0; m < currentBlock.array[n].length; m++) {
currentBlock.array[n][m] = tmp[n][m];
}
}
}
function canRotate():Boolean
{
var cx:int = currentPosX;
var cy:int = currentPosY;
for (var i:uint = 0; i < tmp.length; i++) {
var mx = tmp[i][0];
var my = tmp[i][1];
if ((cy - my) >= 0) {
if (cx + mx < 0 || cx + mx > 9 || cy - my > 19 || board2[cy - my][cx + mx]) {
//ブロックが置かれている、または、ボードの外であれば
return false;
}
}
}
return true;
}
}
}
}
import flash.display.Sprite;
/**
* ...
* @author ...
*/
class Block extends Sprite
{
public function Block()
{
activate(0);
}
public function activate(color:uint):void
{
graphics.clear(); //追記部分
switch(color) {
case 0:
graphics.beginFill(0xCCCCCC);
graphics.drawRect(0, 0, 10, 10);
graphics.endFill();
break;
case 1://T
graphics.beginFill(0xCC00FF);
graphics.drawRect(0, 0, 10, 10);
graphics.endFill();
break;
case 2://key1
graphics.beginFill(0xFF0000);
graphics.drawRect(0, 0, 10, 10);
graphics.endFill();
break;
case 3://key2
graphics.beginFill(0x00FF00);
graphics.drawRect(0, 0, 10, 10);
graphics.endFill();
break;
case 4://l1
graphics.beginFill(0x0000FF);
graphics.drawRect(0, 0, 10, 10);
graphics.endFill();
break;
case 5://l2
graphics.beginFill(0xFFCC00);
graphics.drawRect(0, 0, 10, 10);
graphics.endFill();
break;
case 6://square
graphics.beginFill(0xFFFF00);
graphics.drawRect(0, 0, 10, 10);
graphics.endFill();
break;
case 7://tetris
graphics.beginFill(0x00FFFF);
graphics.drawRect(0, 0, 10, 10);
graphics.endFill();
break;
default:
break;
}
}
}



コメント(2)
twitterでつぶやく
> また、判定処理があまい為か段々と処理が重くなっていく仕様があります。。
たぶん色を塗り替えるときに、前の色を上書きしているからです。
その前に graphics.clear() すると、重くなくなりましたよ、私の場合。
このソースコードでも同じかどうかは試してないですけど。
ご意見ありがとうございます!
描画部分は盲点でした。
後日検証してみますね。