Programando Games com Ruby e MiniGL – Parte 6: Texto e gráficos básicos

Olá colegas!

Depois de um longo intervalo, cá estamos novamente para aprender e nos divertir mais um pouco com desenvolvimento de games, utilizando a linguagem Ruby e a biblioteca MiniGL.

Hoje, conforme comentado no final do último post, vamos tratar do componente de interface de usuário que ficou faltando, o campo de texto (TextField), e, aproveitando o ensejo, vamos falar de como desenhar texto na tela com diversos tipos de alinhamento, fontes, cores e efeitos. Por fim, vamos falar sobre como desenhar elementos gráficos “primitivos”, como linhas, triângulos e retângulos.

Primeiramente, vamos introduzir a classe TextField. Esta classe representa um campo de texto que permite entrada do usuário – corresponde, guardadas as devidas proporções, a um <input type="text">, para os familiarizados com HTML; a um JTextField da biblioteca swing, para os desenvolvedores Java; ou, ainda, a um TextBox de Windows Forms, para os que conhecem a plataforma .Net. Este tipo de controle é muito útil em jogos para, por exemplo, permitir que o jogador insira seu nome para salvar um jogo, ou para mostrar numa tabela de recordes.

Agora que já fomos apresentados, vamos ao que interessa: como usar um TextField no seu projeto MiniGL? Seguindo o padrão dos outros componentes de interface de usuário, apresentados no post anterior, o uso do TextField é muito simples, consistindo da instanciação com uma gama de parâmetros e da chamada dos métodos update e draw dentro dos seus correspondentes da janela do jogo.

Para visualizar isso, vamos fazer uma pequena melhoria no nosso jogo das shurikens: quando o jogador é atingido, vamos solicitar que ele preencha seu nome, e mostrar uma mensagem contendo o nome preenchido e a pontuação (note que isso é a base para a criação de uma tabela de recordes…). Vamos alterar apenas o arquivo ‘jogo.rb’:

...
class MeuJogo < GameWindow
  ...
  def initialize
    ...
    @txt = TextField.new(x: 300, y: 250, font: @font, img: :textField, max_length: 15, margin_x: 4, margin_y: 7)
    @ok_btn = Button.new(350, 315, @font, 'OK', :btn) {
      if @state == :end
        @msg = "Parabéns, #{@txt.text}, você fez #{@score} pontos!"
        @state = :msg
      else
        @state = :new
      end
    }
  end
  ...
  def update
    if @state == :playing
      ...
    elsif @state == :end
      KB.update
      Mouse.update
      @txt.update
      @ok_btn.update
    elsif @state == :msg
      Mouse.update
      @ok_btn.update
    else
      Mouse.update
      @menu.update
    end
  end

  def draw
    ...
    if @state == :playing
      ...
    elsif @state == :end
      @txt.draw
      @ok_btn.draw
    elsif @state == :msg
      @font.draw @msg, 200, 250, 0
      @ok_btn.draw
    else
      @menu.draw
    end
  end
end
...

Para executar o código acima, você vai precisar de uma imagem chamada ‘textField.png’ na pasta padrão de imagens (/data/img). Você pode usar a imagem abaixo:

textField

Basicamente essas alterações acrescentaram um novo estado ao jogo (:msg), durante o qual é exibida a mensagem com o nome e a pontuação do jogador; O estado :end agora é usado para mostrar o campo de texto onde o jogador pode preencher seu nome; O botão @ok_btn serve para alternar entre os estados e voltar ao menu inicial. Note que usamos a inicialização com named parameters para a instância de TextField que chamamos de @txt. Verifiquemos o significado dos parâmetros:

  • x: Coordenada x (posição horizontal) do TextField.
  • y: Coordenada y (posição vertical) do TextField.
  • font: Fonte (objeto Gosu::Font) usada para desenhar o texto do TextField.
  • img: Imagem do campo de texto. O tamanho da imagem é usado como delimitador para a “área ativa” do componente – se o usuário clicar fora desta área, o componente perde o foco.
  • max_length: Limite de caracteres do campo.
  • margin_x: Margem horizontal entre a borda esquerda do campo e o início do texto (ou a posição inicial do cursor), em pixels.
  • margin_y: Margem vertical entre a borda superior do campo e o limite superior do texto (ou do cursor), em pixels.

Na documentação, você pode verificar uma série de outros parâmetros que podem ser usados na instanciação de um TextField, inclusive um bloco de código que pode ser executado toda vez que o texto do componente é alterado. Divirta-se! 😛

Agora, observemos o seguinte: a mensagem com o nome e a pontuação do jogador não está visualmente muito agradável, concorda? É nesse ínterim que entra em cena a classe TextHelper! Esta classe da biblioteca MiniGL expõe métodos para facilmente escrever texto na tela com controle sobre a cor, o alinhamento e até alguns efeitos visuais básicos.

Para a mensagem da pontuação, provavelmente vamos querer uma fonte um pouco maior… Como o tamanho da fonte é definido no seu carregamento (via Res.font), vamos criar um outro objeto Gosu::Font para a fonte da mensagem. Então, um objeto TextHelper será inicializado usando essa fonte, e usado para desenhar o texto centralizado, com uma cor que se destaque melhor no fundo azul:

...
class MyGame ...
  ...
  def initialize
    ...
    @font2 = Res.font(:font1, 32)
    @th = TextHelper.new @font2
  end
  ...
  def draw
    ...
    if ...
    elsif @state == :msg
      @th.write_line @msg, 400, 250, :center, 0x543210
      ...
    else
      ...
    end
  end
end
...

Com apenas duas novas linhas de código (e uma linha alterada), deixamos a nossa mensagem muito mais agradável aos olhos:

mensagem

Os parâmetros do método write_line são fáceis de interpretar: o primeiro é o texto a ser desenhado; o segundo, a posição horizontal de referência – se o texto for alinhado à esquerda, é o limite esquerdo do texto, se for alinhado à direita é o limite direito, e se for centralizado, é o ponto em relação ao qual o texto fica centralizado; o terceiro é a posição vertical (limite superior) do texto; o quarto é o modo de alinhamento – neste caso usamos :center para centralizado, mas estão também disponíveis as opções :left (esquerda, padrão) e :right (direita); finalmente, o quinto é a cor (no já conhecido formato RRGGBB) em que o texto será desenhado.

Podemos também adicionar um pequeno efeito! Vamos apenas fazer alterações sutis na chamada de write_line (linha 14 do trecho anterior):

@th.write_line @msg, 400, 250, :center, 0xffff00, 255, :border

Que tal agora, hã? 🙂

As alterações foram:

  • O quinto parâmetro (cor) foi alterado para exibir o texto em amarelo.
  • Foram adicionados os parâmetros alpha (com valor 255, neste caso apenas para chegar até o próximo parâmetro) e effect, cujo valor é o símbolo :border, mas poderia também ser :shadow – experimente para ver 😉

Como você também poderá ver na documentação, o efeito também pode ser parametrizado, alterando sua cor, opacidade e tamanho. Mas não é só isso que a classe TextHelper tem a oferecer! Imagine um cenário em que você precisa escrever um texto longo, que provavelmente demandará quebras de linha. Tratar manualmente cada quebra de linha e chamar diversas vezes write_line é um trabalho insano… Por isso, existe o método write_breaking, especializado em textos mais longos. Este método permite que o texto quebre linha automaticamente de acordo com o espaço horizontal passado como parâmetro, além de tratar quebras explícitas – indicadas pelo caractere ‘\n’. E mais: em adição aos modos de alinhamento vistos anteriormente (:center, :left e :right), com write_breaking você pode ainda usar o valor :justified, que produz o elegante efeito de texto justificado.

Vamos experimentar! No caso do nosso jogo, podemos escrever um breve texto de instruções. Para isso, vamos alterar o ‘menu.rb’:

...
class Menu
  def initialize(font)
    ...
    @th = TextHelper.new @font
  end
  ...
  def draw
    if @state == :start
      ...
      @th.write_breaking(
        "Mova a carinha com as teclas direcionais. "\
        "Evite as shurikens pelo máximo de tempo que "\
        "puder. Por quantos segundos você é capaz de "\
        "sobreviver??",
        200, 420, 400, :justified)
    else
      ...
    end
  end
end

Agora, logo ao iniciar o jogo, você verá este texto de instruções, agradavelmente alinhado numa região de 400 pixels de largura (o quarto parâmetro da chamada), no centro da tela.

Ok, acho que com relação a textos já temos um bom arsenal em mãos. Para fechar o post, vamos ver como desenhar elementos gráficos básicos. Eles podem ser úteis em algumas situações para dar um pequeno “plus” em partes da interface do jogo. Por exemplo, no nosso caso, poderíamos colocar um retângulo envolvendo os botões do menu. Vamos a ele então:

...
class Menu
  ...
  def draw
    if @state == :start
      G.window.draw_quad 300, 200, 0x66ff0000,
                         500, 200, 0x6600ff00,
                         300, 400, 0x660000ff,
                         500, 400, 0x66ffff00, 0
      @start_components.each ...
      ...
    else
      ...
    end
  end
end

Note que draw_quad é uma função da janela principal fornecida pela biblioteca Gosu. Porém, o rápido acesso à janela por G.window é disponibilizado apenas com a MiniGL. Os parâmetros de draw_quad seguem a seguinte lógica: x, y e cor do primeiro vértice do retângulo, x, y e cor do segundo, x, y e cor do terceiro, x, y e cor do quarto, e z-index (ordem de desenho). Por ser uma função da Gosu, as cores são especificadas em formato AARRGGBB. Vale frisar que, ao fornecer diferentes cores para os vértices, a biblioteca cria um gradiente entre essas cores, gerando um efeito visual interessante. Para desenhar um retângulo de uma cor só, basta fornecer o mesmo valor para o terceiro, o sexto, o nono e o décimo-segundo parâmetros.

A classe Gosu::Window oferece ainda os métodos draw_triangle e draw_line, que desenham um triângulo e uma linha, respectivamente. Os parâmetros seguem exatamente a mesma lógica, porém em draw_triangle são três conjuntos de x, y e cor, e, em draw_line, são dois. Exemplos:

# um triângulo retângulo vermelho no canto da tela
G.window.draw_triangle 0, 0, 0xffff0000,
                       0, 100, 0xffff0000,
                       100, 100, 0xffff0000, 0
# uma linha horizontal variando de preto a branco, no meio da tela
G.window.draw_line 300, 300, 0xff000000,
                   500, 300, 0xffffffff, 0

E é isso! Espero que tenham se divertido e que já estejam produzindo seus próprios jogos interessantes com Ruby e MiniGL. 🙂

No nosso próximo encontro, vamos falar um pouco de física… Não perca!

Programando Games com Ruby e MiniGL – Parte 5: Interface de Usuário

Olá meus caros, sejam bem-vindos a mais uma etapa do tutorial de desenvolvimento de games com a linguagem de programação Ruby e a biblioteca MiniGL!

Mais uma vez, tivemos um hiato meio longo desde o último post… Mas um dos principais motivos para isso é justamente um projeto de jogo em Ruby com MiniGL no qual estou trabalhando (que mencionei no primeiro post, lembra?).

OK… Dadas as devidas explicações, vamos ao que interessa! Hoje, vamos construir uma interface de usuário amigável para o nosso jogo. Sim, isso significa que não vamos trabalhar muito na mecânica do jogo propriamente dita, mas num elemento que desempenha um papel importantíssimo (que às vezes passa despercebido) na atratividade e jogabilidade do jogo, que são as interfaces com que o usuário tem de interagir ao entrar no jogo, para salvar o jogo, para mudar as opções, os controles, etc.

