Ponto V!

Home Java Java 2D Criando uma interface ao usuário usando Nifty GUI
Cristiano de Medeiros Ferreira
Criando uma interface ao usuário usando Nifty GUIImprimir
Escrito por Cristiano de Medeiros Ferreira

Um dos problemas recorrentes ao desenvolver um jogo é criar uma interface ao usuário decente, geralmente se usa das mesmas ferramentas gráficas usadas para criar o ambiente, coisa que nem sempre dá certo, apresentando alguns problemas, entre eles a dificuldade de expandir e a falta de uma padronização.

Tendo isso em vista, nesse artigo pretendo mostrar como criar uma interface gráfica ao usuário (GUI no inglês) utilizando o NiftyGUI.

O que é o NiftyGUI?

O NiftyGUI, para quem não conhece, é uma biblioteca gráfica que ajuda na construção de interfaces interativas para jogos (podendo ser usada em aplicações gráficas sérias também), utilizando LWJGL para renderizar em OpenGL. A configuração para o GUI é armazenada em arquivos XML com pouca necessidade de código em Java (isso para layouts estáticos). Em resumo, Nifty ajuda a criar layouts, mostrar de uma forma bonita, com efeitos gráficos interativos.

Tudo que pode ser feito em XML pode também ser feito via código (permitindo criar layouts dinâmicos).

Um demo dos controles pode ser visto aqui: http://nifty-gui.sourceforge.net/webstart/nifty-examples-1.3.1.jnlp e aqui: http://nifty-gui.sourceforge.net/webstart/nifty-default-controls-examples-1.3.1.jnlp

Por que Nifty?

Porque sim!! Não, sério, meu contato com o Nifty decorreu do uso do motor gráfico JmonkeyEngine (Nifty foi feito inicialmente para esse motor). Após usar por algum tempo puder ver que o Nifty é bem sério no que faz como vocês verão nesse artigo.

Outra razão para usar Nifty é que até agora eu usava o próprio motor para desenhar as informações ao usuário... e isso é menos que ideal pois fica difícil mudar layout sem mudar o código fonte e com resultado final menos que satisfatório.

Com o Nifty é possível definir os elementos que aparecerão na tela, aplicando funcionalidades (um botão, que faz algo ao ser pressionado), mudar a tela baseado em algum evento do jogo (mudar a cor de algum painel baseado na “vida” do jogador, por exemplo), entre outros, limite é a imaginação do desenvolvedor.

Além de que criando-se arquivos XML separados é possível uma separação entre a camada gráfica e a camada da interface, permitindo que equipes diferentes trabalhem paralelamente no jogo/aplicação sem que um atrapalhe o outro. Isso sem falar em reuso e expansão dos componentes e abre a possibilidades dos próprios jogadores modificarem a interface gráfica, Moldando o jogo com os elementos disponíveis.

Onde posso usar o Nifty?

Nifty independe de renderers, podendo ser usado em qualquer sistema que você queira usar, sendo necessário definir algumas interfaces do Nifty com a seu motor gráfico especifico.

Mais detalhes sobre os renderers podem ser encontrado no site: http://sourceforge.net/apps/mediawiki/nifty-gui/index.php?title=Nifty_RenderDevices

Para esse artigo estarei usando o JMonkeyEngine 3.0 por ser, na minha opinião, a melhor motor 3D para Java disponível atualmente.

Configurando o ambiente

Para poder visualizar suas criações no Nifty você vai precisar dos seguintes programas:

Nesse caso, o JME 3.0 já vem com o Nifty integrado, o que facilita a configuração mas caso você deseje, o Nifty pode ser baixado de forma separada:

Recomendo baixar a última versão disponível (Nifty-1.3.1 atualmente). O núcleo é o nifty-1.3.1.jar, o restante dos arquivos adiciona alguma funcionalidades.

O JME 3.0 poderia ser usado como IDE caso o usuário deseje, mas para este artigo estarei usando o Eclipse.

Passo-a-Passo para configurar o ambiente

Eu costumo usar uma pasta raiz para instalar programas relacionados a desenvolvimento, por exemplo: D:\java, pois fica mais fácil achar depois o que foi instalado.

  1. Instale o Eclipse (e rode o mesmo para ver se está ok).
  2. Instale o JME 3.0 (de preferência dentro da pasta Java, eu costumo instalar dentro de :\java\lib)
  3. Crie um Java Project no Eclipse chamado NiftyDemo: clip_image002
  4. Relacione as bibliotecas necessárias pelo JMonkeyEngine para rodar o ambiente gráfico:
    1. Vá em Properties do Projeto (menu Project > Properties)
    2. Clique em Java Build Path e depois em Libraries
    3. Clique em Add Extenal Jars
    4. Vá na pasta de instalação do JME 3: \jmonkeyplatform \jmonkeyplatform\libs e adicione os seguintes arquivos:
      • jMonkeyEngine3.jar
      • lwjgl.jar
      • jME3-lwjgl-natives.jar
      • jME3-testdata.jar (opcional mas contém algumas texturas usadas como demonstração)
      • j-ogg-oggd.jar (sound player)
      • nifty.jar
      • nifty-style-black.jar
      • nifty-examples.jar
      • nifty-default-controls.jar
      • eventbus.jar
      • xmlpull-xpp3.jar
    5. O resultado final deve ser algo assim:

      clip_image004

