'Programming'에 48개의 글이 있습니다.

  1. 2014.04.09iOS 7.0 상단/하단 드래그시 컨트롤센터 메뉴 프로그램 코드로 제어하기
  2. 2013.07.05베지어 곡선 (Bezier curves)
  3. 2013.06.242D 회전행렬 좌표
  4. 2013.06.18PHP+MySQL을 이용한 웹게임 개발
  5. 2013.05.31C# Attempted to read or write protected memory 에러
  6. 2013.05.29RGB 색상표 (16진수) 변환
  7. 2013.05.28XNAExtras로 XBOX360에 폰트를 출력해 보자!! #1
  8. 2013.05.28C# StreamReader 에서의 한글 Encoding 문제
  9. 2013.05.08[XNA] Scrolling background images horizontally across the screen
  10. 2013.05.08[XNA] A Simple 2D Camera

iOS 7.0 상단/하단 드래그시 컨트롤센터 메뉴 프로그램 코드로 제어하기

posted-at2014.04.09 18:51 :: posted-inProgramming/iOS/iPhone :: posted-byNarrL

최근에 OpenGLES2.0 을 사용해서 게임을 개발하고 있는데, 게임화면 윗부분이나 아랫부분을 드래그 할때마다 컨트롤 센터로 연결되는 메뉴가 나오면서 opengl 프레임 드랍 현상이 발생하여 골머리를 썩었다.


안드로이드를 자주 다뤄본 필자로써는 당연히 이 기능을 어플 상에서 제어할 수 있으리라 생각했지만..

구글링 해본 결과, 프로그램 코드로 이것을 제어하는건 불가능하다. 하여간 애플의 이런 고집은 도무지 이해할 수가 없다.

어떻게든 편법이라도 찾아보려 했으나 없는 듯 하니, 게임의 터치가 이 부분과 충돌되는 경우엔 유저에게 옵션에서 이 기능을 끄도록 고지하는 수 밖에 없다.


그래도 최소한의 처리는 해놔야 할 것 같아서, Status Bar 라도 끄기로 했다.


* 최상위 ViewController에 함수 추가

- (BOOL)prefersStatusBarHidden
{
    return YES;
}


* 최상위 ViewController의 viewDidLoad에 추가

if ([self respondsToSelector:@selector(setNeedsStatusBarAppearanceUpdate)])
{
    // iOS 7
    [self prefersStatusBarHidden];
    [self performSelector:@selector(setNeedsStatusBarAppearanceUpdate)];
}
else
{
    // iOS 6
    [[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:UIStatusBarAnimationSlide];
}


* Info.plist 에 아래 추가

<key>UIStatusBarHidden</key>
<true/>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>




번외. ios 7.0 옵션에서 상단, 하단 탭메뉴 (컨트롤 센터) 끄기

[설정 -> 제어 센터]

에서 아래 사진처럼 끄면 된다.

위에껀 잠금화면에서, 밑에건 어플 상에서 기능 사용 여부




베지어 곡선 (Bezier curves)

posted-at2013.07.05 11:46 :: posted-inProgramming :: posted-byNarrL

베지어 곡선

Bézier curves


Written by Paul Bourke
Original: April 1989, Updated: December 1996 




이 문서는 베지어 곡선이라는 특수한 곡선에 대한 수학적인 설명을 담고 있습니다. 이 곡선의 이름은, 1970년대 Renault car를 디자인하는 과정에서 이 곡선을 사용했던 Pierre Bézier라는 프랑스 엔지니어의 이름을 따서 만들어졌습니다. 그때부터 이 곡선은 식자 산업(typesetting industry)에서 절대적인 우위를 점하게 되었고, 특히 Adobe Postscript, 폰트 제품 등에서 널리 사용되었습니다.

3차원 공간에 있는 N+1개의 조절점(control point) pk (k=0,1,...,N)를 생각해봅시다. 베지어 parametric 곡선 공식은 다음과 같습니다.

B(u)는 서로 다른 위치에 있는(discrete) N개의 조절점에 의해 얻어지는 곡선을 구하기 위한 연속함수입니다. u=0이면 첫번째 조절점(k=0)에, u=1이면 마지막 조절점(k=N)에 도달합니다. 아래는 N=4, 즉 조절점이 4개인 경우의 곡선 모습입니다.
역자 주 : 첫번째 조절점이 시작점, 마지막 조절점이 끝점이라고 생각하시면 쉽겠죠? ^_^ 
그리고, N개의 조절점이 아니라 N+1의 조절점이라고 해야 하는 듯. 
나중에 나오지만, 조절점의 위치가 모두 각각 다를 필요는 없습니다.

주의점:

  • 이 베지어 곡선은, 조절점 중 처음과 끝 점을 빼고는 보통 어느 조절점과도 만나지 않습니다. 공식을 사용하면 B(0) = P0 이고 B(1) = PN 이 되겠지요.

  • 곡선은, 항상 조절점으로 이루어진 다각형 안에 들어가게 됩니다. 곡선이 조절점들 사이에서 크게 벗어나거나 하는 일은 없습니다.

  • 조절점이 P0 하나뿐이라면, 즉 N=0 이라면 모든 u에 대해 B(u) = P0 입니다. (역자 주 : u는 0이상 1 이하의 실수)

  • 조절점이 P0 , P1 두개뿐이라면, 즉 N=1이라면 이 공식은 두 점 사이를 잇는 선분을 만들어냅니다.


  • 라는 부분은 blending 함수라고 하는데, 이 부분에서 조절점들을 blend해서 곡선을 생성하기 때문입니다.

  • blending 함수는 조절점의 갯수보다 차수가 하나 낮은 다항함수입니다. 예를 들어, 3개의 조절점이 있다면 베지어 곡선은 포물선을 그리고, 4개의 조절점으로는 3차곡선을 얻게 됩니다.

  • 첫번째 조절점의 위치와 마지막 조절점의 위치가 같을 경우 폐곡선이 생깁니다. 이때 처음의 두 조절점 사이의 접선(tangent)과 끝의 두 조절점의 접선이 같을 경우, first order continuity가 가능합니다. (역자 주 : P0 - P1 간의 기울기와 PN-1 - PN 간의 기울기가 같을 경우..라고 보시면 되겠습니다. first order continuity는, 두 곡선의 (1계)도함수가 같은, 즉 접선의 기울기가 서로 같은 상태를 의미합니다.) 

  • 비슷한 위치에 여러 조절점이 있을 경우, 그쪽으로 베지어 곡선을 "당기는" 정도가 증가합니다.

  • 조절점의 갯수가 많아지면, 좀더 높은 차수의 식과, 많은 횟수의 팩토리얼 값을 계산해야 합니다. 그래서 보통 긴 곡선을 만들 때는, 그 곡선을 다시 여러 곡선으로 작게 쪼개는 방법을 씁니다. 이렇게 하면, 전체적인 모습에 변화를 주지 않고도 곡선의 부분적인 형태를 쉽게 바꿀 수 있는 효과도 얻게 됩니다. 물론 곡선이 첫번째 조절점에서 시작해서 마지막 조절점에서 끝나기 때문에, (나눠진) 곡선 조각들을 이어붙이기는 쉽습니다. 또한 베지어 곡선에서는 마지막 점에서의 접선이 마지막 두 조절점을 이은 선과 같기 때문에, (나누어진 곡선들의) 첫번째 조절점의 기울기도 맞출 수 있습니다. 
    Second order continuity는 보통 불가능합니다. (역자 주 : Second order continuity는, 이계도함수의 값이 같을 때의 상태입니다. 즉, 곡선의 변화율이 같다는 뜻입니다.) 

  • 조절점이 2개인 특수한 경우(직선)을 제외하고는, 한 베지어 곡선에 평행한 다른 베지어 곡선을 유도(derive)해내는 것은 불가능한 것으로 알려져 있습니다.

  • 베지어 곡선으로 원을 완벽하게 표현할 수는 없습니다.

  • coincident parallel curves나 직선인 베지어 곡선의 경우를 제외하고는, 한 베지어 곡선에 평행한 베지어 곡선을 만드는 것은 불가능합니다.

  • 조절점이 3개일 경우는 공식이 다음과 같이 정리됩니다 : 

    B(u) = P0 * ( 1 - u ) 2 + P1 * 2 * u ( 1 - u ) + P2 u2

  • 조절점이 4개일 경우는 공식이 다음과 같이 정리됩니다 : 

    B(u) = P0 * ( 1 - u )3 + P1 * 3 * u * ( 1 - u )2 + P2 * 3 * u2 * ( 1 - u ) + P3 * u3

베지어 곡선은 굉장히 안정적인 형태를 만들고, 계산하기도 쉽기 때문에 널리 쓰이고 있습니다. 이런 형태의 베지어 곡선 말고도 조금 다른 모습을 만들어 내는, 또다른 베지어 곡선 공식이 있습니다. 대체로 모습은 비슷하지만, 곡선이 각 조절점을 모두 통과한다는 점이 다릅니다. Spline 곡선을 참조하세요.



예제

분홍색 선이 조절점들을 이은 선이고, 회색 선이 베지어 곡선입니다.

베지어 곡선을 표현하는 다항식의 차수는 조절점의 갯수보다 하나 낮기 때문에, 조절점이 세 개인 이 경우에는 2차곡선이 만들어집니다. 두 곡선의 조절점의 모양이 대칭을 이루면, 곡선 또한 항상 대칭입니다.

곡선은 항상 끝점을 지나며, 처음의 두 점을 이은 선에 접하고 끝의 두 점을 이은 선에도 접합니다. 이 성질로 인해, first order continuity를 유지하면서 여러 베지어 곡선을 이을 수 있게 됩니다.

곡선은 항상 조절점들로 이루어진 볼록다각형(convex hull) 안에 있게 됩니다. 그래서 곡선은 "안정적이고", 엉뚱하게 튀어나가는 일이 없습니다.

시작점과 끝점을 갖게 했을때 곡선이 생깁니다. 시작점과 끝점의 접선(tangents)이 일치할 경우 곡선은 first order continuity를 유지하면서 닫히게 됩니다. 또, 조절점을 한 곳에 여러번 지정하게 되면, 그 쪽으로 곡선이 쏠리게 됩니다.



C source

typedef struct XYZ

{

    double x;

    double y;

    double z;

}XYZ;

 

/*

조절점이 3개인 베지어 곡선 예제

mu는 0(곡선의 시작)부터 1(곡선의 끝) 사이의 값을 가질 수 있습니다.

*/

XYZ Bezier3(XYZ p1,XYZ p2,XYZ p3,double mu)

{

    double mum1,mum12,mu2;

    XYZ p;

    mu2 = mu * mu;

    mum1 = 1 - mu;

    mum12 = mum1 * mum1;

    p.x = p1.x * mum12 + 2 * p2.x * mum1 * mu + p3.x * mu2;

    p.y = p1.y * mum12 + 2 * p2.y * mum1 * mu + p3.y * mu2;

    p.z = p1.z * mum12 + 2 * p2.z * mum1 * mu + p3.z * mu2;

    return(p);

}

 

/*

조절점이 4개인 베지어 곡선 예제

mu는 0(곡선의 시작)부터 1(곡선의 끝) 사이의 값을 가질 수 있습니다.

*/

XYZ Bezier4(XYZ p1,XYZ p2,XYZ p3,XYZ p4,double mu)

{

    double mum1,mum13,mu3;

    XYZ p;

    mum1 = 1 - mu;

    mum13 = mum1 * mum1 * mum1;

    mu3 = mu * mu * mu;

    p.x = mum13*p1.x + 3*mu*mum1*mum1*p2.x + 3*mu*mu*mum1*p3.x + mu3*p4.x;

    p.y = mum13*p1.y + 3*mu*mum1*mum1*p2.y + 3*mu*mu*mum1*p3.y + mu3*p4.y;

    p.z = mum13*p1.z + 3*mu*mum1*mum1*p2.z + 3*mu*mu*mum1*p3.z + mu3*p4.z;

    return(p);

}

 

/*

일반적인 베지어 곡선 예제

조절점의 갯수는 n+1개가 됩니다.

0 <= mu < 1 *중요!* 마지막 조절점은 계산하지 않습니다.

*/

XYZ Bezier(XYZ *p,int n,double mu)

{

    int k,kn,nn,nkn;

    double blend,muk,munk;

    XYZ b = {0.0,0.0,0.0};

    muk = 1;

    munk = pow(1-mu,(double)n);

    for (k=0;k<=n;k++)

    {

        nn = n;

        kn = k;

        nkn = n - k;

        blend = muk * munk;

        muk *= mu;

        munk /= (1-mu);

        while (nn >= 1) {

            blend *= nn;

            nn--;

            if (kn > 1) {

                blend /= (double)kn;

            kn--;

            }

            if (nkn > 1) {

                blend /= (double)nkn;

                nkn--;

            }

        }

        b.x += p[k].x * blend;

        b.y += p[k].y * blend;

        b.z += p[k].z * blend;

    }

    return(b);

}




  • FAQ

    > 1. SpLines 코드 - 코드를 이해하기 위한, 간단한 테스트
    > 프로그램이 있습니다. 정말 괜찮게 구현해놓았네요. 질문이 하나 있습니다:
    > 어떻게 동적인 결과(dynamic resolution)를 얻을 수 있을까요?
    > 2. Bezier 코드에서 - 어떻게 이 코드를 쓰는지에 대한 예제가 없네요.. 
    > 세 함수 모두 곡선을 만드는 대신, 단지 점 하나만을 리턴하네요. 
    > 게다가 double형 인자인 mu( 0 < mu <1) 는 뭐죠?
    
    함수를 호출할 때는, 0부터 1 사이의 범위에서 mu값을 바꾸면서 함수들을
    호출하세요. mu의 값을 증가시킴에 따라, 곡선 위에서 (곡선 방향으로) 이동하는 
    점들을 얻게 됩니다. 그러므로 mu에 0.5를 넣는다면, 전체 곡선(길이)의 딱 중간에
    있는 점을 함수가 리턴하겠지요. 하나 예를 들어볼게요.
    
       #define NSTEPS 100
       int ncontrolpoints=0;
       XYZ p,*controlpoints;
       
       ... 이곳에 조절점 배열과, 데이터들을 만들고 읽는 루틴이 들어가겠지요.
    
       for (i=0;i%lt;NSTEPS;i++) {
          p = Bezier(controlpoints,ncontrolpoints,i/(double)N));
    
          .... 이제 p 변수를 이용하세요. 예를 들어, 화면에 찍는다거나 등등.
    
       }
    
    spline 코드가 호출되는 방식도, 이와 같습니다.


