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.



Read more ...

Componente XNA de Lockpick

segunda-feira, 18 de outubro de 2010
Depois de MUITO tempo sem postar, retomo a vida e as atividades anunciando a conclusão de um componente de lockpicking...

Lockpicking já é um recurso batante conhecido e amplamente utilizado em diversos jogos. Quem já jogou Splinter Cell, Oblivion ,Fallout (sim, usamos os gráficos de fallout neste demo) e etc. , já se deparou com essa forma de ganhar acesso a uma área, ou item protegido por uma tranca.



LockpickComponent é um componente XNA (Duh!) e pode ser integrado em qualquer jogo facilmente.
A mecânica é bem conhecida. Deve-se encontrar um ponto na circunferência que permita girar a tranca até o fim (45graus) para destrancá-la.
Ao forçar a tranca entretanto, dependendo de quão longe da posição de abrir estiver o pino, mais ou menos dano é aplicado sobre este. O pino pode quebrar se receber muito dano. Caso hajam mais pinos disponíveis, o componente automaticamente posiciona um pino novo para que o jogador possa tentar novamente.

Baseado na dificuldade escolhida, o componente gera um intervalo aleatório onde o pino deve ser posicionado que a tranca possa ser aberta. Quanto mais próximo o pino estiver deste intervalo, mais a chave de fenda conseguirá girar a tranca. Assim, é possível girar a tranca até bem próximo do fim e perceber que o pino apesar de próximo, ainda não está na posição ideal.


O construtor recebe além da referência ao game, a dificuldade e o total de pinos disponíveis para abrir a tranca.
A assinatura do construtor é a seguinte:



public LockpickComponent(Game pGame, Difficulty pDificulty, int pPinCount)


Logo, uma tranca com dificuldade normal, tendo 3 pinos disponíveis ficaria assim:

LockpickComponent _lpc;

// Uma tranca de dificuldade Normal. O jogador terá 3 pinos para tentar abri-la
_lpc = new LockpickComponent(this, _Difficulty.NORMAL, 3);


Ao criar uma instância do componente, pode-se configurar alguns handlers para os 3 eventos gerados pelo componente:


// Evento ao abrir a tranca
_lpc.OnUnlock += onLockEvent;
// Evento ao desistir de abrir a tranca
_lpc.OnCancelPickinLock += onLockEvent;
// Evento ao quebrar todos os pinos disponíveis
_lpc.onBreakAllPins += onLockEvent;


Ao iniciar, o componente faz uma pequena animação, aproximando a “câmera”. O zoom máximo pode ser customizado através da propriedade Scale, sendo 1.0 o tamanho original. No demo o valor padrão (1.5) foi utilizado.


this.Components.Remove(_lpc);


No demo, associei todos os eventos ao mesmo método, que simplesmente habilita o menu e remove o componente deixando a tela principal visível novamente. Em um jogo real, provavelmente você queira associar o eventos a rotinas que atualizem o inventário do personagem para debitar o numero de pinos usados, , abra uma porta em caso de sucesso e obviamente, remova o componente no final.


O demo pode ser baixado aqui Lembrando que é preciso o xna runtime 3.1 para que o demo execute corretamente.

:)
Read more ...