Archive for the ‘Programação de Jogos’ Tag

Gerenciamento de conteúdo (parte 2)

No post passado demonstrei algumas funções para criação e leitura de um pacote de arquivos binários extremamente simples. Neste artigo, iremos colocar as funcionalidades explicadas lá numa classe, e fazer um pequeno gerenciador de conteúdo para que fique fácil recuperar imagens desse arquivo como surfaces SDL.

(NOTA: Pode acessar a parte 1 desta série AQUI)

Inicialmente, vamos olhar a definição da classe responsável por LER arquivos Pack:


class PackReader
{
private:
   FILE            *mFile;
   PackHeader      mHeader;
   PackFileEntry   *mEntries;

public:
   // construtor padrao
   PackReader() : mFile(0), mEntries(0) { }
   ~PackReader() { close(); }

   // abre/fecha arquivo
   void open ( const string &file_name );
   void close ();

   // pega surface SDL
   SDLSurface* getSurface ( const dword id );
};

E agora a implementação:


void PackReader::open ( const string &file_name )
{
   mFile = fopen(file_name.c_str(), "rb");
   if ( !mFile ) { /*ERRO*/ }

   // pega dados do arquivo
   fread(&mHeader, sizeof(PackHeader), 1, mFile);
   mEntries = new PackFileEntry[mHeader.numFiles];
   fread(mEntries, sizeof(PackFileEntry), mHeader.numFiles, mFile);
}

void PackReader::close ()
{
   if ( mFile )
   {
      fclose(mFile);
      mFile = 0;
   }
   memset(&mHeader, 0, sizeof(PackHeader));
   if ( mEntries )
   {
      delete[] mEntries;
      mEntries = 0;
   }
}

SDLSurface* PackReader::getSurface ( const dword id )
{
   if ( !mFile ) { /*ERRO*/ }
   if ( id >= mHeader.numFiles ) { /*ERRO*/ }

   // cria buffer de dados e le do arquivo
   byte *buffer = new byte[mEntries[id].size];
   fseek(pack, mEntries[id].offset, SEEK_SET);
   fread(buffer, sizeof(byte), mEntries[id].size, mFile);
   // transforma em RWOps
   SDL_RWops *rw = SDL_RWFromMem(buffer, mEntries[id].size);
   SDLSurface *surf = <span>IMG_Load_RW ( rw, 1 );</span>

   // apaga dados lidos e retorna imagem
   delete[] buffer;
   return surf;
}

Agora que temos a classe para ler os dados de forma encapsulada, vamos criar o nosso primeiro gerenciador.

Um gerenciador de conteúdo é responsavel por ler e controlar todo o conteúdo de um jogo. Este nosso gerenciador de exemplo vai nos fornecer métodos para ler e liberar surfaces SDL. Iremos nos aprofundar nele e no sistema de arquivamento ao longo dos artigos, implementando diversas técnicas e sistemas de apoio importantes. Por hora, vamos ver o básico:


// estrutura de apoio
struct SurfaceContainer
{
   SDLSurface *surface;
   int        refCount;
};

// o gerenciador em si
class ContentManager
{
private:
   // o nosso leitor de pack
   PackReader                     mPackReader;
   // este map vai conter os conteúdos já em uso
   map<dword,SurfaceContainer*>   mContent;

public:
   ContentManager();
   ~ContentManager();

   void init ();
   SDLSurface *getSurface ( const dword id );
   void releaseSurface ( const SDLSurface *surface );
};

A classe inclui apenas 3 métodos: init(), q vai inicializar o gerenciador, e os dois métodos para pegar e liberar surfaces. Vamos olhar rapidamente a sua implementação:


void ContentManager::init ()
{
   mPackReader.open("arquivo.pck");
}

SDLSurface *ContentManager::getSurface ( const dword id )
{
   // primeiramente, verificamos se já existe esta surface na memória
   map<dword, SurfaceContainer*>::iterator it = mContent.find(id);
   if ( it != mContent.end() )
   {
      // já temos essa surface. Incrementa contador e
      // retorna surface
      SurfaceContainer *container = it->second;
      container->refCount++;
      return container->surface;
   }

   // nao encontramos nenhuma surface, entao vamos ler do arquivo
   SurfaceContainer *container = new SurfaceContainer();
   container.surface = mPackReader.getSurface(id);
   // inicializar contador de referencia
   container.refCount = 1;
   // colocar no map
   mContent[id] = container;
   // e retornar
   return container.surface;
}