http://eunchul.com/Algorithms/BezierCurves/BezierCurves.htm

'Programming' 카테고리의 다른 글

베지어 곡선 (Bezier curves)  (0) 2013.07.05
2D 회전행렬 좌표  (0) 2013.06.24

2D 회전행렬 좌표

posted-at2013.06.24 16:46 :: posted-inProgramming :: posted-byNarrL

http://minilog.tistory.com/145


간만에 이미지 회전을 사용하게 되었습니다..2D 입니다.

문제는 공식이 기억이 안나는군요..증명까지는 바라지도 않습니다.


중심 좌표    :  

회전전 좌표 :   

회전후 좌표 :   

    각도      :  


 


 




'Programming' 카테고리의 다른 글

베지어 곡선 (Bezier curves)  (0) 2013.07.05
2D 회전행렬 좌표  (0) 2013.06.24

PHP+MySQL을 이용한 웹게임 개발

posted-at2013.06.18 16:38 :: posted-inProgramming/Network/Server/DB :: posted-byNarrL

http://115.68.7.121/mediawiki/index.php/Engineering_php%2Bmysql_web_games


이 문서는 PHP + MySQL로 웹게임 제작시 고려해야할 기술적인 문제에 대해 정리한 것이다.

Contents

 [hide]

[edit]Concurrency control

여러 명이 서버에 여러명이 접근하게 되면 웹게임이든 온라인 게임이든 동시성(concurreny)을 고려하고 제작을 해야한다. 동시에 여러명의 사용자가 게임에 접근하게 되면 database의 쿼리나 PHP코드 실행 순서 동기화가 깨지면서개발자가 생각하지 못한 식으로 갱신이 이루어지게 될 수 있다.

예를 들어 PHP 스크립트를 통해 다음과 같은 MySQL쿼리문 두개를 요청하였다고 하자. 다음 쿼리는 uid가 1인 플레이어가 가진 돈을 가져온후 100증가시키는 일을 하고 있다.

$result = mysql_query("SELECT money FROM player WHERE uid=1");
$row = mysql_fetch_row($result);
mysql_query("UPDATE SET money=$row[0] + 100 FROM player WHERE uid=1");

위와 같은 코드가 PHP 스크립트를 실행하는 프로세스 여러개에서 실행된다면 무슨 일이 일어날까? 위 PHP 코드를 두번 실행하면 돈이 200상승 한다고 생각하는게 자연스럽다. 하지만 다음과 같은 상황이 발생하면 돈은 100만 상승하게 된다. Process #1,#2 모두 같은 값의 돈의 크기에 100을 더해주는 상황이 발생하는 것이다.

  • 1. process #1이 SELECT쿼리 요청
  • 2. process #2가 SELETT쿼리 요청
  • 3. process #1이 UPDATE쿼리 요청
  • 4. process #2가 UPDATE쿼리 요청

이와 같은 동시 접근에 따른 순서가 보장되지 않는 문제는 MySQL의 storage engine을 MyISAM을 쓴다면 table lock으로, InnoDB라면 row level lock을 통해 해결할 수 있다. InnDB라면 위의 두개의 쿼리를 하나의 Transaction으로 묶어주고 SELECT ... FOR UPDATE문으로 SELECT 쿼리를 변경해준다.

mysql_query("START TRANSACTION");
$result = mysql_query("SELECT money FROM player WHERE uid=1 FOR UPDATE");
$row = mysql_fetch_row($result);
mysql_query("UPDATE SET money=$row[0] + 100 FROM player WHERE uid=1");
mysql_query("COMMIT");

위와같이 수정하게 되면 SELECT FOR UPDATE에서 접근된 row들은 다른 클라이언트에서 정보를 변경하지 못하게 lock이 걸리게 되어 쿼리 순서 직렬화를 보장받을 수 있는 것이다. 그런데 MySQL의 lock 기법은 transaction isolation level, storage engine에 따라 동작 방법이 틀려진다. 내부에서 일어나는 복잡한 동기화 구현방식을 이해하지 못하게 되면 문제 발생시 해결이 어려워질 수 있다. 게다가 row level lock은 table lock에 비해 빠르게 동작하지만 dead lock의 발생 위험을 지니고 있다. MySQL의 복잡한 row level locking 동작방식을 신경쓰지 않고 다른 방법으로 쿼리 순서 동기화를 처리할 수 있는 방법이 없을까? 순서 동기화 문제는 MySQL만의 이슈가 아니다. 다른 시스템을 이용하여 동시성 문제를 해결할 수도 있다는 말이다. 그럼 웹게임 제작시 사용될 수 잇는 여러가지 동기화 기법에 대해 알아보자.

platformsolution
Game server (one thread)Not Required
Game server (multi thread)Critical Section
WebServer + PHPSemaphore, Custom lock (ex)memcached lock)
MySQLtable lock, row level lock, 하나의 쿼리문으로 합쳐서 요청, procedure로 요청

제일 먼저 보통 온라인 게임에서 쓰이는 socket방식의 game server부터 알아보자. 해당 서버가 원스레드라면 동기화 문제는 일어나지 않으므로 해결할 필요가 없다. 만약 멀티스레드라면 critical section과 같은 동기화 객체를 이용하여 실행 순서를 보장하면 된다.

