메뉴 건너뛰기

XE : Xpress Engine




웹디자인 강의

휴~ 튜토리얼 기사를 거의 1년만에 쓰는건 아닌지 생각됩니다.^^;

오늘은 퍼즐을 들고 왔습니다.
퍼즐 만드는게 생각보다 생당히 까다롭고 어렵더군요.
물론 퍼즐은 만드는 것 보다 푸는 것이 더 어렵다고 합니다.

어렸을 적에 루빅은 한번즘 모두 해보았으리라 생각이 됩니다.
제가 만든 퍼즐은 루빅의 한 단으로 평면입니다.
다음의 그림에서 보는 형태입니다.


이것을 만들기 위해서는 우선 총 9개로 나뉘어지는 면개체가 필요합니다.

9개의 퍼즐면 개체 준비는 복제로 사용할 수도 있으며, 스테이지에 그냥 배치할 수도 있습니다. 방법은 여러가지가 있습니다.
만약에 복제를 한다면 다음과 같은 코드를 사용할 수 있을것 같습니다.
for (var i = 0; i<9; i++)
{
        x = int(i%3);
        y = int(i/3);
        mc = _level.attachMovie("puzzle", puzzle_name[i], i+1, {_x:startX+50*x, _y:startY+50*y});
        if (mc._name != "puzzle")
        {
                mc.N.text = i;
        }
        else
        {
                mc.N.text = "PUZZLE";
        }
}

본 튜토리얼에서는 다음과 같이 같은 퍼즐 9개를 준비하고 있습니다.


코드 버전를 어떻게 구성하는 것이 바람직한가..고민했고,.. AS2.0 클래스로 준비했는데.. 어쩌면 튜토리얼이 좀더 힘겨울 수도 있을텐데요. 이점 양해를 바랍니다.^^;

우선 본 튜토리얼은 3개의 강좌로 나뉘어서 진행하기 때문에 클래스는 Puzzle_00, Puzzle_01, Puzzle_02로 나뉘어 집니다.

클래스 설계전에 필요한 것은 9개의 퍼즐을 준비하는 것입니다.
우선 흰색바탕의 사각형 툴을 사용해서 하나의 무비클립 심벌을 만든 후 위의 그림처럼 총 9개의 퍼즐을 3행3열로 구성하며, 클립의 이름은 puzzle0~puzzle8까지 줍니다.
다음으로 무비클립안에 num이라는 다이나믹텍스트 필드명을 가진 개체를 추가적으로 만들어 줍니다.

그리고 현 무비클립의 타임라인에 다음과 같은 코드를 기술합니다.
this.num.text = this._name.substring(6);

자 그리고 테스트를 하게 되면... 다음과 같이 숫자가 있는 퍼즐의 기본 모습을 볼 수 있을 것이다.





이제 메인(_level) 타임라인으로 돌아옵니다.
클래스만드는 작업부터 시작합니다.
Object.prototype.funnyPuzzle = new Puzzle_00("puzzle", this);

funnyPuzzle라는 Puzzle_00 의 자식을 만드는 순간입니다.
만드는 과정에서 "puzzle"라는 문자열과 함께 this를 보냅니다.
이 this는 _level이기 때문에 클래스로 전달되게 되면 그대로 클래스에서도 메인타임라인과 같은 형식으로 클래스를 원활히 사용할 수 있게 됩니다.

이제 다음 코드를 추가합니다.

// =====================
get_Puzzle_Name = function (puzzles)
{
        funnyPuzzle.A_puzzle_names.push(puzzles);
        funnyPuzzle.puzzle_mc_click(puzzles)
};
// =====================
for (var i = 0; i<9; i++)
{
        get_Puzzle_Name(_level["puzzle"+i]);
}

get_Puzzle_Name 메서드는 funnyPuzzle이 상속받은 A_puzzle_names의 배열에 각 퍼즐의 이름을 저장하고, 각 퍼즐을 클릭해서 작동할 수 있도록 하는 puzzle_mc_click라는 메서드를 puzzles라는 퍼즐 개체를 던지면서 호출하면서 하는 역할을 합니다.