void ContentManager::releaseSurface ( const SDLSurface *surface  )
{
   // vamos procurar o container desta surface
   dword id;
   SurfaceContainer *container = 0;
   map<dword, SurfaceContainer*>::iterator it = mContent.begin();
   map<dword, SurfaceContainer*>::iterator itEnd = mContent.end();
   for ( ; it != itEnd; ++it )
   {
      if ( it->second->surface == surface )
      {
         id = it->first;
         container = it->second;
      }
   }

   // checar erros
   if ( !container ) { /*ERRO*/ }

   // decrementa contador e verifica se podemos remover da memoria
   container->refCount--;
   if ( !(container->refCount) )
   {
      mContent.remove(id);
      SDL_FreeSurface(container->surface);
      delete container;
   }
}

Pronto. Este gerenciador apenas controla surfaces SDL a partir de imagens estocadas no nosso arquivo Pack binário. O controle de uso é simples e com alta probabilidade de erro (ie. necessita que o programador lembre de liberar as surfaces), e seus algoritmos são ineficientes.

Porém, agora temos uma estrutura básica em cima da qual poderei explicar e implementar detalhadamente um sistema de empacotamento e gerenciamento de conteúdo de nível profissional. No próximo artigo, irei me aprofundar em algumas técnicas de empacotamento “reais”, para que a partir daí poderemos implementar um empacotador interessante, rápido prático e útil.

Até mais garotada!

Anúncios

Surfaces SDL a partir de dados na memória

Hoje falaremos sobre surfaces SDL, e como criá-las a partir de dados binarios em memória.

Como sempre, estarei usando as seguintes definições de tipos de dados:


typedef unsigned char byte;
typedef unsigned short word;
typedef unsigned int dword;

A criação de surfaces SDL a partir de arquivos de imagem é muito simples. Basta usar a função SDL_LoadBMP ou então a função IMG_Load da biblioteca SDL_Image (para poder ler imagens com outros formatos alem do BMP), como está exemplificado no codigo a seguir:


SDLSurface *surf;

// usando funçao padrao de load da biblioteca SDL 
surf = SDL_LoadBMP("imagem.bmp"); 

// usando funçao de load da biblioteca SDL_Image
surf = IMG_Load("imagem.png");

Porém, se voce usa no teu jogo um formato de arquivo binário que aglomera todos seus arquivos num só (um .dat, por exemplo), não dá para fazer load de uma surface desse jeito. Geralmente, voce extrai os dados binários dessa imagem a partir do arquivo e os coloca em uma posição de memoria. Como criar uma surface a partir desses dados?  A biblioteca SDL inclui estruturas e funções para exatamente este tipo de situação.

Primeiramente, temos a estrutura SDL_RWops. Esta estrutura vai guardar informações para que a SDL possa criar a surface. A SDL tem várias funções para criar uma SDL_RWops a partir de várias fontes, incluindo arquivos, ponteiros para estrutura FILE e, no nosso caso o que iremos usar, de dados da memória. Para tal, usamos a função SDL_RWFromMem(void*,int). Essa função recebe 2 parametros – um ponteiro para a posição de memória do vetor, e o tamanho desse vetor:


SDL_RWops *rw;
rw = SDL_RWFromMem ( (void*)dados, tamanho );

Agora que temos os dados processados, podemos criar a surface usando a função SDL_LoadBMP_RW(SDL_RWops*,int), ou entao a IMG_Load_RW(SDL_RWops*,int). Estas funções recebem 2 parametros também – o ponteiro para a estrutura SDL_RWops, e um inteiro que define se a função vai liberar automaticamente a estrutura passada (valor = 1) ou voce vai fazer isso manualmente (valor = 0).


SDL_Surface *surf;
surf = IMG_Load_RW ( rw, 1 );

Caso se passe 0 como valor, ou seja, a função não irá liberar a memoria da estrutura, usa-se a função SDL_FreeRW(SDL_RWops*) para liberar a memória.

Pronto, agora temos uma surface SDL criada com dados a partir da memória. Agora quero apenas mostrar um código extra que otimiza essa surface, melhorando a performance. Fazemos isso usando a função SDL_DisplayFormat, que transforma a surface que acabamos de criar em uma nova com o mesmo formato da tela:


SDL_Surface *surfOtimizada;
// criando surface otimizada
surfOtimizada = SDL_DisplayFormat ( surf );
// liberando memoria da surface pre-otimizada
SDL_FreeSurface ( surf );

E pronto – temos um código simples. Aqui está o código completo, dentro de uma função de uso fácil:


SDL_Surface* criaSurface ( const byte *dados, const int tamanho )
{
    SDL_RWops *rw;
    SDL_Surface *surf = 0, *surfOtimizada;

    // cria estrutura de controle
    rw = SDL_RWFromMem ( (void*)dados, tamanho );
    // cria surface pre-otimizada
    surf = IMG_Load_RW ( rw, 1 );
    // otimiza surface
    if ( surf )
    {
        surfOtimizada = SDL_DisplayFormat(surf);
        // libera memória de surface pre-otimizada
        SDL_FreeSurface(surf);
    }

    // retorna surface
    return surfOtimizada;
}