Felizmente para nós, a biblioteca MiniGL oferece já prontos alguns dos mais comuns elementos de interface de usuário: botões, check-boxes, campos de texto, listas de opções (dropdowns) e até barras de progresso. Estes elementos são representados, respectivamente, pelas classes Button, ToggleButton, TextField, DropDownList e ProgressBar. Se você já trabalhou desenvolvendo sistemas, tanto para desktop quanto para a web, deve ser familiarizado com esses componentes.

Legal! Agora, como usamos tudo isso? Vamos resgatar aquele sensacional jogo da carinha feliz e das shurikens (você pode obter o código completo na parte 3) e adicionar a ele um menu inicial, com direito a tela de opções e botões para jogar e sair. Vamos elaborar estas telas de modo a exemplificar o uso de cada um dos componentes citados acima. Preparado? Então, mãos à obra!

Primeiramente, seguindo as boas práticas da programação orientada a objetos, vamos criar uma nova classe para controlar essas telas iniciais. Essa classe deve ser declarada num novo arquivo, ‘menu.rb’:

require 'minigl'
include MiniGL

class Menu
  def initialize(font)
    @start_components = [
      Button.new(350, 235, font, 'Jogar', :btn) {
        G.window.start_game
      },
      Button.new(350, 285, font, 'Opções', :btn) {
        @state = :options
      },
      Button.new(350, 335, font, 'Sair', :btn) {
        G.window.close
      }
    ]
  end

  def update
    @start_components.each { |b| b.update }
  end

  def draw
    @start_components.each { |b| b.draw }
  end
end

Nós já havíamos visto um exemplo de uso de botão na parte 3 do tutorial, então o código acima não deve ser totalmente estranho para você. Basicamente, o construtor da classe Menu recebe um objeto do tipo Gosu::Font (que representa uma fonte para escrever texto) como parâmetro, e inicializa uma lista de componentes, start_components, nesse caso apenas botões, com essa fonte e algumas ações pré-definidas. Em dois deles, nota-se o uso de G.window. A classe G é fornecida pela MiniGL e expõe diversas variáveis globais do jogo, como por exemplo uma referência à janela principal (window). O método close é herdado das classes ancestrais de GameWindow, e causa o fechamento da janela e portanto o fim da execução do programa; O método start_game nós ainda vamos programar…

Além disso, definimos métodos update e draw para o menu, que serão chamados dentro dos métodos update e draw da janela do jogo. Esta é uma arquitetura comum a ser usada para todos os diferentes objetos do jogo – e é usada, por exemplo, pelos próprios botões e outros componentes fornecidos pela MiniGL.

Agora, vamos abrir o arquivo ‘jogo.rb’ e editar algumas coisas:

...

class MeuJogo < GameWindow
  def initialize
    super 800, 600, false
    @sprite = GameObject.new 0, 0, 72, 72, :img1, Vector.new(-14, -14)
    @state = :new
    @font = Res.font(:font1, 16)
    # o botão, '@btn', será substituído pelo menu
    @menu = Menu.new(@font)
    #############################################
  end

  ...

  ############ Novo método
  def start_game
    @sprite.x = 364
    @sprite.y = 264
    @shurikens = []
    @shuriken_prob = 0.005
    @frame_counter = 0
    @score = 0
    @state = :playing
  end
  ########################

  def update
    if @state == :playing
      ...
    else
      Mouse.update
      ############
      @menu.update
      ############
    end
  end

  def draw
    ...
    if @state == :playing
      ...
    else
      ##########
      @menu.draw
      ##########
    end
  end
end

MeuJogo.new.show

As alterações estão destacadas com comentários. Note que agora temos um objeto da classe Menu para substituir aquele único botão que antes usávamos para iniciar/reiniciar o jogo. O conteúdo do recém-criado método start_game nada mais é do que o código que aquele botão executava.

Se você executar o jogo agora, verá que ele inicia mostrando um “menu” com três botões. O primeiro botão, “Jogar”, inicia o jogo da mesma maneira que antes – e quando o jogador perde, retorna para o menu; o segundo botão, “Opções”, não faz nada por enquanto; e o terceiro, “Sair”, finaliza o jogo. Um botão para sair não parece nada demais, mas já é uma experiência de usuário melhor do que clicar no “X” no canto da janela, não? E, mais importante do que isso, esse botão se torna fundamental se o jogo estiver em tela cheia (pois não haverá “X” no canto da janela onde clicar 😛 ).

Antes de prosseguirmos com a tela de opções, vamos explorar um pouco mais da classe Button: o construtor pode receber um grande número de parâmetros, permitindo uma personalização muito maior do visual e do comportamento do mesmo. Por exemplo, os quatro parâmetros seguintes aos que já estávamos usando (x, y, fonte, texto e imagem) definem a cor do texto para os 4 diferentes estados do botão. Experimente trocar a chamada

Button.new(350, 235, font, 'Jogar', :btn)

por

Button.new(350, 235, font, 'Jogar', :btn, 0x0000ff, 0x666666, 0x00ff00, 0xff0000)

e veja o que acontece… Legal, não? Esse é um exemplo de customização visual que pode ser facilmente feita no botão. Outra coisa que você pode querer mudar é o alinhamento do texto. Isso também é bem fácil: os dois parâmetros seguintes definem se o texto deve ser centralizado horizontalmente e verticalmente, nesta ordem. Por exemplo, podemos fazer:

Button.new(350, 235, font, 'Jogar', :btn, 0x0000ff, 0x666666, 0x00ff00, 0xff0000, false, false)

O resultado não é muito agradável: o texto vai ficar “colado” no canto superior esquerdo do botão. Porém, você pode especificar margens! Assim, o espaçamento do texto vai ficar exatamente como você quiser. Tente o seguinte:

Button.new(..., false, false, 6, 12)

Dependendo do tipo de desenho que você tem no botão, o alinhamento do texto é essencial para combinar corretamente com o plano de fundo. E há ainda mais parâmetros! Porém, para evitar de me alongar demais aqui, vou apenas deixar esse link para a documentação, onde todos os parâmetros são explicados com detalhes.

Diante disso tudo, você pode estar pensando: “são parâmetros demais, fica difícil saber o que é o quê…”. De fato, eu percebi essa inconveniência, mas não se aflija: graças ao poder de Ruby, temos uma solução muito elegante, chamada named parameters. O que isso quer dizer é que você pode chamar o método especificando os nomes dos parâmetros antes de passar os valores. Assim, pode especificar apenas aqueles para os quais você quer passar valores diferentes dos padrões. Por exemplo, se você apenas quisesse que o texto não fosse centralizado horizontalmente, poderia escrever:

Button.new(x: 350, y: 235, font: font, text: 'Jogar', img: :btn, center_x: false)

Inteligente, não? Sem os named parameters, você é obrigado a “passar” por todos os parâmetros que vêm antes daquele que você quer modificar, mesmo que vá passar os valores padrão para todos eles. Assim, essa sintaxe pode ser, além de mais clara, mais sucinta. 🙂

Certo, voltemos agora ao jogo. Temos de construir nossa tela de opções. Assim, vamos editar o arquivo ‘menu.rb’:

...
class Menu
  def initialize(font)
    @font = font
    @start_components = ...
    @options_components = [
      Button.new(350, 450, font, 'Voltar', :btn) {
        @state = :start
        G.window.carinha = @dl.value.split[1]
        G.window.difficulty = @pb.value
      },
      ToggleButton.new(320, 120, font, 'Mostrar pontuação',
                       :check, true, 0, 0, 0, 0, false,
                       true, 40) { |c|
        G.window.show_score = c
      },
      (@pb = ProgressBar.new(x: 337, y: 220, w: 125, h: 30,
                             value: 50, bg: 0x6666ff,
                             fg: 0x66ff66, font: font,
                             format: '%')),
      Button.new(307, 220, font, '-', 'mini-btn') {
        @pb.value -= 1
      },
      Button.new(462, 220, font, '+', 'mini-btn') {
        @pb.value += 1
      },
      (@dl = DropDownList.new(337, 280, font, :drop,
                              :btn, ['Carinha Amarela',
                              'Carinha Azul',
                              'Carinha Verde']))
    ]
    @state = :start
  end

  def update
    if @state == :start
      @start_components.each { |b| b.update }
    else
      @options_components.each { |b|
        b.update if b.respond_to? :update
      }
    end
  end

  def draw
    if @state == :start
      @start_components.each { |b| b.draw }
    else
      @options_components.each { |b| b.draw }
      @font.draw_rel 'Dificuldade', 400, 190, 0, 0.5, 0, 1, 1, 0xff000000
    end
  end
end

Agora nosso menu tem dois estados diferentes, que basicamente diferenciam entre a tela inicial e a tela de opções, a qual tem sua própria lista de componentes, @options_components. Aqui, temos um cenário um pouco mais interessante, com botões, um check-box, uma lista de opções e uma barra de progresso (o campo de texto veremos mais adiante…). Bom, são muitos detalhes a explicar, então vamos com calma…

Primeiro, aqui vão algumas imagens que você vai querer incluir no seu projeto para poder visualizar o exemplo acima:

check check.png

mini-btn
mini-btn.png

drop
drop.png

Note que a primeira imagem é uma spritesheet de duas colunas e quatro linhas: esse é o formato de imagem esperado pelo componente ToggleButton. As diferentes linhas correspondem aos mesmos estados dos botões comuns, e as duas colunas diferenciam entre o botão checado/selecionado e “deschecado/desselecionado” (essas palavras provavelmente não existem, mas você entendeu o que eu quis dizer, né? 😛 ).

Agora, vamos abordar os componentes um por um. Basta entendermos os parâmetros dos construtores:

Button.new(350, 450, font, 'Voltar', :btn) {
  @state = :start
  G.window.carinha = @dl.value.split[1]
  G.window.difficulty = @pb.value
}

Este primeiro botão é um botão no mesmo padrão dos anteriores, que serve para voltar para a tela inicial (alterando o estado do menu para :start). Antes de retornar, porém, ele salva as informações da barra de progresso (@pb) e da lista de opções (@dl) em variáveis da janela do jogo, cujos usos veremos mais adiante. O trecho @dl.value.split[1] deve ser lido da seguinte maneira: pega o valor do atributo value de @dl, que é uma string, chama o método split nesta string, sem parâmetros, que vai quebrar a string em todas as ocorrências de espaço em branco, retornando um vetor, e, finalmente, pega o item de índice 1 do vetor (isto é, o segundo). Assim, se o valor selecionado no dropdown é 'Carinha Azul', o valor guardado em G.window.carinha será 'Azul'.

ToggleButton.new(320, 120, font, 'Mostrar pontuação',
                 :check, true, 0, 0, 0, 0, false,
                 true, 40) { |c|
  G.window.show_score = c
}

Cuidado para não se confundir com os parênteses, chaves e ‘|’s aqui! Lembre-se: em Ruby delimita-se por ‘{‘ e ‘}’ blocos de código, e esses blocos podem receber parâmetros, cujos nomes são especificados entre ‘|’s. No caso da classe ToggleButton, o bloco é executado quando a seleção do botão é alterada, e o parâmetro c recebe true se o botão foi selecionado e false se a seleção foi retirada. Neste bloco, definimos a variável show_score da janela para o valor da seleção do botão.

Com relação aos parâmetros do construtor, eles são os mesmos da classe Button, exceto pelo parâmetro checked (neste caso com valor true), que aparece entre o parâmetro da imagem (:check) e a cor do texto (0), servindo para definir o estado inicial de checado/selecionado do botão.