funnyPuzzle의 puzzle_mc_click는 각 개체를 클릭하는 역할을 하지만 그것을 다음처럼 바꾸어서 사용할 수도 있습니다.
for (var i=0; i<12; i++)
{
    this["puzzzle"+i].onRelease = function()
    {
    }
}

어느편을 사용하느냐는 편한 방법을 택할 수 있지만 오히려 코드처리에있어 전자가 더 나은 방법이라 판단이 됩니다.

이제 이렇게 해서 main에서 작업은 모두 마쳤습니다.

이제 클래스를 만들어가는 작업입니다.
클래스를 만들기 위해서는 새 문서(Ctrl+N)를 열고 ActionScript File를 선택합니다.





새로 연 클래스 문서에 다음을 기술합니다.

dynamic class Puzzle_00 extends MovieClip
{

}

클래스는 dynamic로 시작되어 있는데 이것은 클래스의 자식개체가 상속(public)한 속성 외에 자신만의 특성도 추가할 수 있도록 하기 위해서이고, 클래스이름은 Puzzle_00 이며, MovieClip를 상속받았습니다.
이 때 MovieClip를 수퍼클래스, Puzzle_00을 자식클래스라고 하며, 자식 개체인 funnyPuzzle가 볼때는 MovieClip는 할아버지, Puzzle_00은 부모가 됩니다.

이제 클래스를 위해 필요한 각 변수를 정리하면 우선 메인인 플래시문서(fla)에서 던져준 각 퍼즐 클립들과 _level 타임라인입니다.
그리고 클릭했을 때

이와 같이 위치가 서로 바뀌도록 하기 위해서는 우선 빈공간을 나타내는 클립과 클릭한 개체가 지속적으로 빈 공간으로 이동하도록 하도록 하는 하기 위해 저장되어야 할 이전값..입니다.


그러면 다음과 같은 멤버들이 클래스 필요함을 알 수 있습니다.

        public var visible_clip:MovieClip;
        public var randomN:Number;
        private var main:MovieClip;
        private var puzzleClip:MovieClip;
        public var original_x:Number;
        public var original_y:Number;

public, private가 붙어 있는데 그것은 자식에게 상속을 할 것인가 혹은 그렇지 않은 가를 나타냅니다.
public는 상속이 되게 private는 상속이 되지 않도록 하기 위해서 작성된 키워드며, 이들을 보고 접근제어자라고 합니다.
생략해도 되며, 생략하게 되면 변수는 무조건 public특성을 갖게 됩니다.

지금까지 작성된 클래스는 다음과 같습니다.
dynamic class Puzzle_00 extends MovieClip
{
        public var visible_clip:MovieClip;
        public var randomN:Number;
        private var main:MovieClip;
        private var puzzleClip:MovieClip;
        public var original_x:Number;
        public var original_y:Number;
}

클래스는 생성자가 꼭 필요한 것은 아니지만 생성자를 두는 것이 좋습니다.
그건 바로 생성자는 객체를 초기화시키는 일을 담당하기 때문입니다.


이제 멤버다음에 다음을 기술합니다.
        function Puzzle_00(_puzzle, _main)
        {
                this.main = _main;
                this.puzzle = _puzzle;
                trace("Puzzle"+" "+arguments[0]);
                this.randomN = random(9);
                this.visible_clip = this.main[arguments[0]+this.randomN];
                this.first_load();
        }
