melonJS – Desenvolvendo Jogos para HTML5 – 1ª Parte


Com o HTML5 sendo cada vez mais bem suportado pelos navegadores, um leque de novas aplicações vem surgindo utilizando a potencialidade dessa nova versão da linguagem. Meu principal interesse é no desenvolvimento de jogos online, uma área que até então era dominada tão somente pela tecnologia proprietária Flash.

É verdade que a muito tempo que existem implementações de jogos web utilizando javascript, ou até mesmo applets java, mas somente agora podemos bater de frente com bons recursos de processamento gráfico e outras perfumarias a mais, chegando ao ponto de muitos já terem decretado a morte do Flash. Não vou chegar a tanto, pois creio que as duas tecnologias possam coexistir pacificamente. Quem optar por ter suas aplicações rodando em qualquer dispositivo que tenha um navegador web, sem precisar instalar nenhum aplicativo adicional, optará por utilizar  a dupla HTML5/Javascript, os que ainda preferirem ficar presos a uma tecnologia proprietária, fechada, e que demanda a instalação de players para poder rodar (não em qualquer lugar), vai continuar a utilizar o Flash. Mas vamos ao que interessa.

Depois de algum tempo pesquisando entre engines de jogos em javascript, entre proprietárias e livres, acabei encontrando a  melonJS, que além de atender às minhas necessidades básica tem uma boa integração com o editor de mapas Tiled Map Editor, uma mão na roda para agilizar a construção de fases para os jogos. É uma engine muito boa, apesar de estar em sua fase inicial acredito que tem um grande potencial.

Esse tutorial vai ser publicado em duas partes, devido a extensão do mesmo, e trata-se de uma livre adaptação do tutorial disponível no site da biblioteca publicado aqui, é basicamente um apanhado geral da utilização da engine para a construção de jogos web, portanto não se entrará em muitos detalhes sobre o funcionamento das funções utilizadas, deixaremos isso para mais tarde a fim de não estender muito o post, ademais a documentação de classes da engine é muito boa e pode ser consultada online a qualquer momento aqui. Sugiro seguir o tutorial com essa documentação aberta para qualquer coisa que não fique bem entendida.

Nessa primeira parte veremos o básico para a criação de uma fase do jogo, e na segunda parte iremos implementar mais funcionalidades como pontuação, tela de menus, áudio, novas fases, etc.

As dependências.

Para seguir esse tutorial, além da engine propriamente dita tu vai precisar ter instalado o Tiled Map Editor; para quem usa o Ubuntu existe um ppa disponível, então basta adicionar e instalar com o apt-get:

$ sudo apt-get install tiled

Criei um repositório no git para esse tutorial para compartilhar os recursos utilizados (áudio, imagens, biblioteca, etc.),  e um esqueleto da estrutura de arquivos, basta clonar o repositório, ou baixar o pacote clicando aqui.

Mas tu pode ficar a vontade para utilizar os recursos que quiser, e caso tu seja que nem eu que não tem talento nenhum para artes gráficas, tu pode dar uma passada no Open Game Art que tem diversos materiais livres para o desenvolvimento de jogos prontos, além de alguns efeitos sonoros, tu também pode encontrar bastante material no Open Clipart é bem genérico e tem uma infinidade de gráficos para a utilização livre, com a vantagem de estarem todos no formato SVG, uma mão na roda para edição com o Inkscape, eu peguei alguns gráficos de lá para construir o meu tileset e as sprites para esse tutorial, e um bom lugar para encontrar áudio é o CCMixter.

Vamos por a mão na massa então.

Esse post não pretende ser um tratado completo sobre a criação de jogos, mas vamos tentar seguir uma linha básica de desenvolvimento. Dentro das limitações impostas pela falta de criatividade conhecimento na utilização de ferramentas gráficas,  eu lhes apresento:

1 – A História do Jogo

Indiana Tux é um arqueólogo aventureiro que está a procura do Grande Diamante Vermelho, uma joia raríssima, mas para isso ele vai ter que entrar no território dos Macacos Zumbis Radioativos. Enquanto ele coleta joias e tesouros pelo caminho, ele tem que se esquivar da macacada maldita que fará de tudo para impedir que nosso herói tenha sucesso em sua empreitada.

OK que o personagem não é nem um pouco original, e que a história e meio fru-fru, mas serve para o propósito.

2 – Criando uma fase utilizando Tiled Map Editor.

Vamos então partir para a segunda etapa do projeto que é a criação das fases do jogo utilizando a ferramenta Tiled Map Editor. Primeiramente será necessário alterar as configurações do editor, a engine suporta apenas mapas codificados em XML ou Base64 sem compressão, então acesse o menu Editar->Preferências e altere as configurações conforme a imagem abaixo:

Vamos utilizar Base64 para obter um arquivo de tamanho menor.

Bom, agora tu tem que criar o mapa, vou seguir as dimensões utilizadas no tutorial original. O tamanho da tela do jogo vai ser de 640×480. Também construí um pequeno tileset com as dimensões de 32×32.

Clique no botão para criar um novo mapa e deixe as configurações conforme a imagem abaixo:

O tamanho do mapa é relativo o número de tiles, nesse caso 15 tiles de altura por 40 tiles de largura, e o tamanho do tile é o que tu usou para criar o seu tileset.

