Isto é uma pré-visualização de um tema em Hitskin.com
Instalar o tema • Voltar para a ficha do tema
[love + enet] Criando um chat!
3 participantes
Página 1 de 1
[love + enet] Criando um chat!
Antes de qualquer coisa, fiz esse tutorial mais com intuito de aprender do que ensinar (sim, isso mesmo e.e). Foi meu primeiro contato tanto com a biblioteca de rede utilizada (enet), quanto com a biblioteca de interface (suit). A melhor maneira de aprender na minha opinião é ensinando, pois temos que entender cada passo antes de conseguir compartilhar com terceiros.
Recentemente conheci uma biblioteca chamada enet. Vocês podem encontrar todos os detalhes sobre ela no site oficial. Eu curti muito os recursos (mesmo não tendo explorado todos eles neste tutorial .-.) e por isso resolvi aprender mais sobre essa biblioteca incrível.
A biblioteca foi desenvolvida para ser usada em jogos com baixa latência e alta frequência de troca de dados, ou seja: não é o caso em que utilizei neste tutorial (um chat e.e). Mas é preciso entender que o intuito é apenas aprender a usar a biblioteca para então aplicar em um caso de uso real.
O projeto final será esse:
Um detalhe antes de começar é que não irei postar os trecos de código aqui (para que não copiem e colem -.- ), quem quiser seguir o tutorial terá que escrever o que está nas imagens, o projeto final estará disponível em anexo. Estou utilizando a versão 11.1 do Love (a mais recente até o momento).
Antes de começar a programar de fato temos que configurar a pasta do nosso projeto, eu particularmente prefiro deixar a pasta raiz da seguinte forma:
lib: Nesta pasta ficarão todas as bibliotecas que utilizaremos no projeto.
src: Aqui ficará o código fonte da nossa aplicação.
main.lua: Por padrão esse arquivo é ponto inicial de um programa usando love.
conf.lua: Este é outro arquivo padrão nos projetos do love, contém toda a configuração (módulos usados, propriedades da janela...)
Depois de configurar a pasta do nosso projeto podemos então começar a programar. Abra o arquivo main.lua e vamos começar! Neste arquivo precisamos primeiro definir três funções principais:
love.load: Essa função é chamada ao iniciar o aplicativo.
love.update e love.draw: São auto-explicativos. Ao definirmos essas três funções nosso arquivo deverá estar assim:
A função update recebe o um argumento chamado dt (delta time) que é o tempo que levou para completar o ciclo de atualização em segundos.
Agora precisamos baixar nossa biblioteca de interface, como já mencionei antes estou utilizando o SUIT neste tutorial. Baixe a pasta pelo github e extraia para a pasta lib do nosso projeto, em seguida renomeie de suit-master para suit. Ficando da seguinte forma:
Agora que já temos a biblioteca disponível dentro do nosso projeto, podemos usar o require para carregá-la. No topo do arquivo main.lua adicione:
Um detalhes interessante sobre essa biblioteca é que, diferente do que eu estou acosumado a ver em outras com o mesmo propósito, o SUIT não precisa que você instancie nenhum componente, dá para simplesmente adiciona o código dentro do update e nossa GUI estará pronta.
No inicio do tutorial tem algumas imagens do programa final, nela dá pra ver que temos três botões (criar, conectar e sair), todos centralizados tanto horizontal quanto verticalmente na janela (e com espaçamento de 10px entre cada). O SUIT permite a criação de paineis, configurações de layout mais complexas incluindo alinhamento, linhas e colunas. Mas para não complicar nossa vida e também por que este tutorial não tem o foco de ensinar a usar propriamente essa biblioteca, iremos apenas criar os componentes e diretamente definir suas posições na tela.
Como temos um padrão entre os componentes, podemos automatizar o processo e.e
O que dá pra fazer sem que fique muito complexo é criar uma table e armazenar nela as propriedades dos botões. Tem duas coisas que precisamos configurar: o texto que será exibido no botão (criar, conectar e sair) e a função que será executada ao clicar nele. Acho que ficou um pouco confuso de entender, mas o código é simples, seria algo mais ou menos assim:
Entenderam?
Para que nosso código não fique uma zona completa, vamos separar o código em mais dois arquivos que corresponderão as "telas" que podemos acessar dentro do aplicativo. A primeira tela seria o menu, onde temos os três botões, já a segunda tela será o chat propriamente dito, nele teremos uma caixa de texto para digitar as mensagens e um botão para enviar.
Dentro da pasta src crie dois arquivos: chat.lua e menu.lua. Assim podemos deixar nosso código relativamente separado. Abra o arquivo do menu e vamos começar a programá-lo. Lua por padrão não possui classes, isso por que a linguagem é flexível (e compacta) e permite que o desenvolvedor trabalhe da maneira que achar mais adequada para o projeto em questão. Uma imagem de como ficou nossa pasta raiz até o momento:
Abra o arquivo do menu e vamos começar. Lembra que no arquivo main.lua definimos três métodos principais (load, update e draw)? Agora precisamos fazer basicamente o mesmo com o menu, mas ao invés de deixarmos as funções "soltas" dentro do arquivo, vamos defini-las dentro de uma table. Ficando assim:
Cada função está recebendo uma variável chamada self como PRIMEIRO argumento. Isso por que table:func() equivale a table.func(table). Deu pra entender? Quando usamos o sinal de dois pontos ':' para executar uma função em lua, o compilador automaticamente passa a table que contém essa função como primeiro argumento da função, já na definição da função definimos o primeiro argumento com nome de self. No nosso código, dentro das funções self e Menu se referem a mesma table.
No final do código tem um return, isso por que nós não definimos nenhuma variável global no arquivo. Quando usarmos o require para carregar esse arquivo, o que retornará é a table Menu. Um exemplo de uso seria o seguinte:
Perceba que usamos o sinal de dois pontos para executar a função, ou seja: ao executarmos uma função a table que contém a função é passada como primeiro parâmetro (omitido no código com podem ver). O código acima corresponde ao seguinte:
O da primeira imagem ficou bem mais legível, não concordam?
Depois dessa breve (e incompleta) explicação sobre tables vamos voltar ao nosso código. Dentro do arquivo menu.lua, na função draw vamos desenhar um texto qualquer na tela apenas para identificarmos a tela que estamos.
Vamos voltar ao arquivo main.lua. Agora precisamos carregar o arquivo do menu e fazer a chamada daquelas três funções que definimos. No primeiro momento (para testarmos se está tudo correto), vamos fazer isso da maneira mais direta: definindo uma variável para conter a table do menu e chamar os métodos em cada uma das funções dentro do main. Ficando assim:
Para testarmos o projeto é preciso abrir o terminal (linux)/cmd (windows até o 7)/power shell (windows 8 acima) dentro da pasta do projeto. Caso esteja usando o windows, uma maneira simples de fazer isso é pressionando shift e clicando com botão direito dentro da pasta (no navegador de arquivos).
Lembrando que as pasta de instalação do love precisa estar na variável de ambiente PATH para que possam executá-lo a partir do terminal.
Para executar o nosso projeto precisamos passar a pasta raiz do nosso projeto como argumento ao executar o love, ficando assim:
E se excutarmos isso o resultado será:
Da pra notar que nossa janela, apesar de mostrar o conteúdo que definimos no menu, está com tamanho e título padrões do love. Para muda-los vamos ir até o arquivo love.conf, caso queira uma explicação detalhada do que da ou não pra modificar dentro desse arquivo pode acessar a wiki. Nós só precisaremos mudar três (três de novo, esse número ta aparecendo com muita frequência) coisas: título, altura e largura da janela.
Dentro do arquivo conf.lua vamos criar uma função chamada love.conf, ela recebe como parâmetro uma table que será usada para configurar nosso projeto. O código base ficará assim:
Agora precisamos modificar nossa janela, o códifo final ficará assim:
E se executarmos nosso projeto novamente o resultado será esse:
Agora vamos fazer o mesmo que fizemos pro menu, só que pro chat (pra tela do chat e.e), vamos apenas mudar os nomes. Nosso arquivo chat.lua ficará assim:
Agora temos um pequeno problema. Nós temos duas telas diferentes, logo precisamos de alguma maneira fazer a troca de tela (do menu pro chat por exemplo). Assim como qualquer outro problema, existem incontáveis soluções que podemos usar, eu optei pela que achei mais equilibrada entre simplicidade e automação.
Nós iremos criar uma table dentro do arquivo main.lua, nessa table vamos armazenas as tables das telas de menu e chat, e também iremos criar um outro campo para armazenar a tela que estamos atualmente. Seria algo mais ou menos assim:
Tendo isso, para trocarmos de tela bastaria definirmos o valor do campo _current como o de uma das telas (menu ou chat) da seguinte forma:
Só que não faremos isso manualmente, seria chato e em algum momento poderíamos acabar esquecendo de chamar o load após a definição. Então para automatizar o processo vamos criar uma função global chamada changeScreen, que recebe como parâmetro o nome da tela para qual queremos mudar e um bônus e.e.
Como podem ver, o segundo parâmetro são '...' (três pontos), em lua isso faz com que todos os argumentos que forem passados sejam armazenados nesses trêns pontinhos. Caso tenham alguma dúvida sobre como funciona recomendo ler este artigo. Lembrando que o primeiro argumento possui um nome, então somente do segundo para frente serão passados como variáveis.
Nós precisamos disso por que ao mudar de tela podemos precisar passar algum argumento para próxima tela (como verão assim que começarmos a trabalhar com o chat).
Voltando ao código, dentro da função changeScreen precisamos mudar a tela que estamos (definindo um novo valor para screens._current) e em seguida chamar o método load da tela que acabamos de mudar. E temos o seguinte:
Agora precisamos atualizar o resto do código do arquivo main.lua, remover aquela definição de menu que fizer mais cedo e usar screens._current no lugar. O código final ficará assim:
Como podem ver, no método love.load executamos a função changeScreen para definir a primeira tela do jogo como sendo o menu.
Agora que já temos a estrutura básica do nosso projeto pronto, podemos começar a programar as telas. Voltamos ao arquivo menu.lua, vamos precisar definir uma table onde ficarão as propriedades dos botões (como mencionei lá em cima). Após definirmos essas propriedades, o método load do do menu ficará assim:
Chegamos na parte chatinha que é criar os botões e posicioná-los no centro da janela. Para posicionarmos um objeto no centro da janela, precisamos pegar a largura da janela / 2 e subtrair a largura do objeto / 2. Isso fará com que o objeto fique horizontalmente centralizado (nós precisamos centralizar na vertical também). Para centralizar na vertical a lógica é a mesma, porém temos que levar em conta que não teremos apenas um objeto, mas três (os três botões) e o espaçamento entre eles (de 10px).
Como expliquei no inicio, o SUIT permite a criação dos componentes diretamente no update, sem que haja necessidade de criar instâncias para cada um (o componente só existe enquanto o código estiver no update). Caso queira saber com mais detalhes sobre a criação dos componentes com SUIT recomendo que leia a documentação presente no repositório oficial.
O código dentro do update ficará assim:
Onde:
gw2: Corresponde a largura da janela dividida por dois.
gh2: Corresponde a altura da janela dividida por dois.
th2: Corresponde a altura do botão (que é 25px) multiplicada por três (já que temos três botões), somada a quantidade de intervalos (se temos três botões, temos então dois intervalos) multiplicado por dez. Tudo isso divido por dois.
Algo que poderíamos otimizar é passar esses cálculos para fora do update, já que estão sendo feitos a cada atualização, mas deixarei assim pois caso a janela seja redimensionada não teremos problemas. Lembrando que o love possui um callback para redimensionamento da janela e poderiamos por esses calculos lá, mas introduzir isso no meio desse tutorial iria alongar mais ainda.
Se executarmos o código como está agora teremos um erro falando que não existe nenhuma variável chamada suit no escopo global ou local. Isso acontece por que quando demos o require na biblioteca, acabei esquecendo de definir uma variável para global para armazenar o módulo. Voltamos então ao arquivo main.lua e na primera linha vamos mudar para:
Agora se executarmos nosso projeto novamente teremos... nada e.e
Para que o SUIT funcione corretamente precisaremos adicionar algumas chamadas nas funções love.textedited, love.textinput, love.keypressed e love.draw. Essas são funções executadas pelo love assim que algum desses eventos é realizado. Nosso arquivo main.lua ficará da seguinte forma:
E então se executarmos o código veremos nossa tela de menu pronta e um erro ao clicar em Sair. Para corrigir o erro é só mudar events para event dentro da função do botão de sair. O resultado até o momento deve ser o seguinte:
Para concluirmos a parte do menu precisamos adicionar a mudança de tela dentro dos botões de criar e conectar, porém não vamos apenas mudar a tela dessa vez. Precisamos também passar um argumento (agora entram os três pontinhos) que indicará se estamos criando uma sala ou nos conectando a uma já criada. Podemos também remover aquela linha de dentro do draw já que não precisamos mais dela para sabermos que estamos na tela de menu. O código final ficará assim:
Agora vamos para parte final e mais complexa do nosso projeto, que é de fato programar o chat. Antes de começarmos precisamos discutir alguns pontos. Como podem ver na imagem no início do tópico, o chat tem uma área de desenho onde ficarão as linhas, uma área limitada. Precisamos mostrar apenas as últimas linhas do chat (que cabem naquele espaço), para ficar mais simples de enteder eu tentei separar ao máximo o código então no decorrer do tutorial acredito que não será muito difícil de entender o funcionamento. Como já mencionei antes existem diversas maneiras de se resolver o mesmo problema com programação (ainda mais com lua que é uma linguagem muito flexível), optei por manter o histórico do chat sempre armazenado ao inves de ter um número fixo de slots paras as linhas, assim futuramente poderemos enviar o histórico do chat para os novos membros que se conectarem (ou até mesmo adicionar uma rolagem de texto).
No arquivo chat.lua, a primeira coisa que precisamos fazer é adicionar o segundo parâmetro ao método load (nós usamos changeScreen('chat', 'modo') para chamar a tela do chat lembra?). Também vamos precisar de uma variável para armazenar as linhas dos chat, outra para armazenas os membros do chat (caso seja o host). O código do load ficará mais ou menos assim até o momento:
Precisamos carregar o módulo enet para podermos começar a trabalhar com a comunicação do chat. Na primeira linha do arquivo chat.lua adicione:
Agora que já temos o módulo enet disponível, voltemos ao método load. Nós já definimos uma condição que verifica o modo que o chat foi iniciado, vamos aproveitar isso e definir uma outra condição logo abaixo para o modo de se conectar ao chat.
O enet é feito em cima do protocolo UDP, mas possui alguns recursos úteis do TCP como a garantia de que a mensagem será entregue ao destinatário e também a garantia que serão entregues na ordem correta (o que não existe no UDP, pacotes podem se perder ou chegar em ordens completamente diferentes das que foram enviadas). Para usar o enet, precisamos basicamente criar um host, se passarmos um endereço (no formato ip:porta) o host permitirá que outros se conectem a ele (o que seria como o funcionamento do bind em uma socket usando TCP), caso contrário será possível apenas se conectar a um host. Tendo isso em mente iremos ao código (lembrando que podem encontrar a documentação completa do binding do enet para lua aqui):
Bem simples até não acham? Precisamos também definir um método de atualização específico para cada modo do chat (seja como host do chat ou como um membro que se conectou). Vamos criar dois métodos a mais no chat: updateClient e updateServer. Dependendo do modo do servidor um deles será executado. Ficando da seguinte forma:
Agora precisamos criar uma variável chamada updateChat que definirá qual desses métodos usaremos, vamos voltar ao método load então e ele ficará assim:
A lógica de funcionamento do chat é a seguinte:
Existe um host que recebe as mensagens que foram enviadas pelos membros e transmite a mesma para todos os membros (incluindo quem enviou, para simplificar nosso trabalho). Já que até quem enviou a mensagem recebe ela do servidor, ao clicar no botão de enviar não podemos adicionar a mensagem diretamente ao histórico de mensagens, isso será feito quando recebermos a mensagem de volta (após o host enviar para os membros).
Vamos ao código então, dentro do método updateClient vamos verificar se tem algum evento aguardando para ser processado, verificaremos seu tipo (o enet tem três tipos de eventos até onde sei, sendo eles: connect, receive e disconnect). Para o updateClient usaremos apenas o método receive, que é disparado quando recebemos algo (no nosso caso só quem enviar mensagem para os membros é o host do chat). Na documentação do enet (que já linkei no tópico) vocês podem ver alguns exemplos de uso e a descrição detalhada de cada método (já que estou sendo bem superficial aqui). O código ficaria da seguinte forma:
O método updateServer precisará lidar com os três eventos disparados pelo enet, já que precisaremos salvar uma referência de cada membro que se conectou (precisamos fazer isso para enviar as mensagens a todos futuramente). Nós já criamos uma table chamada chat_members para isso. Quando o host receber uma mensagem (de algum dos membros obviamente), adicionaremos essa mensagem ao histórico do chat e logo em seguida enviaremos a mesma mensagem para todos os membros conectados a sala. Quando o evento disconnect for disparado, ou seja, quando um membro se desconectar do chat precisaremos remover a referência dele da lista de membros do chat. Vamos ao código então:
Agora que temos essa parte pronta, precisamos criar nossos componentes (a caixa de texto e um botão de enviar). Vai ser algo parecido com que fizemos no código do menu. Lembre-se que quando clicar no botão de enviar, precisamos verificar qual modo o chat está (como host ou client), caso seja o host do chat clicando em enviar precisaremos adicionar a mensagem ao histórico do chat (já que o host não recebe uma cópia da própria mensagem), após isso vamos enviar a mensagem para todos os membros do chat. Se for um membro clicando em enviar, vamos usar a referência ao host que criamos la no load (server_peer) e enviaremos para ele a mensagem. Após isso tudo vamos definir o texto da caixa de texto como uma string vazia.
Como o SUIT não trabalha com instâncias, precisaremos de uma variável externa para armazenar o texto da caixa de texto, para isso vamos voltar ao método load e definiremos uma variável chamada text_input, ela será uma table com um campo text. Ficará assim:
Esqueci de mencionar que ao final do update precisamos chamar o método updateChat que foi definido la no load, o código final do método update será:
Chegamos na etapa final do tutorial, precisamo configurar o método draw para exibir somente as últimas linhas do chat (desde que caibam no espaço que temos). Primeiro vamos desenhar o modo do chat no topo da janela (como podem ver nas imagens la no inicio do tópico), desenharemos também a quantidade de membros conectados no chat caso seja o host e uma moldura para a área onde as linhas serão desenhadas. Essa parte é relativamente simples e o código ficará assim:
Mas não acabamos ainda, precisamos agora desenhar as linhas do chat, para isso usei uma lógica bem simples. Quando a quantidade de linhas que o histórico de mensagens possui for maior que a quantidade de linhas visíveis no chat, mudaremos o índice inicial a partir do qual acessaremos o histórico de mensagens. Para facilitar o entendimento, vamos declarar algumas variáveis locais dentro do método draw:
Onde:
max_size: É o tamanho da área que temos disponível para desenharmos as linhas do chat, corresponde a área da moldura que desenhamos acima.
line_height: Altura de cada linha.
max_lines: Máximo de linhas que podemos desenha na área disponível, o floor arredonda o número para baixo.
last_line: O índice da última linha do chat que será desenhada.
st_index: O índice da primeira linha que será desenhada.
Como podem ver, as variáveis last_line e st_index não possuem nenhum valor atribuido, isso por que precisamos verificar a quantidade de linhas disponíveis primeiro. Se a quantidade de linhas que o histórico possui for maior que a quantidade de linhas que podemos deixar visíveis então a primeira linha a ser desenhada terá o índice: quantidade de linhas do histórico - quantidade máxima de linhas visíveis - 1. E a variável last_line será igual ao valor do máximo de linhas visíveis (last_line é usado apenas no loop, para delimitar a quantidade de linhas a serem desenhadas).
Se a quantidade de linhas do histórico for menor que a quantidade de linhas que são visíveis então a variável st_index será igual a zero (já que não precisamos pular nenhuma linha) e a last_line será: quantidade de linhas do histórico - 1. Essa parte do código ficará assim:
Agora que já temos esses dados a disposição podemos começar a desenhar o histórico do chat, para isso basta usar um for e percorrer o a table chat_lines, lembrando que temos que percorrer apenas a quantidade de linhas visíveis. O código ficará da seguinte forma:
Lembrando a todos que em lua o primeiro índice de uma table é sempre 1 e não 0 como em outras linguagens. Por último iremos desenhar a quantidade de membros no chat:
Resultado final:
Download:
tutorial-chat.zip
Fontes:
https://love2d.org/wiki/lua-enet
http://leafo.net/lua-enet
https://github.com/vrld/suit
Recentemente conheci uma biblioteca chamada enet. Vocês podem encontrar todos os detalhes sobre ela no site oficial. Eu curti muito os recursos (mesmo não tendo explorado todos eles neste tutorial .-.) e por isso resolvi aprender mais sobre essa biblioteca incrível.
A biblioteca foi desenvolvida para ser usada em jogos com baixa latência e alta frequência de troca de dados, ou seja: não é o caso em que utilizei neste tutorial (um chat e.e). Mas é preciso entender que o intuito é apenas aprender a usar a biblioteca para então aplicar em um caso de uso real.
O projeto final será esse:
- Spoiler:
Um detalhe antes de começar é que não irei postar os trecos de código aqui (para que não copiem e colem -.- ), quem quiser seguir o tutorial terá que escrever o que está nas imagens, o projeto final estará disponível em anexo. Estou utilizando a versão 11.1 do Love (a mais recente até o momento).
Antes de começar a programar de fato temos que configurar a pasta do nosso projeto, eu particularmente prefiro deixar a pasta raiz da seguinte forma:
lib: Nesta pasta ficarão todas as bibliotecas que utilizaremos no projeto.
src: Aqui ficará o código fonte da nossa aplicação.
main.lua: Por padrão esse arquivo é ponto inicial de um programa usando love.
conf.lua: Este é outro arquivo padrão nos projetos do love, contém toda a configuração (módulos usados, propriedades da janela...)
Depois de configurar a pasta do nosso projeto podemos então começar a programar. Abra o arquivo main.lua e vamos começar! Neste arquivo precisamos primeiro definir três funções principais:
love.load: Essa função é chamada ao iniciar o aplicativo.
love.update e love.draw: São auto-explicativos. Ao definirmos essas três funções nosso arquivo deverá estar assim:
A função update recebe o um argumento chamado dt (delta time) que é o tempo que levou para completar o ciclo de atualização em segundos.
Agora precisamos baixar nossa biblioteca de interface, como já mencionei antes estou utilizando o SUIT neste tutorial. Baixe a pasta pelo github e extraia para a pasta lib do nosso projeto, em seguida renomeie de suit-master para suit. Ficando da seguinte forma:
Agora que já temos a biblioteca disponível dentro do nosso projeto, podemos usar o require para carregá-la. No topo do arquivo main.lua adicione:
Um detalhes interessante sobre essa biblioteca é que, diferente do que eu estou acosumado a ver em outras com o mesmo propósito, o SUIT não precisa que você instancie nenhum componente, dá para simplesmente adiciona o código dentro do update e nossa GUI estará pronta.
No inicio do tutorial tem algumas imagens do programa final, nela dá pra ver que temos três botões (criar, conectar e sair), todos centralizados tanto horizontal quanto verticalmente na janela (e com espaçamento de 10px entre cada). O SUIT permite a criação de paineis, configurações de layout mais complexas incluindo alinhamento, linhas e colunas. Mas para não complicar nossa vida e também por que este tutorial não tem o foco de ensinar a usar propriamente essa biblioteca, iremos apenas criar os componentes e diretamente definir suas posições na tela.
Como temos um padrão entre os componentes, podemos automatizar o processo e.e
O que dá pra fazer sem que fique muito complexo é criar uma table e armazenar nela as propriedades dos botões. Tem duas coisas que precisamos configurar: o texto que será exibido no botão (criar, conectar e sair) e a função que será executada ao clicar nele. Acho que ficou um pouco confuso de entender, mas o código é simples, seria algo mais ou menos assim:
Entenderam?
Para que nosso código não fique uma zona completa, vamos separar o código em mais dois arquivos que corresponderão as "telas" que podemos acessar dentro do aplicativo. A primeira tela seria o menu, onde temos os três botões, já a segunda tela será o chat propriamente dito, nele teremos uma caixa de texto para digitar as mensagens e um botão para enviar.
Dentro da pasta src crie dois arquivos: chat.lua e menu.lua. Assim podemos deixar nosso código relativamente separado. Abra o arquivo do menu e vamos começar a programá-lo. Lua por padrão não possui classes, isso por que a linguagem é flexível (e compacta) e permite que o desenvolvedor trabalhe da maneira que achar mais adequada para o projeto em questão. Uma imagem de como ficou nossa pasta raiz até o momento:
Abra o arquivo do menu e vamos começar. Lembra que no arquivo main.lua definimos três métodos principais (load, update e draw)? Agora precisamos fazer basicamente o mesmo com o menu, mas ao invés de deixarmos as funções "soltas" dentro do arquivo, vamos defini-las dentro de uma table. Ficando assim:
Cada função está recebendo uma variável chamada self como PRIMEIRO argumento. Isso por que table:func() equivale a table.func(table). Deu pra entender? Quando usamos o sinal de dois pontos ':' para executar uma função em lua, o compilador automaticamente passa a table que contém essa função como primeiro argumento da função, já na definição da função definimos o primeiro argumento com nome de self. No nosso código, dentro das funções self e Menu se referem a mesma table.
No final do código tem um return, isso por que nós não definimos nenhuma variável global no arquivo. Quando usarmos o require para carregar esse arquivo, o que retornará é a table Menu. Um exemplo de uso seria o seguinte:
Perceba que usamos o sinal de dois pontos para executar a função, ou seja: ao executarmos uma função a table que contém a função é passada como primeiro parâmetro (omitido no código com podem ver). O código acima corresponde ao seguinte:
O da primeira imagem ficou bem mais legível, não concordam?
Depois dessa breve (e incompleta) explicação sobre tables vamos voltar ao nosso código. Dentro do arquivo menu.lua, na função draw vamos desenhar um texto qualquer na tela apenas para identificarmos a tela que estamos.
Vamos voltar ao arquivo main.lua. Agora precisamos carregar o arquivo do menu e fazer a chamada daquelas três funções que definimos. No primeiro momento (para testarmos se está tudo correto), vamos fazer isso da maneira mais direta: definindo uma variável para conter a table do menu e chamar os métodos em cada uma das funções dentro do main. Ficando assim:
Para testarmos o projeto é preciso abrir o terminal (linux)/cmd (windows até o 7)/power shell (windows 8 acima) dentro da pasta do projeto. Caso esteja usando o windows, uma maneira simples de fazer isso é pressionando shift e clicando com botão direito dentro da pasta (no navegador de arquivos).
Lembrando que as pasta de instalação do love precisa estar na variável de ambiente PATH para que possam executá-lo a partir do terminal.
Para executar o nosso projeto precisamos passar a pasta raiz do nosso projeto como argumento ao executar o love, ficando assim:
E se excutarmos isso o resultado será:
Da pra notar que nossa janela, apesar de mostrar o conteúdo que definimos no menu, está com tamanho e título padrões do love. Para muda-los vamos ir até o arquivo love.conf, caso queira uma explicação detalhada do que da ou não pra modificar dentro desse arquivo pode acessar a wiki. Nós só precisaremos mudar três (três de novo, esse número ta aparecendo com muita frequência) coisas: título, altura e largura da janela.
Dentro do arquivo conf.lua vamos criar uma função chamada love.conf, ela recebe como parâmetro uma table que será usada para configurar nosso projeto. O código base ficará assim:
Agora precisamos modificar nossa janela, o códifo final ficará assim:
E se executarmos nosso projeto novamente o resultado será esse:
Agora vamos fazer o mesmo que fizemos pro menu, só que pro chat (pra tela do chat e.e), vamos apenas mudar os nomes. Nosso arquivo chat.lua ficará assim:
Agora temos um pequeno problema. Nós temos duas telas diferentes, logo precisamos de alguma maneira fazer a troca de tela (do menu pro chat por exemplo). Assim como qualquer outro problema, existem incontáveis soluções que podemos usar, eu optei pela que achei mais equilibrada entre simplicidade e automação.
Nós iremos criar uma table dentro do arquivo main.lua, nessa table vamos armazenas as tables das telas de menu e chat, e também iremos criar um outro campo para armazenar a tela que estamos atualmente. Seria algo mais ou menos assim:
Tendo isso, para trocarmos de tela bastaria definirmos o valor do campo _current como o de uma das telas (menu ou chat) da seguinte forma:
Só que não faremos isso manualmente, seria chato e em algum momento poderíamos acabar esquecendo de chamar o load após a definição. Então para automatizar o processo vamos criar uma função global chamada changeScreen, que recebe como parâmetro o nome da tela para qual queremos mudar e um bônus e.e.
Como podem ver, o segundo parâmetro são '...' (três pontos), em lua isso faz com que todos os argumentos que forem passados sejam armazenados nesses trêns pontinhos. Caso tenham alguma dúvida sobre como funciona recomendo ler este artigo. Lembrando que o primeiro argumento possui um nome, então somente do segundo para frente serão passados como variáveis.
Nós precisamos disso por que ao mudar de tela podemos precisar passar algum argumento para próxima tela (como verão assim que começarmos a trabalhar com o chat).
Voltando ao código, dentro da função changeScreen precisamos mudar a tela que estamos (definindo um novo valor para screens._current) e em seguida chamar o método load da tela que acabamos de mudar. E temos o seguinte:
Agora precisamos atualizar o resto do código do arquivo main.lua, remover aquela definição de menu que fizer mais cedo e usar screens._current no lugar. O código final ficará assim:
Como podem ver, no método love.load executamos a função changeScreen para definir a primeira tela do jogo como sendo o menu.
Agora que já temos a estrutura básica do nosso projeto pronto, podemos começar a programar as telas. Voltamos ao arquivo menu.lua, vamos precisar definir uma table onde ficarão as propriedades dos botões (como mencionei lá em cima). Após definirmos essas propriedades, o método load do do menu ficará assim:
Chegamos na parte chatinha que é criar os botões e posicioná-los no centro da janela. Para posicionarmos um objeto no centro da janela, precisamos pegar a largura da janela / 2 e subtrair a largura do objeto / 2. Isso fará com que o objeto fique horizontalmente centralizado (nós precisamos centralizar na vertical também). Para centralizar na vertical a lógica é a mesma, porém temos que levar em conta que não teremos apenas um objeto, mas três (os três botões) e o espaçamento entre eles (de 10px).
Como expliquei no inicio, o SUIT permite a criação dos componentes diretamente no update, sem que haja necessidade de criar instâncias para cada um (o componente só existe enquanto o código estiver no update). Caso queira saber com mais detalhes sobre a criação dos componentes com SUIT recomendo que leia a documentação presente no repositório oficial.
O código dentro do update ficará assim:
Onde:
gw2: Corresponde a largura da janela dividida por dois.
gh2: Corresponde a altura da janela dividida por dois.
th2: Corresponde a altura do botão (que é 25px) multiplicada por três (já que temos três botões), somada a quantidade de intervalos (se temos três botões, temos então dois intervalos) multiplicado por dez. Tudo isso divido por dois.
Algo que poderíamos otimizar é passar esses cálculos para fora do update, já que estão sendo feitos a cada atualização, mas deixarei assim pois caso a janela seja redimensionada não teremos problemas. Lembrando que o love possui um callback para redimensionamento da janela e poderiamos por esses calculos lá, mas introduzir isso no meio desse tutorial iria alongar mais ainda.
Se executarmos o código como está agora teremos um erro falando que não existe nenhuma variável chamada suit no escopo global ou local. Isso acontece por que quando demos o require na biblioteca, acabei esquecendo de definir uma variável para global para armazenar o módulo. Voltamos então ao arquivo main.lua e na primera linha vamos mudar para:
Agora se executarmos nosso projeto novamente teremos... nada e.e
Para que o SUIT funcione corretamente precisaremos adicionar algumas chamadas nas funções love.textedited, love.textinput, love.keypressed e love.draw. Essas são funções executadas pelo love assim que algum desses eventos é realizado. Nosso arquivo main.lua ficará da seguinte forma:
E então se executarmos o código veremos nossa tela de menu pronta e um erro ao clicar em Sair. Para corrigir o erro é só mudar events para event dentro da função do botão de sair. O resultado até o momento deve ser o seguinte:
Para concluirmos a parte do menu precisamos adicionar a mudança de tela dentro dos botões de criar e conectar, porém não vamos apenas mudar a tela dessa vez. Precisamos também passar um argumento (agora entram os três pontinhos) que indicará se estamos criando uma sala ou nos conectando a uma já criada. Podemos também remover aquela linha de dentro do draw já que não precisamos mais dela para sabermos que estamos na tela de menu. O código final ficará assim:
Agora vamos para parte final e mais complexa do nosso projeto, que é de fato programar o chat. Antes de começarmos precisamos discutir alguns pontos. Como podem ver na imagem no início do tópico, o chat tem uma área de desenho onde ficarão as linhas, uma área limitada. Precisamos mostrar apenas as últimas linhas do chat (que cabem naquele espaço), para ficar mais simples de enteder eu tentei separar ao máximo o código então no decorrer do tutorial acredito que não será muito difícil de entender o funcionamento. Como já mencionei antes existem diversas maneiras de se resolver o mesmo problema com programação (ainda mais com lua que é uma linguagem muito flexível), optei por manter o histórico do chat sempre armazenado ao inves de ter um número fixo de slots paras as linhas, assim futuramente poderemos enviar o histórico do chat para os novos membros que se conectarem (ou até mesmo adicionar uma rolagem de texto).
No arquivo chat.lua, a primeira coisa que precisamos fazer é adicionar o segundo parâmetro ao método load (nós usamos changeScreen('chat', 'modo') para chamar a tela do chat lembra?). Também vamos precisar de uma variável para armazenar as linhas dos chat, outra para armazenas os membros do chat (caso seja o host). O código do load ficará mais ou menos assim até o momento:
Precisamos carregar o módulo enet para podermos começar a trabalhar com a comunicação do chat. Na primeira linha do arquivo chat.lua adicione:
Agora que já temos o módulo enet disponível, voltemos ao método load. Nós já definimos uma condição que verifica o modo que o chat foi iniciado, vamos aproveitar isso e definir uma outra condição logo abaixo para o modo de se conectar ao chat.
O enet é feito em cima do protocolo UDP, mas possui alguns recursos úteis do TCP como a garantia de que a mensagem será entregue ao destinatário e também a garantia que serão entregues na ordem correta (o que não existe no UDP, pacotes podem se perder ou chegar em ordens completamente diferentes das que foram enviadas). Para usar o enet, precisamos basicamente criar um host, se passarmos um endereço (no formato ip:porta) o host permitirá que outros se conectem a ele (o que seria como o funcionamento do bind em uma socket usando TCP), caso contrário será possível apenas se conectar a um host. Tendo isso em mente iremos ao código (lembrando que podem encontrar a documentação completa do binding do enet para lua aqui):
Bem simples até não acham? Precisamos também definir um método de atualização específico para cada modo do chat (seja como host do chat ou como um membro que se conectou). Vamos criar dois métodos a mais no chat: updateClient e updateServer. Dependendo do modo do servidor um deles será executado. Ficando da seguinte forma:
Agora precisamos criar uma variável chamada updateChat que definirá qual desses métodos usaremos, vamos voltar ao método load então e ele ficará assim:
A lógica de funcionamento do chat é a seguinte:
Existe um host que recebe as mensagens que foram enviadas pelos membros e transmite a mesma para todos os membros (incluindo quem enviou, para simplificar nosso trabalho). Já que até quem enviou a mensagem recebe ela do servidor, ao clicar no botão de enviar não podemos adicionar a mensagem diretamente ao histórico de mensagens, isso será feito quando recebermos a mensagem de volta (após o host enviar para os membros).
Vamos ao código então, dentro do método updateClient vamos verificar se tem algum evento aguardando para ser processado, verificaremos seu tipo (o enet tem três tipos de eventos até onde sei, sendo eles: connect, receive e disconnect). Para o updateClient usaremos apenas o método receive, que é disparado quando recebemos algo (no nosso caso só quem enviar mensagem para os membros é o host do chat). Na documentação do enet (que já linkei no tópico) vocês podem ver alguns exemplos de uso e a descrição detalhada de cada método (já que estou sendo bem superficial aqui). O código ficaria da seguinte forma:
O método updateServer precisará lidar com os três eventos disparados pelo enet, já que precisaremos salvar uma referência de cada membro que se conectou (precisamos fazer isso para enviar as mensagens a todos futuramente). Nós já criamos uma table chamada chat_members para isso. Quando o host receber uma mensagem (de algum dos membros obviamente), adicionaremos essa mensagem ao histórico do chat e logo em seguida enviaremos a mesma mensagem para todos os membros conectados a sala. Quando o evento disconnect for disparado, ou seja, quando um membro se desconectar do chat precisaremos remover a referência dele da lista de membros do chat. Vamos ao código então:
Agora que temos essa parte pronta, precisamos criar nossos componentes (a caixa de texto e um botão de enviar). Vai ser algo parecido com que fizemos no código do menu. Lembre-se que quando clicar no botão de enviar, precisamos verificar qual modo o chat está (como host ou client), caso seja o host do chat clicando em enviar precisaremos adicionar a mensagem ao histórico do chat (já que o host não recebe uma cópia da própria mensagem), após isso vamos enviar a mensagem para todos os membros do chat. Se for um membro clicando em enviar, vamos usar a referência ao host que criamos la no load (server_peer) e enviaremos para ele a mensagem. Após isso tudo vamos definir o texto da caixa de texto como uma string vazia.
Como o SUIT não trabalha com instâncias, precisaremos de uma variável externa para armazenar o texto da caixa de texto, para isso vamos voltar ao método load e definiremos uma variável chamada text_input, ela será uma table com um campo text. Ficará assim:
Esqueci de mencionar que ao final do update precisamos chamar o método updateChat que foi definido la no load, o código final do método update será:
Chegamos na etapa final do tutorial, precisamo configurar o método draw para exibir somente as últimas linhas do chat (desde que caibam no espaço que temos). Primeiro vamos desenhar o modo do chat no topo da janela (como podem ver nas imagens la no inicio do tópico), desenharemos também a quantidade de membros conectados no chat caso seja o host e uma moldura para a área onde as linhas serão desenhadas. Essa parte é relativamente simples e o código ficará assim:
Mas não acabamos ainda, precisamos agora desenhar as linhas do chat, para isso usei uma lógica bem simples. Quando a quantidade de linhas que o histórico de mensagens possui for maior que a quantidade de linhas visíveis no chat, mudaremos o índice inicial a partir do qual acessaremos o histórico de mensagens. Para facilitar o entendimento, vamos declarar algumas variáveis locais dentro do método draw:
Onde:
max_size: É o tamanho da área que temos disponível para desenharmos as linhas do chat, corresponde a área da moldura que desenhamos acima.
line_height: Altura de cada linha.
max_lines: Máximo de linhas que podemos desenha na área disponível, o floor arredonda o número para baixo.
last_line: O índice da última linha do chat que será desenhada.
st_index: O índice da primeira linha que será desenhada.
Como podem ver, as variáveis last_line e st_index não possuem nenhum valor atribuido, isso por que precisamos verificar a quantidade de linhas disponíveis primeiro. Se a quantidade de linhas que o histórico possui for maior que a quantidade de linhas que podemos deixar visíveis então a primeira linha a ser desenhada terá o índice: quantidade de linhas do histórico - quantidade máxima de linhas visíveis - 1. E a variável last_line será igual ao valor do máximo de linhas visíveis (last_line é usado apenas no loop, para delimitar a quantidade de linhas a serem desenhadas).
Se a quantidade de linhas do histórico for menor que a quantidade de linhas que são visíveis então a variável st_index será igual a zero (já que não precisamos pular nenhuma linha) e a last_line será: quantidade de linhas do histórico - 1. Essa parte do código ficará assim:
Agora que já temos esses dados a disposição podemos começar a desenhar o histórico do chat, para isso basta usar um for e percorrer o a table chat_lines, lembrando que temos que percorrer apenas a quantidade de linhas visíveis. O código ficará da seguinte forma:
Lembrando a todos que em lua o primeiro índice de uma table é sempre 1 e não 0 como em outras linguagens. Por último iremos desenhar a quantidade de membros no chat:
Resultado final:
Download:
tutorial-chat.zip
Fontes:
https://love2d.org/wiki/lua-enet
http://leafo.net/lua-enet
https://github.com/vrld/suit
Ioriyagami190 gosta desta mensagem
Re: [love + enet] Criando um chat!
Que delícia de chat, em.
Postar os códigos usando imagens não é tão bom porque os links quebram com facilidade, tipo os tutoriais do DragonicK
+ 1 crédito
+ 1 crédito
Re: [love + enet] Criando um chat!
Tem esse detalhe e.e
E eu me inspirei nos tutoriais do @Dragonick auehuaehuaeh
Re: [love + enet] Criando um chat!
Muito bom man!. +1 cred.
_________________
using C# and import Python developer || Expert in Unity Engine IDE. || 2D pixel games.
Tópicos semelhantes
» Map | I Love RTP ♥
» Falling In Love Again- paródia Minecraft
» [Urbios Software] I Love Two +18(Contem conteúdo pornográfico)
» Criando RO
» Criando seu .exe
» Falling In Love Again- paródia Minecraft
» [Urbios Software] I Love Two +18(Contem conteúdo pornográfico)
» Criando RO
» Criando seu .exe
Página 1 de 1
Permissões neste sub-fórum
Não podes responder a tópicos