Este blog esta em reforma no momento.

Pensando no sistema de update

Nada muito grande mas como se trata de um jogo que pode levar muito tempo até chegar no final uma opção é realmente os updates.
Bom, como abrir um jogo e receber um aviso: "Favor atualizar seu cliente" é uma coisa chata até no cliente do World of Warcraft.

Então vou tentar incorporar a carga durante a execução do jogo. Teoricamente é simples e usando a experiência adquirida com sockets e threading desses últimos messes será "pá-pimba".
Isso também representa problemas como o desses desenhos: do jogador entrar num cenário, sair, fazer uma quest e voltar e encontrar um cenário modificado.

No entanto usando a arquitetura de database fica mais prático fazer isso do que com estruturas de arquivos, além de poder ter um controle de versionamento do jogo nas mãos do jogador!
É um rascunho de ideia meio doida mas coerente.

Estou adicionando essa ideia no documento de de coisas boas e ruins sobre jogos. Lembram-se dele? É uma tabela colaborativa que eu disponibilizei esses tempos aqui.

Como adotei a ideia de banco de dados como estrutura de arquivos, essa estratégia deve ser fácil de ser adotada, nem que represente clonar uma tabela que esta sendo usada em tempo de execução para realizar o update de forma suave.
(Sim tenho um certo vicio em databases, afinal são quase 15 anos de programação web  XP )

Esses tempos eu me estressei com o recurso das "migrates" do framework Rails, e agora estou eu aqui pensando em montar scripts Lua para fazer o mesmo!!!

Teoricamente é só mandar código que automatize a migração de maquinas de estado dentro e fora do jogo. Bom o tempo dirá se essa teoria é de fato tão simples quanto parece.
^^

Distraindo a cabeça com um novo header

Não vai ajudar nada no desenvolvimento, mas vai fazer um bem a mim e bem pro blog mudar um pouco a cara.
Desenhar e projetar um novo header me faz relaxar.
Meu traço e habilidades de desenho taum um lixo. Só dão pro gasto.
To sem ideias de pose, mas a clássica serve também.
Para deixar o desenho um pouco mais bombado resolvi esculpir o relevo da personagem e jogar como um bumpmap para iluminação.
Agora o novo header vai ter uma interação com a fonte de luz (também).
Esse teste de implementação ainda esta pouco animador. Usei um exemplo que achei neste site, mas o algoritmo serve apenas para "luz direcional".
A chave do bump é essa matriz muito simples, até impressiona (se eu soubesse matemaquiques nesse nível estaria feliz!)
Bom, terminando o freela chato, e acertando o ritmo de postagem no RPG Vale eu volto com força pro projeto.

Recebi uns feed-back positivos do projeto essa semana que me deixaram muito feliz e deram um animo ^^

terra brasilis et difficile

Meu ritimo de postagem diminuiu um pouco, por sorte meus acessos não muito.
 

Bom, olhando a imagem acima vocês podem ver que as plantas parasitárias ficaram um lixo.
Então vou deixar a modelagem manual de lado um pouco. Assim que estiver plotando esta arvore no ambiente do game, vou tentar implementar um sistema de particulas para fazer o trabalho.

A ideia de fazer arvores próximas à realidade que eu encontro ainda esta meio confusa e complicada.
Este modelo de arvore ficou com cerca de 1000 triângulos. Algo bem realista, e fácil de aplicar um LOD.

Pode parecer muito polígono, mas essa é uma das coisas que eu sabia que iam aparecer desde antes de começar o projeto oficialmente.

A implementação com a OgreKit ainda esta nos testes, mas pelo visto vai ser ela mesmo. Talvez eu tenho que migrar do Eclipse para o CMake (texto puro), algo que não deve ser tão ruim assim. No final das contas o Eclipse só esta servindo pra corrigir erros de digitação.
Não me acerto em fazer o Code Blocks funcionar e o Visual Studio não mostro nenhuma vantagem sobre o Eclipse.
Estou preparando o projeto de objetos interativos. Essa imagem é um aviso que o jogador terá que fazer algum teste para conseguir decifrar.

Outro passo corrido é o "model sheet" da Cibele. Eu tenho alguns outros estudos dela em alguma pilha de papel mofado mas não no formato SD que irei usar no jogo.

Bom, uma semana e meia de freela dor-de-cabeça atrapalhando tudo, mas os problemas foram pro ralo.

Arvores da floresta de Aracne