PHP는 코드 실행 동기화를 위해 Semaphore 함수를 제공한다. web serve가 여러개의 PHP프로세스를 실행하더라도 Sempaphore객체가 순서를 보장해주기 때문에 동시성 문제가 일어나지 않지만 Semaphore 함수는 해당 서버 머신에서만 동기화를 보장하기 때문에 확장성이 떨어진다. 여러 머신간의 코드 실행 동기화는 Memcached를 이용한 lock기법을 사용할 수 있다(이는 PHP가 여러 머신간의 세션 공유를 위해 Memcached에 세션정보를 저장하고 있는 방식과 유사한 개념이다)

MySQL은 쿼리의 실행 동기화를 위해 transaction을 통한 lock 기법을 사용한다.

정리하면 웹게임 제작시에 추천하는 쿼리 순서 동기화 방법은 다음과 같다.

  • 1. Gamer server의 critical section을 통한 쿼리 동기화
  • 2. MySQL의 Transaciotn을 통한 lock 기법을 통한 쿼리 동기화(InnoDB)
  • 3. Memcached로 구현된 custom lock을 구현해 쿼리 동기화

Critical section을 통해 특정 코드 영역을 동기화 하면 쿼리문 요청 순서도 같이 동기화 된다. 타 머신간의 쿼리 동기화가 불가능하므로 확장성면에서는 불리하다. MySQL의 transction을 통한 lock은 숙련자가 사용하면 동시성 보장이 되면서 성능도 우수하게 만들 수 있다. 하지만 isolation level, transacion, storage engine에 따라 같은 쿼리문도 lock의 동작이 틀려져 transaction이 적은 웹게임에서는 과한 해결방법이 될 수 있다.

과거 바이시티 개발시에 MySQL의 쿼리 순서 동기화를 위해 특정 키를 memcached에 저장하고, 처리가 끝나면 키를 삭제해서 한번에 명령을 하나만 처리하는 식으로 동시성문제를 해결했다. lock되어 있는 상태에서 다른 유저가 해당 처리를 요청하면 "처리에 실패했습니다" 라는 메시지를 발생시키고 유저의 요청처리를 취소했기 때문에 매끄럽지 못했다. 이를 해결하기 위해서는 php의 while문을 이용하여 spin-lock형태로 memcached를 이용하면 된다. lock wait time을 너무 길게 잡으면 웹 브라우저가 웹서버 프로세스를 계속 물고 있게 되어 다른 유저들이 접속하지 못하는 상황이 발생할 있는 점은 주의해서 만들어야 한다.

[edit]Development design

웹게임 개발을 위한 시스템 구성은 다음 세가지가 대표적이다.

  • 1. Web server and game server(one thread)
  • 2. Web server and game server(multi thread)
  • 3. Only web server

첫번째 구성은 Web server는 게임을 보여주는 역할을 하고 게임 로직처리는 게임서버에서 수행하는 모델. 원스레드로 서버 하나로 유저 요청을 모두 처리하게 되면 MySQL 쿼리에 대한 동시성 문제가 일어 나지 않는다. 이 모델은 서버는 한가지 처리밖에 진행 못하기 때문에 조금이라도 처리가 길어지는 요청이 들어오게 되면 다른 요청을 처리하지 못하고 지연되는 현상이 일어나게 된다. 이런 병목현상이 일어나게 되면 필경 원스레드 서버를 자원갱신 서버, 유저요청처리 서버등의 역할별로 분리하게 될 것이다. 그렇게 되면 다시 외부 기능을 이용하여 동시성 문제를 해결해야 한다.

두번째 구성은 Web server는 게임을 보여주는 역할을 하고 게임 로직처리는 게임서버에서 수행하는 모델. Game server내에서 critical secion을 사용하여 외부 기능을 이용하지 않고 동기화 수행이 가능. Game server code 순서가 보장되면 MySQL 쿼리의문의 순서도 동기화 된다. 멀티스레드 방식이기 때문에 1번에서 제기 되었던 성능문제가 없다. 이 모델 역시 하나의 월드에 대해 게임서버를 여러대 운영할 경우 외부 기능을 이용하여 동시성 문제를 해결해야 한다.

세번째 구성은 Web server가 게임을 보여주는 역할과 로직처리를 모두 수행하는 모델. Web server가 설치된 머신에서는 PHP의 Semaphore를 이용하여 코드를 직렬화 할 수 있지만 여러 머신간의 동시성 문제는 외부 기능을 이용하여 동기화 해야 한다.

부족전쟁류 웹게임이라면 첫번째 모델인 Web server and game server(one thread)를 이용하는 것이 적절하겠다. game server를 운영하게 되면 memory에 게임상태를 저장할 수 있어 좀더 유연한 컨텐츠 기획이 가능해진다. 그리고 원스레드 서버이기 때문에 스레드를 썻을때 생기는 여러가지 개발 이슈에서 자유롭게 된다. 역할별로 분리하여 분산처리하면 확장성도 가능해지게 된다. 하지만 점점 더 웹게임은 온라인 게임과 같은 게임 콘텐츠를 원할 것이므로 일반적인 온라인 게임의 시스템 구성방식인 두번째 모델 사용이 많아 질 것으로 예상된다.

[edit]Performance

웹게임 성능을 좌우하는 요소는 무엇인가? 스크립트만의 처리는 빠르지만 문제는 다른 모듈혹은 서버간의 통신속도이다.( MySQL, Memcached) 특히 다음 요소에 주의를 기울여야 한다.

  • 페이지당 쿼리 개수 10개 이하로 유지
  • 최대한 이미지 개수 줄이기(이미지 하나하나가 HTTP 요청이다). 캐싱이 된다고 하지만 갑자기 최초방자가 몰리는 상황이라면?
  • CDN으로 분리하자
  • 필요한 모듈만 include한다. include명령어 자체는 eaccelerator로 캐싱하면 i/o부하는 일어나지 않으므로 큰 문제는 아니다. 하지만 스크립트 파싱도 의외로 속도가 들어갈 수 있다.

성능을 체크하기 위해서 JMeter등의 툴을 사용한다.

  • 초당 몇 개의 요청을 처리할 수 있는가

많은 수의 js파일이 있으면 성능저하가 일어나는지도 확인이 필요한다. Nginx+php_cgi와 같이 여러클라이언트를 하나의 프로세스에서 처리하는 형태를 가진 웹서버에서 웹스크립트 에러는 hang을 일으켜 성능을 저하시키는 원인이 된다. 바이시티 테스트시에 Nginx의 경우 hang으로 인한 BadGateway 에러등을 내보내기도 했었다. Apache와 같이 클라이언트당 하나의 프로세스를 실행시키면 그 프로세스만 문제가 생기지만 nginx의 경우 여러 클라이언트를 하나의 프로세스에서 처리하기 때문에 오류가 발생하면 잠시 동안 프로세스가 제일을 처리 못하는 것으로 보인다.

siege,ab등의 툴을 이용해 초당 Transaction수를 파악하도록 한다.( Redorf rasmus의 문서참고: http://hardworker.tistory.com/102 ,http://talks.php.net/show/flux) yslow를 이용해서 페이지를 분석한다.

[edit]Web server

웹서버는 nginx 사용(Nginx + fastcgi)한다. apache같은 웹서버보다 사용자 폭주시 안정적으로 버텨준다

[edit]Database

하드웨어 개선을 통해 6000쿼리까지도 처리가능하다고 하지만 기본적으로 초당 3000이상 요청하면 안된다고 생각해야 한다. 웹게임의 성능은 Database최적화에 달려 있다고 해도 과언이 아닌다. 이때 Web server와 database사이에 game server나 memcached등의 미들웨어를 통해 database 접근을 줄여서 빠르게 처리할 수도 있다. GDC강연을 보면 FarmVill도 MySQL의 바로 접근하지 않고 memcached pool을 통해 성능 개선을 한것을 알 수 있다.

데이타베이스 설계시 중요한 것은 테이블 설계를 적절히 하는 것이다. 너무 많게 테이블을 나눠 구성하면 Transaction 코드가 많아질 수밖에 없다. 그러면 실행 순서 동기화 문제가 발생하면 lock을 통해 동기화 해야 되므로 어려움이 예상되기 때문이다. 기본적으로 웹게임이든 온라인 게임이든 단일문의 짧은 쿼리로 데이타 갱신을 처리하고 필요한 부분에만 조심스럽게 transaction lock을 걸어주는 것이 성능과 안정성 면에서 모두 좋은 전략이다.

[edit]Deadlock

데드락은 트랙잭션이 무한히 기다리는 교착상태에 빠진것을 말한다. 혹은 lock wait time이 너무 길어져서 설정된 시간을 넘겨서 transaction이 실패나는 경우에도 데드락이라고 한다

예를 들면 다음과 같은 쿼리가 호출되는 상황에서는 dead lock이 발생한다.

  • 1. client #1> BEGIN; SELECT name FROM table LOCK IN SHADE MODE;
  • 2. client #2> BEGIN; SELECT name FROM table LOCK IN SHADE MODE;
  • 3. client #1> UPDATE SET name= “test1”table; (lock wait .. )
  • 4. client #2> UPDATE SET name= “test2”table; (lock wait .. dead lock!)
  • 5. client #1> COMMIT;

1번과 2번 쿼리가 UPDATE를 제한하는 읽기락의 종류인 LOCK IN SHADE MODE를 사용하였다. 하지만 같은 row을 두고 두개의 클라이언트 모두 lock wait상황이 빠졌기 때문에 어느 클라이언트도 COMMIT을 통해 트랜잭션을 끝낼수가 없다. 그래서 client #2에서 dead lock이란 에러가 뜨고 트랜잭션이 종료된다. 그외 lockwait timeout이 있는데 이는 다른 트랜잭션에 의한 락이 오랫동안 풀리지않는 경우 발생하는 에러이다. 보통 10초가량 세팅해 놓는다. 이것도 dead lock상황의 하나라고 볼수 있다. 게임에서는 추천하지 않지만 일반 웹어플리케이션의 경우 dead lock 상황이 나오면 다시 요청해주는 코드를 추가해주는 것도 고려할만 한다.

게임 개발시 transaction 쿼리는 많지 않다. 많이 사용하는 것 자체가 문제지 많은 transaction에 의한 dead lock을 해결하고자 lock 코드를 변경하거나 외부 custom lock객체등을 이용해 쿼리 순서를 동기화 하는 것은 일을 더 크게 만드는 것이다. 테이블 구조와 게임 기획 변경을 통해 단일 쿼리로 처리할 수 있게 만들어 lock걸리는 상황이 아예 사라지도록 변경하는 것이 옳은 전략이다

[edit]Authentication

쿠키인증은 빠르다. 세션인증은 상대적으로 느리다.(memcached를 이용해서 저장하여 최적화 가능) 쿠키인증이든 세션인증이든 결국 쿠키를 사용하기 때문에 XSS를 통해 쿠키를 훔쳐오는게 가능해진다. 로그인시마다 유효키를 발급받아 셰션에 저장하고 이를 모든 요청에 넣어서 보내주는 방식이 가장 무난할 것으로 보인다.

[edit]Security

웹게임의 보안이슈는 다음과 같다.

  • 입력값는 모두 XSS, SQL Injection을 대비해서체크
  • Parameter Query방식을 통해 Injection방어와 효율성 개선 -> 메모리사용률도 줄어든다.
  • URL입력에 ../을 통해 상위 path로 이동이 가능한 경우도 있따. 이를 통해 /etc/passwd파일을 열어 볼 수 있는 경우도 있었다
  • 사용안하는 port는 블로킹 처리
  • DDOS 방어는? Nginx용 모듈을 찾아보자
  • Nginx는 기본적으로 많은요청에 대해 잘 방어한다. 처리 못하는 유저가 발생한지 아파치처럼 완전히 멈추지는 않는다
  • 하드웨어적으로 상위에서 방어해줘야 한다. DDOS는 어쩔수가 없다. 특정 아이피차단 정도가 할 수 있는 방어의 전부
  • 로그인시 POST 전송 패스워드 암호화 . 네이버 인증시스템을 보면 공개키암호화 시스템이 도입되어 있는 것을 볼 수 있다. 즉 자바스크립트를 딴에서 아이디/암호 문자열 자체를 공개키로 암호화해서 POST전송에 들어가는 내용까지 보호해준다. 이 처리까지 수행하기에는 현실적인 어려움이 따른다. 네트워크 스누핑에만 노출안된다면 별 문제가 없을 것으로 본다.

그외 BurpSuite등의 툴로 취약성 파악이 필수다. rasmuas의 security관련 문서도 읽어보자( http://talks.php.net/show/flux )

C# Attempted to read or write protected memory 에러

posted-at2013.05.31 17:32 :: posted-inProgramming/C#/Xna/Kinect/WPF :: posted-byNarrL

Managed Code에서 "Attempted to read or write protected memory. This is often an indication that other memory is corrupt." 와 같은 오류가 발생하는 것은 흔하지 않다이것은 Native Code에서 잘못된 메모리를 참조하는 경우에 발생하는 데아시다시피 Managed환경에서는 포인터를 이용한 직접적인 메모리를 다루는 경우가 거의 없기 때문에 경험할 일이 없을 것이다하지만, PInvoke 와 같은 Interop 를 사용하는 경우라면 달라질 수 있다이는 결국 Native COM Managed 에서 사용하는 것이므로여전히 Native COM에서의 문제가 Managed환경으로 전달되었을 때, Access Violation의 위험성이 있는 것이다결국, Pageheap enable 하거나 해서 Crash Dump를 다시 잡는 것이 필요할 수도 있다일단, Memory Dump를 확인하면 문제가 발생한 Callstack에 아래와 같은 System.AccessViolationException을 볼 수 있다.

 

0:131> !dso

OS Thread Id: 0xacc (131)

RSP/REG          Object           Name

0000000027bda3f0 00000008d0fff488 System.Threading.Thread

0000000027bda438 00000008d0fff488 System.Threading.Thread

0000000027bda588 00000009710807f8 System.String

0000000027bda5a8 00000009710807f8 System.String

0000000027bda5b0 0000000971080908 System.String

0000000027bda668 0000000971080908 System.String

0000000027bda7d0 00000008d100adc8 System.String

0000000027bda7f8 00000008d100ca20 System.String

0000000027bda840 00000008d100b930 System.String

0000000027bda900 00000008d100b930 System.String

0000000027bda9c0 00000008d0fff7c0 System.String

0000000027bda9c8 00000008d0fff7c0 System.String

0000000027bdaaf0 00000008d10095f8 System.String

0000000027bdab08 00000008d100ca20 System.String

0000000027bdab68 00000008d100ca20 System.String

0000000027bdab80 00000008d0fff340 System.AccessViolationException

0000000027bdabe0 00000008d0fff340 System.AccessViolationException

0000000027bdad28 00000008d0fff340 System.AccessViolationException

0000000027bdb0f0 00000008d0ffd308 System.String

0000000027bdb108 00000008d0ffd290 System.String

0000000027bdb110 00000008d0ffd2b0 System.String

0000000027bdb138 00000008d0ffd270 System.String

0000000027bdb140 00000008d0ffd240 System.String

0000000027bdb148 00000008d0ffd208 System.String

0000000027bdb680 00000008d0fff340 System.AccessViolationException

0000000027bdb910 00000008d0fff340 System.AccessViolationException

0000000027bdb938 00000008d0fff340 System.AccessViolationException

. . .

그리고먼저, !verifyheap 을 통해서 Managed Heap Check 하는 것도 필요하다.

 

0:131> !verifyheap

-verify will only produce output if there are errors in the heap

------------------------------

Heap 0

object 0000000880ff00d0: does not have valid MT

curr_object : 0000000880ff00d0

Last good object: 0000000880ff00b0

----------------

total 4 objects

 

출력된 결과는 curr_object가 corruption되었다는 것과 메모리 영역에서 가장 가까운 Object중에 정상적인 Object address를 알려준다그러므로, curr_object 를 살펴보면 invalid 하다는 결과를 얻는다.

 

0:131> !do 0000000880ff00d0

<Note: this object has an invalid CLASS field>

Invalid object

 

0:131> !do 0000000880ff00b0

Name: System.String

MethodTable: 00000642784365a0

EEClass: 000006427803e4f0

Size: 32(0x20) bytes

GC Generation: 0

 (C:\WINDOWS\assembly\GAC_64\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)

String: AES

Fields:

              MT            Field           Offset                 Type VT             Attr            Value Name

0000000000000000  4000096        8         System.Int32  1 instance                4 m_arrayLength

0000000000000000  4000097        c         System.Int32  1 instance                3 m_stringLength

0000000000000000  4000098       10          System.Char  1 instance               41 m_firstChar

0000000000000000  4000099       20        System.String  0   shared           static Empty

                                 >> Domain:Value  00000000d75a2aa0:NotInit Unable to get AppDomain d7900cc0

0000000000000000  400009a       28              SZARRAY  0   shared           static WhitespaceChars

                                 >> Domain:Value  00000000d75a2aa0:NotInit Unable to get AppDomain d7900cc0

 

정상적인 Data를 같이 보여주는 것은 GC에서는 Memory Allocation이 순차적으로 일어난다는 것과 무관하지 않다. low address에서 아래와 같이 Check 하여  Data를 살펴보거나 해당 Object MethodTable 정보를 Check 하는 것은 개발자가 관련있는 Code verify 하는 데 도움이 될 수 있기 때문이다.

 

0:131> dc 0000000880ff00b0

00000008`80ff00b0  784365a0 00000642 00000004 00000003  .eCxB...........  <--- valid Object

00000008`80ff00c0  00450041 00000053 00000000 00000000  A.E.S...........

00000008`80ff00d0  7843e5e0 00000642 0000000d 00000000  ..CxB...........  <--- invalid Object

00000008`80ff00e0  32313338 30323031 .................

 

또한 !gcroot 에 대한 결과로 관련된 Object 등을 Check 할 수 있는 데아래의 경우는 조금 실망스러운 결과이다.

 

0:131> !gcroot 0000000880ff00b0

Note: Roots found on stacks may be false positives. Run "!help gcroot" for

more info.

Scan Thread 52 OSThread 1440

Scan Thread 79 OSThread f4c

Scan Thread 145 OSThread 1f64

Scan Thread 149 OSThread 144c

Scan Thread 99 OSThread 18e4

Scan Thread 148 OSThread 1d38

Scan Thread 93 OSThread 1c0c

Scan Thread 91 OSThread 149c

Scan Thread 146 OSThread 1980

Scan Thread 96 OSThread 1dac

Scan Thread 136 OSThread f84

Scan Thread 98 OSThread 6ac

RSP:33c6cbb8:Root:  0000000880ff00b0(System.String)

RSP:33c6cd88:Root:  0000000880ff00b0(System.String)

RSP:33c6ce28:Root:  0000000880ff00b0(System.String)

RSP:33c6cfc0:Root:  0000000880ff00b0(System.String)

Scan Thread 106 OSThread 1a10

Scan Thread 108 OSThread 1ce4

Scan Thread 122 OSThread 190c

Scan Thread 121 OSThread 1698

Scan Thread 110 OSThread 14a8

Scan Thread 107 OSThread 1c10

Scan Thread 116 OSThread 1b80

Scan Thread 117 OSThread 141c

Scan Thread 131 OSThread acc

Scan Thread 135 OSThread 6e8

Scan Thread 126 OSThread 1d1c

Scan Thread 132 OSThread 1e18

 

해당 Object가 존재하는 98 Thread에서 연관된 Callstack Check 할 수 있다하지만, Native에서도 마찮가지로 Heap Corruption이 발생한 것은 Dumping 시점 이전에 문제가 발생한 것이 이후에 해당 Managed Heap의 사용에 의해 AV가 발생한 경우가 많기 때문에 문제가 정확히 드러나지 않을 수 있다그러므로다소 시간을 걸리더라도 Pageheap enable 하고 여러 차례 Dump를 수집할 필요가 있을 지도 모른다그리고, 1st chance AV 및 기타Exception에 대해서도 Callstack Check 라던가 관련성 여부를 살펴보는 것이 필요할 수 있다.



출처 : http://byung.egloos.com/4987870

RGB 색상표 (16진수) 변환

posted-at2013.05.29 23:05 :: posted-inProgramming/C#/Xna/Kinect/WPF :: posted-byNarrL

p, td, ul, ol, li { font-size:12px; line-height:140%; margin-top:0; margin-bottom:0; } body { font-size:12px; }

색상표의 16진수 String 값을 Color 구조체로 변환하여 사용

 

string bb = "H808080";

int r = Convert.ToInt32(bb.Substring(1, 2), 16);

int g = Convert.ToInt32(bb.Substring(3, 2), 16);

int b = Convert.ToInt32(bb.Substring(5, 2), 16);

 

Color c = Color.FromArgb(r, g, b);

 

pictureBox1.BackColor = c;

 

 for (KnownColor kc = KnownColor.ActiveBorder; kc <= KnownColor.YellowGreen; ++kc)

 {

         Color c2 = Color.FromKnownColor(kc);

           if ((c.A == c2.A) && (c.R == c2.R) && (c.G == c2.G) && (c.B == c2.B))

          {

                 label1.Text = kc.ToString();

          }

   }


출처 : http://blog.naver.com/kkdcool?Redirect=Log&logNo=110053791437


XNAExtras로 XBOX360에 폰트를 출력해 보자!! #1

posted-at2013.05.28 14:44 :: posted-inProgramming/C#/Xna/Kinect/WPF :: posted-byNarrL


* 주의 사항
0. 이글에 대한 모든 권리는 저에게 있습니다. ( 소스에 대한 권리는 개발자 분에게 있습니다. )
1. 이글을 퍼가실때는 아래 댓글로 어디에 퍼가시는지 반드시 알려 주십시요. 
2. 개인블로그등 비상업적인 목적이라면 출처만 밝혀주시면 모두 허용하겠습니다. 단 상업적인 목적이나 상업적인 싸이트에서 게시하실 경우 저에게 연락해 주십시요. 
3. 오탈자나 잘못된 부분들도 모두 아래 댓글에 달아 주십시요 ^^; 부탁드립니다. 
4. 퍼가실때 이 메시지와 퍼온 주소 (http://nomoreid.egloos.com/2910890)를 빠뜨리지 말아주십시요.
5. 아래 소스틑 아직 XBOX360 에서 테스트 해보지 못했습니다. 기술적으로 안될 이유는 없어 보입니다만 혹시나 안될때는 저에게 알려 주십시요. 아는분 엑박360을 빌려서 테스트 해보겠습니다. ^^;




0. 서문.

폰트 출력하는 루틴을 맨땅에 헤딩으로 짤려다 보니 분명히 누군가 만들어놨는데 삽질하는거 같아서 구글링을 통해 XNAExtras를 찾게 되었습니다.

XNAExtras는 xna diaries 라는 전 MS직원(현재 구글 직원-_-;;)인 Gary Kacmarcik씨가 운영했던 블로그에 공개된 툴 + 소스+ 샘플 입니다. 블로그를 통해 틈틈히 공개했던 소스와 툴을 정리해서 모아둔 거라고 보시면 됩니다. 그 안에 우리가 원하던 윈도우 폰트를 텍스쳐로 만들고 화면에 출력하는 부분이 들어 있는것이죠. ^^; 

너무나도 깔끔하고 쉽게 되어 있어서 별로 설명할게 없더군요. 폰트를 기본으로 하는것이므로 유니코드들도 모두 지원 합니다. 또 콘솔게임에서 꼭 필요한 텍스트 파일을 지정해서 해당 폰트만 따로 텍스쳐로 뽑아내는 기능등도 있습니다. (불필요한 폰트 데이터가 들어가는걸 방지하는거죠. 비됴 메모리는 한정되어 있으니.) 간단하게 응용하면 여러 다른 기종에서도 윈도 폰트를 사용하게  할 수 있을거 같습니다.

(xml의 스키마가 공개되어 있으니 해당 xml을 읽어서 font 데이터와 매칭시키는 부분만 따로 만들어주면 texture 데이터를 그대로 이용해서 쓸 수 있을거 같네요. png 포맷이니.)

일단 간단하게 순서를 정리해 보면 다음과 같습니다.

0. 먼저 XNA Game Studio Express 1.0이 깔려 있는 컴퓨터가 있어야  합니다.
1. xnaextras를 다운 -> 압축풀기
2. bmgontgen 유틸리티로 폰트에서 png와 xml을 추출
3. xnaextras의 bitmap font를 포함하는 프로그램을 짠다.
 



1. xnaextras를 다운 -> 압축풀기

12월 1일 최신 버전입니다. 근데 당분간 업데이트는 없을거 같습니다.개발자가 구글로 회사를 옮겼다고 하네요. -_-;;근데 현재 있는것 만으로도 상당히 쓸만합니다. 

URL : http://blogs.msdn.com/garykac/archive/2006/12/01/xnaextras-update.aspx

압축을 풀면 다음과 같은 디렉토리가 생깁니다.








Tools - bmfontgen 유틸리디가 들어있는 디렉토리
XML Schema - 소스에서 사용하는 xml의 스키마가 들어있는 디렉토리 (볼일은 없음)
XNA Samples - 여러가지 응용 샘플이 있는 디렉토리 , XNAExtras에서 사용하는 실제 소스는 samples/common 에 다 들어 있습니다.


2. bmfontgen 유틸리티로 폰트에서 png와 xml을 추출

일단 C:\WINDOWS\Fonts 에 있는 폰트라면 모두 추출할 수 있습니다. 
요즘 유행하는 비스타의 맑은 고딕을 추출해 보았습니다.
단 맑은 고딕에는 한자가 없더라구요. 바탕체에는 한자가 있습니다.
폰트에 없는 글자는 당연히 출력되지 않습니다.

cmd 창에서 tool이 깔린 디렉토리에 가서 다음과 같이 입력하면 폰트를 같은 디렉토리에 bitmap으로 뽑아줍니다.

BMFontGen -name "맑은 고딕" -size 12 -output "malgun12" -range 0000-fffe
BMFontGen - v1.0 beta (build 1119)
Copyright 2006 Microsoft Corp.
WARNING : No character information for 0100-0110
WARNING : No character information for 0112-0125
WARNING : No character information for 0128-0130
.......................


옵션에 대해 설명드리면 

-name 은 윈도우 폰트 이름 입니다.
-size 출력될 폰트 싸이즈
-output 파일명의 앞부분입니다.
-range 출력될 폰트 코드의 범위입니다. 일단 2byte 문자의 모든 범위 (0000 - fffe) 를 출력하게 했습니다. 아래의 WARNING은 폰트가 없는 코드 범위를 알려주는 겁니다. 폰트별로 모든 코드를 다 가지고 있는게 아니라서 적절한 범위를 짤라 사용하면 되겠습니다.

BMFontGen의 모든 기능에 대해서는 여기를 참고해 주십시요.

유용한 옵션중에 -source 라는 옵션이 있습니다. 게임에서 사용하는 대사파일등을 지정해두면 (중복 지정가능) 해당 대사에 사용된 폰트들만 추출해 주는 기능입니다. ^^;

위와 같이 실행하면 같은 디렉토리 안에 output에서 지정된 파일명으로 추출된 폰트 파일들이 다수 있을겁니다.


png 파일을 클릭해 보면 흰색 박스만 나올것입니다. 투명색 + 흰색 글자라 아무것도 보이지 않습니다.
(저도 처음에 안되는줄 알았습니다. -_-;)

 -blackbg 을 붙이면 확인용으로 배경을 검게 할 수 있습니다. 하지만 그대로 쓰면 화면이 이렇게 나온다는거..

그외에 안티알리아싱이라든지 여러가지 유용한 옵션이 많습니다. 참 -bmsize size (256-1024) 이 옵션은 비트맵의 싸이즈를 256-1024까지 바꿔 줍니다. 디폴트는 512구요. 한글처럼 글자가 많은경우는 1024정도로 크게 뽑아주는게 좋겠죠.

아래는 굴림체를 배경을 검게해서 512X512로 추출한겁니다.


3. xnaextras의 bitmap font를 포함하는 프로그램을 짠다.

 실제 프로그램에 적용하는 순서는 다음과 같다.

ㄱ. 빈프로젝트를 하나 만든다.

ㄴ. Content 디렉토리를 하나 만들고 위에서 생성한 png 파일과 xml 파일을 넣는다. XMLExtra 라는 폴더를 만들고 Samples/Common에 있는 BitmapFont.cs 파일을 copy하고 프로젝트에 add 한다.

ㄷ. png와 xml 파일의 properities 창에서 Build Action 을 Embedded Resource 로 수정.

ㄹ. Solution Explorer에 Reference 쪽에 System.xml 추가.

ㅁ. 프로젝트에 멤버변수로 BitmapFont 를 등록 , Initialize()함수에서 BitmapFont를 new 이때 xml과 연결시켜줌 , LoadGraphicsContent 함수에 등록 , Draw 에 등록

ㅂ. 실행.

이게 전부이다. xml을 이용한 데이터 바인딩을 통해 아주 쉽게 쓸 수 있는 형태로 구현해 두었다는것을 알 수 있다.

아래 부분에 프로젝트를 만들고 파일을 추가한 후 수정한 부분만 붉은색으로 바꿔 보겠다.


#region Using Statements
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;

using XNAExtras;

#endregion

namespace WindowsGame1
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        ContentManager content;

        //Bitmap
        BitmapFont m_Malgun;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            content = new ContentManager(Services);
        }


        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here
            m_Malgun = new BitmapFont("Content/malgun.xml");
            base.Initialize();
        }


        /// <summary>
        /// Load your graphics content.  If loadAllContent is true, you should
        /// load content from both ResourceManagementMode pools.  Otherwise, just
        /// load ResourceManagementMode.Manual content.
        /// </summary>
        /// <param name="loadAllContent">Which type of content to load.</param>
        protected override void LoadGraphicsContent(bool loadAllContent)
        {
            if (loadAllContent)
            {
                // TODO: Load any ResourceManagementMode.Automatic content
            }
            // TODO: Load any ResourceManagementMode.Manual content
            m_Malgun.Reset(graphics.GraphicsDevice);
        }


        /// <summary>
        /// Unload your graphics content.  If unloadAllContent is true, you should
        /// unload content from both ResourceManagementMode pools.  Otherwise, just
        /// unload ResourceManagementMode.Manual content.  Manual content will get
        /// Disposed by the GraphicsDevice during a Reset.
        /// </summary>
        /// <param name="unloadAllContent">Which type of content to unload.</param>
        protected override void UnloadGraphicsContent(bool unloadAllContent)
        {
            if (unloadAllContent == true)
            {
                content.Unload();
            }
        }


        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // Allows the default game to exit on Xbox 360 and Windows
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            // TODO: Add your update logic here

            base.Update(gameTime);
        }


        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            graphics.GraphicsDevice.Clear(Color.CornflowerBlue);

            // TODO: Add your drawing code here
            Color cHilite = Color.DarkBlue;
            Color cWhite = Color.White;

            int nX = 20;
            int nY = -15;

            nY += 20;


            m_Malgun.DrawString(nX, nY +=( m_Malgun.LineHeight +5), cWhite, "안녕하세요 謹賀新年");

            m_Malgun.DrawString(nX, nY +=( m_Malgun.LineHeight +5), cWhite, "반갑습니다 폰트 테스트 ひびつれづれ 是日");

            m_Malgun.DrawString(nX, nY +=( m_Malgun.LineHeight +5), cWhite, "あけましておめでとうございます(´ω`)");

            m_Malgun.DrawString(nX, nY +=( m_Malgun.LineHeight +5), cWhite, "abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ");

            m_Malgun.DrawString(nX, nY +=( m_Malgun.LineHeight +5), cWhite, "1234567890  ♣♬!@#$%^&*() ");
            m_Malgun.DrawString(nX, nY +=( m_Malgun.LineHeight +5), cWhite, "ㄱ - 느낌표, 물음표 등 부호(!,/...)");

            m_Malgun.DrawString(nX, nY +=( m_Malgun.LineHeight +5), cWhite, "ㄴ - 괄호, 따옴표 등 부호([, 》...)");

            m_Malgun.DrawString(nX, nY +=( m_Malgun.LineHeight +5), cWhite, "ㄷ - 더하기, 빼기 등의 수학적 부호(+, >, ×...)");

            m_Malgun.DrawString(nX, nY +=( m_Malgun.LineHeight +5), cWhite, "ㄹ - 단위($, ℃, ㎖...)");

            m_Malgun.DrawString(nX, nY +=( m_Malgun.LineHeight +5), cWhite, "ㅁ - 특수부호(#, ♣, ♬...)");

            m_Malgun.DrawString(nX, nY +=( m_Malgun.LineHeight +5), cWhite, "ㅂ - 연결 및 표시선(─, ┐, ╆...)");

            m_Malgun.DrawString(nX, nY +=( m_Malgun.LineHeight +5), cWhite, "ㅅ - 한글 원문자 및 괄호문자(㉠, ㈐, ㈃...)");

            m_Malgun.DrawString(nX, nY +=( m_Malgun.LineHeight +5), cWhite, "ㅇ - 알파벳과 숫자 원문자 및 괄호문자(ⓑ, ⒀, ⑧...)");

            m_Malgun.DrawString(nX, nY +=( m_Malgun.LineHeight +5), cWhite, "ㅈ - 숫자(Ⅷ, 3, ⅱ...)");

            m_Malgun.DrawString(nX, nY +=( m_Malgun.LineHeight +5), cWhite, "ㅊ - 분수와 아래첨자, 윗첨자(⅔, ⁴, ₂...)");

            m_Malgun.DrawString(nX, nY +=( m_Malgun.LineHeight +5), cWhite, "ㅋ - 한글(ㄱ, ㅞ...)");

            m_Malgun.DrawString(nX, nY +=( m_Malgun.LineHeight +5), cWhite, "ㅌ - 이상한 한글 조합(ㅫ, ㆋ...)");

            m_Malgun.DrawString(nX, nY +=( m_Malgun.LineHeight +5), cWhite, "ㅍ - 알파벳(x, A, N...)");

            m_Malgun.DrawString(nX, nY +=( m_Malgun.LineHeight +5), cWhite, "ㅎ - 그리스 글자(Β, Δ, Σ...)");

            m_Malgun.DrawString(nX, nY +=( m_Malgun.LineHeight +5), cWhite, "ㄲ - 영어 발음기호(æ, ŋ, ð...)");

            m_Malgun.DrawString(nX, nY +=( m_Malgun.LineHeight +5), cWhite, "ㄸ - 일본어 히라가나(あ, け, ず...)");

            m_Malgun.DrawString(nX, nY +=( m_Malgun.LineHeight +5), cWhite, "ㅃ - 일본어 가타카나(ア, ヴ, ォ...)");

            m_Malgun.DrawString(nX, nY +=( m_Malgun.LineHeight +5), cWhite, "ㅆ - 이상한 글자들(Д, ю, щ...)");

            m_Malgun.DrawString(nX, nY +=( m_Malgun.LineHeight +5), cWhite, "ㅉ - 없음");

 

            base.Draw(gameTime);
        }
    }
}

