terça-feira, 14 de abril de 2015

[Game Maker] Extra: Movimentação básica alinhada á Grid

Fiz este post com o intuito de compartilhar um sistema de movimentação básico por setas direcionais para um jogo top-down 2D. A característica principal desta forma de movimento é que o personagem fica alinhando com qualquer grelha (no arquivo você encontrará a configuração para uma grelha 32x32, mas pode ser facilmente alterada).


ArquivoLinkNotas
Move_test.gmk DropboxArquivo para GM8.0


Imagens:

sábado, 11 de abril de 2015

[Game Maker] Aula 5 - Views e Interface do jogador

Oi! Vamos dar continuidade ao nosso projeto de GML inicial. Desta vez, eu irei falar um pouco sobre views, e como você pode utilizá-la. Também irei trabalhar mais com o conceito de Draw Event, para criarmos estão uma interface ao jogador, na qual entregará informações das quais ele precisa ter conhecimento para ter uma experiência agradável, como, por exemplo, a barra de vida do jogador e de seus inimigos.

Conteúdo
1. Introdução
2. Views
     2.1 Recursos do "quadrante vermelho"
     2.2 Recursos do "quadrante verde"
     2.3 Recursos do "quadrante roxo"
     2.4 Usos
3. Interface do jogador
4. Finalização

1. Introdução
View nada mais é que a câmera do seu jogo. Caso você queira, por exemplo, criar uma room 4000x2000, seria algo grande demais para caber na tela e, portanto, faria com que as coisas dentro dela ficassem minúsculas. Para isso serve a view: ela só mostra um pedaço de tamanho estabelecido ao jogador. Um jogo pode ter muitas views (um para cada personagem, por exemplo), mas não é recomendável, pois a cada view o jogo é "redesenhado", podendo cair o desempenho no que diz respeito á fluidez das imagens.

Eu não irei tomar como exemplo o projeto anterior e por isso não estabelecer padrões. O tamanho da câmera do seu jogo (da view) é algo muito pessoal, há quem prefira a câmera próxima à personagem, há quem prefira ela muito distante. Como sendo um jogo 2D, estas são as poucas coisas que podemos definir á respeito do câmera do jogo - se é que o seu jogo terá alguma.

Sendo assim, como fazemos para a interface se encaixar no jogo? Teríamos todos que fazer cálculos pessoais, em todos os jogos, pegando como base o tamanho da câmera do jogo, para então fazer inúmeros testes até que as coisas se encaixem como queremos? Bem, mais ou menos. Automatizaremos isso o máximo possível para que, caso seja necessário, as coisas se ajustem na câmera por conta própria caso a câmera mude de tamanho.

Para isso, nós não iremos criar nenhum outro recurso pois, felizmente, o próprio Game Maker já oferece este recurso, na aba Views nas Rooms.

Note que os recursos da aba views estão, mesmo que não visivelmente, divididos em três funções. Use a imagem acima como referência para as informações abaixo.

2.1. Quadrante vermelho
O primeiro passo, em todas as rooms, é habilitar o uso de Views, marcando a primeira lacuna, "Enable the use of views". Feito isso, esta room passará a ter uma (ou mais) câmera(s). Outro fator interessante é que podemos escolher se ela vai ou não estar visível a partir do momento que o jogador entra na room. Isso é útil, por exemplo, para mudar o estilo da câmera só, e exclusivamente, para uma batalha de boss especial, que fica fixa e é maior que a comum.

2.2. Quadrante verde
Aqui chegamos aos termos técnicos. Note que há dois grupos de lacunas aparentemente iguais, mas note que entendermos suas diferenças é importante.

View in room: aqui você pode especificar a posição inicial (X, Y) e o tamanho da área que a câmera irá cobrir dentro desta room (W e H, sendo W de "Width" [largura] e "H" e Height [altura]).

Port on screen: aqui é como a view irá aparecer para o jogador. Por exemplo, uma view que cobre apenas 160x80 de uma room não precisa ser necessariamente deste tamanho para o jogador. Aliás, é até recomendável que você amplie-a para ele, ou seu jogo seria minúsculo. O tamanho recomendável varia de jogo para jogo, mas prefira valores altos como o padrão 640x480. Lembre-se de manter a proporção exata entre a área que a view cobre seu jogo e o tamanho que ela irá aparecer ao jogador, para que a imagem não fica esticada ou comprimida. Por exemplo, supomos que sua view cubra uma área de 192x96 na room. Para aumentá-la corretamente mantendo a proporção, prefira multiplicar este tamanho por números inteiros, 5 por exemplo (960, 480).

