[SDL]Simple DirectMedia Layer API 사용하기 -2
Simple DirectMedia Layer API 사용하기
라이브러리 초기화
라이브러리를 동적으로 로드하고 초기화하기 위해서는 SDL_Init() 를 사용한다. 이 함수는 활성화하고자 하는 부분에 해당하는 플래그들을 취한다. :
SDL_INIT_AUDIO
SDL_INIT_VIDEO
SDL_INIT_CDROM
SDL_INIT_TIMER
라이브러리의 사용을 마쳤으면 SDL_Quit() 를 통해 라이브러리를 정리한다.
예제:
#include <stdlib.h>
#include "SDL.h"
main(int argc, char *argv[])
{
if ( SDL_Init(SDL_INIT_AUDIO|SDL_INIT_VIDEO) < 0 ) {
fprintf(stderr, "Unable to init SDL: %s\n", SDL_GetError());
exit(1);
}
atexit(SDL_Quit);
...
}
Tip:
SDL은 시스템 라이브러리들이 기본적으로 위치한 디렉토리로부터 SDL 라이브러리를 로드한다. 애플리케이션이 배포될 때 함께 제공된 동적 라이브러리들이 별도의 디렉토리로부터 로드되도록 하려면 SDL_SetLibraryPath() 함수를 사용한다.
비디오(Video)
비디오 모드의 선택과 설정 (쉬운방법)
여러분의 맘에 드는 bit-depth 와 해상도를 선택하여 설정한다!
팁 #1:
SDL_GetVideoInfo() 함수를 사용해서 하드웨어에 의해 지원되는 가장 빠른 비디오 depth 를 얻을 수 있다.
팁 #2:
SDL_ListModes() 함수를 사용해서 특정 bit-depth 하에 지원되는 비디오 해상도의 목록을 얻을 수 있다.
예제:
{ SDL_Surface *screen;
screen = SDL_SetVideoMode(640, 480, 16, SDL_SWSURFACE);
if ( screen == NULL ) {
fprintf(stderr, "Unable to set 640x480 video: %s\n", SDL_GetError());
exit(1);
}
}
화면에 점찍기
화면에 그리는 작업은 그래픽 프레임버퍼에 직접 쓴뒤 화면 갱신 함수를 호출함으로써 수행된다.
팁:
만약 화면에 그릴 것이 많다면, 그리기 전에 (필요하다면) 화면을 한번 잠그고 갱신될 영역의 목록을 유지하다가 디스플레이를 갱신하기 전에 다시 화면을 푸는 것이 가장 좋은 방법이다.
예제:
무작위 포맷의 화면에 점찍기
#include
void DrawPixel(SDL_Surface *screen, int x, int y, Uint8 R, Uint8 G, Uint8 B)
{
Uint32 color = SDL_MapRGB(screen->format, R, G, B);
if ( SDL_MUSTLOCK(screen) ) {
if ( SDL_LockSurface(screen) < 0 ) {
return;
}
}
switch (screen->format->BytesPerPixel) {
case 1: { /* 8-bpp 라고 가정 */
Uint8 *bufp;
bufp = (Uint8 *)screen->pixels + y*screen->pitch + x;
*bufp = color;
}
break;
case 2: { /* 아마 15-bpp 아니면 16-bpp */
Uint16 *bufp;
bufp = (Uint16 *)screen->pixels + y*screen->pitch/2 + x;
*bufp = color;
}
break;
case 3: { /* 느린 24-bpp mode, 보통 사용되지 않는다 */
Uint8 *bufp;
bufp = (Uint8 *)screen->pixels + y*screen->pitch + x * 3;
if(SDL_BYTEORDER == SDL_LIL_ENDIAN) {
bufp[0] = color;
bufp[1] = color >> 8;
bufp[2] = color >> 16;
} else {
bufp[2] = color;
bufp[1] = color >> 8;
bufp[0] = color >> 16;
}
}
break;
case 4: { /* 아마 32-bpp */
Uint32 *bufp;
bufp = (Uint32 *)screen->pixels + y*screen->pitch/4 + x;
*bufp = color;
}
break;
}
if ( SDL_MUSTLOCK(screen) ) {
SDL_UnlockSurface(screen);
}
SDL_UpdateRect(screen, x, y, 1, 1);
}
이미지의 로딩과 디스플레이
SDL은 사용자 편의를 위한 SDL_LoadBMP() 이라는 하나의 이미지 로딩 루틴을 제공한다. 이미지 로딩을 위한 라이브러리는 SDL 데모 모음에서 발견할 수 있다.
SDL_BlitSurface() 를 사용해서 그래픽 프레임버퍼로 이미지를 blit 하면 화면에 디스플레이할 수 있다. SDL_BlitSurface() 는 자동적으로 blit 사각형을 클립(clip)하는데, 이 blit 사각형은 SDL_BlitSurface()에 넘겨져 바뀐 화면의 영역이 갱신되도록 한다.
팁 #1:
만약 여러번 그려질 이미지를 로딩하려면, 이미지를 스크린의 포맷으로 변환하여 blit 속도를 향상시킬 수 있다. SDL_DisplayFormat() 함수가 이러한 변환을 해 준다.
팁 #2:
많은 스프라이트 이미지들은 투명한 배경을 가진다. SDL_SetColorKey() 함수를 사용해서 투명한 blit(컬러키 blit)을 할 수 있다.
예제:
void ShowBMP(char *file, SDL_Surface *screen, int x, int y)
{
SDL_Surface *image;
SDL_Rect dest;
/* BMP 파일을 서페이스로 읽어들임 */
image = SDL_LoadBMP(file);
if ( image == NULL ) {
fprintf(stderr, "Couldn't load %s: %s\n", file, SDL_GetError());
return;
}
/* 화면 서페이스에 Blit.
서페이스는 이 시점에서 잠궈져있지 않아야 한다. */
dest.x = x;
dest.y = y;
dest.w = image->w;
dest.h = image->h;
SDL_BlitSurface(image, NULL, screen, &dest);
/* 화면의 변화된 부분을 갱신 */
SDL_UpdateRects(screen, 1, &dest);
SDL_FreeSurface(image);
}
이벤트(Events)
이벤트 기다리기
SDL_WaitEvent() 함수를 사용해서 이벤트를 기다린다.
팁:
SDL 은 국제 키보드를 지원한다. 키 이벤트를 유니코드와 유사한 코드로 바꾸어 event.key.keysym.unicode 에 넣는다. 이러한 작업은 약간의 프로세싱 오버헤드를 감수해야 하기 때문에, SDL_EnableUNICODE() 가 활성화되어야만 사용된다.
예제:
{
SDL_Event event;
SDL_WaitEvent(&event);
switch (event.type) {
case SDL_KEYDOWN:
printf("The %s key was pressed!\n",
SDL_GetKeyName(event.key.keysym.sym));
break;
case SDL_QUIT:
exit(0);
}
}
이벤트를 폴링(Polling)하기
SDL_PollEvent() 함수를 사용해서 이벤트를 폴링한다(검사한다).
팁:
SDL_PeepEvents() 함수에 SDL_PEEKEVENT 를 넘겨 이벤트를 삭제하지 않고도 이벤트 큐에서 이벤트를 확인할 수 있다.
예제:
{
SDL_Event event;
while ( SDL_PollEvent(&event) ) {
switch (event.type) {
case SDL_MOUSEMOTION:
printf("Mouse moved by %d,%d to (%d,%d)\n",
event.motion.xrel, event.motion.yrel,
event.motion.x, event.motion.y);
break;
case SDL_MOUSEBUTTONDOWN:
printf("Mouse button %d pressed at (%d,%d)\n",
event.button.button, event.button.x, event.button.y);
break;
case SDL_QUIT:
exit(0);
}
}
}
이벤트 상태를 폴링하기(Polling)
이벤트를 직접적으로 처리하는 것 뿐만 아니라, 각 이벤트 타입은 애플리케이션 이벤트 상태를 체크할 수 있는 함수를 제공한다. 만약 이 함수를 독점적으로 사용하면, SDL_EventState() 함수로 모든 이벤트를 무시하고, SDL_PumpEvents() 함수를 주기적으로 호출하여 애플리케이션 이벤트 상태를 갱신해야만 한다.
팁:
SDL_ShowCursor() 를 사용해서 시스템 마우스 커서를 감추거나 보이게 할 수 있다.
예제:
{
SDL_EventState(SDL_MOUSEMOTION, SDL_IGNORE);
}
void CheckMouseHover(void)
{
int mouse_x, mouse_y;
SDL_PumpEvents();
SDL_GetMouseState(&mouse_x, &mouse_y);
if ( (mouse_x < 32) && (mouse_y < 32) ) {
printf("Mouse in upper left hand corner!\n");
}
}
사운드(Sound)
오디오 장치 열기
여러분은 여러분의 오디오 데이타를 혼합하고 그것을 오디오 스트림으로 보내는 콜백 함수를 만들어야 한다. 그 다음엔, 원하는 오디오 포맷과 속도를 선택하고, 오디오 장치를 연다.
SDL_PauseAudio(0) 를 호출할때까지 오디오는 실제로 재생되지 않는다. 이 함수는 여러분의 콜백 함수가 실행되기 전에, (필요하다면) 다른 오디오 초기화작업을 수행할 수 있도록 한다. 사운드 출력을 마친 다음, SDL_CloseAudio() 함수를 사용해 오디오 출력을 닫아야 한다.
팁:
만약 여러분의 애플리케이션이 다른 오디오 포맷을 처리할 수 있다면, 두번째 SDL_AudioSpec 포인터를 SDL_OpenAudio() 에 보내 실제 하드웨어 오디오 포맷을 취하도록 해야 한다. 만약 두번째 포인터를 NULL로 남겨놓는다면, 오디오 데이타는 실행시에 하드웨어 오디오 포맷으로 변환될 것이다.
예제:
#include "SDL.h"
#include "SDL_audio.h"
{
extern void mixaudio(void *unused, Uint8 *stream, int len);
SDL_AudioSpec fmt;
/* 16-bit 스테레오 오디오를 22Khz 로 설정한다. */
fmt.freq = 22050;
fmt.format = AUDIO_S16;
fmt.channels = 2;
fmt.samples = 512; /* 게임을 위해 적당한 값 */
fmt.callback = mixaudio;
fmt.userdata = NULL;
/* 오디오 장치를 열고 사운드를 재생하기 시작한다! */
if ( SDL_OpenAudio(&fmt, NULL) < 0 ) {
fprintf(stderr, "Unable to open audio: %s\n", SDL_GetError());
exit(1);
}
SDL_PauseAudio(0);
...
SDL_CloseAudio();
}
사운드 로딩과 재생
SDL 은 사용자 편의를 위해 하나의 사운드 로딩 루틴인 SDL_LoadWAV() 를 제공한다. 사운드를 로딩한 뒤에 SDL_ConvertAudio() 를 사용해서 사운드 출력에 맞는 오디오 포맷으로 변환하여 여러분의 혼합(mixing) 함수에 사용될 수 있게 해야 한다.
팁:
SDL 오디오 기능은 저수준 소프트웨어 오디오 믹서를 위해 설계되었다. 완벽한 믹서 구현 예제는 LGPL 라이센스 하에 SDL 데모 모음을 통해 제공된다.
예제:
#define NUM_SOUNDS 2
struct sample {
Uint8 *data;
Uint32 dpos;
Uint32 dlen;
} sounds[NUM_SOUNDS];
void mixaudio(void *unused, Uint8 *stream, int len)
{
int i;
Uint32 amount;
for ( i=0; i
if ( amount > len ) {
amount = len;
}
SDL_MixAudio(stream, &sounds[i].data[sounds[i].dpos], amount, SDL_MIX_MAXVOLUME);
sounds[i].dpos += amount;
}
}
void PlaySound(char *file)
{
int index;
SDL_AudioSpec wave;
Uint8 *data;
Uint32 dlen;
SDL_AudioCVT cvt;
/* 비어있는(또는 끝난) 사운드 슬롯을 찾는다. */
for ( index=0; index
break;
}
}
if ( index == NUM_SOUNDS )
return;
/* 사운드 파일을 로드하여 22kHz의 16-bit 스테레오로 변환한다. */
if ( SDL_LoadWAV(file, &wave, &data, &dlen) == NULL ) {
fprintf(stderr, "Couldn't load %s: %s\n", file, SDL_GetError());
return;
}
SDL_BuildAudioCVT(&cvt, wave.format, wave.channels, wave.freq,
AUDIO_S16, 2, 22050);
cvt.buf = malloc(dlen*cvt.len_mult);
memcpy(cvt.buf, data, dlen);
cvt.len = dlen;
SDL_ConvertAudio(&cvt);
SDL_FreeWAV(data);
/* 사운드 데이타를 슬롯에 넣는다(곧바로 재생된다). */
if ( sounds[index].data ) {
free(sounds[index].data);
}
SDL_LockAudio();
sounds[index].data = cvt.buf;
sounds[index].dlen = cvt.len_cvt;
sounds[index].dpos = 0;
SDL_UnlockAudio();
}
CD-ROM 오디오
사용하기 위해 시디롬 드라이브를 열기
SDL_CDNumDrives() 함수를 통해 시스템에서 사용할 수 있는 시디롬 드라이브 갯수를 알 수 있으며, SDL_CDOpen() 를 사용해서 그 중 하나를 사용할 수 있다.
시스템 디폴트 시디롬은 항상 드라이브 0 이다. 만약 드라이브에 디스크가 없더라도 사용하기 위해 드라이브를 열 수 있다.
SDL_CDStatus() 함수를 사용해서 드라이브의 상태를 확인해야 한다. 시디롬 드라이브의 사용을 마쳤다면 SDL_CDClose() 함수를 사용해서 닫는다.
팁:
SDL_CDName() 함수를 사용해서 시디롬 드라이브의 시스템 독립적인 이름을 얻을 수 있다.
예제:
{
SDL_CD *cdrom;
if ( SDL_CDNumDrives() > 0 ) {
cdrom = SDL_CDOpen(0);
if ( cdrom == NULL ) {
fprintf(stderr, "Couldn't open default CD-ROM: %s\n" SDL_GetError());
return;
}
...
SDL_CDClose(cdrom);
}
}
시디롬 재생하기
시디롬 드라이브는 MSF 포맷(분/초/프레임, mins/secs/frames) 또는 직접적으로 프레임을 사용해서 시간을 지정한다. 한 프레임은 시디 시간의 표준 단위이며, 1/75 초에 해당한다. SDL 은 트랙길이와 오프셋을 지정할 때 MSF 포맷 대신에 프레임을 사용하지만, FRAMES_TO_MSF() 과 MSF_TO_FRAMES() 매크로를 사용해서 변환할 수 있다.
SDL 은 SDL_CDStatus() 를 호출할 때까지 SDL_CD 구조의 트랙 정보를 갱신하지 않기 때문에, 항상 시디를 재생하기 전에 SDL_CDStatus() 를 사용해서 드라이브에 시디가 있는지 또, 어떤 트랙이 가용한지를 확인해야 한다. 트랙 인덱스는 첫번째 트랙이 0 부터 시작한다는 것을 주의하자.
SDL 은 시디롬을 재생하기 위해 두가지 함수를 제공한다. SDL_CDPlayTracks() 를 사용해서 시디상의 특정 트랙을 재생할 수도 있고 SDL_CDPlay() 를 사용해서 절대 프레임 오프셋을 재생할 수도 있다.
SDL은 시디삽입 또는 재생완료를 자동으로 감지하는 기능을 제공하지 않는다. 이러한 상황을 감지하기 위해서는, SDL_CDStatus() 를 사용해 주기적으로 드라이브의 상태를 확인해야 한다. 이 함수들이 시디의 목차를 읽기 때문에 짧은 루프안에서 계속적으로 호출되지 않도록 해야 한다.
팁:
여러분은 cdrom->tracks[track].type 를 보고 SDL_AUDIO_TRACK 인지 SDL_DATA_TRACK 인지 비교하여 어떤 트랙이 오디오 트랙이며 어떤 트랙이 데이타 트랙인지 확인해야 한다.
예제:
void PlayTrack(SDL_CD *cdrom, int track)
{
if ( CD_INDRIVE(SDL_CDStatus(cdrom)) ) {
SDL_CDPlayTracks(cdrom, track, 0, track+1, 0);
}
while ( SDL_CDStatus(cdrom) == CD_PLAYING ) {
SDL_Delay(1000);
}
}
쓰레드(Threads)
간단한 쓰레드 만들기
SDL_CreateThread() 에 여러분의 함수를 넘겨줌으로써 쓰레드를 만들 수 있다. SDL_CreateThread()가 성공적으로 리턴된다면, 여러분의 함수는 이제 애플리케이션의 나머지 부분동안에 동시에 수행된다. 자신의 실행 컨텍스트(스택, 레지스터, 등등)를 가지며, 애플리케이션의 나머지 부분에 의해 사용되는 메모리와 파일 핸들을 액세스할 수도 있다.
팁:
SDL_CreateThread() 의 두번째 인수는 쓰레드 함수에 대한 인수로 넘겨진다. 이 인수를 통해 스택상에 데이타 자체를 넘겨줄 수도 있고, 쓰레드에 의해 사용될 데이타에 대한 포인터로 사용할 수도 있다.
예제:
#include "SDL_thread.h"
int global_data = 0;
int thread_func(void *unused)
{
int last_value = 0;
while ( global_data != -1 ) {
if ( global_data != last_value ) {
printf("Data value changed to %d\n", global_data);
last_value = global_data;
}
SDL_Delay(100);
}
printf("Thread quitting\n");
return(0);
}
{
SDL_Thread *thread;
int i;
thread = SDL_CreateThread(thread_func, NULL);
if ( thread == NULL ) {
fprintf(stderr, "Unable to create thread: %s\n", SDL_GetError());
return;
}
for ( i=0; i<5; ++i ) {
printf("Changing value to %d\n", i);
global_data = i;
SDL_Delay(1000);
}
printf("Signaling thread to quit\n");
global_data = -1;
SDL_WaitThread(thread, NULL);
}
리소스에 대한 액세스 동기화
뮤텍스(mutex)를 생성하고 잠금(SDL_mutexP())과 해제(SDL_mutexV())를 통해 하나 이상의 쓰레드가 하나의 리소스를 액세스하는 제한할 수 있다.
팁:
하나이상의 쓰레드에 의해 액세스할 수있는 모든 데이타는 뮤텍스(mutex)에 의해 보호되어져야 한다.
예제:
#include "SDL_thread.h"
#include "SDL_mutex.h"
int potty = 0;
int gotta_go;
int thread_func(void *data)
{
SDL_mutex *lock = (SDL_mutex *)data;
int times_went;
times_went = 0;
while ( gotta_go ) {
SDL_mutexP(lock); /* potty 를 잠근다 */
++potty;
printf("Thread %d using the potty\n", SDL_ThreadID());
if ( potty > 1 ) {
printf("Uh oh, somebody else is using the potty!\n");
}
--potty;
SDL_mutexV(lock);
++times_went;
}
printf("Yep\n");
return(times_went);
}
{
const int progeny = 5;
SDL_Thread *kids[progeny];
SDL_mutex *lock;
int i, lots;
/* 동기화를 위한 lock 생성 */
lock = SDL_CreateMutex();
gotta_go = 1;
for ( i=0; i
}
SDL_Delay(5*1000);
SDL_mutexP(lock);
printf("Everybody done?\n");
gotta_go = 0;
SDL_mutexV(lock);
for ( i=0; i
printf("Thread %d used the potty %d times\n", i+1, lots);
}
SDL_DestroyMutex(lock);
}
타이머(Timers)
현재 시간을 천분의 1초 단위로 얻는다
SDL_GetTicks() 는 지난 임의의 시간으로부터 지금까지 몇 밀리세컨드(milliseconds)가 지났는지 알려준다.
팁:
일반적으로, 게임을 구현할 때 프레임률보다는 시간에 기반하여 게임상의 객체를 움직이는 것이 더 낫다. 그렇게 하면 빠른 시스템이나 느린 시스템이나 동일한 속도로 게임을 즐길 수 있다.
예제:
#define TICK_INTERVAL 30
Uint32 TimeLeft(void)
{
static Uint32 next_time = 0;
Uint32 now;
now = SDL_GetTicks();
if ( next_time <= now ) {
next_time = now+TICK_INTERVAL;
return(0);
}
return(next_time-now);
}
지정된 밀리세컨드만큼 기다리기
SDL_Delay() 는 몇 밀리세컨드만큼 기다리게 한다.
SDL이 지원하는 운영체제들은 멀티태스킹 운영체제이기 때문에, 애플리케이션이 요구된 시간만큼 정확히 지연된다는 보장을 할 수 없다. 이 함수는 특정 시간에 다시 진행되어야 하는 경우보다는 잠깐동안 정지해있어야 하는 경우에 더 적합하다.
탑:
대부분의 운영체제들은 약 10ms 정도의 스케줄러 시간조각(timeslice)을 가진다. SDL_Delay(1) 는 현재의 시간조각에 해당하는 시간조각을 희생하여 다른 쓰레드가 실행될 수 있도록 사용된다. 이 함수는 아주 빨리 돌아가는 루프의 쓰레드가 있을 때 다른 쓰레드(오디오 같은)도 함께 실행되어야 한다면 중요하다.
예제:
{
while ( game_running ) {
UpdateGameState();
SDL_Delay(TimeLeft());
}
}
엔디안 비의존성(Endian independence)
현재 시스템의 엔디안(endianness)을 결정하기
C 전처리기(preprocessor)는 SDL_BYTEORDER 를 현재 시스템의 바이트 순서(byte order)에 따라 SDL_LIL_ENDIAN 또는 SDL_BIG_ENDIAN 로 정의한다.
리틀 엔디안(little endian) 시스템은 아래와 같은 순서로 데이타를 디스크에 쓴다 :
[lo-bytes] [hi-bytes]
빅 엔디안(big endian) 시스템은 아래와 같은 순서로 데이타를 디스크에 쓴다 :
[hi-bytes] [lo-bytes]
팁:
x86시스템들은 리틀 엔디안이며, PPC 시스템들은 빅 엔디안이다.
예제:
#include "SDL_endian.h"
#if SDL_BYTEORDER == SDL_LIL_ENDIAN
#define SWAP16(X) (X)
#define SWAP32(X) (X)
#else
#define SWAP16(X) SDL_Swap16(X)
#define SWAP32(X) SDL_Swap32(X)
#endif
다른 엔디안의 시스템들간의 데이타 변환
SDL 은 SDL_endian.h 에 빠른 매크로들을 제공한다. SDL_Swap16() 와 SDL_Swap32() 함수는 데이타의 엔디안을 스왑(swap)시킨다. 또한 특정 엔디안의 데이타를 로컬 시스템의 엔디안으로 변환시키는 매크로도 제공한다.
팁:
만약 시스템의 바이트-순서(byte-order)만 알고 싶고, 모든 스왑 함수들은 필요없다면, SDL_endian.h 대신에 SDL_byteorder.h 를 인클루드하라.
예제:
#include "SDL_endian.h"
void ReadScanline16(FILE *file, Uint16 *scanline, int length)
{
fread(scanline, length, sizeof(Uint16), file);
if ( SDL_BYTEORDER == SDL_BIG_ENDIAN ) {
int i;
for ( i=length-1; i >= 0; --i )
scanline[i] = SDL_SwapLE16(scanline[i]);
}
}