sábado, 28 de março de 2015

[Game Maker] Aula 3 - Configurando os inimigos

Nesta aula, eu irei mostrar como configurar, passo á passo, um inimigo simples, porém funcional. A característica do "inimigo" em todos os jogos é impedir o progresso do jogador com a inteligência artificial que lhe foi dada, criando um PvE.

Continuando a partir do que foi feito na aula anterior, desta vez vamos configurar os inimigos do jogo. O inimigo será muito simples: suas funções básicas serão avistar o jogador, perseguir o jogador, atacar o jogador caso tenha a oportunidade e também apanhar do jogador, até que venha a morrer.

Índice:
1. Preparações de sprites e objetos
2. Configurando o inimigo I
3. Configurando efeitos visuais
4. Configurando o inimigo II
5. Interação entre inimigo e personagem
6. Configurando o inimigo III
7. Morte, Game Over e outros detalhes
8. Finalização

1. Preparações de sprites e objetos
Primeiro, temos que criar outros sprites para os inimigos, neste caso, chamamos o primeiro de sprite_inimigo_menor. Você pode alterar este nome, como lhe convir melhor. Este primeiro sprite não será diferente de todos os outros: 16x16 e ponto de origem centralizado (x:8;y:8). O segundo, chamados de sprite_inimigo_grande. Como o próprio nome já diz, faremos um inimigo um pouco maior: 24x24, também com o ponto de origem da imagem centralizado (x:12;y:12). Você pode fazer uma animação básica em ambas as sprites se desejar, embora seja puramente cosmético.

Como fizemos um inimigo maior que a nossa Mask padrão (16x16), criaremos então um novo sprite para servir de Mask para ele. Faça como a mask anterior: uma imagem de tamanho 24x24, com um círculo redondo cobrindo a imagem o máximo possível, com a origem centralizada, chamado de sprite_mask_maior24x24. Desta maneira:

Agora, vamos criar objetos para os inimigos, porém vamos fazer isso de uma maneira um pouco diferente do que estávamos fazendo anteriormente. Vamos fazer o uso do recurso Parent.

Crie um objeto. Independente do nome que você deu os sprites, o nome do objeto deve ser objeto_inimigo_geral. Agora crie outros dois objetos, agora com o nome em relação aos sprites que você deu, neste caso, objeto_inimigo_menor e objeto_inimigo_maior. Em ambos, coloque as seguintes características: Solid, coloque suas sprites e masks correspondentes e por fim, seu Parent deve ser o objeto_inimigo_geral. Algo parecido com isso:

Agora que o terreno está pronto, vamos para a parte do GML.
_____________________________________________________

Antes, deixe-me dar uma breve explicação do que ocorreu aqui: nós colocamos o Parent do objeto_inimigo_menor e objeto_inimigo_maior como objeto_inimigo_geral. Ou seja, todas as alterações e tudo o que diz respeito ao objeto_inimigo_geral afetará diretamente os outros dois. Por exemplo: se quiséssemos destruir todas as instâncias do objeto_inimigo_menor e objeto_inimigo_maior, não teríamos que fazer duas ações: basta destruir todos os objeto_inimigo_geral, e seus representantes serão destruídos por consequência.

Neste caso, tudo o que estiver interligado com o objeto_inimigo_geral ou com os outros dois será considerado para o jogo um inimigo. A grande vantagem disso é que só iremos configurar os inimigos uma única vez e depois faremos pequenas alterações entre um e outro para darmos á cada um características mais únicas.

Se não tivéssemos feito isso, teríamos que copiar e colar o mesmo código em todos os inimigos do jogo e, caso algo tivesse dado errado ou quiséssemos fazer uma alteração, teríamos que fazer de um em um, pacientemente. Vale lembrar que uma instância do objeto_inimigo_geral nunca vai entrar diretamente no jogo. Ele só irá participar indiretamente, através dos seus "representantes".

Para que um "filho" faça exatamente a mesma coisa que seu pai em um determinado evento, existem duas opções: deixar o evento intocável no representante, ou então usar o a função "event_inherited();". A função "event_inherited();" basicamente copia o código do evento que ela foi adicionada do objeto-pai para o objeto-representante. Não se preocupe em entender isso agora, logo iremos trabalhar encima deste conceito e tudo ficará mais claro.
_____________________________________________________

Também é interessante colocar o Parent do objeto_inimigo_geral como objeto_obstaculo_geral, pois assim o inimigo também funcionará como um obstáculo comum qualquer, que o jogador não possa atravessar.

2. Configurando o inimigo I
Certo! Agora, vamos mexer nas características do objeto_inimigo_geral. Não precisamos alterar nada á respeito de sprite, nem solid, nem mask, nem visible, etc, pois como já dito, ele nunca vai entrar no jogo diretamente, logo, estas características pouco importam. Primeiro, vamos configurar sua visão simples e faremos com que ele persiga o jogador. No Create Event, coloque:

{
   //Variáveis que serão alteradas no futuro
   var_atributo_velocidade = 0;
   var_atributo_dano = 0;
   var_atributo_visao = 0;
   var_atributo_vida = 0;

   //Variáveis que não serão alteradas
   var_alvo = objeto_jogador_geral
   var_provocado = 0;
   var_morto = false;
}

Como podemos ver, atribuímos muitas características ao nosso futuro oponente. Muitas estão zeradas pois no iremos alterar seus valores somente no objeto_inimigo_menor e objeto_inimigo_maior. Atribuímos também a variável var_alvo como o objeto_jogador_geral para ajudar a sermos menos redundantes nos códigos. Agora vamos trabalhar com elas agora.

No Step Event, coloque o seguinte:

{
//Vira o sprite do inimigo de acordo com a direção que ele anda...
image_angle = direction;

//Caso o jogador estteja dentro do raio de visão inimiga...
if distance_to_object(var_alvo) <= var_atributo_visao
    {
    
    //... e também se não houver nenhuma parede que atrapalhe a visão do inimigo...
    if !collision_line(x, y, var_alvo.x, var_alvo.y, obj_obstaculo_geral, 1, 1)
        {
        
        //A variável var_provocado será constantemente colocada com valor 90.
        //Como será uma variável para indicar tempo, 90 = 3 segundos (pois 30 = 1 segundo).
        var_provocado = 90;
        
        }
    
    }
    
//Se o inimigo estiver provocado...
if var_provocado > 0
    {
    
    //Diminui a variável provocado constantemente.
    //Como acima atribuímos o valor de 90, significa que o inimigo que perder 
    //a visão do jogador tentará encontrá-lo por mais 3 segundos, 
    //e depois irá desistir.
    var_provocado -= 1;
    
    //Tenta chegar ao jogador. O mp_potential_step é
    //muito poderoso pois é uma função que permite
    //as instâncias chegarem entre sí evitando sempre
    //obstáculos sólidos.
    mp_potential_step(var_alvo.x, var_alvo.y, var_atributo_velocidade, 0);
    
    }
}

Acabamos de utilizar as variáveis var_alvo, var_atributo_velocidade, var_atributo_visao, var_provocado. Vale á pena comentar um pouco sobre duas funções importantíssimas que acabamos de utilizar no código acima: collision_line e mp_potential_step.

A collision_line cria uma linha imaginária entre um ponto (x¹, y¹) e outro (x², y²), neste caso, entre o ponto do inimigo e o ponto da personagem. É extremamente útil, especialmente da forma que aplicamos acima: isso funciona como uma linha de visão do inimigo, onde ele só consegue "ver" o jogador caso não tenha nenhum objeto_obstaculo_geral entre os dois.

O mp_potential_step é com certeza a função dos deuses. Ela permite que objetos movam-se em direções á um ponto qualquer automaticamente desviando de obstáculos sólidos. Existe outra versão, chamada de mp_potential_step_object onde o objeto não evita obstáculos sólidos, mas sim apenas o objeto que foi especificado. Isso é útil, por exemplo, para fazer com que o inimigo verde passe somente pela parede verde, enquanto o inimigo amarelo passe somente pela parede amarela.

As próximas funções serão referentes á apanhar e morrer e, convenhamos, estas funções precisam de um tempero á mais, para que o jogador de fato saiba que acertou o inimigo e que o inimigo morreu. Para isso, criamos efeitos visuais.

3. Configurando efeitos visuais
Começamos fazendo o principal neste quesito: os sprites dos efeitos. Nesta ocasião, uma animação convincente é obrigatória, pois é intuito deste recurso. Criaremos dois sprites: sprite_efeito_sangue e sprite_efeito_morte (ambos em padrão 16x16 e origem centralizada).

Ofereço-lhe duas imagens que vão cair bem nesta ocasião, use-as se quiser:



Criemos então um novo objeto: objeto_efeito_geral. Diferente dos inimigos, não é necessário criar um objeto para cada efeito visual pois todos eles possuem o mesmo intuito, sempre: aparecem, fazem suas animações e depois somem. A única característica que devemos alterar é sua Depth: coloque em -1 (agora ele estará sempre acima visualmente de qualquer instância que tenha Depth igual ou maior que 0). Agora, no Create Event, defina uma velocidade menor que o padrão (1), algo como:

{
   image_speed = 0.25;
}

E no Other Event: Animation End, coloque:

{
   instance_destroy();
}

Nosso efeito visual está pronto para ser usado á qualquer instante agora.

4. Configurando o inimigo II
Agora podemos completar um pouco mais nosso objeto_inimigo_geral. Continue editando o Step Event (se quiser criar uma nova folha de código para ajudar a organizar a informação, fica á seu critério) com o seguinte:

{
//Se houver um ataque sobre o inimigo...
if place_meeting(x, y, objeto_ataque_geral)
    {
    
    //Se ele estiver vivo ainda...
    if var_morto == false
        {
        
        //Chamaremos o ataque que estiver sobre ele de "var_temporaria_ooo"...
        var_temporaria_ooo = instance_nearest(x, y, objeto_ataque_geral);
        
        //Reduziremos um ponto de sua vida...
        var_atributo_vida -= 1;
        
        //Destruiremos o ataque, pois este já fez sua função.
        with var_temporaria_ooo do
            instance_destroy();
            
        //E criaremos um efeito visual de sangue saindo do inimigo.
        var_temporaria_efeito = instance_create(x, y, objeto_efeito_geral);
        var_temporaria_efeito.sprite_index = sprite_efeito_sangue;
        
        }
    
    }
    
//Caso a vida dele esteja igual ou menor que zero...
if var_atributo_vida <= 0 && var_morto == false
    {
    
    //Então deve ser considerado morto.
    var_morto = true;
    
    //Cria-se um efeito visual temporário referente á isso.
    var_temporaria_efeito = instance_create(x, y, objeto_efeito_geral);
    var_temporaria_efeito.sprite_index = sprite_efeito_morte;
    
    //E destrói a instância, para tirá-la do jogo.
    instance_destroy();
    
    }
}

Se você analisar bem o código acima, compreenderá que agora o inimigo efetivamente apanha e morre depois que atacado um certo número de vezes. Ele está qualquer pronto, mais ainda não faz o principal: atacar o jogador. Podemos fazer isso de milhares de maneiras diferentes, mas eu pretendendo fazer isto da maneira mais simples o possível.

5. Interação entre inimigo e personagem
Para isso acontecer, precisamos fazer mais alterações na nossa personagem. A ideia vai ser a seguinte: fazer com que, ao tomar dano, o personagem fique inatingível por um curto período, podendo tomar outro dano somente quando esta invencibilidade acabar, e isso será muito simples de fazer.

Para isso, voltemos ao nosso objeto_jogador_geral. No Create Event, acrescente três novas variáveis:

{
var_inatingivel = 0;
var_atributo_vida = 10;
var_morto = false;
}

A var_inatingivel vai servir como um timming para definir o período que o jogador vai ficar totalmente inatingível e, portanto, incapaz de tomar outro ataque inimigo. Vamos trabalhar só com ela por enquanto para não confundirmos todas as informações. Ainda no Step event, acrescente o seguinte:

{
if var_inatingivel > 0
    var_inatingivel -= 1;
}

É interessante sempre comunicarmos ao jogador real do que está se passando no jogo, para que ele entenda realmente como as coisas funcionam. Neste caso, é importante deixarmos claro que o jogador está invencível por este período. A maneira mais clássica, usada pela maioria dos jogos de antigamente, para isso é fazermos o personagem ficar "piscando" durante sua invencibilidade. No Step event, acrescente então:

{
//Efeito visual de invencibilidade
if frac(var_inatingivel/2) == 0
    visible = true;
else
    visible = false;
}

Pronto! Agora nossa personagem sabe como apanhar, resta a nós ensinar aos inimigos como bater. Só que nós vamos fazer isso de uma maneira inesperada. Assim como a lógica do personagem e os obstáculos, onde ao invés de ensinarmos os obstáculos a interromperem a personagem, ensinamos a própria personagem a não atravessar os obstáculos, iremos ensinar a personagem á apanhar dos inimigos, e não os inimigos baterem na personagem. Confuso? Relaxe, que você irá entender.

Anteriormente, já tínhamos adicionados as variável "var_atributo_vida = 10" e "var_morto = false" no Create Event do objeto_jogador_geral. Neste caso, ainda no Step Event do objeto_jogador_geral, acrescente o seguinte:

{
    //Se a distância entre o inimigo e o jogador for de 1 pixel, ou seja, estarem extremamente pertos...
    if distance_to_object(objeto_inimigo_geral) <= 1
        {
        
        //E a personagem não esteja em período de invencibilidade...
        if var_inatingivel <= 0
            {
            
            //Chamaremos

            //A personagem perde pontos de vida e torna-se intocável por 3* segundos. 
            //* Lembre-se que 30 = 1 segundo, em termos de tempo.
            var_atributo_vida -= 1;
            var_inatingivel = 90;
            
            //E por fim criaremos um efeito visual de sangue saindo da personagem.
            var_temporaria_efeito = instance_create(x, y, objeto_efeito_geral);
            var_temporaria_efeito.sprite_index = sprite_efeito_sangue;
            
            }
        
        }
    
    //Se a vida da nossa personagem chegar á estaca zero...
    if var_atributo_vida <= 0 && var_morto == false
        {
        
        //Ela deve ser considerada morta.
        var_morto = true;
        var_inatingivel = 9999;
    }
}

Com este código, fechamos o nosso sistema de combate básico do jogo. Falta só alguns detalhes á serem feitos.

6. Configurando o inimigo III
Chegamos ao nosso último passo no que diz respeito aos nosso queridos inimigos. Agora, vamos atribuir características diferentes á cada um de nossos inimigos. No momento nós só temos dois: objeto_inimigo_menor e objeto_inimigo_maior, mas nada impede que você crie muitos outros (aliás, eu encorajo que você o faça depois!).

No Create Event do objeto_inimigo_menor, coloque o seguinte código:

{
   //Chama-se o evento do Parent, ou seja, copia as variáveis do seu superior, o objeto_inimigo_geral
   event_inherited();
   
   //Variáveis alteradas de acordo com o inimigo. Todas com valores baixos, de acordo com o jogo, pois é o primeiro inimigo e não deve representar tanta ameaça, especialmente para um jogador novato.
   var_atributo_velocidade = 0.5;
   var_atributo_dano = 1;
   var_atributo_visao = 64;
   var_atributo_vida = 3;
}

No Create Event do objeto_inimigo_maior, coloque o seguinte código:

{
   event_inherited();
   
   //Inimigo um pouco mais agressivo que o primeiro, com valores mais altos.
   var_atributo_velocidade = 0.75;
   var_atributo_dano = 2;
   var_atributo_visao = 80;
   var_atributo_vida = 5;
}

Vale á pena comentar novamente sobre a função "event_inherited();". que adicionamos acima. Se não tivéssemos colocado ela no início do código, certamente teríamos um erro ao executar o código, pois algumas variáveis importantes características dos inimigos (como a "var_alvo = objeto_jogador_geral;" e a "var_provocado = 0;") se encontram apenas no objeto-pai.

Por outro lado, se tivéssemos colocado um "event_inherited();" no final do código, também não teria dado certo, pois todas as alterações que fizéssemos seriam em vão, já que todos os valores voltariam á ser 0, igual o objeto-pai (o código é executando sempre da primeira em direção á última linha).

Entendido isso, podemos passar para o último passo desta aula.

7. Morte, Game Over e outros detalhes
Assim como tivemos que adicionar detalhes visuais para quando o inimigo morre, também teremos que o fazer para quando a personagem chega ao seu destino final. Obviamente, a morte da nossa personagem é muito mais relevante que a morte de um inimigo comum e, por este motivo, devemos fazer algo mais impactante.

A ideia é criarmos uma cena em que torne claro que o jogador falhou ao deixar sua personagem morrer e, portanto, terá que tentar novamente desde o começo da fase.

Para isso, podemos criar um novo objeto: o objeto_game_over. Com este nome intuitivo, podemos começar á trabalhar nele. Ele não precisa de sprites, nem masks e nem coisas do tipo, apesar disso, ele irá participar diretamente do jogo e não através de um representante. Porém, como sendo um recurso do jogo irrefutável, deve estar acima de qualquer coisa, logo, terá que ter uma Depth extremamente baixa. Algo em torno de -99 é o suficiente.

Agora, vamos dar o primeiro passo ao que chamamos de Interface do usuário, embora isso é um assunto que será abordado em uma próxima aula. Agora, só faremos o necessário.
No objeto_game_over, vamos trabalhar alguns conceitos diferentes: alarms e draw event. Antes de mais nada, vamos ao Create Event do objeto_game_over:

{
//Torna todas as instâncias da room invisíveis, inclusive ela
with all do
    visible = false;

//Torna-se visível novamente
visible = true;

//Dois alarms, [1] para ser adicionado nesta exato momento e o [0], em 1 segundo, pois 30 = 1 segundo real.
alarm[1] = 1;
alarm[0] = 30;
}

Colocamos dois alarms para executar suas ações com intuitos diferentes. Em geral, coloca-se alarms com número irrisórios, como o caso do alarm[1] = 1, para que ele seja executado exatamente depois do Crate Event e do Other: Room Start do objeto em questão. Já no caso do alarm[0] = 30, colocamos em um perído não tão grande assim (1 segundo apenas), porém já faz uma certa diferença para o jogador real, já que ele vai, de fato, demorar um pouco para ser ativado.