(@pb = ProgressBar.new(x: 337, y: 220, w: 125, h: 30,
                       value: 50, bg: 0x6666ff,
                       fg: 0x66ff66, font: font,
                       format: '%'))

Aqui, temos dois detalhes interessantes de sintaxe: (1) estamos criando um objeto ProgressBar, atribuindo-o à variável de instância @pb do menu e incluindo-o na lista @options_components, tudo ao mesmo tempo! (2) Estamos usando aqui a sintaxe de named parameters – ela não é exclusiva dos botões, estando disponível para todos os componentes de interface de usuário fornecidos pela MiniGL.

Vamos aos parâmetros: x e y, à esta altura, você já deve deduzir o que são, certo? (se não for o caso, experimente rever um pouco dos posts anteriores); w e h representam a largura e altura da barra de progresso, respectivamente. Estas dimensões precisam ser informadas, mesmo que você forneça uma imagem para a barra de progresso – neste exemplo, não vamos usar imagens, mas simplesmente cores; value é o valor inicial da barra, o qual deve ser numérico; bg e fg são as cores ou imagens de fundo e de preenchimento da barra, respectivamente – se forem cores, utiliza-se o formato RRGGBB, com números hexadecimais; se forem imagens, devem-se passar os ids no padrão da classe Res (detalhes aqui); font, como você também já deve ter deduzido, é a fonte que será usada para o texto; e format é o formato do texto – por padrão, a barra mostrará ‘valor atual/valor máximo’, mas você pode especificar ‘%’ para que seja mostrada uma porcentagem.

Button.new(307, 220, font, '-', 'mini-btn') {
  @pb.value -= 1
}

Este é mais um botão comum. O único ponto a ser ressaltado aqui é a ação do botão: ele diminui o valor da barra de progresso @pb, manipulando o seu atributo value. Você pode manipular este valor à vontade, a classe ProgressBar fará as validações necessárias – não permitindo que fique abaixo de zero nem que ultrapasse o valor máximo.

Agora, vou pular o botão seguinte, que é praticamente idêntico a esse acima, e vamos direto ao dropdown:

(@dl = DropDownList.new(337, 280, font, :drop,
                        :btn, ['Carinha Amarela',
                        'Carinha Azul',
                        'Carinha Verde']))

Aqui estamos usando novamente o truque de instanciar o objeto, colocá-lo numa variável (@dl) e na lista, simultaneamente. Vamos agora entender os parâmetros: os dois primeiros são as coordenadas x e y; o terceiro é a fonte; o próximo é a imagem para a “cabeça” do dropdown, isto é, a parte que comumente tem uma setinha apontando para baixo, indicando que se trata de um dropdown; em seguida, a imagem a ser usada para as opções que são mostradas abaixo, quando a lista é expandida – tanto esta imagem quanto a anterior devem ser spritesheets de quatro linhas, como nos botões; por fim, o último parâmetro especificado aqui é a lista de opções propriamente ditas, que nada mais é do que uma lista de strings.

Ufa! Acho que é isso… Porém, para ver essa telinha simpática funcionando, vamos ter que fazer um pequeno ajuste em ‘jogo.rb’:

...
class MyGame ...
  #################### AQUI! #####################
  attr_accessor :show_score, :carinha, :difficulty
  ################################################

  def initialize
    ...
  end
  ...
end
...

Com esta linha, estamos liberando o acesso (de leitura e escrita) às variáveis que usamos na tela de opções. Legal, agora execute o jogo e se maravilhe com o funcionamento dos componentes (clique em ‘Opções’ na tela inicial)! Se você tiver feito tudo certo, deve estar parecendo com isso:

tela-ruby-minigl-5

Divertido, não? Agora, só falta usarmos de verdade as opções no jogo!

...
class MeuJogo ...
  ...
  def initialize
    super 800, 600, false
    @state = :new
    @font = Res.font(:font1, 16)
    @menu = Menu.new(@font)
    # ATRIBUINDO VALORES INICIAIS ÀS OPÇÕES #
    @show_score = true
    @carinha = 'Amarela'
    @difficulty = 50
    #########################################
  end

  ...

  def start_game
    # REINICIANDO A SPRITE A CADA VEZ, PARA PODER     ##################
    # CONSIDERAR A IMAGEM ESCOLHIDA NA TELA DE OPÇÕES ##################
    img =
      case @carinha
      when 'Amarela' then :img1
      when 'Azul'    then :img2
      when 'Verde'   then :img3
      end
    @sprite = GameObject.new 364, 264, 72, 72, img, Vector.new(-14, -14)
    ####################################################################
    @shurikens = []
    # USANDO O PARÂMETRO DE DIFICULDADE #
    @shuriken_prob = 0.0001 * @difficulty
    #####################################
    @frame_counter = 0
    @score = 0
    @state = :playing
  end

  def update
    if @state == :playing
      ...
      if rand < @shuriken_prob
        ...
      end
      ### USANDO O PARÂMETRO DE DIFICULDADE ###
      @shuriken_prob += 0.0000001 * @difficulty
      #########################################
      ...
    else
      ...
    end
  end

  def draw
    ...
    if @state == :playing
      ...
      ############### NOTAR O 'IF' NO FIM DA LINHA! ###############
      @font.draw @score, 5, 580, 0, 1, 1, 0xff000000 if @show_score
      #############################################################
    else
      @menu.draw
    end
  end
end

Acho que os comentários acima são auto-explicativos (se não for o caso, dê-me um toque nos comentários 😛 ). Ah, sim, acima são referenciadas duas outras imagens:

img2
img2.png

img3
img3.png

Pronto, agora sim. Experimente executar o jogo e brincar um pouco com as opções… você verá que elas funcionam mesmo! 🙂

Bom… eu havia prometido falar do TextField, mas o post já está bem comprido, não? Vamos deixar para a próxima, quando também falarei mais sobre como escrever textos com diversos tipos de alinhamentos e configurações visuais. Até lá!

Programando Games com Ruby e MiniGL – Parte 4: Gerenciamento de Recursos

Bom/boa dia/tarde/noite,

Esta é a quarta etapa do nosso tutorial de desenvolvimento de games com a linguagem de programação Ruby e a biblioteca MiniGL. Relembrando, no último passo nós fizemos um primeiro jogo de verdade, embora ele tenha ficado bastante simples, com muitos pontos para melhorar ou acrescentar.

Hoje, o post será mais curto, dedicado a explorar melhor algumas coisas que já vimos antes mas cujos detalhes eu fiquei empurrando com a barriga criteriosamente adiei para apresentar em posts futuros. Vamos falar de símbolos em Ruby e da classe de gerenciamento de recursos da MiniGL (Res).

Símbolos

Como vimos algumas vezes anteriormente, os símbolos são aquelas strings esquisitas iniciadas por ‘:’, em vez de serem delimitadas por aspas, e que são utilizadas em diversos métodos, como por exemplo na especificação da imagem a ser usada por uma Sprite, na especificação do estado do jogo, no retorno de uma função para indicar um status, etc. Basicamente, os símbolos são strings reutilizáveis: são instanciadas apenas na primeira vez que aparecem, sendo recuperadas de uma tabela de símbolos em todas as utilizações subsequentes. Em termos de Ruby, se você usa um mesmo símbolo em dois lugares diferentes, eles serão referências a um mesmo objeto.

Um jeito interessante de enxergar os símbolos é como membros de uma enumeração que não foi previamente declarada. Ou seja, são um jeito conveniente de se codificar os diversos valores possíveis de uma variável de forma muito mais descritiva do que usando números, por exemplo. Para ilustrar melhor, recordemos o uso que fizemos no post anterior, para o atributo @state do jogo: é muito mais fácil entender o significado de @state = :new ou @state = :playing do que @state = 0 ou @state = 1, não?

Vale lembrar que símbolos e strings podem ser facilmente transformados um no outro, através dos métodos to_s (para string) e to_sym (para símbolo). Por fim, deixo aqui a referência de um artigo bastante interessante sobre símbolos (em inglês): http://www.randomhacks.net/2007/01/20/13-ways-of-looking-at-a-ruby-symbol/.

Legal! Agora que você já entende um pouco melhor o que são as strings esquisitas começadas por ‘:’, vai se sentir mais à vontade para continuar explorando a MiniGL (onde símbolos aparecerão com frequência).

Gerenciamento de recursos

Finalmente, chegou a tão prometida hora em que você vai entender e manipular o carregamento de imagens, sons, fontes, e outros, da maneira que você quiser! Vamos explorar mais a fundo as funções da classe Res, e os locais onde ela é utilizada “por debaixo dos panos”.

Comecemos por um exemplo conhecido:

@font = Res.font(:font1, 16)

O código acima é responsável por carregar o arquivo de fonte ‘data/font/font1.ttf’ (caminho relativo ao arquivo de código do jogo), com tamanho de 16 pixels. OK, mas, se passamos apenas o parâmetro :font1, como a classe Res deduz todo o resto do caminho? Simples: há uma convenção ou, se preferir, um padrão pré-estabelecido, para o carregamento de todos os tipos de recurso utilizados comumente em jogos – imagens, efeitos sonoros, músicas e fontes – seguido pela MiniGL.

Esse padrão tem uma lógica simples: cada tipo de recurso ficará num subdiretório do diretório ‘data’, e o nome do subdiretório remete ao tipo de recurso. A ideia de usar esse primeiro diretório, ‘data’, em vez de usar apenas os diretórios para cada tipo de recurso, é agrupar todos os recursos – como os exemplificados acima -, e também outros possíveis arquivos auxiliares – como, por exemplo, um arquivo contendo as strings usadas no jogo -, de modo que fiquem separados dos arquivos de código. Se não usássemos esse agrupamento, o diretório onde fica o código do jogo ficaria muito mais “bagunçado”, com uma série de pastas. E mais: se você quisesse organizar alguns arquivos de código numa subpasta, a bagunça ficaria ainda maior.

Enfim, dadas as justificativas, segue uma enumeração completa dos diretórios pré-definidos:

  • ‘data/img’, para imagens;
  • ‘data/tileset’, para tilesets; (mais sobre isso em outros posts…)
  • ‘data/sound’, para efeitos sonoros;
  • ‘data/song’, para músicas;
  • ‘data/font’, para fontes.

Além dos diretórios, há uma convenção relacionada às extensões dos arquivos, que visa a tornar ainda mais sucinta a sintaxe de carregamento – note que usamos o símbolo :font1, e não algo como “font1.ttf”. Para imagens e tilesets, a extensão padrão é ‘.png’; para efeitos sonoros é ‘.wav’; para músicas é ‘.ogg’; e para fontes, como você deve ter deduzido, é ‘.ttf’. Pronto, assim fica explicada a “magia” por trás da função Res.font. Ah, claro, devo citar aqui que a classe Res apresenta um método análogo a font para os outros tipos de recursos. Veja abaixo:

img = Res.img :img1 # carrega a imagem data/img/img1.png

spr = Res.imgs :spr1, 2, 3 # carrega a imagem
                           # data/img/spr1.png como
                           # uma spritesheet de duas
                           # colunas e 3 linhas,
                           # retornando, portanto, um
                           # array com 6 imagens.

til = Res.tileset '1' # carrega a imagem
                      # data/tileset/1.png como um
                      # tileset (explicações mais
                      # tarde). Note que o parâmetro
                      # aqui foi uma string e não um
                      # símbolo.

snd = Res.sound :snd1 # carrega o arquivo de som
                      # data/sound/snd1.wav

bgm = Res.song :bgm1 # carrega a música
                     # data/song/bgm1.ogg