Quando tu cria um novo mapa, é criado uma camada de tiles automaticamente. Também já criei uma nova camada para inserir outros elementos do jogo, para dar uma aparência melhor na disposição dos mesmos. Para criar uma nova camada basta selecionar a opção adicionar nova camada, conforme imagem abaixo:

Agora vamos carregar o tileset criado, clique no menu Mapa->Novo Tileset e carregue a imagem do tileset que vai ser utilizada, conforme imagem abaixo:

Feito isso, tu deverá agora ter essa disposição no editor (clique na imagem para ampliar):

Agora é só construir o cenário, o processo é bem simples basta selecionar o tile e posicioná-lo no local do mapa aonde você quer que ele fique posicionado. Apenas atente para o fato de selecionar a camada em que seus tiles ficarão posicionados, nessa caso vamos utilizar a camada frontal para o chão e a vegetação, e as lápides vamos inserir na camada background. O meu mapa ficou assim (clique na imagem para ampliar):

Concluída a inserção dos tiles, vamos definir uma cor de fundo para o mapa. Clique no menu Mapa->Propriedades do Mapa e adicione uma nova propriedade background_color. O valor é no formato CSS:

Estando aqui, tu já concluiu essa etapa. Agora salve o mapa dentro da estrutura de arquivos que foi criada para o jogo, se tu clonou o repositório git, basta salvar o mapa dentro do diretório data.

3 – Carregando o Mapa no Jogo.

Agora iremos carregar o mapa criado em nosso jogo. Se tu clonou o repositório, ou baixou o pacote, tu deve ter um arquivo main.js, caso não tenha feito, crie um arquivo com o seguinte conteúdo:

// game resources
var g_resources = [];
var jsApp = {
    /* --- Initialize the jsApp --- */
    onload: function() {

    // init the video
    if (!me.video.init('jsapp', 640, 480, false, 1.0)) {
        alert("Sorry but your browser does not support html 5 canvas.");
        return;
    }

    // initialize the "audio"
    me.audio.init("mp3,ogg");

    // set all resources to be loaded
    me.loader.onload = this.loaded.bind(this);

    // set all resources to be loaded
    me.loader.preload(g_resources);

    // load everything & display a loading screen
    me.state.change(me.state.LOADING);
},

    /* --- callback when everything is loaded --- */
    loaded: function() {

    // set the "Play/Ingame" Screen Object
    me.state.set(me.state.PLAY, new PlayScreen());

    // start the game
    me.state.change(me.state.PLAY);
}

};
// jsApp

/* the in game stuff*/
var PlayScreen = me.ScreenObject.extend({

onResetEvent: function() {
// stuff to reset on state change
},

/* --- action to perform when game is finished (state change) --- */
onDestroyEvent: function() {
}

});

//bootstrap
window.onReady(function() {
jsApp.onload();
});

O Arquivo em sim não é muito complexo, o que temos aí é o carregamento de áudio, recursos e o desenho da tela do jogo assim que a página é carregada. Também é definido um callback que será chamado quando tudo estiver pronto para ser usado.  Não vou me atentar às classes utilizadas, em um momento vindouro vamos estudar cada uma delas em detalhe, mas tu pode consultar a documentação a qualquer momento através do link passado no início do tutorial.

O mapa que foi criado para o jogo, será adicionado através do objeto g_resources:

var g_resources = [{
    name: "tile",
    type: "image",
    src: "data/tileset/tile.png"
}, {
    name: "mapa",
    type: "tmx",
    src: "data/mapa.tmx"
}];

O que fizemos aí foi informar o tileset que estamos utilizando, e o mapa no qual ele esta aplicado. Agora, incluiremos na função onResetEvent(), que é chamada a cada mudança de estado, que seja mostrada na tela os elementos de nosso jogo:


onResetEvent: function() {
    me.levelDirector.loadLevel("mapa");
 }

Também tem disponível no pacote um arquivo index.html, que é o arquivo que será chamado para a execução do jogo. Se tudo ocorrer bem, ao carregar o arquivo no teu navegador tu deve ter o mapa carregado conforme imagem abaixo.

NOTA: Devido à questões de segurança do Google Chrome, para rodar o jogo localmente nesse navegador vai ser necessário carregá-lo com o seguinte parâmetro: –allow-file-access-from-files. No Firefox 6 rodou direito, já no Opera não foi possível carregar o mapa, como não é meu navegador padrão vou ter que dar uma pesquisada a respeito, mas segundo o desenvolvedor da biblioteca deveria funcionar sem problemas.

Perceba que ficou uma grade nos encaixes dos tiles inferiores, e um traço do lado da pedra e das lápides, mas não se preocupe, isso é um problema com o tamanho dos elementos do meu tileset, pois fiz isso na corrida, e acabou ficando esse espaçamento; em outro post veremos como construir um tileset de qualidade melhor, aonde iremos consertar esses problemas.

Bola pra frente.

4 – A jornada do herói

Joseph Campbell que me perdoe pela utilização do título de uma de suas obras clássicas (que aliás é leitura obrigatória para quem pretende ingressar nesse universo de criar aventuras), mas cai como uma luva para essa parte do tutorial, pois é aqui que iremos dar vida ao nosso personagem principal do jogo, ele mesmo: o Indiana Tux.

O jogador sera criado estendendo-se a classe me.ObjectEntity da engine. Para a animação do dito cujo, irei usar essa sprite que criei do mesmo:

