음... 아마 이번만 하면.. 솔직히 왠만한 간단한 게임 만들면서 NDS 에 관한 내부 구조 및 코딩방법은 파악이 될듯 싶네요

게임 사운드... 게임을 구성하는데 중요한 요소이지만, 게임 프로그래밍을 배우는 사람들에게 있어서 무시하기 쉬운 부분이기도 합니다.

일단 사운드는.. 함수 자체가 출력.. (방법 정의 ex ) loop 로 돌릴꺼야(배경음), 한번 출력하고 말꺼냐(효과음)) 정도이다 보니, 그래픽을 다루는것에 비해 상당히 간단하죠..
그래서 인지 PAlib 에서도 sound 예제는 상당히 대충 대충.. orz

사운드 삽입하는건 어렵지 않지만 그 방법이 좀 만만치 않습니다. (이거 삽질에 2일 걸렸던..)
일단 기존에 하던거에서 사운드를 출력해서 뭔가... 뭔가.. 좀 있는 느낌을 줘봅시다.

일단 switch 라는 프로그램을 다운로드 합시다.
http://www.palib.info/wiki/doku.php?id=day7ko 에 소개되있는.. 이 라이브러리 제작자가 추천하는 프로그램입니다.
실제 다운로드는 http://www.nch.com.au/switch/ 여기서 합니다.
일단 설치하면.... 아래와 같은게 뜹니다..

사용자 삽입 이미지
자기네 회사꺼 다른 프로그램 더 설치할꺼냐 하는데.. wavepad 는 사운드 편집기로 쓸만합니다.. 나머지는;;; 선택적..

설치가 끝나면 아래와 같은 switch 프로그램이 뜹니다.
사용자 삽입 이미지

이제 NDS에 넣을 음악을 고릅니다.
저는... 몇일전 일본에 가서 겨우 구한 미즈키 나나의 Starcamp 앨범안의 3번 트랙 Dancing in the velvet moon 를 넣어 보겠습니다.
사용자 삽입 이미지

mp3 파일이든 wav 파일이든 만든뒤 switch에 Add Files 버튼으로 추가합니다.
그리고 아래의 Output Format 을 .raw 로, Encoder options... 를 그림과 같이 설정하고 컨버팅 합니다.
사용자 삽입 이미지


컨버팅을 한 뒤 결과물을 project 폴더 내의 data 폴더에 넣습니다.
사용자 삽입 이미지

주의점으로.. 파일을 있는데로 다 끌어 쓰면 안됩니다.
이 NDS 프로그래밍에서는 ROM 파일의 용량이 4MB로 한정 되 있어서 (아무래도 롬파일을 NDS 메모리에 모두 적재 하고 실행시키는 구조인가 봅니다... 참고로 원래 NDS 칩은 128MB 의 용량을 가지고 있습니다)

사운드 파일 크기든, 다 합해서 4MB가 초과 되면 컴파일이 안됩니다.
위의 WavePad 로 적절히 끊어서 저장해서 raw 파일 용량을 줄이던지 해야 합니다.


경험상..  여기 밑에 포스트 되있는 제 졸업작품경우... 그림 + 알고리즘 소스가 1MB, 사운드 3MB 됩니다.
(소스야.. 원래 모바일 WIPI 소스다 보니 최적화를 잘 시켜놔서 별로 용량이 안됩니다)

여하튼...
그 뒤 Visual Stdio 상에서 아래와 같이 사운드 파일을 추가해주고
#include "사운드파일 이름.h" 로 선언해 줍니다.
사용자 삽입 이미지

이후 소스 입니다. 역시 새로 추가한 부분만 빨간색입니다.
//////////////////////////////////////////////////////
// Includes
#include <PA9.h>       // Include for PA_Lib
#include "gfx/all_gfx.c"
#include "gfx/all_gfx.h"
#include "velvet_moon.h"

#define UP_SCREEN 1
#define DOWN_SCREEN 0

#define BG0   0
#define BG1   1
#define BG2   2
#define BG3   3

#define SPRITE_MENU_PAL  0
#define SPRITE_POINT_PAL 1

#define SPRITE_MENU   0
#define SPRITE_POINT  1

#define TRUE 1
#define FALSE 0

// Video ram 의 모든 그림을 제거 하는 함수
void UnLoad_Screen()
{
 int i;

 for(i=0; i<2; ++i)
 {
  PA_ResetBgSysScreen(i);
  PA_ResetSpriteSysScreen(i);
 }
}

// 현재 출력중인 사운드를 모두 끈다
void UnLoad_Sound()
{
 int i;
 
 for(i=0; i<16; ++i)
  PA_StopSound(i);
}

