비트맵 예제: 회전하는 달 애니메이션Flash Player 9 이상, Adobe AIR 1.0 이상 회전하는 달 애니메이션 예제는 Bitmap 객체 및 비트맵 이미지 데이터(BitmapData 객체)를 사용하는 방법을 보여 줍니다. 이 예제에서는 평평한 달 표면의 이미지를 원시 이미지 데이터로 사용하여 회전하는 둥근 달의 애니메이션을 만듭니다. 다음과 같은 작업을 수행하는 방법을 볼 수 있습니다.
이 샘플에 대한 응용 프로그램 파일을 가져오려면 www.adobe.com/go/learn_programmingAS3samples_flash_kr를 참조하십시오. 회전하는 달 애니메이션 응용 프로그램 파일은 Samples/SpinningMoon 폴더에 있습니다. 이 응용 프로그램은 다음과 같은 파일로 구성됩니다.
외부 이미지를 비트맵 데이터로 로드이 샘플에서의 첫 번째 중요한 작업은 달 표면의 사진인 외부 이미지 파일을 로드하는 것입니다. 로드 작업은 MoonSphere 클래스의 두 메서드에 의해 처리되는데 하나는 로드 프로세스가 시작되는 MoonSphere() 생성자이고 다른 하나는 외부 이미지가 완전하게 로드되면 호출되는 imageLoadComplete() 메서드입니다. 외부 이미지를 로드하는 것은 외부 SWF를 로드하는 것과 비슷합니다. 둘 다 flash.display.Loader 클래스의 인스턴스를 사용하여 로드 작업을 수행합니다. 이미지를 로드하기 시작하는 MoonSphere() 메서드의 실제 코드는 다음과 같습니다. var imageLoader:Loader = new Loader(); imageLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, imageLoadComplete); imageLoader.load(new URLRequest("moonMap.png")); 첫 번째 행에서는 imageLoader라는 Loader 인스턴스를 선언합니다. 세 번째 행에서는 Loader 객체의 load() 메서드를 호출하고 로드할 이미지의 URL을 나타내는 URLRequest 인스턴스를 전달하여 로드 프로세스를 실제로 시작합니다. 두 번째 줄에서는 이미지가 완전히 로드되면 트리거될 이벤트 리스너를 설정합니다. addEventListener() 메서드는 Loader 인스턴스 자체에서 호출되지 않고 Loader 객체의 contentLoaderInfo 속성에서 호출됩니다. Loader 인스턴스 자체에서는 로드되는 내용과 관련된 이벤트를 전달하지 않습니다. 그러나 이 인스턴스의 contentLoaderInfo 속성에는 Loader 객체로 로드되는 내용(여기서는 외부 이미지)과 연결된 LoaderInfo 객체에 대한 참조가 들어 있습니다. LoaderInfo 객체는 외부 내용 로드의 진행과 완료에 관련된 여러 이벤트를 제공합니다. 이러한 이벤트에는 이미지가 완전하게 로드되면 imageLoadComplete() 메서드에 대한 호출을 트리거할 complete 이벤트(Event.COMPLETE) 등이 있습니다. 외부 이미지 로드를 시작하는 것도 프로세스에서 중요한 부분이지만 로드가 완료될 때 해야 할 작업에 대해 알고 있는 것도 똑같이 중요합니다. 위의 코드에서와 같이 이미지가 로드될 때 imageLoadComplete() 함수가 호출됩니다. 이 함수는 로드된 이미지 데이터를 사용하여 여러 작업을 수행하는데, 이 내용은 뒷부분에서 이어서 설명합니다. 이미지 데이터를 사용하려면 해당 데이터에 액세스해야 합니다. Loader 객체를 사용하여 외부 이미지를 로드하는 경우 로드된 이미지는 Bitmap 인스턴스가 되며 이 인스턴스는 Loader 객체의 자식 표시 객체로 연결됩니다. 이 경우에 Loader 인스턴스는 이벤트 리스너 메서드에 매개 변수로 전달되는 이벤트 객체에 포함되므로 이벤트 리스너 메서드에서 이 인스턴스를 사용할 수 있습니다. imageLoadComplete() 메서드의 첫 번째 줄은 다음과 같습니다. private function imageLoadComplete(event:Event):void { textureMap = event.target.content.bitmapData; ... } 이벤트 객체 매개 변수의 이름은 event이고 이는 Event 클래스의 인스턴스입니다. Event 클래스의 모든 인스턴스에는 target 속성이 있으며 이 속성은 이 이벤트를 트리거하는 객체(이 경우 앞에서 설명한 대로 addEventListener() 메서드가 호출된 LoaderInfo 인스턴스)를 참조합니다. 또한 LoaderInfo 객체에는 content 속성이 있는데 로드 프로세스가 완료되면 이 속성에는 로드된 비트맵 이미지가 들어 있는 Bitmap 인스턴스가 포함됩니다 . 이미지를 화면에 바로 표시하려면 이 Bitmap 인스턴스(event.target.content)를 표시 객체 컨테이너에 연결하면 됩니다. 또한 Loader 객체를 표시 객체 컨테이너에 연결할 수도 있습니다. 그러나 이 샘플에서는 로드된 내용이 화면에 표시되지 않고 원시 이미지 데이터의 소스로 사용됩니다. 결과적으로 imageLoadComplete() 메서드의 첫 번째 줄은 로드된 Bitmap 인스턴스의 bitmapData 속성(event.target.content.bitmapData)을 읽어 이를 textureMap이라는 인스턴스 변수에 저장합니다. 이 변수는 회전하는 달의 애니메이션을 만들기 위한 이미지 데이터의 소스로 사용됩니다. 이 내용에 대해서는 다음 단원에서 설명합니다. 픽셀을 복사하여 애니메이션 만들기애니메이션의 기본 정의는 이미지를 시차를 두고 계속 바꾸어서 움직이거나 변화하는 듯한 효과를 주는 것입니다. 이 샘플의 목표는 세로 축을 중심으로 회전하는 둥근 달의 효과를 만드는 것입니다. 그러나 애니메이션의 목적을 위해 샘플의 구형 왜곡 측면은 무시할 수 있습니다. 달 이미지 데이터의 소스로 로드되고 사용된 실제 이미지를 살펴 보십시오. 보는 바와 같이 이미지는 한 개나 여러 개의 구가 아니라 사각형의 달 표면 사진입니다. 이 사진은 정확하게 달의 적도에서 촬영되었기 때문에 이미지의 맨 위쪽과 아래쪽으로 가까울수록 이미지가 확장되고 왜곡되어 있습니다. 이미지에서 왜곡 현상을 제거하고 구형으로 보이도록 하기 위해 뒷부분에 설명된 대로 위치 변경 맵 필터를 사용할 것입니다. 그러나 소스 이미지가 사각형이기 때문에 구가 회전하는 효과를 만들기 위해 코드는 단지 달 표면 사진을 가로로 움직이기만 하면 됩니다. 이미지에는 실제로 달 표면 사진의 복사본 두 개가 서로 나란히 포함되어 있습니다. 이 이미지는 움직이는 모양을 만들기 위해 이미지 데이터가 반복적으로 복사된 것의 소스 이미지입니다. 이미지의 복사본 두 개가 서로 나란히 있으면 끊기지 않는 연속적인 스크롤 효과를 쉽게 만들 수 있습니다. 애니메이션의 프로세스를 단계별로 진행하여 애니메이션이 어떻게 작동하는지 살펴보겠습니다. 이 프로세스에는 실제로 두 개의 별도 ActionScript 객체가 사용됩니다. 먼저 로드된 소스 이미지가 있는데 이 이미지는 코드에서 textureMap이라는 BitmapData 인스턴스로 표현됩니다. 앞에서 설명한 대로 textureMap은 외부 이미지가 로드되는 즉시 다음 코드를 사용하여 이미지 데이터로 채워집니다. textureMap = event.target.content.bitmapData; textureMap의 내용은 사각형 달 이미지입니다. 또한 회전 애니메이션을 만들기 위해 코드에서는 sphere라는 Bitmap 인스턴스를 사용하는데 이는 달 이미지를 화면에 보여 주는 실제 표시 객체입니다. textureMap처럼 sphere 객체는 imageLoadComplete() 메서드에서 다음 코드를 사용하여 만들어지고 초기 이미지 데이터로 채워집니다. sphere = new Bitmap(); sphere.bitmapData = new BitmapData(textureMap.width / 2, textureMap.height); sphere.bitmapData.copyPixels(textureMap, new Rectangle(0, 0, sphere.width, sphere.height), new Point(0, 0)); 코드에 표시된 대로 sphere가 인스턴스화됩니다. sphere에 의해 표시되는 원시 이미지 데이터인 bitmapData 속성은 textureMap과 같은 높이에 절반의 너비로 생성됩니다. 즉, textureMap 이미지에는 두 개의 달 사진이 나란히 있으므로 sphere의 내용은 한 장의 달 사진 크기가 됩니다. 다음으로 bitmapData 속성은 copyPixels() 메서드를 사용하여 이미지 데이터로 채워집니다. copyPixels() 메서드 호출에서 매개 변수는 다음과 같은 사항을 나타냅니다.
시각적으로 표시하면 코드에서는 다음 이미지에서 외곽선이 표시된 textureMap에서 픽셀을 복사하여 sphere로 붙여 넣습니다. 즉, sphere의 BitmapData 내용은 다음에서 강조 표시된 textureMap의 일부분입니다. 그러나 이것은 sphere의 초기 상태, 즉 sphere로 복사된 첫 번째 이미지 내용일 뿐입니다. 소스 이미지가 로드되고 sphere가 만들어지면 imageLoadComplete() 메서드가 수행하는 마지막 작업은 애니메이션을 설정하는 것입니다. 애니메이션은 rotationTimer라는 Timer 인스턴스에 의해 작동되는데 이는 다음 코드에 의해 만들어지고 시작됩니다. var rotationTimer:Timer = new Timer(15); rotationTimer.addEventListener(TimerEvent.TIMER, rotateMoon); rotationTimer.start(); 코드에서는 먼저 rotationTimer라는 Timer 인스턴스를 만듭니다. Timer() 생성자로 전달되는 매개 변수는 rotationTimer가 15밀리초마다 자신의 timer 이벤트를 트리거해야 한다는 것을 나타냅니다. 다음으로 timer 이벤트(TimerEvent.TIMER)가 발생하면 rotateMoon() 메서드를 호출하도록 지정하여 addEventListener() 메서드를 호출합니다. 마지막으로 start() 메서드를 호출하여 타이머가 실제로 시작됩니다. rotationTimer가 정의된 방식으로 인해 약 15밀리초마다 Flash Player가 MoonSphere 클래스의 rotateMoon() 메서드를 호출하며 여기에서 달의 애니메이션이 발생합니다. rotateMoon() 메서드의 소스 코드는 다음과 같습니다. private function rotateMoon(event:TimerEvent):void { sourceX += 1; if (sourceX > textureMap.width / 2) { sourceX = 0; } sphere.Data.copyPixels(textureMap, new Rectangle(sourceX, 0, sphere.width, sphere.height), new Point(0, 0)); event.updateAfterEvent(); } 이 코드는 다음과 같은 세 가지 작업을 수행합니다.
이 코드는 15밀리초마다 반복해서 호출된다는 점에 유의하십시오. 소스 사각형의 위치가 계속 이동하고 픽셀이 sphere로 복사됨에 따라 화면에는 sphere가 나타내는 달 사진 이미지가 미끄러지듯 계속 이동하는 것으로 나타납니다. 즉, 달이 계속해서 회전하는 것처럼 보입니다. 구형 만들기달은 당연히 구형이며 사각형이 아닙니다. 따라서 샘플에서는 지속적으로 움직이는 사각형 달 표면 사진을 가져와서 구형으로 변환해야 합니다. 이 작업은 별도의 두 단계로 이루어집니다. 마스크를 사용하여 달 표면 사진의 원형 영역을 제외한 모든 내용을 숨기고, 위치 변경 맵 필터를 사용하여 3차원으로 보이도록 달 사진의 모양을 왜곡합니다. 먼저 원형 마스크를 사용하여 필터에 의해 생성된 구를 제외한 MoonSphere 객체의 내용을 모두 숨깁니다. 다음 코드는 마스크를 Shape 인스턴스로 만들어서 MoonSphere 인스턴스의 마스크로 적용합니다. moonMask = new Shape(); moonMask.graphics.beginFill(0); moonMask.graphics.drawCircle(0, 0, radius); this.addChild(moonMask); this.mask = moonMask; Sprite 클래스에 기반을 둔 MoonSphere는 표시 객체이므로 상속된 mask 속성을 사용하여 마스크를 MoonSphere 인스턴스에 직접 적용할 수 있습니다. 원형 마스크를 사용하여 사진의 일부를 숨기는 것만으로는 실제처럼 보이는 회전하는 구의 효과를 충분히 낼 수 없습니다. 달 표면 사진의 촬영 방식으로 인해 사진의 치수가 비례적이지 않으며 적도 부분과 비교해 이미지의 맨 위쪽이나 아래쪽으로 갈수록 이미지가 더욱 왜곡되고 늘어납니다. 달 사진의 모양을 왜곡하여 3차원으로 보이도록 하기 위해 위치 변경 맵 필터를 사용합니다. 위치 변경 맵 필터는 이미지를 왜곡하는 데 사용하는 필터 유형입니다. 이 경우 달 사진이 좀 더 사실적으로 보이도록 이미지의 가운데 부분은 바꾸지 않고 그대로 둔 채 맨 위쪽과 맨 아래쪽을 가로로 눌러서 달 사진을 "왜곡"합니다. 필터가 사진의 사각형 부분에 작동한다고 가정하고 가운데를 제외하고 맨 위쪽과 아래쪽을 누르면 사각형이 원형으로 변합니다. 이렇게 왜곡된 이미지에 애니메이션 효과를 적용하면 이미지의 가운데 부분이 맨 위쪽과 아래쪽에 가까운 부분보다 실제 픽셀 거리에서 더 멀리 이동하는 것처럼 보이므로 원이 실제로 3차원 객체(구)처럼 보이게 됩니다. 다음 코드는 displaceFilter라고 하는 위치 변경 맵 필터를 만드는 데 사용됩니다. var displaceFilter:DisplacementMapFilter; displaceFilter = new DisplacementMapFilter(fisheyeLens, new Point(radius, 0), BitmapDataChannel.RED, BitmapDataChannel.GREEN, radius, 0); 첫 번째 매개 변수인 fisheyeLens는 맵 이미지로 알려져 있습니다. 이 경우에는 프로그래밍 방식으로 작성되는 BitmapData 객체입니다. 해당 이미지를 만드는 방법은 픽셀 값을 설정하여 비트맵 이미지 만들기에서 설명합니다. 다른 매개 변수는 필터링된 이미지에서 필터가 적용될 위치, 위치 변경 효과를 조정하는 데 사용될 색상 채널 및 색상 채널이 위치 변경에 미치는 영향의 정도에 대해 설명합니다. 위치 변경 맵 필터가 만들어지면 이 필터는 imageLoadComplete() 메서드 내부에서 sphere에 적용됩니다. sphere.filters = [displaceFilter]; 마스크 및 위치 변경 맵 필터가 적용된 최종 이미지는 다음과 같은 모양입니다. 회전하는 달 애니메이션의 매 주기마다 소스 이미지 데이터의 새로운 스냅샷이 구의 BitmapData 내용을 덮어씁니다. 그러나 필터는 매번 다시 적용되지 않아도 됩니다. 필터가 비트맵 데이터(원시 픽셀 정보)에 적용되지 않고 Bitmap 인스턴스(표시 객체)에 적용되기 때문입니다. Bitmap 인스턴스는 실제 비트맵 데이터가 아니라 비트맵 데이터를 화면에 표시하는 표시 객체라는 것에 유의하십시오. 비유하자면 Bitmap 인스턴스는 화면에 사진 슬라이드를 표시하는 데 사용되는 슬라이드 프로젝터와 비슷하고 BitmapData 객체는 슬라이드 프로젝터를 통해 나타낼 수 있는 실제 사진 슬라이드와 비슷합니다. 필터는 BitmapData 객체에 직접 적용할 수 있는데 이는 이미지를 변경하기 위해 사진 슬라이드에 직접 그림을 그리는 것과 비슷합니다. 필터를 Bitmap 인스턴스 등의 모든 표시 객체에 적용할 수도 있는데 이는 원래 슬라이드를 전혀 변경하지 않고 화면에 보여 주는 출력을 왜곡하기 위해 슬라이드 프로젝터의 렌즈 앞에 필터를 두는 것과 같습니다. 원시 비트맵 데이터는 Bitmap 인스턴스의 bitmapData 속성을 통해 액세스할 수 있으므로 필터를 원시 비트맵 데이터에 직접 적용했을 수도 있습니다. 그러나 이 경우 필터를 비트맵 데이터에 적용하지 않고 Bitmap 표시 객체에 적용하는 것이 더 좋습니다. ActionScript의 위치 변경 맵 필터 사용에 대한 자세한 내용은 표시 객체 필터링을 참조하십시오. 픽셀 값을 설정하여 비트맵 이미지 만들기위치 변경 맵 필터의 한 가지 중요한 점은 실제로는 두 개의 이미지가 사용된다는 것입니다. 한 이미지는 소스 이미지로 필터에 의해 실제로 변경되는 이미지입니다. 이 샘플에서 소스 이미지는 sphere라는 Bitmap 인스턴스입니다. 필터에서 사용하는 다른 이미지는 맵 이미지라고 합니다. 맵 이미지는 실제로 화면에는 표시되지 않습니다. 그 대신 각 픽셀의 색상은 위치 변경 함수의 입력으로 사용됩니다. 즉, 맵 이미지에서 특정 x, y 좌표에 있는 픽셀의 색상은 위치 변경(물리적 위치 이동)이 소스 이미지에서 해당 x, y 좌표에 있는 픽셀에 얼마나 적용되는지 결정합니다. 결과적으로 구 효과를 만드는 데 위치 변경 맵 필터를 사용하려면 샘플에 적절한 맵 이미지가 있어야 합니다. 이 이미지에는 다음과 같이 회색 배경과 어두운 색에서 밝은 색으로 가로로 나타나는 단색(빨강)의 그래디언트로 채워진 원이 있습니다. 이 샘플에서는 단 한 개의 맵 이미지와 필터를 사용하므로 맵 이미지는 외부 이미지의 로드가 완료될 때 imageLoadComplete() 메서드에서 한 번만 만들어집니다. fisheyeLens라는 맵 이미지는 MoonSphere 클래스의 createFisheyeMap() 메서드를 호출하여 만들어집니다. var fisheyeLens:BitmapData = createFisheyeMap(radius); createFisheyeMap() 메서드에서 맵 이미지는 BitmapData 클래스의 setPixel() 메서드를 사용하여 실제로 한 번에 한 픽셀씩 그려진 것입니다. createFisheyeMap() 메서드의 전체 코드는 다음과 같습니다. 그 다음에는 이 코드의 작동에 대해 단계별로 설명합니다. private function createFisheyeMap(radius:int):BitmapData { var diameter:int = 2 * radius; var result:BitmapData = new BitmapData(diameter, diameter, false, 0x808080); // Loop through the pixels in the image one by one for (var i:int = 0; i < diameter; i++) { for (var j:int = 0; j < diameter; j++) { // Calculate the x and y distances of this pixel from // the center of the circle (as a percentage of the radius). var pctX:Number = (i - radius) / radius; var pctY:Number = (j - radius) / radius; // Calculate the linear distance of this pixel from // the center of the circle (as a percentage of the radius). var pctDistance:Number = Math.sqrt(pctX * pctX + pctY * pctY); // If the current pixel is inside the circle, // set its color. if (pctDistance < 1) { // Calculate the appropriate color depending on the // distance of this pixel from the center of the circle. var red:int; var green:int; var blue:int; var rgb:uint; red = 128 * (1 + 0.75 * pctX * pctX * pctX / (1 - pctY * pctY)); green = 0; blue = 0; rgb = (red << 16 | green << 8 | blue); // Set the pixel to the calculated color. result.setPixel(i, j, rgb); } } } return result; } 먼저 메서드가 호출되면 만들 원 모양 이미지의 반지름을 나타내는 radius라는 매개 변수를 받습니다. 다음으로 코드는 원이 그려질 BitmapData 객체를 만듭니다. result라는 이름의 이 객체는 결국 메서드의 반환 값으로 다시 전달됩니다. 다음 코드와 같이 result BitmapData 인스턴스는 원지름과 같은 크기의 너비와 높이로 투명하지 않게(세 번째 매개 변수에 false가 설정됨) 만들어지며 0x808080(중간 회색) 색상으로 미리 채워집니다. var result:BitmapData = new BitmapData(diameter, diameter, false, 0x808080); 다음으로 코드는 두 개의 루프를 사용하여 이미지의 각 픽셀을 반복 실행합니다. 바깥쪽 루프가 이미지의 각 열을 왼쪽에서 오른쪽으로 이동하는(i 변수를 사용하여 현재 처리 중인 픽셀의 가로 위치 표시) 반면 안쪽 루프는 현재 열의 각 픽셀을 맨 위쪽에서 아래쪽으로(j 변수를 사용하여 현재 픽셀의 세로 위치 표시) 이동합니다. 이 루프의 코드는 다음과 같습니다(안쪽 루프의 내용 생략). for (var i:int = 0; i < diameter; i++) { for (var j:int = 0; j < diameter; j++) { ... } } 루프가 픽셀을 하나씩 차례로 이동하면 각 픽셀에서 값(맵 이미지에서 해당 픽셀의 색상 값)이 계산됩니다. 이 프로세스는 4단계로 이루어집니다.
|
|