Como exemplifiquei acima, o parâmetro para o carregamento do recurso não precisa ser um símbolo, mas pode ser uma string. No exemplo, foi utilizada uma string porque não se pode criar um símbolo cujo nome inicia por número, mas isso também será muito útil se você precisar definir dinamicamente o caminho de um arquivo, pois poderá fazer uso de interpolação, como abaixo:

img = Res.img "img#{numero}"

No código acima, se a variável local numero tivesse valor 5, por exemplo, a imagem carregada seria ‘data/img/img5.png’. Observação: a interpolação de strings em Ruby só é feita para as strings delimitadas por aspas duplas! Se você escrevesse 'img#{numero}' no código acima, a biblioteca ia procurar pelo arquivo ‘data/img/img#{numero}.png’, o que provavelmente não seria o que você queria…

Certo, vamos adiante. Ainda há muito o que comentar. Por exemplo, dependendo do tipo de recurso, os métodos de carregamento poderão receber parâmetros específicos: é o caso do número de colunas e linhas da spritesheet, em imgs, do tamanho dos tiles, em tileset (omitido no exemplo), e do tamanho da fonte, em font. O ideal é consultar a documentação da biblioteca para ficar a par de todos eles.

Se você se der ao trabalho de consultar a documentação como sugerido acima, verá também que há mais parâmetros, comuns a todos os métodos, que não foram mostrados: global e extension. O papel do parâmetro global é um pouco complicado de explicar, mas tem a ver com gerenciamento de memória: se você carregar uma quantidade muito grande de recursos e quiser liberar um pouco de memória, pode usar o método Res.clear – ele vai limpar todas as referências a recursos já carregados, exceto os que foram carregados com o parâmetro global = true. Vale notar que global é true por padrão para as fontes (porque a maioria dos jogos dificilmente utiliza uma variedade muito grande de fontes). Já o papel de extension é muito mais fácil de deduzir: ele define as extensões padrão que comentamos anteriormente. Ou seja, se você quiser carregar um recurso que não segue a extensão padrão de seu tipo, deve especificar a extensão no parâmetro extension. O exemplo abaixo carrega o arquivo de fonte ‘data/font/font2.otf’ como não global:

font = Res.font :font2, 20, false, '.otf'

Agora, vamos falar do “separador”. Os métodos de carregamento foram idealizados para a utilização de símbolos, mas nomes de símbolos não podem conter ‘/’ (nem outros caracteres que representam operadores em Ruby, como ‘-‘, ‘+’, etc.). Assim, haveria problemas para carregar arquivos em subdiretórios dos diretórios pré-definidos. Porém, para contornar este problema, a MiniGL utiliza também a convenção do “separador”, um caractere que pode ser colocado no símbolo para substituir o ‘/’, e indicar subpastas. Seu valor padrão é ‘_’. Assim, para carregar o arquivo ‘data/img/icon/1.png’, basta especificar:

icon1 = Res.img :icon_1

Tá… tudo muito bem até aqui mas… e se os meus arquivos de recursos tiverem o caractere ‘_’ no nome? Bem, isso seria um problema se não fosse possível mudar o separador. Você pode especificar o caractere que quiser para usar como separador (na verdade, qualquer string pode ser usada), e se achar que ele mais atrapalha do que ajuda, basta defini-lo para um valor que você sabe que nunca vai aparecer nos nomes de seus arquivos. Por exemplo:

Res.separator = '!@#$%&*'

No caso acima, o separador só vai te afetar se você tiver algum arquivo contendo ‘!@#$%&*’ no nome (pouco provável, certo?). Você ainda poderá carregar arquivos de subpastas usando strings, em vez de símbolos, para especificar o caminho.

Bom, agora que já mostramos que você pode configurar o separador usado pela classe Res, há algo talvez mais relevante que eu ainda não tinha revelado: você pode configurar também todos aqueles diretórios padrão! Afinal, você não é obrigado a concordar com a organização dos arquivos em subdiretórios de um diretório chamado ‘data’ – embora eu a considere bastante razoável. 😛
Veja os métodos abaixo:

Res.prefix = '' # eliminando o prefixo comum 'data'

Res.img_dir = 'image' # agora as imagens são carregadas
                      # diretamente do diretório 'image'

Res.sound_dir = '' # deixando os arquivos de som no
                   # mesmo diretório que os arquivos
                   # de código, ou ainda permitindo
                   # o uso de caminhos absolutos
                   # (não faça isso!)

Res.prefix = 'resources' # utilizando um prefixo
                         # alternativo 'resources'

Res.tileset_dir = 'ts' # agora os tilesets são procurados
                       # em 'resources/ts'

Res.song_dir = 'bgm' # as músicas são buscadas em
                     # 'resources/bgm'

Res.font_dir = 'ttf' # e as fontes no diretório
                     # 'resources/ttf'

Interessante, não? Agora, o detalhe que faltou: e os outros lugares onde usamos símbolos para especificar imagens (no caso, na instanciação de Sprite, GameObject e Button)? A resposta é simples: os construtores destas classes simplesmente repassam o símbolo (ou string) recebido para o método Res.imgs (pois carregam spritesheets). Assim, todas as regras que se aplicam aos métodos de carregamento direto de imagens pela classe Res servirão para a definição da imagem usada em Sprites, GameObjects, Buttons e outras classes que ainda vamos ver…

Note que isso implica em algumas restrições: você não tem controle sobre os parâmetros global e extension usados nesses casos – global será sempre false e extension sempre '.png'. Mas esses valores foram definidos criteriosamente para atender aos casos mais comuns: PNG é o formato mais comum de imagem que suporta transparência, e esta última quase sempre é importante para sprites, e para qualquer elemento que não tenha formato retangular; num jogo que possui muitas sprites, onde a liberação de memória tende a ser importante, cada uma das sprites também tende a ser pouco utilizada, e portanto o fato de não ser global não será grande prejuízo.

Pronto! Isso é tudo. Agora você deve ser mestre em carregamento e gerenciamento de recursos com MiniGL. Eu sei que esse post deve ter sido meio “chato”, mas infelizmente tudo na vida tem seu lado chato, não é mesmo? 😛

O próximo será muito mais empolgante, eu prometo. Espero vocês lá, hein!
Até!

Programando Games com Ruby e MiniGL – Parte 3: Um jogo (de verdade)

Olás!

Desculpem a demora para postar, mas vocês logo verão que valeu a pena… 🙂

Dando continuidade à parte 2 do nosso espetacular tutorial de Ruby + MiniGL + programação de games, vamos agora fazer um jogo – mas, como o título desta parte 3 sugere, um jogo de verdade! Ou seja, um jogo com objetivo, desafios, pontuações e etc.

No final da lição de hoje, teremos um joguinho simples, com visual simples, mas que já poderá render alguma diversão (será?)… Vamos construir em cima do que tínhamos começado (com aquela carinha feliz), e criar um jogo de desvio de obstáculos. Com isso, teremos a oportunidade de explorar as seguintes features da MiniGL:

  • Objetos de jogo (GameObject)
    • Limites físicos e intersecções
    • Animações
  • Interação com o teclado
  • Uso de botões

A biblioteca tem muito mais a oferecer, mas vamos começar de baixo. 😛

Primeiro, vamos programar os obstáculos. Estes serão representados por uma classe que faremos herdar da classe GameObject, oferecida pela MiniGL. A classe GameObject é semelhante a uma Sprite, porém agrega o conceito de limites “físicos” ao objeto, em outras palavras, será possível checar, com facilidade, se o objeto está encostando em outro, por exemplo. Além disso, os limites físicos do objeto não necessariamente são associados com o tamanho da imagem, permitindo uma interação mais realista entre os objetos – mais para a frente veremos um exemplo que tornará tudo isso mais claro.

Para programar esta classe, o ideal é utilizarmos um arquivo diferente – um modelo clássico de organização de projetos orientados a objeto é colocar cada classe num arquivo, embora às vezes isso acabe gerando arquivos demais para o meu gosto… Vamos criar então um arquivo chamado ‘shuriken.rb’ (vocês logo verão por quê) no mesmo nível de ‘jogo.rb’, e adicionar uma imagem ‘shuriken.png’ na pasta de imagens, obtendo a seguinte estrutura para o projeto:

meu-jogo
├ jogo.rb
├ shuriken.rb
└ data
  └ img
    ├ img1.png
    └ shuriken.png

Para a shuriken, vamos utilizar uma spritesheet, para obter o efeito de animação:

shuriken

Spritesheets são imagens compostas de diversas variações de uma imagem. Em geral, são usadas para concentrar as animações de um elemento de um jogo numa única imagem. A imagem acima mede 60 por 60 pixels, mas cada “pedaço” que vamos usar na animação mede 30 por 30 pixels, e portanto queremos dividi-la em duas linhas e duas colunas. Felizmente, a MiniGL oferece um método muito fácil para carregar imagens como spritesheets e mostrar animações a partir delas. Você pode desfrutar disso na classe Sprite (não é à toa o nome, sacou?) ou em GameObject (que na verdade herda de Sprite), conforme veremos a seguir.

Então, hora de escrever um pouco de código. Abra o arquivo ‘shuriken.rb’ e comece a digitar:

require 'minigl'
include MiniGL

class Shuriken < GameObject
  def initialize(x, y, direction, speed)
    super x, y, 20, 20, :shuriken, Vector.new(-5, -5), 2, 2
  end
end

OK. Vamos ficar só com isso por enquanto para eu poder explicar os detalhes…

  • Na linha 1, você já sabe, estamos importando a biblioteca ‘minigl’ para usar neste arquivo. Mais à frente veremos que, quando referenciarmos este arquivo (shuriken.rb) no arquivo principal (jogo.rb), poderemos dispensar o require 'minigl' neste último, pois a biblioteca já foi importada pelo primeiro.
  • Na linha 2, você também já sabe, estamos incluindo o “namespace” da biblioteca para poder referenciar suas classes diretamente pelo nome.
  • Na linha 4, estamos declarando a classe Shuriken e indicando que deve herdar de GameObject.
  • Na linha 5, declaramos o construtor para a classe Shuriken (que posteriormente será acessado por Shuriken.new, lembra?), com os parâmetros x, y, direction e speed, os quais vamos usar para definir a posição inicial, a direção e a velocidade do movimento do objeto. Note novamente a ausência de tipos.
  • Na linha 6, estamos chamando o construtor da classe pai. Os parâmatros do construtor de GameObject são os seguintes:
    • x: coordenada horizontal inicial do objeto
    • y: coordenada vertical inicial do objeto
    • w (para o qual passamos o valor 20): largura “física” do objeto. Os limites físicos dos objetos são definidos por uma área retangular.
    • h (que também recebeu 20): altura “física” do objeto.
    • img (para o qual fornecemos :shuriken): código da imagem a ser usada para o objeto. Esse código segue as convenções de carregamento de imagens da MiniGL, que neste caso procura no diretório ‘data/img’ (diretório pré-definido para imagens) um arquivo chamado ‘shuriken.png’ (combinação do símbolo :shuriken com a extensão padrão ‘.png’). Veremos futuramente com mais detalhes essa codificação e como é possível alterar os padrões (e também uma descrição do que são símbolos em Ruby).
    • img_gap (que recebeu um objeto Vector): este elemento é o que permite a desacoplação entre o tamanho da imagem (ou o retângulo definido pela imagem) e os limites físicos do objeto. Basicamente, as coordenadas x e y do objeto serão somadas às coordenadas deste vetor (um objeto da classe Vector representa um vetor bidimensional, ou um par ordenado de duas coordenadas, que são os parâmetros do construtor) para determinar a posição em que a imagem será efetivamente desenhada. Isso trará o efeito de poder haver sutis superposições das imagens de objetos, mesmo que eles não estejam se superpondo fisicamente. Veja abaixo:
      exemplo-img_gap
    • sprite_cols (com valor 2): Número de colunas da spritesheet. Como vimos anteriormente, serão duas.
    • sprite_rows (com valor 2): Número de linhas da spritesheet. Também são duas, como pode ser visto na imagem da shuriken.
  • As linhas 7 e 8 apenas fecham os escopos abertos em 5 e 4, respectivamente.

