Aulas de 12 a 13: Código Fonte de Game1.cs e comentários
Código fonte de Game1.cs
A seguir apresentamos o desenvolvimento do código de Mars War 3.01 que
incorpora alguma lógica elaborada pela equipe ...
Incluímos níveis no jogo, sons, score, transições entre os níveis e movimentação do
canhão, sobre o projeto original desenvolvido pela Microsoft.
#region File Description
//---------------------------------------------------------------------------// Game1.cs
//
// Microsoft XNA Community Game Platform
// Copyright I Microsoft Corporation. All rights reserved.
// Inclusoes e alteracoes na logica original: Paulo Sergio Custodio
// Alteraçoes: niveis, score, saidas e movimentacao
// do canhao.
//---------------------------------------------------------------------------#endregion
#region Using Statements
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
#endregion
namespace Mars_War_3._01
{
/// <summary>
/// This is the main type for your game
/// </summary>
public enum GameStates
{
MainMenu,PlayingGame,Options,Exit
}
public enum PlayingGame
{
Level1,Level2,Level3,Level4,Level5
}
public class Game1 : Microsoft.Xna.Framework.Game
{
#region Game Data
GraphicsDeviceManager graphics;
Texture2D backgroundTexture,m_logoExit;
Rectangle viewportRect;
SpriteBatch sb;
GameObject cannon;
const int maxCannonBalls = 3;
GameObject[] cannonBalls;
KeyboardState previousKeyboardState = Keyboard.GetState();
KeyboardState key = Keyboard.GetState();
const
const
const
const
const
int maxEnemies = 4;
float maxEnemyHeight =
float minEnemyHeight =
float maxEnemyVelocity
float minEnemyVelocity
0.1f;
0.5f;
= 7.5f;
= 2.6f;
Random random = new Random();
GameObject[] enemies;
int score,score_enemies;
SpriteFont font;
Vector2 scoreDrawPoint = new Vector2(0.1f, 0.1f);
SoundEffect m_sound;
GameStates m_currGameState;
PlayingGame m_currPlayingGame;
Texture2D m_logo;
protected float m_totals = 0.0f;
protected float m_elapsedGameTime = 0.0f;
private int m_condicao_vitoria = 2000;
#endregion
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = “Content”;
}
protected override void Initialize()
{
m_currGameState = GameStates.MainMenu;
m_currPlayingGame = PlayingGame.Level1;
base.Initialize();
}
protected override void LoadContent()
{
sb = new SpriteBatch(graphics.GraphicsDevice);
backgroundTexture =
Content.Load<Texture2D>(“Sprites\\background”);
m_logo = Content.Load<Texture2D>(@”Sprites\Logo3”);
m_logoExit = Content.Load<Texture2D>(@”Sprites\GameOverLogo”);
cannon = new GameObject(Content.Load<Texture2D>(“Sprites\\cannon”));
cannon.position = new Vector2(120,
graphics.GraphicsDevice.Viewport.Height – 80);
cannonBalls = new GameObject[maxCannonBalls];
for (int i = 0; i < maxCannonBalls; i++)
{
cannonBalls[i] = new GameObject(Content.Load<Texture2D>(“Sprites
\\cannonball”));
}
Devemos carrregar cada um dos elementos bullets que compõem as balas de
canhão. Como esta é uma estrutura tipo Array, com tamanho fixo, cada objeto
que será carregado e desenhado deverá ser referenciado do modo acima, dentro
de um laço for que é responsável por carregar cada bullet em memória. Note
que o construtor da classe GameObject recebe uma Textura como argumento.
enemies = new GameObject[maxEnemies];
for (int i = 0; i < maxEnemies; i++)
{
enemies[i] = new GameObject(Content.Load<Texture2D>(“Sprites\\en
emy”));
}
font = Content.Load<SpriteFont>(“Fontes\\Arial”);
//drawable area of the game screen.
viewportRect = new Rectangle(0, 0,
graphics.GraphicsDevice.Viewport.Width,
graphics.GraphicsDevice.Viewport.Height);
O viewportRect é usado para testar a pertinência dos inimigos e bullets
dentro da área do
jogo. Há um método nativo chamado Contains em XNA que
testa se um retângulo está dentro de coordendas especificadas. Podemos usar
Point ou Vector2 como os argumentos que recebem as coordenadas do objeto para
realizar esse teste. Isto ficará mais claro algumas linha abaixo.
#region carregar sons
m_sound = Content.Load<SoundEffect>(@”Sons\collision”);
#endregion
base.LoadContent();
}
public void UpdateCannonBalls()
{
foreach (GameObject ball in cannonBalls)
{
if (ball.alive)
{
ball.position += ball.velocity;
if (!viewportRect.Contains(new Point(
(int)ball.position.X,
(int)ball.position.Y)))
{
ball.alive = false;
continue;
}
Aula 12 Propriedades booleanas
Se a bola que representa o tiro está em ação (ball.alive = true) então ela
irá se mover com uma velocidade especificada. Se o retângulo viewportRect não
contiver a bola (as coordenadas da bola são dadas pelo vetor ball.position) o
estado ball.alive muda para false. Devemos agora criar um retângulo para a
colisão, que acompanhe a bola (apenas quando ela está em ball.alive = true).
Os argumentos do retângulo abaixo acompanham a bola e têm o tamanho correto,
pois recebem a largura e altura do sprite da bola.
Rectangle cannonBallRect = new Rectangle(
(int)ball.position.X,
(int)ball.position.Y,
ball.sprite.Width,
ball.sprite.Height);
foreach (GameObject enemy in enemies)
{
Rectangle enemyRect = new Rectangle(
(int)enemy.position.X,
(int)enemy.position.Y,
enemy.sprite.Width,
enemy.sprite.Height);
Devemos associar um retângulo para cada inimigo do grupo de inimigos que é um
Array,o retângulo deve acompanhar o objeto e ter o tamanho correto.
if (cannonBallRect.Intersects(enemyRect))
{
ball.alive = false;
enemy.alive = false;
score += 10;
m_sound.Play();
break;
}
Se os retângulos de cada bola de canhão e cada inimigo colidirem, as
propriedades alive de cada um deverão ser configuradas para false, o score de
pontuação deverá ser atualizado e deverá ser tocado o som da colisão.
}
#endregion
}
}
}
protected override void UnloadContent()
{
if (score >= m_condicao_vitoria || score_enemies >= m_condicao_vi
toria)
{
cannon.sprite.Dispose();
}
base.UnloadContent();
}
Caso um dos scores seja maior do que a condição de vitória, o canhão deverá
ser apagado da tela, para isso usamos Dispose( ) que é um método nativo em
XNA com esse propósito.
protected override void Update(GameTime gameTime)
{
KeyboardState keyboardState = Keyboard.GetState();
switch (m_currGameState)
{
case GameStates.MainMenu:
{
#region Main menu
if (keyboardState.IsKeyDown(Keys.Enter))
{
m_currGameState = GameStates.PlayingGame;
}
#endregion
break;
}
case GameStates.PlayingGame:
{
#region Move to exit
if (keyboardState.IsKeyDown(Keys.Escape))
{
m_currGameState = GameStates.Exit;
}
#endregion
if (score <= m_condicao_vitoria && score_enemies <= m_con
dicao_vitoria)
{
m_elapsedGameTime = gameTime.TotalGameTime.Seconds;
keyboardState = Move_canhao(keyboardState);
if (keyboardState.IsKeyDown(Keys.Space) &&
previousKeyboardState.IsKeyUp(Keys.Space))
{
FireCannonBall();
}
previousKeyboardState = keyboardState;
Este método merece um cuidado especial: precisamos usar o teclado para
atirarmos nos inimigos, para isso usamos o estado atual e prévio do teclado:
se o estado atual é pressionado e o estado prévio é não pressionado,
permitimos ação do tiro (FireCannonBall()), em seguida, estado prévio recebe
o estado atual, para atualizar corretamente no próximo frame. Faça um teste
de mesa com este código.
cannon.rotation = MathHelper.Clamp(cannon.rotation, MathHelper.Pi, 0);
A rotação do canhão deverá estar entre 0 e –Pi, a função Clamp da classe
MathHelper garante isto. O jogo inicia apenas após cinco segundos contados de
acordo com a variável m_elapsedGameTime.
if (m_elapsedGameTime >= 5.0f)
{
UpdateCannonBalls();
UpdateEnemies();
}
#endregion
}
else
{
//logica para a parada
}
break;
}
case GameStates.Options:
{
if (keyboardState.IsKeyDown(Keys.Escape))
{
m_currGameState = GameStates.Exit;
}
//configurar mais niveis e seus parametros
break;
}
case GameStates.Exit:
{
if (keyboardState.IsKeyDown(Keys.Q) && previousKeyboardSt
ate.IsKeyUp(Keys.Q))
{
this.Exit();
}
break;
}
}
base.Update(gameTime);
}
private KeyboardState Move_canhao(KeyboardState keyboardState)
{
if (keyboardState.IsKeyDown(Keys.Left))
{
cannon.rotation -= 0.1f;
}
if (keyboardState.IsKeyDown(Keys.Right))
{
cannon.rotation += 0.1f;
}
if (keyboardState.IsKeyDown(Keys.A))
{
cannon.position += cannon.velocity_canhao;
}
if (keyboardState.IsKeyDown(Keys.B))
{
cannon.position -= cannon.velocity_canhao;
}
if (cannon.position.X < 0)
{
cannon.position.X = 0;
}
if (cannon.position.X > this.Window.ClientBounds.Width –
cannon.sprite.Width)
{
cannon.position.X = this.Window.ClientBounds.Width –
cannon.sprite.Width;
}
return keyboardState;
}
public void UpdateEnemies()
{
foreach (GameObject enemy in enemies)
{
if (enemy.alive)
{
enemy.position += enemy.velocity;
if (!viewportRect.Contains(new Point(
(int)(enemy.position.X),
(int)enemy.position.Y)))
{
enemy.alive = false;
score_enemies += 70;
}
}
else
{
enemy.alive = true;
enemy.position = new Vector2(
viewportRect.Right,
MathHelper.Lerp(
(float)viewportRect.Height * minEnemyHeight,
(float)viewportRect.Height * maxEnemyHeight,
(float)random.NextDouble()));
enemy.velocity = new Vector2(
MathHelper.Lerp(
-minEnemyVelocity,
-maxEnemyVelocity,
(float)random.NextDouble()), 0);
}
}
}
public void FireCannonBall()
{
foreach (GameObject ball in cannonBalls)
{
if (!ball.alive)
{
ball.alive = true;
ball.position = cannon.position – ball.center;
ball.velocity = new Vector2(
(float)Math.Cos(cannon.rotation),
(float)Math.Sin(cannon.rotation)) * 5.7f;
return;
}
}
}
O vetor velocidade da bola
do mesmo. Para acompanhar
vetor velocidade devem ter
que além de se movimentar,
deve acompanhar o movimento do canhão e a rotação
a rotação, faremos o seguinte: as componentes do
a mesma orientação do vetor de rotação do canhão,
ainda rotaciona de acordo com as ações do teclado.
O vetor velocidade usa as funções angulares sin(X) e cos(X), que em C# são
escritos como (float)Math.Cos( ) ou (float)Math.Sin( ) para implementar estas
funções. O método FireCannonBall( ) é ativado apenas dentro da condição de
disparado que foi escrita bem acima, quando testamos a condição lógica da
tecla de disparo. Está escrito em UpDate(GameTime gameTime), que controla a
lógica do jogo.
Aula 13 Desenhando o conteúdo do Jogo
Finalmente, precisamos desenhar todo o conteúdo que foi carregado
anteriormente. A estrutura deve ser idêntica ao modo de cosntruírmos os
gameStates, bastando preencher as ocorrências para as variáveis de estado
correspondentes. Siga o código abaixo:
protected override void Draw(GameTime gameTime)
{
graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
switch (m_currGameState)
{
case GameStates.MainMenu:
{
sb.Begin();
sb.Draw(backgroundTexture,
viewportRect,
Color.White);
sb.Draw(m_logo,
new Rectangle(100, 100,
(int)m_logo.Width,
(int)m_logo.Height),
Color.White);
sb.DrawString(font,
"Press ENTER to begin",
new Vector2(200, 300),
Color.Red);
sb.DrawString(font,
"Press O to choose options",
new Vector2(200, 320),
Color.Red);
sb.DrawString(font,
graphics.GraphicsDevice.Viewport.Width.ToString(),
new Vector2(200, 340),
Color.Red);
sb.DrawString(font,
graphics.GraphicsDevice.Viewport.Height.ToString(),
new Vector2(200, 360),
Color.Red);
sb.End();
#endregion
break;
}
case GameStates.PlayingGame:
{
sb.Begin();
//Draw the backgroundTexture sized to the width
//and height of the screen.
sb.Draw(backgroundTexture,
viewportRect,
Color.White);
foreach (GameObject ball in cannonBalls)
{
if (ball.alive)
{
sb.Draw(ball.sprite,
ball.position,
Color.White);
}
}
sb.Draw(cannon.sprite,
cannon.position,
null,
Color.White,
cannon.rotation,
cannon.center, 0.5f,
SpriteEffects.None, 0);
foreach (GameObject enemy in enemies)
{
if (enemy.alive)
{
sb.Draw(enemy.sprite,
enemy.position,
Color.White);
}
}
sb.DrawString(font,
"Score Jogador: " + score.ToString(),
new Vector2(scoreDrawPoint.X * viewportRect.Width, s
coreDrawPoint.Y * viewportRect.Height),
Color.Yellow);
sb.DrawString(font,
"Score Martians: " + score_enemies.ToString(),
new Vector2(400 + scoreDrawPoint.X * viewportRect.Widt
h, scoreDrawPoint.Y * viewportRect.Height),
Color.Yellow);
if (m_elapsedGameTime <= 4)
{
sb.DrawString(font, "Tempo p Iniciar Jogo: " + (5
- m_elapsedGameTime).ToString(), new Vector2(400, 70), Color.LightYellow);
}
if (score > m_condicao_vitoria)
{
sb.DrawString(font, "Humano venceu! Press Escape
to exit: " + score.ToString(), new Vector2(80, 70), Color.LightYellow);
}
else if (score_enemies > m_condicao_vitoria)
{
sb.DrawString(font, "Marcianos venceram!Press Esc
ape to exit: " + score_enemies.ToString(), new Vector2(80, 70), Color.LightYe
llow);
}
sb.End();
#endregion
break;
}
case GameStates.Options:
{
break;
}
case GameStates.Exit:
{
sb.Begin();
sb.Draw(backgroundTexture,
new Vector2(0, 0),
Color.White);
sb.Draw(m_logoExit,
new Vector2(0,0),
Color.White);
sb.End();
break;
}
}
base.Draw(gameTime);
}
}
}
Comentários Gerais
Este pequeno jogo pode ser implementado com muitas mudanças, e é um ponto de partida para
desenvolvermos um jogo tipo arcade melhor. Todos eles apresentam as mesmas estruturas
básicas, e como sugestão, implementem a fase de tiros pelos próprios inimigos.
Boa sorte na disciplina.
Paulo Sérgio Custódio, tutor.
Download

Aulas de 12 a 13: Código Fonte de Game1.cs e