Kodumaro :: Câmera exemplo

Publicado em 26 de Maio, 2016
As sombras da programação.
LÖVE

Em muitos jogos, o movimento de câmera é um recurso essencial. Diversos frameworks o implementam nativamente.

LÖVE ou Love2D é a plataforma de desenvolvimento de jogos em Lua, com suporte a FFI e shaders nativo, porém exige um pouco de verborragia para lidar com outros recursos básicos, como câmera e temporização.

Mas não ficamos órfãos de recursos ao programar em LÖVE. A comunidade tem desenvolvido inúmeras bibliotecas, resolvendo os recursos faltantes.

De todas, um biblioteca da qual não se pode abrir mão é HUMP, que agrega recursos de controle de estado, temporização, gerência de vetores, sinais, câmera e até mesmo adiciona suporte a classes a Lua.

Vamos escrever um exemplo muito trivial de gerência de câmera usando HUMP em MoonScript. Esteja atento, pois será necessário compilar os arquivo .moon para .lua com moonc a cada alteração.

Baixando HUMP

No diretório onde o código ficará execute (você precisa do git):

sh$ git clone git@github.com:vrld/hump.git

Arquivo de configuração

LÖVE usa um arquivo de configuração inicial, conf.lua, com informações sobre tamanho e tipo da janela, e quais recursos ativar ou não.

Vamos criar então um conf.moon com a versão de LÖVE que estamos usando, a identidade da aplicação – uma string de uso interno – e as informações da janela – título, tamanho e se será fullscreen ou não:

love.conf = (t) ->
    with t
        .version = "0.10.1"
        .identity = "cameraexample"

    with t.window
        .title = "CameraExample"
        .width = 600
        .height = 600
        .fullscreen = false

Nossa aplicação se chama cameraexample, tem título “CameraExample”, não é fullscreen e tem dimensões 600x600.

Para compilar para Lua:

sh$ moonc conf.moon
Built conf.moon
sh$

Isso gera o arquivo conf.lua, que é o que LÖVE usará.

Cabeçalho do arquivo principal

LÖVE lê os callbacks do arquivo main.lua. Criaremos então um arquivo main.moon para nosso teste. Esse arquivo deve conter a importação do(s) módulo(s) HUMP, a aplicação em si e os callbacks que LÖVE espera.

Para a aplicação, vamos criar uma classe CameraExample. Podemos começar o arquivo então:

local *

Camera = assert require "hump.camera"
app = nil

class CameraExample
    new: =>
        @camera = Camera 0, 0
        @shapes = {
            -> love.graphics.circle "fill", 0, 0, 50
        }

Importamos hump.camera – padronizado como Camera –, reservamos uma variável para a instância de aplicação e criamos a classe, apenas com o construtor.

No construtor da classe, instanciamos uma câmera olhando para a posição (0, 0), e criamos uma lista de formas (shapes) contendo uma função anônima que desenha um círculo de 50 pixels na posição (0, 0).

Nossa classe ainda precisa de um método para chamar as funções que desenham as formas. Acrescente à classe o método:

    draw: =>
        love.graphics.setColor 0x00, 0x00, 0x00
        shape! for shape in *@shapes

Agora precisamos registrar a instanciação da callback no carregamento de LÖVE (love.load) e o desenho na de desenho (love.draw). Abaixo, fora da classe, crie as seguintes funções:

love.load = ->
    app = CameraExample!
    love.graphics.setBackgroundColor 0x00, 0x50, 0x90

love.draw = ->
    app\draw!

Isso já deve funcionar! Compile e rode:

sh$ moonc main.moon
Built main.moon
sh$ love .

O resultado esperado é abrir uma janela azul com um círculo preto no canto. Pode fechá-la, ela ainda não faz mais nada.

Ativando a câmera

Vamos usar a câmera visualizar o “mundo” exibido. Altere o método draw da classe para usar a câmera:

    draw: =>
        love.graphics.setColor 0x00, 0x00, 0x00
        @camera\draw ->
            shape! for shape in *@shapes

Com o contexto gerado pela chamada do método @camera\draw agora, ao ao rodar a aplicação (não se esqueça de recompilar main.moon!), o círculo aparecerá no centro da janela.

Entenda o que aconteceu: agora a câmera está olhando para a posição onde está o círculo, (0, 0) – antes a posição (0, 0) era o canto superior esquerdo.

Movendo a câmera

Há diversos métodos para mover a câmera – veja a documentação. Vamos usar o método que movimenta a câmera de acordo com um delta.

Precisaremos de um método para atualizar o estado da câmera. Tradicionalmente esse método é chamado update e recebe um delta de tempo. Vamos criar um delta de movimento a partir do delta de tempo e adicioná-lo a deltas separados para os eixos x e y de acordo com que teclas forem pressionadas. Depois passamos os deltas separados para a função que move a câmera:

    update: (dt) =>
        ds = dt * 200  -- fator 200 funcionou bem pra mim
        dx, dy = 0, 0
        with love.keyboard
            dx -= ds if .isDown"left"
            dx += ds if .isDown"right"
            dy -= ds if .isDown"left"
            dy += ds if .isDown"left"
        @camera\move dx, dy

Agora precisamos avisar LÖVE para chamar esse método a cada vez que o sistema atualizar seu estado. Isso é feito através do callback :

love.update = (dt) ->
    app\update dt

Então é possível mover a câmera usando as setas do teclado.

Adicionando mais objetos

Parece estranho… aperta para a esquerda, o círculo vai para a direita, aperta para baixo, vai para cima…

Isso porque o que está sendo movido é a câmera. Para deixar mais claro, basta acrescentar mais formas.

Altere o construtor da classe para receber a lista de formas:

   new: (...) =>
       @camera = Camera 0, 0
       @shapes = {...}

E na instanciação coloque mais formas:

love.load = ->
    app = CameraExample (-> love.graphics.circle "fill", 0, 0, 50),
                        (-> love.graphics.rectangle "fill", 100, 80, 10, 10),
                        (-> love.graphics.circle "fill", -80, 150, 20)
    love.graphics.setBackgroundColor 0x00, 0x50, 0x90

Compile e rode novamente.

Brincando com tweening

Para ficar mais interessante, vamos acrescentar um tweening.

No cabeçalho, logo abaixo da imporação do módulo de câmera acrescente:

Timer = assert require "hump.timer"

Mude agora o construtor da classe para reter a cor dos objetos:

    new: (...) =>
        @color = 0x00
        @camera = Camera 0, 0
        @shapes = {...}

Adicione um método start para iniciar o tweening, que mudará a cor entre branco e preto a cada 2s:

    start: =>
        guard = ->
            color = if @color < 0x80 then 0xff else 0x00
            Timer.tween 2, @, {:color}, "in-sine", guard
        guard!

Esse método precisa ser chamado no carregamento de LÖVE:

love.load = ->
    app = CameraExample (-> love.graphics.circle "fill", 0, 0, 50),
                        (-> love.graphics.rectangle "fill", 100, 80, 10, 10),
                        (-> love.graphics.circle "fill", -80, 150, 20)
    love.graphics.setBackgroundColor 0x00, 0x50, 0x90
    app\start!

O módulo de tweening precisa ser atualizado no callback. Mude o código também:

love.update = (dt) ->
    Timer.update dt
    app\update dt

Ao compilar e rodar a aplicação você verá os objetos piscando, enquanto a câmera pode ser movida para mudar o ponto de visão.

Lua | Jogos