É um sprite bem simples feito só pra dar uma sensação de movimento, não tive nenhuma intenção de simular um ciclo de caminhada perfeito, mas não quer dizer que tu não devas, se tiveres o dom o faça. Mais uma vez atento aqui para a utilização de um daqueles portais de recursos gráficos livres para quem, assim como eu, não tem muita intimidade com aplicativos de produção gráfica; esse chapéu do Indiana Tux eu não precisei criar, peguei pronto já no Open Clipart. Como todos os gráficos estão disponíveis no formato SVG, foi muito simples utilizá-lo nesse pinguin-ovo que eu criei utilizando o Inkscape.

Nesse tutorial nosso herói irá apenas andar e pular. Nos próximos vamos aplicar mais interatividade, mas para isso teremos que ter um sprite mais elaborado com outras animações.

Bem, vamos carregar o personagem no jogo então. Logo abaixo do carregamento do mapa vamos inserir o seguinte trecho de código:


{
 name: "indiana_tux",
 type: "image",
 src: "data/sprite/indiana_tux.png"
}

Veja que o formato é o mesmo utilizado anteriormente para a inserção do mapa e do tileset.

Agora vamos instanciar nosso personagem:


/*----- a player entity ---------- */
var PlayerEntity = me.ObjectEntity.extend({

     /* ----- constructor ------ */
      init: function(x, y, settings) {
            // call the constructor
            this.parent(x, y, settings);

            // set the walking & jumping speed
            this.setVelocity(3, 15);

           // set the display to follow our position on both axis
            me.game.viewport.follow(this.pos, me.game.viewport.AXIS.BOTH);

 },

 /* ----- update the player pos------ */
 update: function() {

      if (me.input.isKeyPressed('left')) {
              this.doWalk(true);
        }else if (me.input.isKeyPressed('right')) {
              this.doWalk(false);
       } else {
              this.vel.x = 0;
      }
      if (me.input.isKeyPressed('jump')) {
          this.doJump();
      }

       // check & update player movement
       updated = this.updateMovement();

       // update animation
       if (updated) {
          // update objet animation
          this.parent(this);
        }
      return updated;
  }

});

Mais uma vez o código é bem ilustrativo, estendemos a classe para cria nosso personagem, setamos a velocidade do personagem e definimos a posição da câmera do jogo, além de verificar se algumas teclas foram pressionadas para controlar a movimentação do personagem. E finalmente testamos o retorno da função updateMovement(), que faz parte da classe e permite controlar a animação do personagem.

Estou inserindo a codificação do personagem no mesmo arquivo main.js que trata do carregamento do jogo e dos seus recursos, mas para fins de organização é mais indicado que o mesmo seja criado em um arquivo separado e posteriormente lincado. Mas fica a critério de cada um.

Feito  isso vamos agora voltar ao Tiled Map Editor e inserir nosso personagem no mapa. Clique na opção de Adicionar Nova Camada, e adicione uma Camada de Objeto. Renomeie a camada para playerTux, ou o nome que tu quiser.

Agora vamos inserir um objeto na cena, que será o nosso personagem, para tanto clique no botão Inserir Objetos e em seguida clique em algum lugar da tela aonde ficará posicionado o objeto, não se preocupe com o local pois iremos definir nas propriedades, o botão é esse selecionado na imagem abaixo:

Feito isso clique no botão à esquerda do botão Inserir Objetos, esse botão tem a função de Selecionar Objetos, e em seguida selecione o objeto que tu criou. Clique com o botão direito em cima do objeto, selecione a opção Propriedades do Objeto e configure conforme a imagem abaixo:

O Nome não tem muita importância, apenas lembre que tu vai utilizar esse mesmo para carregar o personagem na função de carregamento do jogo, a opção Tipo podes deixar em branco mesmo. A seguir em Posição, tu vai definir aonde quer que o objeto, nesse caso o Indiana Tux, inicie no jogo, definido pela quantidade de tiles no eixo X e a quantidade tiles no eixo Y. O Tamanho do objeto também é apresentado em quantidade de tiles, nesse caso construí o Indiana Tux com 2 tiles de altura por 2 de largura. A propriedade image recebe como valor o nome da imagem do sprite utilizado no objeto, aqui sim o nome é importante, portanto utilize o mesmo que foi utilizado no sprite, a propriedade spritewidth recebe como valor a largura, em pixels, que um elemento da sprite ocupa, como o Indiana Tux tem a largura de 2 tiles de 32 pixels, logo ele ocupa 64 pixels de largura.

Clique em OK e o objeto será reposicionado no mapa, conforme podes ver na imagem abaixo:

Perceba que o indiana ficou meio ‘gordo’ para esse mapa de tiles, mas isso não é problema, pois a nossa intenção aqui é apenas ilustrar o funcionamento da engine, pretendo focar esse, e outros detalhes relacionados à construção gráfica em uma outra ocasião.

Vamos agora criar uma camada de tiles de colisão, pois precisamos dizer para a engine quais são os tiles com os quais nosso personagem deve interagir fisicamente, nesse caso aqui para evitar que ele atravesse paredes ou o chão. Para tanto vamos primeiramente adicionar um novo tileset no mapa. No material que disponibilizei no git, tem um tileset chamado  colisao.png, vamos utilizar essa imagem para a construção do mapa:

O procedimento é o mesmo realizado anteriormente apara caregar o tileset do mapa inicial.

Agora acesse a aba desse novo tileset, clique com o botão direito em cima do primeiro tile e selecione a opção Propriedades do Tile, e vamos inserir uma propriedade conforme a imagem abaixo:

Repita o procedimento com o segundo tile, e defina uma propriedade type com o valor platform.

É isso aí. Existem outras propriedades que não iremos cobrir nesse tutorial. Agora adicione uma Nova Camada de Tiles e renomeie-a para Collision, esse nome é obrigatório para que a engine reconheça a mesma como uma camada de colisão. Feito isso, selecione a camada e comece a posicionar os tiles de acordo com o tu mapa, por aqui as coisas ficaram mais ou menos assim:

Salve o mapa e insira o seguinte código no nosso callbak:


// add our player entity in the entity pool
 me.entityPool.add("indianaTux", PlayerEntity);

 // enable the keyboard
 me.input.bindKey(me.input.KEY.LEFT, "left");
 me.input.bindKey(me.input.KEY.RIGHT, "right");
 me.input.bindKey(me.input.KEY.X, "jump", true);

Salve e abra o projeto no navegador novamente, se tudo ocorreu bem tu já deve ter uma fase completamente acessível, conforme podes ver no vídeo abaixo:

01 – Player do Jogo from Relsi on Vimeo.

Note pelo game play, que realmente o Indiana Tux ficou um pouco desproporcional em relação ao mapa. E também que não existe colisão com os tiles do lago e as estacas, mas isso é pra ser assim mesmo por enquanto, depois arrumaremos. Ele também vai ficar preso sem conseguir sair de uma parte do mapa, mas isso deixo como exercício para vocês encontrar e resolverem.

Em tempo. Quando criamos o personagem automaticamente é criado uma caixa de colisão ao redor do mesmo, podemos observar isso inserindo a seguinte opção no arquivo main.js:


me.debug.renderHitBox = true;

Mas perceba que a caixa de colisão é bem maior que o personagem, sendo assim, o Indiana Tux, propriamente dito, não irá colidir com os objetos do jogo e sim a sua caixa de colisão, mas não primeos cânico isso pode ser resolvido inserindo o seguinte código na instância do personagem:


this.updateColRect(8, 48, -1, 0);

O que fizemos foi ajustar o tamanho da caixa de colisão de acordo com os parâmetros passados, relativos à dimensão do tile:

Agora sim temos uma caixa de colisão do tamanho adequado. Vamos em frente.

5 – Inserindo um fundo com efeito Parallax

Vamos agora criar um fundo para o nosso jogo a fim de substituir a cor azul chapada, isso vai proporcionar um ambiente melhor para o nosso joguinho. A utilização de feitos Parallax é muito comum em jogos desse gênero pois da um efeito de profundidade para o mesmo.

Inserir um fundo com esse efeito é muito fácil utilizando o editor de mapas, e não será necessário uma linha de código sequer para o efeito funcionar, a não ser as necessárias para carregar o elemento gráfico utilizado.

Aqui vou utilizar apenas duas imagens de fundo, mas tu podes utilizar a quantia que desejar para obter um efeito mais complexo, apenas atente para o fato de que isso pode influenciar na performance do jogo, nesse caso menos é mais. A imagem utilizada foi compostas a partir de gráficos baixados do Open Clipart (clique para aumentar):

Vamos excluir a propriedade background_color do mapa, pois ela não será mais necessária. Em seguida crie duas novas camadas de tiles e renomeie-as tendo a atenção para que o nome seja composto pela string Parallax_ pois a mesma serve de identificador para que a engine possa aplicar esse efeito:

Feito isso, adicione às camadas uma propriedade imagesrc tendo como valor o nome da imagem utilizada, sem a extensão:

Agora, temos que inserir os elementos gráficos a serem carregados no jogo através do código:


{
 name: "fundo_01",
 type: "image",
 src: "data/background/fundo_01.png"
},{
 name: "fundo_02",
 type: "image",
 src: "data/background/fundo_02.png"
}

Abaixo um vídeo do efeito em execução:

6 – Inserindo Itens coletáveis

Embora o Indiana Tux esteja atrás do Grande Diamante Vermelho, vamos inserir no cenário do jogo alguns objetos que possam ser coletados pelo mesmo. Itens coletáveis podem ser adicionados facilmente estendendo a classe me.CollectableEntity.

Vamos usar a sprite abaixo para os itens coletáveis:

Devemos carregar a imagem nos recursos do jogo, da mesma forma que fizemos com os outros elementos até então:


{
 name: "rubi",
 type: "image",
 src: "data/sprite/rubi.png"
}

Em seguida devemos criar o elemento no jogo, estendendo a classe me.CollectableEntity:


var RubiEntity = me.CollectableEntity.extend(
 {
     init: function (x, y, settings)
         {
            // call the parent constructor
            this.parent(x, y , settings);
         }

 });

Por enquanto esse é o código necessário para o construtor, vamos definir as propriedades do item no editor, para isso crie uma nova camada de objetos e renomeie como desejar, em seguida crie um novo objeto e defina as suas propriedades conforme a imagem abaixo:

Para inserir mais objetos no mapa, basta clicar com o botão direito no mesmo e escolher a opção Duplicar Objeto. Selecionando vários objetos tu consegue duplicar tantos quanto tiver na seleção.

Agora baste adicionar o item no jogo:


me.entityPool.add("Rubi", RubiEntity);

Execute o jogo, e tu deve ter esse resultado até aqui:

Bem, até o momento os rubis são apenas objetos comuns do cenário, precisamos dizer para o nosso personagem que ele deve colidir com eles, dentro da classe do personagem insira a seguinte função:


me.game.collide(this);

Execute o jogo e veja o efeito, ao colidir com o rubi, o mesmo deverá sumir do mapa.

7 – Adicionando Inimigos

Nem tudo na vida do Indiana Tux vai ser fácil como coletar alguns rubis durante a sua jornada, para chegar até o Grande Diamante Vermelho ele vai ter enfrentar os Macacos Radioativos, que irão tentar impedir o nosso herói de alcançar seu objetivo. Vamos agora adicionar alguns inimigos no jogo. Para fazer isso também iremos estender a classe me.ObjectEntity.extend. A sprite utilizada será essa:

Assim como o Indiana Tux, a sprite é bem simples.

Vamos inserir o Macaco Radioativo no mapa. Para isso crie uma nova Camada de Objetos, renomeie conforme desejar e insira um novo objeto. Nas propriedades desse objeto insira apenas o Nome, as demais propriedades iremos setar via código fonte, para que tu possas ver que ambas as formas são possíveis (lembre que até então inserimos as propriedades no editor).

Também precisaremos aumentar o tamanho do objeto de acordo com a área que queremos que ele ocupe, que nesse caso vai ser a área em que o macaco irá caminhar, para fazer isso basta posicionar o ponteiro do mouse no quadradinho que se encontra no canto inferior direito da moldura do objeto e arrastar para aumentar ou diminuir a área:

Para inserir mais objetos no mapa, repita o mesmo procedimento feito com os rubis.

Salve o mapa e, para criar o inimigo  vamos adicionar o seguinte código no nosso arquivo main.js:

/***** Enemy Entity    ****/
 var MacacoEntity = me.ObjectEntity.extend(
     {
         init: function (x, y, settings)
             {
               // define this here instead of tiled
               settings.image = "macaco_radioativo";
               settings.spritewidth = 64;

               // call the parent constructor
               this.parent(x, y , settings);

               this.startX = x;
               this.endX   = x+settings.width - settings.spritewidth;
               // size of sprite

              // make him start from the right
              this.pos.x = x + settings.width - settings.spritewidth;
              this.walkLeft = true;

              // walking & jumping speed
              this.setVelocity(2, 0);

             // adjust the bounding box
             this.updateColRect(8, 48, -1, 0);

             // make it collidable
             this.collidable = true;
             this.type = me.game.ENEMY_OBJECT;
     },

     // manage the enemy movement
     update : function ()
          {
              if (this.alive)
                 {
                   if (this.walkLeft && this.pos.x <= this.startX)
                      {
                         this.walkLeft = false;
                      }
                   else if (!this.walkLeft && this.pos.x >= this.endX)
                      {
                        this.walkLeft = true;
                       }

                   //console.log(this.walkLeft);
                   this.doWalk(this.walkLeft);
                 }
             else
                 {
                    this.vel.x = 0;
                 }
             // check & update movement
             updated = this.updateMovement();

             if (updated)
                {
                  // update the object animation
                  this.parent();
                }
             return updated;
       }
 });

O mesmo é um pouco mais extenso, pois na classe definimos as propriedades do objeto via código e em seguida a função para o movimento do Macaco. Ffizemos isso dessa forma, apenas para ti ver que existem as duas possibilidades de inserção dessas propriedades do objeto, diretamente no Tiled ou através do código fonte da classe. Tu decide qual a melhor maneira de lidar com isso.

Devemos então carregar os elementos gráficos do macaco:


{
 name: "macaco_radioativo",
 type: "image",
 src: "data/sprite/macaco_radioativo.png"
}

E em seguida carregamos o inimigo no jogo:


me.entityPool.add("macacoMau", EnemyEntity);

Execute o jogo, e se tudo estiver certo, teremos a macacada em ação na tela do jogo:

Agora vamos criar a interação dos macacos com nosso herói, perceba que simplesmente não há nenhum tipo de colisão entre os dois vamos implementar isso agora.

Na classe do Indiana Tux, altere o seguinte trecho:


me.game.collide(this);

Para:


res = me.game.collide(this);

E logo abaixo insira o seguinte código:

if (res)
    {
        if (res.type == me.game.ENEMY_OBJECT)
         {
           // let's flicker in case we touched an enemy
           this.flicker(45);
         }
}

O que fizemos aqui foi simplesmente passar o o retorno da função me.game.collide(this) para a variável res, em seguida verificamos se  o tipo do retorno é igual a  me.game.ENEMY_OBJECT, se for significa que topamos com um inimigo, nesse caso fazemos o Indiana Tux ficar piscando:

Por enquanto é isso que o Indiana Tux irá pagar por topar com um Macaco Radioativo.

Para completar essa etapa, vamos dar ao Indiana Tux a capacidade de causar algum dano ao Macaco Radioativo também.

Insira o seguinte código na classe do macaco:


// call by the engine when colliding with another object
// obj parameter corresponds to the other object (typically the player)
//touching this  one
onCollision : function (res, obj)
     {
        // res.y >0 means touched by something on the bottom
        // which mean at top position for this one
       if (res.y > 0)
           {
              this.flicker(45);
        }
}