* 실제 코딩하는 부분은 거의 없습니다.^^; 데이터 바인딩을 xml로 ^^;

* 이외에 리소스를 embeded resource로 해서 실행파일에 넣지 않고 따로 파일로 뽑아서 하는 방법도 있습니다.(samples/BitmapFontDemo)

* Samples 에 여러가지 데모들이 있으니 많이 연구하시고 공개 해주세요. ^^; Sprite를 응용한 TextBox 예제도 있습니다.



출처 : http://nomoreid.egloos.com/2910890

C# StreamReader 에서의 한글 Encoding 문제

posted-at2013.05.28 12:20 :: posted-inProgramming/C#/Xna/Kinect/WPF :: posted-byNarrL

오늘은 개발자 한 분이... ASP.NET 에서 한글이 포함된 HTML 텍스트를 로딩했는 데, 한글이 깨진다는 문제를 들고 왔습니다.

텍스트 파일의 특성상, 가볍게 다음과 같은 코드로 마무리를 지었던 것입니다.

string fileContent = null;
using ( StreamReader sr = new StreamReader( filePath ) )
{
 fileContent = sr.ReadToEnd();
 sr.Close();
}


저 역시, 위의 코드를 보고 너무 표준적인 코드라 ^^ 문제가 없어 보였지요.

 .NET 에서는 문자열 처리를 명시적으로 지정하지 않는 한, 기본적으로 "System.Text.UTF8Encoding" 으로 처리를 합니다. 문제는 거기서 발생을 하지요.

 해당 HTML 텍스트 한글 파일은 메모장에서 "ASCII" 형식으로 저장된 것이었고, 디코딩을 UTF-8 로 해버리니 당연히 깨질 수 밖에 없습니다.

 한글이 포함된 ASCII 코드를 정상적으로 읽어들이기 위해서는 인코딩을 지정해야 합니다. StreamReader 의 두번째 인자에는 바로 그 인코딩 방식을 지정할 수가 있죠. 우리가 아는 것처럼 "KS_C_5601-1987" 인코딩 방식을 지정해야 합니다. 다음과 같은 코드로.

