|
Neste artigo veremos como criar uma barra de energia (saúde, força, etc.) de modo que possamos reutiliza-la em jogos clássicos como: jogos de luta, RTS e RPG. O código deste tutorial foi criado e testado no XNA 3.1, ou seja, qualquer mudança de versão pode acarretar ou não em reajustes no código.
Introdução
A barra de energia é a maneira talvez mais popular de exibir uma forma de medida para saber se algum personagem está vivo ou se está próximo da morte. Esta forma visual de exibir a vida dos personagens esta presente no universo dos jogos praticamente desde o momento em que surgiram. O seu conceito é muito simples: a tela contém uma barra em algum lugar, e na medida em que o objeto controlado pelo jogador (personagem, carro, nave, etc.) é acertado (golpeado, entra em colisão, etc.) essa barra começa a reduzir até chegar ao final, ou seja, o jogador perde (vida, crédito, round, jogo, etc.)
Este tutorial cria então uma classe de barra de energia horizontal capaz de representar alguns exemplos como:
- Jogos de luta medindo impactos, golpes especiais, capacidades de defesa e até mesmo a barra de recuperação da versão Marvel Super Heroes vs Street Fighter
- Jogos de RPG afim de, medir o HP (Hit Points) de um personagem, as magias como visto nas barras de MP (Magic Points), ou ver o quanto você esta causando dano num oponente, ou quando você poderá atacar e quando você subirá de level.
- Jogos de RTS onde a energia do personagem o acompanha pela tela para você poder visualizar melhor quem esta morrendo ou quem tem mais vida, o nível de uma determinada construção entre outras representações dentro deste tipo de jogo.
Uma característica dessas barras de energia é que dependendo do jogo você pode recuperar uma determinada quantidade pegando algum item ou até mesmo aumentar o total de energia (jogos de RPG quando você sobe de nível).
Projeto da classe EnergyBar (Horizontal)
Vamos começar então a pensar em como seria uma classe para representar essas variações de barras de energia na horizontal. Veja o desenho abaixo para tentarmos pensar um pouco.
- Precisamos de imagem para representar a barra de energia e outra para representar a caixa que envolve a barra de energia como vocês podem ver na figura acima
- Tanto a caixa quanto a barra de energia tem posições na tela.
- Se for um jogo de luta a barra de energia de um jogador reduz para a direita e do outro reduz para esquerda, então precisaremos de uma variável booleana que indique se um flip acontece na redução de energia.
- A barra de energia pode ser da cor que você quiser e até mesmo ter variações de cores conforme a porcentagem de vida (Color.Lerp). Exemplo: energia cheia amarelo e conforme vai ficando vazia ela tende para o vermelho.
- Se for um jogo do tipo Marvel Super Heroes vs Street Fighter, temos uma barra de recuperação que aparece após o personagem tomar um golpe, esta barra tem a mesma imagem da barra de energia, porém ela tem um tempo de redução e geralmente uma cor vermelha. Ela também tem uma posição na tela que é a mesma da barra de energia também.
- Caso seja um jogo de RPG, ou RTS ou algum outro jogo que precisa deslocar a barra de lugar para acompanhar um personagem você vai provavelmente precisar de um método que movimente esta barra (lembrando que você deve movimentar a barra e a caixa dela ao mesmo tempo).
- Você precisa de meios para diminuir ou aumentar a energia e também modificar o valor máximo de energia caso seja um jogo de RPG, ou você queira personagens com quantidade de energias diferentes.
Essas são as considerações iniciais que nossa classe deve ter, vamos ver como ficaria o diagrama de classes que representaria todas essas condições.
Após analisar nossa classe, vamos então colocar a mão na massa, opa, no teclado é claro e começar a programar.
Codificando nossa classe
1º Passo: Crie uma classe para nossa barra de energia, no meu caso eu a chamarei de “EnergyBar”
namespace Utils.Componentes.EnergyBar
{
///
/// Classe para barras de energia, saúde, vida etc... porém somente horizontal
///
public class EnergyBar
{
}
}
2º Passo: Logo após criaremos as variáveis conforme o diagrama de classes que definimos na fase de projeto. Perceba que temos variáveis de cor para energia vazia e energia cheia, também temos variáveis de posições e booleanos que nos dizem se a energia diminui para um lado ou outro.
#region [ Fields ] ////// Imagem da caixa por trás da barra de energia /// private Texture2D boxTexture = null; ////// Imagem da barra de energia /// private Texture2D energyTexture = null; ////// Posição da caixa /// private Vector2 boxPosition; ////// Posição da barra de energia /// private Vector2 energyPosition; ////// Nível máximo de energia /// public float MaxEnergy { get { return this.maxEnergy; } set { this.maxEnergy = value; } } private float maxEnergy = 100.0f; ////// Energia /// public float Energy { get { return this.energy; } set { this.energy = MathHelper.Clamp(value, 0, maxEnergy); } } private float energy = 100.0f; ////// Cor da barra de energia quando esta vazia /// private Color emptyEnergyColor = Color.White; ////// Cor da barra de energia quando esta cheia /// private Color fullEnergyColor = Color.White; ////// Indica se deseja reduzir a energia para o outro lado (padrão = esquerda, flip = direita) /// private bool flipEnergyReduce = false; ////// Indica se existira barra de recuperação de energia /// private bool recoveryBar = false; ////// Quantidade de recuperação atual /// private float recovery; ////// Fator de descida da recuperação /// private float recoveryFactor; ////// Cor da barra de recuperação /// private Color recoveryColor = Color.White; #endregion
3º Passo: Vamos criar agora o construtor dessa classe, mas vale lembrar que a classe deve permitir construir de diversas formas a barra de energia, para isso, vamos fazer 6 sobrecargas do nosso construtor. Essas sobrecargas servem como facilitadores para nós quando vamos instanciar nosso objeto de maneira simples ou até mesmo mais complexa. Vejamos como isso ficaria.
#region [ Constructor ]
///
/// Contrutor da barra de energia
///
/// Imagem da caixa que envolve a energia
/// Imagem da barra de energia
/// Posição da caixa que envolve a energia
/// Posição da barra de energia
public EnergyBar(Texture2D boxTexture, Texture2D energyTexture, Vector2 boxPosition, Vector2 energyPosition)
{
this.boxTexture = boxTexture;
this.energyTexture = energyTexture;
this.boxPosition = boxPosition;
this.energyPosition = energyPosition;
}
///
/// Contrutor da barra de energia
///
/// Imagem da caixa que envolve a energia
/// Imagem da barra de energia
/// Posição da caixa que envolve a energia
/// Posição da barra de energia
/// Cor da barra de energia
public EnergyBar(Texture2D boxTexture, Texture2D energyTexture, Vector2 boxPosition, Vector2 energyPosition,
Color color)
{
this.boxTexture = boxTexture;
this.energyTexture = energyTexture;
this.boxPosition = boxPosition;
this.energyPosition = energyPosition;
this.emptyEnergyColor = color;
this.fullEnergyColor = color;
}
///
/// Contrutor da barra de energia
///
/// Imagem da caixa que envolve a energia
/// Imagem da barra de energia
/// Posição da caixa que envolve a energia
/// Posição da barra de energia
/// Cor da barra de energia para quando ela estiver vazia
/// Cor da barra de energia para quando ela estiver cheia
public EnergyBar(Texture2D boxTexture, Texture2D energyTexture, Vector2 boxPosition, Vector2 energyPosition,
Color emptyEnergyColor, Color fullEnergyColor)
{
this.boxTexture = boxTexture;
this.energyTexture = energyTexture;
this.boxPosition = boxPosition;
this.energyPosition = energyPosition;
this.emptyEnergyColor = emptyEnergyColor;
this.fullEnergyColor = fullEnergyColor;
}
///
/// Contrutor da barra de energia
///
/// Imagem da caixa que envolve a energia
/// Imagem da barra de energia
/// Posição da caixa que envolve a energia
/// Posição da barra de energia
/// Cor da barra de energia para quando ela estiver vazia
/// Cor da barra de energia para quando ela estiver cheia
/// Reduz a barra de energia para a direita
public EnergyBar(Texture2D boxTexture, Texture2D energyTexture, Vector2 boxPosition, Vector2 energyPosition,
Color emptyEnergyColor, Color fullEnergyColor, bool flipEnergyReduce)
{
this.boxTexture = boxTexture;
this.energyTexture = energyTexture;
this.boxPosition = boxPosition;
this.energyPosition = energyPosition;
this.emptyEnergyColor = emptyEnergyColor;
this.fullEnergyColor = fullEnergyColor;
this.flipEnergyReduce = flipEnergyReduce;
}
///
/// Contrutor da barra de energia
///
/// Imagem da caixa que envolve a energia
/// Imagem da barra de energia
/// Posição da caixa que envolve a energia
/// Posição da barra de energia
/// Ativa ou não a barra de recuperação de energia
/// Fator de recuperação de energia
public EnergyBar(Texture2D boxTexture, Texture2D energyTexture, Vector2 boxPosition, Vector2 energyPosition,
bool recoveryBar, float recoveryFactor)
{
this.boxTexture = boxTexture;
this.energyTexture = energyTexture;
this.boxPosition = boxPosition;
this.energyPosition = energyPosition;
this.recoveryBar = recoveryBar;
this.recoveryFactor = recoveryFactor;
}
///
/// Contrutor da barra de energia
///
/// Imagem da caixa que envolve a energia
/// Imagem da barra de energia
/// Posição da caixa que envolve a energia
/// Posição da barra de energia
/// Cor da barra de energia para quando ela estiver vazia
/// Cor da barra de energia para quando ela estiver cheia
/// Ativa ou não a barra de recuperação de energia
/// Fator de recuperação de energia
public EnergyBar(Texture2D boxTexture, Texture2D energyTexture, Vector2 boxPosition, Vector2 energyPosition,
Color emptyEnergyColor, Color fullEnergyColor, bool recoveryBar, float recoveryFactor)
{
this.boxTexture = boxTexture;
this.energyTexture = energyTexture;
this.boxPosition = boxPosition;
this.energyPosition = energyPosition;
this.emptyEnergyColor = emptyEnergyColor;
this.fullEnergyColor = fullEnergyColor;
this.recoveryBar = recoveryBar;
this.recoveryFactor = recoveryFactor;
this.recoveryColor = emptyEnergyColor;
}
///
/// Contrutor da barra de energia
///
/// Imagem da caixa que envolve a energia
/// Imagem da barra de energia
/// Posição da caixa que envolve a energia
/// Posição da barra de energia
/// Cor da barra de energia para quando ela estiver vazia
/// Cor da barra de energia para quando ela estiver cheia
/// Ativa ou não a barra de recuperação de energia
/// Fator de recuperação de energia
/// Reduz a barra de energia para a direita
public EnergyBar(Texture2D boxTexture, Texture2D energyTexture, Vector2 boxPosition, Vector2 energyPosition,
Color emptyEnergyColor, Color fullEnergyColor, bool recoveryBar, float recoveryFactor, bool flipEnergyReduce)
{
this.boxTexture = boxTexture;
this.energyTexture = energyTexture;
this.boxPosition = boxPosition;
this.energyPosition = energyPosition;
this.emptyEnergyColor = emptyEnergyColor;
this.fullEnergyColor = fullEnergyColor;
this.recoveryBar = recoveryBar;
this.recoveryFactor = recoveryFactor;
this.recoveryColor = emptyEnergyColor;
this.flipEnergyReduce = flipEnergyReduce;
}
#endregion
4º Passo: Agora precisamos pensar em alguns métodos com por exemplo: recarregar toda energia, movimentar a barra de energia e até mesmo recuperar a energia até a ultima posição da barra de recuperação.
-
FullRecovery: este método deve fazer a energia atual da barra receber a máxima energia, sendo assim, a barra de energia voltará a ficar cheia. Lembrando que o mesmo deve ser feito para a barra de recuperação.
-
MaxRecovery: este método somente serve para os jogos que utilizam recuperação de energia estilo Marvel Super Heroes vs Street Fighter, fazendo com que a barra de energia se iguale a barra de recuperação, recuperando um pouco da energia perdida no ultimo golpe tomado.
-
MoveTo: Este método movimenta a barra de energia para a nova posição.
#region [ Methods ] ////// Recupera toda a energia /// public void FullRecovery() { energy = maxEnergy; recovery = energy; } ////// Recupera o máximo possível de energia /// public void MaxRecovery() { recovery = energy; } ////// Move a barra de energia para outro lugar na tela /// Muito utilizado em jogos de estratégia, onde a /// barra de energia acompanha o personagem /// /// Nova posição da barra de energia public void MoveTo(Vector2 newPosition) { Vector2 diff = energyPosition - boxPosition; boxPosition = newPosition; energyPosition = boxPosition + diff; } #endregion
5º Passo: Para finalizarmos deixei o método de desenho por último por ser o mais chatinho. Neste método devemos pensar que precisamos desenhar a caixa, a barra de energia e barra de recuperação se necessário, porém se for preciso devemos mudar a direção também da redução de energia. Vamos definir como seria desenhar cada imagem da energia:
- Barra de recuperação de energia: esta barra somente é desenhada se recoveryBar for verdadeiro. Se é verdadeiro, então devemos fazer a quantidade (recovery) ser reduzida pelo seu fator (recoveryFactor) multiplicado pelo tempo passado. E para garantirmos que um valor fique entre o mínimo e o máximo desejado usamos o métodos MathHelper.Clamp.
- Para reduzir o desenho da barra de energia e recuperação fazemos uma simples regra de três no 3º parâmetro da 6ª sobrecarga do método Draw do SpriteBatch.
- Se flipEnergyReduce for verdadeiro, devemos rotacionar a barra 180º e modificar a origem de desenho dela, ambas instruções são realizadas no método Draw também.
Vejamos como isso ficaria.
#region [ Draw Method ]
public void Draw(GameTime gameTime, SpriteBatch spriteBatch)
{
// Se existir recuperação de energia
if (recoveryBar)
{
// Reduz a recuperação de energia conforme o tempo passado de jogo
recovery -= recoveryFactor * (float)gameTime.ElapsedGameTime.TotalSeconds;
recovery = MathHelper.Clamp(recovery, energy, maxEnergy);
// Desenha a barra de recuperação de energia
spriteBatch.Draw(energyTexture,
energyPosition,
new Rectangle(0, 0, (int)(recovery * energyTexture.Width / maxEnergy), (int)energyTexture.Height),
new Color(recoveryColor, 0.25f),
flipEnergyReduce ? MathHelper.ToRadians(180) : 0.0f,
flipEnergyReduce ? new Vector2(energyTexture.Width, energyTexture.Height) : Vector2.Zero,
1.0f,
SpriteEffects.None,
0.0f);
}
// Desenha a caixa que envolve a energia
spriteBatch.Draw(boxTexture,
boxPosition,
new Rectangle(0, 0, boxTexture.Width, boxTexture.Height),
Color.White,
0.0f,
Vector2.Zero,
1.0f,
SpriteEffects.None,
0.0f);
// Desenha a barra de energia
spriteBatch.Draw(energyTexture,
energyPosition,
new Rectangle(0, 0, (int)(energy * energyTexture.Width / maxEnergy), (int)energyTexture.Height),
Color.Lerp(emptyEnergyColor, fullEnergyColor, energy / maxEnergy),
flipEnergyReduce ? MathHelper.ToRadians(180) : 0.0f,
flipEnergyReduce? new Vector2(energyTexture.Width, energyTexture.Height) : Vector2.Zero,
1.0f,
SpriteEffects.None,
0.0f);
}
#endregion
Usando a classe em algum jogo
Para usar esta classe em seu jogo, siga os seguintes passos:
1º Passo: Adicione a classe “EnergyBar.cs” em seu projeto.
2º Passo: Adicione a namespace da barra de energia na classe que irá declarar o objeto.
using Utils.Componentes.EnergyBar;
3º Passo: Declare um objeto do tipo EnergyBar
EnergyBar energyBar;
4º Passo: Instância um objeto, ou seja, utilize algumas das sobrecargas do nosso construtor para construir a barra de energia. No meu caso vou utilizar a 6ª sobrecarga. Lembrando que é necessário ter as imagens para desenhar a barra de energia.
energyBar = new EnergyBar(Content.Load<Texture2d>(@"box"),
Content.Load<Texture2d>(@"energybar"),
new Vector2(175.0f, 284.0f),
new Vector2(178.0f, 287.0f),
Color.Red,
Color.Yellow,
true,
1.0f);
5º Passo: Desenha sua barra de energia.
energyBar.Draw(gameTime, spriteBatch);
Conseguiram ver como é fácil utilizar esta classe? Veja o resultado nas imagens abaixo. A figura a esquerda exibe a barra de energia por completo no centro da tela, na figura a direita, a barra de energia sofreu um deslocamento e teve sua energia reduzida.
Com este tutorial, você já pode colocar diversas barras de energia em seu jogo e deixa-lo com uma aparência melhor caso necessário. Vale lembrar que você pode alterar o valor da energia simplesmente modificando a propriedade “Energy”, aumentar o valor de energia que começa em 100 somente modificando o valor de “MaxEnergy”.
Download
Para fazer download desta classe de barra de energia com o exemplo de teste e sair utilizando em seus jogos clique na imagem abaixo.
Conclusão
Espero que esta classe seja muito útil para seus jogos, uma vez que ela é genérica para criar diferentes tipos de barras horizontais, podendo assim ser utilizada em diversos jogos. Qualquer melhoria na classe poderá ser sugerida por vocês, para mantermos todos atualizados. Caso você queira fazer uma barra vertical sugiro criar uma IEnergyBar (interface) e copiar fazer uma classe abstrata (EnergyBar) herdar a interface e implementar tudo, menos o método Draw, pois é ali que se encontra a mudança. Logo em seguida criar duas classes EnergyBarH e uma EnergyBarV para fazer as duas implementações no qual consiste em simplesmente modificar o retângulo de desenho da barra de energias quando se desenha ela. Mas para aqueles que tentarem fazer e não conseguirem, não se preocupe este será o próximo tutorial.
-
11/02/2011 20:49:12 | Kleber Andrade