main에서는 "puzzle"와 _level인 this를 던지며,
생성자에서는 정확히 두개의 인수로 각각 받습니다.
그리고 다시 생성자 자신의 것으로 만듭니다.
this.main = _main
이렇게 되면 앞으로 클래스에서 this.main만 사용하면 _level이 되게 됩니다.
그리고 this.puzzle는 arguments로 더이상 사용하지 않지만 사용되었는데, 그건 둘중 편한것으로 선택해서 작성할 수 있도록 하기 위해서입니다.
그리고 this.visible_clip는 arguments[0]+randomN으로 받았는데..
여기서 arguments 는 이 생성자함수의 독립변수를 나타내는 영어의미입니다.
그리고 arguments를 사용하게 되면 더이상 함수에서 매개변수를 사용하지 않아도 된다는 것을 의미합니다.
가령 함수를 호출시 2005, "Hi~!"를 인수를 보냈을 때..
받는 곳에서는 인수없이 받고 arguments를 사용할 수 있습니다.
Hello(2005, "Hi~!");

Hello = function()
{
   arguments[0]; // 2005
   arguments[1]; // "Hi~!"
}
바로 arguments는 함수를 위한 함수만의 독특한 배열이라고 생각하면 됩니다.
물론 for문을 사용할수도 있습니다.
Hello = function()
{
   for(var i=0; i++!=arguments.length;)
   {
      trace(arguments[i]);
   }
}

그래서 다음과 같은 형식이 가능하게 된 것입니다.

this.main[arguments[0]+this.randomN];

this.randomN은 0~8까지의 무작위 수를 추출하게 되므로
위의 한줄은 다음과 같은 코드입니다.
_level["puzzle"+N] // N은 random(9)로 나온 난수를 나타냅니다.
this.visible_clip 는 한줄을 대신할 무비클립입니다.

생성자의 마지막줄에서는 this.first_load(); 라는 메서드를 호출합니다.

이제 this.first_load 메서드를 기술합니다.

        private function first_load()
        {
                this.visible_clip._visible = false;
                this.original_x = this.visible_clip._x;
                this.original_y = this.visible_clip._y;
        }

이렇게 되면 난수로 인해서 발생된 최초의 클립은 감추어지게 되고,
그 클립이 가진 값은 각 변수에 저장됩니다.
이들 변수는 각 퍼즐을 클릭했을 때 퍼즐들이 이동할 최초의 좌표값을 나타냅니다.

이제 마지막으로 main(fla)에서 호출했던 함수를 작성합니다.

        public function puzzle_mc_click(_puzzle)
        {
                this.puzzleClip = _puzzle;
                this.puzzleClip.funnyPuzzle = this.main.funnyPuzzle;
                this.puzzleClip.onRelease = function()
                {
                        var oldX = this._x;
                        var oldY = this._y;
                        this._x = this.funnyPuzzle.original_x;
                        this._y = this.funnyPuzzle.original_y;
                        this.funnyPuzzle.original_x = oldX;
                        this.funnyPuzzle.original_y = oldY;
                };
        }

코드가 난해할 수도 있습니다.
우선 이 코드는 클래스 안에 위치하며,
클래스가 낳은 자식개체인 funnyPuzzle 에 의해 작동된다는 것이 특징입니다.
// main flash document
// =====================
Object.prototype.funnyPuzzle = new Puzzle_00("puzzle", this);
// =====================
get_Puzzle_Name = function (puzzles)
{
        funnyPuzzle.A_puzzle_names.push(puzzles);
        funnyPuzzle.puzzle_mc_click(puzzles)
};
// =====================
for (var i = 0; i<9; i++)
{
        get_Puzzle_Name(_level["puzzle"+i]);
}

그리고 this.puzzleClip는 메인에서 던져준 값으로 인해서 각 퍼즐클립을 담고 있습니다.
개별퍼즐클립을 나타냅니다.
this.puzzleClip = _puzzle;
그리고 코드의 수를 줄이기 위해서 클래스의 자식개체인 funnyPuzzle는
퍼즐 클립에 속한 개체로 저장되게됩니다.

this.puzzleClip.funnyPuzzle = this.main.funnyPuzzle;


이제 퍼즐의 원리는 지금 부터입니다.

우선 클릭하기 전에 먼저 생각해야 할 것은 클릭한 후 개체가 이동되면 개체가 있던 원래의 자리는 다시 빈 공백이 되는 동시에
클릭되었던 클립을 포함해서 모든 클립들이 가야할 다음의 위치입니다.
그렇다면 가장 먼저 선행되어야 할 부분은 우선 현재 클립의 좌표 저장이 됩니다.

var oldX = this._x;
var oldY = this._y;

이때까지는 아직 개체가 출발하지 않은 상태입니다.

그리고 이동을 위해서 다음의 코드가 필요합니다.
this._x = this.funnyPuzzle.original_x;
this._y = this.funnyPuzzle.original_y;
현재 비어있는 공간으로 이동하는 것을 나타냅니다.

위의 클래스 생성자에서 randomN으로 인해서 임의적인 개체를 생성했을 때 first_load 메서드에서는 그 개체의 x, y를 각각 original_x, original_y로 저장했다는 것을 분명히 알 수 있습니다.
그리고 코드에서는 this.funnyPuzzle.original_x 이와 같이 작성되었는데..
그렇다면 이것은 무엇을 말하는 걸까요...
바로 클래스에서 메인의 funnyPuzzle 를 가리키며, 상속받았던 값 x를 나타냅니다.

이제 개체는 옮겨진 상태가 됩니다.
개체는 옮겨지고 난 뒤 옮기기전의 자리는 공백으로 남게 됩니다
이는 또 다시 모든 개체가 가야할 미래의 좌표가 됩니다.
그렇다면 이것을 해결하기 위해서는 현재의 좌표인 this._x, this._y에 할당되었던 this.funnyPuzzle.original_x, this.funnyPuzzle.original_y에 다시 이전 값을 담아야 한다는 결론을 말해줍니다.

this.funnyPuzzle.original_x = oldX;
this.funnyPuzzle.original_y = oldY;

이 부분이 바로 퍼즐 알고리즘의 가장 기초적인 원리입니다. 이 부분을 분명히 이해한다면 스스로 퍼즐을 만들 수 있습니다.

이동에 대한 코드를 정리하면 다음과 같습니다.

        var oldX = this._x;
        var oldY = this._y;
        this._x = this.funnyPuzzle.original_x;
        this._y = this.funnyPuzzle.original_y;
        this.funnyPuzzle.original_x = oldX;
        this.funnyPuzzle.original_y = oldY;

이제 클래스에서 사용된 전체코드를 제시합니다.

dynamic class Puzzle_00 extends MovieClip
{
        public var visible_clip:MovieClip;
        public var randomN:Number;
        private var main:MovieClip;
        private var puzzleClip:MovieClip;
        public var original_x:Number;
        public var original_y:Number;
        function Puzzle_00(_puzzle, _main)
        {
                this.main = _main;
                this.puzzle = _puzzle;
                trace("Puzzle"+" "+arguments[0]);
                this.randomN = random(9);
                this.visible_clip = this.main[arguments[0]+this.randomN];
                this.first_load();
        }
        private function first_load()
        {
                trace("this.visible_clip = "+typeof this.visible_clip);
                this.visible_clip._visible = false;
                this.original_x = this.visible_clip._x;
                this.original_y = this.visible_clip._y;
        }
        public function puzzle_mc_click(_puzzle)
        {
                this.puzzleClip = _puzzle;
                trace("this.puzzleClip = "+this.puzzleClip);
                this.puzzleClip.funnyPuzzle = this.main.funnyPuzzle;
                this.puzzleClip.onRollOver = function()
                {
                };
                this.puzzleClip.onRelease = function()
                {
                        // 이 개체를 누르면 이 개체는 비어있는 공간으로 이동해야 한다.
                        // 그전에 할일은 이동되기전 좌표값을 저장해두어야 한다.
                        // 그 이유는 이동한 후에 다시 그 자리가 비어있게 되기 때문이다.
                        // 이것을 해결하는 것이 oldX와 oldY의 할일이다.
                        var oldX = this._x;
                        var oldY = this._y;
                        // 이제 개체를 비어있는 곳으로 이동시킨다.
                        // 여기서 중요한점은 비어있는 곳의 자리는 단 한번만 처음 random 개체의 위치값이고,
                        // 그 후에는 항상 현개체를 눌러서 이동하기전의 자리이다.
                        this._x = this.funnyPuzzle.original_x;
                        this._y = this.funnyPuzzle.original_y;
                        // 그렇기 때문에 개체를 이동시킨 후 다음과 같이이전의 자리를 다시 이동할 자리로 이동시켜준다.
                        // 이렇게 되면 무한 자리반복이 일어나는 결과가 발생된다.
                        this.funnyPuzzle.original_x = oldX;
                        this.funnyPuzzle.original_y = oldY;
                };
        }
}



