메뉴 건너뛰기

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 48035
1875 자신이 만든 이미지를 그런지 스타일로 만들기. file 폐인생활 ⓝⓩⓔⓞ 2005.02.18 12831
1874 D-Day 카운터 만들기.(진짜!!! 수정) [13] file 엔젤퀸 2005.02.18 8954
1873 간단하게 문자 디자인 해봤습니다_중복이면 삭제바람 [5] file 양배추™ 2005.02.16 11281
1872 [BulRyang]1st. 말풍선 그리기 [8] [Fs]불량 2005.02.15 11515
1871 이올린 팬드래건 [7] file 카심 2005.02.12 9599
1870 [도트] 고양이를 안고 있는 소녀 [9] file 이현숙 2005.02.10 15265
1869 [페인터] 귀여운 차이나풍의 소녀 케릭 그리기 [4] file Hwoarang 2005.02.10 8757
1868 이상한 연기 만들기 [4] file Mr.Rays 2005.02.09 10635
» [ 퍼즐 알고리즘의 원리 1 ] [1] file Fc 2005.02.09 5603
1866 움직이는 메뉴 네비게이션 [2] file totomylove 2005.02.09 16417
1865 부드럽게 움직이는 효과를 이용한 마스크효과 [4] file totomylove 2005.02.06 14066
1864 간단한 네비게이션 액션 [7] file totomylove 2005.02.06 9406
1863 Art history brush tool을 이용한 회화효과 나타내기 [1] file 로티 2005.02.06 7481
1862 'Make a planet' [18] file RuIX 2005.02.05 11429
1861 알수 없는 글씨 효과 강좌. ; [7] file 검은호수 2005.02.04 14676
1860 'Make a realtic starfield' [10] file RuIX 2005.02.03 9182
1859 일러스트 인물따기..<머리카락&완성편> [7] file i3in_˝ 2005.02.01 18084
1858 반입체 계단효과 [9] file RuIX 2005.02.01 12950
1857 CG 채색 과정 [4] file [수아]작은아씨 2005.01.31 9776
1856 일러스트 인물따기..<코> [3] file i3in_˝ 2005.01.29 12960