Criando o “Hello Nifty”

Agora é hora de um Hello World. No Eclipse, no seu projeto, clique com botão direito em src, selecione New > Class e dê o nome de NiftyHelloWorld:

clip_image006

E cole o código abaixo:

import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.math.Vector3f;
import com.jme3.niftygui.NiftyJmeDisplay;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Box;
import de.lessvoid.nifty.Nifty;
import de.lessvoid.nifty.screen.Screen;
import de.lessvoid.nifty.screen.ScreenController;
import java.net.URL;

public class NiftyHelloWorld extends SimpleApplication implements ScreenController {

    private Nifty nifty;

    public static void main(String[] args){
        NiftyHelloWorld app = new NiftyHelloWorld();
        app.setPauseOnLostFocus(false);
        app.start();
    }

    public void simpleInitApp() {
        Box b = new Box(Vector3f.ZERO, 1, 1, 1);
        Geometry geom = new Geometry("Box", b);
        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        mat.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg"));
        geom.setMaterial(mat);
        rootNode.attachChild(geom);

        NiftyJmeDisplay niftyDisplay = new NiftyJmeDisplay(assetManager,
                                                          inputManager,
                                                          audioRenderer,
                                                          guiViewPort);
        nifty = niftyDisplay.getNifty();
        nifty.fromXml("Interface/Nifty/HelloJme.xml", "start", this);

        // attach the nifty display to the gui view port as a processor
        guiViewPort.addProcessor(niftyDisplay);

        // disable the fly cam
//        flyCam.setEnabled(false);
//        flyCam.setDragToRotate(true);
        inputManager.setCursorVisible(true);
    }

    public void bind(Nifty nifty, Screen screen) {
        System.out.println("bind( " + screen.getScreenId() + ")");
    }

    public void onStartScreen() {
        System.out.println("onStartScreen");
    }

    public void onEndScreen() {
        System.out.println("onEndScreen");
    }

    public void quit(){
        nifty.gotoScreen("end");
    }

}

Não se assuste ainda, isso é só para ver se está tudo ok.

