인터넷에 떠돌던 알카노이드 비슷한 게임.
처음에 게임을 시작하면 엄청난 양의 블럭에 당황하게 되지만
블럭이 블럭을 부수면서 엄청나게 늘어나는 공(?) 덕분에 클리어가 가능하다.
화면을 가득채우는 총천연색의 공들이 장관을 이루는, 제작자의 센스가 돋보이는 게임.
package
{
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.geom.ColorTransform;
import flash.geom.Matrix;
import flash.geom.Rectangle;
import flash.text.TextField;
import flash.text.TextFieldAutoSize;
import net.hires.debug.Stats;
[SWF(width = "465", height = "465", frameRate = "30")]
public class BlockBreaker extends Sprite
{
private static const HEIGHT:Number = 465;
private static const WIDTH:Number = 465;
private var _canvas:BitmapData;
private var _blocks:Blocks;
private var _fallBlocks:Vector.<Particle>;
private var _balls:Vector.<Particle>;
private var _bar:Bitmap;
public function BlockBreaker()
{
_canvas = new BitmapData(WIDTH, HEIGHT,false,0x000000);
addChild(new Bitmap(_canvas));
_blocks = new Blocks(WIDTH, 100);
_fallBlocks = new Vector.<Particle>();
var b:BitmapData = new BitmapData(50, 10, false, 0x00FF00);
addChild(_bar = new Bitmap(b));
_bar.y = WIDTH -b.width;
var _ball:Particle = new Particle(WIDTH / 2, HEIGHT / 2);
_ball.vx = Math.random() *10;
_ball.vy = -Math.random() *9 -1;
_ball.color = 0xFFFFFF;
_balls = new Vector.<Particle>();
_balls.push(_ball);
//var stats:Stats = new Stats();
//stats.y = 200;
//addChild(stats);
addEventListener(Event.ENTER_FRAME, update);
}
private function update(e:Event):void
{
_canvas.lock();
_canvas.colorTransform(_canvas.rect, new ColorTransform (0.9, 0.5, 0.9));
for each(var block:Particle in _blocks.values)
{
if (block)
{
_canvas.setPixel(block.x, block.y, block.color);
}
}
var removeBalls:Vector.<Particle> = new Vector.<Particle>();
for each(var ball:Particle in _balls)
{
var bvx:Number = ball.vx;
var bvy:Number = ball.vy;
var bspeed:Number = Math.sqrt(bvx * bvx + bvy * bvy);
var bradius:Number = Math.atan2(bvy, bvx);
for (var i:int = 0; i < bspeed;i++)
{
ball.x += ball.vx/bspeed;
ball.y += ball.vy/bspeed;
var hitParticle:Particle = _blocks.getParticle(ball.x, ball.y);
if(hitParticle)
{
var removedP:Particle = _blocks.removeParticle(ball.x, ball.y);
removedP.vx = Math.cos(bradius+Math.PI*2/(30*Math.random())-15)*3;
removedP.vy = 1;
removedP.color = hitParticle.color;
_fallBlocks.push(removedP);
ball.vy = -ball.vy;
}
if ((ball.x < 0 && ball.vx < 0) || (ball.x > WIDTH && ball.vx > 0))
{
ball.vx = -ball.vx;
}
if (ball.y < 0 && ball.vy < 0)
{
ball.vy = -ball.vy;
}
if (ball.y > HEIGHT)
{
removeBalls.push(ball);
}
if (_bar.hitTestPoint(ball.x, ball.y))
{
ball.vy = -Math.abs(ball.vy);
}
_canvas.setPixel(ball.x, ball.y, ball.color);
}
}
removeBalls.forEach(function(b:Particle, ...args):void {
var index:int = _balls.indexOf(b);
if (index != -1)
{
_balls.splice(index, 1);
}
});
var removeFallBs:Vector.<Particle> = new Vector.<Particle>();
_fallBlocks.forEach(function(fallP:Particle, ...args):void {
fallP.vy += 0.1;
fallP.x += fallP.vx;
fallP.y += fallP.vy;
_canvas.setPixel(fallP.x, fallP.y, fallP.color);
if (_bar.hitTestPoint(fallP.x,fallP.y))
{
var newball:Particle = new Particle(fallP.x,fallP.y);
newball.vx = Math.random() * 10;
newball.vy = Math.random() * 9 + 1;
newball.color = fallP.color;
_balls.push(newball);
removeFallBs.push(fallP);
}else if (fallP.y > HEIGHT)
{
removeFallBs.push(fallP);
}
});
removeFallBs.forEach(function(b:Particle,...args):void{
var index:int = _fallBlocks.indexOf(b);
if (index != -1)
{
_fallBlocks.splice(index, 1);
}
});
_bar.x = stage.mouseX;
_canvas.unlock();
if (_blocks.count == 0)
{
removeEventListener(Event.ENTER_FRAME, update);
var clearTF:TextField = new TextField();
clearTF.text = "CLEAR!\nおめでと";
clearTF.textColor = 0xFFFFFF;
clearTF.autoSize = TextFieldAutoSize.LEFT;
_canvas.draw(clearTF,new Matrix(5,0,0,5,WIDTH/2-clearTF.width*5/2,HEIGHT/2-clearTF.height*5/2));
}
}
}
}
import frocessing.color.ColorHSV;
class Blocks
{
public function get count():int { return _count;}
private var _count:int;
public function get width():Number { return _width; }
private var _width:Number;
public function get height():Number { return _height; }
private var _height:Number;
public var values:Vector.<Particle>;
function Blocks(width:Number,height:Number)
{
_width = width;
_height = height;
_count = width * height;
values = new Vector.<Particle>(width * height, false);
var c:ColorHSV = new ColorHSV();
for (var i:int = 0; i < _width; i++)
{
c.h = 360 * i / _width;
for (var j:int = 0 ; j < _height; j++ )
{
var p:Particle = new Particle(i, j);
p.color = c.value;
values[i + j * _width] = p;
}
}
}
public function getParticle(x:int, y:int):Particle
{
var index:int = x + y * _width;
if (index >= values.length || index < 0)
{
return null;
}
return values[x + y * _width];
}
public function removeParticle(x:int, y:int):Particle
{
var p:Particle = values[x + y * _width];
if (p)
{
_count--;
values[x + y * _width] = undefined;
}
return p;
}
}
class Particle
{
public var x:Number;
public var y:Number;
public var vx:Number = 0;
public var vy:Number = 0;
public var color:uint;
public function Particle(x:Number=0,y:Number=0 )
{
this.x = x;
this.y = y;
}
}
코드를 살펴보면 Blocks라는 구조체는 처음 시작할때의 블럭들의 정보만 담는듯 보인다. 개개의 블럭은
Particle이라는 구조체로 구성되는데 위에 보이듯 별 특별한 역할은 없다. blocks의 particle이 파괴되면
해당 particle을 blocks에서 제거하고 _fallBlocks라는 벡터에 해당 파티클의 정보를 속도를 조절해서
추가하는 듯 하다.
BlockBreaker라는 클래스가 핵심 클래스인데 생성자에서 여러 정보를 초기화해준다. 생성자에서 addChild
되는건 배경맵과 _bar(플레이어가 조종하는 공 튕기는 막대)뿐이다. _ball은 현재 플레이어가 튕길 수 있는
공의 벡터이다. 생성자의 마지막 부분에는 update 함수를 addEventListener에 추가해서 게임을 실행한다.
update 함수의 초반의 for each 문에서는 _blocks의 파티클 수 만큼 반복문을 돌면서 해당 픽셀의 위치와 컬러에 따라
_canvas(배경맵)의 픽셀을 바꿔준다. 굳이 addChild하지 않아도 되게끔 구성한 듯 하다.
그 밑의 반복문은 _ball의 파티클 수만큼 각 볼의 위치를 속도만큼 변경하고 튕기는 판정을 한다. Blocks과 충돌 판정을
매 프레임 거쳐 _fallBlocks에 파티클을 추가하고 벽과 막대에 대한 충돌 판정도 행하는데 이 반복문은 bspeed라는
값에 의해 내부적으로 다시 한번 루프를 돈다. bspeed는 볼의 x,y 속도값의 대각선 성분(x,y 각각의 제곱을 더한 값의
제곱근)의 크기인데 이것은 update함수에 따른 ball의 위치가 불연속적이라는 문제를 해결하기 위한 부분이다.
bspeed의 크기만큼 속도를 나눠 반복문 내에서 반복해주면 ball이 update 함수의 한번의 루프동안 이동한 모든 경로에
대해 충돌 판정이 가능해진다(게임중에 간혹 공이 위쪽 블럭 내부로 들어가서 블럭들을 헤집어 놓고 텅 빈 공간을 만드는
경우가 발생하는데 이 로직 때문으로 보인다 ). bspeed와 관련된 루프에선 원래 속도에서 bspeed만큼을 나눠준 값을
루프 내의 속도로 취해 ball의 위치를 계산한다. 화면 바깥으로 나가는 공은 removeBalls에 추가되는데 바로 밑의 함수가
removeBalls에 추가된 공만큼 _balls의 공을 제외시킨다. 아래의 _fallBlocks를 처리하는 부분도 위와 흡사하게
구성돼있다. 가장 하단에는 _blocks의 count를 헤아려 모든 블럭이 파괴되면 게임을 종료한다.
update 함수의 가장 위쪽의 lock()은 최적화를 위한 코드로 unlock() 이후에 setpixel의 변화를 bitmap 클래스가
인지하도록 하는 함수이다. 20% 정도 속도를 향상시킬 수 있다고 한다.
매 프레임마다 colorTransform()을 실행하게 되는데, 이것은 지정한 rect 구역을 인자값만큼 색상변환을 해준다.
픽셀들이 궤적을 남기며 이동하는 부분을 이 함수 하나로 간단하게 표현해냈다.
BlockBreaker.swf
최근 덧글