No evento do Alarm[1] do objeto_game_over, que será ativado praticamente no momento que o objeto_game_over entrar em cena, coloque:

{
//Isso vai fazer com que o jogo congele por 2000 milissegundos, ou 2 segundos).
sleep(2000);
}

Já no evento do alarm[0] coloque o seguinte:

{
//Isso fará com que a room reinicie-se, como forma de punição ao jogador por ter deixado a personagem morrer.
room_restart();
}

Agora, no Draw Event, vamos realmente ao que interessa: a mensagem do jogo ao jogador real de que ele falho e deve tentar novamente. O Draw Event nada mais é que desenho de vetores, formas geométricas e textos na tela do jogador. Não é algo que interaja com as instâncias da room, tornando assim o meio ideal para criar a interface do jogador.

Como vamos trabalhar com textos, é interessante primeiro criar uma fonte. A Font pode ser criada como qualquer outro recurso comum. Lá, você pode pegar os caracteres de uma fonte qualquer instalada no seu computador, mas deve levar em conta que isso não significa que o jogador que baixar o seu jogo terá a mesma fonte no computador dele, portanto, prefira fontes comuns e populares (isso explica do por quê você baixa aquele jogo em coreano e todas as "letras" transformam-se em quadrados iguais).
Você tem de considerar também que nem todas as fontes são adaptadas ao português e, portanto, não terão caracteres de letras acentuadas. Para verificar se a fonte possui tais caracteres, clique em "All" para que o character range fique de 0 á 255, e depois verifique dentro do jogo. A fonte pode ser á sua escolha, no tamanho que achar melhor, negrito ou não, itálico ou não. Apenas dê o nome á ela de fonte_padrao0.

Agora, para finalizar, no Draw Event do objeto_game_over, coloque o seguinte código:

{
    //Torna todas as formas geométricas, textos e vetores á seguir na cor PRETA.
    draw_set_color(c_black)
    
    //Desenha um retângulo com essas característica.
    draw_rectangle(x-96, y - 32, x + 96, y + 32, 0);
    
    //Torna todas as formas geométricas, textos e vetores á seguir na cor BRANCA.
    draw_set_color(c_white);
    
    //Torna todos os textos á seguir centralizados tanto horizontalmente quanto verticamente.
    draw_set_halign(fa_center);
    draw_set_valign(fa_middle);
    
    //Aplica a fonte que criamos anteriormente.
    draw_set_font(fonte_padrao0);
    
    //Desenha o texto conforme as características dadas acima.
    draw_text(x, y, "Uh oh!#Tente novamente!");
}

Agora, vamos á tão esperada...


8. Finalização
Verifique se tudo está OK durante o teste. Característica á se verificar:

- Inimigos perseguem o jogador.
- Inimigos atacam o jogador.
- Inimigos não adentro paredes.
- Jogador apanha somente em no mínimo de 3 em 3 segundos.
- Jogador morrer.
- Inimigos apanham e morrem.
- Cena de game over, e logo após a sala reinicializa.

Se tudo ocorrer como o esperado, parabéns! Mais um passo concluído. Vale á pena lembrar que o seu personagem consegue andar para trás, então use esta função durante o teste para atacar ao mesmo tempo que se afasta do inimigo!
Ufa! até a próxima!


terça-feira, 24 de março de 2015

[Game Maker] Aula 2 - Adicionando os obstáculos básicos!

Continuaremos nosso guia para iniciantes em Game Maker. Neste guia, continuaremos o que fizemos na aula passada. Desta vez, vamos dar uma olhada mais á fundo nas Rooms, e discutirmos um pouco sobre background. Mas, principalmente, iremos aprender a usar os importantíssimos tilesets!

Na aula passada, conseguimos terminar o nosso personagem básico. Ele anda, ele ataca, mas provavelmente está deprimido, pois ele não tem por onde andar e muito menos em quem atacar. Neste caso, vamos dar um passo de cada vez. Começaremos então utilizando a room criada na aula passada, a nossa rm_testroom.

Começaremos a dar um visual ao nosso jogo, adicionando dezenas de Tilesets. A vantagem do uso de Tilesets é que você pode criar um único obstáculo geral, e colocá-lo sobre os Tilesets que podem tanger com a personagem. O jogador terá a impressão de que tudo aqui é sólido e real, mas na verdade estamos usando uma parece invisível por cima de tudo aquilo. A grande vantagem disso é a ENORME economia de memória, tornando o seu jogo muito mais rápido, leve e com uma boa fluidez. 

"Ahn?!? TILESET?!? Que isso?! Parede invisível?! Porra, explica isso direito!"

Bom, nada melhor do que visualizarmos como vai ser feito:

Isso é uma room com os Tilesets. Tudo o que você vê nela: árvores, lagos e pedras, não existe. Você está vendo, mas para o jogo isso não tem o menor significado, ou seja, seu personagem vai passar por cima disso tudo como se não houvesse nada. E, de fato, não há. 
A ideia é marcarmos os obstáculos para o jogo entender que o seu personagem não pode, em hipótese alguma, passar por ali andando. E obviamente não iremos fazer isso em todos as árvores, rios e pedras da tela: é aí que mora o segredo. Nós vamos colocar as paredes invisíveis que eu mencionei apenas sobre os obstáculos que o jogador pode a vir encostar:

Agora que nós ja temos idéia, vamos trabalhar encima disso. Primeiro de tudo, vamos criar uma nova Mask, desta vez com o formado quadrado e que tenha sua origem no (0,0). Isso por quê paredes invisíveis devem ser blocos consistentes que ocupam a área toda (se nós usarmos a Mask redonda, dará a impressão que ela tem "buracos" quando o jogador andar encostando nela). E o fato da origem ser (0,0) é que é muito mais fácil alinhar com os tilesets desta maneira. Então, na prática, deverá ficar algo parecido com isto:
Nomeie esta nova mask de sprite_mask_qudrado16x16. Esta mask será exclusiva das paredes invisíveis: todo o resto dos objetos utilizará a mask anterior (sprite_mask_padrao16x16).

