안펍 세미나 동영상 from kevinkim on Vimeo.



전체 화면으로 보셔야 선명하게 보일듯 하네요.


이 동영상은 2배속으로 인코딩한겁니다. 실제로는 59분 걸렸어요.


디자인 하는데 20분 정도, 나머지는 코딩하는데 걸린 시간입니다.



세미나에 사용된 파워포인트 자료는 첨부파일로 올렸습니다.


슬라이드쇼로 보시면 됩니다.



소스는 아쉽게도 풀소스는 아닙니다만, 게임 부분 전체 소스를 넣었습니다.




  1. package beeniesoft.game.test;
  2.  
  3. import java.util.Random;
  4.  
  5. import javax.microedition.khronos.opengles.GL10;
  6. import android.content.*;
  7. import android.media.AudioManager;
  8. import android.media.MediaPlayer;
  9. import android.media.SoundPool;
  10. import android.os.Vibrator;
  11. import bayaba.game.lib.*;
  12.  
  13. public class GameMain
  14. {
  15.     class KeyStatus // 멀티터치를 위한 클래스
  16.     {
  17.         public int x = 0, y = 0;
  18.         public boolean status = false;
  19.        
  20.         public void SetKey( int px, int py, boolean flag )
  21.         {
  22.             x = px;
  23.             y = py;
  24.             status = flag;
  25.         }
  26.     }
  27.     public GL10 gl = null; // OpenGL 객체
  28.    
  29.     private static int MAX_GAME = 300; // 게임에서 사용할 오브젝트의 최대 개수 설정
  30.    
  31.     public KeyStatus Key[] = new KeyStatus [5]; // 멀티터치를 기록할 배열 (기본값 최대 5개)
  32.    
  33.     private int Sort[] = new int [MAX_GAME]; // 오브젝트 소팅을 위한 배열
  34.     private int Data[] = new int [MAX_GAME];
  35.    
  36.     public boolean Pause = false, GameStart = false; // 포즈 상태 플래그, 게임 시작 플래그 변수
  37.    
  38.     private SoundPool SndPool;
  39.     private MediaPlayer Music;
  40.     private Vibrator Vibe;
  41.     private Context MainContext;
  42.     private Random MyRand = new Random();
  43.    
  44.     private int SndBuff[] = new int [20];
  45.    
  46.     public GameInfo gInfo; // 게임 환경 설정용 클래스 : MainActivity에 선언된 것을 전달 받는다.   
  47.    
  48.     private Sprite Pattern[] = new Sprite [30]; // 스프라이트 파일을 읽어들일 클래스 배열
  49.     private GameObject Game[] = new GameObject [MAX_GAME]; // 오브젝트를 처리할 클래스 배열
  50.     private GameObject Button[] = new GameObject [30]; // UI를 위한 별도의 오브젝트
  51.    
  52.     public int GameMode = 0, TotalGame = 0, TotalButton = 0; // GameMode는 게임 화면의 상태, TotalGame은 실제로 화면에 뿌려질 오브젝트의 개수, TotalButton은 UI용 오브젝트의 개수
  53.     private long QuakeTimer = 0; // 지진 효과가 지속될 시간 타이머
  54.    
  55.    
  56.     private long TargetTimer = 0, ArrowTimer = 0;
  57.     public boolean ArrowFlag = false, FireFlag = false;
  58.    
  59.    
  60.     public GameMain( Context context, GameInfo info )
  61.     {
  62.         int i;
  63.        
  64.         MainContext = context;
  65.         gInfo = info;
  66.        
  67.         Vibe = (Vibrator)context.getSystemService( Context.VIBRATOR_SERVICE );
  68.        
  69.         for ( i = 0; i < 30; i++ ) Pattern[i] = new Sprite(); // 스프라이트 파일을 담을 클래스 생성
  70.         for ( i = 0; i < 5; i++ ) Key[i] = new KeyStatus(); // 멀티터치용 클래스 생성
  71.        
  72.         Music = MediaPlayer.create(context, R.raw.intro);
  73.         Music.setLooping(false);
  74.        
  75.         SndPool = new SoundPool(5, AudioManager.STREAM_MUSIC, 0);
  76.         SndBuff[1] = SndPool.load(context, R.raw.arrow, 1);
  77.         SndBuff[2] = SndPool.load(context, R.raw.hit, 1);
  78.  
  79.         for ( i = 0; i < MAX_GAME; i++ ) // 게임 오브젝트 생성
  80.         {
  81.             Game[i] = new GameObject();
  82.             Game[i].dead = true;
  83.             Game[i].show = false;
  84.         }
  85.         for ( i = 0; i < 30; i++ ) Button[i] = new GameObject(); // UI용 오브젝트 생성
  86.     }
  87.  
  88.     public void PlaySound( int num )
  89.     {
  90.         SndPool.play( SndBuff[num], 1f, 1f, 0, 0, 1f );
  91.     }
  92.    
  93.     public void PlayVibe( int val )
  94.     {
  95.         Vibe.vibrate( val );
  96.     }
  97.    
  98.     public void LoadLogoData() // 로고 화면을 보여준다.
  99.     {
  100.         Pattern[0].LoadSprite( gl, MainContext, R.drawable.logo, "back.spr" ); // logo 이미지를 불러와서 back.spr과 연결시킨다.
  101.  
  102.         TotalButton = 0;
  103.         Button[TotalButton].SetObject( Pattern[0], 0, 0, 240, 400, 0, 0 ); // UI용 오브젝트에 로고 스프라이트의 0번 모션, 0번 프레임을 등록한다.
  104.         Button[TotalButton].trans = 0.0f; // 투명도를 0으로 설정해서 서서히 밝아지도록 한다.
  105.         Button[TotalButton++].scroll = false; // 화면 스크롤의 영향을 받지 않도록 한다.
  106.        
  107.         GameMode = 0;
  108.     }
  109.    
  110.     public void LoadGameData()
  111.     {
  112.         Pattern[0].LoadSprite( gl, MainContext, R.drawable.main, "main.spr" ); // main 이미지를 불러와서 main.spr과 연결시킨다.
  113.  
  114.         TotalGame = 0;
  115.         Game[TotalGame].SetObject( Pattern[0], 0, 0, 240, 400, 0, 0 ); // 게임 오브젝트에 배경 스프라이트의 0번 모션, 0번 프레임을 등록한다.
  116.         Game[TotalGame].trans = 0.0f; // 투명도를 0으로 설정해서 서서히 밝아지도록 한다.
  117.         Game[TotalGame].scroll = false; // 화면 스크롤의 영향을 받지 않도록 한다.
  118.         Game[TotalGame++].dead = false; // 살아있는 오브젝트로 선언한다.
  119.        
  120.         TargetTimer = System.currentTimeMillis(); // 타겟 타이머 설정
  121.         ArrowTimer = System.currentTimeMillis() - 1000; // 화살 타이머 설정
  122.         ArrowFlag = FireFlag = false; // 화살 및 발사 플래그 변수
  123.        
  124.         GameMode = 1;
  125.     }
  126.    
  127.     public void PushButton() // 터치가 들어왔을때 호출되는 함수
  128.     {
  129.         int i, k;
  130.         boolean flag;
  131.         float x, y;
  132.        
  133.         if ( GameMode == 0 ) // 로고 화면일 때 아무 처리를 하지 않는다.
  134.         {
  135.            
  136.         }
  137.         else if ( GameMode == 1 ) // 게임 화면일 때
  138.         {
  139.             for ( i = 0; i < TotalButton; i++ ) // 멀티터치 처리영역 : 이 게임에선 쓰지 않음
  140.             {
  141.                 flag = false;
  142.                
  143.                 for ( k = 0; k < 5; k++ )
  144.                 {
  145.                     if ( Key[k].status )
  146.                     {
  147.                         x = Key[k].x * gInfo.ScalePx;
  148.                         y = Key[k].y * gInfo.ScalePy;
  149.                         if ( Button[i].CheckPos((int)x, (int)y) ) { flag = true; break; }
  150.                     }
  151.                 }
  152.                
  153.                 if ( flag )
  154.                 {
  155.                    
  156.                 }
  157.             }
  158.             if ( System.currentTimeMillis()-ArrowTimer >= 500 ) FireFlag = true; // 타이머가 500 밀리세컨을 넘은 상태면 화살 발사가 가능하게 한다.
  159.         }
  160.     }
  161.  
  162.     private void Sort( int start, int count ) // 오브젝트 소팅 함수
  163.     {
  164.         int i = start, w, x, k = count;
  165.  
  166.         x = Sort[(start + count) / 2];
  167.  
  168.         while ( i <= k )
  169.         {
  170.             while ( Sort[i] < x ) i++;
  171.             while ( Sort[k] > x ) k--;
  172.  
  173.             if ( i <= k )
  174.             {
  175.                 w = Sort[i];
  176.                 Sort[i] = Sort[k];
  177.                 Sort[k] = w;
  178.  
  179.                 w = Data[i];
  180.                 Data[i] = Data[k];
  181.                 Data[k] = w;
  182.  
  183.                 i++; k--;
  184.             }
  185.         }
  186.         if ( start <= k ) Sort( start, k );
  187.         if ( i <= count ) Sort( i, count );
  188.     }
  189.    
  190.     private void MakeTarget() // 타겟 생성 함수
  191.     {
  192.         if ( MyRand.nextInt(100) == 0 ) // 1% 확률로 화살을 생성
  193.         {
  194.             if ( System.currentTimeMillis()-TargetTimer >= 1500 ) // 1% 조건이 됐어도 마지막 화살을 생성한지 1.5초가 지나야 새로운 화살 생성이 가능하게 한다.
  195.             {
  196.                 TargetTimer = System.currentTimeMillis(); // 타겟타이머 리셋
  197.                
  198.                 for ( int i = 0; i < MAX_GAME; i++ )
  199.                 {
  200.                     if ( Game[i].dead ) // 죽어있는 오브젝트들 중에서 선택
  201.                     {
  202.                         Game[i].SetObject( Pattern[0], 1, 1, 520, 400, 1, 0 ); // 타겟 오브젝트를 type 1로 , main.spr의 1번 모션, 0번 프레임으로 생성한다.
  203.                         Game[i].scroll = false; // 스크롤의 영향을 받지 않도록 한다.
  204.                         Game[i].dead = false; // 살아있는 오브젝트로 선언
  205.                         return;
  206.                     }
  207.                 }
  208.             }
  209.         }
  210.     }
  211.    
  212.    
  213.     private void MakeArrow() // 화살 생성 함수
  214.     {
  215.         if ( !ArrowFlag )
  216.         {
  217.             if ( System.currentTimeMillis()-ArrowTimer >= 500 ) // 마지막 화살이 발사된 후, 0.5초가 지나면 새로운 화살을 생성한다.
  218.             {
  219.                 for ( int i = 0; i < MAX_GAME; i++ )
  220.                 {
  221.                     if ( Game[i].dead ) // 죽어있는 오브젝트들 중에서 선택
  222.                     {
  223.                         ArrowFlag = true;
  224.                         Game[i].SetObject( Pattern[0], 2, 2, 240, 700, 3, 0 ); // 화살 오브젝트를 type 2로 , main.spr의 3번 모션, 0번 프레임으로 생성한다.
  225.                         Game[i].power = 2000; // 화살이 최초에 날아가는 중력 파워
  226.                         Game[i].scroll = false; // 스크롤의 영향을 받지 않도록 한다.
  227.                         Game[i].dead = false; // 살아있는 오브젝트로 선언
  228.                         return;
  229.                     }
  230.                 }
  231.             }
  232.         }
  233.     }
  234.        
  235.     private void ZoomObject( GameObject sprite, float ratio ) // 오브젝트를 확대시킨다.
  236.     {
  237.         float sx = sprite.x2 - sprite.x1, sy = sprite.y2 - sprite.y1;
  238.  
  239.         sprite.zx += (sx * ratio) - sx;
  240.         sprite.zy += (sy * ratio) - sy;
  241.     }
  242.    
  243.     private void MakePiece( int k ) // 파편 생성 함수
  244.     {
  245.         int i, cnt = 0;
  246.        
  247.         for ( i = 0; i < MAX_GAME; i++ )
  248.         {
  249.             if ( Game[i].dead ) // 죽어있는 오브젝트들 중에서 선택
  250.             {
  251.                 Game[i].SetObject( Pattern[0], 3, 3, Game[k].x, Game[k].y, 2, cnt ); // 파편 오브젝트를 type 3로 , main.spr의 2번 모션, cnt번 프레임으로 생성한다.
  252.                 Game[i].scroll = false; // 스크롤의 영향을 받지 않도록 한다.
  253.                 Game[i].direct = MyRand.nextInt( 360 ); // 파편이 날아갈 각도를 설정한다.
  254.                 Game[i].power = 600 + MyRand.nextInt( 600 ); // 날아갈 속도를 자연스럽게 보이도록 랜덤하게 설정한다.
  255.                 Game[i].PutSprite( gInfo ); // x1,y1,x2,y2 값을 계산한다. (확대를 위해)
  256.                 Game[i].dead = false; // 살아있는 오브젝트로 선언
  257.                
  258.                 if ( ++cnt >= 10 ) return; // 파편이 10개 생성되면 그만둔다.
  259.             }
  260.         }
  261.     }
  262.    
  263.     private void CheckTarget( int num ) // 화살과 타겟의 충돌 체크, num은 화살 오브젝트의 번호
  264.     {
  265.         int i, k;
  266.        
  267.         for ( k = 0; k < TotalGame; k++ ) // 게임에 있는 모든 오브젝트를 검사한다.
  268.         {
  269.             i = Data[k];
  270.            
  271.             if ( Game[i].type == 1 ) // 그중에 타겟 오브젝트만 검사한다.
  272.             {
  273.                 Game[i].PutSprite( gInfo ); // 정확한 계산을 위해 x1,y1,x2,y2를 설정한다.
  274.                
  275.                 if ( Game[num].x >= Game[i].x1 && Game[num].x <= Game[i].x2 ) // 화살이 타겟의 영역에 들어간 상태면
  276.                 {
  277.                     MakePiece( i ); // 파편 조각 생성 함수 호출
  278.                     PlaySound( 2 ); // 사운드 출력
  279.                    
  280.                     Game[i].show = false; // 타겟을 사라지게 한다.
  281.                     Game[i].dead = true; // 타겟을 삭제한다.
  282.                     return;
  283.                 }
  284.             }
  285.         }
  286.     }
  287.  
  288.     private void UpdateGame() // 논리적인 오브젝트 처리 함수, 실제로 화면에 출력하진 않지만, 좌표, 크기 등을 미리 계산한다.
  289.     {
  290.         int i, k;
  291.         float y;
  292.        
  293.         if ( GameMode == 0 ) // 로고 화면일때
  294.         {
  295.             if ( Button[0].move == 0 )
  296.             {
  297.                 Button[0].trans += 0.033f; // 투명도를 서서히 높혀 화면에 나타나게 한다.
  298.                
  299.                 if ( Button[0].trans >= 1 ) // 로고가 모두 나타났으면
  300.                 {
  301.                     Button[0].trans = 1;
  302.                     Button[0].move = 1; // 다음 처리 루틴으로 이동한다.
  303.                     Button[0].atimer = System.currentTimeMillis();
  304.                    
  305.                     Music.start(); // 로고 사운드를 출력한다.
  306.                 }
  307.             }
  308.             else if ( Button[0].move == 1 ) // 로고가 타나난 상태에서 1.5초를 대기한 후 다음 루틴으로 이동한다.
  309.             {
  310.                 if ( System.currentTimeMillis() - Button[0].atimer >= 1500 ) Button[0].move = 2;
  311.             }
  312.             else if ( Button[0].move == 2 ) // 로고를 다시 사라지게 한다.
  313.             {
  314.                 Button[0].trans -= 0.033f; // 서서히 사라지게 한다.
  315.                
  316.                 if ( Button[0].trans <= 0 ) // 전부 사라졌으면 게임을 로딩한다.
  317.                 {
  318.                     LoadGameData(); // 게임 로딩 함수 호출
  319.                 }
  320.             }
  321.         }
  322.         else if ( GameMode == 1 ) // 게임 오브젝트 처리 영역
  323.         {
  324.             if ( !Pause )
  325.             {
  326.                 TotalGame = 0;
  327.                
  328.                 MakeTarget(); // 타겟 오브젝트 생성 함수 호출
  329.                 MakeArrow(); // 화살 오브젝트 생성 함수 호출
  330.  
  331.                 for ( i = 0; i < MAX_GAME; i++ ) // 살아있는 모든 오브젝트를 검색한다.
  332.                 {
  333.                     if ( !Game[i].dead )
  334.                     {
  335.                         Data[TotalGame] = i;
  336.                         Sort[TotalGame] = (int)Game[i].layer;
  337.                         TotalGame++;
  338.                     }
  339.                 }
  340.                 Sort( 0, TotalGame - 1 ); // 소팅한다.
  341.                
  342.                 for ( k = 0; k < TotalGame; k++ ) // 실제로 살아있는 오브젝트들만 처리한다.
  343.                 {
  344.                     i = Data[k];
  345.                    
  346.                     if ( Game[i].type == 0 ) // 배경 화면 오브젝트 처리
  347.                     {
  348.                         if ( Game[i].move == 0 )
  349.                         {
  350.                             Game[i].trans += 0.033f; // 배경이 서서히 보이도록 한다.
  351.                            
  352.                             if ( Game[i].trans >= 1 )
  353.                             {
  354.                                 Game[i].trans = 1;
  355.                                 Game[i].move = 1;
  356.                             }
  357.                         }
  358.                     }
  359.                    
  360.                     if ( Game[i].type == 1 ) // 타겟 오브젝트 처리 루틴
  361.                     {
  362.                         Game[i].x -= 2.0f; // 타겟이 오른쪽에서 왼쪽으로 2도트씩 움직이게 한다.
  363.                        
  364.                         if ( Game[i].x < -50 ) // 화면의 바깥으로 -50 위치 이하로 사라지면 오브젝트를 삭제한다.
  365.                         {
  366.                             Game[i].show = false; // 타겟을 안보이게 하고
  367.                             Game[i].dead = true; // 타겟을 삭제한다.
  368.                         }
  369.                     }
  370.                    
  371.                     if ( Game[i].type == 2 ) // 화살 오브젝트 처리 루틴
  372.                     {
  373.                         if ( Game[i].attack == 0 )
  374.                         {
  375.                             if ( FireFlag && ArrowFlag ) // 터치된 상태(FireFlag)이고, 화살이 생성된(ArrowFlag) 상태이면
  376.                             {
  377.                                 PlaySound( 1 ); // 화살이 날아가는 사운드 출력
  378.                                 Game[i].attack = 1; // 날아가는 루틴으로 이동
  379.                             }
  380.                         }
  381.                         else
  382.                         {
  383.                             y = Game[i].y; // 이전의 좌표를 기억해둔다
  384.                             Game[i].y -= Game[i].power / 100; // 화살이 날아가는 파워에 따라 y 좌표 계산
  385.                             Game[i].power -= 40; // 파워를 점점 감속시킨다.
  386.                             if ( Game[i].power < 500 ) Game[i].power = 500; // 화살의 감속 속도가 최대 500 이하로 내려가지 않게 한다.
  387.                            
  388.                             ZoomObject( Game[i], 0.99f ); // 화살을 10%씩 축소한다.
  389.                            
  390.                             if ( y >= 190 && Game[i].y <= 190 ) CheckTarget( i ); // 이전 좌표와 날아간 좌표가 타겟을 넘어간 상태면 충돌을 체크한다.
  391.                            
  392.                             if ( Game[i].y < 150 ) // y 좌표가 150 이하로 내려가면 화살을 삭제한다.
  393.                             {
  394.                                 ArrowFlag = false; // 새로운 화살을 생성하게 한다.
  395.                                 Game[i].show = false; // 화살을 보이기 않게 하고
  396.                                 Game[i].dead = true; // 화살을 삭제한다.
  397.                             }
  398.                         }
  399.                     }
  400.                    
  401.                     if ( Game[i].type == 3 ) // 터질때 파편들
  402.                     {
  403.                         Game[i].x += (gInfo.SinTBL[Game[i].direct] * (Game[i].power / 100)); // x 좌표를 sin 값에 따라 지정된 각도로 이동한다.
  404.                         Game[i].y -= (gInfo.CosTBL[Game[i].direct] * (Game[i].power / 100)); // y 좌표를 cos 값에 따라 지정된 각도로 이동한다.
  405.                        
  406.                         Game[i].power -= 40; // 날아가는 파워를 감속시킨다.
  407.                         if ( Game[i].power < 100 ) Game[i].power = 100; // 파워가 100 이하로 떨어지지 않게 한다.
  408.                        
  409.                         ZoomObject( Game[i], 0.99f ); // 10%씩 축소시킨다.
  410.                        
  411.                         if ( Game[i].x2 - Game[i].x1 <= 5 ) // 축소되는 이미지의 가로 길이가 5도트 이하가 되면 삭제한다.
  412.                         {
  413.                             Game[i].show = false; // 파편을 안보이게 하고
  414.                             Game[i].dead = true; // 파편을 삭제한다.
  415.                         }
  416.                     }
  417.                     Game[i].PutSprite( gInfo ); // 좌표들을 재설정한다.
  418.                 }
  419.                
  420.                 // 지진 효과 처리 루틴 - 이 게임에선 쓰지 않는다.
  421.                 if ( QuakeTimer >= System.currentTimeMillis() )
  422.                 {
  423.                     gInfo.QuakeY = System.currentTimeMillis() % 2 == 0 ? 3 : -3;
  424.                 }
  425.                 else
  426.                 {
  427.                     gInfo.QuakeY = 0;
  428.                 }
  429.             }
  430.         }
  431.     }
  432.  
  433.     public void DoGame() // 실제 OpenGL 렌더링 영역에서 호출되는 부분
  434.     {
  435.         int i, k;
  436.        
  437.         UpdateGame(); // 오브젝트의 논리적인 좌표 계산을 처리한다.
  438.  
  439.         if ( GameMode == 0 ) // 로고 처리
  440.         {
  441.             Button[0].DrawSprite( gInfo ); // 로고 화면을 실제로 화면에 그린다.
  442.         }
  443.         else if ( GameMode == 1 ) // 게임 처리
  444.         {
  445.             for ( k = 0; k < TotalGame; k++ ) // 살아있는 모든 오브젝트를 그린다.
  446.             {
  447.                 i = Data[k];
  448.                
  449.                 Game[i].DrawSprite( gInfo ); // 실제로 화면에 그린다.
  450.             }
  451.         }
  452.     }
  453. }
  454.