Terminal de comandos embutido no jogo

quinta-feira, 21 de outubro de 2010



No jogo que estamos fazendo, precisei continuamente reinicar o jogo para forçar o reload do cenário, ou inserir DrawString()’s no método Draw() para acompanhar o estado de algmas variáveis. Percebi que já havia tido necessidades semelhantes neste e em outros projetos. Decidi então que era hora de incluir um terminal que me permitisse automatizar ações comuns durante o desenvolvimento.

Muitos jogos utilizam um terminal para permitir que os jogadores ativem cheats ou façam configurações rápidas no contexto do jogo. Não sei dizer qual foi o primeiro game a incorporar um terminal de comandos mas acredito que apareceram na época dos vovô Quake2(alguém sabe ?).

Seja para Forçar Spawn de um NPC, Mostrar/Ocultar geometria de colisão ,Carregar/Recarregar um level, Exibir estatísticas (como uso de memória e Frame Rate) ou qualquer coisa você precise, um terminal é uma maneira confortável de interagir com seu jogo com o máximo de controle.




Utilizando o TerminalComponent

O terminal foi implementado segundo o padrão de projeto Singleton,
A primeira coisa a fazer é inicializar o componente:
protected override void Initialize()
{
ConsoleComponent.InitializeConsole(this, 25);
}

Agora basta, obter uma instância e adicionar a lista de componentes :
ConsoleComponent _terminal;

protected override void Initialize()
{
ConsoleComponent.InitializeConsole(this, 25);
_terminal = ConsoleComponent.GetConsole()
Components.Add(_terminal);

}


Por padrão a tecla F2 é utilizada para exibir/ocultar o terminal. Caso seu jogo já utilize esta tecla (pouco provável :D), você pode redefini-la através da propriedade ShowHideKey. Por exemplo, se quisermos utilizar a letra “T” bastaria o seguinte:
protected override void Initialize()
{
ConsoleComponent.InitializeConsole(this, 25);
_terminal = ConsoleComponent.GetConsole()
Components.Add(_terminal);
_terminal.ShowHideKey = Keys.T;

}



Pronto! Nosso game já tem um terminal! Mas pra que seja realmente útil, devemos definir alguns comandos, pois são eles – os comandos – a razão de ser do terminal.

Adicionando comandos personalizados

Vamos definir um comando chamado exit cuja função é finalizar o programa.
A primeira coisa a fazer é criar um método que será executado quando o comando for chamado. Este método deve obedecer a assinatura do TerminalCommandDelegate, ou seja, retornar void e receber um string[] como parâmetro.

private void ExitCommand(string[] args)
{
   Exit();
}


Simples assim. Falta agora definir um comando no terminal que execute este método. Definir um comando é simplesmente passar para o método AddCommand o nome, uma descrição e um TerminalCommandDelegate (Delegate responsável pela execução do comando).
Para o nosso comando exit, ficaria assim:
protected override void Initialize()
{
ConsoleComponent.InitializeConsole(this, 25);
_terminal = ConsoleComponent.GetConsole()
Components.Add(_terminal);
_terminal.ShowHideKey = Keys.T;
_terminal.AddCommand("exit", "Terminates the program", ExitCommand);

}

Podemos definir também comandos mais complexos que recebam parâmetros, simplesmente verificando o array de strings passado para o método do comando.
Neste array, o primeiro elemento – índice zero – é sempre o nome do comando e os demais elementos, são os parâmetros passados para o comando.


Comandos pré-definidos

Além dos comandos que você definir para o terminal, existe sempre 2 comandos pré-definidos:
Comando help [comando]:
 Exibe a descrição de um comando, ou  se invocado sem parâmetros, exibe uma lista com todos os comandos definidos.

Comando clear:
 Limpa o terminal removendo todas as linhas de texto.


Chamar o comando help passando o nosso comando exit como parâmetro, receberíamos como resposta, a descrição que criamos para o comando.
help exit
Command Description: Terminates the program
> 


Considerações Finais

Cabem aqui algumas considerações:

1 - É conveniente impedir que o game processe input do teclado enquanto o terminal estiver ativo.  Fazemos isso simplesmente impedindo que  qualquer outro processamento aconteça enquanto o terminal estiver ativo
protected override void Update(GameTime gameTime)
{
   // Não processa input se o terminal estiver ativo
  if (_terminal.State == TerminalState.Active)
                return;

// processa input
HandleInput(gameTime);
}


2 – O console deve sempre ser o último componente a ser desenhado , por isso, se durante a execução do jogo precisar adicionar um componente, adicione o componete com Insert() em alguma posição que não seja a última ou
remova o terminal, adicione o componente e adicione o terminal novamente.
//Remove o terminal
Components.Remove(ConsoleComponent.GetConsole());
//Adiciona o novo componente
Components.Add(outroComponente);
//Adiciona novamente o terminal
Components.Add(ConsoleComponent.GetConsole());


3 - Como o terminal é um Singleton podemos obter uma referencia em qualquer parte do programa e utilizá-lo como saída padrão de erros, mensagens e exceções.
#if DEBUG
 ConsoleComponent.GetConsole().WriteLine(“Recarregando cenário”, Color.White);
#endif


Conclusão

É isso! O terminal será tão útil quanto os comandos que você definir para ele..
Deixe um comentário aqui ou mande um e-mail se este componente foi útil ao seu projeto ok ?

Baixe aqui o demo.



4 comentários:

  1. [erick@octopusteam ~]$ vi comment
    ~
    Um componente assim é realmente muito útil!
    Eu estou usando algo mais arcaico para fazer uma tarefa similar.
    Eu utilizo no event handler uma tecla para executar uma função específica.
    Pena que no mundo da Irrlicht ainda não existe um componente desses... =P
    ~
    P.S.:Ótimo post, cara. Terminais são sempre confiáveis.
    ~
    < comment [New] 12 L, 49C written >
    [erick@octopusteam ~]$ halt

    ResponderExcluir
  2. Maneirissimo, Marcinho !
    Nao vejo a hora de jogar a demo !

    Grande abraço e sucesso pro time todo !

    ResponderExcluir
  3. Muito bom Márcio!
    Se der tempo vou tentar colocar isso no meu TCC tb!
    Abraço!

    ResponderExcluir
  4. Me faz lembrar de warcraft 2 =D
    glitteringprizes, itsagooddaytodie... bons tempos
    To muito ansioso pra jogar esse jogo! Tenho certeza de que se sairá muito bem em qualquer concurso que entrar.

    ResponderExcluir