Agora, crie um objeto chamado de "objeto_obstaculo_geral". Nele, coloque a mask quadrada 16x16 que acabamos de criar, desmarque a opção Visible e marque a opção Solid. Agora, precisamos que o nosso personagem entenda que isso é um obstáculo, e ele não pode passar por ele. Quero que note que isso não deve ser feito ao contrário: nunca devemos ensinar o obstáculo a não deixar o personagem passar por cima dele. Isso se deve ao fato de que em todas as rooms teremos um único personagem, e dezenas ou centenas de paredes invisíveis. É muito melhor ensinar um que vale por todos, do que ensinar todos para lidar com um. Além do mais, para objetos em que usaremos muitas instâncias simultaneamente como é o caso das paredes invisíveis, é sempre melhor que usemos o mínimo de eventos possível, para não sobrecarregar a memória do computador. 

Já podemos deixar o objeto_obstaculo_geral de lado então, e voltemos ao objeto_jogador_geral mais uma vez. Desta vez uma coisa bem rápida: vamos adicionar um evento de Colisão entre o nosso objeto_jogador_geral com o nosso objeto_obstaculo_geral. Apenas este simples código irá resolver nosso problema:

{

//Ao colidir com o obstáculo, o jogador volta para as coordenadas anteriores à colisão, efetivamente tornando impossível ele passar por cima de um obstáculo qualquer.
x = xprevious;
y = yprevious;

}

Pronto! Agora temos o obstáculo em sí, a parede invisível. Mas para que ela faça algum sentido (à menos que o propósito do jogo seja encontrar o caminho através de um caminho invisível) precisamos também da parte visual. E isso é trabalho dos já mencionados Tilesets
Um tileset nada mais é que um plano de fundo (background) dividido em vários cortes iguais. O tamanho do Background pode ser qualquer. Eu recomendo que você, por hora, use um do tamanho 80x80. As divisões devem ser de 16x16 (para dividir, selecione a opção do background "Use as Tileset"). Na hora da edição da imagem, você só conseguirá ver onde estão as linhas de recorte se você configurar e ativar a Grid corretamente, desta maneira:
Lembre-se que deixar cada um no seu quadrado, literalmente, é muito importante. Você pode utilizar dois ou mais quadrados para um mesmo desenho, sem problemas, mas é importante respeitar as linhas de divisão dos outros desenhos. Neste Tileset, você irá colocar todos os seus obstáculos. Se precisar, aumente o tamanho do background utilizando a opção de edição "Resize cavas...", mantendo-os sempre á esquerda-superior, para evitar estragar os desenhos nas Rooms. Como os obstáculos são tamanho padrão 16x16, prefira manter o background em múltiplos de 16 para formar quadrados perfeitos (por exemplo, 80x80, 160x160, 128x128, 192x192, etc).

Assim que desenhado os obstáculos, distribua-os na rm_testroom e aplique a mesma lógica mostrada no início: coloque uma instância do objeto_parede_invisível sobre todos os tilesets que o jogador possa, de qualquer maneira, encostar.

É importante lembrar que, assim como os objetos, os tilesets também usam o recurso de depth. Logo, você pode criar várias camadas diferentes de tilesets, garantindo sempre que o tileset usando no chão fique abaixo do tileset da parede, por exemplo.

Para finalizar, para cobrir o plano de fundo você pode criar outro background comum, chamado bg_comum_0. Em seguida, para aplicar um background na room, basta ir na aba "backgrounds", selecionar o "background 0" e selecionar a imagem. Dica: ao usar uma imagem como background, desative a opção "Draw background color" para, de novo, poupar o máximo de memória da máquina possível.
Não se esqueça de desativar a "Draw Bg. Color", como eu fiz.
Feito isso, já podemos ir para a nossa primeira...


Observer se a movimentação da personagem está com fluidez. Observe se ele não fica travando nas paredes. Caso isso ocorra, o problema está provavelmente nas Mask tanto da personagem quanto da parede invisível. Lembre-se de manter as paredes invisiveis sempre organizadas e perfeitamente colocadas sobre os Tilesets, para não dar a impressão de que o jogo está com algum defeito.

Eis outro exemplo da utilização dos Tilesets com a disposição das paredes invisíveis:
Sem as paredes invisíveis.
Com as paredes invisíveis.
Certinho! Nossos obstáculos estão funcionando perfeitamente (uhul!)! E agora que você já sabe o truque, pode começar a praticar, criando novos Tilesets e treinando seu olho para perceber com mais velocidade onde você precisa ou não encaixar as paredes invisíveis. Tudo é questão de prática!

Com isso, já podemos ir para a próxima aula!

segunda-feira, 23 de março de 2015

[Game Maker] Aula 1: Meu primeiro personagem!

Olá! Neste guia, eu irei focar em dar dicas preciosas para alguém que está iniciando agora na GML, para começar com o pé direito!

Ao iniciar o trabalho em qualquer programa novo, é normal ficar perdido. O que fazer primeiro? Como fazer as coisas? O que é importante? Devo me preocupar com isso agora? São essas e muitas outras perguntas que passa pelas nossas cabeças. Mas, não se preocupe. Este guia foi feito para lhe abrir o caminho principal, do início até o Boss da fase! Não deixaremos nenhum item importante para trás!

Caso você não esteja muito familiarizado com a interface do Game Maker, sugiro que dê uma olhada na seção Introdutória antes. Então, vamos lá. Neste guia eu abordarei criação de personagens, inimigos básicos, obstáculos básicos, armadilhas básicas e salas simples. Para os mais avançados, já aviso que não entrarei em detalhes como Views, 

Índice:
1. Preparações básicas
2. Faremos o primeiro personagem

1. Preparações básicas:
Para viajarmos no conteúdo desse módulo inicial, vou propor um desafio essencial em qualquer jogo: criar o personagem controlável pelo jogador. Vale lembrar que eu sou um adepto ao estilo RPG Top-Down, então é neste estilo que eu irei usar para exemplificar.

Padrões do jogo:
Todos os jogos precisam de um padrão, para que permaneçam no mesmo estilo do começo ao fim. Para isso, você deve estabelecer primeiro alguns pontos de referência para depois sair criando todas as coisas.

No meu caso, o padrão de jogo é o seguinte:
Estilo: Top-Down (vista de cima, sem gravidade, personagem move-se para qualquer direção)
Tamanho da máscara: 16x16 (explicarei o que é isso já já!)
Origem da máscara: X:8 Y:8
Tamanho dos sprites: aproximadamente 16x16