Obrigado, espero ter ajudado ou que futuramente sirva para seus jogos!
-
17/02/2011 10:19:52 |200.158.35.xxx| droidevr

Muito bom mesmo.
Eu estava pensando algo sobre isso por esses dias, e seu artigo já me ajudou.
Um detalhe engraçado:
Eu lembro que muitas vezes quando jogava Street Fighter no saudoso SNES, a barra de energia do inimigo ou a minha algumas vezes costumava ficar totalmente vazia, mas o jogo continuava até um próximo golpe para finalmente a luta terminar.
O fato dela estar vazia e a luta não terminar, pensando no lado interno (código), poderia indicar fração, e o valor era tão baixo que não dava para renderizar 1 pixel do box. Será?
Abraços.
-
17/02/2011 10:25:04 | Kleber Andrade

Olá amigo, fico feliz que o tutorial tenha te ajudado.
Sobre a barra do Street Fighter é bem por ai mesmo, provavelmente era uma fração e geralmente os retângulos são desenhados com inteiros, então não dava para desenhar, mas devia ter menos de um ponto de vida, o que realmente não indica que o personagem estava morto até levar mais um golpe.
Abraços,
-
17/02/2011 10:24:26 | Bruno Crivelari Sanches
Citou:O fato dela estar vazia e a luta não terminar, pensando no lado interno (código), poderia indicar fração, e o valor era tão baixo que não dava para renderizar 1 pixel do box. Será?Provavelmente sobrava um tiquinho mesmo de energia que não aparecia na tela
.T+
-
27/02/2011 22:32:26 |189.101.214.xxx| Guilherme André