Pronto para continuar? OK, vamos então completar o construtor da Shuriken:

class Shuriken < GameObject
  def initialize(x, y, direction, speed)
    super ...
    case direction
    when :up    then @aim = Vector.new(@x, @y - 630)
    when :right then @aim = Vector.new(@x + 830, @y)
    when :down  then @aim = Vector.new(@x, @y + 630)
    when :left  then @aim = Vector.new(@x - 830, @y)
    end
    @speed_m = speed
  end
end

Vejamos agora o que está sendo feito aqui (a partir desta explicação, eu pularei as linhas que contêm apenas a palavra-chave end, porque já ficou meio óbvio o que ela faz):

  • Na linha 4, estamos declarando uma estrutura case com a variável direction. Isto equivale ao switch de Java e algumas outras linguagens.
  • Na linha 5, definimos o que será executado quando (when) o valor da variável sendo testada (no caso, direction) for :up. Aqui, estamos criando uma variável de instância @aim, que receberá um objeto Vector, correspondendo à posição da tela até onde a shuriken deverá se mover quando for criada (mais detalhes adiante).
  • Nas linhas de 6 a 8, fazemos algo análogo à linha 5, definindo o alvo da shuriken (@aim) para cada valor de direction.
  • Finalmente, na linha 10, guardamos em outra variável de instância, @speed_m, o módulo da velocidade da shuriken. Digo módulo porque a velocidade propriamente dita é um vetor, com suas componentes horizontal e vertical (inclusive, a classe GameObject já define uma variável @speed com essa finalidade, e você não deve sobrescrevê-la com um número, que será o caso do parâmetro speed).

Talvez você já tenha deduzido em linhas gerais a essa altura, mas a mecânica do jogo que pretendemos fazer é a seguinte: o jogador controla a carinha feliz de modo a desviar das shurikens, que aparecerão aleatoriamente na tela se movendo em alguma das quatro direções que definimos no trecho acima. Se uma shuriken encostar na carinha, é fim de jogo, e a pontuação do jogador é o número de segundos que ele sobreviveu.

Assim, vamos voltar ao arquivo principal do jogo, ‘jogo.rb’ para começar a implementar um pouco dessa mecânica. Observe o código abaixo:

require_relative 'shuriken'

class MeuJogo < GameWindow
  def initialize
    super 800, 600, false
    @sprite = GameObject.new 0, 0, 72, 72, :img1, Vector.new(-14, -14)
  end

  def update
    KB.update
    @sprite.x -= 3 if KB.key_down? Gosu::KbLeft
    @sprite.x += 3 if KB.key_down? Gosu::KbRight
    @sprite.y -= 3 if KB.key_down? Gosu::KbUp
    @sprite.y += 3 if KB.key_down? Gosu::KbDown
  end

  def draw
    clear 0xabcdef
    @sprite.draw
  end
end

MeuJogo.new.show

Este código é bastante semelhante ao código que tínhamos no final da parte 2 do tutorial, com algumas modificações:

  • Conforme comentado anteriormente, vamos referenciar o arquivo de definição da Shuriken para poder usar aqui. Assim, no lugar do require 'minigl' e do include MiniGL, temos, na linha 1, apenas require_relative 'shuriken'. O comando require_relative serve para referenciar arquivos por seus caminhos relativos ao arquivo atual (enquanto que require procura os arquivos dentre as bibliotecas instaladas). A extensão ‘.rb’ é omitida, e como o arquivo ‘shuriken.rb’ está no mesmo diretório que este, o caminho relativo é apenas ‘shuriken’. O arquivo ‘shuriken.rb’ agora faz o papel de importar a biblioteca MiniGL para uso no jogo.
  • Linha 6: agora nosso objeto @sprite não será mais uma Sprite (veja só que ironia), mas um GameObject, pois vamos precisar das definições de limites físicos do objeto. Os parâmetros já foram explicados na inicialização de Shuriken. Note que estamos passando dois argumentos a menos, no entanto: sprite_cols e sprite_rows foram omitidos porque a imagem da carinha não tem animação (não é uma spritesheet).
  • Na linha 18, trocamos o branco do fundo da janela por um azul suave, com o simpático código hexadecimal ‘abcdef’. 😛

Por enquanto, nada mudou na prática, exceto que a imagem da carinha agora deve estar aparecendo um pouco “cortada” no canto superior esquerdo, quando você inicia o jogo. Isso se deve ao conceito do img_gap, explicado anteriormente. Queremos utilizar este img_gap para que as shurikens possam “raspar de leve” na carinha sem “matá-la”.

Agora, vamos querer que o jogador possa jogar quantas vezes quiser, sem precisar reiniciar o programa. Quando ele perder, devemos exibir sua pontuação e oferecer a possibilidade de recomeçar. Assim, teremos de introduzir aqui um conceito extremamente comum na programação de jogos: o estado do jogo. Basicamente, dependendo do estado do jogo, você vai querer executar lógicas diferentes e desenhar coisas diferentes na tela. Vamos começar a preparar nosso jogo para lidar com diferentes estados:

...
class MeuJogo < GameWindow
  def initialize
    super 800, 600, false
    @sprite = GameObject.new 0, 0, 72, 72, :img1, Vector.new(-14, -14)
    @state = :new
  end

  def update
    if @state == :playing
      KB.update
      @sprite.x -= 3 if KB.key_down? Gosu::KbLeft
      @sprite.x += 3 if KB.key_down? Gosu::KbRight
      @sprite.y -= 3 if KB.key_down? Gosu::KbUp
      @sprite.y += 3 if KB.key_down? Gosu::KbDown
    end
  end

  def draw
    clear 0xabcdef
    if @state == :playing
      @sprite.draw
    end
  end
end
...

Se você executar o jogo neste momento, não vai ver nada além do fundo azul da tela. Isso porque definimos o estado inicial como :new, mas o objeto @sprite apenas será atualizado e desenhado na estado :playing. As pequenas modificações no código acima refletem isso. Então, como vamos permitir que o jogador comece de verdade o jogo? Fácil, usamos um botão! Para isso, vamos precisar de mais arquivos: uma imagem para o botão e uma fonte para o texto do botão (e depois para outros textos). Você pode usar a imagem abaixo:

btn

Não parece muito, mas isso aí em cima é uma spritesheet. Os botões da biblioteca MiniGL usam sempre uma spritesheet neste formato pré-definido: a primeira linha é a imagem padrão do botão; a segunda é usada quando o cursor está sobre o botão; a terceira é usada quando o botão está pressionado; e a quarta é usada quando o botão está desabilitado. Com relação à fonte, você pode copiar qualquer arquivo de fonte TrueType que tenha no seu computador, para um novo subdiretório ‘font’, dentro de ‘data’. A nova estrutura de arquivos do projeto fica assim (supondo que você salve a imagem do botão como ‘btn.png’ e renomeie o arquivo de fonte para ‘font1.ttf’):

meu-jogo
├ jogo.rb
├ shuriken.rb
└ data
  └ img
    ├ img1.png
    ├ shuriken.png
    └ btn.png
  └ font
    └ font1.ttf

Uma vez preparados os arquivos, voltemos ao código:

...
class MeuJogo ...
  def initialize
    ...
    @font = Res.font(:font1, 16)
    @btn = Button.new(350, 285, @font, 'Jogar', :btn) {
      @state = :playing
    }
  end

  def needs_cursor?
    @state != :playing
  end

  def update
    if @state == :playing
      ...
    else
      Mouse.update
      @btn.update
    end
  end

  def draw
    clear 0xabcdef
    if @state == :playing
      @sprite.draw
    else
      @btn.draw
    end
  end
end
...

Bom, temos algumas novidades aqui, a saber:

  • Estamos carregando, na linha 5, uma fonte, através da classe de gerenciamento de recursos da MiniGL, Res. Esta classe permite o carregamento dos diversos tipos de recursos que se usa num jogo (imagens, fontes, sons, etc.) com uma sintaxe muito curta e um melhor gerenciamento da memória. Esta sintaxe você notará que é semelhante àquela usada para definir a imagem de uma Sprite ou um GameObject – isto porque estas classes usam, por debaixo dos panos, a classe Res -, ou seja, passa-se um símbolo que, combinado com um diretório e uma extensão padrões, geram o caminho relativo até o arquivo. O segundo parâmetro da chamada é o tamanho com que a fonte será carregada.
  • Na linha 6, estamos instanciando um botão. Esta é uma das features mais interessantes da MiniGL: com uma linha de código é possível instanciar um botão plenamente funcional, e com um bloco de código definido logo após a instanciação pode-se definir a ação de clique do botão. Este bloco de código, delimitado por chaves, representa um elemento chamado de closure, que é um conceito muito interessante de linguagens dinâmicas. Basicamente, é como se o bloco de código se tornasse um objeto, que guarda o contexto de onde foi declarado e também conhece o contexto de onde é “chamado”. Isso quer dizer que você pode usar e manipular variáveis, tanto da classe MeuJogo quanto do próprio botão, dentro deste bloco. No caso, queremos que a ação do botão seja mudar o estado do jogo para :playing. Os parâmetros do construtor do botão são, nesta ordem: coordenada x, coordenada y, fonte, texto e código da imagem.
  • Na linha 11, declaramos um novo método para a janela do jogo, needs_cursor?. Na verdade, estamos sobrescrevendo um método fornecido pela classe pai. Por padrão, o cursor do mouse não é mostrado dentro da tela do jogo. Este método define, através do valor retornado, se o cursor deve ou não ser mostrado. No caso, queremos que seja mostrado desde que não estejamos no estado principal do jogo (:playing), pois vamos querer apertar o botão.
  • Tanto em update quanto em draw, onde tínhamos a checagem if @state == :playing, acrescentamos um else para indicar o que deve acontecer quando o estado do jogo não é :playing. Por enquanto, queremos que no estado inicial (:new), o botão que acabamos de criar seja atualizado e desenhado (através de seus próprios métodos update e draw). O mouse precisa ser atualizado também nesse caso (Mouse.update), para que o clique no botão possa ser detectado.

Certo, agora vamos seguir com os detalhes da mecânica do jogo. Primeiramente, quando o estado é :playing, temos que gerar, de tempos em tempos, shurikens que vão aparecer na tela e atravessá-la, de modo que o jogador terá que desviar delas se elas estiverem indo em sua direção. Vamos usar números pseudoaleatórios para isso (os novos trechos de código estão destacados entre comentários):

...
  def initialize
    ...
    # trecho novo ===========
    @shurikens = []
    @shuriken_prob = 0.005
    # fim do trecho novo ====
  end

  def update
    if @state == :playing
      KB.update
      
      # trecho novo ========================================
      if rand < @shuriken_prob
        pos = rand(4)
        x = y = dir = nil
        case pos
        when 0 then x = 5 + rand(771); y = 605; dir = :up
        when 1 then x = -25; y = 5 + rand(571); dir = :right
        when 2 then x = 5 + rand(771); y = -25; dir = :down
        else        x = 805; y = 5 + rand(571); dir = :left
        end
        speed = 2 + rand(3)
        @shurikens << Shuriken.new(x, y, dir, speed)
      end
      # fim do trecho novo =================================      

      @sprite.x -= 3 if KB.key_down? Gosu::KbLeft
      ...

      # trecho novo ==================
      @shurikens.each { |s| s.update }
      # fim do trecho novo ===========
    else
      ...
    end
  end

  def draw
    if @state == :playing
      ...
      # trecho novo ================
      @shurikens.each { |s| s.draw }
      # fim do trecho novo =========
    else
      ...
    end
  end