int main()
{
 PA_Init();    // Initializes PA_Lib
 PA_InitVBL(); // Initializes a standard VBL

 // Init AS_Lib for normal sound playback only
 PA_InitASLibForSounds(AS_MODE_SURROUND | AS_MODE_16CH);

 PA_InitText(UP_SCREEN, BG2);
 PA_OutputSimpleText(UP_SCREEN, 1, 2, "Hello World!");

 // 배경 화면 로드
 PA_EasyBgLoad(UP_SCREEN, BG3, bg2);
 PA_EasyBgLoad(DOWN_SCREEN, BG2, bg_charactor);
 PA_EasyBgLoad(DOWN_SCREEN, BG3, bg1);

 // 스프라이트 준비 1. 팔레트 설정
 PA_LoadSpritePal(DOWN_SCREEN, SPRITE_MENU_PAL, (void*)Sprite_Init_Menu_Pal);
 PA_LoadSpritePal(DOWN_SCREEN, SPRITE_POINT_PAL, (void*)Sprite_Point_Pal);

 // 스프라이트 준비 2. 실제 그림 생성
 PA_CreateSprite(DOWN_SCREEN, SPRITE_MENU,
    (void*)Sprite_Init_Menu_Sprite, OBJ_SIZE_64X64,
    TRUE, SPRITE_MENU_PAL, 10, 10);

 PA_CreateSprite(DOWN_SCREEN, SPRITE_POINT,
    (void*)Sprite_Point_Sprite, OBJ_SIZE_32X32,
    TRUE, SPRITE_POINT_PAL, 80, 10);

 PA_CreateSprite(DOWN_SCREEN, SPRITE_POINT+1,
    (void*)Sprite_Point_Sprite, OBJ_SIZE_32X32,
    TRUE, SPRITE_POINT_PAL, 80, 40);

 // 스프라이트 준비 3. 몇몇 변경
 PA_SetSpriteAnim(DOWN_SCREEN, SPRITE_MENU, 2);
 PA_SetSpriteHflip(DOWN_SCREEN, SPRITE_POINT, TRUE);

 s16 x =0, y = 0;

 AS_SoundDefaultPlay((u8*)velvet_moon, (u32)velvet_moon_size, 127, 64, true, 0);
 // Infinite loop to keep the program running
 while (1)
 {
  // 배경 움직이기
  PA_EasyBgScrollXY(UP_SCREEN, BG3, --x, ++y);
  PA_EasyBgScrollX(DOWN_SCREEN, BG3, x);

  // 스타일러스 팬 위치에 스프라이트를 이동 시키기.
  PA_SetSpriteXY(DOWN_SCREEN, SPRITE_MENU, Stylus.X, Stylus.Y);
  PA_WaitForVBL();
 }

 UnLoad_Screen();  // 리소스 정리
 UnLoad_Sound();
 return 0;
}

//////////////////////////////////////////////////////

음.. 각각 설명입니다..
#include "velvet_moon.h"

위에서 설명되있듯, 사운드 파일이 있음을 컴파일러에게 알려주는 역활을 합니다.
밑의 Makefile 을 분석하시면 대략 아실꺼라 생각합니다.

요약하면, 컴파일러가 저 velvet_moon.h 가 있는지 보고, 당연히 선언이 안되어있으니
raw, jpg, gif ... 등 확장자를 바꿔가면서 /data/ 디렉토리에 해당 파일이 있는지 봅니다.
그리고 관련된 파일 (raw 파일 있으니 이거겠죠..)이 있으면 파일이름.h 헤더를 생성하고
거기에 대략 맞는(선언 위주의) 헤더를 생성합니다.

// 현재 출력중인 사운드를 모두 끈다
void UnLoad_Sound()
{
 int i;
 
 for(i=0; i<16; ++i)
  PA_StopSound(i);
}
위 소스는 제가 만든겁니다.
현 게임 상황에서 다음 게임 상황 (Menu state 에서 Game state 로 넘어갈때) 사운드가 바뀌지 않아
아예 다 꺼버리는 매크로 같은겁니다.
(즉.. 사운드는 0~15번까지 각각 재생이 된다는거라 생각합니다만.. 확실치는... orz
다만 0~7번이 효과음, 8~15번이 배경음 으로 지정되 있는거 같습니다)

 PA_InitASLibForSounds(AS_MODE_SURROUND | AS_MODE_16CH);
이 소스는 사운드를 출력할태니 준비하라는 초기화 API 입니다.
당연히 이걸 선언 안하면 사운드가 출력이 안되겠죠.

 AS_SoundDefaultPlay((u8*)velvet_moon, (u32)velvet_moon_size, 127, 64, true, 0);
위 소스로 사운드를 실행 시킵니다.
대략 AS_SoundDefaultPlay( (u8*) 사운드 파일 이름,
                                       (u32) 사운드 파일 이름_size,
                                       127, 64  (사운드 화음 같은거 같네요..)
                                        true, (이건 루프를 돌릴껀지 선택하는겁니다),
                                        0);
음... 사운드 파일 이름은.. 위에서 컴파일러가 자동으로 만들어 주는 "사운드파일.h" 에 선언되 있습니다.
그런이유로 컴파일 에러가 나진 않죠..

여튼 컴파일 해서 이상이 없으면 아래와 같은 전에 했던 작업에
사운드 파일이 재생되는것을 들으실 수 있습니다.

사용자 삽입 이미지

음.. 대략.. 이걸로 환경조성하고, 배경띄우고, 스프라이트 조작하고, 사운드를 넣어보았습니다.
기본적으로 NDS 게임 프로그래밍은 이거를 조금더 풀어쓴다고 생각하시면 될꺼 같네요
(여기 있는게, MFC 같은 캡슐화된 메소드들이고, 실제는 여기서 좀 풀어쓴 API 급 정도..)

게임의 기본 루프는

fn_game1()
{
     // 메모리 청소 및 초기화
     ...
 
     // 화면에 띄울것 메모리에 Load
     ...

     // 게임 알고리즘 셋팅
     ...

     while( 탈출 변수 )
     {
           // 화면 움직임 처리
           ...
           //  입력 감지 및 처리
           switch(입력?)
           {
                 case 1 :  fn_game_1();  break;
                 case 2 :  fn_game_2();  break;
                 .....
            }
      }
       // 메모리 청소
}

등으로 구성 됩니다.
이 정도면.. 간단한 2D 정도는 문제 없을꺼라 생각되네요.

신고
블로그 이미지

프로그래머 지향자 RosaGigantea

바쁜 일상 생활중의 기억 장소

티스토리 툴바