Código auto explicativo né?!? :)

E na classe do Indiana altere o código:

if (res)
    {
        if (res.type == me.game.ENEMY_OBJECT)
            {
                 // let's flicker in case we touched an enemy
                 this.flicker(45);
         }
}

Para:

if (res)
{
    if (res.type == me.game.ENEMY_OBJECT)
        {
            if ((res.y>0) && !this.jumping)
               {
                 // bounce
                 this.forceJump();
                }
            else
               {
                // let's flicker in case we touched an enemy
                this.flicker(45);
               }
       }
}

Aqui adicionamos outra condição que verifica se o Indiana pulou na cabeça do Macaco, e nesse caso vai forçar o indiana a pular novamente, o que ir impedir que ele seja atingido pelo Macaco, nesse caso só o Macaco irá piscar:

Por enquanto é isso pessoal. até aqui deu para ter uma noção básica do funcionamento da engine, e para quem quiser se aprofundar mais sem esperar pela segunda parte, basta consultar a documentação da engine.

Segue a listagem completa do arquivo main.js até então:

// game resources
var g_resources = [{
    name: "tile",
    type: "image",
    src: "data/tileset/tile.png"
}, {
    name: "mapa",
    type: "tmx",
    src: "data/mapa.tmx"
},{
    name: "indiana_tux",
    type: "image",
    src: "data/sprite/indiana_tux.png"
},{
    name: "fundo_01",
    type: "image",
    src: "data/background/fundo_01.png"
},{
    name: "fundo_02",
    type: "image",
    src: "data/background/fundo_02.png"
},{
    name: "rubi",
    type: "image",
    src: "data/sprite/rubi.png"
},
{
   name: "macaco_radioativo",
   type: "image",
   src: "data/sprite/macaco_radioativo.png"
}];

//me.debug.renderHitBox = true;

/*----- a player entity ---------- */
var PlayerEntity = me.ObjectEntity.extend({

    /* ----- constructor ------ */
    init: function(x, y, settings) {
        // call the constructor
        this.parent(x, y, settings);

        // set the walking & jumping speed
        this.setVelocity(3, 15);

        // adjust the bounding box
        this.updateColRect(8, 48, -1, 0);

        // set the display to follow our position on both axis
        me.game.viewport.follow(this.pos, me.game.viewport.AXIS.BOTH);
    },

    /* ----- update the player pos------ */
    update: function() {

        if (me.input.isKeyPressed('left')) {
            this.doWalk(true);
        } else if (me.input.isKeyPressed('right')) {
            this.doWalk(false);
        } else {
            this.vel.x = 0;
        }
        if (me.input.isKeyPressed('jump')) {
            this.doJump();
        }

        // check & update player movement
        updated = this.updateMovement();

          // check for collision
         res = me.game.collide(this);

          if (res)
         {
            if (res.type == me.game.ENEMY_OBJECT)
            {
               if ((res.y>0) && !this.jumping)
               {
                  // bounce
                  this.forceJump();
               }
               else
               {
                  // let's flicker in case we touched an enemy
                  this.flicker(45);
               }
            }
         }

        // update animation
        if (updated) {
            // update objet animation
            this.parent(this);
        }
        return updated;
    }

});

 /*-- Collectable Entity --*/
var RubiEntity = me.CollectableEntity.extend(
{
init: function (x, y, settings)
{
// call the parent constructor
this.parent(x, y , settings);

}

});

/***** Enemy Entity	****/
	var MacacoEntity = me.ObjectEntity.extend(
	{
		init: function (x, y, settings)
		{
			// define this here instead of tiled
			settings.image = "macaco_radioativo";
			settings.spritewidth = 64;

			// call the parent constructor
			this.parent(x, y , settings);

			this.startX = x;
			this.endX   = x+settings.width - settings.spritewidth; // size of sprite

			// make him start from the right
			this.pos.x = x + settings.width - settings.spritewidth;
			this.walkLeft = true;

         // walking & jumping speed
			this.setVelocity(2, 0);

          // adjust the bounding box
         this.updateColRect(8, 48, -1, 0);

         // make it collidable
			this.collidable = true;
			this.type = me.game.ENEMY_OBJECT;

		},

	   // call by the engine when colliding with another object
      // obj parameter corresponds to the other object (typically the player)	touching this one
		onCollision : function (res, obj)
		{

			// res.y >0 means touched by something on the bottom
			// which mean at top position for this one
			if (res.y > 0)
			{
		      this.flicker(45);
			}
		},

		// manage the enemy movement
		update : function ()
		{

			if (this.alive)
			{
				if (this.walkLeft && this.pos.x <= this.startX)
				{
					this.walkLeft = false;
				}
				else if (!this.walkLeft && this.pos.x >= this.endX)
				{
					this.walkLeft = true;
				}

				//console.log(this.walkLeft);
				this.doWalk(this.walkLeft);
			}
			else
			{
				this.vel.x = 0;
			}
			// check & update movement
			updated = this.updateMovement();

			if (updated)
			{
				// update the object animation
				this.parent();
			}
			return updated;
		}
	});