...

Na linha 5, estamos criando uma nova variável de instância, @shurikens, que inicialmente é uma lista (ou array) vazia. Ela servirá para guardar todas as shurikens geradas. Na linha 6, estamos definindo a probabilidade de uma shuriken ser gerada a cada frame do jogo (@shuriken_prob). Note que o valor é propositalmente baixo, já que a cada segundo são executados aproximadamente 60 frames.

O primeiro novo trecho inserido em update parece complicado, mas é basicamente a lógica de geração de shurikens com posições iniciais, direções e velocidades aleatórias. Ruby oferece a função rand como parte da biblioteca padrão, a qual pode receber um número variável de parâmetros: se nenhum parâmetro é fornecido, ela gera um número de ponto flutuante entre 0 e 1; se um parâmetro n é fornecido, ela gera um inteiro entre 0 e n – 1 (ou seja, pode gerar n valores diferentes). O teste tem probabilidade @shuriken_prob de dar certo. Caso dê certo, definimos os parâmetros, também de maneira aleatória, para a inicialização de uma nova shuriken. Após instanciada, ela é acrescentada à lista @shurikens (pela intuitiva sintaxe @shurikens << Shuriken.new(...)).

Mais abaixo, inserimos também um trecho para que todas as shurikens existentes sejam atualizadas a cada frame. Mas… espera um pouco: ainda não definimos como uma shuriken deve ser atualizada! Exato. Se você executar o jogo neste momento, ele eventualmente vai fechar com um erro. Para evitar isso, vamos ter de definir o método update para a classe Shuriken – em breve vamos a isso, mas não antes de terminarmos a compreensão deste trecho. O método each é fornecido pela classe Array, e itera por todos os elementos do array, na ordem, passando o elemento atual como parâmetro para o bloco de código que vem a seguir (note que o bloco delimitado por chaves é iniciado por um identificador s, delimitado por pipes; este é o parâmetro do bloco). Assim, para cada elemento da lista, chamamos seu método update.

Por fim, mais abaixo, incluímos na função draw uma iteração pela lista de shurikens para chamar a função draw delas, ou seja, para que elas se desenhem na tela. Felizmente, neste caso, não temos que definir nada, pois a classe GameObject, da qual Shuriken herda, já define o método draw.

Agora sim, vamos ver o que uma shuriken deve fazer na sua atualização:

class Shuriken ...
  ...
  def update
    move_free @aim, @speed_m
    animate [0, 1, 2, 3], 4
  end 
end

Você se lembra que no construtor nós definimos as variáveis @aim e @speed_m como o alvo e a velocidade do movimento? Então, estes são os parâmetros requeridos pelo método move_free (fornecido pela classe GameObject), que vai fazer o objeto se mover até a posição @aim, com a velocidade fixa @speed_m.

Além disso, chamamos também o método animate, o qual vai oferecer o efeito de animação para as shurikens. O primeiro argumento é uma lista de índices. Os índices correspondem à posição da “sub-imagem” dentro da spritesheet – tínhamos duas linhas e duas colunas, logo são quatro imagens; os índices vão de 0 a 3, crescendo da esquerda para a direita e de linha em linha. O segundo parâmetro é a duração em frames de cada “sub-imagem”.

Execute o jogo agora, e, após clicar no botão “Jogar”, você deverá ver shurikens aparecendo de vez em quando na tela e se movendo em linha reta, com uma animação de rotação. Divertido, não? Mas ainda temos vários detalhes a consertar. Por exemplo, as shurikens precisam sinalizar o fim do jogo quando atingem a carinha feliz, e precisam ser removidas da lista quando terminam de atravessar a tela. Vamos ter que fazer mais alguns ajustes na Shuriken:

...
class Shuriken
  attr_reader :dead

  def initialize ...

  def update(obj)
    move_free @aim, @speed_m
    animate [0, 1, 2, 3], 4
    return :dead if obj.bounds.intersect?(bounds)
    @dead = true if @speed.x == 0 && @speed.y == 0
  end
end

Você vai notar que adicionamos uma coisa estranha no topo: attr_reader :dead. Isto é uma sintaxe abreviada para definir um método “getter” para um atributo (ou variável de instância) chamado @dead – vamos usá-lo para indicar que a shuriken está “morta”, ou, no caso, que ela atravessou a tela e deve ser removida.

Com relação ao método update, o parâmetro obj será o GameObject que representa a carinha feliz. Na linha 10, testamos se os limites físicos do objeto (obj.bounds) têm intersecção (intersect?) com os limites físicos do objeto atual (bounds). Em caso positivo, retornamos o símbolo :dead, para que o laço principal do jogo receba a informação de que o jogador “morreu”. Logo abaixo, testamos também se a velocidade da shuriken (nesse caso, o vetor) é zero (o que ocorre quando ela atinge seu alvo), e definimos o atributo @dead para true se for o caso.

Estas últimas modifiacações vão exigir que alteremos algumas coisas no arquivo ‘jogo.rb’ também:

...
  def update
    ...
    @shurikens.each { |s|
      result = s.update(@sprite)
      if result == :dead; return @state = :end
      elsif s.dead; @shurikens.delete s; end
    }
    ...
  end
...

Note que agora chamamos update nas shurikens passando o objeto @sprite como parâmetro. Também checamos se foi retornado o símbolo :dead e, se for o caso, mudamos o estado do jogo para :end (fim). Caso o jogo não tenha terminado, verificamos se a shuriken precisa ser removida da lista (o que é indicado pelo seu atributo dead).

Legal! Agora, se você for atingido por uma shuriken, o jogo volta para um estado que mostra apenas o botão. Porém, se você clicar no botão de novo, o jogo não reinicia: em vez disso, ele retorna para um estado em que o jogador está sendo atingido, e logo no quadro seguinte retorna para o botão. Ou seja, temos que dar um jeito de reiniciar todas as variáveis quando isso acontece. Mas isso é simples: basta colocar toda a reinicialização no clique do botão. Além disso, precisamos deixar o jogo mais desafiador, de modo que fique cada vez mais difícil desviar das shurikens:

...
  def initialize
    ...
    @btn = Button.new(350, 285, @font, 'Jogar', :btn) {
      @sprite.x = 364
      @sprite.y = 264
      @shurikens = []
      @shuriken_prob = 0.005
      @frame_counter = 0
      @score = 0
      @state = :playing
    }
  end

  def update
    ...
    if rand < @shuriken_prob
      ...
    end
    @shuriken_prob += 0.000005
    @frame_counter += 1
    if @frame_counter == 60
      @score += 1
      @frame_counter = 0
    end
    ...
  end
...

Aproveitei para já inserir duas novas coisas: o aumento da probabilidade de surgimento das shurikens e também um contador para a pontuação do jogador. Bom, comecemos com o bloco que define a ação do botão @btn, em initialize:

  • Nas linhas 5 e 6, estamos definindo @sprite.x = 364 e @sprite.y = 264 para que a carinha feliz comece no centro da tela, e não no canto.
  • Logo abaixo, estamos limpando a lista de shurikens, na linha 7, e retornando a probabilidade de surgimento de shurikens para o valor inicial (já que a partir de agora ela vai aumentando ao longo do tempo), na linha 8.
  • Em seguida, iniciamos com 0 um contador de quadros (@frame_counter) e a pontuação do jogador (@score), nas linhas 9 e 10. Vamos usar um contador de quadros porque a pontuação deve ser dada em função dos segundos, e um segundo corresponde aproximadamente a 60 quadros.
  • Por fim, o clique do botão continua definindo o estado do jogo como :playing (linha 11).

Agora, para o novo trecho em update:

  • Na linha 20, aumentamos a probabilidade de surgimento de shurikens a cada quadro, com um valor bem pequeno, para que o aumento de dificuldade seja bem gradual.
  • Na linha 21, incrementamos o contador de frames.
  • Na linha 22, testamos para ver se o contador chegou a 60 – em outras palavras, se um segundo se passou.
  • Se o teste acima for verdadeiro, incrementamos a pontuação do jogador (linha 23) e voltamos o contador de frames para 0 (linha 24).

Então é isso! Agora temos um jogo realmente jogável. Porém, ainda não está sendo possível visualizar a pontuação do jogador, o que obrigaria as pessoas a jogar com um cronômetro do lado, o que não é exatamente o ideal… Podemos, em vez disso, exibir a pontuação na tela! Este será o toque final para o jogo:

...
  def draw
    ...
    if @state == :playing
      ...
      @font.draw @score, 5, 580, 0, 1, 1, 0xff000000
    else
      ...
      @font.draw_rel "Fim de jogo. Você fez #{@score} pontos", 400, 250, 0, 0.5, 0, 1, 1, 0xff000000 if @state == :end
    end
  end
...

Pronto, agora sim! 🙂
Alguns esclarecimentos:

  • @font é um objeto da classe Font que definimos no initialize há algum tempo, lembra? @font.draw é um método para escrever texto. Os parâmetros são, na ordem: texto, posição x, posição y, posição z (explicarei melhor no futuro), escala em x (valores maiores que 1 vão “esticar” o texto horizontalmente, valores menores que 1 vão “espremê-lo”), escala em y (idem anterior, mas verticalmente) e cor (formato AARRGGBB). Na linha 6, estamos utilizando-o para mostrar a pontuação no canto inferior esquerdo da tela enquanto o jogador está jogando (estado :playing).
  • O método draw_rel é semelhante ao draw, mas tem dois parâmetros adicionais para definir um “ponto de referência” para o desenho do texto (no código acima, são os valores consecutivos 0.5 e 0). O primeiro é relativo à coordenada x: o valor 0 indica que o texto será alinhado à esquerda começando desta coordenada, o valor 0.5 significa que o texto estará centralizado nesta coordenada, e o valor 1 indica que o texto ficará alinhado à direita, terminando nesta coordenada. Outros valores trazem efeitos intermediários entre essas possibilidades. O segundo é relativo à coordenada y e funciona de maneira análoga. Assim, na linha 9, usamos 0.5 para o primeiro deles para desenhar texto centralizado na tela. O texto é uma mensagem que diz quantos pontos o jogador fez ao término (estado :end). Note a sintaxe "... Você fez #{@score} pontos". Ruby permite a interpolação de strings, isto é, colocar variáveis dentro das strings, sinalizadas pelos delimitadores ‘#{‘ e ‘}’.

Ufa! É isso, acabou… Para evitar confusões, como o código foi apresentado em muitos pedaços, segue o código final completo dos dois arquivos:

shuriken.rb

require 'minigl'
include MiniGL

class Shuriken < GameObject
  attr_reader :dead

  def initialize(x, y, direction, speed)
    super x, y, 20, 20, :shuriken, Vector.new(-5, -5), 2, 2
    case direction
    when :up    then @aim = Vector.new(@x, @y - 630)
    when :right then @aim = Vector.new(@x + 830, @y)
    when :down  then @aim = Vector.new(@x, @y + 630)
    when :left  then @aim = Vector.new(@x - 830, @y)
    end
    @speed_m = speed
  end

  def update(obj)
  	move_free @aim, @speed_m
  	animate [0, 1, 2, 3], 4
    return :dead if obj.bounds.intersect?(bounds)
  	@dead = true if @speed.x == 0 && @speed.y == 0
  end
end

jogo.rb

require_relative 'shuriken'