2.3. Quadrante roxo
Neste quadrante, obtemos uma propriedade das views que é uma verdadeira mão-na-roda, e que provavelmente todos vocês estariam esperando para descobrir. Afinal de contas, como fazemos a câmera seguir o jogador? Como o jogo irá saber qual é a personagem que ele deve seguir constantemente? Bem, é aqui que a mágica acontece. Tudo é muito simples: basta selecionar o objeto que você quer que a view siga no "Object following".

O Hbor e Vbor se referem ás "bordas" invisíveis da câmera que, quando o objeto que a câmera está seguindo as toca, a câmera "anda". Para manter o personagem sempre centralizado na câmera, coloque estes valores como a metade do tamanho da view na room. Por exemplo, se sua view tem o tamanho de 250x120, então para mantermos o personagem centralizado colocamos a Hbor como 125 e a Vbor como 60.

A Hsp e Vsp referem-se á velocidade com que a câmera segue o jogador. Caso você queira que a câmera siga o jogador sempre na mesma velocidade que ele, não ficando para trás quando a personagem anda, basta deixar ambas -1.

2.4. Usos
Como já disse, as Views são algo de gosto pessoal, que podem ser usadas de infinitas formas. O guia está um pouco diferente dos outros, pois eu não quis tornar o tutorial sobre views algo que seja influenciado pelo meu gosto pessoal referente á câmera dos meus projetos. Eu, por exemplo, gosto de usar apenas uma única view pequena com um tamanho considerável ao jogador.

3. Interface do jogador:
Agora finalmente iremos retornar ao nosso projeto anterior. Aplicar a função das views é algo que fica á seu gosto: alguns jogos ficam melhores com, ou ficam melhores sem. E isso quem decide é você.

Porém, existe algo que é indispensável em qualquer jogo que seja: a interface do jogador, por mais simples que ela deva ser. Neste nosso projeto, faremos ela bem simples, como tudo dentro do jogo. Iremos adicionar informações sobre a vida da personagem, a vida dos inimigos e a pontuação atual do jogador. Para isso, iremos precisa da ajuda de um novo objeto. Nomeie-o de objeto_interface e coloque-o com depth baixa (algo em torno de -99). Lembre-se que ele não precisa de um sprite, mas precisa estar sempre Visible.

Este objeto deve estar presente em todas as rooms. Coloque-o sempre na posição de origem (0,0) para evitar que ele fique perdido dentro da room aleatoriamente. Neste objeto só iremos trabalhar no Draw Event:

{

with objeto_jogador_geral do
    {
    
    //Quadrado que servirá como base á barra de vida visual do jogador
    draw_set_color(c_black);
    draw_rectangle(x - 12, y - 20, x + 12, y - 12, 0);
    
    //Quadrado que servirá como barra de vida visual do jogador
    draw_set_color(c_green);
    draw_rectangle(x - 12 , y - 20,  (x - 12 + var_atributo_vida*24/var_atributo_vida_maximo), y - 12, 0);
    
    }

with objeto_inimigo_geral do
    {
    
    if var_provocado > 0
        {
        //Quadrado que servirá como base á barra de vida visual do inimigo
        draw_set_color(c_black);
        draw_rectangle(x - 12, y - 20, x + 12, y - 12, 0);
        
        //Quadrado que servirá como barra de vida visual do inimigo
        draw_set_color(c_green);
        draw_rectangle(x - 12 , y - 20,  (x - 12 + var_atributo_vida*24/var_atributo_vida_maximo), y - 12, 0);
        }
     }
    
}

Este código usa a função de desenhar retângulos com a finalidade de apresentar barras de vida ao jogador e do inimigo. Porém, para um bom observador, não foi difícil perceber um grande erro neste código: uma variável que é usada, tanto para o objeto_jogador_geral quando para o objeto_inimigo_geral, que não foi declarada em ambos, a tal da "var_atributo_vida_maximo". Isso certamente faria o jogo crashar assim que rodado. Então temos que consertar isso. 