Primeiro passo: criar uma sprite. Estas sprite deverá ser de tamanho padrão, contendo apenas uma subimagem de um círculo perfeito (é importante que ele esteja perfeito, cobrindo a imagem toda! Se precisar, use a tecla Shift enquanto faz o círculo) de qualquer cor opaca. Nas opções de Origem da imagem (Origin, como visto na imagem abaixo), marque x:8 y:8 (pois é o centro de uma imagem 16x16) ou simplesmente aperte o botão "Center" uma única vez. Algo parecido com isso:
Este tipo de sprite é importante para todos os jogos 2D, pois ele funcionará como uma "máscara" para todas as outras coisas. Em outras palavras, este círculo vermelho vai funcionar nos bastidores do jogo como uma espécie de área de contato para o jogador, inimigos, paredes, armadilhas... todos são círculos, só que o jogador não vê isso (claro que é possível criar infinitos tipos de máscaras, como quadrados, triângulos, maiores, menores, irregulares, ou todas elas juntas. Mas não iremos complicar muito por agora)!

Deixar a origem da imagem no centro é importante, como já dito, para o nosso progresso funcionar corretamente. Eu particularmente não tinha este recurso quando comecei a usar o Game Maker, e me acostumei a trabalhar sempre sem ele, deixando as origens das minhas imagens x:0, y:0. Isso, na verdade, sempre me dá trabalho adicional, mas o ponto que eu quero chegar é que deixarmos o ponto de origem da imagem centralizado é uma ótima convenção. Recomendo você começar desde já com este costume.

Conclua e coloque o nome da nossa máscara exatamente assim: "sprite_mask_padrao16x16". Feito isso, crie uma pasta chamada "Sprites importantes" na seção dos sprites (Botão Direito sobre a pasta fixa dos Sprites > "Create Group"). Coloque nossa máscara padrão nesta pasta de sprites importantes. A razão dela estar ali é que será um recurso que usaremos durante todo o jogo.

Só com isso já podemos ir para o próximo passo!

2. Faremos a primeira personagem
É mais que claro que quando pegamos um jogo qualquer com a personagem principal que aparentemente consegue destruir tudo, matar hordas de inimigos, gigantes, dragões, e todos os tipos de criaturas utilizando um arsenal mitológico de armas mágicas e poderes proibidos no qual ele vem adquirindo por toda sua aventura, e tudo isso sozinho, nos sentimos impressionados.

Muito bem, não vamos fazer nada disso. Ainda.

A verdade é que para fazer todos esses tipo de coisa, não é necessário ser nenhum Expert em GML. Basta dominar o básico, ter muita imaginação e muita paciência para aplicar o que você quer dentro do seu jogo. Como o objetivo deste guia é deixar as coisas claras, e não complicar sua vida, faremos uma personagem que apenas anda, ataca e coleta itens, ok? Muito bem, criaremos o primeiro objeto do jogo... a personagem!

Crie um objeto chamado "objeto_jogador_geral". Faça as coisas mais básicas nele já, colo marcar o check-up de Solid e adicionarmos a mask dele (que fizermos anteriormente). Como já dito, a máscara transforma o personagem num eterno quadrado para o funcionamento do jogo, que ajuda a impedir muitas situações de travamento em paredes e em inimigos enquanto anda ou ataca. Deixe-o de lado um pouco.

Voltamos ás sprites. Agora é a hora que os seus dotes artísticos falam mais alto, mas como o objetivo é simplificar, vamos usar o sistema de preguiçoso: uma única sprite animada para cada ação da personagem, que gira de acordo com a sua direção com uma função chamada image_angle. Vale lembrar que isso só funciona na versão paga do GM, e caso você não tenha adquirido-a, não se preocupe, pois nos guias seguintes abandonaremos o método do preguiçoso e usaremos algo que todos possam usar, algo que dá significantemente mais trabalho e resulta em cerca de 21 animações diferentes para uma única personagem.

Crie uma sprite chamada "sprite_jogador_andar". Faça-o no mesmíssimo padrão que fizemos a mask: tamanho 16x16, origem centralizada (não se esqueça!). Desta vez você pode colocar várias subimagens na sprite para fazer uma animação do personagem andando. Uma animação básica leva cerca de 4 subimagens, animações complexas podem levar números altos. De qualquer forma, isso não vai alterar na jogabilidade do nosso jogo. Um exemplo de animação básica que quebra um galho:

Se quiser salvar essa imagem e usá-la você mesmo:

Feito isso, deixe-a de lado e faremos outra animação necessária, seguindo sempre a mesma idéia. A nova sprite deverá se chamar "sprite_jogador_atacar". Neste caso, apenas 4 subimagens é muito pouco. Tudo vai depender da sua técnica e da sua capacidade artística. Outro detalhe, neste caso eu não usei uma sprite 16x16, e sim uma 20x20, para ter mais espaço para fazer a animação de ataque. É importante notar isso pois neste caso, a origem da imagem não pode ser centrelizada, e nem igual ás anteriores! (ou a imagem ficará deslocada quando o personagem atacar!). Caso você use a minha animação, o ponto de origem para que ela se alinhe com as outras animações é x:8, y:10. Caso você use a sua própria, você terá de alinhar a imagem conforme o necessário.

Imagem para salvar:

Certo, já temos as duas animações mais importantes! Normalmente uma personagem teria dezenas de animações, como pular, usar cada habilidade, morrer, descer e subir escadas, enfim, uma infinidade de ações visuais, mas não vamos nos preocupar com nada nisso agora. Vamos deixar só por isso mesmo.

Voltemos ao nosso "objeto_jogador_geral". Coloque a sprite padrão dele como "sprite_jogador_andar", e vamos começar a trabalhar encima dele. Primeiramente, deixe-me dar uma rápida refrescada de como funciona os eventos dos objetos:

1) O Create Event, Game Start e Room Start são eventos de inicialização. Tudo que você colocar dentro deles será feito em primeiro lugar, útil para estabelecer as regras.
2) O step event padrão é onde todos os códigos que precisam ser executados o tempo todo, de novo e de novo, até que o objeto seja destruído ou passe para uma outra room, como por exemplo scripts para os inimigos andar.
3) Os eventos de Colisão ocorrem ao bater o objeto em que a colisão é especificada, isso é muito importante para impedir que o jogador adentre a parede, por exemplo.
4) Existem outras dezenas de tipos de eventos, dos quais são úteis em situações específicas. Logo você descobrirá quando usar cada um deles, mas agora não é importante.

Para adicionar qualquer evento, apenas clique no botão "Add Event" e selecione qual você quer atribuir ao objeto. Usaremos todos os eventos vistos na imagem abaixo: Create Event, Step Event, Cinco funções para o teclado, o Event Other Animation End e um evento de Colisão. Eventos que ficam vazios somem automaticamente ao concluir a edição do objeto, então não se preocupe se você adicionar o evento errado. Por este mesmo motivo, não se preocupe em criar todos os eventos agora, fique calmo, e eu direi quando você deverá fazê-los.

Certo! Agora, no Create Event do "objeto_jogador_geral", crie uma folha de código vazia. Escreva apenas isso:

{
//Setup do personagem
var_atacando = false;
}

Acabamos de criar uma regra para o objeto. Agora ele reconhece uma nova variável, a tal da "var_atacando". Ele não sabe o que é isso, ele não sabe o seu propósito, e ele não faz a menor idéia de sequer vai usá-la alguma vez, mas o importante é que ele já conhece, e para ela foi atribuído um valor: false. Isso é equivalente ao binário 0. Para o computador, 1 e 0 é como sim ou não: tudo depende da pergunta. E agora que ele conhece a sua nova variável "var_atacando" e sabe que é "false", nós podemos utilizá-la para algum propósito. Para nós, é um pouco óbvio do por quê ela vai servir mais á frente, mas para o computador não - lembre-se sempre disso.

Agora vamos ao Step Event do objeto jogador. Crie outra folha de código vazia e este provavelmente será o maior código, onde atribuiremos um monte de novas regras que o nosso personagem deverá seguir para o jogo funcionar como o esperado. Estas regras serão totalmente lógicas: com um pouco de conhecimento básico em inglês e matemática, não será muito difícil você entender o que está se passando.

Eu também adicionei uma série de comentários no meio do código para ajudá-lo á entender a lógica. Para facilitar ainda mais (uau!), vamos dividir este código em quatro etapas só para questões didáticas (as quatro partes podem estar numa mesma folha de código, sem problema nenhum, porém você deve se lembrar que não é permitido separá-los em quatro grupos de {} chaves diferentes sem mais nem menos, logo evite copiar o código com as { chaves iniciais e as chaves finais }), assim, você poderá estudá-las separadamente sem precisar criar conexões entre elas.

Primeira etapa do step event: configuração rápida visual do jogador:

{
//1. Vira a sprite do jogador na direção que ele está no momento
image_angle = direction;
}

Antes de continuarmos o código do step event, vamos fazer com que o jogador se movimente. Para isso, temos que ensiná-lo como, e quando se movimentar. Adicione agora quatro eventos de teclado: um para virar á esquerda, outro á direita, outro para se mover á frente e outro para recuar. Eu recomendo as setas do teclado: Keyboard Up, Keyboard Down, Keyboard Left e Keyboard Right (use a imagem acima para ajudar na identificação se necessário). Você também pode usar o padrão "WASD".

No evento do botão para virar á esquerda (left), adicione o seguinte código:
{
//Lembre-se que uma rotação circular completa é 360 graus. Isso explica o número abaixo.

if direction < 360
    direction += 9;
else
    direction = 0;
}

No evento do botão á direita (Right), o exato oposto do código acima:
{
if direction >= 0
    direction -= 9;
else
    direction = 360;
}

No evento do botão para mover-se á frente (Up):
{
//A velocidade deve ser proporcional ao tamanho dos sprites que você vai usar. Esse valor mostra quantos pixels o objeto se move por ação. Neste caso, como o jogo utiliza um padrão de tamanho 16x16, 3 pixels por movimento é uma velocidade agradável: nem muito lerdo, nem muito rápido.

speed = 3;
}

E por fim, no evento de recuar (mover-se para trás, Down).
{
//Velocidade negativa = move-se na direção contrária. É interessante sempre colocar uma velocidade menor neste caso, pois é comum a personagem andar mais lenta para trás.

speed = (-2);
}


Muito bem. Agora, que atribuímos ás teclas direcionais a função de dar direção e velocidade ao nosso personagem, precisamos colocar nele um freio. Não fazer isso seria igual fazer um carro que só possui o acelerador: ele não para mais. E não existe ar no nosso jogo, logo, é quase uma simulação de vácuo: o personagem nunca vai perder essa velocidade adquirida.

Isso, é claro, não está certo. Pelo menos não nesta ocasião, então, para concertarmos isso, voltaremos ao código do Step Event para completá-lo mais uma vez. Já aviso: acostume-se sempre em viajar por entre os diferentes eventos ao montar qualquer coisa: nenhum evento age completamente isolado, um está dependendo do outro.

Segunda etapa do step event: questões de movimentação e de animação de movimentação

{
//Vale lembrar que o Game Maker possui uma série de variáveis pré-estabelecidas, como por exemplo: speed, image_speed, image_index, direction, etc...

//2. Se a speed do jogador for maior ou menor que zero...
if (speed > 0 || speed < 0)
    {
    
    //Diminui a velocidade do personagem, como um carro: desacelera ele quando o jogador parar de acelerar
    if speed > 0
        speed -= 1;
        
    //Velocidade negativa: anda na direção oposta, ou seja, para trás.
    if speed < 0
        speed += 1;
    
    //Faz com que a animação de andar aconteça
    image_speed = 0.25;
    
    }
}

Se você analisar o código acima, perceberá diversas programações lógicas extremamente simples que, como já dito, com um conhecimento básico em inglês e matemática, mesmo sem os meus comentários, você é capaz de perceber o que cada parte do código faz. Vale lembrar que ao montar um código, a ordem da cadeia lógica pode ser importante.