class MeuJogo < GameWindow
  def initialize
    super 800, 600, false
    @sprite = GameObject.new 0, 0, 72, 72, :img1, Vector.new(-14, -14)
    @state = :new
    @font = Res.font(:font1, 16)
    @btn = Button.new(350, 285, @font, 'Jogar', :btn) {
      @sprite.x = 364
      @sprite.y = 264
      @shurikens = []
      @shuriken_prob = 0.005
      @frame_counter = 0
      @score = 0
      @state = :playing
    }
  end

  def needs_cursor?
    @state != :playing
  end

  def update
    if @state == :playing
      KB.update
      
      if rand < @shuriken_prob
        pos = rand(4)
        x = y = dir = nil
        case pos
        when 0 then x = 5 + rand(771); y = 605; dir = :up
        when 1 then x = -25; y = 5 + rand(571); dir = :right
        when 2 then x = 5 + rand(771); y = -25; dir = :down
        else        x = 805; y = 5 + rand(571); dir = :left
        end
        speed = 2 + rand(3)
        @shurikens << Shuriken.new(x, y, dir, speed)
      end
      @shuriken_prob += 0.000005
      @frame_counter += 1
      if @frame_counter == 60
      	@score += 1
      	@frame_counter = 0
      end
      
      @sprite.x -= 3 if KB.key_down? Gosu::KbLeft
      @sprite.x += 3 if KB.key_down? Gosu::KbRight
      @sprite.y -= 3 if KB.key_down? Gosu::KbUp
      @sprite.y += 3 if KB.key_down? Gosu::KbDown

      @shurikens.each { |s|
        result = s.update(@sprite)
        if result == :dead; return @state = :end
        elsif s.dead; @shurikens.delete s; end
      }
    else
      Mouse.update
      @btn.update
    end
  end

  def draw
    clear 0xabcdef
    if @state == :playing
      @sprite.draw
      @shurikens.each { |s| s.draw }
      @font.draw @score, 5, 580, 0, 1, 1, 0xff000000
    else
      @btn.draw
      @font.draw_rel "Fim de jogo. Você fez #{@score} pontos", 400, 250, 0, 0.5, 0, 1, 1, 0xff000000 if @state == :end
    end
  end
end

MeuJogo.new.show

Repare agora no total de linhas: isso mesmo, você acaba de criar um jogo de verdade com menos de cem linhas de código (e meia dúzia de arquivos)!

Então é isso! Agora você pode se divertir jogando o jogo (te desafio a sobreviver por cinco minutos!) ou brincando com os parâmetros numéricos, com os controles, com as imagens usadas no jogo, etc.

Observação: Como o foco deste tutorial é o uso da biblioteca MiniGL e a programação de games, pode ser que você esteja tendo alguma dificuldade em acompanhar as particularidades da linguagem Ruby em si (se não a conhecia previamente). Se esse for o caso, recomendo que você procure tutoriais de Ruby pela Internet para aprender o básico (de fato, você não vai precisar de nada muito avançado aqui), há muitos tutoriais bons por aí (como esse do site oficial).

Qualquer dúvida, fique à vontade para comentar. Obrigado por me acompanhar até aqui, e até a próxima!

Programando Games com Ruby e MiniGL – Parte 2: Um jogo

Olá, como vão?
Animados para programar?

Espero que sim, porque hoje vamos programar um pouco. Mas vamos nos divertir também, porque o que vamos programar é um jogo! 🙂

Apenas recapitulando, vamos tratar aqui do desenvolvimento de jogos com a linguagem de programação Ruby e a biblioteca MiniGL. Se você caiu aqui por acaso e não tem a mínima ideia do que estou falando, este primeiro post pode ajudar.

Bom, considerando que você não está mais perdido aqui, sigamos com o que interessa. Como o objetivo dessa série de posts é poder atingir o maior público possível de desenvolvedores – tenho certeza que há muitos deles frustrados por quererem fazer games, mas terem de trabalhar com sistemas bancários para sobreviver 😛 -, eu não vou assumir que você conhece Ruby neste tutorial. Vamos aprendendo Ruby junto com o uso da biblioteca, e conforme o necessário. Isso será possível graças à infinita elegância e simplicidade da linguagem – OK, sou suspeito para falar…

Comecemos, então, apresentando as ferramentas. Para desenvolver e executar um projeto Ruby, tudo o que você precisa é de um editor de texto e do interpretador. O interpretador vimos no post anterior como obter (e ele pode ser chamado pelo comando ruby no terminal, tanto no Linux quanto no Windows). Com relação ao editor, as opções são inúmeras, e o que você vai usar vai acabar dependendo de seu gosto, em última instância. 😛

Mas se você quer sugestões, eu tenho algumas: um editor muito bom, com diversos atalhos úteis, auto-complete de código e até uma interface bonita é o SublimeText (para Linux e Windows), que possui uma versão gratuita praticamente sem limitações. Também tenho utilizado o RubyMine (também para ambas as plataformas), que já pode ser classificado como IDE. Tem muito mais opções, mas por outro lado alguns bugs um pouco incômodos. De todo modo, o balanço é positivo. Você pode fazer um trial de 30 dias para decidir se vale a pena.

No Windows, pode-se citar também o Notepad++, um editor interessante (embora eu ache que o Sublime o supere na maioria dos aspectos), ou ainda o Notepad (também conhecido como bloco de notas) – brincadeira, não faça isso.

OK, tudo preparado? Editor e terminal em mãos? Hora de programar??? Não, mas quase… Primeiro, vamos preparar alguns arquivos. Afinal, todo jogo (ok, nem todo, mas me refiro ao sentido comum da palavra) tem de utilizar alguns recursos além do código propriamente dito. Para esse primeiro tutorial, utilizaremos apenas uma imagem. Se você não tem às mãos uma imagem de exemplo, em formato PNG, sinta-se à vontade para dar um “salvar como…” na obra de arte abaixo:

img1

Agora, crie uma pasta para seu novo e promissor projeto e construa a seguinte estrutura:

meu-jogo
├ jogo.rb
└ data
  └ img
    └ img1.png

Na estrutura acima, ‘meu-jogo’ é o diretório do projeto, ‘jogo.rb’ é o arquivo de código onde vamos programar o jogo, e ‘img1.png’, dentro da pasta ‘data’ e da subpasta ‘img’, é uma imagem qualquer de exemplo (que pode ser a carinha feliz acima). Mais para a frente veremos que a estrutura do seu projeto não precisa ser exatamente essa, mas aqui estamos seguindo convenções para que o código fique mais curto ;).

Enfim, hora da programação. Vou jogar o código abaixo, mas não se assuste – você logo estará entendendo tudo, prometo. Aqui vai:

require 'minigl'
include MiniGL

class MeuJogo < GameWindow
  def initialize
    super 800, 600, false
    @sprite = Sprite.new 0, 0, :img1
  end
  def draw
    clear 0xffffff
    @sprite.draw
  end
end

MeuJogo.new.show

Com as treze linhas de código acima, você deverá gerar uma janela de 800 por 600 pixels, branca, com a imagem que você salvou como ‘img1.png’ desenhada no canto superior esquerdo. Você pode testar isso rodando ruby jogo.rb no terminal (navegando, antes disso, até o diretório do seu projeto). Um tanto impressionante para apenas treze linhas, não? Mas ainda fica muito melhor! 🙂

Agora, alguns esclarecimentos. Vou utilizar a numeração das linhas para facilitar.

  • Na linha 1, estamos importando a biblioteca MiniGL. Lembra-se da instalação da “gem”? O nome que especificamos após o require é, em geral, o nome da gem.
  • Na linha 2, estamos incluindo o namespace MiniGL neste arquivo de código, de modo que podemos referenciar tudo o que ele contém (classes, módulos, constantes, …) apenas pelo nome. Caso não utilizássemos a diretiva include, teríamos de escrever, mais abaixo, MiniGL::GameWindow e MiniGL::Sprite.
  • Na linha 4, estamos iniciando a declaração da classe MeuJogo, que representará a janela do jogo, e conterá os métodos principais que fazem o jogo funcionar. Para adquirir toda essa funcionalidade sem muito esforço, fazemos ela herdar a classe GameWindow, definida dentro da MiniGL. O sinal < indica herança de classes (vou assumir que você é, pelo menos, familiarizado com programação orientada a objetos).
  • Na linha 5, estamos declarando o método initialize dentro da classe MeuJogo. Em Ruby, initialize é um nome especial, usado para definir o construtor da classe. Neste caso, o construtor não recebe parâmetros, e a convenção em Ruby é não utilizar parênteses para a declaração (e a chamada) de funções sem parâmetros.
  • Na linha 6, chamamos a função super, cujo efeito é chamar a função correspondente da classe pai (ou seja, estamos chamando o construtor de GameWindow), e passamos os parâmetros para a largura, a altura, e o modo da janela (false para janela, true para full screen). Note novamente a ausência de parênteses: eles são opcionais.
  • Na linha 7, declaramos uma variável de instância, isto é, uma variável que fica armazenada com seu próprio valor para cada instância desta classe, e que é acessível dentro de todos os métodos. Note que a declaração ocorreu junto com a atribuição de um valor, e que não há definição de tipo, pois Ruby é uma linguagem dinâmica, fracamente tipada. Use-se o sinal @ como primeiro caractere do nome de uma variável para defini-la como variável de instância. A ela atribuímos um objeto da classe Sprite (outra classe fornecida pela MiniGL, sobre a qual falaremos mais em posts futuros), que foi ali instanciado através do método new (este método corresponde à chamada do initialize, ou seja, ao construtor), com parâmetros para a posição x e y (horizontal e vertical) e para a imagem. Discutiremos mais sobre o formato em que a imagem foi especificada em outros posts.
  • A linha 8 apenas finaliza a declaração de initialize.
  • Na linha 9, declaramos outra função, draw. Esta é uma função que precisa ser implementada para que a janela do jogo exiba alguma coisa. Toda a lógica ligada a desenhar imagens no seu jogo deve ficar ali.
  • Na linha 10, chamamos o método clear para preencher a tela com uma cor. A cor é especificada através de seu código RGB, em formato hexadecimal (indicado pelo prefixo 0x, o qual também é usado em linguagens como C e Java).
  • Na linha 11, pedimos para a Sprite declarada no método anterior “se desenhar” na tela. Ela será desenhada na posição em que a inicializamos (é claro que essa posição pode ser alterada posteriormente, como veremos mais para a frente).
  • A linha 12 fecha o método draw.
  • A linha 13 fecha a classe MeuJogo.
  • Finalmente, a linha 15 combina, de maneira muito simpática, duas chamadas de método: a chamada MeuJogo.new, que instancia um objeto da recém-declarada classe MeuJogo, e a chamada show, feita no resultado de MeuJogo.new (ou seja, na janela criada), que é responsável por fazer a janela ser de fato mostrada pelo sistema operacional.

E é isso. Mas o resultado está muito estático, não? Vamos movimentar essa sprite! Que tal usar as teclas direcionais para isso? Veja abaixo:

...
class MeuJogo < GameWindow
  ...
  def update
    KB.update
    @sprite.x -= 3 if KB.key_down? Gosu::KbLeft
    @sprite.x += 3 if KB.key_down? Gosu::KbRight
    @sprite.y -= 3 if KB.key_down? Gosu::KbUp
    @sprite.y += 3 if KB.key_down? Gosu::KbDown
  end
end
...

