Introdução ao XNA. Curso de Jogos Digitais disciplina: Programação no XNA - PDF
Download "Introdução ao XNA. Curso de Jogos Digitais disciplina: Programação no XNA"
Ana Lívia Canedo Barata
1 Introdução ao XNA Paulo Sérgio Custódio, prof FMU Curso de Jogos Digitais disciplina: Programação no XNA Nas primeiras aulas do nosso curso vamos tomar contato com a biblioteca XNA de desenvolvimento de jogos, elaborada e distribuída gratuitamente pela Microsoft. As plataformas alvo do XNA são PC, Xbox 360 e mais recentemente Zune. As versão mais recentes do XNA são 3.0 e 3.1 que se agregam facilmente no Visual Studio 2008, versões Express ou superior. Em alguns casos, no decorrer da instalação, é possível que o instalador peça a instalação de algum pré-requisito (servicepack) do próprio Visual Studio (isto é mais comum se você tem o Visual Studio 2005). Se for o caso, procure o prérequisito no MSDN, instale mesmo e logo após instale o XNA. Após a instalação, abriremos o XNA normalemente no próprio Visual Studio. Lá, você encontra em Projetos o tipo de projeto da plataforma XNA. No MSDN e na Internet é muito fácil encontrar sites, recursos gratuitos e dicas sobre o uso do XNA e jogos exemplo. O site contém sprites gratuitos que podemos usar nos nossos projetos e algumas apostilas interessantes. Outras engines interessantes são Unity, Visual3D.NET que suportam a linguagem de programação C# para a construção de jogos. É muito importante que dominemos a biblioteca XNA antes de partirmos para o uso das engines usadas na indústria (mas veja que algumas indústrias usam o XNA direto como engine). Vamos entrar nesses detalhes mais adiante.
2 Aula 01 Brincando com sprites O arquivo Game1.cs contém os métodos que nós precisamos para executar todas as ações de um jogo em XNA. Nesta aula precisamos tomar contato com os métodos contidos neste arquivo. Dê uma olhada abaixo no código deste arquivo: using System; using System.Collections.Generic; using System.Linq; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.GamerServices; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Media; using Microsoft.Xna.Framework.Net; using Microsoft.Xna.Framework.Storage; namespace WindowsGame1 /// <summary> /// This is the main type for your game /// </summary> public class Game1 : Microsoft.Xna.Framework.Game GraphicsDeviceManager graphics; SpriteBatch spritebatch; public Game1() graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; /// <summary> /// Allows the game to perform any initialization it needs to before starting to run. /// This is where it can query for any required services and load any non-graphic /// related content. Calling base.initialize will enumerate through any components /// and initialize them as well. /// </summary> protected override void Initialize() // TODO: Add your initialization logic here base.initialize(); to load /// <summary> /// LoadContent will be called once per game and is the place /// all of your content.
3 /// </summary> protected override void LoadContent() // Create a new SpriteBatch, which can be used to draw textures. spritebatch = new SpriteBatch(GraphicsDevice); // TODO: use this.content to load your game content here /// <summary> /// UnloadContent will be called once per game and is the place to unload /// all content. /// </summary> protected override void UnloadContent() // TODO: Unload any non ContentManager content here /// <summary> /// Allows the game to run logic such as updating the world, /// checking for collisions, gathering input, and playing audio. /// </summary> /// <param name="gametime">provides a snapshot of timing values.</param> protected override void Update(GameTime gametime) // Allows the game to exit if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.exit(); // TODO: Add your update logic here base.update(gametime); /// <summary> /// This is called when the game should draw itself. /// </summary> /// <param name="gametime">provides a snapshot of timing values.</param> protected override void Draw(GameTime gametime) GraphicsDevice.Clear(Color.CornflowerBlue); // TODO: Add your drawing code here base.draw(gametime);
4 Neste código você irá notar que há um grupo de variáveis em nível de classe que foram providenciadas automaticamente, um construtor Game1 e 5 métodos. A primeira variável em nível de classe é do tipo GraphicsDeviceManager. Um objeto desta classe é muito importante, pois ele providencia o modo de você acessar os dispositivos gráficos em seu PC, XBox 360 ou Zune. O objeto da classe GraphicsDeviceManager tem uma propriedade chamada GraphicsDevice que representa o real dispositivo gráfico de sua máquina. Uma vez que este objeto é uma espécie de conduite entre o jogo XNA e a placa gráfica de sua máquina, tudo o que você fizer na tela estará amarrado a este objeto. A segunda variável é uma instância da classe SpriteBatch. Este é o objeto núcleo que você estará usando para desenhar os seus sprites. Em termos da computação gráfica, um sprite é definido como uma imagem 2D ou 3D imersa numa cena maior. Jogos 2D são compostos de múltiplos sprites 2D numa cena (player sprites, sprites dos inimigos, sprites do background, etc). Nós estaremos usando este conceito nesta aula, e desenharemos sprites para usá-los nos jogos (ou usaremos sprites gratuitos). Os objetos que descrevemos acima estão nas linhas de código abaixo: GraphicsDeviceManager graphics SpriteBatch spritebatch; Os objetos em si são: graphics e spritebatch, instâncias das classes que estão escritas em azul. Vamos descrever agora os métodos do arquivo Game1.cs: O método Initialize é usado para inicializar variáveis e outros objetos associados com o seu objeto Game1. O seu objeto graphics device será instanciado neste ponto, e pode ser usado no método Initialize para auxiliá-lo a inicializar outros objetos que dependem destas configurações.
5 Você irá usar este método para inicializar o marcador de pontos (score) e itens semelhantes. Como exemplo, vamos configurar um título para a janela do nosso jogo, vamos chamar este título de Novo Jogo. De agora em diante, apenas para título de limpeza, vou retirar os comentários que vem no código pronto do XNA tornando a nossa área menos poluída. Mas, lembre-se que quando escrevermos código muito complexo nos nossos métodos, é muito conveniente escrever comentários para documentarmos adequadamente nossos projetos. Escreva o seguinte trecho de código dentro do método Initialize: protected override void Initialize() Window.Title = "Novo Jogo"; base.initialize(); Clique em F6 para compilarmos o nosso projeto, isto é, verificarmos a existência de erros de sintaxe e construção. Neste estágio, com apenas esta linha escrita, provavelmente não ocorreram erros, então, clique em F5 para rodarmos o nosso primeiro jogo em XNA:
6 Sucesso! Temos uma tela em azul com o título que escolhemos que faz parte da Inicialização, ou seja, está configurado dentro do método Initialize. Vamos continuar a descrever os outros métodos, feche a tela acima, e investiguemos agora o método LoadContent: O método LoadContent é chamado após o método Initialize. Este método é responsável por carregar conteúdo gráfico, sons, modelos, texturas, etc, na memória da máquina, para que a mesma gerencie este conteúdo em tempo de execução ao rodar o jogo. Como está escrito o nosso código, ainda não há nada para ser carregado lá. Como vimos, rodando o nosso primeiro jogo, a menos do título que escolhemos, houve apenas o carregamento de um fundo azul. Após LoadContent terminar, o objeto Game1 irá entrar no que é conhecido como GameLoop. Todo e qualquer jogo, escrito em XNA ou não, entra neste tipo de loop. Aparentemente, a nossa tela azul é estática, mas na verdade, o fundo azul está sendo redesenhado diversas vezes por segundo. A velocidade deste carregamento é denominado framerate (taxa de frames) e pode ser parametrizada em código também.
7 O GameLoop consiste numa série de métodos que são chamados e chamados, continuamente, até o término do jogo. Em XNA, o GameLoop contém dois métodos: UpDate e Draw. Toda a ação principal do jogo, quando ele está rodando, é executada ou em UpDate ou em Draw. O método Draw é responsável pelo desenho de tudo o que ocorre no jogo. Toda a lógica do jogo deve ser escrita no método UpDate: movimento dos objetos, checar colisões, atualizar scores, checar a lógica do fim do jogo, níveis, etc. Ciclo de execução de um Jogo Uma aplicação comercial tem uma estrutura muito diferente de um jogo. Numa aplicação comercial, o programa espera alguma ação do usuário, a escrita de uma senha, um comando, clicar de um mouse sobre um botão de comando. Quando estas ações são executadas, disparam uma série de outras ações, que vão usar os recursos da máquina, bancos de dados, conexão, etc. Por fim, algum processamento termina, e a aplicação mais uma vez fica dormente, esperando novas ações do usuário. Um jogo é uma aplicação muito diferente: o computador captura ações do mouse, teclado, joystick, usa estes eventos disparados pelo usuário. Mas, diferentemente de uma aplicação comercial, o jogo continua a executar muitas outras ações, independentemente de se o jogador faz alguma coisa ou não. Como mencionado acima, no método Update você atualiza as posições dos itens na tela, scores, sequências de animação, sons, etc. Você verifica as detecções de colisão, e ajusta os algoritmos de inteligência artificial (AI) que deverão ser escritos neste método. O método Draw é usado para você desenhar todos os elementos que compõem a parte gráfica do jogo. Aqui, é usado o objeto graphics mencionado acima. A linha de código: GraphicsDevice.Clear(Color.CornflowerBlue); Limpa a tela (Clear) e aplica um fundo azul. Temos a ligeira impressão de que isto é feito estaticamente, mas não é verdade, esta ação é
8 reatualizada diversas vezes, num ritmo constante chamado framerate, como já mencionamos. O loop do jogo termina quando pressionamos uma tecla de saída do GamePad, isto está descrito nas linhas de código: if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.exit(); O método Exit( ) pertence à classe Game e ele é responsável pelo término da execução do loop. Ele é aplicado ao objeto this, que se refere ao objeto Game1. Como não temos um GamePad em nosso laboratório, vamos implementar uma saída deste loop ao pressionarmos a tecla de Escape do atalho. Para isso, dentro do método UpDate, escreva o seguinte: KeyboardState key = Keyboard.GetState(); if (key.iskeydown(keys.escape)) this.exit(); Criamos um objeto chamado key (tecla) que é uma instância da classe KeyBoardState. Este objeto é chamado ou instanciado através da leitura do estado do teclado, ou seja, usando GetState( ). Então, GetState( ) obtém o estado do teclado: KeyBoard. Agora, dentro do if nós inspecionamos qual tecla foi apertada: método IsKeyDown. Keys.Escape diz que a tecla é a tecla Escape. Então, se (if) a tecla pressionada (key.iskeydown) for a tecla Escape (Keys.Escape), encerramos o jogo. Agora, aperte F6 e em seguida F5 para executarmos. Após aparecer a tela azul, pressione a tecla Escape e você verá que o jogo foi encerrado. Não há como capturar isto neste texto, experimente no laboratório. Modificando o nosso jogo Tudo ok até aqui. Agora, vamos voltar a estudar o método Draw. protected override void Draw(GameTime gametime)
9 GraphicsDevice.Clear(Color.CornflowerBlue); base.draw(gametime); Note que este método recebe um parâmetro, chamado gametime, que é uma instância da classe GameTime. A classe GameTime contém métodos usados para a contagem do tempo que está correndo no jogo, seja em milisegundos, segundos, minutos, etc. Por que precisamos rastrear o tempo na execução do jogo? O motivo é que diferentes computadores trabalham a diferentes velocidades. Esta variável vai auxiliá-lo a acompanhar e controlar o ritmo do jogo calculando o tempo real transcorrido, e não de acordo com a velocidade do processador. A variável tempo vai estar amarrada a quando uma animação deve ocorrer, quando algo deve acontecer, etc. A taxa de frames (framerate), animações, sons e efeitos estarão amarrados a esta variável. Vamos escrever código que controla e rastreia o tempo transcorrido do jogo nos exemplos desse curso. Agora, vamos mudar a cor do fundo, no código acima, mude a propriedade de Color. Uma vez que aparece o Intellisense, escolha a propriedade black: Clicando em Black, o código se torna: protected override void Draw(GameTime gametime) GraphicsDevice.Clear(Color.Black); base.draw(gametime);
10 Rodando, o fundo muda para a cor preta, evidentemente. Não vamos imprimir esta figura aqui, pois não é necessário. Veja que o mesmo parâmetro gametime é passado para o método Update, pois como ele controla a lógica do jogo, esta lógica controla as ações que se desenrolam ao longo do tempo. A última linha é base.draw(gametime); Esta linha não deve ser retirada, aliás, nenhuma das linhas que vieram com o esqueleto do código que está escrito no arquivo Game1.cs. Exceto as linhas de comentário. O objeto base está vinculado ao modficador override. No começo do código há a linha: public class Game1:Microsoft.Xna.Framework.Game Game1 é uma classe pública (isto é, os seus métods podem ser acessados a partir de outras classes). Os dois pontos antes de Microsoft.Xna.Framework.Game indicam a Herança. Herança é um conceito muito importante em OOP: ela significa que a classe Game1 herdará as propriedades e métodos que já vem escritos dentro da classe Microsoft.Xna.Framework.Game. Esta classe, escrita pelos desenvolvedores da Microsoft, englobam métodos que permitem escrever e gerenciar jogos de modo otimizado dentro da plataforma, aproveitando da melhor forma possível os recursos de hardware da máquina, e as propriedades básicas da computação gráfica, sons, animação, etc. Os métodos que estão contidos dentro desta classe serão usados juntamente com suas propriedades, para escrevermos jogos que são: orientados a objeto, fáceis de manter (em termos das boas práticas de controle de versão e engenharia de software), reutilização garantida e portáveis para plataformas interessantes como Xbox 360 e Zune (com algumas modificações simples no código). O método Draw que vimos acima pertence a esta classe. O modificador override permite a sobrescrita, logo daí surge a necessidade do objeto base.
11 O modificador void significa que o método Draw não retornará qualquer valor para a execução principal do programa. Finalmente, todos os métodos exigem parênteses, e os seus parâmetros devem ter a declaração do seu tipo. Como gametime pertence à classe GameTime, esta deve ser escrita para indicar ao método qual é o tipo de parâmetro que está sendo usado. Finalemente, vamos dar uma olhada na chamada a Clear. O método Clear apaga tudo na tela e cobre a mesma com a cor especificada. E o gameloop? O método Draw depende do parâmetro gametime. O framerate padrão é de 60fps (60 frames por segundo). Logo, o XNA está pintando o nosso fundo de black nessa taxa (lembre-se que nós mudamos a cor para preta): 60 frames por segundo. Também, dentro deste intervalo, o programa continuamente fiscaliza se você pressiona a tecla de saída. No nosso caso, a tecla Escape do teclado funciona para encerrar o jogo. Frames e framerate O que é um frame? Como vimos na explicação acima, o XNA irá limpar a tela e redesenhar a cena toda vez que o método Draw é chamado. Definição: Uma cena que contém os resultados de uma única chamada a Draw é chamada frame. A cada 16 milisegundos (60 vezes por segundo), a tela é limpa e uma nova cena é desenhada. Quando ocorre o desenho do próximo frame, algum sprite ter-se-á movido, e a combinação desta execução, neste ritmo, dará a ilusão de um movimento contínuo. Múltiplos frames fazem uma animação num jogo, e o número de frames desenhados por segundo representa o framerate do jogo. 60 fps = 60 frames por segundo. Adicionando um sprite ao seu projeto Agora está na hora de desenharmos algo na nossa tela. Todos os gráficos, sons, efeitos e outros itens são carregados em XNA através de algo chamado Content Pipeline.
12 Essencialmente, o Content Pipeline toma todos os formatos de arquivos que foram carregados (.jpg,.bmp,.png e outros formatos) e os converte durante a compilação, num formato amigável ao XNA. Se você carregou uma imagem em seu jogo e o Content Pipeline é capaz de reconhecê-lo durante a compilação, você não precisará mais se preocupar com ele. Como vamos carregar imagens 2D em nossos jogos, vamos importar uma figura para o nosso projeto. Antes de carregarmos uma figura, vamos criar uma pasta, chamada Imagens, dentro da pasta Content (botão direito do mouse: Add -> New Folder) veja como fica o ProjectExplorer: Esta pasta dedicar-se-á a armazenar as figuras e imagens 2D. Criaremos uma pasta apropriada para os arquivos de sons mais adiante. Na área de trabalho há uma pasta chamada Conteúdo_XNA, onde nós armazenamos os arquivos que usaremos nos projetos. Selecione a pasta Imagens, recém criada e com o botão direito do mouse selecione Add -> Existing Item, navegando até o item chamado logo.png que se encontra na pasta Conteúdo_XNA da área de trabalho. Obteremos o resultado abaixo:
13 Construa a solução a partir deste ponto, clique F6 (ou Build), se não aparecerem mensagens de erro, quer dizer que o Content PipeLine reconheceu o formato de arquivo e converteu esse formato para um formato interno reconhecível pelo XNA (e este formato é proprietário, evidentemente). Selecione o painel propriedades (deixando marcado logo.png) e obteremos as propriedades de logo.png: O nome do asset é logo, sem extensão de arquivo. Por default, todos os nomes de assets serão o nome do arquivo que o asset representa sem a extensão de arquivo. Os nomes dos assets não podem repetir sem os mesmos estiverem no mesmo arquivo criado em Content, mas se os assets estiverem em subpastas diferentes, poderão ter o mesmo nome.
14 Note que Content Processor e Content Importer reconheceu o arquivo como do tipo textura (Texture). Uma textura é uma imagem que pode ser aplicada a uma superfície de algum objeto. Carregando e desenhando seu Sprite Agora que o Content Pipeline reconheceu a nossa textura, ela pode ser carregada para ser desenhada em tela. Mas antes disso, precisamos dizer no código de Game1.cs que nós vamos usar este recurso e carregá-lo na tela, e onde nós desejamos nesta tela. Crie uma variável que se refere ao objeto 2D que carregaremos em tela, ele deve ser do tipo textura, logo, pertence à classe Texture2D. Ele pode ficar logo abaixo das variáveis que já vieram declaradas nesta classe, repetimos as mesmas para que você possa reconhecer: GraphicsDeviceManager graphics; SpriteBatch spritebatch; Texture2D texture; Agora você precisa carregar o arquivo da imagem real em sua variável Texture2D. Para acessar dados do Content Pipeline você usa a propriedade Content da classe Game. Esta propriedade é do tipo ContentManeger, e providencia acesso a todos objetos carregados no Content PipeLine. A classe ContentManeger tem um método chamado Load que permite carregar conteúdo em diferentes objetos de formato XNA. Adicione a seguinte linha dentro do métod LoadContent: texture = Imagens/logo ); O método fica então assim: protected override void LoadContent() texture = spritebatch = new SpriteBatch(GraphicsDevice); Note que o parâmetro se refere ao nome do asset e não o nome do arquivo, daí o fato de não haver a extensão de arquivo.png após logo. Construa a sua aplicação clicando em Build e verifique se ocorreram erros.
15 O métod Load é um método genérico que espera a especificação de um tipo da variável que deve ser carregada. Neste caso você espera que um objeto do tipo Texture2D seja retornado. O seu arquivo imagem foi carregado e está pronto para ser usado, mas onde e como devemos desenhá-lo? É aqui que aparece o método Draw que vai nos dizer como e onde desenhar a textura acima. Adicione as seguintes 3 linhas de código dentro do método Draw: spritebatch.begin( ); spritebatch.draw(texture, Vector2.Zero, Color.White); spritebatch.end( ); O métod Draw fica assim: protected override void Draw(GameTime gametime) GraphicsDevice.Clear(Color.Black); spritebatch.begin(); spritebatch.draw(texture, Vector2.Zero, Color.White); spritebatch.end(); base.draw(gametime); Compile e rode o nosso projeto:
16 Vamos estudar estas 3 linhas de código em detalhes: A primeira coisa a notar é que todas as 3 linhas de código usam um objeto chamado spritebatch, que é do tipo SpriteBatch. Esta variável é definida em seu projeto quando você a criou e instanciou no método LoadContent, relembrando: protected override void LoadContent() texture = spritebatch = new SpriteBatch(GraphicsDevice); Basicamente, o que está acontencendo aqui é que com as chamadas Begin e End a partir do objeto SpriteBatch, XNA está contando ao dispositivo gráfico que ele deve enviar um sprite (imagem 2D). O dispositivo gráfico estará recebendo grandes quantidades de dados através de um XNA game, e os dados estarão em diferentes formatos e tipos. A chamada SpriteBatch.Begin( ) inicia a ação do dispositivo gráfico
17 (placa gráfica) para que reconheça e renderize os dados em tela. O método Draw possui 3 parâmetros: Parâmetro Tipo Descrição texture Texture2D Um objeto 2D que armazena a imagem a ser renderizada em tela. Posição Vector2 A posição em coordenadas 2D, medida a partir da posição no canto superior esquerdo da tela. Cor Color A cor que está associada ao sprite, com exceção de white: não pinta o sprite de branco, ele fica com a cor original. Tabela 1: parâmetros do método Draw(,, ) Vector2.Zero significa um vetor com coordenada X=0 e Y=0, ele é equivalente a escrevermos: Vector2(0,0). Orientação das coordenadas 2D na tela O canto superior esquerdo da tela começa as suas coordenadas em X=0 e Y=0. Os valores positivos de X estão à direita e os valores positivos de Y estão na direção vertical indo para baixo a partir daquele canto! Isto é padrão definido pelo DirectX. Para entendermos o que ocorre, desenhe novamente o mesmo sprite na tela do exemplo anterior (cujo arquivo era logo.png) e especifique o vetor posição com as coordenadas: X = 40 e Y = 60. Vamos compilar e rodar para ver onde estará o sprite:
18 protected override void Draw(GameTime gametime) Vector2 vector; vector.x = 40; vector.y = 60; GraphicsDevice.Clear(Color.Black); spritebatch.begin(); spritebatch.draw(texture, vector, Color.White); spritebatch.end(); base.draw(gametime); Na primeira linha criamos um vetor 2D para indicarmos a posição do sprite. Nas duas linhas abaixo, temos: vector.x = 40; a propriedade.x de vector é um inteiro e nesse caso = 40. A propriedade.x do vetor acima é simplesmente a coordenada X deste vetor. O mesmo vale para a coordenada Y. Rodando o nosso projeto obteremos: Até agora tudo bem, mas lembre-se de um detalhe: as coordenadas X e Y deste vetor marcam um ponto do plano, e a figura acima não é um ponto,
19 logo, as coordenadas X e Y (respectivamente X = 40 e Y = 60) marcam qual ponto da figura acima? Alguma das extremidades? Um ponto no meio? Neste ponto (desculpe o trocadilho) é importante não esquecermos de mais outra padronização: qualquer textura 2D carregada no XNA terá associada a sua posição a partir da extremidade superior esquerda da figura. É esta extremidade que marca a posição da textura, ou seja, a posição da textura se refere sempre a este ponto. Logo, quando as nossas figuras começarem a se mexer e colidir, vamos ter que nos preocupar no código da segunda questão interessante sobre esses sprites: Qual é a largura do sprite? Qual é a altura do sprite? Estas 2 propriedades acima devem ser referenciadas e conhecidas no código, como veremos nos próximos exemplos. Figura: a seta amarela acima indica a partir de qual ponto nós calculamos a posição do sprite, que é carregado via uma textura 2D. As 2 setas (brancas) maiores na figura acima mostram a direção onde as coordenadas X e Y têm valores crescentes: à direita os valores de X
20 aumentam, para baixo os valores de Y aumentam. Não se esqueça desta convenção. Largura e Altura da Janela e do Sprite A variável Window.ClientBounds.Width denota a largura da janela onde roda o jogo, e esta quantidade também funciona se o jogo está em modo janela ou fullscreen. A variável Window.ClientBounds.Height denota a altura. Voltando ao nosso sprite, veja que o objeto carregado é texture, é a variável tipo Texture2D que criamos para armazenar a imagem da figura logo. Esta variável também tem as propriedades largura e altura, logo, as variáveis texture.width e texture.height irão denotar as propriedades largura e altura do sprite, respectivamente. Como exercício, vamos colocar o nosso sprite precisamente no meio da tela, ou seja, a posição do ponto que é a sua extremidade superior esquerda deve estar bem no meio da janela. Como fazemos isso? Ora, sabemos quem são as alturas e larguras da janela e do sprite, e como desejamos colocá-lo no meio, precisamos dividir por 2. Apague o código anterior do método Draw e escreva o seguinte: protected override void Draw(GameTime gametime) GraphicsDevice.Clear(Color.Black); spritebatch.begin(); spritebatch.draw(texture, new Vector2((Window.ClientBounds.Widthtexture.Width)/2,(Window.ClientBounds.Heighttexture.Height)/2), Color.White); spritebatch.end(); base.draw(gametime); Compile e rode: