Flex 4는 Flex 3를 완전히 갈아 엎었다는 느낌이 든다. 기존 컴포넌트에서 없어진 것도 있고 새로 추가 된 것도 상당히 많다. 익숙해지려면 적지 않은 시간투자가 필요할 것 같다.

 

CSS에 네임스페이스 추가

 

Flex 4부터 CSS에 네임스페이스가 추가가 되었다. 이러한 구분이 필요한 것은 컴포넌트가 기존 halo 컴포넌트 외에 Spark 컴포넌트가 새로 추가가 되면서 필요해진 것이다. 즉 두개의 컴퍼넌트가 다른 네임스페이스를 사용하고 있기 때문에 CSS도 다른 네임스페이스를 쓰게 하는 것이다. 다음 소스를 보면 이해가 쉽겠다.

 

<?xml version="1.0" encoding="utf-8"?>
<s:Application 
	xmlns:fx="http://ns.adobe.com/mxml/2009" 
	xmlns:s="library://ns.adobe.com/flex/spark" 
	xmlns:mx="library://ns.adobe.com/flex/halo"
	minWidth="1024" minHeight="768">
    <mx:DateChooser id="main_calendar" x="20" y="20"/>
    <s:Button label="submit" x="220" y="20"/>
	<fx:Style>
	 
	    @namespace s "library://ns.adobe.com/flex/spark";
	    @namespace mx "library://ns.adobe.com/flex/halo";
	 
	    s|Button {
	        color: #FF0000;
	    }
	 
	    mx|DateChooser {
	        color: #0000FF;
	    }
	 
	</fx:Style>    
</s:Application>

 

 

<fx:Style>내에 @namespace를 정의하고 네임스페이스명|컴포넌트 의 형태로 CSS를 정의할 수 있도록 되었다. 그다지 어려운 사실은 아니다. ^^

 

이런 구조로 만들어진데에는 Flex 4가 Flash Catalyst와 연동이 되면서 디자이너와 개발자의 협업에 충실해지기 위한 거라고 한다. 예를 들어 Slider의 경우 liveDragging속성이 CSS쪽에서는 제어할 수 없던 반면 이번 Flex 4부터는 할 수 있게 되어 개발자는 코드상에서 디자이너는 CSS쪽에서 접근이 가능해져 협업이 되는 것이다. 디자이너의 경우 CSS를 직접 건드리지 않고 Flash Catalyst를 통해서 작업하기 때문에 CSS에 대해서는 몰라도 되지만 그 결과물은 CSS에 속성이 정의 되어 있음을 확인할 수 있을 것이다. 이러한 점이 개발자와 디자이너간에 협업을 가능하게 한 것이라고 생각한다.

 

 

고급 CSS (Advanced CSS)

 

Flex 3의 CSS에서 Selector는 단지 2개 였다. Class SelectorsType Selectors가 그것이다. 하지만 Flex 4부터는 이 Selector 종류가 크게 늘어났다. 이는 더욱 세밀하게 CSS 작업을 할 수 있다는 것을 의미한다. 이제 거의 HTML CSS와 맞먹을 정도인 것 같다. ^^ (CSS는 개인적으로 Flex의 강점중에 하나라고 생각한다. 기존 Flex 3 만으로도 강력한 CSS기능은 나로 하여금 Flex 매력에 푹 빠지게 했다.)

 

참고로 Class Selector와 Type Selector는 다음과 같다.

 

Type Selector

 