본 튜토리얼 기사는 퍼즐에 대해 다루고 있기 때문에 액션스크립트를 잘 모른다면 난해할 수 있습니다.


다음에는 지금의 코드를 업데이트해서 좀더 퍼즐 다운 형식을 다룰 예정입니다.

첨부파일을 보면 튜토리얼을 익히는데 도움이 되리라 봅니다..


휴~ 땀나네요...

아무쪼록 퍼즐에 대한 이해가 섰길 바랍니다.


새해 복 많 받으세요...


from Adam (Fc)




--------------------------------------------
Adam - flashconference.co.kr
            ysbn200.com
            nfmk.com
            Team NFMK
--------------------------------------------


번호 제목 글쓴이 날짜 조회 수
공지 공지 강좌를 올리기전 공지사항을 읽어주세요 [12] Eccen 2004.12.15 48030
171 플래쉬와 php 연동 예제 [5] file 한태종 2007.03.16 9859
170 좌우로 열리는 알림판 [9] file 아킨도 2005.11.18 9815
169 [동영상]입체도형 올려갑니다. [1] 이희덕 2002.09.24 9804
168 가속도 운동을 이용한 마우스 따라다니는 오브젝트.. [6] file 세나^.^ 2003.05.30 9804
167 [MX]초보강좌 - 마스크 강좌 [11] file 용신 2003.01.05 9775
166 [플래시동영상강의] 액션스크립트를 이용한 눈내리는 무비제작하기 [6] 서기 2006.12.21 9726
165 [중급] lineTo()로 그리는 플래시 그래픽 [5] 박승제 2004.04.20 9581
164 알람시계 만들기 [6] gimbob 2003.01.15 9571
163 [플래시 MX 동영상 강좌] 12. 버튼심볼 [11] 서기 2005.05.31 9546
162 모든 동영상을 -> swf,flv로 변환 tool[1분강좌] [3] 에듀멘 2006.11.09 9451
161 [동영상]복권(?)을 긁자 [9] 이희덕 2002.09.24 9432
160 간단한 네비게이션 액션 [7] file totomylove 2005.02.06 9406
159 플래쉬메뉴2 - 조용히 확대-ㅅ-?;; file 한태종 2007.02.06 9392
158 [플래시 MX 동영상 강좌] 19. 마스크 사용하기 [5] 서기 2005.06.08 9384
157 [플래시 MX 동영상 강좌] 24. 액션스크립트의 기초!!! [9] 서기 2005.06.20 9384
156 포토샵 blur필터를 이용한 이미지 효과 [4] file totomylove 2005.02.21 9359
155 타이틀이미지 쉽게 제작해보기 [11] 서기 2009.01.08 9310
154 [플래시 MX 동영상 강좌] 13. 무비클립 &모션가이드 [12] 서기 2005.06.01 9268
153 플래시에서 사진을 업로드하여 바로 미리보기 할수 있을까요? [2] file 최진영 2007.03.16 9235
152 제로보드 리스트를 플래시로(이미지미리보기가능) .. [17] file 삐리리 2005.06.21 9225