E ai, já acompanho seu tutoriais desde o seu blog, e vi que agora você está utilizando UML, então gostaria de saber qual ferramenta vc usa pois a interface me parece agradável e é disso que preciso para começar a planejar um pequeno projeto pessoal. Até
-
27/02/2011 22:39:58 | Kleber Andrade

Olá amigo fico feliz em saber que você anda acompanhando meus tutoriais.
Sobre este diagrama de classes em específico, ele é gerado pelo próprio Visual Studio C# (Completo).
Agora se você esta querendo gerar um diagrama antes de começar o projeto coisa que também faço, no meu caso eu uso o StarUML por ser free. Infelizmente eu não conheço muitas ferramentas cases free, só conheço essa mesmo.
Abraços,
-
27/02/2011 23:03:50 | Bruno Crivelari Sanches

Outra opção além do Star é o Astah, que possui uma versão grátis (é preciso apenas se cadastrar no site).
http://astah.change-vision.com/en/index.html
O astah é um pouco mais voltado para java, apesar de UML ser independente de linguagem ele tem algumas facilidades para quem usa Java, mas eu sempre usei ele para modelar SW criado em C++, C# e nunca foi problema. Gosto mais do visual dele do que o StarUML, mas é uma questão pessoal.
T+
-
28/02/2011 08:59:07 | Vinícius Godoy de Mendonça