using ( StreamReader sr = new StreamReader( filePath, Encoding.GetEncoding( "ks_c_5601-1987" ) ) )
{
 fileContent = sr.ReadToEnd();
 sr.Close();
}


 명시적인 Encoding 문자열 지정대신에, Encoding.Default 를 지정해도 됩니다. 시스템 레벨로 설정된 (제어판의 Regional Settings) code page 값이 한글 윈도우즈에서는 기본적으로 "KS_C_5601-1987" 이기 때문입니다.

 하지만, 여기서 끝이 아니죠. ^^
 만약 해당 파일이 utf-8 또는 unicode 로 인코딩된 텍스트 파일이라면? 당연히 위의 코드로 읽어들이게 되면 역시 한글이 깨지게 됩니다.

 Unicode 또는 UTF-8 등의 텍스트 파일은 그 인코딩 방법을 표시하기 위해, 파일의 최초 2~3바이트에 BOM(byte order mark)를 표시해 둡니다. utf-8 로 인코딩된 텍스트 파일을 윈도우즈의 "메모장"으로 열어보면 그러한 표시를 생략하고 순수 텍스트만 보여주지만, 2진 파일 형식으로 볼 수 있는 hexa 에디터등을 통해서 보게 되면, 최초 2~3바이트의 내용이 인코딩에 따라서 달라지는 것을 확인할 수 있습니다.