Criando árvores gigantes para a cena da Aracne.
A produção da malha textura da árvore esta em andamento, o primeiro passo foi pegar uma textura real de árvore...
Lugar da foto no Google Maps
e transformar em textura ladrilhável com a ferramenta clone e o filtro de "Tornar encaixável".

Dei também um acabamento de desenho à árvore. O processo inclui saturação, posterização e sobreposição de camadas.

O segundo passo foi criar um plano, multiplica-lo com Alt+D (clone). Setar a UV para pegar todo o quadrado e depois sub-dividilo em centenas de vezes. Agora o relevo é feito com o Sculpt.

Já as pequenas trepadeiras foram acertadas no GIMP com auxílio do normalmap plugin.

A imagem gerada no GIMP foi usada no material dentro do Blender, na hora do bake ele é incorporado ao resultado final. Assim temos um mapeamento muito rico com poucos poligonos.

As folhas e galhos menores serão feitos com textura. Do mesmo modo que no Yo Frankie.
Minha árvore esta virando um Frankenstein, mas usar imagens de perto é tradição des-de as primeiras tentativas.

O tipo de mata
Bom, a ideia é criar um ambiente denso, sombrio e de árvores altas. Logicamente tenho dificuldade em achar um lugar como esse como referência.
Ao olhar árvores altas vê-se que os galhos ficam em cima mesmo. Tomei a liberdade de experimentar os modelos com galhos saindo do meio, assim posso utiliza-los para deixar o cenário mais complexo. Podendo Cibele caminhar sobre as árvores.
A árvore é composta de três malhas, a principal, os galhos intermediários e as folhas. Em proporções reais ela tem 40 metros.

Uma mata brasileira seria um problema mesmo para GPUs modernas.
Tentarei posteriormente ver se consigo usar um sistema de carregamento híbrido da gamekit com o carregamento de arquivo mesh normal da OGRE para usufruir dos recursos de LOD da OGRE.

A divisão do tronco da árvore em 2 malhas é para facilitar a física, os galhos menores não devem ser acessíveis (espero).

Páu Mulato - Deve ter uns 25~30 m.

Escrevendo roteiro e pensando em I.A.


Eu aqui pensando reversamente: o personagem, a ação e oque precisaria em forma de código.
Lembra um pouco aquela dos “ditos populares em php”. Mas em fim.

Sou Aracne, não lembro qual nome usei no passado antes de ser esta forma que me conheço agora. Não importa mais.

class CAracne (Char)
CAracne.passado = nil
CAracne.nome = “Aracne”
CAracne.idade = “?”

Aracne = CAracne.new()

Escuro, húmido e gostoso. O sol lá fora não me incomoda, estou feliz na sombra. Ouço os pássaros e os insetos da floresta. Apenas a harmonia escura a sombra. O cheiro do musgo, da casca da árvore e da flor, o cheiro do que me serviu de alimento.... a harmonia me afasta a solidão.

Aracne:attachSensor( Vista:new() )
Aracne:attachSensor( Audio:new() )
Aracne:attachSensor( Olfato:new() )
-- regula parãmetros para o personagem
Aracne.olfato.dist = 5
Aracne.audio.dist = 30 -- 30 metros

-- procura sombra, as áreas de sombra já estão demarcadas
-- no terreno. Escrita como função genérica para ser reaproveitada
-- por outros personagens.
function Char:procuraSombra( )
    local pos = self.phys.position
    local initialDist = 50
    sombra = nil
    for el in mapa:getElementByGroup(“sombra”)
        if el:dist( self ) < initialDist then
            initialDist = el:dist(self)
            sombra = el
        end
    end
    return sombra
end

Mas... que som é este? Se esgueira, é pesado, não é rato, nem coelho. Alguém talves? Comida para mais meses ou um sonho que me despertou?
É cheiro de homem... não tem algo a mais nele me incomodando. É cheiro de fêmea. Humpf, apenas comida.
Está chegando perto,  mais perto, quase perto, perto, AGORA!

function CAracne:main()
    if self.repouso then
        if self.audio:proximidade() or self.visao:proximidade() or self.olfato:proximidade() then
            self.epouso = false
        end
    else
        if self.primeiroAtaque then
            self.animation.set(“camuflagem”)
            local dist, char = minimo( {self.audio.alvo(), self.visao.alvo(), self.olfato.alvo() } )
            if dist <= self.distanciaDeBote then self.bote( char ) end
        else
            self.cacar()
    end
    self.continueAcao() --executa a ação escolhida ou continua a ação corrente que pode ser uma acao de parado, ou movimento
end

Estudando o Gamekit OGRE

http://code.google.com/p/gamekit/

Este projeto me chama atenção, pois já implementa OGRE e Lua numa tacada só.
 
Possui um grupo completo de 7 integrantes em seu trabalho e uma atividade realmente alta no desenvolvimento (até o fechamento deste post eles atualizavam o Google Code todos os dias).

Não tem muita documentação no site, mas ao baixar o source ele vem junto com toda a API de Lua.

Lua
Bastante completa, e com um modelo de programação lindíssimo.
Notei até que possui um objeto de maquina de estado já pronto ^_^.

Nos exemplos que vem no doc a coisa parece bem interessante.

Demo = BaseClass(OgreKit.Engine)
function Demo:constructor()
   self.scene = self:getActiveScene()
end
function Demo:OnUpdate(delta)
         OgreKit.DebugPrint("Demo main loop running ==> " .. self.scene:getName())
end
demo = Demo()
demo:connect(OgreKit.EVT_TICK, demo, Demo.OnUpdate)

C++
Na pasta Engine tem muitos arquivos que indicam as potencialidades da engine.
gkEngine.h mostra que ele usa uma instância de OGRESingleton.
 
E o gkSceneManager parece receber um nome como parâmetro. Apesar de eu ainda não achar na API Lua onde se pode influenciar isso, já é algo promissor.