As duas ferramentas são ótimas, discutir qual é a melhor é o mesmo que ficar brigando sobre qual IDE é a melhor.

Só a título de informação, o Astah era antigamente chamado de Jude.
-
19/03/2011 22:39:36 |189.1.22.xxx| Matheus Lins

COMO FAÇO PARA QUE A BARRA POSSA ACOMPANHAR O PERSONAGEM , COMO APLICO O METODO "MoveTo" ?
-
20/03/2011 20:45:21 | Kleber Andrade

Olá amigo, é simples...
Você deve justamente aplicar o método MoveTo durante a atualização do personagem, então toda vez que o personagem muda de posição, você deve chamar o método MoveTo(novaPosição.X, novaPosição.Y) para que a barra de energia se desloque até la.
-
07/04/2011 14:54:40 |200.244.134.xxx| Conan Goodwin

no codigo, no metodo draw, onde esta este codigo:
"if (recoveryBar)
{
// Reduz a recuperação de energia conforme o tempo passado de jogo
recovery -= recoveryFactor * (float)gameTime.ElapsedGameTime.TotalSeconds;
recovery = MathHelper.Clamp(recovery, energy, maxEnergy);
// Desenha a barra de recuperação de energia
spriteBatch.Draw(energyTexture,
energyPosition,
new Rectangle(0, 0, (int)(recovery * energyTexture.Width / maxEnergy), (int)energyTexture.Height),
new Color(recoveryColor, 0.25f),
flipEnergyReduce ? MathHelper.ToRadians(180) : 0.0f,
flipEnergyReduce ? new Vector2(energyTexture.Width, energyTexture.Height) : Vector2.Zero,
1.0f,
SpriteEffects.None,
0.0f);
}"onde esta o parametro "new Color(recoveryColor, 0.25f)", da erro nos parametros do "Color"... nao seria só "recoveryColor" ?
nao sei se estou fazendo algo de errado, darei uma checada aqui...
-
09/04/2011 16:16:18 | Kleber Andrade