var jsApp = {
    /* --- Initialize the jsApp --- */
    onload: function() {

        // init the video
        if (!me.video.init('jsapp', 640, 480, false, 1.0)) {
            alert("Sorry but your browser does not support html 5 canvas.");
            return;
        }

        // initialize the "audio"
        me.audio.init("mp3,ogg");

        // set all resources to be loaded
        me.loader.onload = this.loaded.bind(this);

        // set all resources to be loaded
        me.loader.preload(g_resources);

        // load everything & display a loading screen
        me.state.change(me.state.LOADING);
    },

    /* --- callback when everything is loaded --- */
    loaded: function() {

        // set the "Play/Ingame" Screen Object
        me.state.set(me.state.PLAY, new PlayScreen());

       // add our player entity in the entity pool
		me.entityPool.add("indianaTux", PlayerEntity);
        me.entityPool.add("Rubi", RubiEntity);
        me.entityPool.add("macacoMau", MacacoEntity);

		// enable the keyboard
		me.input.bindKey(me.input.KEY.LEFT, "left");
		me.input.bindKey(me.input.KEY.RIGHT, "right");
		me.input.bindKey(me.input.KEY.X, "jump", true);

        // start the game
        me.state.change(me.state.PLAY);
    }

};
// jsApp

/* the in game stuff*/
var PlayScreen = me.ScreenObject.extend({

    onResetEvent: function() {
        // stuff to reset on state change

        // load a level
        me.levelDirector.loadLevel("mapa");
    },

    /* --- action to perform when game is finished (state change) --- */
    onDestroyEvent: function() {
    }

});

//bootstrap :) 
window.onReady(function() {
    jsApp.onload();
});

Na segunda parte do tutorial iremos:

  • Adicionar um sistema de pontuações.
  • Adicionar efeitos sonoros.
  • Adicionar outros níveis (fases do jogo).
  • Criar uma tela de menu.
  • Implementar as ações decorrentes da colisão do Indiana com o Macaco e os Rubis, como criar pontuação, retirar vidas, etc.

Até o Próximo!