Rode esse programa (botão direito em qualquer lugar do código, selecione Run As > Java Application e a seguinte tela aparece:

clip_image008

Isso é seleção da resolução da tela, tecle Enter ou clique OK.

Como estamos rodando um exemplo já presente no JME 3, o que aparece é uma mensagem padrão:

clip_image010

Ok, vamos ao código agora:

public class NiftyHelloWorld extends SimpleApplication implements ScreenController {

Definição da classe, estendendo SimpleApplication (classe base para criação de jogos no JME3.0) e implementando o ScreenController (usado pelo Nifty para comunicar com o seu programa)

private Nifty nifty;

Objeto nifty criado, é por esse objeto que temos acesso aos elementos da tela

public static void main(String[] args){
    
    NiftyHelloWorld app = new NiftyHelloWorld();

    app.setPauseOnLostFocus(false);

    app.start();

}
 

Inicialização padrão do JME.

public void simpleInitApp() {
        Box b = new Box(Vector3f.ZERO, 1, 1, 1);
        Geometry geom = new Geometry("Box", b);
        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        mat.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg"));
        geom.setMaterial(mat);
        rootNode.attachChild(geom);

Esse método “simpleInitApp()” é chamado pelo JME3 para inicializar os elementos da tela, esse código está criando uma Box (caixa) de dimensão 1 e aplicando uma textura em cima dela e anexando a mesma na tela.

NiftyJmeDisplay niftyDisplay = new NiftyJmeDisplay(assetManager,
                                                          inputManager,
                                                          audioRenderer,
                                                          guiViewPort);
        nifty = niftyDisplay.getNifty();       

Aqui temos uma classe de interface entre o Nifty e o JME (NiftyJmeDisplay) criando uma nova instância do Nifty e a mesma sendo atribuída à variável nifty.

nifty.fromXml("Interface/Nifty/HelloJme.xml", "start", this);

Essa parte é importante, pois é aqui que é carregado o arquivo de layout da tela (vamos ver ele daqui a pouco), “start” serve para indicar qual tela será chamada (pode haver mais de uma dentro do arquivo)

        // attach the nifty display to the gui view port as a processor
        guiViewPort.addProcessor(niftyDisplay);

        // disable the fly cam
//        flyCam.setEnabled(false);
//        flyCam.setDragToRotate(true);
        inputManager.setCursorVisible(true);

O nifty é adicionado a lista de “processors”. O nifty para rodar precisa ser chamado a cada frame para atualizar a tela e é para isso que serve o processor, para que o programador não tenha que fazer esse serviço.

public void bind(Nifty nifty, Screen screen) {
        System.out.println("bind( " + screen.getScreenId() + ")");
    }    

    public void onStartScreen() {
        System.out.println("onStartScreen");
    }

    public void onEndScreen() {
        System.out.println("onEndScreen");
    }

    public void quit(){
        nifty.gotoScreen("end");
    }

Bind: sempre que uma nova tela é carregada (o nifty pode ter várias), esse método é então chamado, é um método essencial e vamos ver sua aplicação em detalhes.

onStartScreen e onEndScreen são auto-explicativas: são métodos chamados sempre que uma nova tela é chamada ou finalizada. Eu não vejo muito uso, mas estão aí caso precise.

Método quit() é um método iterativo, ou seja, é chamado via interação no nifty (o usuário pode adicionar essas interações dentro do XML).

E agora, o arquivo helloJme.xml, responsável pela tela que apareceu:



  
    
      
        
        
          
          
          
        
        
      
    
  
  
  

Eis o que cada elemento quer dizer:

  • screen: identifica o nome da tela (a tela inicial é por padrão chamada “start”), controller identifica qual classe é responsável pelo tratamento dos eventos. Lembre-se que a screen é a base para qualquer layout, tudo que é visto na tela deve estar dentro de uma screen (e você pode alternar entre screens, como Menu principal, tela de jogo, opções, etc.
  • Layer: Uma screen pode ter várias “layers”, uma layer é uma camada da tela, você pode ter uma camada mostrando uma imagem, uma camada mostrando elementos em posições fixas e outra camada com janelas flutuantes, ou então camadas invisíveis, como um console que só aparece quando se tecla F1.
  • Panel: Painéis são componentes que podem conter outros componentes e ajudam no layout da tela (painéis podem conter outros painéis)
  • Text: é, como o nome diz, um texto que aparecerá na tela com base nos atributos definidos.
  • Interact: adiciona interação aos componentes, no caso desse xml: quando se clica no painel será chamado o método quit() do Controller.
  • Effect: define alguns efeitos que acontecem dependendo da ação do usuário.

Melhorando o código

No nosso exemplo de Hello World, o controller do Nifty estava integrado ao Jogo, isso não é uma boa prática, para resolver isso vamos separar em classes independentes:

  1. Crie um package chamado meujogo (src>new>Package)
  2. Crie uma nova classe chamada “MeuJogo” dentro desse package com o código abaixo:
    package meujogo;
    
    
    import meujogo.nifty.ControleTela;
    
    import com.jme3.app.SimpleApplication;
    import com.jme3.material.Material;
    import com.jme3.math.Vector3f;
    import com.jme3.niftygui.NiftyJmeDisplay;
    import com.jme3.scene.Geometry;
    import com.jme3.scene.shape.Box;
    
    import de.lessvoid.nifty.Nifty;
    
    public class MeuJogo extends SimpleApplication{
    
        private Nifty nifty;
    
        public static void main(String[] args){
            MeuJogo app = new MeuJogo();
            app.setPauseOnLostFocus(false);
            app.start();
        }
    
        public void simpleInitApp() {
            Box b = new Box(Vector3f.ZERO, 1, 1, 1);
            Geometry geom = new Geometry("Box", b);
            Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
            mat.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg"));
            geom.setMaterial(mat);
            rootNode.attachChild(geom);
    
            NiftyJmeDisplay niftyDisplay = new NiftyJmeDisplay(assetManager,
                                                              inputManager,
                                                              audioRenderer,
                                                              guiViewPort);
            nifty = niftyDisplay.getNifty();
            nifty.fromXml("meujogo/nifty/MeuNifty.xml", "start", new ControleTela(this));
    
            //nifty.setDebugOptionPanelColors(true);//Não se esqueça de desativar depois
            
            // attach the nifty display to the gui view port as a processor
            guiViewPort.addProcessor(niftyDisplay);
    
            // disable the fly cam
    //        flyCam.setEnabled(false);
    //        flyCam.setDragToRotate(true);
            inputManager.setCursorVisible(true);
        }
    }

    (Note que além de não contar mais com alguns métodos, o nifty.fromXml também mudou, indicando que o controller será outra classe, liberando essa classe do trabalho de lidar com os componentes da interface e se preocupando mais na lógica do jogo e em como desenhar os objetos)

  3. Crie um package chamado meujogo.nifty
  4. Crie uma nova classe chamada ControleTela com o código abaixo:
    package meujogo.nifty;
    
    import meujogo.MeuJogo;
    import de.lessvoid.nifty.Nifty;
    import de.lessvoid.nifty.screen.Screen;
    import de.lessvoid.nifty.screen.ScreenController;
    
    public class ControleTela implements ScreenController {
        MeuJogo jogo; //Usado para acessar os dados do jogo
        Nifty nifty;  //Usado para interagir com a tela
        
        public ControleTela (MeuJogo jogo){
            this.jogo=jogo;
        }
    
    
        public void bind(Nifty nifty, Screen screen) {
            System.out.println("bind( " + screen.getScreenId() + ")");
            this.nifty=nifty;
        }    
    
        public void onStartScreen() {
            System.out.println("onStartScreen");
        }
    
        public void onEndScreen() {
            System.out.println("onEndScreen");
        }
    
        public void quit(){
            nifty.gotoScreen("end");
        }
    }
  5. O código está similar ao que já encontramos antes, só que está mais limpo pois a lógica do jogo está em outra classe.

  6. Crie um package dentro de nifty que terá os xmls usados pela aplicação: “meujogo.nifty.xml

  7. adicione um arquivo xml chamado NiftyLayout.xml em meujogo.nifty.xml (new > File) com o código abaixo:

    <?xml version="1.0" encoding="UTF-8"?>
    < nifty xmlns="http://nifty-gui.sourceforge.net/nifty-1.3.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://nifty-gui.sourceforge.net/nifty-1.3.xsd http://nifty-gui.sourceforge.net/nifty-1.3.xsd">
       <!-- +++++++++++++++++++++++++++++++++++++++ -->
       <!-- Carrega estilos e controles que serão usados por essa tela -->
       <!-- +++++++++++++++++++++++++++++++++++++++ -->
       <useStyles filename="console/custom-console-style.xml" />
       <useStyles filename="nifty-default-styles.xml" />
       <useControls filename="nifty-default-controls.xml" />
       <!--  recomendo que todos os seus XML tenham pelo menos o código daqui para cima -->
      
       <!-- +++++++++++++++++++++++++++++++++++++++ -->
       <!--  tela start -->
       <!-- +++++++++++++++++++++++++++++++++++++++ -->
       <screen id="start" controller="meujogo.nifty.MeuController" defaultFocusElement="name">
           <layer id="layer" backgroundColor="#003f" childLayout="vertical">
                 <text id="text1" style="nifty-label" height="15%" width="75%"
                       backgroundColor="#f60f" text="TextHAlign: center, TextVAlign: top"
                       color="#000f" textHAlign="center" textVAlign="top"/>
                 <text id="text2" style="nifty-label" height="15%" width="75%"
                       backgroundColor="#f80f" text="TextHAlign: center, TextVAlign: center"
                       color="#000f" textHAlign="center" textVAlign="center"/>
                 <text id="text3" style="nifty-label" height="15%" width="75%"
                       backgroundColor="#fa0f" text="TextHAlign: center, TextVAlign: bottom"
                       color="#000f" textHAlign="center" textVAlign="bottom"/>
                 <text id="text4" style="nifty-label" height="15%" width="75%"
                       backgroundColor="#fc0f" text="TextHAlign: left, TextVAlign: center"
                       color="#000f" textHAlign="left" textVAlign="center"/>
                 <text id="text5" style="nifty-label" height="15%" width="75%"
                       backgroundColor="#fe0f" text="TextHAlign: center, TextVAlign: center"
                       color="#000f" textHAlign="center" textVAlign="center"/>
                 <text id="text6" style="nifty-label" height="15%" width="75%"
                       backgroundColor="#ff2f" text="TextHAlign: right, TextVAlign: center"
                       color="#000f" textHAlign="right" textVAlign="center"/>
            </layer>
        </screen>
    </nifty>

Vou aproveitar esse código XML para mostrar o alinhamento do texto.

Para rodar, basta ir em MeuJogo e dar um run As / Java Application. O resultado então será esse:

clip_image012

Essa tela dá uma boa ideia de como funciona o TextAlign, servindo como referência.

Esse foi o NiftyLayout.xml, para alterar o XML a ser mostrado basta ir em MeuJogo.java e mudar o arquivo XML para outro valor.

Existem 2 tipos de Controllers:

  • ScreenController: É o controlador da tela, é ele quem deve controlar o fluxo de, centralizando a comunicação entre os controles visuais e o jogo.
  • Controller: É o tipo de controller usado pelos componentes da tela, teoricamente não é necessário, mas é muito melhor separar os componentes da tela em sub-componentes, como veremos mais a frente, dessa forma podemos ter um controller para gerenciar o status do usuário, um controller para gerenciar o status do alvo/inimigo, um controller para gerenciar o mini mapa e assim por diante. Dá para fazer tudo isso no ScreenController mas fica muito bagunçado e após alguns componentes fica impraticável se localizar no código (trust me)…

Padronizando a definição do nome dos arquivos

Para evitar confusão entre controles XML e o controller do mesmo, a partir daqui vou tratar o componente gráfico como Control e o código que gerencia esse controle de Controller, ou seja, caso eu criasse um controle que mostrasse o progresso de 0 a 100%, como a que segue abaixo:

clip_image013

Eu criaria os arquivos:

  • ProgressBarControl.xml
  • ProgressBarController.java

Algo simples mas que ajuda a não se perder no código.

Criando Formulários interativos

Avançando um pouco, que tal criar uma tela que peça alguns dados do usuário, calcule algo interativamente e mostre o resultado?

Algo bem aula de programação básica mesmo, com entrada de dados, processamento e saída do resultado. Vou desenhar uma tela básica aqui para guiar o trabalho:

image

Para ter esse resultado, siga esses passos:

  1. Salve o código abaixo em “meujogo.nifty.xml” em um arquivo chamado NiftyForm.xml:
    <?xml version="1.0" encoding="UTF-8"?>
    < nifty xmlns="http://nifty-gui.sourceforge.net/nifty-1.3.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://nifty-gui.sourceforge.net/nifty-1.3.xsd http://nifty-gui.sourceforge.net/nifty-1.3.xsd">
       < !-- +++++++++++++++++++++++++++++++++++++++ -->
       < !-- load default styles and controls -->
       < !-- +++++++++++++++++++++++++++++++++++++++ -->
       < useStyles filename="console/custom-console-style.xml" />
       < useStyles filename="nifty-default-styles.xml" />
       < useControls filename="nifty-default-controls.xml" />
       < !--  recomendo que todos os seus XML tenham pelo menos o código daqui para cima -->
      
       <!-- +++++++++++++++++++++++++++++++++++++++ -->
       <!-- start screen -->
       <!-- +++++++++++++++++++++++++++++++++++++++ -->
       <screen id="start" controller="meujogo.nifty.ControleTela" >
           <layer id="formLayer" childLayout="vertical" controller="meujogo.nifty.FormularioController">
                < panel id="dialog" style="nifty-panel" padding="18px,28px,28px,16px" childLayout="center" width="50%" height="38%" align="center" valign="center">
                      < effect>
                           < onStartScreen name="move" timeType="exp" factor="3.5" direction="top" mode="in" length="500" inherit="true"/>
                           < onEndScreen name="move" timeType="exp" factor="3.5" direction="bottom" mode="out" length="500" inherit="true"/>
                      < /effect>
                      < panel childLayout="vertical" align="center" valign="center">
                            < panel id="namePanel" childLayout="horizontal">
                                  < text style="nifty-label" text="Nome: " width="150px" align="left" textVAlign="center" textHAlign="left" />
                                  < control id="nome" name="textfield" text="(Seu nome)">< /control>
                            < /panel>
              
                            < panel childLayout="horizontal" height="8px" />
                            < panel childLayout="horizontal">
                                  < text style="nifty-label" text="Sua idade" width="150px" align="left" textVAlign="center" textHAlign="left"/>
                                  < control id="idade" name="textfield" maxLength="3" text="30"/>
                            < /panel>
              
                            < panel childLayout="horizontal" height="8px" />
                                  < panel childLayout="horizontal">
                                  < control id="calcula" name="button" label="Calcula" align="right">
                                        < interact onClick="calcula()" />
                                  < /control>
                            < /panel>
                
                            < panel childLayout="horizontal" height="8px" />
                            < panel childLayout="horizontal">
                                  < control id="resultado" name="textfield"  />
                            < /panel>
                            < panel childLayout="horizontal" height="*" />
                     < /panel>
              < /panel>
         < /layer>
       < /screen>
    < /nifty>

    Pontos de interesse:

    • style=”nifty-panel”: Para quem conhece CSS e HTML sabe que isso quer dizer que será usado um estilo para desenhar o componente na tela, a forma de uso é a mesma.

    Para criar um estilo bastaria inserir o seguinte código no XML (ou importar via useStyles de um arquivo separado):

    O estilo pode ser aplicado a quase qualquer componente e pode ter como base outro estilo (button-font nesse caso)

    • effect: ao iniciar e ao fechar a tela (caso fosse o caso) é aplicado um efeito de tela, no caso movendo para cima ou baixo (start e end, respectivamente). timeType=exp implica passagem de tempo exponencial, ou seja, o movimento fica mais rápido com tempo até o final do mesmo (pode ser exp (exponencial), linear (linear) ou infinite (infinito) ).
    • Controller: nesse casso, quando a camada “formLayer” for inicializada será instanciado o objeto definido no parâmetro controller: controller="meujogo.nifty.FormularioController">
  2. Altere o arquivo ControleTela.java para ficar assim:
    package meujogo.nifty;
    
    import meujogo.MeuJogo;
    import de.lessvoid.nifty.Nifty;
    import de.lessvoid.nifty.elements.Element;
    import de.lessvoid.nifty.screen.Screen;
    import de.lessvoid.nifty.screen.ScreenController;
    
    public class ControleTela implements ScreenController {
        MeuJogo jogo; //Usado para acessar os dados do jogo
        Nifty nifty;  //Usado para interagir com a tela
        
        
        
          private Element formLayer; //Esse objeto conterá a referência a layer da tela referente ao formulário
          private FormularioController formController; //Similarmente, esse objeto fará referência ao objeto controle
    
        
        public ControleTela(MeuJogo jogo){
            this.jogo=jogo;
        }
    
    
        public void bind(Nifty nifty, Screen screen) {
            System.out.println("bind( " + screen.getScreenId() + ")");
            this.nifty=nifty;
            
            //Aqui eu busco na tela a camada de Formulário
            formLayer= screen.findElementByName("formLayer");
            if (formLayer!=null){ 
                formController=formLayer.getControl(FormularioController.class);
                formController.setNome("Qual seu nome??");
                formController.setControleTela(this);
                System.out.println("FormController Encontrado!");
            }
    
        }    
    
        public void onStartScreen() {
            System.out.println("onStartScreen");
        }
    
        public void onEndScreen() {
            System.out.println("onEndScreen");
        }
    
        public void quit(){
            nifty.gotoScreen("end");
        }
    
    }
  3. Crie esse novo arquivo FormularioController.java em meujogo.nifty:
    package meujogo.nifty;
    
    import java.util.Properties;
    
    import de.lessvoid.nifty.Nifty;
    import de.lessvoid.nifty.controls.Controller;
    import de.lessvoid.nifty.controls.TextField;
    import de.lessvoid.nifty.elements.Element;
    import de.lessvoid.nifty.input.NiftyInputEvent;
    import de.lessvoid.nifty.screen.Screen;
    import de.lessvoid.xml.xpp3.Attributes;
    
    /*
     * Esse controle é responsável pelo tratamento do formulário, todos os eventos referentes ao 
     * formulário devem passar por aqui.
     * Note: você pode chamar métodos dessa classe através do ControleTela.
     * Exemplo: formController.setNome("Novo Nome");
     */
    public class FormularioController implements Controller {
        Screen screen;
        Element element;
        
        //Referencia a objetos da tela
        TextField elNome,elIdade;  
        TextField elResultado;
        ControleTela controleTela; //Caso seja necessário chamar algum método do controle 
                                   //principal já temos a referência
        
        
        @Override
        public void bind(Nifty nifty, Screen screen, Element element, Properties arg3,
                Attributes arg4) {
            this.screen=screen;
            this.element=element;
            
            elNome= element.findNiftyControl("nome", TextField.class);
            elIdade= element.findNiftyControl("idade", TextField.class);
            elResultado= element.findNiftyControl("resultado", TextField.class);
    
        }
        
        /*
         * Método chamado quando o usuário clica no botão "Calcula" 
         */
        public void calcula(){
            elResultado.setText(elNome.getText()+", você tem "+Integer.parseInt(elIdade.getText())*365+" dias.");
        }
        
        public void setNome(String s){
            elNome.setText(s);
        }
        
        @Override
        public void init(Properties arg0, Attributes arg1) {
            // TODO Auto-generated method stub
            
        }
    
    
    
        
        @Override
        public boolean inputEvent(NiftyInputEvent arg0) {
            // TODO Auto-generated method stub
            return false;
        }
    
        @Override
        public void onFocus(boolean arg0) {
            // TODO Auto-generated method stub
            
        }
    
        @Override
        public void onStartScreen() {
            // TODO Auto-generated method stub
            
        }
    
    
        public ControleTela getControleTela() {
            return controleTela;
        }
    
    
        public void setControleTela(ControleTela controleTela) {
            this.controleTela = controleTela;
        }
    }
  4. Rode o programa MeuJogo agora.

    O resultado final ficou assim:

    clip_image015

    Pontos de interesse:

    • Definição dos elementos da tela, defini 3 elementos como sendo do tipo textfield.
    • Atribuição do objeto da tela, quando eu digo “elNome= element.findNiftyControl("nome", TextField.class);” eu estou dizendo:

    ElNome receberá o resultado da procura pelo controle “nome” no elemento onde o controle foi inicializado, instanciado em uma classe TextField.

    • Método calcula(): faz o cálculo e joga no componente, simples.
    • setNome(novoNome): Muda o texto dentro do input nome.
    • Objeto controleTela: pode ser usado como comunicação entre o controle do formulário (ou qualquer outro tipo de controle) e o controle principal que por sua vez se comunicaria com o jogo/aplicação.

    Um ponto interessante é notar que o atributo “name” do objeto control é quem define o tipo de objeto, exemplos:

    <control id="idade" name="textfield" maxLength="3" text="30"/>
    <control id="calcula" name="button" label="Calcula" align="right">
    <control id="overview" name="window" title="Overview" width="460px" height="240px" y="30%" x="60%" childLayout="vertical"> 

    Entre outros.

Criando um HUD Simples

OK, hora de fazer algo interessante, a ideia é fazer um HUD que mostre:

  • Dados do jogador (HP, velocidade, etc), mostrando alguma imagem que representa o jogador
  • Dados do alvo selecionado (HP, distância, etc)
  • Overview com objetos, permitindo alternar entre filtros pré-determinados (todos objetos, inimigos, amigos, etc)
  • Overlay nos objetos, mostrando o nome do objeto na posição do mesmo e permitindo selecionar objetos.

E, para padronizar o serviço, permitindo a reutilização do código em jogos/aplicações diferentes, vamos criar uma biblioteca separada do nosso projeto atual.

Para isso, criei um novo projeto chamado NiftyLib com a seguinte estrutura:

clip_image017

O que coloquei em cada pacote:

  • Complemento: Extrai algumas classes usadas pelo meu projeto, como Vector3.java e Formatacao.java. Não é preciso usar elas, você pode usar outras classes... um jogo 2D não teria porque armazenar dados em estruturas 3D, por exemplo.
  • Niftlib.components.* conterá os controles criados pelo usuário (arquivos referentes ao controle deve ficar dentro de seu pacote, como visto abaixo:clip_image019
  • Niftylib.estilos contém arquivos com styles (estilos), ou seja, formatação e definições visuais (fonte, cor, tamanho da borda, etc)
  • Niftylib.interfaces: Interface de uso genérico, o seu jogo deve implementar essas interfaces para poder haver comunicação entre controles.
  • Niftylib.propriedades: É um conceito de MVC, onde para poder mostrar na tela alguma informação é preciso antes definir como você pode recuperar a informação (e notificar quando a informação for atualizada), pensando nisso eu criei um arquivo Enum (enumeração) contendo: PROP_NAME, PROP_DISTANCE, PROP_POSITION, PROP_ORIENTATION, etc (não sendo limitado a isso). O componente Overview usa muito isso, onde a informação da distância é atualizado sempre que a mesma muda.
Baixem o código anexado com o projeto já pronto

Não vou colocar todo o código aqui (afinal é muita coisa), ao invés disso peço para que baixem o código anexado (clicando aqui ou no botão no final do artigo) e vou explicando os pontos principais. Adicionei vários comentários no código falando o porque de fazer do jeito que eu fiz.

Eu não inclui o JME3 no download por ser muito grande: basta criar um Project no Eclipse chamado “biblioteca” e colocar os jars do Jmonkeyengine ali.

Note que são só recomendações, um ponto inicial para quem quiser desenvolver, caso deseje você pode mudar toda essa estrutura e fazer do jeito que quiser, ou simplesmente ignorar tudo. Pois ao meu ver, o código pode melhorar muito ainda...

Interfaces

Todas as interfaces apresentadas aqui possuem uma implementação no código disponibilizado para download (como exemplo):

IJogo.java:

public interface IJogo {
    IObjeto getPlayer(); //Retorna o objeto que corresponde ao jogador 
    IObjeto getSelection(); //Retorna a seleção
    void setSelection(IObjeto objeto); //Altera a seleção para o objeto de parâmetro
    void sendConsoleCommand(String command); //Envia um comando de debug pelo console
    Vector3 getScreenCoordinates(Vector3 pos3d); //Retorna a posição na tela do parametro.
    Vector3 getPlayerAbsolutePosition(); //Retorna a posição do jogador, como vista pela engine do jogo
    void notifyPropertyChanged(IObjeto objeto,Enum property);//Usado para notificar o HUD quando uma propriedade de algum objeto mudar
}

IJogo representa a sua classe principal, teoricamente só há uma instância desse objeto e é nele onde fica a lógica do jogo e a atualização e desenho das entidades do jogo.

IObjeto.java:

public interface IObjeto {

    String getName();          // Pega o nome do objeto
    void setName(String name); // Define o nome do objeto 
    Vector3 getCoord(); //Retorna a posição do objeto visual
    Vector3 getAbsolutePosition(); //Retorna a posição absoluta do objeto, normalmente é igual a getCoord()
    void setCoord(double x,double y,double z);//atualiza a posição do objeto visual 
    void update();      //Método chamado a cada frame
    Vector3 getVelocity();
    Vector3 getOrientation();
    
    void activateControl(String group);  //envia um comando para a entidade (exemplo: TURN_LEFT)
    
    /*
     * Devo dizer que não é a melhor solução, mas esses 3 métodos abaixo
     * resolvem a maioria dos problemas
     */
    String getPropertyAsText(Enum property); //Devolve uma propriedade como texto
    Vector3 getPropertyAsVector3(Enum property);
    double getPropertyAsDouble(Enum property);
    
}

Cada entidade do jogo deve implementar essa interface IObjeto.

IControleTela:

public interface IControleTela extends ScreenController{
    
    IJogo getJogo(); //Retorna o objeto Jogo
    
    /*
     * Registra os objetos 
     * Lembre-se: registre apenas os objetos visíveis
     */
    void registerObjetoJogo(IObjeto item);//Deve ser chamado cada objeto visivel na tela (objeto de jogo) (os principais pelo menos)
    void unregisterObjetoJogo(IObjeto item);//quando um objeto nao estiver mais visivel esse metodo deve ser chamado 
    boolean sendConsoleCommand(String command);
    void printConsole(String msg); //Caso exista console, escreve uma mensagem na tela
    void registerController(IControle controle);
    void update();
    void addOverviewField(String titulo,Enum field); //Adiciona um campo que será exibido no controle overview
    void propertyChanged(IObjeto objeto,Enum property);//Usado para notificar o HUD quando uma propriedade de algum objeto mudar
    void initialize(); //Alguns objetos precisam ser inicializados...
    void updateSelection(IObjeto objeto); //chamado quando o objeto selecionado é alterado
}

Controladora da Tela, é o coração do HUD.

public interface IControle extends Controller {

    public void update(IJogo jogo); //esse método deve ser chamado pelo controle da tela como forma de comunicação entre o jogo e o componente
    public void setControleTela(IControleTela controleTela);
    void registerObjetoJogo(IObjeto objeto); //Registra uma entidade (caso o controle necessite)
    void unregisterObjetoJogo(IObjeto objeto);
}

Todos os Controllers devem implementar essa interface idealmente. Não é essencial, você pode criar um controle genérico que não precise ser integrado à interface, ProgressBar é um exemplo, não há lógica de jogo nele, é apenas uma barra de progresso. Vai do gosto pessoal.

Exemplos de Componentes

Criei alguns componentes que se referem ao que eu quero no meu projeto, como vocês podem ver é muito fácil fazer um componente no nifty e adicionar interação/dinamicidade ao mesmo. Alguém que vai fazer um jogo de esportes não está interessado em mostrar quantos Hitpoints o oponente tem...

Os componentes não estão finalizados também, a ideia é mostrar como fazer e não entregar tudo pronto.

Overview

clip_image021

Com esse componente temos uma relação de objetos do jogo, mostrando nome, distancia e velocidade (campos dinâmicos, podem ser outros campos).

Esse é o componentes menos finalizado por assim dizer, tem alguns bugs como o texto ultrapassando o tamanho da célula e também não implementei a ordenação e o filtro. E por algum motivo ele deixa a aplicação bem lenta, quando ele está na tela a aplicação fica em uns 10-20 fps e quando sai vai para 250... devo acabar tendo que reescrever esse componente ou abandonar de vez (então, se alguém ver alguma solução, favor me avisar).

Overlay

clip_image023

Esse componente cria rótulos flutuantes nos objetos, podendo ser ampliado para outros usos, por exemplo, mostrando uma imagem (avatar), mostrando a quantidade HP entre outros (imaginação é o limite). Quando o usuário clica no nome é disparado então um evento para o jogo falando que a seleção mudou (o jogo então sabe onde você clicou).

TargetInfo

clip_image025

A esquerda temos os dados do jogador e à direita os dados do alvo selecionado (quando não tiver nenhum então nada aparece). Outra coisa é que o comportamento do controle referente ao jogador é diferente do comportamento referente ao alvo selecionado: a 3a barra está ausente e no seu lugar aparece a distância.

Console

clip_image027

Componente padrão do Nifty mas com algumas adições no controller e integrado ao framework.

Conclusões

Espero que tenham gostado desse artigo e do que expus aqui, após ter escrito esse artigo pude ver que o principal aprendizado foi a organização do meu projeto em si, criando interfaces para padronizar o acesso às informações, uma classe para centralizar as propriedades da entidade e a facilidade de reuso do código, criar os componentes visuais é apenas um resultado disso.

Bom, próximo passo é usar esse projeto como base para meu próximo artigo, onde pretendo criar um jogo em MVC (algo prometi fazer mas por falta de tempo acabou não rolando).

Em casos de dúvidas e/ou sugestões, favor entrar em contato comigo ou postar um comentário aqui, vou fazer o possível para responder.

Abraços a todos (e não esqueçam de acessar o código, a maior parte do artigo está lá nos comentários).

Para baixar o código fonte do exemplo final, clique no botão abaixo:

Links úteis:


      Comentários (3)
      • Michel Montenegro  - Parallax
        avatar

        Sinto que este artigo foi uma consequencia dos debates gerados em torno da engine Parallax e suas tecnologias. B)

        Para quem não sabe o projeto Parallax usa NiftyGui em sua interface, abaixo o link para o site do projeto.
        http://www.einformacao.com.br/parallax/

        NiftyGui é um ambiente muito poderoso para a criação da GUI/Widgets do jogo e vem constantemente sendo melhorada e evoluida.

      • Vinícius Godoy de Mendonça
        avatar

        Não, esse artigo não teve nada a ver com a Parallax. O autor usa essa engine para fazer a GUI no projeto dele, que não usa a Parallax.

      • Michel Montenegro
        avatar

        Uma pena, pensei pois ele fez bem depois de diversas conversas sobre essas tecnologias que até então não eram divulgadas antes do Parallax. (rsrsr...).

        Legal se ele usa a GUI, significa que ele usa Java em algum projeto, fiquei curioso agora, sabe algo sobre o projeto dele?

        Viny sabe algum material ou local que tenha informações que possam enriquecer o assunto ( Jogos digitais e educação ), minha monografia (pós) e sobre jogos e sociedade (isso inclui esse pensamento em relação a educação, Gamification, contribuição tecnologica, etc.) e estou quase terminando.

      Escrever um comentário
      Your Contact Details:
      Gravatar enabled
      Comentário:
      [b] [i] [u] [url] [quote] [code] [img]   
      :angry::0:confused::cheer:B):evil::silly::dry::lol::kiss::D:pinch::(:shock:
      :X:side::):P:unsure::woohoo::huh::whistle:;):S:!::?::idea::arrow:
      Security
      Por favor coloque o código anti-spam que você lê na imagem.
      LAST_UPDATED2  

      Busca

      Linguagens

      Twitter