Dá pra alterar facilmente conforme minhas necessidades?
Olhando com atenção o mecanismo de carga de arquivos, vê-se que ele é bem estruturado em várias classes.
A função loadFile na verdade é apenas um manipulador caso ocorra algum erro.
gkBlendFile *gkBlendLoader::loadFile(const gkString &fname, int options, const gkString &inResourceGroup)
{
       bool resetLoad = false;
       try {

               return loadAndCatch(fname, options, inResourceGroup);
       }

Depois o arquivo é pedido emloadAndCatch:
gkBlendFile *gkBlendLoader::loadAndCatch(const gkString &fname, int options, const gkString &inResourceGroup)
{
       m_activeFile = getFileByName(fname);
       if (m_activeFile != 0)
               return m_activeFile;


       m_activeFile = new gkBlendFile(fname, inResourceGroup);


Ela tem um mecanismo de segurança para evitar que o arquivo em execução atualmente seja recarregado. Não vejo um motivo muito sustentável para essa segurança, mas trata-se de uma engine predestinada a usuário intermediário talvez por isso.

Essa classe de carregamento é na verdade um manipulador de recursos, já que nada mais é que um gerente para uma lista de arquivos.

Indo reversamente ao código acho:
bool gkBlendFile::parse(int opts)
{

       utMemoryStream fs;
       fs.open(m_name.c_str(), utStream::SM_READ);
Esse objeto utMemoryStream tem um nome sinistro pra quem procura um stream de arquivo...
Mas o prefixo indica que esta na pasta Utils então espero que seja mesmo útil XD
class utMemoryStream : public utStream
{
public:
       utMemoryStream();
       ~utMemoryStream();

       void clear(void);

       void open(const char *path, utStream::StreamMode mode);
       void open(const utFileStream &fs, utStream::StreamMode mode);
       void open(const void *buffer, UTsize size, utStream::StreamMode mode);

       bool    isOpen(void)    const   {return m_buffer != 0;}
       bool    eof(void)       const   {return !m_buffer || m_pos >= m_size;}
       UTsize  position(void)  const   {return m_pos;}
       UTsize  size(void)      const   {return m_size;}

       UTsize read(void *dest, UTsize nr) const;
       UTsize write(const void *src, UTsize nr);


       void    seek(const UTsize pos, int dir) const;

       void            *ptr(void)          {return m_buffer;}
       const void      *ptr(void) const    {return m_buffer;}

protected:

       void reserve(UTsize nr);

       char            *m_buffer;
       mutable UTsize  m_pos;
       UTsize          m_size, m_capacity;
       int             m_mode;
};
Notem o método sobrecarregado open, a criança parece "bonbadinha". Mas aqui já parece que estou encontrando as referências a stream de arquivo clássicas, o lance é subistituilas por um manipulador de buffer (que a classe já tem).
void utFileStream::open(const char *p, utStream::StreamMode mode)
{
       if (m_handle != 0 && m_file != p)
               utFileWrapper::close(m_handle);


       m_file = p;
       m_handle= utFileWrapper::open(m_file.c_str(), mode);
       if (m_handle)
       {
               if (!(mode & SM_WRITE))
                       m_size= utFileWrapper::size(m_handle);
       }
}

Vamos ao utFileWrapper, uma função grandinha:
utFileHandle utFileWrapper::open(const char *filename, int mode)
{
#if UT_PLATFORM == UT_PLATFORM_WIN32 && defined(UT_WIN32_FILE)

       DWORD dwDesiredAccess= 0;
       if (mode & utStream::SM_READ)
               dwDesiredAccess |= GENERIC_READ;
       if (mode & utStream::SM_WRITE)
               dwDesiredAccess |= GENERIC_WRITE;

       DWORD dwShareMode= 0;
       if (mode & utStream::SM_READ)
               dwShareMode |= FILE_SHARE_READ;
       if (mode & utStream::SM_WRITE)
               dwShareMode |= FILE_SHARE_WRITE;


       DWORD dwCreationDisposition= OPEN_EXISTING;
       if (mode == utStream::SM_WRITE)
               dwCreationDisposition= CREATE_ALWAYS;

       HANDLE h= ::CreateFile((LPCTSTR)filename, dwDesiredAccess, dwShareMode, 0,
                              dwCreationDisposition, FILE_ATTRIBUTE_NORMAL, 0);

       return h == INVALID_HANDLE_VALUE ? 0 : h;

#else
       char fm[3] = {0,0,0};

       char *mp = &fm[0];

       if (mode & utStream::SM_READ)
               *mp++ = 'r';
       else if (mode & utStream::SM_WRITE)
               *mp++ = 'w';
       *mp++ = 'b';
       fm[2] = 0;
       return fopen(filename, fm);
#endif
}
80% dela é para o manipulador no ambiente Windows (Windows não se divide em módulos bonitinhos e fáceis de organizar) oque não deve fazer muita diferênça já que a ideia é desviar para um manipulador SQLite.

Ok, deste ponto em diante é um stream de arquivo simples. Em teoria basta trocar as chamadas de arquivo por um manipulador de campo BLOB do SQLite (sqlite3_blob_open(), sqlite3_blob_read()...)

Até aqui parece humanamente fácil, pesquizei um pouco mais para ver se não havia como diminuir a possibilidade de criar buffers de dados em excesso. E aparentemente não tem sem ter um arduo trabalho que poderia se justificar em portes para o iPhone (por exemplo a segunda geração conta com 256MB de memória apenas por tanto não se pode esperar muito!)

BlendELF mão na massa: testes e observações

Após uma semana de testes consegui estabelecer um padrão para a engine. E durante a semana postarei um mini-tutorial básico.

E qual a vantagem de se ter uma situação dessas se a GE do Blender é tão forte?
Primeiro eliminar o peso dos dados de edição, segundo ter um mecanismo de teste diferente do Blender.
Há também uma questão de carisma, eu simpatizei imediatamente quando percebi que o autor seguia um caminho paralelo ao meu.

Exportador do Blender
Primeira coisa que chama a atenção é a facilidade, roda-se o exporter e tem-se um cenário completo exportado.

A segunda é que nem todos os atributos suportados na BGE são suportados pela BlendELF, como por exemplo múltiplas camadas de textura. Se quiser usar iluminação é pela GLSL da própria BlendELF mesmo.

O que pra mim não significa tanto, já que todos os materiais tinham que ser setados manualmente quando exportados para a OGRE (não necessariamente uma falha do exporter do OGRE, mas um fator de que a OGRE tem muuuuto mais recursos no seu script de materiais que a BGE).

Na verdade abrindo-se o editor você verá que ele so suporta os mapas: difuse, normal, displace, height e specular.
No entanto o difuse map pode conter o canal alpha.


Quanto à animação por bone, é imprensidivel tudo estar na escala 1x1 (malha e armature) e no mesmo pivo (ponto zero). Se não da pau total na animação (distorções bizarras)

Coding
Parti para o experimento com scripts, há métodos para você capturar os “atores” da cena, e um esquema para vc criar um script lua dentro do Blender e associa-lo à instância do objeto em cena.
No entanto quando chamei uma função global pelo script atrelado à um objeto gerou uma “falha de segmentação”.


O modelo de programação não lembra a orientação a objetos tão bonito que estamos acostumados, em verdade me lembra meus primeiros experimentos de linkagem com Lua o que faz pensar que esta é a “primeira viagem” do autor neste tipo de linkagem.
Ex.: meuAtor = elf.GetActorByName(“zezinho”)

Meu projeto preferi adotar esta forma, onde uma metatable gerada pela linguagem host guarda as funções, muito mais elegante.


Acho que para um objetivo de testes dá pra encapsular usando a implementação de classes para Lua que ando usando no projeto de I.A. assim eu farei:
meuAtor = AtorELF.new()
(onde faço todo o processo elf.GetActorByName....... e também encapsulo todo o sistema necessário)


Animação também tem que ser disparada por código.
elf.LoopEntityArmature( model_body, 1.0, 250.0, 30.0 )
Até aqui nada demais, a atual API Python do Blender não da suporte à muitos recursos.


Detectar colisão é uma tarefa relativamente simples. Mas tive que entrar no forum da BlendELf para saber detalhes de como fazer os testes.
De um modo facil:
function actorCollizionHandler()
  -- loop through the entity collisions
  local actorcolidor = elf.GetActorByName(scn,"SphereCol")
  for i = 0, elf.GetActorCollisionCount(actorcolidor)-1 do
     local col = elf.GetActorCollision(actorcolidor,i)
     local data = getColisionInfo( col )
     print( data.nor.x, data.nor.y, data.nor.z, elf.GetActorName(data.act) )
  end
end


O difícil é saber como manusear essas coisas. Por exemplo o objeto collision retornado por GetActorCollision esta listado na referência da API Lua do próprio site, mas no formato tipoRetorno = elf.NomeDaFuncao( lista de parametros... )

De fato a documentação é bastante escaça, apesar de completa. Tive que fivar vasculhando a API para descobrir quais os elementos eu poderia pegar com as funções do BlendELF e criei a função getColisionInfo(objCollision).


Para matar minhas duvidas e saber qual é a estrutura deste objeto tive que baixar o source de C++ e analisar as estruturas nos headers.
Por exemplo o userdata correspondente ao objeto collision é um ponteiro para esta estrutura descrita no Physics.h
struct elf_collision {
    ELF_OBJECT_HEADER;
    elf_actor *actor;
    elf_vec3f position;
    elf_vec3f normal;
    float depth;
};



Outro fator comprometedor é que não obtive um bom resultado (pelo menos nesses primeiros testes) com colisão em malha animada.

A ideia é jogar uma malha simples em volta do personagem como na imagem, e usa-la como teste de colisão. No entanto a engine não retornou resultados de colisão. Vou fazer mais testes, claro.

Agradecimentos à TiZeta que deixou este modelo para download.

Encapsulamento, levels, shaders
Encapsular os dados parece ser simples, o source da engine é bem pequeno. Oque podera facilitar trocar o sistema de leitura de arquivo por um de leitura de banco de dados (aparentemente muuuito mais facil que reescrever o sistema da OGRE pra isso).

A criação de levels com gargas posteriores esta descrita nos foruns, e exige que se tenha um certo trabalho pesado num gerenciador de recursos em Lua, nada que seja tão negativo ja que é uma engine leve pra jogos casuais.

Shaders, você fica limitado aos existentes na própria engine, a não ser que escreva a implementação em C++ ou C# apartir dos fontes.

Suporte e documentação
A documentação é bem precária, apesar de simples para quem já tem conhecimento em engines 3D/2D.

O forum é bem bombado, tem um numero de acessos bom, e o autor aparente ter tempo e disposição para responder, até perguntas esdruxulas dos usuários.

Na verdade ele foi até bem rápido e atencioso quando lhe perguntei.

Futuro?
É interessante, trata-se de um projeto de faculdade mas caiu num certo carisma da galera da Blender Artists
Em verdade tive 7 visitas neste blog domindo de gente procurando por essa engine.


Se o autor receber incentivo pode ocorrer algo como no caso do Sculptriz, onde o autor foi contratado por uma empresa grande (e infelizmente o programa foi vendido junto).


Conclusão
Com uma qualidade de recursos gráficos inferiores ao OGRE ela acaba sendo muito prática em velocidade pois não ha nada oque configurar.

Desenvolver lógica nela é mais pratico e gostoso que no Python.

Já que usa Lua nativamente posso incorporar ela nos testes do meu jogo, ou seja trabalhando direto no 3D ao invés de fazer testes com a Plataforma Flash 2D.

Mesmo que a longo prazo o criador desista da engine, eu ainda posso usa-la para os testes iniciais do jogo. Ou seja o núcleo pesado que eu estava desenvolvendo pode ficar para depois e eu terei mais animo ao ver as coisas andando na tela.

Downloads
BlendELF do site oficial: BlendELF
O fonte das imagens acima, basta coloca-lo na pasta do BlendELF ou baixar esta versão mini.


Recomendo ^_~