34 ideias sobre “melonJS – Desenvolvendo Jogos para HTML5 – 1ª Parte

  1. também não consegui carregar o mapa! nem copiando os arquivos do tutorial, nem adaptando ao meu jogo. Nos arquivos do tutorial aparece uma janela preta com “melon JS” escrito no meio. :/

      • Erro emitido pelo console do Chrome:

        Uncaught SyntaxError: Unexpected token (
        477melonJS-0.9.0-min.js:13Uncaught TypeError: object is not a function
        875
        melonJS-0.9.0-min.js:13Uncaught TypeError: object is not a function

        espero que possa ajudar.

        • Esse erro normalmente é do Chrome, vc tem que ter um servidor para rodar, isso é questão de segurança quando abra um arquivo local. Rode por um servidor local ou troque de navegador.

  2. Pingback: HTML5 - Vamos usar agora, ou deixar para o futuro | Como Criar Programas de Computador e Sites | Criar Programas

  3. Então Relsi Hur Maron não aprece nenhum erro e exibe apenas (0/0 fps), mesmo copiando todos os arquivos. Seria porque estou testando no SO Windows? Testei em todos os navegadores também e não obtive nenhum resultado positivo.

    Aguardo resposta.

    • Olá Wellington,

      Realmente não testei no windows então não sei dizer se o problema esta relacionado com o SO.
      De ante mão, eu tive alguns problemas com o chrome sim, mas que resolvi rodando o jogo no servidor (em localhost), então pode ser que isso resolva teu problema com o windows, mas é só uma hipótese, não tenho o windows instalado aqui para testar.

      Bueno coloquei aqui a versão pronta dessa parte do jogo: http://www.tuxtilt.com/tutorial_jogo/ tente acessar, e se rodar sem problemas sugiro que instale o apache na sua máquina e rode o jogo a partir do servidor.

      Qualquer coisa de um toque.

  4. Então, creio eu que quem não conseguil carregar o mapa foi porque ele não existe no arquivo lá em cima, você precisa cria-lo… seguindo o tutorial
    Muito bom estava já a um tempo atraz de um egine para html5 e essa me pareceu ser bem promissora vou testar assim que chegar em casa e ver se consigo criar algo legal
    parabens pelo tutorial.
    vlw

  5. Boas, eu tive alguns problemas em conseguir correr o jogo. Primeiro faltava o ficheiro GameObj.js, depois estava a tentar correr no Chrome, no FireFox e no IE e nenhum deles abriar o jogo… depois lá descobri que o mapa não podia estar comprimido, Tive de alterar a opção de compactar o mapa para descompactado, mas de qualquer das formas, pelo Chrome e IE só corre se estiver num servidor com apache, pelo Firefox corre directo porque este browser utiliza apache. Qualquer dúvida podem contar comigo. Abc!

    • Como esse material é apenas um ponto de partida, eu decidi concentra todo o código do jogo dentro de um único arquivo, o main.js, por isso não existem os mesmos arquivos dos exemplos da biblioteca, como o GameObj.js, mas o conteúdo desse arquivo está contido dentro do arquivo main.js.
      Em relação à compressão do mapa, é mostrado bem no início do tutorial que tem que ser descomprimido ;) , esse problema de não rodar o jogo diretamente nos navegadores webkit é por uma questão de segurança que eles implementam, vou dar uma pesquisada para ver como contornar isso nas versões atuais, mas de qualquer forma, como tu mesmo observou, a aplicação roda normalmente se tiver num servidor de aplicações.

      Valeu pela visita e pelo comentário aí. A última parte do tutorial já tá saindo do forno!!

  6. Cara amei seu tutorial.
    Os testes rodam legau no crome e no firefox mas não descobri quais botoes controlam o heroi ele anda mas sempre cai nunca pula ou sobe
    Procurei e achei este tutorial porque estou tentando utilizar SVG e HTML 5 pra portar aquela aplicação de comercio ERP que voce me ajudou para os tablets android tem alguma sujestão
    Daria para criar as interfaces do aplicativo com esta lib

    • Grande Helder! Satisfação vê-lo por aqui!

      A tecla que controla o pulo do personagem é a tecla X, tu deve ter esse entrada e algum lugar do código, logo após o comentário “enable the keyboard”: me.input.bindKey(me.input.KEY.X, “jump”, true); Tu pode trocar para a que tu desejar.

      MelonJS é uma biblioteca desenvolvida para games, então a manipulação gráfica a qual ela se destina é um pouco diferente da que tu pretenderias utilizar para a manipulação de uma GUI.

      Para esse fim sugiro a utilização de uma biblioteca mais específica como jQueryUI[1] (minha escolha já que trabalho com jQuery) ou a Dojo Toolkit [2] (conheci recentemente, mas achei bem interessante).

      Para o backend tu pode continuar utilizando o PHP, ou fazer como eu que estou migrando minhas aplicações para python, utilizando o framework web2py [3], se a migração do aplicativo para o ambiente web é o foco, sugiro veemente dar uma boa conferida nessa possibilidade, se quiser podemos trocar umas idéias aí!

      Super abraço!
      [1]http://jqueryui.com/
      [2]http://dojotoolkit.org/
      [3]http://web2py.com

  7. Rodou perfeitamente, você vai continuar o tópico para mostrar o dano do personagem, eu estou fazendo um joguinho e queria se você pode me dar uma ajudinha sobre como fazer esse dano do personagem com o inimigo…

  8. Excelente matérial cara, muito bom. No aguardo para a parte dois. Esta sendo bom para mim aprender mais sobre o HTML5 e ao mesmo tempo aprender com algo que curto, jogo.

  9. O mapa não ta rodando tou usando o Windos 7, ja testei no Chrome, Mozilla e Opera e nada, Fica MelonJS e uma barra de progresso e nada FPS 60/60, coloco aqueles outros
    var g_resources = [{name: "tile",type: "image",src: "data/tileset/tile.png"}, {name: "mapa",type: "tmx",src: "data/mapa.tmx"}];
    e
    onResetEvent: function() {me.levelDirector.loadLevel(“mapa”);}

    e nem assim!

      • Legal, compartilhe então com o pessoal aí qual foi o teu problema e como resolveu, assim se mais alguém passar pela mesma situação, poderá resolver!
        Abração!

    • Opá! Não conheço o ambiente Windows muito bem para te ajudar, mas veja a nota que coloquei em relação às restrições para rodar o jogo, talvez seja isso. Se for, minha sugestão é que rode o mesmo em um servidor, mesmo que localhost.

  10. Excelente post, Relsi!!!!

    Sou arquiteto java em ambientes corporativos e, estou començando a estudar essa parte de games… Se puderes me indicar outras fontes que aches interessante, ficarei muito agradecido.

    Parabéns mais uma vez pelo post.

    • Olá David, valeu pela visita.
      Bueno, em relação às referências, diga ai quais são suas intenções, web, mobile, etc. que eu tenho algumas coisas por aqui sim.

  11. pessoal que ta tendo problema no windows para carregar o MAPA.
    1. abra o Tiled Map editor
    2. vá na aba map > map Properties
    3. em Layer Format, coloque a opção “base64(uncompressed)”
    4. pronto! agora funciona no Chrome

  12. Olá Relsi, primeiro parabéns pelo tuto… está bem explicadinho para quem tá começando. Continue com as outras partes. Estou desenvolvendo um game com a melonJS, e digo para que está começando: é muito bom! Estou começando com um blog sobre games e terá uma aba só para o melonJs, quem quiser dá uma olhada http://www.gamedeveloperbox.blogspot.com.br/

    Dica:
    No site oficial tem uma galeria de games, dá para baixar os códigos e analisá-los, além de ver o que podemos fazer com essa ferramenta. Aproveitem, é uma ótima engine e acima de tudo é free e open!

    • Opá, como eu disse pro Edilson, to meio sem tempo agora para continuar com os tutos, e mudou bastante coisa na lib depois que eu fiz esse, teria que atualizar esse dai primeiro. No mais eu to lidando com a Unity3D agora e um pouco afastado do html5, mas to pra começar outra página só dedicada aos games, assim que tiver tempo suficiente para começar a escrever um material legal, eu coloco no ar. Bacana o teu blog, se quiser podemos trocar umas ideias ai pra frente, me adiciona lá no Facebook. Manda um Banner que eu link aqui no tuxtilt também. E disponibiliza o feed do blog por email também. Tem algumas outras libs bacana para a jogos com html, como a Quintus e a Caat, sugiro dar uma olhada.

      Abração.

Deixe uma resposta

O seu endereço de email não será publicado Campos obrigatórios são marcados *

*

Você pode usar estas tags e atributos de HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>