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.