No Create Event do objeto_jogador_geral, adicione-a da seguinte forma, logo após a  já existente "var_atributo_vida":

{
var_atributo_vida_maximo = var_atributo_vida;
}

E a mesma coisa no Create Event do objeto_inimigo_geral:

{
var_atributo_vida_maximo = var_atributo_vida;
}

Aproveitando a deixa, podemos aproveitar deste recurso para fazermos um "anti-bug". Obviamente não queremos que a vida da personagem ou dos inimigos ultrapasse a nossa nova var_atributo_energia_maximo. Então, no Step Event de ambos (objeto_inimigo_geral e objeto_jogador_geral), acrescente:

{
if var_atributo_energia > var_atributo_energia_maximo
    var_atributo_energia = var_atributo_energia_maximo
}

Mas... espere um pouco. No caso dos inimigos, esta variável seria igual para todos eles: 0. Para que ela seja "atualizada" com o número correto, teríamos que coloca-la depois de ter declarado seu novo valor, em todos os inimigos, de um em um. Neste caso, é uma tarefa muito fácil: tínhamos criado apenas dois inimigos, então deve ser uma tarefa fácil e rápida, porém em jogos mais avançados, o mal planejamento pode levar á tortuosas horas de reparação aos códigos. Diante de um caso destes, nós temos duas opções: fazer o trabalho na marra, o que é sempre a melhor opção, pois daí pode evitar futuros bugs em determinadas ocasiões, ou então fazer uma rápida "gambiarra". Neste caso, acho que... não faz mal nós utilizarmos deste método.

A ideia é fazer alguma alteração no objeto_inimigo_geral que permita fazer com que o valor da var_atributo_vida_maximo seja atualizada dentro do jogo para todos os inimigos, automaticamente. Um meio fácil para isso é recorrer aos Alarms. Crie um evento de alarm qualquer no objeto_inimigo_geral, neste caso, pode ser o Alarm[1] e dentro deste evento colocarmos:

{
var_atributo_vida_maximo = var_atributo_vida;
}

E então é só acionarmos este alarm no Create Event do mesmo:

{
alarm[1] = 1;
}

Está pronto!

Como último passo, adicionaremos uma nova informação á nossa pequena interface. Repare que as informações anteriores não dependiam do uso de views para se localizarem corretamente: eram barras de vida que ficavam imediatamente acima da cabeça do jogador e do inimigo. Mas ás vezes queremos que informações fiquem fixas em um local, especialmente utilizando das Views e aí teríamos que pegar um ponto de referência para tal. A grande sacada é que, ao invés de pegarmos um ponto imóvel, utilizamos como referência a própria view. Assim, a informação iria para a onda view vai, e tudo fica em seu lugar para sempre.

Habilite o uso de Views em uma room de testes. Não se esqueça de adicionar o objeto_interface nela. A view deve seguir o objeto_jogador_geral, e deverá mostrar apenas uma parcela da room. Poderíamos fazer isso sem o uso de views na verdade, mas não teria o menor sentido, pois quando a câmera é imóvel, basta selecionar o local fixo que a informação deverá aparecer e pronto. Quando se trata de uma câmera móvel como agora, devemos colocar um código flexível quando á isso.

Como não queremos perder tempo fazendo cálculos, podemos fazer algo genérico como, por exemplo (ainda no Draw Event do objeto_interface):

{
    draw_set_color(c_yellow);
    draw_set_font(fonte_padrao0);
    draw_set_halign(fa_center);
    draw_text(view_xview[view_current] + view_wview[view_current]/2, view_yview[view_current] - 80 + view_hview[view_current]/2, "PONTOS: "+string(global.var_pontos)+"!")
}

Dependendo do tamanho da sua view, o código acima pode encaixar bem. Lembrando que eu peguei uma view de tamanho 320x240, embora o código acima se encaixa automaticamente em qualquer view, bastando apenas alterar o "80" caso você queira a informação mais acima ou mais abaixo. Resultado dentro do meu jogo de exemplo:

4. Finalização:

Feito isso, acho que entendemos como funciona ambos os recursos da interface básica do jogador: as informações que seguem objetos, e as informações que ficam em um ponto fixo na tela. Com isso, você pode usar sua criatividade para resolver suas futuras necessidades, da maneira que você achar melhor, pois a base você já tem! 

Para sua comodidade, eu irei listar abaixo a lista das ações básicas de draw event, as quais você pode usar livremente (você encontra estas informações na seção Help do próprio Game Maker)

draw_point(x,y) Desenha um ponto na posição (x,y).
draw_line(x1,y1,x2,y2) Desenha uma linha iniciando na posição (x1,y1) até a posição (x2, y2).
draw_line_width(x1,y1,x2,y2,w) - Desenha uma linha iniciando na posição (x1,y1) até a posição (x2, y2) com a grossura w.
draw_rectangle(x1,y1,x2,y2,outline) Desenha um retângulo iniciando na posição (x1,y1) até a posição (x2, y2).
draw_roundrect(x1,y1,x2,y2,outline) - Desenha um retângulo com as pontas arredondadas iniciando na posição (x1,y1) até a posição (x2, y2).
draw_triangle(x1,y1,x2,y2,x3,y3,outline) - Desenha um triângulo com as vértices nas posições (x1, y1), (x2, y2) e (x3, y3).
draw_circle(x,y,r,outline) - Desenha um círculo iniciando na posição (x, y) com o raio r.
draw_ellipse(x1,y1,x2,y2,outline) - Desenha um elipse iniciando na posição (x1,y1) até a posição (x2, y2).

Agora, como sempre...

Espero que tudo tenha dado certo da melhor forma possível! Agora, quero propor um exercício para você fazer por conta própria. Lembra-se da cena de morte da personagem, que fizemos nas aulas passadas? Então, que tal usar de seus conhecimentos para colocá-la centralizada na view? 

Até a próxima!

Aula anterior - Próxima aula

sexta-feira, 3 de abril de 2015

[Game Maker] Aula 4 - Adicionando mais recursos importantes!

Dando continuidade ao nosso projeto de GML, iremos agora adicionar outros fatores importantes que você deverá criar o costume de empregar em seus futuros jogos. Vamos dar uma olhada em variáveis globais, em rooms de Setup, no Menu principal do jogo, e também iremos adicionar elementos importantes para o jogo em si, como itens.

Esta aula nós iremos dividir em duas partes, as quais irão se conectar no final. A primeira parte, iremos lidar como o "Out Game", coisas que não são feitas para serem jogadas e sim para fazerem o jogo funcionar, como rooms que servem apenas para "iniciar" o jogo e também o menu principal. A segunda parte, iremos adicionar mais elementos bacanas em nosso jogo, como itens e também formas de "passar para a próxima fase".

Índice:
1. Criando uma room de Setup
2. Criando uma room de menu principal
3. Criando itens e sistemas de pontuação
4. Detalhes finais

1. Criando uma room de Setup
Primeiramente, iremos criar uma nova room ao nosso jogo. Esta será de vital importância ao jogo, e deverá ser a primeira room de todas, portanto, acima de todas as outras, para que seja sempre a primeira a ser inicializada com o jogo. Chame-a de room_setup_geral.

Nesta room, iremos na aba Settings e clicaremos no botão "Creation Code". Nele, iremos adicionar o seguinte comando:

{
    //Setup de variáveis globais
    global.var_pontos = 0;
    global.var_opcao_do_menu = 0;
    
    //Vai para a próxima room
    room_goto_next();
}

Assim é a melhor forma de adicionar novas variáveis globais, ou seja, variáveis que servem para todas as rooms. Por enquanto, iremos adicionar apenas este sistema básico de pontuação e também uma variável que irá nos ajudar a construir o menu, que é o próximo passo.

2. Criando uma room de Menu principal:
O Menu principal pode ser feito de milhares de jeitos diferentes, mas eu tentarei mantê-lo o mais básico possível, contendo apenas o indispensável. Outro objetivo é manter o mouse desfuncional neste jogo, logo, não iremos fazer botões reais, e o menu será todo controlado pelo teclado, assim como o resto do jogo.

Criemos então outra room, que deve ser colocada logo em seguida da room_setup_geral. Esta por sua vez deverá se chamar room_menu_principal. Agora iremos criar um novo objeto, que deverá comandar as funções do menu principal. Chame-o de objeto_menu_principal. Com a ajuda da variável global que criamos no passo anterior, a global.var_opcao_do_menu, tudo ficará mais fácil.

No draw event do objeto_menu_geral, coloque o seguinte:

{
//Seleciona as opções de acordo com o número da variável global.var_opcao_do_menu
//Neste caso foi definido o seguinte:
//  0 = iniciar o jogo.
//  1 = Ajuda sobre o jogo.
//  2 = sair do jogo.
switch(global.var_opcao_do_menu)
    {
    case 1: var_meu_texto = "Sobre o jogo!"; break;
    case 2: var_meu_texto = "Sair do jogo!"; break;
    default: var_meu_texto = "Iniciar jogo!"; break;
    }


//Define como será desenhado o texto no jogo
draw_set_font(fonte_padrao0);
draw_set_color(c_white);
draw_set_halign(fa_center);
draw_set_valign(fa_middle);

//Desenha o texto com as caracteres e as características definidas acima, centralizado automáticamente com o tamanho da room.
draw_text_transformed(room_width/2, room_height/2 - 32, "TÍTULO DO SEU JOGO!", 2.5, 2.5, 0);
draw_text(room_width/2, room_height/2 + 128, "<    "+string(var_meu_texto)+"    >");
}

Isso irá desenhar o básico. Agora é a sua vez: enfeite o Menu principal com novos tilesets á sua escolha, da maneira que você achar melhor. Prefira também usar uma cor de fundo escura, ou então troque a cor do texto acima para uma cor escura (c_black, c_navy, c_dkgray...) para fazer contraste ao plano de fundo para que o jogador consiga ler o que está escrito. Não se esqueça de alterar a parte do nome do seu jogo também. Eis um exemplo de menu básico:

Se algo der errado, considere ver se você colocou a room_menu_principal depois da room_setup_geral. Caso o "í" do "título" fique bugado, significa que a fonte que você está usando na fonte_padrao0 não possui caracteres acentuados (verifique também se a Character Range da fonte_padrao0 está de "0 til 255"). Agora o menu está visualmente pronto, mas precisa ser trabalhado para que funcione realmente.

No objeto_menu_principal adicionaremos o seguinte no Create Event:

{
var_delay = 0;
}

E no Step Event:

if var_delay > 0
    var_delay -= 1;
else
    {
    
    //Lembre-se que definimos três opções no menu principal, sendo a primeira a número 0.
    //Logo, o número máximo de opções é 2.
    
    if keyboard_check(vk_right)
        {
        //Trava as opções por um pequeno período
        var_delay = 6;
        
        //Se a opção selecionada não for a última... (!= 2)
        if global.var_opcao_do_menu < 2
            global.var_opcao_do_menu += 1;
        else
            global.var_opcao_do_menu = 0;
        }
        
    if keyboard_check(vk_left)
        {
        //Trava as opções por um pequeno período
        var_delay = 6;
        
        //Se a opção selecionada não for a primeira... (!= 0)
        if global.var_opcao_do_menu > 0
            global.var_opcao_do_menu -= 1;
        else
            global.var_opcao_do_menu = 2;
        }
    
    }

Agora você já consegue fazer transição entre as opções do menu, embora não funcionem realmente. Recomendo que você faça um teste agora para ver se tudo está de acordo. Caso positivo, continuaremos então!

Ainda no Step Event do objeto_menu_principal, acrescente:

{

//Ao pressionar a tecla "enter"...
if keyboard_check(vk_enter)
    {
    
    switch(global.var_opcao_do_menu)
        {
        case 1: show_info(); break;
        case 2: game_end(); break; 
        default: room_goto_next(); break;
        }
    
    }

}

Com este simples comando, todas as nossas opções de menu agora possuem comandos básicos importantes para o jogo. No caso do opção 1, "Show Info", abrirá uma tela de informações própria do Game Maker. Para alterá-la, clique no ícone respectivo da barra de recursos:

Você também pode alterar as configurações desta tela em como ela aparece durante o jogo, indo em File > Options.

Com isso, completamos o nosso menu principal básico do jogo, e podemos então ir para a segunda parte da aula.