UTF-8: EF BB BF
Unicode: FF FE

 실제로, BOM 을 통한 디코딩을 지원하지 않는 Editor 로 UTF-8 인코딩된 파일을 열게 되면, 최초 3byte 를 깨진 텍스트로 출력해 주는 것을 볼 수 있습니다.

 아무튼... 그렇다면, 우리도 BOM 을 읽어서 상황에 따라 StreamReader 의 2번째 인자에 각각 해당하는 인코딩을 넣어주면 되겠지요. ^^; 아마도 C++ 이었다면 틀림없이 그렇게 해야 했을 것입니다. 하지만, 우리의 "친절한 .NET 씨"(이 표현은 Loner(
http://simpleisbest.net)에서 빌려옴) 는 그런 작업을 모두 해놓았습니다. 바로 StreamReader 의 3번째 인자에서 그러한 역할을 대신해 줍니다.

public StreamReader(..., bool detectEncodingFromByteOrderMarks);

 매개변수 이름 부터 그런 태생임을 짐작하게 해줍니다. 해당 값을 true 로 전달하면 BOM 마크가 없는 - 예를 들어 ANSI 파일 - 경우에는 2번째 인자에서 지정한 Encoding 방식으로 인식해서 디코딩을 하지만, BOM 이 있으면 거기서 지정된 Encoding 방식으로 되어 있다고 인식해서 디코딩을 하게 됩니다.

그러니... 앞으로 국내에서의 .NET 개발자들은 텍스트 파일을 로딩하기 위한 표준 코드를 다음과 같이 해야 합니다.
using ( StreamReader sr

 = new StreamReader( filePath, Encoding.GetEncoding( "ks_c_5601-1987" ), true ) )
{
 fileContent = sr.ReadToEnd();
 sr.Close();
}


또는

using ( StreamReader sr = new StreamReader( filePath, Encoding.Default, true ) )
{
 fileContent = sr.ReadToEnd();
 sr.Close();
}


 두 가지 모두, 경우에 따라서 논란의 여지가 있지만... 적어도 한글 윈도우즈에서 호스팅 된다는 보장만 된다면, "Encoding.Default" 인자가 가장 좋을 것 같네요. ^^




[XNA] Scrolling background images horizontally across the screen

posted-at2013.05.08 17:24 :: posted-inProgramming/C#/Xna/Kinect/WPF :: posted-byNarrL

If you're working on 2D games, at some point you're going to want to know how to create a scrolling background. This tutorial will cover setting up a horizontally scrolling background that auto-scrolls behind your character. Let's go ahead and get started.

 

Creating the new game project:

We are going to be using the "Sprite" class we created in the tutorial "Creating a 2D Sprite". Instead of creating a new XNA Game project, you can just download the source for that project and begin working from that game project.

If you would rather create a new game project to start from, but are having trouble remembering how to do that, just follow the steps in this tutorial here and come back to the this tutorial after you've refreshed your memory. Once you have your new game project, you will need to add the "Sprite" class created in this tutorial here to your new project.

Adding the images:

Now, let's add the background images that we are going to be using in this tutorial to the game project. You can download the images used in the project from here. Once you have downloaded the images, add all five background images to the Content folder in your game. If you are having trouble remembering how to add images to an XNA Game project, just follow the steps in this tutorial here and come back to this tutorial after you've refreshed your memory.

Enhancing the Sprite class:

To start out, we are going to make some enhancements to the Sprite class. These enhancements will make the sprite class a bit more flexible and allow us to more easily add in our scrolling background.

Add the following objects to the top of the Sprite class.

        //The size of the Sprite
        public Rectangle Size;
 
        //Used to size the Sprite up or down from the original image
        public float Scale = 1.0f;

The Size object will be used to store the height and width of the scaled sprite and the Scale object will be used to increase or decrease the size of the sprite from the original image. We are defaulting the Scale variable to 1.0 which tells the SpriteBatch to leave the image at its original size. Making Scale a larger number will increase the size of the sprite and a smaller number for scale will being to shrink the sprite.

Next, we will modify the LoadContent method to calculate and initialize the size of our Sprite. Modify the LoadContent method of the Sprite class to look like this.

        //Load the texture for the sprite using the Content Pipeline
        public void LoadContent(ContentManager theContentManager, string theAssetName)
        {
            mSpriteTexture = theContentManager.Load<Texture2D>(theAssetName);
            Size = new Rectangle(0, 0, (int)(mSpriteTexture.Width * Scale), (int)(mSpriteTexture.Height * Scale));
        }

Next, we want to use one of the many overrides of the SpriteBatch object to draw our image with our new Scale applied. Change the Draw method to look like the following.

        //Draw the sprite to the screen
        public void Draw(SpriteBatch theSpriteBatch)
        {
            theSpriteBatch.Draw(mSpriteTexture, Position, 
                new Rectangle(0, 0, mSpriteTexture.Width, mSpriteTexture.Height),Color.White, 
                0.0f, Vector2.Zero, Scale, SpriteEffects.None, 0);
        }

We have added a whole lot of parameters to pass into the Draw method of the SpriteBatch, but the one we are tracking is “Scale”. This will tell the SpriteBatch object to increase or decrease the size of the sprite proportionally.

Do a quick build now to make sure everything compiles. If you’re working off the source code for “Creating a 2D Sprite”, you should see the two sprites drawn to the screen.

Ok, now that we have laid the groundwork in our Sprite class we can now get started on adding the scrolling backgrounds to our game project.

We’ll start by adding some new Sprite objects to the top of the Game1.cs class. Add the following objects to the top of the class.

        Sprite mBackgroundOne;
        Sprite mBackgroundTwo;
        Sprite mBackgroundThree;
        Sprite mBackgroundFour;
        Sprite mBackgroundFive;

These Sprite objects will be used to move our background images across the screen.

Next, let’s Initialize our new Sprite objects and give them there scale value. The original size of the background images is 400 by 300 and the default size for a new Windows game is 800 by 600. So, if we double the size of our images, one will fill the whole screen. We can do that by adjusting the Scale value of our sprites.

Modify the Initialize method to look like this.

        protected override void Initialize()
        {
            // TODO: Add your initialization logic here
            mSprite = new Sprite();
            mSpriteTwo = new Sprite();
 
            mBackgroundOne = new Sprite();
            mBackgroundOne.Scale = 2.0f;
 
            mBackgroundTwo = new Sprite();
            mBackgroundTwo.Scale = 2.0f;
 
            mBackgroundThree = new Sprite();
            mBackgroundThree.Scale = 2.0f;
 
            mBackgroundFour = new Sprite();
            mBackgroundFour.Scale = 2.0f;
 
            mBackgroundFive = new Sprite();
            mBackgroundFive.Scale = 2.0f;
 
            base.Initialize();
        }

You can see we have created a new instance of each of our Sprite objects and adjusted the Scale for our background images.

Now that we have initialized our sprites, we need to load the content. Modify the LoadContent method to look like this.

        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);
 
            // TODO: use this.Content to load your game content here
            mSprite.LoadContent(this.Content, "SquareGuy");
            mSprite.Position = new Vector2(125, 245);
 
            mSpriteTwo.LoadContent(this.Content, "SquareGuy");
            mSpriteTwo.Position.X = 300;
            mSpriteTwo.Position.Y = 300;
 
            mBackgroundOne.LoadContent(this.Content, "Background01");
            mBackgroundOne.Position = new Vector2(0, 0);
 
            mBackgroundTwo.LoadContent(this.Content, "Background02");
            mBackgroundTwo.Position = new Vector2(mBackgroundOne.Position.X + mBackgroundOne.Size.Width, 0);
 
            mBackgroundThree.LoadContent(this.Content, "Background03");
            mBackgroundThree.Position = new Vector2(mBackgroundTwo.Position.X + mBackgroundTwo.Size.Width, 0);
 
            mBackgroundFour.LoadContent(this.Content, "Background04");
            mBackgroundFour.Position = new Vector2(mBackgroundThree.Position.X + mBackgroundThree.Size.Width, 0);
 
            mBackgroundFive.LoadContent(this.Content, "Background05");
            mBackgroundFive.Position = new Vector2(mBackgroundFour.Position.X + mBackgroundFour.Size.Width, 0);
 
        }

You can see that we load the content for each of the Sprite images we added for our backgrounds. We then position each of the images in a trail from left to right. The first image starts at 0,0 which is the top left corner of the screen and then each image is placed in a line after that.

Scrolling backgrounds work by moving the background images across the screen in a snake like fashion. When an image moves all the way off the screen on the left, it is placed at the end of the snake tail so that the images can continually loop in that fashion giving you a seamless horizontally scrolling background.

To create this effect, we need to both move the images and then check to see if the image has moved off the screen and re-position it at the end. We do that by modifying the Update method to look like this.

        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();
 
            // TODO: Add your update logic here
 
 
            if (mBackgroundOne.Position.X < -mBackgroundOne.Size.Width)
            {
                mBackgroundOne.Position.X = mBackgroundFive.Position.X + mBackgroundFive.Size.Width;
            }
 
            if (mBackgroundTwo.Position.X < -mBackgroundTwo.Size.Width)
            {
                mBackgroundTwo.Position.X = mBackgroundOne.Position.X + mBackgroundOne.Size.Width;
            }
 
            if (mBackgroundThree.Position.X < -mBackgroundThree.Size.Width)
            {
                mBackgroundThree.Position.X = mBackgroundTwo.Position.X + mBackgroundTwo.Size.Width;
            }
 
            if (mBackgroundFour.Position.X < -mBackgroundFour.Size.Width)
            {
                mBackgroundFour.Position.X = mBackgroundThree.Position.X + mBackgroundThree.Size.Width;
            }
 
            if (mBackgroundFive.Position.X < -mBackgroundFive.Size.Width)
            {
                mBackgroundFive.Position.X = mBackgroundFour.Position.X + mBackgroundFour.Size.Width;
            }
            
            Vector2 aDirection = new Vector2(-1, 0);
            Vector2 aSpeed = new Vector2(160, 0);
 
            mBackgroundOne.Position += aDirection * aSpeed * (float)gameTime.ElapsedGameTime.TotalSeconds;
            mBackgroundTwo.Position += aDirection * aSpeed * (float)gameTime.ElapsedGameTime.TotalSeconds;
            mBackgroundThree.Position += aDirection * aSpeed * (float)gameTime.ElapsedGameTime.TotalSeconds;
            mBackgroundFour.Position += aDirection * aSpeed * (float)gameTime.ElapsedGameTime.TotalSeconds;
            mBackgroundFive.Position += aDirection * aSpeed * (float)gameTime.ElapsedGameTime.TotalSeconds;            
 
            base.Update(gameTime);
        }