Olá amigo, o valor 0.25f é para deixar o canal alpha com somente 1/4 do seu valor, deixando a imagem um pouco transparente.
Pelo que percebi isso só funciona no XNA 3.1, se você estiver usando o XNA 4.0 vai ser um pouco diferente. Caso queira só deixar recoveryColor também pode.
Abraços,
-
19/06/2011 14:13:32 |187.16.61.xxx| Andreas - Rectangle

Cara!!! to acompanhando alguns do seus tutoriais e achei muito legal e bem explicado... e gostei da barra que ficou muito legal...
Mas eu me deparei com um problema e não acho nada na net pra resolver!!!! É que eu to fazendo um jogo de demolição de carro, mais ta bem no inicio, e eu vou usar colisão por pixel (com base em un de seus tutoriais)... So que quando eu estou rotacionando a imagem eu tenho que rotacionar o rectangle que tem os dados da imagem...pois a rotação da imagem acontece no spriteBatch.Draw...! Dai vem a pergunta tem como eu rotacionar um rectangle, pra que ele possa acompanhar a rotação da imagem?
-
19/06/2011 16:06:24 | Kleber Andrade

Olá Andreas, infelizmente a imagem é rotacionada somente na hora do desenho, então mesmo que você use colisão por pixel ele vai continuar pegando informações da imagem original, isso não vai funcionar realmente.
A sugestão é usar colisão por rectangle, porém com a classe Rectangle do XNA também não iria funcionar corretamente, pois ela não inclui rotação. Então você terá que construir uma nova incluindo a informação de rotação (um angulo) e sobrescrever o método intercept.
Vou tentar criar algum tutorial sobre o assunto para você. Abraços.












Muito bom cara! Parabéns!