3. Criando itens e sistemas de pontuação ao jogo
Agora iremos voltar á trabalhar com recursos que aparecem durante o jogo. Iremos adicionar os itens dos quais o jogador pode pegar. O plano é adicionar três tipos de moedas que garantem diferentes pontuações para o jogador obter durante as fases e dois itens especiais que ajudarão o jogador á completar sua missão.

Para isso, criemos cinco novos sprites padrão (16x16, origem centralizada): sprite_moeda_bronze, sprite_moeda_prata, sprite_moeda_ouro, sprite_pocao_cura, sprite_pocao_invencivel. Os nomes são intuitivos e você pode adicionar qualquer animação básica nelas. Eis alguns exemplos:

Agora, criaremos um novo objeto: o objeto_item_geral. No Step Event dele, colocaremos o seguinte:

if place_meeting(x, y, obj_jogador_geral)
    {
    
    alarm[0] = 1;
    
    }

E no evento do Alarm[0]:

{
instance_destroy();
}

E no Create Event, para que a animação fique mais lenta e para que o item fiquem abaixo dos outros objetos:

{
image_speed = 0.25;
depth = 1;
}

Isso nos dará uma certa liberdade para alterar os diferentes itens da forma que quisermos. O plano é colocar o efeito de cada item no alarm[0] e em seguida chamarmos o event_inherited(), para que o item seja destruído, dando a impressão que ele foi consumido ou obtido pela personagem. Neste caso, criemos outros cinco objetos, com características padrões: objeto_item_moeda_bronze, objeto_item_moeda_prata, objeto_item_moeda_ouro, objeto_item_pocao_cura, objeto_item_pocao_invencivel. Todos eles com a sprite_mask_padrao16x16 como Mask, não sólidos, visíveis e com o Parent no objeto_item_geral. Agora é só alterar o alarm[0] do...

... objeto_item_moeda_bronze:
{
global.var_pontos += 1;
event_inherited();
}

... objeto_item_moeda_prata:
{
global.var_pontos += 5;
event_inherited();
}

... objeto_item_moeda_ouro:
{
global.var_pontos += 10;
event_inherited();
}

... objeto_item_pocao_cura:
{
obj_jogador_geral.var_atributo_vida += 5;
event_inherited();
}

... objeto_item_pocao_invencivel:
{
//270 = 9 segundos.
obj_jogador_geral.var_inatingivel += 270;
event_inherited();
}

Agora todos os itens estão configurados! Você pode adicionar quantos mais quiser, e distribuí-los pelas fases como você desejar! Nota: se você quiser com que os inimigos gerem pontos ao morrer, você pode adicionar um Destroy Event no objeto_inimigo_geral e então colocar o seguinte comando:

{
global.var_pontos += 1;
}

4. Detalhes finais
Agora podemos ir para os últimos acertos referentes á este guia. Para fechar com chave de ouro, vamos adicionar alvo que funcione como "End Level" para o jogador, algo como um portal que a personagem adentre e vá para a próxima fase. Talvez mais á frente adicionaremos algum Boss para que ela deva derrotar e então prosseguir, mas agora esta não é a prioridade.

Criemos então um sprite para tal local. No meu caso, eu criei um sprite_portal e fiz a seguinte animação:

Depois, crieremos um objeto chamado objeto_end_level (deve ser um objeto visible, não sólido, com a Mask sprite_mask_maior24x24) e colocaremos um simples comando em seu step event, muito parecido com os itens que fizemos anteriormente:

{
if place_meeting(x, y, obj_jogador_geral)
    {
    
    global.var_pontos += 10;
    
    if room != room_last
        room_goto_next();
    else
        {
        
        show_message("Parabéns! Você completou o jogo! Sua pontuação foi de "+string(global.var_pontos)+"!");
        game_restart();
        
        }
    
    }
}

Se quiser, diminua a velocidade de animação através do Create Event:

{
image_speed = 0.25;
}

Pronto! Agora distribua o portal final no fim de algumas fases de teste, coloque itens espalhados, junto á um número moderado de inimigos Não se esqueça do que fizemos nas aulas passadas para isso.

Agora...


Verifique também se o menu está em ordem, e se as opções fazem suas respectivas funções corretamente. Se tudo deu certo, parabéns! Mais uma etapa completa! Até a próxima!