This code consists of two different sections. We’ll discuss the first section of code below which deals with keeping the "chain" of images going.

            if (mBackgroundOne.Position.X < -mBackgroundOne.Size.Width)
            {
                mBackgroundOne.Position.X = mBackgroundFive.Position.X + mBackgroundFive.Size.Width;
            }
 
            if (mBackgroundTwo.Position.X < -mBackgroundTwo.Size.Width)
            {
                mBackgroundTwo.Position.X = mBackgroundOne.Position.X + mBackgroundOne.Size.Width;
            }
 
            if (mBackgroundThree.Position.X < -mBackgroundThree.Size.Width)
            {
                mBackgroundThree.Position.X = mBackgroundTwo.Position.X + mBackgroundTwo.Size.Width;
            }
 
            if (mBackgroundFour.Position.X < -mBackgroundFour.Size.Width)
            {
                mBackgroundFour.Position.X = mBackgroundThree.Position.X + mBackgroundThree.Size.Width;
            }
 
            if (mBackgroundFive.Position.X < -mBackgroundFive.Size.Width)
            {
                mBackgroundFive.Position.X = mBackgroundFour.Position.X + mBackgroundFour.Size.Width;
            }

You might expect to move the background images first, but this will actually begin to create gaps between the images. Often in game development, you have to be very aware in the order that you are doing things. If we move the images first, then check to see if they’ve moved off the screen, you have to remember that the player has never seen that happen yet because they haven’t been drawn to the screen yet! That doesn’t happen until Draw is called.

So, instead, we first move images that have moved off the screen (and hopefully the player has seen this occur already) to the end of the trail of images. This is done by checking the Sprite’s current position and seeing if it’s moved further then it’s width off the screen. This means that the entire image is now out of the viewable area for the player. If this is true, then we move the image to the one it should be following. The first image follows the fifth, and then the fifth follows the fourth and so on and so on.

Now let’s look at the second part of the code, the movement.

            Vector2 aDirection = new Vector2(-1, 0);
            Vector2 aSpeed = new Vector2(160, 0);
 
            mBackgroundOne.Position += aDirection * aSpeed * (float)gameTime.ElapsedGameTime.TotalSeconds;
            mBackgroundTwo.Position += aDirection * aSpeed * (float)gameTime.ElapsedGameTime.TotalSeconds;
            mBackgroundThree.Position += aDirection * aSpeed * (float)gameTime.ElapsedGameTime.TotalSeconds;
            mBackgroundFour.Position += aDirection * aSpeed * (float)gameTime.ElapsedGameTime.TotalSeconds;
            mBackgroundFive.Position += aDirection * aSpeed * (float)gameTime.ElapsedGameTime.TotalSeconds;            

Movement is done by increasing the position of a sprite by multiplying Direction, times the speed, times the time that has elapsed.

Direction is just a -1 or 1 in the X and or Y direction. In our case, we want to move the sprites to the left, so we are decreasing in the X direction, so we use a negative one. We are not moving at all in the Y direction, so we leave that 0 for our Direction vector.

Speed is how fast you want the sprite to move. We want the Sprite to move at a decent pace in the X direction and not at all for Y. You can increase or decrease the speed of the scrolling by increasing or decreasing this number.

Finally, you multiply by time elapsed. This is done so that computers that are faster and with faster refresh rates see the same speed of scrolling that slower computers with slower refresh rates see. By default, the XNA framework tries to maintain this internally by keeping everything moving at a fixed time step, but it’s a good habit to get into.

Ok, we’re re-positioning our background sprites as necessary and moving them along at a steady clip in the right direction. The only thing left to do it draw them to the screen.

Modify the Draw method to look like this.

        protected override void Draw(GameTime gameTime)
        {
            graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
 
            // TODO: Add your drawing code here
            spriteBatch.Begin();
 
            mBackgroundOne.Draw(this.spriteBatch);
            mBackgroundTwo.Draw(this.spriteBatch);
            mBackgroundThree.Draw(this.spriteBatch);
            mBackgroundFour.Draw(this.spriteBatch);
            mBackgroundFive.Draw(this.spriteBatch);
 
            mSprite.Draw(this.spriteBatch);
            mSpriteTwo.Draw(this.spriteBatch);
            spriteBatch.End();
 
            base.Draw(gameTime);
        }

The important thing to note here is that we are drawing the backgrounds BEFORE the two square guy sprites. Sprites are drawn in layers with the first Draw calls drawn first to the screen and the others drawn on top of them. It’s important to remember that when adding your draw code. If we had placed the backgrounds after the sprites, we would have never seen them because the background images would have been drawn on top of the square guys.

That’s it, go ahead and Build CropperCapture2632 the game. You should see the background images begin to scroll horizontally across the screen and the two square guys looking like their floating through the air.

Scrolling Background

Congratulations! You now have successfully learned how to add a horizontally scrolling background to your game. While, this is a good introductory example to scrolling a Horizontal background, but the code wasn’t written in a very object oriented way. I have taken this tutorial a bit further and done some work to make it a little more re-usable project. You can check out the more advanced Horizontal scrolling example.

Can you think of some things you can change? How hard would it be to make this scroll vertically? Do you think you know what you might change?

Play with it, experiment and ask questions. Most of all make sure you’re having fun!


출처 : http://www.xnadevelopment.com/tutorials/scrollinga2dbackground/ScrollingA2DBackground.shtml

[XNA] A Simple 2D Camera

posted-at2013.05.08 17:21 :: posted-inProgramming/C#/Xna/Kinect/WPF :: posted-byNarrL

What is a camera? Intuitively we know what a camera is:  simply a way to show the action.

A Camera allows us to deal with the display of the action in a detached way from the action.

Implementation

I like to start with what we are trying to achieve. For the purposes of this post, I want to have two cameras showing the same action at different zoom levels, like this:

Capture

In this case we want 2 cameras, with each camera having it’s own ViewPort that we assign when creating like this:

int halfScreenWidth = GraphicsDevice.Viewport.Width/2;
_camera1 = new Camera(new Viewport(0, 0, halfScreenWidth - 3, GraphicsDevice.Viewport.Height));

What we are saying with that is that the Camera class will have a Viewport that occupies the left side of the screen.  The – 3 is there to add a visible gap between the left and right sides of the screen. Lets look at the following portion of the Draw method. Please note you could have all the parameters for _spriteBatch.Begin() null, except for the transform.

 1:             GraphicsDevice.Viewport = _camera1.Viewport;
 2: 
 3:             _spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, DepthStencilState.None, RasterizerState.CullNone, null, _camera1.Transform);
 4:             _level.Draw(_spriteBatch);
 5:             _spriteBatch.Draw(_tankTexture, _tankPosition, null, Color.White, _rotation, new Vector2(_tankTexture.Width / 2, _tankTexture.Height / 2), 1, SpriteEffects.None, 0);
 6:             _spriteBatch.End();

Since we have a viewport  per camera, the graphics device needs to know which one to use, and that’s what we are doing in line 1 in the code above. The other important part of the code is the last parameter of the _spriteBatch.Begin call, where we are using the transformation matrix from the camera. This transform is calculated on each update.

So finally lets look at the Camera class:

 1:     public class Camera
 2:     {
 3:         public Matrix Transform { get; private set; }
 4:         public Viewport Viewport { get; private set; }
 5: 
 6:         public Camera(Viewport viewport)
 7:         {
 8:             Transform = Matrix.Identity;
 9:             Viewport = viewport;
 10:         }
 11: 
 12:         public void Update(GameTime gameTime, float rotation, Vector2 position, float zoom)
 13:         {
 14:             Transform = Matrix.CreateTranslation(-position.X, -position.Y, 0) *
 15:                         Matrix.CreateRotationZ(rotation) *
 16:                         Matrix.CreateScale(new Vector3(zoom, zoom, 1)) *
 17:                         Matrix.CreateTranslation(Viewport.Width / 2, Viewport.Height / 2, 0);
 18:         }
 19:     }

The least obvious part of this code lies in the matrix transformation. It is important to understand that we have two coordinate systems in place, the screen and the world. With the transformation matrix we are trying to project the world coordinate system onto the screen system. With that in mind, the first translation matrix (line 14 in the code above) will reposition the world so that point (position.X, position.Y) lines up with the screen’s origin. The result is as follows:

CaptureCapture

What you see at the top left of the pic is a quarter of the tank. If you change the tank’s position then you will see that the tank remains static at the top left (tho rotation applies) and the world moves underneath it.

Capture

The next line, Matrix.CreateRotationZ, creates a matrix representing a rotation around the Z axis. The Z axis points straight out of the screen. In 2D, to perform a rotation, we always rotate around Z.

Capture

Then we need to scale, using the parameters that we sent on update. Don’t forget we set different levels of zoom in each of the two cameras. We use the overload of Matrix.CreateScale() that takes a Vector3 to create a scaling matrix. However we could use the overload that just takes a float and pass the zoom value as a parameter with the same result. Unsurprisingly applying the scale, scales Smile.

Finally we want to center the tank in the middle of each viewport and that’s why you apply the last line (line 17 on the sample above).

Capture

And we are done Smile tho I m sure there are plenty of ways to improve the code, but hopefully it will help as a simple example.

If you want to have a better look at the code, a working sample is available here. If you have any comments, improvements, questions, as always you are very welcome.

UPDATE: I followed up with a post about Spring Camera here.


출처 : http://roundcrisis.com/2012/04/19/xna-a-simple-2d-camera/