Agora, isso sim é impressionante! Acrescentamos interação com o usuário com meras sete linhas de código (as linhas de 4 a 10, que foram acrescentadas ao código anterior). A lógica por trás disso é bem simples:

  • Acrescentamos à classe MeuJogo o método update, o qual é chamado automaticamente, assim como draw, aproximadamente sessenta vezes por segundo, enquanto a janela do jogo está aberta. Toda a lógica do seu jogo que não for relacionada a desenhar coisas na tela deve ficar ali.
  • Dentro dela, chamamos KB.update. KB é uma classe da MiniGL para facilitar a manipulação do teclado. A chamada a update é necessária para sua utilização.
  • Depois, alteramos a posição da sprite (mudando os valores de x e y) de acordo com eventos do teclado. Note a sintaxe do if em uma linha: é um jeito interessante de escrever que torna a leitura mais natural (indicado quando a ação condicional for de apenas uma linha). Utilizamos o método KB.key_down? (isso mesmo, o método possui uma interrogação no nome, convenção para métodos cujo retorno é booleano) para saber se uma determinada tecla, indicada pelo parâmetro, está pressionada. Os parâmetros são constantes numéricas, que no caso possuem aliases definidos pela biblioteca Gosu, para facilitar. As constantes usadas acima referem-se às teclas direcionais para a esquerda, para a direita, para cima e para baixo, respectivamente. Uma observação importante é que a coordenada x cresce da esquerda para a direita enquanto y cresce de cima para baixo.

Agora você pode brincar com sua carinha feliz móvel (ou outra coisa móvel, se você não tiver gostado da carinha), rodando o jogo novamente e usando suas teclas direcionais!

Bom, isso é apenas o começo de uma longa jornada, espero que tenha sido animador. Voltamos a nos ver semana que vem!

Programando games com Ruby e MiniGL – Parte 1: Preparando o Ambiente

Saudações, desenvolvedores!

Faz um bom tempo desde a última vez que passei por aqui, não? Sim, é verdade… Muita coisa aconteceu desde então, e conheci muitas tecnologias novas, tanto para o desenvolvimento de games quanto para desenvolvimento de propósito geral.

Dentre as muitas novidades com as quais tive contato neste tempo, posso afirmar que a linguagem Ruby foi uma das mais interessantes. Trata-se de uma linguagem interpretada, dinâmica, altamente orientada a objetos, com uma sintaxe muito amigável e, na minha humilde opinião, muito elegante.

Após aprender o básico da linguagem e perceber seu potencial, fui logo atrás de ferramentas/bibliotecas/arcabouços para criar games com ela – como venho fazendo com praticamente toda nova linguagem que aprendo. 😛

Embora, num panorama geral, Ruby não seja uma linguagem muito indicada para desenvolver games, devido a suas limitações de performance (característica das linguagens interpretadas), há cenários e cenários. O ponto principal é que, se seu jogo não for especialmente complexo, ou seja, não precisar trabalhar com grandes massas de dados a cada quadro (frame), você poderá desenvolver um jogo que vai rodar a 60 FPS, utilizando as bibliotecas “certas” (mais adiante veremos o que isso significa).

Isso tanto é verdade que, no final deste último ano, concluí meu curso de Ciência da Computação apresentando como TCC uma biblioteca e um jogo desenvolvidos com Ruby – você pode ver mais aqui. E, no balanço geral, percebi que para muitas das minhas ideias de jogos, seria muito mais fácil abrir mão de certos detalhes para ganhar performance, mas poder usar Ruby, do que continuar desenvolvendo em C++, que, sinceramente, eu acho extremamente difícil e não intuitiva.

Assim, prossegui com otimismo na empreitada de desenvolver games em Ruby. Para evitar resultados catastróficos de performance, no entanto, é necessário que pelo menos o código de renderização de gráficos seja gerenciado por alguma coisa de mais baixo nível (logo, mais rápida). Ruby possibilita esta mágica através das chamadas “bibliotecas com extensões”, que criam uma interface entre código compilado, escrito em C, e código Ruby. A biblioteca Gosu faz isso e encapsula funções da poderosa OpenGL para desenhar gráficos (além de outras funções básicas para som e entrada). Quando a descobri, percebi que o principal obstáculo estava vencido.

No entanto, Gosu não era exatamente um arcabouço para desenvolvimento de games. Eu senti que havia como aumentar mais ainda a produtividade, construindo algo com base nessa biblioteca. Daí comecei o desenvolvimento da minha própria biblioteca – que ousei chamar de arcabouço, no TCC -, e devo dizer que hoje estou bastante satisfeito com ela. Desde então, foram milhares de downloads no repositório oficial de bibliotecas Ruby, e melhorias constantes liberadas em diversas versões. Recentemente, a versão 2.0.0 foi lançada, com melhorias e acréscimos significativos.

Enfim, falei tudo isto para justificar o objetivo deste e dos próximos posts: ensiná-los a usar Ruby e MiniGL para desenvolver jogos de forma rápida e divertida. Hoje, vamos falar apenas de como preparar o seu ambiente para começar a brincadeira, mas já vou deixar aqui alguns screenshots para motivá-los:

tela1Tela do jogo “Aventura do Saber”, parte do meu TCC

2Tela de “Super Bombinhas”, projeto em andamento

Agora, ao trabalho!

Linux

Se você usa Linux, a configuração do ambiente poderá ser feita com uns poucos comandos no terminal (e se você não usa, recomendo dar uma explorada 😉 ). Primeiro, você precisa instalar o interpretador Ruby e suas bibliotecas padrão, na versão 2.0 ou superior. Há algumas maneiras de se fazer isso no Linux, mas a mais indicada é utilizando uma ferramenta chamada RVM. Para instalar o RVM, execute os seguintes comandos:

(Instalando mpapis public key)
gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3

(Atualizando a lista de pacotes do APT)
sudo apt-get update

(Instalando o programa ‘curl’, que permite baixar coisas a partir de URLs)
sudo apt-get install curl

(Baixando os arquivos da instalação do RVM)
curl -L https://get.rvm.io | bash -s stable

O próximo comando deverá usar um caminho que será mostrado na saída do comando acima. Este é o caminho onde o script de instalação do RVM foi baixado. Em geral, o caminho terá o formato ‘/home/<nome_usuario>/.rvm/scripts/rvm’.

source /home/<nome_usuario>/.rvm/scripts/rvm

Finalmente, instalaremos o Ruby propriamente dito, usando o RVM:

rvm install 2.1

Recomendo o uso das versões 2.1.x, pois elas apresentam melhorias de performance em relação às versões 2.0.x, mas se por algum motivo você preferir a série 2.0, ela também será suficiente.

Uma vez instalado o interpretador e as bibliotecas padrão, uma das ferramentas que será disponibilizada é o programa “gem”. Este programa serve para instalar, desinstalar e fazer outras coisas relacionadas a bibliotecas Ruby (chamadas de “gems”). Com ele, você instalará a MiniGL. Porém, a MiniGL depende da biblioteca Gosu, como comentamos anteriormente, e esta, por sua vez, depende de outras bibliotecas do sistema, devido ao uso de código C “por debaixo dos panos”. Assim, instalemos primeiro as dependências da Gosu:

sudo apt-get install build-essential libsdl2-dev libsdl2-ttf-dev libpango1.0-dev libgl1-mesa-dev libfreeimage-dev libopenal-dev libsndfile-dev

Após a conclusão dessas instalações, o toque final:

gem install minigl

Pronto! Você tem um ambiente prontinho para começar a programar seus jogos com Ruby e MiniGL! Para fazer um teste básico, você pode rodar um dos jogos de exemplo que vêm com a “gem”. Encontre o caminho dos arquivos da “gem”:

gem which minigl

Copie o caminho informado até antes do último diretório “lib”. Por exemplo, se a saída do comando foi “/caminho/ateh/minigl/minigl-2.0.x/lib/minigl.rb”, copie “/caminho/ateh/minigl/minigl-2.0.x”. Agora, entre com o comando:

ruby </caminho/ateh/minigl/minigl-2.0.x>/test/game.rb

Deverá ser mostrada uma janela com textos e imagens diversas (é apenas um teste, um tanto bagunçado, admito). Se você vir isso, é sinal que a instalação correu bem.

Obs.: Os passos acima foram testados nas distribuições Linux Mint e Ubuntu. Para outras distribuições, é possível que seja necessário alterar alguns detalhes do processo…

Windows

Como não poderia deixar de ser, a preparação do ambiente para Windows é um pouco menos prática. 😛
O jeito mais recomendado de instalar o Ruby no Windows é através do RubyInstaller, um instalador no formato “next, next, finish” que algum desenvolvedor teve a gentileza de criar para facilitar um pouco a nossa vida. Tenha o cuidado de escolher a versão certa de arquitetura (32 ou 64 bits), entre os downloads do site. As configurações padrão da instalação devem servir.

Após instalado o produto, execute o prompt de comando com Ruby (deverá ser disponibilizado no seu menu Iniciar algo como “Start Command Prompt with Ruby”). No terminal aberto, execute:

gem install minigl

E deveria ser apenas isso. Por algum motivo, a biblioteca Gosu disponibiliza binários para o Windows, de modo que ela não precisa ser compilada e portanto não precisa da instalação das dependências que vimos acima para o Linux. Porém, é possível que você se depare com um erro ao executar o comando acima.

Nas últimas vezes que testei, o comando “gem install” não estava funcionando devido a um erro de certificado SSL. Se aparecer para você uma mensagem de erro relacionada a isso, você precisará fazer um pequeno “work-around” para resolver o problema:

  1. Baixe o arquivo AddTrustExternalCARoot.pem. Seu browser o abrirá como um arquivo texto. Ao salvá-lo localmente, ele também será salvo como texto. Você precisará alterar a extensão do arquivo de ‘.txt’ para ‘.pem’.
  2. Execute, no prompt de comando com Ruby, o comando “gem which rubygems”. A saída será um caminho, que você deverá copiar até o final, exceto a extensão ‘.rb’.
  3. Visite o caminho copiado no Windows Explorer. Mova o certificado que você baixou no passo 1 para este diretório.

Pronto! Agora o comando “gem install” deverá funcionar normalmente e a MiniGL será instalada corretamente. Para testar, valem os mesmos passos descritos no caso do Linux.

Então, por hoje é só! Mas até que já foi muito para quem não postava há uns três anos…
A verdadeira diversão ainda está por vir, então não deixe de conferir o DevSV em breve, pois mais posts estão a caminho. Abraços e até a próxima!

Adicionando JARs ao classpath de sua aplicação via IDE.

No post sobre MigLayout, um dos leitores comentou que a parte de adicionar o JAR do miglayout tinha ficado vaga. Inicialmente, deixei esta questão de lado, pois estava fora do escopo do post (MigLayout), mas depois eu pensei: “por que não criar um novo post mostrando como adicionar JARs a um projeto?”. A partir daí resolvi escrever este tutorial apenas utilizando recursos de algumas IDEs, nada muito mirabolante, bem voltado para o pessoal que está começando e quer importar um JAR externo para o seu projeto.

Ao longo deste post, mostrarei como realizar a tarefa proposta usando as IDEs Eclipse, NetBeans e IntelliJ IDEA.
Continuar lendo

Como implementar um TableModel.

Quando comecei a estudar sobre table models, escrevi um “rascunhão” que me ajudou bastante na hora fixar alguns conceitos. Como tenho visto nos fóruns sobre Java que tem bastante gente que busca informações sobre o assunto, resolvi adaptar o rascunhão e transformar num post. Além disso, preparei um exemplo de código mostrando passo a passo a implementação de um table model para uma tabela de sócios.

O que é o TableModel?

A maioria dos componentes swing possuem uma arquitetura que separa model e view, de forma que é definida uma interface para cada model. Dentro deste contexto, TableModel é a interface que representa o model da JTable.
Continuar lendo