같은 종류의 컴포넌트에 적용된다. 다음과 같은 경우 spark.components.Button 및 이를 확장한 컴포넌트에 적용된다.

 

  s|Button { color: #CC0000; }

 

Class Selector

 

컴포넌트의 styleName을 지정한 컴포넌트라면 종류에 상관없이 적용이 된다.

 

  .header { background-color: #CCCCCC; }

 

 

아래부터는 Flex 4에서 추가된 CSS selector에 대해 예제와 함께 살펴본다.

 

1. ID Selectors

예전부터 컴포넌트 ID별로 CSS를 줄 수 있으면 좋겠다는 생각을 가졌다. Flex 4에 부터 적용되니 기쁘다. ID별로 CSS를 적용할 수 있기 때문에 같은 계열의 컴포넌트라도 하나의 객체로 생성된 유일한 1개의 컴포넌트에만 CSS를 적용할 수 있게 된다.

<?xml version="1.0" encoding="utf-8"?>
<s:Application 
	xmlns:fx="http://ns.adobe.com/mxml/2009" 
	xmlns:s="library://ns.adobe.com/flex/spark" 
	xmlns:mx="library://ns.adobe.com/flex/halo"
	minWidth="1024" minHeight="768">
    <s:Button id="btnTest" x="41" y="24" label="ID 적용됨"/>
    <s:Button x="128" y="24" label="ID 적용 안됨" />
	<fx:Style>
		#btnTest{
			color: #0000ff;
		} 
	</fx:Style>    
</s:Application>

 

ID가 btnTest로 지정된 것만 Label색이 파란색으로 지정된다. ID를 이용하기 때문에 네임스페이스 구분이 필요없다. 이제 컴포넌트 끼리 CSS 사생활 침해를 금지 시킬 수 있겠구나~ (농담)

 

 

2. Multiple class selectors

 

이는 Class selector가 확장된 개념이다. Class Selector는 중첩해서 사용할 수 있다. 아래 예제를 보면 쉽게 이해할 수 있을 것이다.

<?xml version="1.0" encoding="utf-8"?>
<s:Application 
	xmlns:fx="http://ns.adobe.com/mxml/2009" 
	xmlns:s="library://ns.adobe.com/flex/spark" 
	xmlns:mx="library://ns.adobe.com/flex/halo"
	minWidth="1024" minHeight="768">
	<mx:Label styleName="class1 class2" text="중첩 Class Selector 사용" x="8" y="40"/>
	<fx:Style>
		.class1 
		{
			color : #ff0000;
		}
		
		.class2
		{
			fontFamily : "궁서";
		}
	</fx:Style>    
</s:Application>

위 예제에서 Label에 styleName을 설정할 때 class selector를 2개 썼다. class1, class2는 각각 폰트색과 종류를 설정했는데 이 2개의 selector를 사용한 Label은 두개의 class selector에 정의가 적용된다. 결국 Label의 글자색은 빨강, 폰트는 궁서로 보이게 된다.

 

이렇게 class selector를 중첩이 가능하게 됨에 따라 중복되는 selector가 없도록 깔끔하게 CSS구성을 할 수 있게 되었다.

 

 

3. Descendant Selectors

이 selector는 자식/부모 관계에 있는 컴포넌트에 해당한다. Panel위에 Button을 놓았다고 하자. 이때 관계에서 Panel이 부모, Button은 자식이 된다. 특정 부모는 자식에게 어떤 것을 바랄 수 있게 된다. 그래서 사과를 줄수도 있고 바나나도 줄 수 있다. 여기에 특정 부모라는 말이 중요하다. 아무 부모나 자식에게 그렇게 주는 것을 금지한다. 이 관계를 이용한 것이 바로 Descendant selector이다.

 

<?xml version="1.0" encoding="utf-8"?>
<s:Application 
	xmlns:fx="http://ns.adobe.com/mxml/2009" 
	xmlns:s="library://ns.adobe.com/flex/spark" 
	xmlns:mx="library://ns.adobe.com/flex/halo"
	minWidth="1024" minHeight="768">
    <s:Panel x="14" y="42" width="250" height="200">
        <s:Button x="12" y="12" label="Button"/>
    </s:Panel>
    <s:Group x="33" y="393" width="200" height="200">
        <s:Button x="33" y="29" label="Button"/>
    </s:Group>
	<fx:Style>
	    @namespace s "library://ns.adobe.com/flex/spark";
	    @namespace mx "library://ns.adobe.com/flex/halo";
		s|Panel s|Button{
			color: #304F6B;
			baseColor: #5387B7;
		} 
	</fx:Style>    
</s:Application>

 

위 코드에서 볼 수 있듯이 Panel에 자식 Button만 CSS를 적용하도록 되어 있기 때문에 Group위에 올라간 Button에는 CSS적용이 안된다.

 

 

4. Pseudo selectors

 

Flex 4에서 상태변화에도 CSS를 적용할 수 있게 되었다. Flex 4에서는 Skin을 ProgrammaticSkin 기반이 아닌 SparkSkin기반으로 MXML형태로 제작할 수 있도록 되어  있다. flex4.swc에 spark.skins.*쪽을 훑어보기 바란다. Button의 경우 ButtonSkin.mxml이 존재하는데 아래와 같이 상태별로 다른 스킨을 줄 수 있도록 되어 있다.

<s:states>
	<s:State name="up" />
	<s:State name="over" />
	<s:State name="down" />
	<s:State name="disabled" />
</s:states>

 

이 상태에 따라서 CSS를 적용할 수 있도록 한것이 바로 Pseudo selector이다. 아래 예제에서 볼 수 있듯이 :를 이용해 컴포넌트의 상태에 따라 CSS를 주는 것을 확인할 수 있다.

<?xml version="1.0" encoding="utf-8"?>
<s:Application 
	xmlns:fx="http://ns.adobe.com/mxml/2009" 
	xmlns:s="library://ns.adobe.com/flex/spark" 
	xmlns:mx="library://ns.adobe.com/flex/halo"
	minWidth="1024" minHeight="768">
	<s:Button label="Test"/>
	<fx:Style>
	    @namespace s "library://ns.adobe.com/flex/spark";
	    @namespace mx "library://ns.adobe.com/flex/halo";
	 
		s|Button:up{
			color: #304F6B;
			baseColor: #5387B7;
		} 
		
		s|Button:down 
		{
			baseColor: #917541;
			color: #B8A553;
		}
		
		s|Button:over 
		{
			baseColor: #B8A553;
			color: #6B5630;
		}
	</fx:Style>    
</s:Application>

 

 

CSS Selector 사용하기

 

위에서 설명한 Selector 들은 독립적으로 사용하는 것외에도 다른 Selector와 함께 사용할 수 있다.

 

Flex 3에서는 아래와 같이 단순한 Type Selector와 범용적으로 사용되는 Class Selector만 사용할 수 있었다.

 

Button { color:#00FF00; }     /* Simple type selector */
.special { color:#FF0000; }     /* Universal class selector */

하지만 Flex 4부터는 새로운 Selector와 함께 섞어서 사용할 수 있게 되었다. 아래 CSS 적용 예시를 보자.

 

Button { color:#00FF00; }     /* 단순 type selector */
.special { color:#FF0000; }     /* 범용 class selector */
Button.special { color:#FF0000; }     /* class selector와 함께 사용된 Type selector  */
Button#b13 { color:#0000FF; }     /* ID selector와 함께 사용된 Type selector */
#b13 { color:#FF9933; }     /* 범용 id selector */
Panel VBox Button { color:#990000; }     /* Descendant selector */
Button:up { color:#FF9900; }     /* pseudo selector와 함께 사용된 Type selector  */
:up { color:#FF9933; }     /* 범용 pseudo selector */

 

위 내용을 좀더 새부적으로 이해하기 위해 예를 하나 들어보겠다.

 

Panel Button { color:#DD0000; }

 

위처럼 CSS Selector를 중첩한 경우 경우 다음과 같은 상황에 있는 컴포넌트에 color가 설정된다.

 

- Panel 위에 Button

- Panel을 확장한 컴포넌트 위에 Button

- Panel또는 Panel을 확장한 컴포넌트 위에 Button을 확장한 CheckBox와 같은 커스텀 Button

 

 

정리하며

 

Flex 4 CSS의 Selector를 외우기 쉽게 하기 위해 아래를 꼭 기억하자.

  • local name : type selector - 컴포넌트 종류
  • styleName : class selector - 컴포넌트 스타일명
  • identity : id selector - 컴포넌트 ID
  • state : pseudo selector - 컴포넌트 상태
  • display list : descendant selector - 컴포넌트 디스플레이 리스트 부모/자식 관계

 

참고글

 

글쓴이 : 지돌스타(http://blog.jidolstar.com/548)

토성(Saturn)

 

Papervision3D나 Away3D 라이브러리를 이용하지 않고 Flash Player 10 3D API만을 이용해 태양계의 행성인 토성을 그려보았다. 토성을 그리기 위해 먼저 표면부분과 고리부분의 Texture를 구해야한다. 아래 사이트에서 다양한 행성의 Texture 그림을 구할 수 있다.

 

http://planetpixelemporium.com/saturn.html

 

토성의 고리 Texture

토성의 표면 Texture

 

Texture를 이용해 3D로 만들기 위한 아이디어는 아래 링크를 참고한다.

 

[Flash 3D]우리 아가 회전시키기 2-Texture를 이용해 여러모양 만들기

[Flash 3D]우리 아가 회전시키기-UV맵핑,원근투영 이용

 

 

토성 그리기 : 표면과 고리가 겹치는 문제

 

지구와 같은 행성과 다르게 토성의 경우에는 고리가 있다. 토성의 표면은 그냥 구형태로 Mesh데이타를 만들어 처리하면 된다. 하지만 고리의 경우는 다른 형태로 Mesh데이타를 만들어야 한다. 설명은 생략하겠다.

 

아래는 아래 예시의 소스이다.

invalid-file

고리와 구가 겹치는 문제가 있는 소스

 

토성을 모양을 만들기 위해 토성의 표면과 고리에 대한 Mesh데이타를 만들어야 한다. 아래코드는 고리(Ring) 부분의 Mesh데이타를 만드는 부분이다. 표면부분과 다르게 고리 Texture를 반복해서 그리는 데이타를 만든다.

/**
 * 고리 Mesh 데이터 작성 
 * @param radius 고리 안쪽 반지름  
 * @param thickness 고리 두께
 * @param div 고리 조각수 
 * @return mesh 데이터 
 */
function createRingMesh( radius:Number, thickness:Number, div:uint ):GraphicsTrianglePath
{
    var vertices:Vector. = new Vector.( 0, false );
    var indices:Vector. = new Vector.( 0, false );
    var uvtData:Vector. = new Vector.( 0, false );
    var mesh:GraphicsTrianglePath = new GraphicsTrianglePath( vertices, indices, uvtData, TriangleCulling.NONE );
    var s:Number = 0;
    var x1:Number = radius;
    var x2:Number = radius + thickness;
    var y1:Number = 0;
    var y2:Number = 0;
    var z:Number = 0;          
    var cos:Number;
    var sin:Number;    
    var n:Number = 0;
    
    for( var i:uint = 0; i < div; i++ )
    {
        //Vertices
        mesh.vertices.push( 
            x1, y1, z, 
            x2, y2, z 
        );
        s = Math.PI * 2 * (i + 1) / div;
        cos = Math.cos( s );
        sin = Math.sin( s );
        x1 = radius * cos;
        x2 = (radius + thickness) * cos;
        y1 = radius * sin;
        y2 = (radius + thickness) * sin;
        mesh.vertices.push( 
            x1, y1, z, 
            x2, y2, z 
        );
        //UVT
        mesh.uvtData.push( 
            0,1,1, 
            1,1,1, 
            0,0,1, 
            1,0,1 
        );
        
        //Indices
        n = i * 4;
        mesh.indices.push( n, n+1, n+2, n+2, n+1, n+3 );             
    }    
    
    return mesh;
}

 

Enterfame시에 렌더링을 실시하는데 Utils3D.projectVectors(), graphics.beginBitmapFill(), graphics.drawTriangles() 메소드를 2번씩 호출한다. 즉, 토성의 구와 고리를 그리기 위해 투영, 렌더링을 2번씩 하는 것이다.

private function onEnterFrame( event:Event ):void
{
    world.identity(); //단위행렬로 전환 
    world.appendRotation( getTimer() * 0.0027, Vector3D.Z_AXIS );
    world.appendRotation( xRotation, Vector3D.X_AXIS );
    world.appendRotation( yRotation, Vector3D.Y_AXIS );
    world.appendTranslation(0, 0, viewPortZAxis); //이동 
    world.append(projection.toMatrix3D()); //투영 변환 적용 
    
    // mesh 데이터를  투영하여  projected 생성 
    // uvtData도 갱신된다. 갱신되는 데이터는 T값이다. 
    Utils3D.projectVectors( world, saturnFaceMesh.vertices, saturnFaceProjected, saturnFaceMesh.uvtData );
    Utils3D.projectVectors( world, saturnRingMesh.vertices, saturnRingProjected, saturnRingMesh.uvtData );    
    
    viewport.graphics.clear();
    // Triangle 라인을 그림 
    if( visibleTriangle )
    {
        viewport.graphics.lineStyle( 1, 0xff0000, 0.1 );
    }
    
    //Texture 입힌다.
    viewport.graphics.beginBitmapFill( saturnFaceTexture, null, false, true );
    viewport.graphics.drawTriangles( saturnFaceProjected, getSortedIndices(saturnFaceMesh), saturnFaceMesh.uvtData, saturnFaceMesh.culling );                
    viewport.graphics.beginBitmapFill( saturnRingTexture, null, false, true );
    viewport.graphics.drawTriangles( saturnRingProjected, getSortedIndices(saturnRingMesh), saturnRingMesh.uvtData, saturnRingMesh.culling );                
}

 

언뜻보면 이 코드는 문제가 없어보이지만 실제로 실행해보면 바로 문제가 있다는 것을 확인할 수 있다.

아래는 이 프로그램을 실행한 것이다.

 

 

마우스로 돌려보고 화살표키로 확대/축소도 할 수 있다. 또한 Space키 누르면 삼각형 부분이 보인다.

 

실행한 결과를 보면 문제가 있다. 토성 표면 뒤에 고리가 보인다. 당연히 저렇게 그려지면 안된다. 이 문제는 위의 방법처럼 Texture와 Mesh데이타가 2개여서는 방법이 나오지 않는다. 왜냐하면 graphics.drawTriangles() 함수를 2번 그려주게 되면 먼저 그린 것은 항상 나중에 그린것에 의해 가려지기 때문이다.

 

 

토성 그리기 : 고리 겹치지 않게 만들기

 

invalid-file

표면과 고리가 겹치지 않는 소스

 

위처럼 고리가 토성표면에 겹치는 문제를 해결하기 위한 방법은 Texture를 하나로 통합하고 Mesh데이타도 하나로 만든다. 그리고 graphics.drawTriangles() 를 한번만 호출하도록 하면 된다.

 

아래 함수는 고리, 표면 Texture를 1개의 텍스쳐로 만들어준다.

/**
 * 텍스쳐 생성 
 * @param faceTexture 표면의 BitmapData
 * @param ringTexture 고리의 BitmapData 
 * @return TexturInfo의 객체 
 */ 
function createTexture( faceTexture:BitmapData, ringTexture:BitmapData ):TextureInfo
{
	var texture:BitmapData;

	texture = new BitmapData( 
			Math.max( faceTexture.width, ringTexture.width ), 
			faceTexture.height + ringTexture.height, 
			true,
			0xff000000 
	);

	faceTexture.lock();
	ringTexture.lock();

	var facePixels:ByteArray ;
	var ringPixels:ByteArray;
	facePixels = faceTexture.getPixels( new Rectangle( 0, 0, faceTexture.width, faceTexture.height ) );
	ringPixels = ringTexture.getPixels( new Rectangle( 0, 0, ringTexture.width, ringTexture.height ) );
	facePixels.position = 0;
	ringPixels.position = 0;

	texture.setPixels( 
		new Rectangle( 0, 0, faceTexture.width, faceTexture.height ), 
		facePixels 
	);
	texture.setPixels( 
		new Rectangle( 0, faceTexture.height, ringTexture.width, ringTexture.height ), 
		ringPixels 
	);
	
	var faceRect:Rectangle = new Rectangle( 0, 0, faceTexture.width, faceTexture.height );
	var ringRect:Rectangle = new Rectangle( 0, faceTexture.height+1, ringTexture.width, ringTexture.height );
	
	faceTexture.unlock();
	ringTexture.unlock();
	
	return new TextureInfo( texture, faceRect, ringRect ); 
}

 

결국 아래 그림처럼 Texture BitmapData가 만들어진다.

 

위 함수에 TextureInfo는 실제 texture Bitmap정보와 Texture상의 표면과 고리 부분에 대한 영역정보를 가지고 있다. 이 영역정보를 가지고 Mesh 데이타를 만들도록 한다.

 

/**
 * 토성모양의 Mesh 데이타 
 * @param textureInfo 텍스쳐 정보 
 * @return Mesh 데이타 
 */ 
function createMesh( textureInfo:TextureInfo ):GraphicsTrianglePath
{
	var width:Number = textureInfo.texture.width;
	var height:Number =textureInfo.texture.height;
	var faceRect:Rectangle = textureInfo.faceRect;
	var ringRect:Rectangle = textureInfo.ringRect;

	var minU:Number;
	var maxU:Number;
	var minV:Number;
	var maxV:Number;
	
	//표면 Mesh 데이타 만들기 
	minU = faceRect.x / width;
	maxU = (faceRect.x + faceRect.width) / width;
	minV = faceRect.y / height;
	maxV = (faceRect.y + faceRect.height) / height;
	var faceMesh:GraphicsTrianglePath = createSphereMesh( 50, 24, 24, minU, maxU, minV, maxV );
	
	//고리 Mesh 데이타 만들기 
	minU = ringRect.x / width;
	maxU = (ringRect.x + ringRect.width) / width;
	minV = ringRect.y / height;
	maxV = (ringRect.y + ringRect.height) / height;
	var ringMesh:GraphicsTrianglePath = createRingMesh( 70, 20, 50, minU, maxU, minV, maxV );

	//고리 Mesh 데이타에서 Index 부분 조정 
	var deltaIndex:uint = faceMesh.vertices.length/3; //Vertex는 x,y,z 3개씩 묶이므로... ^^
	var length:uint = ringMesh.indices.length;
	for( var i:int = 0; i < length; i++ )
	{
		//아래와 같이 2개의 mesh가 합쳐지면 뒤에 붙는  ring부분의 index값이 변경되야한다.
		ringMesh.indices[i] += deltaIndex; 
	}
	
	//최종 Mesh 데이타 완성 
	var mesh:GraphicsTrianglePath = new GraphicsTrianglePath();
	mesh.vertices = faceMesh.vertices.concat( ringMesh.vertices );
	mesh.uvtData = faceMesh.uvtData.concat( ringMesh.uvtData );
	mesh.indices = faceMesh.indices.concat( ringMesh.indices );
	mesh.culling = TriangleCulling.NONE;
	return mesh;	
}

 

위 함수는 2개의 Mesh데이터를 이용해 1개의 Mesh데이타로 통합해준다. 이로써 이제는 투영, 렌더링을 한번만 하도록 한다.

 

private function onEnterFrame( event:Event ):void
{
	world.identity(); //단위행렬로 전환 
	world.appendRotation( getTimer() * 0.0027, Vector3D.Z_AXIS );
	world.appendRotation( xRotation, Vector3D.X_AXIS );
	world.appendRotation( yRotation, Vector3D.Y_AXIS );
	world.appendTranslation(0, 0, viewPortZAxis); //이동 
	world.append(projection.toMatrix3D()); //투영 변환 적용 
	
	// mesh 데이터를  투영하여  projected 생성 
	// uvtData도 갱신된다. 갱신되는 데이터는 T값이다. 
	Utils3D.projectVectors( world, mesh.vertices, projected, mesh.uvtData );
	
	viewport.graphics.clear();

	// Triangle 라인을 그림 
	if( visibleTriangle )
	{
		viewport.graphics.lineStyle( 1, 0xff0000, 0.1 );
	}
	
	//Texture 입힌다.
	viewport.graphics.beginBitmapFill( textureInfo.texture, null, false, true );
	viewport.graphics.drawTriangles( projected, getSortedIndices(mesh), mesh.uvtData, mesh.culling );            	

}

 

위 함수처럼 1번만 투영/렌더링 처리하면 이제 토성의 표면과 고리는 아래처럼 자연스럽게 나오게 된다.

 

 

하지만 완벽하지 않다. 이유는 렌더링시 Culling을 None으로 지정했기 때문이다. 이는 보이지 않는 표면까지 그린다는 의미이다. 고리의 경우 양쪽면이 다보여야한다. 그러므로 고리의 Mesh데이타는 None으로 지정한다. 하지만 토성 표면의 경우는 화면 앞쪽을 향하는 부분만 보이면 되므로 Culling을 Positive로 지정하면 된다. 첫번째 예제에서는 어짜피 Mesh 데이터가 2개이므로 상관없었지만 두번째의 경우에는 양면 다보여야하는 고리때문에 토성의 표면도 None처리 했다. 이는 보이지 않는 부분까지 렌더링하므로 그만큼 속도저하가 일어난다. 이를 해결하기 위해 Positive로 지정하되 고리를 앞뒤면으로 겹쳐서 그리면 된다. 하지만 이 방법밖에 없는지 의문이다.

 

 

정리하기

 

이런 작업은 신나는 일이다. ^^

 

이 토성에 Light효과를 주어 Shading 예제도 만들어봐야 겠다.

 

참고로 모든 작업은 Flash Builder 4에서 했으며 Flex 4 SDK를 이용했다. 결과물은 Flash Player 10이상 설치된 브라우져에서만 확인할 수 있다.

 

위 코드는 아래 링크에서도 볼 수 있다.

 

Saturn 3D, 토성, 고리 중첩 해결못한 것

Saturn 3D, 토성, 고리 중첩 해결한 것

 

참고사이트

[Flash 3D]우리 아가 회전시키기 2-Texture를 이용해 여러모양 만들기

[Flash 3D]우리 아가 회전시키기-UV맵핑,원근투영 이용

 

글쓴이 : 지돌스타(http://blog.jidolstar.com/547)

 

Flex 3에서 DataGrid의 Header부분의 Separator는 headerSeparatorSkin 스타일로 정의되어 있다. 화면은 Flex DataGrid에 Separator가 붙은 보통의 모습이다.

 

 

위 프로그램은 아래와 같이 프로그래밍 한다.

<?xml version="1.0" encoding="utf-8"?>
<!-- http://blog.flexexamples.com/2009/03/20/removing-the-header-separator-on-the-datagrid-control-in-flex/ -->
<mx:Application
	name="DataGrid_headerSeparatorSkin_test"
	xmlns:mx="http://www.adobe.com/2006/mxml"
	backgroundColor="white" 
	layout="vertical">
	<mx:DataGrid id="dataGrid">
		<mx:dataProvider>
			<mx:ArrayCollection>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
			</mx:ArrayCollection>
		</mx:dataProvider>
	</mx:DataGrid>
</mx:Application>

만약 Separator를 지우고 싶다면 단순히 headerSeparatorSkin 에 mx.skins.ProgrammaticSkin으로 정의하면 된다. 아래는 실행화면과 소스코드이다. mx.skins.ProgrammaticSkin는 프로그램적으로 스킨을 만들 필요가 있을때 사용하는 클래스로 이 클래스 내부에서는 어떤 렌더링도 하지 않기 때문에 headerSeparatorSkin 을 이 클래스로 대체하는 것만으로도 Header의 separator를 삭제할 수 있는 것이다.

 

 

 

<?xml version="1.0" encoding="utf-8"?>
<!-- http://blog.flexexamples.com/2009/03/20/removing-the-header-separator-on-the-datagrid-control-in-flex/ -->
<mx:Application
	name="DataGrid_headerSeparatorSkin_test"
	xmlns:mx="http://www.adobe.com/2006/mxml"
	backgroundColor="white" 
	layout="vertical">
	<mx:DataGrid id="dataGrid" 
		headerSeparatorSkin="mx.skins.ProgrammaticSkin">
		<mx:dataProvider>
			<mx:ArrayCollection>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
			</mx:ArrayCollection>
		</mx:dataProvider>
	</mx:DataGrid>
</mx:Application>

 

위 코드 대신 아래 코드처럼 <Style/> 블록이나 외부 .CSS 파일을 이용해서 headerSeparatorSkin의 스타일을 바꿀 수 있다.

 

 

<?xml version="1.0" encoding="utf-8"?>
<!-- http://blog.flexexamples.com/2009/03/20/removing-the-header-separator-on-the-datagrid-control-in-flex/ -->
<mx:Application
	name="DataGrid_headerSeparatorSkin_test"
	xmlns:mx="http://www.adobe.com/2006/mxml"
	backgroundColor="white" 
	layout="vertical">
    <mx:Style>
        DataGrid {
            headerSeparatorSkin: ClassReference("mx.skins.ProgrammaticSkin");
        }
    </mx:Style>	
	<mx:DataGrid id="dataGrid">
		<mx:dataProvider>
			<mx:ArrayCollection>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
			</mx:ArrayCollection>
		</mx:dataProvider>
	</mx:DataGrid>
</mx:Application>

 

또는 ActionScript 를 사용해 버튼을 누를때 동적으로 headerSeparatorSkin 스타일을 변경할 수 있도록 다음 코드처럼 만들 수 있다.

<?xml version="1.0" encoding="utf-8"?>
<!-- http://blog.flexexamples.com/2009/03/20/removing-the-header-separator-on-the-datagrid-control-in-flex/ -->
<mx:Application
	name="DataGrid_headerSeparatorSkin_test"
	xmlns:mx="http://www.adobe.com/2006/mxml"
	backgroundColor="white" 
	layout="vertical">
    <mx:Script>
        <![CDATA[
            import mx.skins.ProgrammaticSkin;

            private function btn_click(evt:MouseEvent):void {
                dataGrid.setStyle("headerSeparatorSkin", ProgrammaticSkin);
            }
        ]]>
    </mx:Script>

    <mx:Button id="btn"
            label="Set header separator skin"
            click="btn_click(event);" />
            
	<mx:DataGrid id="dataGrid">
		<mx:dataProvider>
			<mx:ArrayCollection>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
			</mx:ArrayCollection>
		</mx:dataProvider>
	</mx:DataGrid>
</mx:Application>

 

좀더 파헤쳐보자.

 

DataGrid에는 drawSeparators() 메소드가 protected로 지정되어 있다. 만약 DataGrid를 커스터마이징해서 Separator 그리는 방식을 바꾸고 싶다면 drawSeparators() 부터 찾아가면 된다. 아래 코드는 DataGrid의 drawSeparators() 메소드 정의이다

/**
 *  Creates and displays the column header separators that the user 
 *  normally uses to resize columns.  This implementation uses
 *  the same Sprite as the lines and column backgrounds and adds
 *  instances of the headerSeparatorSkin and attaches mouse
 *  listeners to them in order to know when the user wants
 *  to resize a column.
 */
protected function drawSeparators():void
{
    DataGridHeader(header)._drawSeparators();
    if (lockedColumnHeader)
        DataGridHeader(lockedColumnHeader)._drawSeparators();
}

 

위 코드에서 볼 수 있듯이 결국 DataGridHeader에 정의된 _drawSeparators()쪽을 살펴봐야한다. 아래는 DataGridHeader의 _drawSeparators()와 drawSeparators() 부분이다.

mx_internal function _drawSeparators():void
{
    drawSeparators();
}

/**
 *  Creates and displays the column header separators that the user 
 *  normally uses to resize columns.  This implementation uses
 *  the same Sprite as the lines and column backgrounds and adds
 *  instances of the headerSeparatorSkin and attaches mouse
 *  listeners to them in order to know when the user wants
 *  to resize a column.
 */
protected function drawSeparators():void
{
    if (!separators)
        separators = [];

    var lines:UIComponent = UIComponent(getChildByName("lines"));
    
    if (!lines)
    {
        lines = new UIComponent();
        lines.name = "lines";
        addChild(lines); 
    }
    else
        setChildIndex(lines, numChildren - 1);

    // required to deal with some 2.x clipping behavior
    lines.scrollRect = new Rectangle(0, 0, unscaledWidth, unscaledHeight + 1);

    if (headerSepSkinChanged)
    {
        headerSepSkinChanged = false;
        clearSeparators();
    }

    var n:int = visibleColumns ? visibleColumns.length : 0;
    
    if (!needRightSeparator && n > 0)
    	n--;
    
    for (var i:int = 0; i < n; i++)
    {
        var sep:UIComponent;
        var sepSkin:IFlexDisplayObject;
        
        if (i < lines.numChildren)
        {
            sep = UIComponent(lines.getChildAt(i));
            sepSkin = IFlexDisplayObject(sep.getChildAt(0));
        }
        else
        {
            var headerSeparatorClass:Class =
                getStyle("headerSeparatorSkin");
            sepSkin = new headerSeparatorClass();
            if (sepSkin is ISimpleStyleClient)
                ISimpleStyleClient(sepSkin).styleName = this;
            sep = new UIComponent();
            sep.addChild(DisplayObject(sepSkin));
            lines.addChild(sep);
            
            separators.push(sep);
        }
        // if not separator
        if ( !(i == visibleColumns.length-1 && !needRightSeparatorEvents) )
        {
            DisplayObject(sep).addEventListener(
                MouseEvent.MOUSE_OVER, columnResizeMouseOverHandler);
            DisplayObject(sep).addEventListener(
                MouseEvent.MOUSE_OUT, columnResizeMouseOutHandler);
            DisplayObject(sep).addEventListener(
                MouseEvent.MOUSE_DOWN, columnResizeMouseDownHandler);
        }
		else
		{
            // if not separator
            if ( (i == visibleColumns.length-1 && !needRightSeparatorEvents) )
            {
                DisplayObject(sep).removeEventListener(
                    MouseEvent.MOUSE_OVER, columnResizeMouseOverHandler);
                DisplayObject(sep).removeEventListener(
                    MouseEvent.MOUSE_OUT, columnResizeMouseOutHandler);
                DisplayObject(sep).removeEventListener(
                    MouseEvent.MOUSE_DOWN, columnResizeMouseDownHandler);
            }
		}

        var cols:Array = visibleColumns;
        if (!(cols && cols.length > 0 || dataGrid.headerVisible))
        {
            sep.visible = false;
            continue;
        }

        sep.visible = true;
        sep.x = headerItems[i].x +
                visibleColumns[i].width - Math.round(sepSkin.measuredWidth / 2);
        if (i > 0)
        {
            sep.x = Math.max(sep.x,
                             separators[i - 1].x + Math.round(sepSkin.measuredWidth / 2));
        }
        sep.y = 0;
        sepSkin.setActualSize(sepSkin.measuredWidth, Math.ceil(cachedHeaderHeight));
        
        // Draw invisible background for separator affordance
        sep.graphics.clear();
        sep.graphics.beginFill(0xFFFFFF, 0);
        sep.graphics.drawRect(-separatorAffordance, 0,
							  sepSkin.measuredWidth + separatorAffordance,
							  cachedHeaderHeight);
        sep.graphics.endFill();
		sep.mouseEnabled = true;
    }

    while (lines.numChildren > n)
    {
        lines.removeChildAt(lines.numChildren - 1);
        separators.pop();
    }
}

 

DataGridHeader의 drawSeparators()에서 headerSeparatorSkin 스타일을 사용하는 것을 찾아볼 수 있다. 개발자는 이 부분을 커스터마이징할 수 있는 것이다.

 

DataGrid나 DataGridHeader를 커스터마이징할 필요없이 스킨만 변경하고 싶다면 headerBackgroundSkin의 기본 스킨인 mx.skins.halo.DataGridHeaderSeparator를 커스터 마이징하거나 mx.skins.Programmatics를 확장해서 사용하면 되겠다. 아래코드는 DataGrid에서 headerBackgroundSkin이 Style로 정의된 것을 보여주고 있다.

/**
 *  The class to use as the skin that defines the appearance of the  
 *  background of the column headers in a DataGrid control.
 *  @default mx.skins.halo.DataGridHeaderSeparator
 */
[Style(name="headerBackgroundSkin", type="Class", inherit="no")]

 

 

아래 코드는 mx.skins.halo.DataGridHeaderSeparator를 보여준다.

////////////////////////////////////////////////////////////////////////////////
//
//  ADOBE SYSTEMS INCORPORATED
//  Copyright 2005-2007 Adobe Systems Incorporated
//  All Rights Reserved.
//
//  NOTICE: Adobe permits you to use, modify, and distribute this file
//  in accordance with the terms of the license agreement accompanying it.
//
////////////////////////////////////////////////////////////////////////////////

package mx.skins.halo
{

import flash.display.Graphics;
import mx.skins.ProgrammaticSkin;

/**
 *  The skin for the separator between column headers in a DataGrid.
 */
public class DataGridHeaderSeparator extends ProgrammaticSkin
{
	include "../../core/Version.as";

	//--------------------------------------------------------------------------
	//
	//  Constructor
	//
	//--------------------------------------------------------------------------

	/**
	 *  Constructor.
	 */
	public function DataGridHeaderSeparator()
	{
		super();
	}
	
	//--------------------------------------------------------------------------
	//
	//  Overridden properties
	//
	//--------------------------------------------------------------------------

	//----------------------------------
	//  measuredWidth
	//----------------------------------
	
	/**
	 *  @private
	 */
	override public function get measuredWidth():Number
	{
		return 2;
	}
	
	//----------------------------------
	//  measuredHeight
	//----------------------------------

	/**
	 *  @private
	 */
	override public function get measuredHeight():Number
	{
		return 10;
	}
	
	//--------------------------------------------------------------------------
	//
	//  Overridden methods
	//
	//--------------------------------------------------------------------------

	/**
	 *  @private
	 */
	override protected function updateDisplayList(w:Number, h:Number):void
	{
		super.updateDisplayList(w, h);
		var g:Graphics = graphics;
		
		g.clear();
		
		// Highlight
		g.lineStyle(1, 0xFFFFFF, 0.5);
		g.moveTo(0, 0);
		g.lineTo(0, h);
		g.lineStyle(1, getStyle("borderColor")); 
		g.moveTo(1, 0);
		g.lineTo(1, h);
	}

}

}

 

이외로 단순해서 놀랬는가? 결국 이 스킨은 borderColor 스타일로 지정된 색으로 구분자 선만 그어준다. 나는 이것을 바꿔서 실선을 점선을 그리도록 해보겠다. 아래 코드는 이를 구현한 DataGridHeaderDotSeparator 클래스이다.

 

package
{
import flash.display.Graphics;

import mx.skins.halo.DataGridHeaderSeparator;

/**
 * DataGrid Header에 점선을 그린다.
 */ 
public class DataGridHeaderDotSeparator extends DataGridHeaderSeparator
{
	/**
	 *  Constructor.
	 */		
	public function DataGridHeaderDotSeparator()
	{
		super();
	}
	
	/**
	 *  @private
	 */		
	override protected function updateDisplayList(w:Number, h:Number):void
	{
		var g:Graphics = graphics;
		
		g.clear();
		
		
		g.lineStyle(1, getStyle("borderColor"), 1); 
		var i:int;
		for( i = 0; i < h; i+=4 )
		{
			g.drawCircle( 1.5, i, 0.5 );
		}
	}		
}
}

 

 

아래 코드는 DataGridHeaderDotSeparator 스킨을 사용하는 예제이다.

<?xml version="1.0" encoding="utf-8"?>
<!-- http://blog.flexexamples.com/2009/03/20/removing-the-header-separator-on-the-datagrid-control-in-flex/ -->
<mx:Application
	name="DataGrid_headerSeparatorSkin_test"
	xmlns:mx="http://www.adobe.com/2006/mxml"
	backgroundColor="white" 
	layout="vertical">
	<mx:DataGrid id="dataGrid"
		headerSeparatorSkin="DataGridHeaderDotSeparator">
		<mx:dataProvider>
			<mx:ArrayCollection>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
			</mx:ArrayCollection>
		</mx:dataProvider>
	</mx:DataGrid>
</mx:Application>

 

아래는 위 코드를 실행한 화면이다. Header부분에 구분자(Separator)가 점선으로 되었다. 좀 뭉뚝하지만 그냥 예제일 뿐이니깐 넘어가자. ^^;

 

 

Flex는 이처럼 프로그램적으로 스킨을 변경할 수 있다. 물론 이미지를 이용하는 방법도 있다. 이와 같은 방법으로 스킨을 변경하는 것은 Flex의 모든 컴포넌트에서 지원하므로 이런 스킬에 익숙해질 필요가 있겠다.

 

원본 예제 : Removing the header separator on the DataGrid control in Flex

 

주절주절 : 그냥 이렇게 가볍게 포스팅하는 것이 좋을 것 같다는 생각이 든다.

 

글쓴이 : 지돌스타(http://blog.jidolstar.com/546)

 

이전에 [Flash 3D]우리 아가 회전시키기-UV맵핑,원근투영 이용 라는 제목으로 3D Texture 맵핑을 이용하는 기초적인 예제를 설명했다. 이 글은 이를 응용해 실제 3D 모양을 만들어 Texture를 입히는 예제 보여준다.

 

Adobe Flash Player 10이상 부터 3D 관련 API를 제공하고 있다. 그러므로 Papervision3D, Away3D와 같은 기존 3D라이브러리를 이용하지 않아도 충분히 3D 구현이 가능해졌다.

 

이번에도 예진이가 모델이 되어주었다. 3D Texture 모델 이쁜 공쥬~~ 훗~

 

하지만 모델의 운명은 가혹하다는....

 

아래는 예제 프로그램의 캡쳐화면이다.

 

원기둥과 원환체(도너츠 모양)를 Mesh 데이터로 삼았다.

프로그램 상에서 키보드 1을 누르면 원기둥으로 키보드 2를 누르면 원환체로 바뀐다.

 

원기둥 모양

 

원환체 모양

 

화면위에 마우스를 클릭하면 렌더링된 삼각형이 그려진다. 이것으로 Texture가 어떻게 입혀졌는지 한눈에 확인할 수 있다.

 

원기둥 모양

원환체

 

키보드 T를 누르면 Texture가  입혀지지 않고 아래처럼 형태만 보여준다.

 

마우스 휠을 이용하면 z축으로 확대/축소가 가능하다.

 

Flash Player 10 API 만으로도 이런 것을 만들 수 있다는데 어쩌면 감동을 받을지도.... 나 또한 그랬다.

 

다음은 위 예제 프로그램이다. Flash Builder 4나 Flash CS4에서 ActionScript 3.0 프로젝트를 생성해서 만들면 되겠다.

 

package
{
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.GraphicsTrianglePath;
	import flash.display.Shape;
	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageQuality;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	import flash.events.KeyboardEvent;
	import flash.events.MouseEvent;
	import flash.geom.Matrix3D;
	import flash.geom.PerspectiveProjection;
	import flash.geom.Utils3D;
	import flash.geom.Vector3D;
	import flash.utils.getTimer;
	
    [SWF(frameRate=60, backgroundColor=0x000000)]
	
	/**
	 * 3D Texture 예제.
	 * @author Yongho, Ji
	 * @since 2009.07.01
	 * @see http://help.adobe.com/ko_KR/ActionScript/3.0_ProgrammingAS3/WSF24A5A75-38D6-4a44-BDC6-927A2B123E90.html
	 */ 
	public class Texture3D extends Sprite
	{
		[Embed(source="mybaby.png")]
		private var ImageClass:Class;
		
		// 투영된  Vertex 정보 
		private var projected:Vector.<Number>;

        // 투영
        private var projection:PerspectiveProjection = new PerspectiveProjection();
        
        // World 변환 행렬 
        private var world:Matrix3D = new Matrix3D();
		
		// Viewport (3D 렌더링 대상)
		private var viewport:Shape = new Shape();
		
		// Mesh 데이터 
		private var mesh:GraphicsTrianglePath
		
		// Texture
		private var texture:BitmapData = (new ImageClass() as Bitmap).bitmapData;
		
		//triangle을 보여줄지 여부  
		private var visibleTriangle:Boolean = false;
		
		//Texture를 보여줄지 여부 
		private var visibleTexture:Boolean = true;
		
		//실린더  Mesh번호 
		private const MESH_CYLINDER:int = 1;
		
		//원형체  Mesh번호  
		private const MESH_TORUS:int = 2;
		
		//선택한 Mesh 번호 
		private var selectedMesh:int;
		
		//Viewport의 Z축 위치 
		private var viewPortZAxis:Number = 300;
		
		/**
		 * 생성자 
		 */ 
		public function Texture3D()
		{
			super();
			
			//화면 설정 
			stage.align = StageAlign.TOP_LEFT;
			stage.scaleMode = StageScaleMode.NO_SCALE;
			stage.quality = StageQuality.BEST;
			
			//viewport를 화면의 중심으로 
			viewport.x = stage.stageWidth/2;
			viewport.y = stage.stageHeight/2;
			addChild(viewport);
			
			//projection의 fieldOfView를  60으로 지정 
			projection.fieldOfView = 60;
			
			//mesh 데이터 설정 
			selectMesh( MESH_CYLINDER );
			
			//이벤트 처리 							
			stage.addEventListener( Event.RESIZE, onResize );
			addEventListener( Event.ENTER_FRAME, onEnterFrame );
			stage.addEventListener( MouseEvent.CLICK, onMouseClick );
			stage.addEventListener( KeyboardEvent.KEY_DOWN, onKey );
			stage.addEventListener( MouseEvent.MOUSE_WHEEL, onMouseWheel );
		}
		
		/**
		 * Mesh를 선택한다.
		 */ 
		private function selectMesh( meshNumber:int ):void
		{
			if( selectedMesh == meshNumber ) return;
			selectedMesh = meshNumber;
			
			//투영결과 Vertex 데이타를 초기화시킨다. 
			//이것을 해야 Mesh데이터가 바뀔때마다 drawTriangle에서 에러 발생 안함 
			projected  = new Vector.<Number>(0, false);
			switch( selectedMesh )
			{
				//실린더 
				case MESH_CYLINDER:
					mesh = createCylinderMesh( 50, 100, 20, 5 );
					break;
				//원형체 
				case MESH_TORUS:
					mesh = createTorusMesh( 50, 25, 32, 16 );
					break;
			}			
		}
		
		/**
		 * 사이즈 변경시 처리  
		 */ 
		private function onResize( event:Event ):void
		{
			//viewport는 항상 화면의 중심에 위치하도록 처리 
			viewport.x = stage.stageWidth/2;
			viewport.y = stage.stageHeight/2;	
		}
		
		/**
		 * 프레임 마다 처리. 
		 */ 
		private function onEnterFrame( event:Event ):void
		{
            world.identity(); //단위행렬로 전환 
            world.appendRotation( getTimer() * 0.027, Vector3D.X_AXIS ); //X축 회전
            world.appendRotation( getTimer() * 0.061, Vector3D.Y_AXIS ); //Y축 회전 
            world.appendTranslation(0, 0, viewPortZAxis); //이동 
            world.append(projection.toMatrix3D()); //투영 변환 적용 
            
            // mesh 데이터를  투영하여  projected 생성 
            // uvtData도 갱신된다. 갱신되는 데이터는 T값이다. 
            Utils3D.projectVectors( world, mesh.vertices, projected, mesh.uvtData );
            
            // texture를 이용해 렌더링
            viewport.graphics.clear();
            
            // Triangle 라인을 그림 
            if( visibleTriangle )
            {
            	viewport.graphics.lineStyle( 1, 0xff0000, 1.0 );
            }
            
            //Texture 입힌다.
            if( visibleTexture )
            {
            	viewport.graphics.beginBitmapFill( texture, null, false, true );
                viewport.graphics.drawTriangles( projected, getSortedIndices(mesh), mesh.uvtData, mesh.culling );            	
            }
            else
            {
            	viewport.graphics.beginFill( 0x00ccff, 1.0 );
                viewport.graphics.drawTriangles( projected, getSortedIndices(mesh), null, mesh.culling );            	
	            viewport.graphics.endFill();			
            }
		}
		
		/**
		 * 마우스 클릭 처리 
		 */ 
		private function onMouseClick( event:MouseEvent ):void
		{
			//삼각형  선 Visible 바꿈 
			visibleTriangle = !visibleTriangle;	
		}
		
		/**
		 * 키보드 이벤츠 처리  
		 */ 
		private function onKey( event:KeyboardEvent ):void
		{
			if( event.charCode == 116 )
			{
				visibleTexture = !visibleTexture;	
			}
			else
			{
				selectMesh( event.charCode - 48 ); //1,2...
			}
		}
		
		/**
		 * 마우스 휠 처리 
		 */ 
		private function onMouseWheel( event:MouseEvent ):void
		{
			viewPortZAxis += event.delta * 10;
		}
	}
}

import flash.display.GraphicsTrianglePath;
import flash.display.TriangleCulling;

/**
 * GraphicsTrianglePath를 기반으로, Z축으로 sort된 인덱스를 돌려준다.
 * 이 작업을 해주어야 z축 깊이에 따라 Triangle이 제대로 그려진다. 
 * @param mesh 정보 
 * @return sort된 index 데이터 
 */
function getSortedIndices( mesh:GraphicsTrianglePath ):Vector.<int> 
{
    var triangles:Array = [];
    var length:uint = mesh.indices.length;
    
    //z축 sort를 위한 기반 제작 
    for ( var i:uint=0; i < length; i += 3 ) 
    {
        var i1:uint = mesh.indices[ i+0 ];
        var i2:uint = mesh.indices[ i+1 ];
        var i3:uint = mesh.indices[ i+2 ];
        var z:Number = Math.min( mesh.uvtData[i1 * 3 + 2], mesh.uvtData[i2 * 3 + 2], mesh.uvtData[i3 * 3 + 2]);
        if (z > 0) 
        { 
        	triangles.push({i1:i1, i2:i2, i3:i3, z:z}); 
        }
    }
    
    //z축으로 sort
    triangles = triangles.sortOn("z", Array.NUMERIC);
    
    //sort된 값을 이용해 Vector값 만듬 
    var sortedIndices:Vector.<int> = new Vector.<int>(0, false);
    for each (var triangle:Object in triangles) 
    {
        sortedIndices.push(triangle.i1, triangle.i2, triangle.i3);
    }
    return sortedIndices;
}

/**
 * 원통모양의 Mesh 데이터 작성 
 * @param radius 원통의 반지름
 * @param height 원통의 높이 
 * @param hDiv 반지름 방향으로 조각 수 
 * @param vDiv 높이 방향의 조각수 
 * @return mesh 데이터 
 */ 
function createCylinderMesh( radius:Number, height:Number, hDiv:uint, vDiv:uint ):GraphicsTrianglePath
{
	var vertices:Vector.<Number> = new Vector.<Number>( 0, false );
	var indices:Vector.<int> = new Vector.<int>( 0, false );
	var uvtData:Vector.<Number> = new Vector.<Number>( 0, false );
	var mesh:GraphicsTrianglePath = new GraphicsTrianglePath( vertices, indices, uvtData, TriangleCulling.NONE );
	
	for( var i:uint = 0; i <= hDiv; i++ )
	{
		var theta:Number = Math.PI * 2 * i / hDiv; //z축에서 y축으로 잰각 
		for( var j:uint = 0; j <= vDiv; j++ )
		{
			var x:Number = -height/2 + j * height/vDiv;
			var y:Number = radius * Math.sin( theta );
			var z:Number = radius * Math.cos( theta );
			mesh.vertices.push( x, y, z );				//하나의 Vertex 데이타 
			mesh.uvtData.push( i / hDiv, j / vDiv, 1 ); //하나의 UVT 데이타 . Texture의 점과 Vectex를 일치시켜 Texture를 입히기 위한 데이타이다.
			if( j < vDiv && i < hDiv )
			{
                var a:uint =  i      * (vDiv + 1) + j;
                var b:uint = (i + 1) * (vDiv + 1) + j;
                mesh.indices.push(a, a + 1, b, b + 1, b, a + 1); //삼각형의 index이다. culling방향 고려 
			} 
		}
	}
	return mesh;
}

/**
 * 원환체(도너츠모양) Mesh 데이터 작성 
 * @param hRadius 원환체의 수평축 반지름
 * @param vRadius 원환체의 수직축 반지름  
 * @param hDiv 수평 방향의 조각 수 
 * @param vDiv 높이 방향의 조각수 
 * @return mesh 데이터 
 */
function createTorusMesh( hRadius:Number, vRadius:Number, hDiv:uint, vDiv:uint ):GraphicsTrianglePath 
{
	var vertices:Vector.<Number> = new Vector.<Number>( 0, false );
	var indices:Vector.<int> = new Vector.<int>( 0, false );
	var uvtData:Vector.<Number> = new Vector.<Number>( 0, false );
	var mesh:GraphicsTrianglePath = new GraphicsTrianglePath( vertices, indices, uvtData, TriangleCulling.NONE );
	for (var i:uint=0; i<=hDiv; i++) 
	{
		var s1:Number = Math.PI * 2 * i / hDiv;
		for (var j:uint=0; j<=vDiv; j++) 
		{
            var s2:Number = Math.PI * 2 * j / vDiv;
            var r:Number = Math.cos(s2) * vRadius + hRadius;
			var x:Number = Math.cos(s1) * r;
			var y:Number = Math.sin(s1) * r;
			var z:Number = Math.sin(s2) * vRadius;            
            mesh.vertices.push( x, y, z );	
            mesh.uvtData.push(i / hDiv, j / vDiv, 1);
            if (j < vDiv && i < hDiv) {
                var a:uint =  i      * (vDiv + 1) + j;
                var b:uint = (i + 1) * (vDiv + 1) + j;
                mesh.indices.push(b, a + 1, a, a + 1, b, b + 1);
            }
        }
    }
    return mesh;
}

 

아래는 위 프로그램을 실행한 것이다. Flash Player 10이 브라우져에 설치되어야 제대로 볼 수 있겠다.

 

 

키 1, 2 - 형태바꾸기

키 T(영문) - Texture 입히기 여부

마우스 클릭 - 삼각형 보이기 여부

마우스 휠 - 확대/축소

 

위 프로그램에서 사용된 GraphicsTrianglePath는 2D 공간에 3D 기하 도형 렌더링을 지원하기 위해 사용되는 클래스이다. 특별한 기능은 없지만 3D 데이터를 다룰 때 index, vertex, uvt 등의 데이타를 모두 담을 수 있기 때문에 사용하면 유용하다.

 

위 프로그램에서 원뿔, 구에 대한 mesh 함수를 추가하면 더 좋은 예제가 될 것이라 생각한다. 또한 광원(light)처리까지 곁들이면 금상첨화일 것이다.

 

 

참고글

 

 

Flash Player 10부터 3D를 구현할 수 있는 일련의 Native API 코드가 추가되었다. Papervision3D나 Away3D 처럼 멋진 3D 효과를 만들기에는 부족하긴 하지만 여러가지 시도는 해볼 수 있겠다.

 

Adobe에서 제공하는 Flash용 ActionScript 3.0 프로그래밍 가이드를 살펴보면 3차원(3D)에서 작업에 대한 내용이 있다. 이 문서만 보아도 3D 표현을 하는데 큰 도움을 받을 수 있다. 문서에는 몇가지 예제가 있는데 예제중에 UV맵핑에 올라온 예제를 가지고 몇가지 테스트 코드를 만들었다.

 

 

테스트에 참가할 나의 딸, 예진이의 사진이다. ^^

 

 

T값을 이용한 원근표현

 

UV 맵핑은 텍스쳐(Texture) 맵핑이다. 텍스쳐는 일반적인 2D 사진을 의미하며 이 사진을 삼각형의 형태로 쪼개서 여러가지 3D 표현을 할 수 있다. 사진가지고 깃발처럼 펄럭거리게 할 수 있고, 구의 형태로 만들수도 있는 것이다.

 

UV맵핑은 사진의 어느부분을 버텍스(Vertex, 꼭지점)으로 삼을것인가 지정한다. UV맵핑에서 U는 사진의 가로축을 말하며 V는 사진의 세로축을 의미한다. 가로축/세로축 범위는 0~1까지다. 그러므로 U값이 0이면 사진의 좌측맨끝부분을 의미하며 1이면 사진의 우측맨끝을 말한다. V값도 마찬가지로 0이면 사진의 위끝부분, 1이면 맨아래 부분을 의미한다. UV값은 3D 버텍스와 1:1 대응되도록 한다. 그래서 지정된 1개의 버텍스는 UV좌표에 지정된 사진의 좌표에 대응되어 맵핑이 이뤄지도록 하는 것이다. 이러한 맵핑을 할 수 있도록 하는 함수가 Graphics의 drawTriangle()이다. 이 함수의 1번째 인자가 버텍스값이고 3번째 인자가 UV데이타 값이다.

 

사진의 경우 이미지 그대로 화면에 표현하기 위해 삼각형이 최소 2개가 필요하다. 사진에서 표현되는 실제 버텍스는 4개이지만 각각 삼각형의 버텍스는 6개가 된다. 이중에 2개의 버텍스는 중복이 된다. 이 중복을 제거하기 위해 사용하는 것이 인덱스이다. 인덱스를 이용하면 삼각형을 그리기 위한 버텍스의 순서를 정의함으로서 중복된 버텍스를 없앤다. 이 값은 drawTriangle()의 2번째 인자값으로 지정할 수 있다.

 

여기서 사용한 T값은 버텍스의 위치가 3D 형태로 변하면서 사진도 이에 맞게 크기를 조절할 필요가 있기 때문에 사용하는 값이다. 이를 이용해 원근표현을 할 수 있게 된 것이다.

 

아래 코드를 보면서 UV데이타와 T값을 이용한 3D 표현법에 대한 기초를 공부할 수 있다.

 

(소스를 복사할때는 이 소스를 직접복사하지 말고 소스 위에 마우스를 올려 소스보기 아이콘이 뜰때 그것을 눌러 새창이 뜨면 소스를 긁어다가 사용하면 되겠다.)

package
{
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.display.TriangleCulling;
	import flash.events.Event;
	import flash.utils.getTimer;
	
	/**
	 * 3D 회전. T값을 이용해 원근 적용 
	 * @author Yongho, Ji
	 * @since 2009.06.30
	 * @see http://help.adobe.com/ko_KR/ActionScript/3.0_ProgrammingAS3/WS509D19BB-239B-4489-B965-844DDA611AE7.html
	 */ 
	public class Baby3D extends Sprite
	{
		[Embed(source="mybaby.png")]
		private var ImageClass:Class;
		
		//평면 버택스 좌표와 t값 
		private var x1:Number = -100, y1:Number = -100, z1:Number =0, t1:Number = 0;
		private var x2:Number = 100, y2:Number = -100, z2:Number =0, t2:Number = 0;
		private var x3:Number = 100, y3:Number = 100, z3:Number =0, t3:Number = 0;
		private var x4:Number = -100, y4:Number = 100, z4:Number =0, t4:Number = 0;
		
		//초점거리 
		private var focalLength:Number = 200;
		
		//버텍스에 대한 인덱스 
		private var indices:Vector.<int>;
		
		private var container:Sprite;
		
		private var bitmapData:BitmapData;

		public function Baby3D()
		{
			super();
			
			this.stage.align = StageAlign.TOP_LEFT;
			this.stage.scaleMode = StageScaleMode.NO_SCALE;
			
			//1평면당 2개의 삼각형을 가진다. 
			indices = new Vector.<int>;
			indices.push( 0,1,3, 2,3,1 );
			
			container = new Sprite();
			container.x = 200;
			container.y = 200;
			addChild( container );
			
			var bitmap:Bitmap = new ImageClass();
			bitmapData = bitmap.bitmapData;
			this.addEventListener( Event.ENTER_FRAME, rotatePlane );	
		}
		
		private function rotatePlane( event:Event = null ):void
		{
			//버텍스 회전 
			var ticker:Number = getTimer() / 400;
			z2 = z3 = -(z1 = z4 = 100 * Math.sin( ticker ) );
			x2 = x3 = -(x1 = x4 = 100 * Math.cos( ticker ) );
			
			//t값 계산 
			t1 = focalLength / ( focalLength + z1 );
			t2 = focalLength / ( focalLength + z2 );
			t3 = focalLength / ( focalLength + z3 );
			t4 = focalLength / ( focalLength + z4 );
			
			//삼각형 버택스
			//t값을 이용해서 버텍스 좌표에 원근값을 주도록 한다.  
			var vertices:Vector.<Number> = new Vector.<Number>;
			vertices.push( 
					x1*t1,y1*t1, 
					x2*t2,y2*t2, 
					x3*t3,y3*t3, 
					x4*t4,y4*t4 
			);
			
			// UV 맵핑 
			// T값을 이용해  원근값이 적용된 맵핑이미지를 만들기 위해  
			var uvtData:Vector.<Number> = new Vector.<Number>;
			uvtData.push( 
					0,0,t1,
					1,0,t2,
					1,1,t3,
					0,1,t4
			);	
			
			//그리기 
			container.graphics.clear();
			container.graphics.lineStyle( 1, 0xff0000, 1.0 );
			container.graphics.beginBitmapFill( bitmapData );
			container.graphics.drawTriangles( vertices, indices, uvtData, TriangleCulling.NONE );
			container.graphics.endFill();							
		}
	}
}

 

 

위 코드는 Adobe에서 제공하는 예제를 약간 변형한 것이다. 실행결과는 다음과 같다.

 

소개한 코드는 그냥 실행하는데 그치는 것은 좋지 못하다. 값을 적절하게 변경해가면서 어떻게 변경되는가 알아보는 것도 좋은 학습법이라 생각한다.

 

 

DisplayObject의 Y축을 이용한 회전. 원근투영 이용

 

위 예제와 UV맵핑은 동일하지만 T값은 1로 고정한다. 그리고 Y축만 회전하고 원근표현은 T값 대신에 perspectiveProjection 속성을 이용한다.

 

T값을 1로 한다는 것은 T값을 가지고 원근표현을 하지 않겠다는 의미이다. 아래 예제를 보면 알겠지만 T값이 변하지 않기 때문에 EnterFrame 이벤트 발생시마다 그려주는 대신 생성자에서 한번만 그렸다.

 

원근투영(Perspective Projection)은 Flash Player 10에서 기본적으로 지원해준다. 가장 단순한 투영방식으로 하나의 소실점을 가지도록 하는 Linear한 투영방식이다. 만약 이 투영방식을 사용하지 않는다면 Matrix3D를 이용해 다른 방식의 원근투영방식을 적용해야 한다.

 

아래 코드에서 root.transform.perspectiveProjection.fieldOfView는 100으로 설정되어 있다. 이 값은 T값으로 원근표현할때와 비슷한 느낌을 주기 위한 값이다. fieldOfView 0~180까지 적용할 수 있다. 0은 말그대로 매우 먼곳에서 대상을 바라보는 것과 같다. 즉, 왜곡이 없는 원통투영이 된다. 하지만 180에 가까워 질 수록 대상과 가까이서 보는 느낌을 주게 된다. 한개의 소실점을 가지는 원근투영이 된다. 이 값을 적절하게 조절하면서 실행해보자 무슨 의미인지 알 수 있을 것이다.

package
{
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.display.TriangleCulling;
	import flash.events.Event;
	
	/**
	 * 3D 회전. 원근 투영을 이용
	 * @author Yongho, Ji
	 * @since 2009.06.30
	 * @see http://help.adobe.com/ko_KR/ActionScript/3.0_ProgrammingAS3/WS36223081-8938-4b45-BB89-F1F8B1A52E4E.html
	 */ 
	public class Baby3D_2 extends Sprite
	{
		[Embed(source="mybaby.png")]
		private var ImageClass:Class;		
		
		private var container:Sprite;
				
		public function Baby3D_2()
		{
			super();
			
			this.stage.align = StageAlign.TOP_LEFT;
			this.stage.scaleMode = StageScaleMode.NO_SCALE;
			
			container = new Sprite();
			container.x = 200;
			container.y = 200;
			addChild( container );
			
			//원근투영의 FiendOfView 설정. 기본값은 55임 
			root.transform.perspectiveProjection.fieldOfView = 100;
			
			//비트맵 데이타 생성 얻어오기 
			var bitmap:Bitmap = new ImageClass();
			var bitmapData:BitmapData = bitmap.bitmapData;

			//버텍스 
			var vertices:Vector.<Number> = new Vector.<Number>;
			vertices.push( 
					-100,-100, 
					100,-100, 
					100,100, 
					-100,100 
			);
			
			//위 베텍스에 대한 인덱스 , 1평면당 2개의 삼각형을 가지고 있다. 
			var indices:Vector.<int> = new Vector.<int>;
			indices.push( 0,1,3, 2,3,1 );

			//UV 맵핑 데이타  
			var uvtData:Vector.<Number> = new Vector.<Number>;
			uvtData.push( 
					0,0,1,
					1,0,1,
					1,1,1,
					0,1,1
			);	
			
			//그리기 
			container.graphics.clear();
			container.graphics.lineStyle( 1, 0xff0000, 1.0 );
			container.graphics.beginBitmapFill( bitmapData );
			container.graphics.drawTriangles( vertices, indices, uvtData, TriangleCulling.NONE );
			container.graphics.endFill();				
				
			this.addEventListener( Event.ENTER_FRAME, rotatePlane );
		}

		private function rotatePlane( event:Event = null ):void
		{
			//컨테이너 회전 
			container.rotationY -= 5;
		}
	}
}

 

 

 

실행된 이미지가 조금 다르게 보일 것이다. T값을 이용한 원근표현 방식의 경우 렌더링을 계속하기 때문에 삼각형 선(빨간색)이 왜곡되지 않는다. 하지만 원근투영방식을 이용하면 한번 렌더링을 해주고 그것 자체를 회전시키는 방식이기 때문에 삼각형 선이 왜곡되어 보인다.

 

위 예제들은 쉬운 코드들이긴 하지만 UV맵핑, T값, 원근투영, 버텍스 등에 대한 이해가 없다면 어려울 수 밖에 없다. 3D 프로그래밍을 한번도 안한 분이라면 다른 서적이나 인터넷을 통해 학습자료를 찾아 공부하면 좋겠다는 생각이 든다.

 

Flash Player 10의 3D 기법은 아주 기초 수준이기 때문에 Papervision3D, Away3D처럼 화려한 3D를 만드는 것은 기대하기 어렵다. 하지만 Native수준에서 기능을 제공하므로 빠른 연산이 가능해서 간단한 3D 구현하겠다면 위 예제 처럼 만들자. 앞으로 Papervision3D, Away3D도 Flash Player 10 기반으로 제공한다고 하니깐 조금더 향상된 퍼포먼스를 가진 Flash 3D 애플리케이션을 기대해도 좋을 것 같다.

 

참고글

3차원(3D)에서 작업

 

기존 Flex Builder 3를 구입한 사람에게만 적용됩니다.

 

Step 1. Adobe Labs에 접속합니다. (http://labs.adobe.com)

 

Step 2. Adobe Flash Builder 4 Beta 다운로드 페이지로 이동합니다.

https://www.adobe.com/cfusion/entitlement/index.cfm?e=labs%5Fflashbuilder4

Adobe 회원이 아니라면 먼저 회원가입하세요.

 

Step 3. 30-day Beta 부분에 request extension serial number 를 선택합니다.

기존 Flex Builder 3를 고객만 해당되며 Flash Builder 4 다운로드 후 21일 후에 Flash Builder 4에 대한 Serial Number를 받을 수 있다고 명시되어 있습니다.

 

 

Step 4. 자신의 이름과 전자우편주소, Flex Builder 3 Serial Number를 입력하고 Do you accept this term?를 check한다음에 Request Serial Number 버튼을 누릅니다.

입력한 전자우편 주소로 Flash Builder 4 Serial Number가 전송되므로 정확하게 입력해야합니다.

 

성공적으로 발송되면 다음과 같은 페이지가 나옵니다.

 

Step 5. 입력한 전자우편 계정에서 Flash Builder 4 Serial Number를 확인합니다.

 

 

Step 6. Flash Builder 4를 실행시키고 받은 Serial Number를 입력합니다.

 

 

이제부터 Flash Builder 사용기간에 구애받지 말고 마음대로 쓰세요.

ActionScript 3.0(이하 AS3)은 Java와 매우 유사한 구조를 가진다. 그러므로 Java를 공부한 사람이라면 AS3를 쉽게 접근할 수 있게 된다. C++을 했던 사람도 비슷한 구조때문에 쉽게 접근이 가능하다.

 

AS2를 공부했던 사람은 객체지향적으로 설계된 AS3를 공부하면 된다.

 

여기서는 ActionScript 3.0에 대해서 어떤 절차없이 생각나는대로 적어보았다. 매우 개인적인 생각이기 때문에 반박하고 싶으신 내용도 있을지 모르겠다. 그렇다면 얼마든지 댓글 환영이다. ^^

 

 

Java와 ActionScript 3.0 차이점

 

Java와 AS3간에 차이점을 보여주는 표이다. 쭉 훑어보자.

http://blog.naver.com/surfwon/30049390718

 

 

ActionScript 3.0의 아쉬운점

 

AS3는 객체지향기반의 언어이지만 몇가지 아쉬운 점이 존재한다.

 

1. 메소드(함수)의 오버로드(overload)를 할 수 없다.

 오버로드는 클래스안에 같은 이름의 함수를 중복해서 사용할 수 있는 기능이다. AS3에서는 Java나 C++에서 지원되는 이 오버로드 기능이 없기 때문에 개발시 아에 생각을 하지 않게 하는 장점도 있지만 정말 필요할때 다른 방법으로 대체해야하는 불편함이 생긴다. 아래글을 보자.

 http://blog.jidolstar.com/484

 

2. 생성자는 하나만 존재한다.

위 오버로드를 지원하지 않는 것과 일맥 상통하는 내용이다.

 

3. 추상클래스가 지원되지 않는다.

Java와 같은 abstract 키워드가 존재하지 않아 추상클래스를 만들 수 없다. 하지만 throw등을 이용해 컴파일시가 아닌 런타임에서 동작하는 추상클래스는 제작이 가능하다. 추상클래스는 AS3에도 클래스 설계시 꼭 필요하지만 아직 지원하지 않아 아쉬운 부분이다. 아래 글을 보자.

http://blog.jidolstar.com/452

 

 

4. private 생성자가 지원되지 않는다.

private 생성자는 외부에서 클래스 객체를 임의로 만드는 것을 금지시켜주는 역할을 한다. 하지만 AS3에서는 생성자에 public외에는 private, protected 를 쓸 수 없다. private 생성자가 필요한 단적인 예는 싱글턴 패턴을 구현할 때인데 AS3에서는 아래와 같이 다른 방법으로 싱글턴 패턴을 구현한다.

http://koko8829.tistory.com/304

 

아래 링크는 싱글턴 패턴을 응용한 형태이다.

http://blog.jidolstar.com/468

 

 

 

ActionScript 3.0을 왜 사용해야하는가?

 

AS2를 주로 했던 사람은 AS3의 강력함을 잘 느끼지 못해서 전향하지 않는 사람들이 많은 것 같다. 아래 글을 보고 AS3를 왜 해야하는가 알아보자.

 

AS3를 왜 사용해야하는가? : http://ddongkang.tistory.com/76 

 

 

ActionScript 3.0 의 재미

 

초보자가 Java, C++에서 느끼지 못하는 AS3의 재미는 처음 만드는 애플리케이션이 시각화된 결과물이라는 점일 것이다. 이 게시판에 앞어 간단한 AS3프로젝트를 통해 3D를 단 몇줄도 안되는 코드로 구현했던 것을 보면 그 의미를 알 것이다. 또한 AS3를 하다보면 매우쉽게 데이터통신, 데이터제어, 각종 미디어 기술지원, Flex, AIR로의 개발확장성등으로 인해 C++, Java와는 또 다른 재미를 느끼게 될 것이다.

 

 

ActionScript 3.0 공부하자.

 

AS3는 얼마든지 공부할 수 있다. 좋은 동영상 강좌도 있다.

AS3 강좌모음 : http://ddongkang.tistory.com/73

 

AS3 학습을 위해 아래 링크의 문서와 친해지자.(한글이라서 좋다!)

http://help.adobe.com/ko_KR/ActionScript/3.0_ProgrammingAS3/

http://help.adobe.com/ko_KR/AS3LCR/Flash_10.0/

 

위 문서는 영문도 있다.

http://help.adobe.com/en_US/ActionScript/3.0_ProgrammingAS3/

http://help.adobe.com/en_US/AS3LCR/Flash_10.0/

 

 

 

Flash 기술에 대한 오해

 

아래 글에 오해하시는 분들이 많으신 것 같아요. 아래 제 댓글을 보시고 제가 보는 관점에서 아래 글을 판단해주셨으면 합니다. ^^

 

C++,  Java하시던 분(기초수준 및 서버개발만 주로 한 분들)이 AS3나 Flex를 접할때 잘못된 태도가 있다. 내가 좀 언어좀 하는데 그냥 쉽게쉽게 할 수 있겠지라는 마인드이다. 또는 Flash인데 그것도 언어야?... 이런 마인드.. 금물이다.

 

AS3나 Flex도 그만의 독특한 구조와 기능이 있다. C++, Java만 했던 사람이 데이터 바인딩, 메타데이터태그, 이벤트 모델, 비디오제어, 음원제어, 마이크로폰 제어, 디스플레이객체 다루기, 응용애플리케이션구조,각종보안, XML 다루기등에 대해서 잘 알리가 없지 않은가? 그만큼 Flash 기술이 단순히 그래픽을 위한 기술이다라고 오해하는데서 비롯된다고 생각한다. Flash 기술은 이미 데스크탑에서 모바일, 각종운영체제, 각종 브라우져에 MS계열의 기술보다 더욱 빠르고 거대하게 확장되어 이미 우리 실생활에 적용되고 있다. 그러므로 AS3.0을 공부할때 너무 가벼운 마음으로만 공부한다는 생각은 버리자. 요즘 안드로이드 폰에 Flash 지원하고 앞으로 AIR로 각종 모바일 프로그램을 만들 수 있게 된다는 것을 생각하면 가벼운 기술은 아니구나라는 생각을 할 수 있을 것이다.(못믿겠으면 여기를 본다) Flash는 이미 클라이언트 기반을 넘어 서버기반의 기술도 지원해주어 진정한 RIA의 선봉장에 있다. 이러한 점을 잘 이해하고 Flex,AS3,AIR,Flash등을 공부하는 것이 좋겠다.

 

Flash에서 이미지를 표현하기 위한 방법은 VectorBitmap이 있다.

 

Vector은 점,선,면등의 단순한 수학적 정보만 이용하여 이미지를 표현한다. 그렇게 때문에 적은 용량으로 이미지를 표현할 수 있으며 특히 확대/축소에도 깨지지 않는 이미지를 표현하는데 탁월하다. 또한 곡선 및 각종 이펙트 표현력도 훌륭하다. 적은 용량으로 이미지를 표현할 수 있기 때문에 메모리 관리에 도움이 된다. 하지만 복잡한 이미지의 경우 도리어 퍼포먼스의 저하가 일어날 수 있으며 이 경우 이동과 같은 에니메이션을 줄 경우 CPU연산이 많아지는 단점이 있다.

 

Bitmap은 이미지를 표현하기 위해 Pixel정보를 이용한다. 하나의 Pixel정보에는 ARGB값을 가진다. Pixel 하나하나 정보를 가지고 다루므로 복잡한 이미지를 나타내는데 좋으며 Vector와 달리 이동 애니메이션을 주더라도 CPU연산이 적다. 하지만 이미지가 클수록 Pixel 정보량이 제곱단위로 커지기 때문에 확대/축소시에 메모리가 과다하게 사용될 여지가 있다. 잘못사용하면 너무 큰 이미지로 인해 Flash Player가 다운될 수도 있다.

 

아래는 필자가 다루고자 하는 Vector와 Bitmap 이미지의 장단점을 언급한다.(아래 장단점은 완벽한 것은 아니다. 가령 Vector라고 해도 객체수가 적으면 속도에 무리가 없기 때문이다. 적어도 필자가 하고 있는 프로젝트 내에서 이런 문제가 있다는 것을 미리 언급한다.)

 

  • Vector 이미지
    - 메모리 효율이 좋다.(확대/축소시에도 메모리 점유율 변화 없음)
    - 이동 애니메이션 처리가 느리다.(CPU 연산이 커짐)
  • Bitmap 이미지
    - 메모리 효율이 나쁘다.(확대시 메모리 점유율 커짐)
    - 이동 애니메이션 처리가 빠르다.(CPU 연산 적음)

 

Vector 이미지와 Bitmap 이미지를 연구하게된 배경은 필자가 스타플 서비스(http://starpl.com/)의 별지도를 만드는데 이 부분에 대한 정보가 필요했기 때문이다. 먼저 아래 글을 읽어보면 이해가 쉽겠다.

 

[Flash,ActionScript 3.0]DisplayObject cacheAsBitmap 속성의 적용시점 문제

[Flex/AIR] 메모리의 무서운 적! DisplayObject의 cacheAsBitmap 속성

 

스타플의 별지도

 

구글맵과 같은 일반 지도의 경우 타일(Tile)들의 구성요소는 Bitmap 이미지이다. 최적화된 Bitmap이미지이기 때문에 렌더링 시간도 짧고 이동도 빠르다. 필자가 작업한 별지도는 천체 데이터 및 사용자 별 데이터를 읽어와 Vector 이미지로 렌더링하는 방식이다. 따로 데이터 가공 및 렌더링 시간이 필요하고 Vector이미지는 일단 이동 애니메이션이 느리다. 별이 반짝거리므로 단순히 Bitmap으로 바꿔서 처리할 수 있는 것도 아니다. 결국 스타플 별지도는 기본적으로 Vector 이미지로 타일을 구성해야한다.

 

앞서 설명했듯이 Vector 이미지를 이용하면 메모리 문제는 발생하지 않는다. 문제는 이동시 속도가 느리다는 것이다. 마우스로 지도를 드래그해서 이동하는 경우 저사양 컴퓨터에서는 버벅거림이 발생한다. Vector 이미지 타일로 구성된 지도의 단점이다. 그래서 이것을 극복하기 위해 첫번째로 사용하려했던 기능이 flash.display.DisplayObject의 cacheAsBitmap 속성이였다. 이 속성을 true로 설정하면 타일이 Bitmap으로 캐싱되고 실제로 적용후 이동처리해보면 훨씬 부드러운 이동 애니메이션을 볼 수 있다.

 

하지만 cacheAsBitmap은 Bitmap 이미지를 만드는 것이므로 확대/축소에서 문제가 있다. 이 속성을 true로 설정하고 확대하면 메모리가 기하급수적으로 늘어난다. 그래서 간단하게 확대/축소전에 cacheAsBitmap을 false로 지정하면 되겠다고 생각했다. 하지만 이 속성이 false로 설정된 순간 캐싱된 Bitmap을 해제시켜주지 못했다. 결국 false로 지정하고 확대해도 여전히 메모리가 증가한다.

 

이를 극복하기 위해 BitmapData를 이용했다. 만들어진 타일(Tile) 클래스에 bitmapMode라는 속성을 정의하고 true이면 Bitmap이미지를 false이면 Vector이미지를 보일 수 있도록 만든다. 그렇게 해서 이동이 발생시에는 Bitmap이미지를 사용하고 평상시 또는 확대/축소시에는 Vector 이미지를 사용할 수 있도록 한다. 아래는 이것을 구현하는 간단한 코드이다.(완벽한 코드가 아니다. 단지 예시일 뿐이다.)

 

this.addEventListener( Event.REMOVED_FROM_STAGE, onRemovedFromStage, false, 0, true );

public function bitmapMode( value:Boolean ):void
{
	if( _bitmapMode == value ) return;
	_bitmapMode = value;
	
	.....

	if( _bitmapMode )
	{
		var bitmapData:BitmapData = new BitmapData( getWidth()+100, getHeight()+100, true, 0x00000000 );
		var matrix:Matrix = new Matrix();
		matrix.translate( 50, 50 );						
		bitmapData.draw( this, matrix, null, null, null, false );
		bitmapImage = new Bitmap( bitmapData );
		bitmapImage.x = -50;
		bitmapImage.y = -50;
		addChild( bitmapImage );
		removeChild( vectorImage );
	}
	else
	{
		addChild( vectorImage );
		removeChild( bitmapImage );
	}

	.....
}


public function disposeBitmap():void
{
	bitmapMode = false;
	if( bitmapImage )
	{
		bitmapImage.bitmapdata.dispose();
		bitmapImage = null;
	}
}

private function onRemovedFromStage( event:Event ):void
{
	this.removeEventListener( Event.REMOVED_FROM_STAGE, onRemovedFromStage, false );
	disposeBitmap();
}

 

bitmapMode 속성을 구현할때 주의할 사항은 bitmapMode가 true일 때마다 BitmapData를 생성하는 일은 매우 비효율적이라는 것이다. 왜냐하면 이동할때마다 BitmapData를 만드는 것이기 때문이다. 그러므로 처음 이동이 발생할때만 BitmapData를 만들도록 하는게 중요하다. 또한 bitmapMode가 false라고 해서 기존에 만든 BitmapData를 삭제해서는 안된다. 삭제하면 언급한데로 다시 BitmapData를 만들어야하기 때문이다.

 

또 한가지 여기서 발생하는 가장 큰 문제는 적절한 메모리 관리이다. BitmapData를 잘 삭제해주어야 메모리 문제가 발생하지 않기 때문에다. 단순히 위에서 언급한 bitmapMode 변경으로는 BitmapData를 제대로 삭제할 수 없다. 그러므로 타일(Tile)클래스에 disposeBitmap() 함수를 만들어 bitmap.bitmapdata.dispose()를 호출하여 비트맵을 삭제하도록 하고 타일이 removeChild 될때 이 disposeBitmap()을 호출하여 BitmapData를 완벽하게 삭제하도록 해준다. removeChild가 되는 시점은 Event.REMOVED_FROM_STAGE 이벤트 처리를 하면 바로 알 수 있다.

 

삭제된다는 이야기는 메모리에서 바로 삭제된다는 것을 의미하지 않는다. Flash Player는 가비지 컬렉터가 작동한다. 즉 메모리에서 삭제될 대상을 모아두었다가 어느 시점에 한꺼번에 삭제한다. 이는 메모리 관리의 효율적인 방법중에 하나로서 개발자는 가비지 컬렉터 대상이 되도록 모든 참조를 없애주도록 해야한다. 가비지 컬렉터에 대해서는 아래 필자의 글을 참고한다.

 

[Flex / AIR / ActionScript3] Event 청취자와 메모리 관리

Flash Player 의 가비지 컬렉션(GC) 동작 방식에 대해

 

메모리가 제대로 반환이 되는지 확인하기 위해 Flex Builder(현 Flash Builder)의 프로파일링 기능을 이용하거나 System.totalMemory를 적극적으로 활용하여 메모리 상태를 점검하는것이 좋겠다.

 

소개한 대로 Bitmap과 Vector 이미지를 필요할 때마다 바꿔가면서 처리하는 것이 필자가 발견한 최상 방법이였다. 적용시 키포인트는 적절한 메모리 관리와 BitmapData를 자주 생성하지 않도록 하는 것이다.

 

 

 

DisplayObject 속성중에 cacheAsBitmap을 사용해 본 적이 있나요?

 

cacheAsBitmap은 시각화된 객체를 Bitmap으로 캐싱해주는 역할을 하는 속성입니다. 이것이 true가 되면 DisplayObject는 Bitmap처럼 되는 것이지요. Flash에서 Bitmap과 상반되는 것이 Vector입니다. 일반적으로 Flash에서 그래픽적인 요소는 거의 Vector로 처리되지요. Bitmap과 Vector에 대한 차이점과 장단점을 굳이 설명안하겠습니다.

 

단지 제가 궁금하고 해결하고 싶은 문제는 cacheAsBitmap속성이 적용된 것을 확대/축소할때 입니다.

이 속성을 true로 하고 이동처리(마우스 Drag등)을 하는 것과 안한것은 엄청난(?)속도 차이를 보입니다. 복잡한 Graphic이 들어가고 이동이 필요한 경우 cacheAsBitmap을 true로 설정하여 애플리케이션의 퍼포먼스를 높힐 수 있지요. 하지만 cachAsBitmap은 메모리와는 적입니다. 가령 확대/축소할때 가장 큰 문제가 되는데 Bitmap이 확대/축소할때 이미지는 그 크기만큼 메모리를 잡아먹습니다. 아래 제가 쓴 글을 보시면 이해될 겁니다.

 

[Flex/AIR] 메모리의 무서운 적! DisplayObject의 cacheAsBitmap 속성

 

저는 이동시에는 cacheAsBitmap을 true로 설정하고 확대/축소할때는 false로 설정해서 퍼포먼스 개선을 하려고 합니다. 하지만 cacheASBitmap을 설정하더라도 적용되는 시점이 문제입니다. 확대/축소하기 전에 false로 지정하고 바로 확대/축소를 하면 메모리를 잡아먹는다는 것이지요. 다음 Render시점에서 확대/축소를 해야하는 것인가 생각이 드는데 아직 뾰족한 묘안이 없습니다.

 

혹시 저와 같은 경험을 하셨다던가 아니면 해결책을 알고 계신분 있다면 댓글 부탁드리겠습니다.

 

 

Adobe Labs에서는 Learn to Use Flash Catalyst 라는 제목으로 Flash Catalyst에 관련된 페이지를 오픈해두었습니다.

아래 링크를 통해서 어렵지 않게 Flash Catalyst에 대해 접할 수 있습니다.

 

http://labs.adobe.com/technologies/flashcatalyst/tutorials/

 

Adobe Developer Center에서는 Flex에 관련된 새로운 소식이 속속 올라옵니다. 유용한 기술문서가 자주 올라오기 때문에 참고하시면 도움이 됩니다.

 

http://www.adobe.com/devnet/flex/

 

여기에 최근에 올라온

등을 한번쯤 구독해 보심을 권합니다.

 

Flex 4 및 Flash Builder에 대해서 자세히 알고 싶다면 아래 Flex 4 Livedocs를 참고합니다.

 

http://livedocs.adobe.com/flex/gumbo

 

 

 

Flex SDK 4를 이용한 예제가 Tour de Flex에 추가 되었습니다.

 

Flex 4가 기존 Flex 3에 비해서 바뀐 부분이 있는데 이를 쉽게 공부하길 원한다면 Tour de Flex를 이용하는 것이 좋을 것 같습니다.

 

Tour de Flex에는 Flex 4의 Components, Effects, Layouts, Primitives, Styles 사용법에 대해서 쉬운 예제를 제공하고 있습니다.

 

Flex 개발자라면 꼭 한번 보시길 바랍니다.

 

 

Tour de Flex는 Web용, 데스크탑용(AIR로 제작됨)이 있습니다.  아래 링크를 통해 보시거나 다운로드 받아 설치하세요.

 

 

 

Tour de Flex Eclipse Plug-in 소개 및 설치, 사용해보기

Tour de Flex에서는 Eclipse Plug-in을 제공하고 있습니다. 여러분은 쉽게 Tour de Flex에서 제공하는 200여가지의 Flex 샘플을 Flex Builder 3.x 또는 Eclipse 3.4를 통해 쉽게 검색해 볼 수 있습니다. 다음과 같은 순서로 설치해보십시오.

  1. Flex Builder 3에서 Help > Software Updates > Find and Install 을 선택합니다.
  2. 새창이 뜨면 Search for new features to install 을 선택후 Next 버튼을 누릅니다.
  3. Net Remote Site 버튼을 눌러 새창이 뜨면 Name에 Tour de Flex, URL에 http://tourdeflex.adobe.com/eclipse 를 입력합니다.
  4. Site to include in search 리스트 창에 Tour de Flex만 check하고 Finish 버튼을 누릅니다.
  5. 성공적으로 연결이 되면 Select the features to install 리스트 창에 Tour de Flex가 뜹니다. 이것을 선택하고 Next를 누릅니다.
  6. Feature License에서 라이센스 동의를 선택후 Next 를 누릅니다.
  7. Finish를 눌러 Plug-in을 다운로드 합니다.
  8. 다운로드가 완료되면 install이나 Install All을 눌러 설치합니다.
  9. 설치 완료 이후 자동으로 다시 Flex Builder 3를 구동해야 한다는 메시지가 뜨면 Yes버튼을 눌러 재시작합니다.

 

설치가 완료되고 Flex Builder 3가 다시 구동되면 메뉴에서 Windows > Other Views에 들어가 봅니다.

 

 

위 그림처럼 Tour de Flex가 설치되어 있고 이것을 선택하고 OK 버튼을 누릅니다.

 

아래와 같은 Updating 메시지가 뜨면 Download now를 눌러 설치하세요. 이런 메시지가 뜨는 것으로 보아 Plug-in이 Tour de Flex AIR 프로그램을 자동으로 설치 및 업데이트를 해주는 것으로 보입니다.

 

 

Flex Builder에 아래와 같은 창이 나오면 Serach Components를 통해 해당 컴포넌트를 검색할 수 있습니다. 필자는 아래 화면과 같이 Button으로 검색해봤습니다. 결과가 나오면 보고 싶은 항목을 선택하여 더블클릭하도록 합니다. 그러면 설치형 Tour de Flex를 실행시켜 해당 컴포넌트의 사용 예제를 보여주도록 되어 있습니다.

 

 

아래는 Button Loader Skin을 더블클릭으로 선택해서 나온 결과입니다.

 

 

아쉽게도 약간의 버그가 있는 것 같고 Flash Builder 4에는 아직 제대로 동작이 안되는 것을 확인했습니다. 이 부분에 대해서는 업데이트 될 때까지 기다려야할 것 같습니다. 개발 하면서 궁금한 사항이 있다면 이 Tour de Flex Eclipse Plug-in을 적극 활용하면 도움이 될 것이라 생각합니다.

Flash Builder 4와 더불어 Flash Catalyst Beta 버전이 배포되었습니다. 이것도 여전히 공식배포 버전은 아니며 테스트 버전이라고 볼 수 있지만 거의 완성단계가 아닌가 판단이 됩니다.

 

Flash Catalyst(이하 FC)는 개발자와 디자이너가 협업의 도구입니다. 지금까지 Flex Builder의 디자인 모드 및 CSS를 통해 어느정도 디자인이 가능했지만 디자이너가 접근하기에는 너무나도 불편한 구조였습니다. 또한 디자이너가 MXML, ActionScript 3.0 코드까지 알아야하는 경우도 발생해서 더욱 다루기 어렵고 개발자와의 협업이 어려웠던 것이 사실입니다.

 

FC는 디자이너가 프로그래밍 코드를 전혀 알것없이 PhotoShop 또는 Illustrator 와 같은 디자인 툴로 작업한 PDS나 AI파일을 카탈리스트로 불러와 레이아웃, State등을 잡고 각종 Effect도 잡을 수 있도록 만들 수 있습니다. 그 결과물(MXML코드)을 개발자에게 넘겨주면 개발자가 MXML에 비지니스 로직을 추가하는 방법으로 진행이 됩니다. 완벽한 디자인 분리가 될 것이라는 것은 해봐야 알겠지만 아무튼 이런 툴이 만들어진 것은 분명 반가운 일입니다.

 

FC는 기존 Flash를 사용해본 디자이너라면 쉽게 접근해서 사용할 수 있는 구조로 되어 있습니다. 그러므로 학습하는데도 큰 무리가 없을 것으로 보입니다.

 

FC로 작업한 결과물은 Flex뿐 아니라 AIR로도 개발이 가능해집니다. 이것 또한 큰 매력이라고 할 수 있습니다.

 

FC는 아래 링크에서 다운로드 받을 수 있으며 30일 Trial버전으로 테스트해볼 수 있습니다.(Adobe 가입/로그인을 해야합니다.)

http://www.adobe.com/cfusion/entitlement/index.cfm?e=labs_flashcatalyst

재미있는 것은 Serial Number를 함께 제공해줍니다.

 

FC에 대해서 더욱 자세히 알고 싶다면 아래 링크를 참고하세요. 동영상을 꼭 보시길…

http://labs.adobe.com/technologies/flashcatalyst/

 

아래 링크는 GotoAndLearn 사이트에서 제공하는 Flash Catalyst and Flex 4에 대한 간단한 강의입니다.

Flash Catalyst and Flex 4: Part 1

Flash Catalyst and Flex 4: Part 2

 

 

1. Flash Catalyst 설치하기

시리얼 번호를 입력하라는 창이 나옵니다. 배포 페이지에서 Serial Numbers를 함께 제공하므로 그것을 입력해서 사용합니다.

 

사용권 계약입니다. 당연히 동의해야 설치가 가능하겠죠?

 

옵션 설정인데… 뭐 선택할 것이 없네요. 그냥 설치합니다.

 

아래 화면처럼 설치 진행률을 보여줍니다.

 

 

2. Flash Catalyst 사용해보기

필자도 처음 해보는 것이라 자세한 기능 설명은 해드릴 수 없습니다. 엉성하게 나마 소개해 드리겠습니다.

 

설치가 완료되면 실행해보시길 바랍니다. BETA 1이 선명하게 표시되어 있군요.

 

아래는 실행화면입니다. 한글버전이 지원되는군요. 매우 좋습니다.

 

잘 보시면 Adobe Illustrator AI파일에서, Adobe Photoshop PSD 파일에서, 마지막으로 FXG 파일에서 작업을 시작할 수 있음을 알 수 있습니다. 아직 이런 작업물이 없으므로 그냥 새로운 프로젝트를 만들어보겠습니다.

 

아래와 같은 창이 나옵니다. 프로젝트 이름과 폭,높이,색상을 정합니다. 이름에 한글입력은 전혀 안됩니다. 프로젝트 명이니 당연한 것인지도..

 

프로젝트를 만들어봤습니다. 매우 단순해보입니다. 아래 그림의 상단에 Page1은 Flex의 State(상태)를 의미합니다.


즉, 화면전환을 위한 것이지요. 오른쪽에 간단한 도구와 Flex Component도 볼 수 있습니다.

 

맨 아래 타임라인을 클릭해보면 아래처럼 볼 수 있습니다. 이것은 State 전환을 위한 설정입니다. FC로 State 및 각종 Effect도 줄 수 있다는 것을 예상 할 수 있습니다.

 

버튼을 올려봤습니다. 버튼이 선택되어 있는 상태에서 버튼 설정을 쉽게 할 수 있도록 만들어져 있습니다. 필자는 개발자라 오히려 한글로 표현된 것이 어색하네요.

 

단추모양편집을 눌러보겠습니다.

 

아래처럼 버튼의 상태모양을 편집할 수 있습니다. 버튼의 State를 변경할 수 있도록 되어 있군요. 되돌아 가려면 검정색으로 보이는 바에 프로젝트명을 클릭하면 됩니다.(아래 그림에서는 jidolstar를 클릭합니다.)

 

응용프로그램 상호작용의 +버튼을 눌러보면 Flex 컴포넌트의 CreationComplete 이벤트가 발생했을때 처리를 설정할 수 있도록 되어 있습니다. 가령 상태 변환 및 동작시퀀스 제작등입니다.

 

그 아래 사용자 정의 상호 작용을 누르면 마우스 이벤트에 대한 다음 동작을 정의할 수 있습니다.

 

다른 주제로 넘어가서…

 

FC의 화면의 페이지 상태 밑에 중복상태, 비어있는 상태 버튼이 있는 것을 볼 수 있습니다. 차이점은 선택되어 있는 상태를 중복해서 상태를 만들것이냐 아니면 새로 상태를 만들것이냐 차이입니다. 확실한 것은 눌러보면 알 수 있다는 ^^

 

아래처럼 상태를 만들어 봤습니다. 타임라인 부분도 변경된 것을 확인할 수 있습니다.

 

각 상태 변화를 보기 위해  다른 컴포넌트를 올려보겠습니다.

아래는 2번째 상태입니다. 기존 상태를 중복한 겁니다. 확인(CheckBox)를 추가했습니다.

 

아래는 가로 스크롤 막대(HScrollBar)를 넣어봤습니다.

 

이제 이것을 저장해보겠습니다. 확장자가 FXP이군요. 기억해야겠네요.

 

 

3. Flash Builder 4에서 로드해보기

 

Flash Builder 4를 실행해보시고 방금 만들 FC파일을 로드합니다.

 

아래처럼 Flex 프로젝트가 만들어지고 MXML 코드가 들어가 있습니다. 상태 및 각 컴포넌트 배치, Transition 설정까지 모두 다 있습니다. 또한 components에는 만든 컴포넌트의 스킨도 포함되어 있는 것을 확인 할 수 있습니다. 개발자는 이제 FC에서 만들어진 결과물을 가지고 비지니스 로직만 구축하면 됩니다.

 

4. 정리하기

Flash Catalyst는 필자도 처음 사용해본 것입니다. 그러기 때문에 더 자세한 내용은 전달 할 수 없었습니다. 앞으로 필자도 이와 관련된 연구를 계속해서 함께 정보공유를 할 수 있는 분위기가 만들어졌으면 합니다. 기존 Flex 개발자는 Flash Builder 4 및 Flex 4 SDK를 공부해야할 것이고 디자이너는 Flash Caalyst를 익혀야 할 것입니다. Flash Builder와 Flash Caalyst는 Adobe RIA 개발/디자인 최초(?)의 협업도구 입니다. 첫번째 버전이라 아마도 부족한 점이 있을 것이지만 앞으로의 발전을 기대해봅니다.

Adobe Flash Builder 4 Beta가 배포되었습니다. 공식판은 아니고요. Flash Builder 4는 Flex Builder 3의 차기버전으로 Flex SDK 4(Flex Gumbo)를 기본 SDK로 설정되어 있습니다. Flex SDK 4로 만들어진 애플리케이션은 기본적으로 Flash Player 10 버전에서 동작이 가능합니다. 물론 기존 Flex SDK 3도 사용할 수 있습니다.

 

아래 링크에서 언제든지 다운로드 받을 수 있습니다.(회원가입하셔야 합니다.) 윈도우, 맥 버전이 있으니 자신의 운영체제에 맞게 다운로드 받으시면 됩니다.

 

http://www.adobe.com/cfusion/entitlement/index.cfm?e=labs%5Fflashbuilder4

 

 

다음 글을 참고하세요.

 

1. 설치하기

위 링크에서 다운로드 받아 쉽게 설치할 수 있습니다.

 

처음 설치화면입니다.

 

C:/Program Files/Adobe/Flash Builder Beta 경로에 기본 설치되는군요.

 

설치중인 화면입니다.

 

이름도 Gumbo입니다. ㅎㅎ 아직 Beta라는 것이겠죠.

 

실행해 보겠습니다.

 

30일 Trial버전으로 사용해봅니다.

 

첫 실행 화면입니다. FB 문자가 선명하네요. FB는 Flex Builder가 아니라 Flash Builder 입니다.

 

 

 

2. 사용해보기

 

메뉴 구성 변화

FXP 임포트도 되고 Test Case Class, Test Suit Class도 만들 수 있도록 되어 있네요. Flex Builder로 테스트 주도형 제작이 가능해지겠군요.

 

Data 메뉴가 상당히 직관적으로 바뀌었네요. 또한 더욱 많은 서비스도 지원하도록 만들어졌고요. 진정한 RIA를 구축하기 위한 서버측 기술 지원을 배려한 기능이군요.

 

 

이전에 없었던 Data/Services, ASDoc, Network Monitor 등이 지원되네요.

 

아래는 네트워크 모니터툴입니다. RPC 통신등을 할 때 서버와 통신이 어떻게 되는지 확인하는데 요긴하게 사용할 수 있을 것 같습니다. 이제 Http Watcher가 필요 없겠군요!!!!!

 

 

Flex 코딩해보기

Flex Project를 만들어보겠습니다. SDK를 Flex SDK 3.4와 Flex 4.0을 사용할 수 있도록 되어 있네요. Flex 4.0 기반에서 작업해 보겠습니다.

 

FDT(Flash Development Tools)에서 보이는 구조와 비슷하네요. 프로젝트에 사용하고 있는 Flex 4.0 SDK 라이브러리들도 보이고 각각의 (+)버튼을 누르면 정의된 클래스도 한눈에 볼 수 있습니다. 이거 정말 편해졌군요. SWC도 못봤던 netmone.swc, sparksins.swc등이 있습니다.

 

재미있는 것은 Flex Gumbo가 Fx이니셜로 컴포넌트가 만들어진 것으로 알고 있었는데 FxApplication이 아닌 그냥 Application입니다.

그리고 몇가지 namespace(fx, s, mx)로 구분 되었네요. ComboBox가 DropDownList로 이름이 바뀐 것 같습니다.

 

<?xml version="1.0" encoding="utf-8"?>
<s:Application
    xmlns:fx="http://ns.adobe.com/mxml/2009"
    xmlns:s="library://ns.adobe.com/flex/spark"
    xmlns:mx="library://ns.adobe.com/flex/halo"
    minWidth="1024" minHeight="768">
    <s:Panel x="14" y="44" width="250" height="200" title="타이틀입니다.">
        <s:Button x="15" y="12" label="버튼"/>
        <s:CheckBox x="15" y="46" label="체크박스"/>
        <mx:ColorPicker x="16" y="71"/>
        <s:DropDownList x="103" y="13"/>
        <s:RichEditableText x="92" y="50" width="147" height="39" text="RichEditableText 컴포넌트"/>
    </s:Panel>
</s:Application>

 

디자인 모드입니다. 스킨도 바뀌고 새로운 컴포넌트도 있네요.

 

실행해볼까요?

헛! 깔끔해진 실행화면!, Flex가 이젠 기본스킨도 이쁘게 나왔네요.

 

 

Theme 선택 기능

프로젝트의 Properties를 보면 Flex Theme가 추가되었습니다. 와우! 그런데 아직 기능이 완벽하지 못한 것 같습니다. 적용이 잘 안되네요 ㅡㅡ;

 

 

Flash CS4 Component 연동

디자인 모드에서 Components를 보시면 Custom에 New Flash Componet, New Flash Container가 있습니다. 즉 Flash CS4에서 만들어진 SWC와 연동이 가능해졌다는 것을 의미합니다.

 

드래그 해서 디자인 뷰에 붙이면 아래처럼 화면에 보이고 선택하면 오른쪽 Properties에 Create in Adobe Flash 버튼이 보입니다.

 

 

버튼을 누르면 Class를 선택할 수 있게 하고 SWC 파일을 선택합니다.

Create 버튼을 누르면 Flash CS4가 실행되면서 컴포넌트를 만들 수 있게 되어 있네요.

 

 

ASDoc 기능

와우! Panel에 마우스를 올려보니 간단한 설명도 나오네요. Eclipse에서는 되던건데… ㅎㅎ

 

앗… ASDoc View가 바로 이런 역할을 가지는 거였군요. 해당 컴포넌트를 선택하면 ASDoc View에 아래처럼 보입니다. 강력하군요.

 

 

Getter/Setter 생성기능 / ASDoc Commnet 기능

 

엄청난 기능(?)이 추가되었군요. Eclipse에서는 당연히 되던건데 왜 Flex Builder에서는 안되나 궁금했던 기능이 이제야 추가되었군요.

 

properties를 만들고 해당 속성의 Getter/Setter 기능을 아래처럼 추가할 수 있습니다. 단축키는 없네요. ㅡㅡ;

또한 Add ASDoc Commnent 기능도 추가 되었습니다. @author, @since 등을 자동으로 붙여줄 수 있으면 좋으련만… 아마 설정이 가능할겁니다. ^^

 

 

이벤트 핸들러 및 Service Call 등록 기능

 

코딩작업 없이 Event 핸들러 및 Service Call 기능을 추가할 수 있게 되었습니다.

 

 

위 그림에서 처럼 버튼을 선택하고 Properties에서 On click에 Generate Event Handler 를 선택하면 아래처럼 이벤트 핸들러가 생성됩니다. 넘 좋군요. ^^

 

버튼을 click하면 바로 아래 코드가 실행되는겁니다.

 

그리고 Generate Service Call을 누르면 Data/Services를 생성해서 연결할 수 있습니다. 와우 RIA 개발툴 다운 면모가 보이는군요.

 

 

File Templates 기능

메뉴의 Windows의 Perferences에 들어가면 아래와 같은 창이 뜹니다.

여기에서 Flash Builder > File Template 가 있습니다. 이를 선택하면 자신의 코딩방식을 설정할 수 있도록 되어 있습니다. 좋은 기능이네요.

 

 

3. 정리하며

이외에도 아직 제가 발견하지 못한 기능이 상당합니다.

 

이제 FDT를 사용하지 않아도 Flash Builder로 충분히 예전에 지원되지 않았던 기능들을 사용할 수 있게 되었습니다. 몇가지 버그가 있는 것 같지만 그 버그만 잘 잡아준다면 훌륭합니다.

 

제가 적지 않은 기능이 있다면 소개해주세요. 그리고 제 블로그에 트랙백(엮인글) 적극 환영합니다. ^^

오늘 한국 Adobe RIA 공식사이트에서 발생된 ACC 뉴스레터를 이메일로 받았습니다. 

 

ACC는 Adobe Community Champion의 약어로 글로벌 정책으로 Adobe 각 제품군(Flash/Flex/AIR등)별 전문가들을 말합니다.  ACC 뉴스레터는 1개월에 한번씩 정기적으로 회원들에게 이메일을 통해 전송되며 한국 Adobe RIA 공식사이트에 가입하시면 받으실 수 있습니다.

 

제가 쓴 글을 비롯해 ACC분들의 글들이 올라왔습니다. 

 

ACC는 단순히 전문가만을 의미하지 않습니다. 또한 ACC라서 최상의 기술을 가지고 있는 것 또한 아닙니다. ACC가 아니더라도 드러내지 않는 실력 가지신 분들도 많답니다. 그럼 ACC는 어떤 사람들일까요?

 

제가 생각하는 ACC는 바로 Adobe 기술의 전도사입니다.

 

처음 Adobe 기술을 접하시거나 Adobe의 새로운 기술을 알고 싶고 때로는 토론하고 싶다면 ACC 분들과 적극적으로 커뮤니케이션을 유지하시면 좋겠다는 생각을 합니다.

 

한가지 조언드릴 것은 Adobe 기술도 나름 분야가 방대합니다. Flash, Flash Lite, Flex, ColdFusion, AIR, LiveCycle, BlazeDS 등등.. 아무리 ACC더라도 자신이 잘알고 해본 분야는 제한되어 있고요. 다 아는 것이 아닙니다. 그러므로 각각의 ACC의 전문분야를 잘아시고 목적에 맞게 대화를 하시는 것이 좋을 것 같습니다.

 

저의 경우 스타플(http://starpl.com)에 Adobe Flex/Flash 기술을 도입하면서 얻은 지식을 공유할 수 있습니다. Java분야나 ColdFusion, Flash Lite는 저도 잘 모릅니다. 그러므로 스타플에서 사용된 별지도, 별꾸미기, 위젯, 이미지에디터 등 Adobe RIA 기술에 국한되어 답변할 수 있을 것 같습니다. 같은 ACC지만 저도 다른 ACC의 도움을 받습니다. 그러므로 이 글을 보시는 여러분도 ACC와 소통하실때 그 부분을 잘 참고하시길 바랍니다.

 

또 한가지!

 

소통의 도구로 가장 좋은 것은 블로그입니다. 글 쓰는게 어려우신 분은 어쩔 수 없다지만 제가 생각할 때 블로그만큼 좋은 소통의 도구가 없습니다. 제가 ACC가 된 것도 블로그를 통해 기술을 공유하고 또한 소통하면서 얻은 타이틀입니다. 제가 잘해서 또는 이쪽에 탁월한 전문가이기 때문은 아니라는겁니다. ACC는 기술의 전도사라는 말을 기억하시면 이를 쉽게 이해할 수 있을겁니다.

 

제가 블로그를 강조하는 이유는 가령 이렇습니다. 일전에 제가 [ActionScript 3.0] Stage.invalidate()를 호출해도 Event.RENDER 이벤트가 발생하지 않는 문제 해결 글을 올렸습니다. 이 글을 올린데에는 단순히 지식전달 수준이 아닙니다. 지식전달만 목적이라면 블로그 할 필요없죠. 댓글을 보시면 답이 나옵니다. 서로 지식이 공유되고 있지요. 이게 파격적인 겁니다. 잘 모르더라도 잘 정리해서 글을 적으면 함께 고민하는 사람과 이야기할 수 있고 서로 알아갈 수 있습니다. 블로그는 댓글 기능만 있는 것이 아니라 블로그의 글끼리 묶어주는 트랙백(엮인글)이 됩니다. 또한 RSS발행을 통해 관심분야 사람들 유입경로를 만들어줍니다. 아직도 개발자인데 블로그 안하시나요? 공부하고 싶고 인맥을 구축하고 싶은데 블로그를 안하시나요? 실력이 없어서 또는 잘 모르는 지식 때문에 괜히 악플달릴까 고민하는 것은 좋지 않는 생각입니다. 틀리는 것을 부끄럽다고 생각하지 말아야 발전이 있는 것이니깐요. 블로그 하시는 것이 여러분의 실력을 키우는데 큰 도움이 되는 것을 강조하고 싶습니다. 적어도 저는 블로그의 혜택을 톡톡히 보는 사람중에 한사람이라 자신있게 강조합니다. ^^

 

ACC는 앞으로도 Adobe RIA 기술의 전도사로서 여러 활동을 할겁니다. 기대해주시고 기억해주세요. ^^

Adobe Flex가 아닌 Flash CS3, CS4, ActionScript 3.0로 개발해본 사람이면 Stage를 자주 접하게 된다. 하나의 ActionScript 3.0 애플리케이션은 Stage 하나를 가지고 있다. 이것은 모든 DisplayObject 계열의 객체가 addChild()를 통해 시각화 과정이 완료후에 그 객체의 stage속성을 통해 접근이 가능하다. 모든 DisplayObject 계열의 객체에서 stage속성에 접근할 수 있다는 것은 stage가 매우 중요하다는 것을 암시하고 있다. 그러므로 잘 알고 활용해야 삽질을 방지할 수 있지 않을까?



1. Stage.invalidate()와 Event.RENDER 이벤트의 이해 


Stage 클래스에는 invalidate() 메소드가 있다. 이것을 호출하면 다음 렌더링 시점에 Event.RENDER 이벤트를 발생시킨다. 모든 DisplayObject 객체는 stage에 접근이 가능하므로 Event.RENDER 이벤트를 청취할 수 있다. 이는 애플리케이션 퍼포먼스를 향상시키고자 하는 개발자들이 반드시 알고 있어야할 사항이라고 생각한다. Stage의 invalidate()와 Event.RENDER 이벤트를 이용해 쓸데없는 렌더링을 예방함으로써 퍼포먼스를 향상시킬 수 있기 때문이다. 그 이유를 명확하게 알기 위해 다음 예제를 보자.


필자는 일반 ActionScript 3.0 프로젝트를 만들고 Sprite기반에서 애플리케이션을 만들고자 한다. 이때 두개의 클래스를 만드는데 하나는 Stage.invalidate()를 사용한 클래스이고 하나는 사용하지 않는것이다. 이 두개를 비교하면 Stage.invalidate()의 유용성을 확연히 알 수 있다.


InvalidateTest.as

package {
	import flash.display.Sprite;
	import flash.display.StageScaleMode;
	import flash.display.StageAlign;

	/**
	 * Stage.invalidate() 테스트 메인 
	 * @author Yongho, Ji (jidolstar@gmail.com)
	 * @since 2009.6.1
	 */ 
	public class InvalidateTest extends Sprite
	{
		public function InvalidateTest()
		{
			super();
			
			stage.align = StageAlign.TOP_LEFT;
			stage.scaleMode = StageScaleMode.NO_SCALE;
			
			var button1:NotUseInvalidateButton = new NotUseInvalidateButton();
			button1.setWidth( 100 );
			button1.setHeight( 100 );
			button1.backColor = 0xff0000;
			button1.lineColor = 0x00ff00;
			button1.x = 20;
			button1.y = 20;
			addChild( button1 );
			
			var button2:UseInvalidateButton = new UseInvalidateButton();
			button2.setWidth( 100 );
			button2.setHeight( 100 );
			button2.backColor = 0xff00ff;
			button2.lineColor = 0x0000ff;
			button2.x = 130;
			button2.y = 20;
			addChild( button2 );			
		}

	}
}

위처럼 두개의 버튼 클래스를 만들었다. 2개 모두 폭, 높이, 배경색, 선색을 지정하도록 되어 있다. 결과는 비슷하다. 하지만 내부적으로 NotUseInvalidateButton은 내부에 Stage.invalidate()를 사용하지 않았고 UseInvalidateButton은 사용했다. 소스를 보자.


NotUseInvalidateButton.as

package
{
	import flash.display.Sprite;

	/**
	 * Stage.invalidate()를 사용하지 않는 Button
	 * @author Yongho, Ji (jidolstar@gmail.com)
	 * @since 2009.6.1
	 */ 
	public class NotUseInvalidateButton extends Sprite
	{
		private var _height:Number = 50;
		private var _width:Number = 50;
		private var _backColor:Number = 0xffffff;
		private var _lineColor:Number = 0x000000;
		
		public function NotUseInvalidateButton()
		{
			super();
		}
		
		public function get backColor():Number
		{
			return _backColor;
		}
		
		public function set backColor( value:Number ):void
		{
			_backColor = value;
			drawNow();
		}
		
		public function get lineColor():Number
		{
			return _lineColor;
		}
		
		public function set lineColor( value:Number ):void
		{
			_lineColor = value;
			drawNow();
		}
		
		public function getWidth():Number
		{
			return _width;
		}
		
		public function setWidth( value:Number ):void
		{
			_width = value;
			drawNow();
		}
		
		public function getHeight():Number
		{
			return _height;
		}
		
		public function setHeight( value:Number ):void
		{
			_height = value;
			drawNow();
		}
		
		public function drawNow():void
		{
			trace( "[NotUseInvalidateButton] drawNow" );			
			graphics.clear();
			graphics.beginFill( backColor, 1 );
			graphics.lineStyle( 1, lineColor, 1 );
			graphics.drawRect( 0, 0, getWidth(), getHeight() );
			graphics.endFill();
		}
		
	}
}


UseInvalidateButton.as

package
{
	import flash.display.Sprite;
	import flash.events.Event;

	/**
	 * Stage.invalidate()를 사용하는 Button
	 * @author Yongho, Ji (jidolstar@gmail.com)
	 * @since 2009.6.1
	 */ 
	public class UseInvalidateButton extends Sprite
	{
		private var _height:Number = 50;
		private var _width:Number = 50;
		private var _backColor:Number = 0xffffff;
		private var _lineColor:Number = 0x000000;
		
		private var isDrawNow:Boolean = false;
		private var isInvalidated:Boolean = false;
				
		public function UseInvalidateButton()
		{
			super();
			
			this.addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
		}		
		
		private function onAddedToStage( event:Event ):void
		{
			this.removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
			
			if( isInvalidated )
			{
				isInvalidated = false;
				this.stage.addEventListener(Event.RENDER, onRender, false, 0, true );
				this.stage.invalidate();
			}
		}
		
		private function onRender(event:Event):void
		{
			if( this.stage )
			{
				this.stage.removeEventListener( Event.RENDER, onRender, false );
			}
			if( isDrawNow )
			{
				isDrawNow = false;
				drawNow();
			}
		}
		
		private function invalidate():void
		{
			if( this.stage )
			{
				this.stage.addEventListener(Event.RENDER, onRender, false, 0, true );
				this.stage.invalidate();
			}
			else
			{
				isInvalidated = true;	
			}
		}
		
		
		public function get backColor():Number
		{
			return _backColor;
		}
		
		public function set backColor( value:Number ):void
		{
			_backColor = value;
			isDrawNow = true;
			invalidate();
		}
		
		public function get lineColor():Number
		{
			return _lineColor;
		}
		
		public function set lineColor( value:Number ):void
		{
			_lineColor = value;
			isDrawNow = true;
			invalidate();
		}
		
		public function getWidth():Number
		{
			return _width;
		}
		
		public function setWidth( value:Number ):void
		{
			_width = value;
			isDrawNow = true;
			invalidate();
		}
		
		public function getHeight():Number
		{
			return _height;
		}
		
		public function setHeight( value:Number ):void
		{
			_height = value;
			isDrawNow = true;
			invalidate();
		}
		
		public function drawNow():void
		{
			trace( "[UseInvalidateButton] drawNow" );
			graphics.clear();
			graphics.beginFill( backColor, 1 );
			graphics.lineStyle( 1, lineColor, 1 );
			graphics.drawRect( 0, 0, getWidth(), getHeight() );
			graphics.endFill();
		}		
		
	}
}

디버깅 모드로 프로그램을 실행해보면 콘솔창에 다음과 같이 출력된다.


[NotUseInvalidateButton] drawNow

[NotUseInvalidateButton] drawNow

[NotUseInvalidateButton] drawNow

[NotUseInvalidateButton] drawNow

[UseInvalidateButton] drawNow 


NotUseInvalidateButton은 drawNow()메소드가 4번 호출되고 UseInvalidateButton은 1번만 호출된다. 이것만 보더라도 쓸데없는 렌더링을 줄여준 UseInvalidateButton 클래스가 더 잘 설계되었다는 것을 확인할 수 있다. 


NotUseInvalidateButton를 잘 살펴보면 setWidth(), setHeight(), set backColor, set lineColor 를 호출할 때마다 drawNow() 메소드를 호출한다. 개발자라면 이 4가지 속성이 모두 적용된 다음에 drawNow()를 호출하여 한번만 그려주고 싶어질 것이다. 이때 유용하게 사용할 수 있는 것이 Stage.invalidate() 이다.


UseInvalidateButton을 보자 NotUseInvalidateButton보다 약간 복잡해보이지만 잘 따라가보면 어렵지 않게 분석이 가능할 것이다. 결국 4개의 속성이 설정되면 invalidate() 메소드가 호출되고 Event.RENDER 이벤트를 받는 onRender()에서 drawNow()가 호출되도록 한다. 이렇게 되면 4개든 여러개든 할 것 없이 렌더링에 영향을 주는 다중 속성을 설정할 때마다 drawNow()를 호출하여 그림을 그려주는 부담을 줄일 수 있다. 


그림을 그리는 행위는 일반 속성을 설정하는 것보다 더 비싼 비용을 가진다. 그러므로 그림을 그리는 것은 되도록 한번에 처리할 수 있도록 하는 것이 애플리케이션 퍼포먼스 향상의 중요한 요소가 될 수 있는 것이다. 



2. Stage.invalidate()의 버그와 해결방법 


하지만 이렇게 좋은 Stage.invalidate() 가 있음에도 불구하고 실제로 이 메소드를 호출해도Event.RENDER 이벤트가 발생하지 않아 당황스러운 경우가 필자에게 있었다. 그래서 어떤 것은 drawNow()가 호출되고 또 어떤 것은 호출안되는 상황이 발생한 것이다. 너무도 어이가 없지만 Flash Player 10으로 넘어오면서도 이 버그는 아직까지도 Fix가 되지 않은 모양이다. 



위 링크는 모두 Adobe Bugs 관리 시스템에 등록된 것이다.(여러분도 가입해서 투표하길 바란다. 버그 시스템을 잘 이용하면 해결하지 못하는 문제도 쉽게 해결할 수 있을지 모른다. ^^)


Event.RENDER가 발생한 중간에 stage.invalidate()가 호출되면 이게 무시가 되나보다. 그래서 동작상으로는 문제없지만 개발자에게는 버그처럼 느껴질 수 있을지 모르겠다. 어쨌든 필자도 이 문제로 고민을 하다가 한가지 해결방법을 찾았는데 그것은 Event.ENTER_FRAME 이벤트를 이용하는 방법이다. 


Event.ENTER_FRAME을 이용해서 stage.invalidate()의 버그를 말끔히 해결할 수 있다. 다음 코드는NotUseInvalidateButton을 Event.ENTER_FRAME을 이용하는 것으로 바꿔본 것이다.


NotUseInvalidateButton.as



package
{
	import flash.display.Sprite;
	import flash.events.Event;

	/**
	 * Stage.invalidate()를 사용하는 Button.
	 * Event.ENTER_FRAME 로 Stage.invalidate() 버그 우회 
	 * @author Yongho, Ji (jidolstar@gmail.com)
	 * @since 2009.6.1
	 */ 
	public class UseInvalidateButton extends Sprite
	{
		private var _height:Number = 50;
		private var _width:Number = 50;
		private var _backColor:Number = 0xffffff;
		private var _lineColor:Number = 0x000000;
		
		private var isDrawNow:Boolean = false;
		private var isInvalidated:Boolean = false;
				
		public function UseInvalidateButton()
		{
			super();
			
			this.addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
		}		
		
		private function onAddedToStage( event:Event ):void
		{
			this.removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
			
			if( isInvalidated )
			{
				isInvalidated = false;
				this.stage.addEventListener(Event.RENDER, onRender, false, 0, true );
				this.stage.addEventListener(Event.ENTER_FRAME, onRender, false, 0, true );
				this.stage.invalidate();
			}
		}
		
		private function onRender(event:Event):void
		{
			if( this.stage )
			{
				this.stage.removeEventListener( Event.RENDER, onRender, false );
				this.stage.removeEventListener( Event.ENTER_FRAME, onRender, false );
			}
			if( isDrawNow )
			{
				isDrawNow = false;
				drawNow();
			}
		}
		
		private function invalidate():void
		{
			if( this.stage )
			{
				this.stage.addEventListener(Event.RENDER, onRender, false, 0, true );
				this.stage.addEventListener(Event.ENTER_FRAME, onRender, false, 0, true );
				this.stage.invalidate();
			}
			else
			{
				isInvalidated = true;	
			}
		}
		
		
		public function get backColor():Number
		{
			return _backColor;
		}
		
		public function set backColor( value:Number ):void
		{
			_backColor = value;
			isDrawNow = true;
			invalidate();
		}
		
		public function get lineColor():Number
		{
			return _lineColor;
		}
		
		public function set lineColor( value:Number ):void
		{
			_lineColor = value;
			isDrawNow = true;
			invalidate();
		}
		
		public function getWidth():Number
		{
			return _width;
		}
		
		public function setWidth( value:Number ):void
		{
			_width = value;
			isDrawNow = true;
			invalidate();
		}
		
		public function getHeight():Number
		{
			return _height;
		}
		
		public function setHeight( value:Number ):void
		{
			_height = value;
			isDrawNow = true;
			invalidate();
		}
		
		public function drawNow():void
		{
			trace( "[UseInvalidateButton] drawNow" );
			graphics.clear();
			graphics.beginFill( backColor, 1 );
			graphics.lineStyle( 1, lineColor, 1 );
			graphics.drawRect( 0, 0, getWidth(), getHeight() );
			graphics.endFill();
		}		
		
	}
}


위에서 stage.addEventListener( Event.ENTER_FRAME )과 stage.removeEventListener( Event.ENTER_FRAME ) 이 추가된 것을 확인하자. 무작정 이벤트를 청취하고 있으면 안되므로 적절하게 삭제도 하고 있다. 


이렇게 처리함으로써 stage.invalidate()가 제대로 작동안하더라도 drawNow()메소드가 호출이 안되는 경우는 없게 된다.



3. fl.core.UIComponent 수정하기 


Flash의 UIComponent 를 보면 이것 stage.invalidate() 때문에 렌더링이 안되는 경우가 종종 발생한다고 한다. 필자는 Flash는 해본적이 없기 때문에 확실하게 모르지만 이것도 Event.ENTER_FRAME 을 이용해 해결할  수 있다. 그 방법은 다음 링크를 참고한다.


How to Fix the Flash CS3 Components


UIComponent의 callLater()가 어떻게 동작하는 것인지 이것을 보고 감을 잡을 수 있을 것이다. 결국  Event.RENDER, Event.ENTER_FRAME 으로 한다!


4. mx.core.UIComponent의 무효화 메소드들


Flex의 UIComponent에는 invalidateDisplayList(), invalidateProperties(), invalidateSize()와 같은 invalidate() 메소드가 존재한다. 이는 앞선 메소드들의 호출에 대해 각각 updateDisplayList(), commitProperties(), measure() 메소드가 다음 렌더링 시점에 호출되도록 하고 있다. 이는 invalidate/validate 패턴이다. 


재미있게도 Flex에서는 Stage.invalidate()에서 처럼 동작하지 않는 버그는 존재하지 않는다. 어떻게 된 것일까?


사실 Flex 내부를 살펴보면 Stage.invalidate() 와 Event.ENTER_FRAME을 이용해 invalidate 계열의 메소드를 처리하도록 되어 있다. UIComponent 부터, LayoutManager, SystemManager 를 들춰보면 Event.RENDER와 Event.ENTER_FRAME을 가지고 invalidate/validate 패턴을 구현하고 있다. (Flex 개발자도 어쩔 수 없었나보다. ㅡㅡ;;) 


이에 대한 자료는 아래 링크를 읽어보면 되겠다.


Flex internals: Setting a button label



Flex만 접해보고 ActionScript 3.0 학습에 소홀히 했다면 절대 이런 문제를 발견할 수 없었을 것이다. 




정리하며


Stage.invalidate()가 호출할 때 때떄로 Event.RENDER가 호출되지 않는 문제는 버그가 아닐 수 있다. 원래 그렇게 만들어졌을지도 모른다. 하지만 개발자는 이것을 버그로 여긴다. 왜냐하면 보통 개발할때는 그런 것은 생각안하고 메뉴얼만 보고 당연히 그러겠지 생각하기 때문이다. 좀 황당하지만 버그 아닌 버그로 Flex 까지 다 까보게 되었다 ㅡㅡ;


Event.RENDER, Stage.invalidate() 모를때는 setTimeout()과 Timer를 이용해 invalidate/validate 패턴을 구현했었다. 아~ 무식은 용감하다. ㅋ



Flash에서는 Remoting 호출을 위한 클래스가 존재하지 않는다. NetConnection을 가지고 할 수 있으나 Remoting 호출을 위한 전용 클래스가 아니기 때문에 실용적이지 못하다.

Flex에서는 Remoting 호출을 위해서 RemoteObject 클래스가 있다. 하지만 Flex 전용이라 Flash CS3, 4 개발자는 쓸 수 없다.

여기서는 NetConnection을 한번 wrapping하여 Remoting 호출을 구현한 Service 클래스를 소개한다. 이 Service 클래스는 byteArray.org 블로그 운영자가 만든 것으로 필요할 때 사용하면 매우 유용할 것이라 생각한다.

mport org.bytearray.remoting.Service;
import org.bytearray.remoting.PendingCall;
import org.bytearray.remoting.events.FaultEvent;
import org.bytearray.remoting.events.ResultEvent;
 
var service:Service=new Service("HelloRemoting",
"http://localhost:8001/amfphp/gateway.php");
 
var pendingCall:PendingCall=service.sayHello("remoting");
pendingCall.addEventListener(ResultEvent.RESULT,onResult);
pendingCall.addEventListener(FaultEvent.FAULT,onFault);
 
function onResult(e:ResultEvent):void{
trace(e.result);
}
 
function onFault(e:FaultEvent):void{
trace(e.fault);
}

사용법은 위처럼 너무 간단하다. AMFPHP, OpenAMF등으로 Remoting 서비스를 위한 서버를 구축하고 클라이언트는 Flash를 통해 개발하려는 사람에게 매우 유용할 듯 싶다. 또는 Flex가 아닌 순수 ActionScript 3.0 으로만 개발하려는 사람에게도 괜찮을 것 같다.



더 자세한 내용은 아래에 이 코드를 만든 원저자의 글을 보길 바란다.


Making Flash Remoting easier in Flash CS3


 

제가 속한 회사(위콘 커뮤니케이션즈)에서 iPhone, iPod용 애플리케이션을 출시했습니다. 이름하야 AppConnet(앱커넥트).

 

앱스토어에 올라온 수많은 애플리케이션 중에 유용한 애플리케이션 정보를 서로 공유하고 추천해주는 애플리케이션입니다. 앱스나이퍼, 앱리뷰, 판도라박스, 바겐빈, 앱마이너와 비슷한 컨셉을 가지지만 여기에 SNS(Social Network Service) 기능을 더했습니다. 그래서 친구를 맺고 친구끼리 좋은 애플리케이션 정보를 평가하여 서로 공유할 수 있습니다. 불특정 다수가 추천하는 것보다는 성향이 비슷한 친구끼리 공유한다면 그것 또한 큰 매력이라고 생각합니다.

 

 

 

무료 애플리케이션, 최근 가격이 떨어진 애플리케이션, 포인트가 상승된 애플리케이션이 상단에 나와줌으로 양질의 애플리케이션을 쉽게 구할 수 있게 구성되어 있습니다.

 

유료 컨텐츠는 Cart에 보관하여 나중에 구입할 수 있게 되어 있고 선택한 애플리케이션을 Buy 하면 앱스토어로 직접 연결해 구입하도록 유도합니다.

 

더욱 자세한 사항은 아래 링크를 참고하세요.

 

 

아이팟, 아이폰에 AppConnect를 설치하셨다면 jidolstar를 친구맺어주세요. ^^

 

 

ByteArray.org에 기존 Flash Player 9에서 사용되는 corelib의 JPGEncoder보다 더 빠른 Flash Player 10용으로 제작된 JPGEncoder를 만들어 공개했다. 더 빠를 수 있었던 것은 VectorBitmapData.setVector() 때문이다. 기존 Array 형태보다 글쓴이 컴퓨터에서는 2.5배 빠르다고 한다. 아래 프로그램에서 테스트 할 수 있다. 2880 x 2800 비트맵데이타를 encoding 하도록 만들어졌다.

 

 

 

필자는 아직 여러가지 문제로 Flash Player 10용으로 개발하지 못하고 있어 아쉽지만 위와 같은 기능을 앞으로 잘 사용하면 속도개선에 큰 도움이 될 것이라 판단이 된다.

 

저번주 2009년 5월 23일 서울 서초동 비트컴퓨터 멀티미디어관 지하 2층에서 OKGosu.net 세미나가 있었습니다. 저는 "스타플(starpl.com) 서비스에 이용된 Flex 기술" 이라는 제목으로 발표를 했습니다.

 

스타플 홍보도 하고 싶고 함께 기술도 공유하고 싶어서 그랬는지 주제 자체가 너무 커져서 많은 것을 전달하지 못해 약간 아쉬움이 남는 시간이였던 것 같습니다.

 

이번 세미나로 제가 말씀드리고 싶었던 것은 아래 내용이였습니다.

 

1. 스타플 소개

2. Flex를 도입할 때 고려해야할 사항

3. 개발 방법 소개

4. 모듈 개발시 최적화 방법

5. Flex 학습방법

 

한가지만 해도 30분 이상씩 잡아야 제대로 설명이 가능한 건데 너무 많은 주제를 놓고 이 모든 것은 40분 내에 말할려고 했네요. ^^;;

 

약간 보충해서 설명드릴께요.

 

1. 스타플 소개

스타플 구경을 못드렸네요. http://starpl.com 에 가시면 구경하실 수 있답니다.

홍보 동영상을 다시한번 보고 싶으시다면 "홍보영상보기"를 눌러주세요.

스타플에 대해서 더 알고 싶다면 제 블로그에서 스타플로 검색해보세요.

 

2. Flex를 도입할 때 고려해야할 사항

발표시 말씀드렸던 것은 Flex/Flash 중 도입 선택에 대한 문제였습니다. 어떤 경우에는 Flex를 도입하는 것이 맞는 반면 경우에 따라서는 Flex를 쓰는 것이 오히려 문제가 될 수 있습니다. 가령, Flex Framework에서 제공하는 컴포넌트를 거의 사용하지 않는데 Flex를 도입하는 것은 무리입니다. 또한 Flex를 도입하면 쉽게 해결될 것을 Flash로 만들면 그것 또한 문제지요. 이 점을 잘 생각하시고 선택하시길 바래요.

 

3. 개발 방법 소개

제 개인적인 개발 방법을 소개했습니다.

분석-설계-제작-테스트-배포 형태로 된다는 것을 설명드렸죠.

말씀 드리고 싶었던 것은 "기획자, 디자이너간에 커뮤니케이션 중요하다. 서로 존중하면서 대화에 임하라."라는 것과 제작환경 구성시 "ANT, SVN은 꼭 잘 활용해라"라는 것이였습니다. 더불어 통신관련 도식화와 애플리케이션 도식화, 그리고 UML 제작을 개발하기 전에 꼭 해보라는 것이였습니다. 이러한 작업은 앞으로 문서화에도 도움이 되며 다른 사람에게 인수인계할때도 좋고요. 나중에 그 프로그램을 다시 보더라도 내가 짠 것 내가 이해 못하는 꼴이 발생하지 않습니다. ^^

 

4. 모듈 개발시 최적화 방법

SWF가 다른 SWF를 로드하고 Class를 서로 참조하는 일이 발생하는 개발을 하게 되면 모듈 프로그래밍을 한다고 할 수 있습니다. 중요한 것은 다른 SWF간에 정의된 Class중복을 어떻게 해결할 것인가였습니다. 중복 자체가 크게 문제를 일으키는 것은 아니지만 필요없는 Class를 가지고 있기 때문에 프로그램 용량이 그만큼 커집니다. 그래서 중복된 Class를 없애기 위해 RSL도 이용해보고 mxmlc 컴파일러 옵션에 -include-libraries, -load-externs, -link-report 등의 옵션을 사용하는 예도 보여드렸습니다.

 

Main애플리케이션과 모듈이 서로 같은 Class를 담고 있는 경우 그대로 로드하면 어느 한쪽의 클래스를 사용하게 되겠지요. 이때 중요한 개념이 ApplicationDomain인데 제가 이 설명을 빠뜨렸습니다.

 

이 이야기는 다음 링크를 참고하세요.

Flex 모듈 프로그래밍의 기초 - Application domain의 이해 1부

Flex 모듈 프로그래밍의 기초 - Application domain의 이해 2부

 

 

5. Flex 학습법

이 부분은 시간관계상 설명 못드렸네요.

어떻게 보면 매우 중요한 부분인데 ㅎㅎ

제가 말씀 드리고 싶었던 것은 Flex 낚는 어부가 되시라는 거였습니다. Flex라는 물고기만 계속 받아먹기만 하면 발전이 없잖아요. 그래서 Flex 낚는 어부가 되기 위해 이것만은 했으면 좋겠다는 내용이였죠. 설명 못드려 아쉽네요. 시간조정을 못한 제 불찰입니다. ㅜㅜ

 

 

아무튼 짧았지만 좋은 시간이였던 것 같습니다. 제가 발표했던 자료는 아래 링크를 통해 받아가세요.

 

 

다른 분의 자료는 [여기]에서 받아가세요.

 

이번 세미나를 개최하시느라 고생하신 옥상훈(http://okgosu.tistory.com)님 수고 많으셨고요.

주최에 도움을 줬던 데브멘토(http://devmento.co.kr) 관계자 분들도 수고 많으셨습니다.

 

발표자였던 오창훈(http://lovedev.tistory.com/), 이정웅(http://bluemetal.tistory.com/)님도 수고했어요.

 

저는 항상 열려있습니다.

저처럼 바보같은 질문도 괜찮습니다. 반대로 제가 답변 못하더라도 전 부끄럽지 않습니다.

오히려 몰랐던 것을 알게 되니 좋은 것이지요. ^^

대한민국 개발자들이 서로 소통하는데 힘써주셨으면 합니다.

 

 

 

 

 

스타플(starpl.com)에서 별에서 키우는 애완동물 아이템인 스타펫을 출시했습니다. 별에서 키우는 애완동물인 만큼 외계생물이겠군요. ^^;

 

밥은 별가루예요. 별가루는 열심히 활동하는 사람에게 준답니다. 별가루로 각종 아이템, 위젯, 별스킨 등도 구입할 수 있습니다.

 

성장과정에서 별가루를 안주면 스타펫이 배고파 죽어 유령이 되요. 성인이 될 때까지 잘 키워주셔야합니다.

 

 

 

스타펫의 알의 종류는 3가지입니다. 같은 모양의 알이더라도 별가루를 어떤 식으로 주냐에 따라 성장과정이 달라집니다.

 

그럼 스타펫을 구입하러 가볼까요?

 

스타펫 구입하기

 

 

 

아이구 우리 아가들... 배고프다고 하네요. 빨랑 별가루 줘야지~ 아빠가 이쁘게 키워줄께~ ^^

 

관련글

 

홍보동영상

 

스타플

 

 

5000m 고도에서 본 은하수(출처 & 저작권 : Serge Bruner)

 

아래 동영상은 미국 텍사스주 스타파티(star party)에서 플로리다대학 교수 william Castleman이 밤 21시 20분 부터 다음날 아침 6시 43분까지 어안렌즈를 통해 찍은 모습을 40초로 압축한 영상이다.

 

 

아름다운 은하수가 지구의 자전에 의해 동에서 서로 움직이는 모습과 간혹 별똥별(유성)의 모습이 보인다. 저런 밤하늘을 본지 꽤 오래되었는데... 언제 하루 날 잡아 별보러 가고 싶다.

 

Flex, Flash, ActionScript, AIR....

Adobe RIA 기술을 처음 접해본 이들에게 이 용어들의 구분은 매우 생소하다. 앞에 3개는 모두 Flash Player에서 돌아가는 SWF파일을 만들어주는 툴 및 언어이다. AIR는 이러한 것을 확장해서 데스크탑 영역까지 애플리케이션을 제작할 수 있는 기술이라고 할 수 있다.

 

많이 헷갈리는 것중에 하나가 바로 Flex, Flash, ActionScript 의 구분이다. ActionScript는 Flex, Flash 애플리케이션을 개발하는 가장 핵심이면서 기반이 되는 언어이다. Flex는 ActionScript 3.0을 확장해 RIA를 지원하기 위한 일종의 프레임워크이다. Flash는 ActionScript 초기 시절부터 있는 일종의 툴로서 이펙트 및 다양한 효과를 제작하는데 적절하도록 구성된다. 결국 Flex, Flash의 태생은 ActionScript 이고 어떤 목적을 가지고 제작하느냐에 따라 Flex, Flash로 구분해서 개발한다. 물론 어떤 것을 선택하든지 결과물은 SWF이다.

 

재미있는 사실은 Flash는 뭔가 디자인 중심적으로 느껴지는 반면, Flex는 개발적이고 기술적으로 여겨는 풍토가 있다. Flash 개발자라고 말하는 것보다 Flex 개발자라고 하면 연봉협상에도 유리할지 모른다. 하지만 Flash 개발이든 Flex 개발이든 어떤 애플리케이션을 만드냐에 따라 선택이 달라지는 것이지 개발의 질이 달라지는 것은 절대 아니다. 여기서 선택이라는 것은 가령 이렇다. 기업솔루션을 구축해야하는 상황이면 서버 구성 및 데이타 통신, 데이터 관리, 각종 컴포넌트를 아낌없이 지원해주는 Flex로 개발하는 것이 좋다. 하지만 Google Map과 같이 Flex 컴포넌트를 전혀 사용하지 않는다면 Flash 기반이나 순수 ActionScript 기반으로 개발하는 것이 맞다.

 

Adobe에서 Flex Builder 3의 다음 버전을 Flex가 아닌 Flash Builder 4로 지정하기로 했다. Flash Builder 4는 현재 Flash CS4와 같은 툴이 아니라 지금의 Flex Builder와 같이 Eclipse기반으로 만들어진 툴이다. 또한 Flex 4 SDK(현 Flex Gumbo)를 기반으로 Flex 개발환경을 지원한다. 결국 그런거다. Adobe RIA기술의 핵심은 Flash다. 그리고 Flex도 결국 Flash다. 다만 Flex SDK를 기반으로 제작하면 Flex SDK로 만들어진 Flash 애플리케이션인 것이다.

 

차기 버전이 Flex Builder 4 대신 Flash Builder 4로 지정한 것은 여러모로 바람직해 보인다. 일단 같은 SWF 결과물을 만들어 준다는 암시를 지니기 때문에 의미전달이 쉬워진다. 겉모습은 전부 Flash이지만 개발방법은 선택하기 나름인 것이다. Flex SDK를 사용하면 그 사람은 여전히 Flex 개발자인 것이다. 앞으로 디자이너와 개발자간에 협력 도구인 Flash Catalyst는 Flash Builder와 매우 자연스럽게 연동이 된다. 그런데 어떤것은 Flex, 어떤 것은 Flash, 이렇게 되면 뭔가 섞일 수 없는 느낌이 든다.

 

2009년 6월 쯤에 1차 베타버전이 나오고 4분기면 정식 버전이 출시될 것이다. 아무튼 기존 보다 개발의 편의성을 극대화 시킬 수 있는 Flash Builder 4가 나오길 기대한다.

 

이것만은 기억하자.

이제 더 이상 Flex는 Flex Builder를 지칭하지 않는다. Flex는 Flex SDK를 지칭하며 그것을 이용해서 개발하는 툴은 Flash Builder 4인 것이다.

Flash Builder 4는 다음 링크를 통해 다운받으세요.
http://www.adoberia.co.kr/pds/down.html?src=text&kw=000026 

구글 애플리케이션 엔진(Google App Engine)에 대해서 들어본 적 있는가? 구글 애플리케이션 엔진은 구글의 제공하는 통합개발환경이다. 플랫폼에 제한 받지 않고 구글의 자원을 적극적으로 활용해서 웹애플리케이션을 만들 수 있도록 한다.

 

 

구글 애플리케이션 엔진을 이용하여 개발한 Ajax API Playground라고 명명된 애플리케이션이 내 눈을 사로잡았다. 이것은 구글에서 제공하는 Open API를 사용하는 예제를 총 망라해서 보여주는 프로그램이다. 한눈에 Google Open API 사용법을 볼 수 있기 때문에 개발자들에게 매우 도움이 된다. 현재 Ajax API Playground에서는 Lanaguage, Blogger, Calendar, Earch, Feeds, Friend Connect, Maps, Libraries, Search, Visualization, YouTube등의 Open API를 사용하는 예제가 있다. 매우 훌륭한 점은 Edit HTML 기능을 통해 직접 코딩도 해볼 수 있다는 점이다. Open API를 사용하려면 해당 라이브러리를 다운받고 이것저것 환경설정하고 Key도 발급받아야하는데 이런 과정이 전혀 없이 테스트해볼 수 있으니 실로 놀랍다.

 

이미 오래전에 나왔던 것인데 이제서야 발견했다. RSS 구독하면서도 왜 이걸 못봤는지… ㅡㅡ;; 정보에 좀 민감해야하겠다는 생각이 든다.

 

이런 비슷한 것으로 ActionScript 3.0 코드를 웹에서 직접 코딩하고 하나의 포트폴리오로 제작할 수 있는 Wonderfl 서비스가 있다.

 

 

 

Flex에서는 Tour de Flex라는 흥미로운 서비스가 있는데 Flex에서 제공하는 컴포넌트를 사용하는 예제가 한페이지 안에 담겨있다. 하지만 Ajax API Playground나 Wonderfl 처럼 직접 코딩해보지는 못한다.

 

 

오랜만에 너무 놀랍고 흥미로운 기술을 보게 되어 흥분된다.

 

 

관련글

Flex Builder에서 라이브러리 프로젝트를 구성하다가 컴파일시 "unable to export SWC oem" 에러가 발생하며 SWC를 생성하지 못하는 경우가 종종 발생한다. 대부분의 경우, 라이브러리 빌드 Assets 경로에 등록한 파일의 경로가 강제로 수정되거나 삭제되었을 때 발생한다. 이 에러를 해결하기 위해 다음과 같이 진행해보자.

 

이 Assets 경로는 SWC로 만들때 필요한 자원을 포함하기 위한 것으로 comps 컴파일러의 옵션중에 -include-file 을 주는 것과 동일하다.

 

만약 존재하는 asset이 Assets 리스트에 존재하는데도 불구하고 에러가 발생했다면

 

1. Project 메뉴 > Properties > Flex Library Build Path > Assets 탭

2. asset을 uncheck한다.

3. OK버튼을 누른다.

4. 다시 똑같이 돌아가서 해당 asset을 re-check한뒤 OK버튼을 누른다.

 

 

만약 존재하지 않는 asset이 Asset 리스트에 등록되어 있어 에러가 발생했다면 임시로 해당 asset을 추가하는 방법으로 해결한다.

 

1. New > File 에서  하나의 파일을 만든다.

2. Project 메뉴 > Properties > Flex Library Build Path > Assets 탭

3. 새로 만든 파일을 check한다.

4. OK 버튼을 누른다.

5. 다시 돌아가 새로운 파일을 uncheck한 뒤 그것을 삭제한다.

 

 

같은 에러가 발생한 분들에게 도움이 되었으면 한다. ^^

대부분의 Adobe RIA관련 개발자들은 AIR, Flex, Flash 애플리케이션을 개발하기 위해 Flash IDE또는 Flex Builder를 많이 사용할 것 입니다. 또는 FDT(Flash Development Tools)라는 것도 쓰고요. 그런데 이들 툴의 단점은 유료라는 겁니다.

 

이런 툴이 없어도 Flex, AIR의 경우 개발이 가능하다만 개발툴없이 개발하는 것은 그만큼 생산력이 떨어지지요.

 

지금 소개하는 FlashDevelop은 Flash 기반 개발툴입니다. 중요한 것은 무료인데다가 개발도 큰 불편함이 없다는 장점이 있습니다. 또한 수시로 업데이트되어 관리가 되는 툴입니다. FlashDevelop은 일단 Flex Builder나 FDT에 비해 가볍고, AS2, AS3 환경에서도 모두 개발이 가능하다는 장점이 있습니다. 몇가지 기능적 제한(?)이 있을지 모르겠지만 개발하는데 크게 무리가 없다는 생각이 듭니다. 제게 필요한 ANT 개발 및 SVN 소스 관리가 되는지는 잘 모르겠군요.

 

우리나라에서도 FlashDevelop을 이용해 개발하시는 분들이 꽤 있나봅니다. Naver 카페에 "FlashDevelop을 쓰는 사람들"이 만들어졌더군요. 올해 4월 30일에 만들어졌으니 1개월도 안된 신생 카페입니다. 그래도 벌써 회원수가 100명이 넘었습니다. FlashDevelop에 대한 정보를 공유하고자 하는 사람에게 매우 유용할 것 같습니다. 필자도 FlashDevelop를 많이 써본적 없고 관련 정보에 매말라 있었는데 이렇게 좋은 카페가 생겨서 흐뭇하네요. 기회가 닿으면 FlashDevelop으로도 개발해보고 싶네요.

 

좋은 카페를 만들어 주신 재규어님께 감사합니다. ^^

 

 

 

Essensial ActionScript 3.0(이하 EAS)책에 보면 Stage의 invalidate() 메소드와 Event.RENDER의 활용법이 나와있다.(한국어 판이 나올 예정이니 기대가 크다. ^^)

 

invalidate() 메소드는 Event.RENDER 이벤트를 발생해주는 역할을 한다. 이것을 적절하게 사용하면 화면갱신을 필요할 때만 적절하게 수행할 수 있기 때문에 애플리케이션의 전체적인 퍼포먼스를 향상할 수 있게 된다. Event.RENDER 이벤트는 invalidate() 메소드의 호출 외에도 MouseEvent, KeyboardEvent등의 updateAfterEvent() 메소드를 호출해도 발생한다.

 

EAS 23장에 Event.RENDER를 이용한 최적화에서 최종 예를 보면 다음과 같다.(올려도 되는건가??)

 

package {
 import flash.display.Shape;
 import flash.events.*;

 public class Ellipse extends Shape {
  private var w:Number;
  private var h:Number;
  private var changed:Boolean;

  public function Ellipse (width:Number, height:Number) {
   var stageDetector:StageDetector = new StageDetector(this);
   stageDetector.addEventListener(StageDetector.ADDED_TO_STAGE,
                  addedToStageListener);
   stageDetector.addEventListener(StageDetector.REMOVED_FROM_STAGE,
                  removedFromStageListener);

   w = width;
   h = height;

   setChanged( );
  }

  public function setWidth (newWidth:Number):void {
   w = newWidth;
   setChanged( );
  }

  public function getWidth ( ):Number {
   return w;
  }

  public function setHeight (newHeight:Number):void {
   h = newHeight;
   setChanged( );
  }

  public function getHeight ( ):Number {
   return h;
  }

  private function setChanged ( ):void {
   changed = true;
   if (stage != null) {
    stage.invalidate( );
   }
  }

  private function clearChanged ( ):void {
   changed = false;
  }

  protected function hasChanged ( ):Boolean {
   return changed;
  }

  private function addedToStageListener (e:Event):void {
   stage.addEventListener(Event.RENDER, renderListener);

   if (hasChanged( )) {
    stage.invalidate( );
   }
  }

  private function removedFromStageListener (e:Event):void {
   stage.addEventListener(Event.RENDER, renderListener);
  }

  private function renderListener (e:Event):void {
   if (hasChanged( )) {
    draw( );
   }
  }

  private function draw ( ):void {
   graphics.clear( );
   graphics.lineStyle(1);
   graphics.beginFill(0xFFFFFF, 1);
   graphics.drawEllipse(0, 0, w, h);
  }
 }
}

 

왜 저렇게 만들었는가 이유를 알고 싶다면 직접 분석하거나 EAS를 구입해보는 것이 좋겠다. 이것만은 기억하자 저렇게 만든 이유는 witdh, height가 바뀔때마다 draw() 메소드가 호출되어 쓸데없이 렌더링되는 것을 방지하기 위함이다.

 

본인의 생각에 위 코드의 최적화 부분에서 약간 문제점이 있다는 생각이 든다. 그것은 Ellipse객체가 Stage에 추가될 때 호출되는 addedToStageListener()내에 정의된 stage.addEventListener(Event.RENDER, renderListener)이 있는 부분이다.

 

이는 만약 100개의 Ellipse객체가 Stage에 추가된 경우 100개의 renderListener()이 등록된 것과 동일하다. Event.RENDER 이벤트가 발생할 때 renderListener()에서 hasChanged()메소드를 이용해 변경해야하는 경우에만 draw()를 호출한다고 하지만 그래도 여전히 100개의 renderListener() 이벤트 청취자 함수가 호출되는 것은 마찬가지이다.

 

렌더링하는 시점에만 stage.addEventListener(Event.RENDER, renderListener)로 renderListener() 메소드를 등록하고 renderListener()이 호출되면 stage.removeEventListener(Event.RENDER, renderListener)를 실행하면 쓸데없이 renderListener() 메소드가 호출되는 것을 막을 수 있지 않을까? 가령, 1개의 Ellipse객체만 다시 그려야하는 경우 그 객체의 renderListener() 메소드만 호출하면 되기 때문에 전체 퍼포먼스를 향상하는데 더 좋을 것이다. 그래서 본인은 위 Ellipse 클래스를 아래처럼 수정해봤다.

 

 

package {
 import flash.display.Shape;
 import flash.events.*;

 public class Ellipse extends Shape {
  private var w:Number;
  private var h:Number;
  private var changed:Boolean;

  public function Ellipse (width:Number, height:Number) {
   var stageDetector:StageDetector = new StageDetector(this);
   stageDetector.addEventListener(StageDetector.ADDED_TO_STAGE,
                  addedToStageListener);
   stageDetector.addEventListener(StageDetector.REMOVED_FROM_STAGE,
                  removedFromStageListener);

   w = width;
   h = height;

   setChanged( );
  }

  public function setWidth (newWidth:Number):void {
   w = newWidth;
   setChanged( );
  }

  public function getWidth ( ):Number {
   return w;
  }

  public function setHeight (newHeight:Number):void {
   h = newHeight;
   setChanged( );
  }

  public function getHeight ( ):Number {
   return h;
  }

  private function setChanged ( ):void {
   changed = true;
   if (stage != null) {
    stage.addEventListener(Event.RENDER, renderListener, false, 0, true);
    stage.invalidate( );
   }
  }

  private function clearChanged ( ):void {
   changed = false;
    stage.removeEventListener(Event.RENDER, renderListener);
  }

  protected function hasChanged ( ):Boolean {
   return changed;
  }

  private function addedToStageListener (e:Event):void {
   if (hasChanged( )) {
		stage.addEventListener(Event.RENDER, renderListener, false, 0, true);
    stage.invalidate( );
   }
  }

  private function removedFromStageListener (e:Event):void {
   clearChanged();
  }

  private function renderListener (e:Event):void {
   if (hasChanged( )) {
    clearChanged();
    draw( );
   }
  }

  private function draw ( ):void {
   graphics.clear( );
   graphics.lineStyle(1);
   graphics.beginFill(0xFFFFFF, 1);
   graphics.drawEllipse(0, 0, w, h);
  }
 }
}

 

자, 위처럼 하면 필요할때만 renderListener()를 호출하므로 더 최적화 되었다고 할 수 있겠다.

 

이 방식을 응용하여 Sprite를 확장해서 필요할 때만 렌더링해주는 Flex의 UIComponent와 같은 추상클래스(?)를 만들어주면 어떨까?

 

이름은 flash.display.UISprite로 하고 그에 대한 인터페이스는 flash.display.IUISprite로 정했다. UISprite에는 Flex의 UIComponent에서 제공하는 invalidateProperties(), invalidateDisplayObject()를 Event.RENDER를 이용해 흉내낸 클래스이다. Flex SDK를 활용하지 않고 개발할 때 이 클래스는 최적화된 Rendering을 하는데 도움을 줄 수 있을 것이다.

 

 

package flash.display
{
	import flash.display.Sprite;
	import flash.events.Event;

	/**
	 * ActionScript 3.0 기반으로 만드는 프로젝트의 시각화 객체를 사용해야할 경우 이것을 확장해서 사용하도록 한다.
* 사용법은 Flex의 UIComponent와 흡사하다. * * @author Yongho, Ji (jidolstar@gmail.com) * @since 2009.05.12 */ public class UISprite extends Sprite implements IUISprite { private var isInvalidateProperties:Boolean = true; private var isInvalidateDisplayObject:Boolean = true; private var isCreatedChildren:Boolean = false; private var isCreationComplete:Boolean = false; public function UISprite() { super(); this.addEventListener( Event.ADDED_TO_STAGE, onAddedToStage, false, 0, true ); this.addEventListener( Event.REMOVED_FROM_STAGE, onRemovedFromStage, false, 0, true ); } /** * 고정적인 시각화 객체를 추가할 때 사용하는 함수로 Override 해서 사용한다. */ protected function createChildren():void { } /** * 속성이 변경되었을 때 처리하는 함수로 Override 해서 사용한다. */ protected function commitProperties():void { } /** * 시각화 객체가 새로 렌더링할때 사용하는 함수로 Overide해서 사용한다. */ protected function updateDisplayObject():void { } /** * 라이프 사이클이 모두 완료되면 이 함수가 호출된다. * 단 한번만 호출되는데 필요할 때 Override해서 사용하면 되겠다. */ protected function creationComplete():void { } /** * @inheritDoc */ public function validateProperties():void { isInvalidateProperties = true; commitProperties(); } /** * @inheritDoc */ public function validateDisplyObject():void { isInvalidateDisplayObject = true; updateDisplayObject(); } /** * @inheritDoc */ public function invalidateProperties():void { if( isInvalidateProperties ) return; isInvalidateProperties = true; invalidate(); } /** * @inheritDoc */ public function invalidateDisplayObject():void { if( isInvalidateDisplayObject ) return; isInvalidateDisplayObject = true; invalidate(); } /** * 무효화 */ protected function invalidate():void { if( stage != null ) { stage.addEventListener( Event.RENDER, onRender, false, 0, true ); stage.invalidate(); } } /** * Stage에 추가 되었을때 처리 */ private function onAddedToStage( event:Event ):void { //자식 추가, 처음에만 한번 호출됨 if( !isCreatedChildren ) { isCreatedChildren = true; createChildren(); } //무효화 처리 if( isInvalidateProperties || isInvalidateDisplayObject ) { invalidate(); } } /** * Stage에서 제거 되었을 때 처리 */ private function onRemovedFromStage( event:Event ):void { if( stage != null ) { stage.removeEventListener( Event.RENDER, onRender ); } } /** * Render 이벤트 처리 */ private function onRender( event:Event ):void { if( stage != null ) { stage.removeEventListener( Event.RENDER, onRender ); } //commit properties if( isInvalidateProperties ) { isInvalidateProperties = false; commitProperties(); } //update display object if( isInvalidateDisplayObject ) { isInvalidateDisplayObject = false; updateDisplayObject(); } //creation complete. call only once if( !isCreationComplete ) { isCreationComplete = true; creationComplete(); } } /** * @inheritDoc */ public function get childrenCreated():Boolean { return isCreatedChildren; } /** * @inheritDoc */ public function get creationCompleted():Boolean { return isCreationComplete; } /** * @private */ override public function set width(value:Number):void { super.width = value; invalidateDisplayObject(); } /** * @private */ override public function set height(value:Number):void { super.height = value; invalidateDisplayObject(); } /** * @private */ override public function set scaleX(value:Number):void { super.scaleX = value; invalidateDisplayObject(); } /** * @private */ override public function set scaleY(value:Number):void { super.scaleY = value; invalidateDisplayObject(); } /** * override해서 trace문을 대신 정의해서 사용한다. * * @example * * override protected function TRACE( ...args ):void * { * trace( prototype.constructor.toString(), args ); * } * */ protected function TRACE( ...args ):void { //trace( prototype.constructor.toString(), args ); } } }

 

UISprite는 검증되지 않았고 프로젝트에 적용하기 위해 테스트 단계에 있다.

 

전체 소스는 아래서 다운로드 받는다.

 

 

이 클래스에 대한 문제점 및 다른 의견이 있다면 언제든지 댓글 및 피드백을 걸어주었으면 한다.


페르시아 왕자를 Flash 버전으로 만든겁니다. 제가 만든게 아니라요 ㅎㅎ
우비소프트에서 개발했다고 하네요.
동작방식은 기존 페르시아 왕자 1과 비슷하지만 맵이 다릅니다.
또한 기존보다 더 어렵더군요.

개발은 어떻게 했을까요?
일반 백터 방식으로 하지 않고 순수 BitmapData만 가지고 처리하도록 만들었을 겁니다. 그말은 DisplayObject객체는 거의 사용하지 않았다는 의미입니다. 그래야 저런 그래픽이 나오겠죠.

어쨌든 한번 과거 페르시아 왕자에 푹 빠져보세요.


 

오늘 ACC 오픈 세미나가 열립니다. ACC는 Adobe Community Champion의 약어로 Adobe Flash Platform 관련 전문인들로 구성된 그룹(현 23명)을 말하는 것입니다. 이들이 주최가 되는 오픈 세미나입니다.

 

오늘( 5월 16일 토요일 ) 오후 1:00~6:00 까지 강남교보타워 23층에서 열립니다.

 

이번에 특별한 활동은 ACC릴레이발표와 OST 주제별 토론입니다.

 

이번 ACC 릴레이 발표는 효율적인 통신을 위한 LCDS와 HTML로 만드는 Adobe AIR 애플리케이션이라는 주제로 발표될 것입니다.

 

OST(Open Source Technology)는 이번 세미나에 참석한 모든 분들이 함께 자유로운 주제를 가지고 토론하거나 기술을 공유하는 자리입니다. 이전 세미나가 1:n 으로 진행되었다면 이번 세미나는 n:n이 될 수 있겠네요. 참 좋은 시도일 것 같습니다.

 

저도 ACC에 속해서 오늘 가게 됩니다.

오시는 분들과 좋은 시간을 가졌으면 좋겠네요. ^^

 

ACC 오픈 세미나 공지

ACC 소개

 

아래 링크는 각 OS별 ANT로 Web Browser를 구동하는 방법이 소개하고 있다.

 

Launching Firefox from ANT on OSX

 

 

관련글

Flex Builder 3에서 ANT 사용하기

Flex Builder 3에서 ANT로 FTP를 사용하는 방법 소개

 

 

 

Flex Builder 3에서 ANT로 원하는 파일을 FTP로 전송하는 방법에 대해서 소개한다.

 

참고로 Flex Builder에서 ANT 환경을 구성하는 방법은 필자가 이전에 작성한 “Flex Builder 3에서 ANT 사용하기”를 읽어보면 되겠다.

 

ANT 환경에서 FTP를 이용하는 경우는 필자의 경우 asdoc등으로 만들어진 문서를 웹에 배포할 때였다. FTP는 ANT에서 기본적으로 제공하는 명령이 아니기 때문에 추가적으로 라이브러리를 설치해야 한다. 이 라이브러리는 Apache Jakarta프로젝트에서 개발한 Commons Net를 사용하면 된다.

 

FTP작업을 위한 환경 만들기

다음과 같은 순서로 환경을 만든다.

  1. Commons Net 2.0 Binary버전을 다운받아 압축을 푼다. 그리고 해당 폴더를 관리하기 편한 위치에 옮겨둔다.
  2. Flex Builder의 메뉴에서 Windows > Perferences를 선택하면 아래와 같은 창이 나온다. 왼쪽 메뉴에 Ant > Runtime을 선택한다. 그리고 Classpath 탭을 누르고 Ant Home Entries를 선택한다. 오른쪽에 Add External JARs… 버튼을 눌러 Commons Net안에 commons-net-2.0.jar와 commons-net-ftp-2.0.jar을 추가하도록 한다.

 

 

FTP 전송을 위한 ANT Script 작성

ANT 스크립트를 아래와 같이 작성한다.

 

<ftp server="${ftp.server}"
port="${ftp.port}"
remotedir="${ftp.dir}"
userid="${ftp.userid}"
password="${ftp.password}"
depends="yes"
binary="no"
>
<fileset dir="{dir.docs}">
<include name="**/*.html"/>
</fileset>
</ftp>

 

위 스크립트를 실행하면 아래처럼 비슷하게 Flex Builder 3의 Console창에 출력하며 전송이 될 것이다.

Buildfile: D:\STARPL_DEV_02\Map\mapdev3\20090421\as3utils\build\as3utils.build.xml
asdoc.ftp:
[ftp] sending files
[ftp] transferring D:\STARPL_DEV_02\Map\mapdev3\20090421\as3utils\docs\all-classes.html
[ftp] transferring D:\STARPL_DEV_02\Map\mapdev3\20090421\as3utils\docs\all-index-A.html
[ftp] transferring D:\STARPL_DEV_02\Map\mapdev3\20090421\as3utils\docs\all-index-B.html
[ftp] transferring D:\STARPL_DEV_02\Map\mapdev3\20090421\as3utils\docs\all-index-C.html

[ftp] transferring D:\STARPL_DEV_02\Map\mapdev3\20090421\as3utils\docs\style.css
[ftp] transferring D:\STARPL_DEV_02\Map\mapdev3\20090421\as3utils\docs\title-bar.html
[ftp] 75 files sent
BUILD SUCCESSFUL
Total time: 15 seconds

 

FTP 사용시 각종 옵션은 http://ant.apache.org/manual/OptionalTasks/ftp.html 를 참고한다.

 

 

+ Recent posts