Agora, vamos deixar novamente o Step Event de lado. Vamos adicionar uma nova habilidade ao nosso personagem. Aquela que estávamos planejando desde o início, no Create Event: permitir que o personagem possa atacar. Nós já trabalhamos na animação do ataque do personagem anteriormente, agora é hora de a implantarmos nele. Para fazermos isso, nós precisaremos criar um novo objeto: o "objeto_ataque_geral". Como o próprio nome já diz, ele será o que de fato irá machucar os inimigos, e os inimigos o usarão para nos machucar.

O objeto_ataque_geral deverá ter a Mask e a Sprite da nossa "sprite_mask_padrao16x16", e também aplicaremos um novo conceito: desmarcaremos a opção Visible, ou seja, em código, visible = false. Quando não é visible, ele ainda existe, ele ainda está lá, e ele ainda faz tudo o que deve fazer. A diferença que o jogador não pode vê-lo, e é isso que nós queremos que a área do nosso ataque seja: algo que exista, mas que não fique visível ao jogador.

No Create Event do objeto_ataque_geral também usaremos algo extremamente útil, os Alarms. Ao todo, existem 12 alarms diferentes. Alarms são eventos que são ativados depois de um período determinado, executam o código atribuído á eles e depois só são executados novamente se forem acionados novamente. É exatamente igual um despertador da vida real: a diferença que este daqui não serve para fazer barulho. Os alarms são acionados pelo seguinte código:

Padrão: alarm[numero do alarm, entre 0 e 11] = (segundos que o alarm demora para acionar*30)
Exemplos: alarm[0] = 45; alarm[3] = 180; alarm[2] = 30; alarm[11] = 300 (Demoram 1.5, 6, 1 e 10 segundos para serem acionados, respectivamente). Bem, só usaremos um único Alarm por agora. Pode ser qualquer um, mas por convenção usaremos o alarm[0].

O alarm[0] do objeto_ataque_geral o destruirá, para que o ataque não fique existindo para sempre. Neste caso, atribuiremos uma vida útil rápida de um ataque, neste caso, 1/6 de segundo é o suficiente. No Create Event do objeto_ataque_geral colocaremos então:

{
//Lembre-se: 30 = 1 segundos, 60 = 2 segundos, 150 = 5 segundos, 300 = 10 segundos...)
alarm[0] = 5;
}

Agora, criaremos o evento do Alarm 0 ainda no objeto_ataque_geral e colocaremos o seguinte nele:
{
instance_destroy();
}

Como ainda não temos nenhum inimigo, deixaremos o ataque por isso mesmo. A verdade é que só precisamos disso para terminarmos a nossa personagem, depois nos preocupamos com o resto. Deixe o objeto_ataque_geral de lado e voltemos ao objeto_jogador_geral.

Muito bem. Agora vamos criar um novo evento de teclado. Eu escolhi a tecla Barra de Espaço para funcionar como tecla de ataque, mas você pode escolher qualquer outra de sua preferência.

No evento da tecla de ataque, atribuiremos o seguinte código:

if var_atacando == false
    {
    //Avisamos ao jogo que o personagem iniciou um ataque.
    var_atacando = true;
    
    //Criaremos uma instância do objeto_ataque_geral. "aaa" é como queremos que o jogo interprete o ataque criado. Pode ser qualquer outro nome, como "i", "yy", "var_ataque", "ataquecriado", etc...
    aaa = instance_create(x, y, obj_ataque_geral);

    //Então atribuímos variáveis para o ataque criado, no qual o jogo aprendeu que é o tal do "aaa".
    aaa.speed = 4;
    aaa.direction = direction;
    
    }

Certo. Agora, o jogador efetivamente "ataca". Mas para que isso seja 100%, precisaremos retornar ao Step Event para concluirmos as últimas etapas que darão vida ao personagem. Adicione as últimas peças de códigos que concluem a lógica:

Terceira etapa do step event: alternar corretamente entre a animação de andar e atacar
{
//3. Se o jogador estiver atacando...
if var_atacando == true
    {
    //Animação de atacar...
    sprite_index = spr_jogador_atacar;
    image_speed = 0.25;
    }
else
    {
    //Se não... ele tem a animação de andar.
    sprite_index = spr_jogador_andar;
    }
}

Última etapa do step event: toques finais para sofisticar mais a animação do personagem e evitar erros
{
//4. Caso o jogador não esteja andando nem atacando, a velocidade da animação dele zera e a subimagem volta ao zero
if (speed == 0 && var_atacando == false)
    {
    image_speed = 0;
    image_index = 0;
    }
}

Ótimo! Antes de realizar um rápido teste, temos que fechar o círculo ainda para que não haja nenhum erro. Ainda falta uma coisa importante! Veja só, para nós, o circuito se fecha no momento que o jogador lança o ataque, e este é destruído, e o jogador volta á sua posição original, pronto para realizar outro golpe, certo? Certo! Mas para o computador, não é tão simples. Ele não compreende isso, e por isso precisamos ensiná-lo o momento certo em que o ataque do jogador é finalmente finalizado. Na etapa do botão de ataque, como fizemos acima, nós avisamos que o jogador iniciou um ataque. Agora, temos que avisar que o ataque é finalizado no exato momento que o jogador guarda o seu punhal no bolso. Para a nossa sorte, existe um Evento que demarca este exato momento: Animation End.

Adiciona o evento Animation End no objeto_jogador_geral e adicione o seguinte código:

{
if var_atacando == true
    var_atacando = false;
}

Pronto. Agora o círculo se fecha: temos uma história com começo, meio e fim. Quando isso acontece, é sinal de que algo já está em pleno funcionamento e pronto para ser testado. Então, o que está esperando?


Salve o seu progresso, coloque o objeto_ataque_geral como visible temporariamente para que você conseguia ver se realmente existe um ataque saindo na direção certa da personagem enquanto ela ataca. Crie uma room qualquer e dê o nome de rm_testroom. Durante o teste, observe se a animação da personagem volta à posição original após o ataque, observe se ela anda em todas as direções (inclusive para trás).

Caso tudo ocorra bem, parabéns! Você conseguiu, e o mérito foi todo seu! Caso algo tenha dado errado, analise o que não está dando certo, volte atrás no passo que corresponde ao erro e observe o que está faltando.

Acho que estamos mais que prontos para ir para o próximo passo!