Desenvolvendo jogos com MonoGame*
Por Bruno Sonnino
Muitos desenvolvedores querem desenvolver jogos. E porque não? Jogos estão entre os mais
vendidos na história da computação e as fortunas envolvidas no negócio de jogos continuam a
atrair desenvolvedores. Como um desenvolvedor, eu com certeza gostaria de estar entre aqueles
que desenvolveram o próximo Angry Birds* ou Halo*.
Na prática, o desenvolvimento de jogos é uma das áreas mais difíceis do desenvolvimento de
software. Você deve lembrar daquelas aulas de trigonometria, geometria e física que pensou que
nunca usaria, e que passam a ser parte importante de um jogo. Além disso, seu jogo deve combinar
som, vídeo e uma estória de uma maneira que o usuário queira jogar mais e mais. E isto antes de
escrever uma única linha de código!
Para facilitar as coisas, há diversos frameworks disponíveis para o desenvolvimento de jogos
usando não somente C e C++, mas até C# ou JavaScript* (sim, você pode desenvolver jogos
tridimensionais para seu browser usando HTML5 e JavaScript).
Um destes frameworks é o Microsoft XNA*, construído sobre a tecnologia Microsoft DirectX*, que
permite criar jogos para o Xbox 360*, Windows* e Windows Phone*. A Microsoft está
descontinuando o XNA mas, enquanto isso, a comunidade open source introduziu um novo
participante: MonoGame*.
O que é o MonoGame?
MonoGame é uma implementação open source da API (Application Programming Interface) XNA.
Ele implementa a API XNA não apenas para Windows, mas também para Mac* OS X*, Apple iOS*,
Google Android*, Linux* e Windows Phone. Isto significa que você pode desenvolver um jogo para
qualquer uma destas plataformas com apenas pequenas modificações. Isto é uma característica
fantástica: você pode criar jogos usando C# que podem ser portados facilmente para todas as
maiores plataformas desktop, tablet ou smartphone. É um grande empurrão para quem quer
conquistar o mundo com seus jogos.
Instalando MonoGame no Windows
Você nem precisa ter o Windows para desenvolver com MonoGame. Você pode usar o
MonoDevelop* (uma IDE [Integrated Development Environment] open source para linguagens
Microsoft .NET) ou Xamarin Studio*, uma IDE cross-platform desenvolvida pela Xamarin. Com
estas IDEs, você pode desenvolver usando C# no Linux ou Mac.
2
Desenvolvendo Jogos com MonoGame
Se você é um desenvolvedor Microsoft .NET e usa o Microsoft Visual Studio* diariamente, como eu,
você pode instalar o MonoGame no Visual Studio e usá-lo para criar seus jogos. Quando este artigo
foi escrito, a última versão estável era a versão 3.2. Esta versão roda no Visual Studio 2012 e 2013
e permite que você crie um jogo desktop DirectX, que você irá precisar se quiser suportar toque no
jogo.
A instalação do MonoGame traz diversos modelos para o Visual Studio, que você pode usar para
criar seus jogos, como mostrado na Figura 1.
Figura 1. Novos modelos instalados pelo MonoGame*
Para criar seu primeiro jogo, clique em MonoGame Windows Project e selecione um nome. O
Visual Studio cria um novo projeto com todos os arquivos e referências necessárias. Se você
executar este projeto, obterá algo como mostrado na Figura 2.
Desenvolvendo Jogos com MonoGame
3
Figura 2. Jogo criado com o modelo MonoGame*
Sem graça, não? Somente uma tela azul clara, mas este é o início para qualquer jogo que você criar.
Tecle Esc e a janela fecha.
Você pode começar a escrever seu jogo com o projeto que tem agora, mas existe um porém: Você
não poderá adicionar recursos, como imagens, sprites, sons ou fontes sem compilar eles em um
formato compatível com o MonoGame. Para isso, você tem uma destas opções:
 Instalar o XNA Game Studio 4.0.
 Instalar o Windows Phone 8 software development kit (SDK).
 Usar um programa externo, como o XNA content compiler.
XNA Game Studio
O XNA Game Studio tem tudo o que você precisa para criar jogos para Windows e Xbox 360. Ele
também tem um compilador de conteúdo para compilar seus recursos para arquivos .xnb, que
podem ser adicionados ao seu projeto MonoGame. Ele vem com a instalação apenas para o Visual
Studio 2010. Se você não quer instalar o Visual Studio 2010 somente para isso, você pode instalar
o XNA Game Studio no Visual Studio 2012 (veja o link na seção “Para Mais Informações” deste
artigo).
4
Desenvolvendo Jogos com MonoGame
Windows Phone 8 SDK
Você não pode instalar o XNA Game Studio diretamente no Visual Studio 2012 ou 2013, mas o SDK
Windows Phone 8 pode ser instalado sem problemas nestas duas IDEs. Você pode usá-lo para
criar um projeto para compilar seus recursos.
XNA Content Compiler
Se você não quer instalar uma SDK para compilar seus recursos, você pode usar o XNA content
compiler (veja o link em “Para Mais Informações”), um programa open source que pode compilar
seus recursos para arquivos .xnb, que podem ser usados no MonoGame.
Criando seu Primeiro Jogo
O jogo anterior que foi criado com o modelo MonoGame é o ponto inicial para todos os jogos. Você
irá usar o mesmo processo em todos os jogos. Em Program.cs, você tem a função Main. Esta função
inicializa e executa o jogo:
static void Main()
{
using (var game = new Game1())
game.Run();
}
Game1.cs é o coração do jogo. Ali, você tem dois métodos que são chamados 60 vezes por Segundo
em um loop: Update e Draw. Em Update, você recalcula os dados para todos os elementos no jogo;
em Draw, você desenha estes elementos. Note que este é um loop muito estreito. Você tem 1/60 de
Segundo, ou seja, 16.7 milissegundos para calcular e desenhar os dados. Se você levar mais tempo
que isso, o programa pode pular alguns ciclos Draw e você irá ver falhas de desenho em seu jogo.
Até recentemente, a entrada de dados para jogos em computadores desktop era o teclado e o
mouse. A menos que o usuário tivesse comprado hardware extra, como volantes ou joysticks, você
não poderia assumir que houvesse outros métodos de entrada. Com os novos equipamentos, como
dispositivos Ultrabook™, Ultrabook 2 em 1, em PCs all-in-one, essas opções mudaram. Você pode
usar entrada de toque e de sensores, dando aos usuários um jogo mais imersivo e realista.
Para este primeiro jogo, iremos criar um jogo de chutes de pênaltis de futebol. O usuário irá usar
toque para “chutar” a bola e o goleiro do computador irá tentar pegá-la. A direção e a velocidade
da bola serão determinadas pelo toque do usuário. O goleiro irá escolher um canto e velocidade
arbitrários para pegar a bola. Cada gol resulta em um ponto. Se não houver gol, o goleiro fica com o
ponto.
Desenvolvendo Jogos com MonoGame
5
Adicionando conteúdo ao jogo
O primeiro passo no jogo é adicionar conteúdo. Inicie adicionando o fundo do campo e a bola. Para
fazer isso, crie dois arquivos .png: um para o campo de futebol (Figura 3) e outro para a bola
(Figura 4).
Figura 3. O campo de futebol
Figura 4. A bola de futebol
6
Desenvolvendo Jogos com MonoGame
Para usar estes arquivos no jogo, você deve compilá-los. Se você está usando o XNA Game Studio
ou o SDK Windows Phone 8, você deve criar um projeto de conteúdo XNA. Este projeto não precisa
estar na mesma solução, você irá usá-lo apenas para compilar os recursos. Adicione os recursos a
este projeto e compile-o. Em seguida, vá para o diretório destino e adicione os arquivos .xnb
resultantes a seu projeto.
Eu prefiro usar o XNA Content Compiler, pois este não requer um novo projeto e permite que você
compile os recursos quando necessário. Abra o programa, adicione os arquivos à lista, selecione o
diretório de saída e clique em Compile. Os arquivos .xnb estão prontos para serem adicionados ao
projeto.
Arquivos de Conteúdo
Uma vez que os arquivos .xnb estão disponíveis, adicione-os à pasta Content de seu jogo. Você
deve configurar a build action para cada arquivo como Content e a opção Copy to Output
Directory como Copy if Newer. Se não fizer isso, você terá um erro ao tentar carregar os
recursos.
Crie dois campos para armazenar as texturas do campo e da bola:
private Texture2D _backgroundTexture;
private Texture2D _ballTexture;
Estes campos são carregados no método LoadContent:
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
_backgroundTexture = Content.Load<Texture2D>("SoccerField");
_ballTexture = Content.Load<Texture2D>("SoccerBall");
}
Note que os nomes das texturas são os mesmos que o nome dos arquivos na pasta Content, mas
sem a extensão.
Em seguida, desenhe as texturas no método Draw:
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Green);
// Set the position for the background
var screenWidth = Window.ClientBounds.Width;
var screenHeight = Window.ClientBounds.Height;
var rectangle = new Rectangle(0, 0, screenWidth, screenHeight);
Desenvolvendo Jogos com MonoGame
7
// Begin a sprite batch
_spriteBatch.Begin();
// Draw the background
_spriteBatch.Draw(_backgroundTexture, rectangle, Color.White);
// Draw the ball
var initialBallPositionX = screenWidth / 2;
var ínitialBallPositionY = (int)(screenHeight * 0.8);
var ballDimension = (screenWidth > screenHeight) ?
(int)(screenWidth * 0.02) :
(int)(screenHeight * 0.035);
var ballRectangle = new Rectangle(initialBallPositionX, ínitialBallPositionY,
ballDimension, ballDimension);
_spriteBatch.Draw(_ballTexture, ballRectangle, Color.White);
// End the sprite batch
_spriteBatch.End();
base.Draw(gameTime);
}
Este método limpa a tela com uma cor verde e desenha o fundo e a bola na marca do pênalti. O
primeiro método spriteBatch.Draw desenha o fundo redimensionado para o tamanho da janela,
na posição 0,0; o segundo método desenha a bola na marca do pênalti, redimensionada
proporcionalmente ao tamanho da janela. Não há movimento aqui, pois as posições não mudam. O
próximo passo é movimentar a bola.
Movendo a Bola
Para mover a bola, você deve recalcular sua posição para cada iteração do loop e desenhá-la na
nova posição. Faça o cálculo da nova posição no método Update:
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed ||
Keyboard.GetState().IsKeyDown(Keys.Escape))
Exit();
// TODO: Add your update logic here
_ballPosition -= 3;
_ballRectangle.Y = _ballPosition;
base.Update(gameTime);
}
A posição da bola é atualizada em cada loop, subtraindo-se três pixels. Se você quiser fazer com
que a bola se movimente mais rápido, você deve subtrair mais pixels. As variáveis _screenWidth,
_screenHeight, _backgroundRectangle, _ballRectangle e _ballPosition são campos privados,
inicializados no método ResetWindowSize:
private void ResetWindowSize()
{
_screenWidth = Window.ClientBounds.Width;
8
Desenvolvendo Jogos com MonoGame
_screenHeight = Window.ClientBounds.Height;
_backgroundRectangle = new Rectangle(0, 0, _screenWidth, _screenHeight);
_initialBallPosition = new Vector2(_screenWidth / 2.0f, _screenHeight * 0.8f);
var ballDimension = (_screenWidth > _screenHeight) ?
(int)(_screenWidth * 0.02) :
(int)(_screenHeight * 0.035);
_ballPosition = (int)_initialBallPosition.Y;
_ballRectangle = new Rectangle((int)_initialBallPosition.X, (int)_initialBallPosition.Y,
ballDimension, ballDimension);
}
Este método reinicializa todas as variáveis que dependem do tamanho da janela. Ele é chamado no
método Initialize:
protected override void Initialize()
{
// TODO: Add your initialization logic here
ResetWindowSize();
Window.ClientSizeChanged += (s, e) => ResetWindowSize();
base.Initialize();
}
Este método é chamado em dois lugares diferentes: no início do processo e toda vez que o
tamanho da janela muda. Initialize manipula ClientSizeChanged, de maneira que quando o
tamanho da janela muda, as variáveis que dependem do tamanho da janela são reavaliadas e a
bola é reposicionada para a marca do pênalti.
Se você executar o programa, você verá que a bola move-se em uma linha reta, mas não para
quando o campo termina. Você pode reposicionar a bola quando ela alcança o gol com o seguinte
código:
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed ||
Keyboard.GetState().IsKeyDown(Keys.Escape))
Exit();
// TODO: Add your update logic here
_ballPosition -= 3;
if (_ballPosition < _goalLinePosition)
_ballPosition = (int)_initialBallPosition.Y;
_ballRectangle.Y = _ballPosition;
base.Update(gameTime);
}
A variável _goalLinePosition é outro campo inicializado no método ResetWindowSize:
_goalLinePosition = _screenHeight * 0.05;
Você deve fazer outra mudança no método Draw: remover todo o código de cálculo de posições.
Desenvolvendo Jogos com MonoGame
9
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Green);
var rectangle = new Rectangle(0, 0, _screenWidth, _screenHeight);
// Begin a sprite batch
_spriteBatch.Begin();
// Draw the background
_spriteBatch.Draw(_backgroundTexture, rectangle, Color.White);
// Draw the ball
_spriteBatch.Draw(_ballTexture, _ballRectangle, Color.White);
// End the sprite batch
_spriteBatch.End();
base.Draw(gameTime);
}
O movimento é perpendicular ao gol. Se você quiser que a bola se movimente num ângulo, crie um
campo _ballPositionX e incremente-o para movimentar para a direita ou decremente-o, para
movimentar para a esquerda. Uma maneira melhor é usar um Vector2 para a posição da bola,
como o seguinte:
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed ||
Keyboard.GetState().IsKeyDown(Keys.Escape))
Exit();
// TODO: Add your update logic here
_ballPosition.X -= 0.5f;
_ballPosition.Y -= 3;
if (_ballPosition.Y < _goalLinePosition)
_ballPosition = new Vector2(_initialBallPosition.X,_initialBallPosition.Y);
_ballRectangle.X = (int)_ballPosition.X;
_ballRectangle.Y = (int)_ballPosition.Y;
base.Update(gameTime);
}
Se você executar o programa, verá que a bola se move com um ângulo (Figura 5). O passo seguinte
é fazer a bola se mover quando o usuário “chuta” ela.
10
Desenvolvendo Jogos com MonoGame
Figura 5. Jogo com a bola em movimento
Toque e Gestos
Neste jogo, o movimento da bola deve iniciar com um “peteleco”. Este toque determina a direção e
velocidade da bola.
No MonoGame, você pode ter entrada de toque usando a classe TouchPanel. Você pode usar os
dados brutos de entrada ou usar a API de gestos. Os dados brutos trazem mais flexibilidade, pois
você pode processar os dados de entrada da maneira que deseja, enquanto que a API de gestos
transforma os dados brutos em gestos filtrados, de maneira que você só recebe entrada para os
gestos que deseja.
Embora a API de gestos seja mais fácil de usar, há alguns casos em que ela não pode ser usada. Por
exemplo, se você quer detectar um gesto especial, como um X ou gestos com mais de dois dedos,
deverá usar os dados brutos.
Para este jogo precisamos apenas do peteleco, e a API de gestos suporta isso, então iremos usá-la.
A primeira coisa a fazer é indicar quais os gestos que você quer, usando a classe TouchPanel. Por
exemplo, o código:
TouchPanel.EnabledGestures = GestureType.Flick | GestureType.FreeDrag;
Desenvolvendo Jogos com MonoGame
11
. . . faz com que o MonoGame detecte e notifique apenas quando for feito um peteleco ou arrastado
o dedo. Então, no método Update, você pode processar os gestos como da seguinte maneira:
if (TouchPanel.IsGestureAvailable)
{
// Read the next gesture
GestureSample gesture = TouchPanel.ReadGesture();
if (gesture.GestureType == GestureType.Flick)
{
…
}
}
Inicialmente, determine se um gesto está disponível. Se estiver, você pode chamar ReadGesture
para obtê-lo e processá-lo.
Iniciando o Movimento com Toque
Habilite gestos de peteleco no jogo usando o método Initialize:
protected override void Initialize()
{
// TODO: Add your initialization logic here
ResetWindowSize();
Window.ClientSizeChanged += (s, e) => ResetWindowSize();
TouchPanel.EnabledGestures = GestureType.Flick;
base.Initialize();
}
Até agora, a bola estava em movimento enquanto o jogo estava em execução. Use um campo
privado, _isBallMoving, para dizer ao jogo quando a bola está em movimento. No método Update,
quando o programa detecta um peteleco, você deve configurar _isBallMoving para True, para
iniciar o movimento. Quando a bola alcança a linha do gol, configure _isBallMoving para False e
reposicione a bola:
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed ||
Keyboard.GetState().IsKeyDown(Keys.Escape))
Exit();
// TODO: Add your update logic here
if (!_isBallMoving && TouchPanel.IsGestureAvailable)
{
// Read the next gesture
GestureSample gesture = TouchPanel.ReadGesture();
if (gesture.GestureType == GestureType.Flick)
{
_isBallMoving = true;
12
Desenvolvendo Jogos com MonoGame
_ballVelocity = gesture.Delta * (float)TargetElapsedTime.TotalSeconds / 5.0f;
}
}
if (_isBallMoving)
{
_ballPosition += _ballVelocity;
// reached goal line
if (_ballPosition.Y < _goalLinePosition)
{
_ballPosition = new Vector2(_initialBallPosition.X, _initialBallPosition.Y);
_isBallMoving = false;
while (TouchPanel.IsGestureAvailable)
TouchPanel.ReadGesture();
}
_ballRectangle.X = (int) _ballPosition.X;
_ballRectangle.Y = (int) _ballPosition.Y;
}
base.Update(gameTime);
}
A velocidade da bola não é mais fixa: o programa usa o campo _ballVelocity para configurar a
velocidade da bola nas direções x e y. Gesture.Delta retorna a variação de movimento desde a
última atualização. Para calcular a velocidade do peteleco, multiplique este vetor pela propriedade
TargetElapsedTime.
Se a bola está se movendo, o vetor _ballPosition é incrementado pela velocidade (em pixels por
frame) até que a bola alcança a linha do gol. O código a seguir:
_isBallMoving = false;
while (TouchPanel.IsGestureAvailable)
TouchPanel.ReadGesture();
. . . faz duas coisas: para a bola e remove todos os gestos da fila de entrada. Se você não fizer isso, o
usuário poderá fazer gestos enquanto a bola se move, fazendo com que ela reinicie o movimento
após ter parado.
Ao executar o jogo, você pode dar petelecos na bola e ela se moverá na direção do peteleco, com a
velocidade do gesto. Entretanto, temos um porém: o código não detecta onde o gesto ocorreu.
Você pode dar petelecos em qualquer ponto da tela (não somente na bola) e a bola irá iniciar o
movimento. Você poderia usar gesture.Position para detectar a posição do gesto, mas esta
propriedade sempre retorna 0,0 e assim ela não pode ser usada.
A solução é usar os dados brutos, obter a entrada de toque e ver se ela está próxima da bola. O
código a seguir determina se a entrada de toque coincide com a bola. Se coincidir, configuramos o
campo _isBallHit:
TouchCollection touches = TouchPanel.GetState();
if (touches.Count > 0 && touches[0].State == TouchLocationState.Pressed)
{
Desenvolvendo Jogos com MonoGame
13
var touchPoint = new Point((int)touches[0].Position.X, (int)touches[0].Position.Y);
var hitRectangle = new Rectangle((int)_ballPositionX, (int)_ballPositionY,
_ballTexture.Width,
_ballTexture.Height);
hitRectangle.Inflate(20,20);
_isBallHit = hitRectangle.Contains(touchPoint);
}
Assim, o movimento só inicia se o campo _isBallHit é True:
if (TouchPanel.IsGestureAvailable && _isBallHit)
Se você executar o jogo, verá que o movimento da bola só começa se você der um peteleco nela.
Ainda temos um problema aqui: se você atingir a bola muito devagar ou em uma direção que a
bola não atinge a linha do gol, o jogo termina pois a bola não volta nunca para a posição inicial.
Você deve configurar um timeout para o movimento da bola. Quando o tempo expirar, o jogo
reposiciona a bola.
O método Update tem um parâmetro: gameTime. Se você armazenar o valor de gameTime quando o
movimento for iniciado, você poderá saber o tempo que a bola está em movimento e reinicializar o
jogo quando este tempo expirar:
if (gesture.GestureType == GestureType.Flick)
{
_isBallMoving = true;
_isBallHit = false;
_startMovement = gameTime.TotalGameTime;
_ballVelocity = gesture.Delta*(float) TargetElapsedTime.TotalSeconds/5.0f;
}
...
var timeInMovement = (gameTime.TotalGameTime - _startMovement).TotalSeconds;
// reached goal line or timeout
if (_ballPosition.Y < _goalLinePosition || timeInMovement > 5.0)
{
_ballPosition = new Vector2(_initialBallPosition.X, _initialBallPosition.Y);
_isBallMoving = false;
_isBallHit = false;
while (TouchPanel.IsGestureAvailable)
TouchPanel.ReadGesture();
}
Adicionando um Goleiro
O jogo está funcionando, mas ele precisa de um elemento de dificuldade: você deve adicionar um
goleiro que fica se mexendo depois que a bola é chutada. O goleiro é um arquivo .png que é
compilado pelo XNA Content Compiler (Figura 6). Você deve adicionar este arquivo compilado à
14
Desenvolvendo Jogos com MonoGame
pasta Content, configurar a opção build action para Content, e configurar Copy to Output
Directory para Copy if Newer.
Figura 6. O goleiro
O goleiro é carregado no método LoadContent:
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
_backgroundTexture = Content.Load<Texture2D>("SoccerField");
_ballTexture = Content.Load<Texture2D>("SoccerBall");
_goalkeeperTexture = Content.Load<Texture2D>("Goalkeeper");
}
Você deve desenhar ele no método Draw:
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Green);
// Begin a sprite batch
_spriteBatch.Begin();
// Draw the background
_spriteBatch.Draw(_backgroundTexture, _backgroundRectangle, Color.White);
// Draw the ball
_spriteBatch.Draw(_ballTexture, _ballRectangle, Color.White);
// Draw the goalkeeper
_spriteBatch.Draw(_goalkeeperTexture, _goalkeeperRectangle, Color.White);
// End the sprite batch
_spriteBatch.End();
base.Draw(gameTime);
}
_goalkeeperRectangle contém o retângulo do goleiro na janela. Ele é mudado no método Update:
protected override void Update(GameTime gameTime)
{
…
_ballRectangle.X = (int) _ballPosition.X;
_ballRectangle.Y = (int) _ballPosition.Y;
_goalkeeperRectangle = new Rectangle(_goalkeeperPositionX, _goalkeeperPositionY,
_goalKeeperWidth, _goalKeeperHeight);
base.Update(gameTime);
}
Desenvolvendo Jogos com MonoGame
15
Os campos _goalkeeperPositionY, _goalKeeperWidth e _goalKeeperHeight são atualizados no
método ResetWindowSize:
private void ResetWindowSize()
{
…
_goalkeeperPositionY = (int) (_screenHeight*0.12);
_goalKeeperWidth = (int)(_screenWidth * 0.05);
_goalKeeperHeight = (int)(_screenWidth * 0.005);
}
A posição inicial do goleiro é no meio da tela, perto da linha do gol:
_goalkeeperPositionX = (_screenWidth - _goalKeeperWidth)/2;
O goleiro inicia o movimento ao mesmo tempo em que a bola. Ele se move de um lado para outro
em um movimento harmônico. Esta senoide descreve seu movimento:
X = A * sin(at + δ)
A é a amplitude do movimento (a largura do gol), t é o tempo do movimento, e a e δ são
coeficientes aleatórios (isso fará com que o movimento seja aleatório, de modo que o usuário não
possa prever a velocidade e o canto que o goleiro irá tomar).
Os coeficientes são calculados quando o usuário chuta a bola:
if (gesture.GestureType == GestureType.Flick)
{
_isBallMoving = true;
_isBallHit = false;
_startMovement = gameTime.TotalGameTime;
_ballVelocity = gesture.Delta * (float)TargetElapsedTime.TotalSeconds / 5.0f;
var rnd = new Random();
_aCoef = rnd.NextDouble() * 0.005;
_deltaCoef = rnd.NextDouble() * Math.PI / 2;
}
O coeficiente a é a velocidade do goleiro, um número entre 0 e 0.005 que representa uma
velocidade entre 0 e 0.3 pixels/segundos (máximo de 0.005 pixels em 1/60 de segundo). O
coeficiente δ é um número entre 0 e pi/2. Quando a bola está se movendo, você muda a posição do
goleiro:
if (_isBallMoving)
{
_ballPositionX += _ballVelocity.X;
_ballPositionY += _ballVelocity.Y;
_goalkeeperPositionX = (int)((_screenWidth * 0.11) *
Math.Sin(_aCoef * gameTime.TotalGameTime.TotalMilliseconds +
_deltaCoef) + (_screenWidth * 0.75) / 2.0 + _screenWidth * 0.11);
…
16
Desenvolvendo Jogos com MonoGame
}
A amplitude do movimento é _screenWidth * 0.11 (o tamanho do gol). Adicione (_screenWidth *
0.75) / 2.0 + _screenWidth * 0.11 ao resultado, de modo que o goleiro se movimente em frente ao
gol. Agora é hora de fazer o goleiro pegar a bola.
Teste de Colisão
Se você quiser saber se o goleiro pega a bola, você deve saber se o retângulo da bola intercepta o
retângulo do goleiro. Você faz isso no método Update,depois de calcular os dois retângulos:
_ballRectangle.X = (int)_ballPosition.X;
_ballRectangle.Y = (int)_ballPosition.Y;
_goalkeeperRectangle = new Rectangle(_goalkeeperPositionX, _goalkeeperPositionY,
_goalKeeperWidth, _goalKeeperHeight);
if (_goalkeeperRectangle.Intersects(_ballRectangle))
{
ResetGame();
}
ResetGame é somente uma refatoração do código que reinicializa o jogo ao estado inicial:
private void ResetGame()
{
_ballPosition = new Vector2(_initialBallPosition.X, _initialBallPosition.Y);
_goalkeeperPositionX = (_screenWidth - _goalKeeperWidth) / 2;
_isBallMoving = false;
_isBallHit = false;
while (TouchPanel.IsGestureAvailable)
TouchPanel.ReadGesture();
}
Com este código simples, o jogo sabe se o goleiro pegou a bola. Agora, você precisa saber se a bola
atingiu o gol. Você faz isso quando a bola passa da linha do gol.
var isTimeout = timeInMovement > 5.0;
if (_ballPosition.Y < _goalLinePosition || isTimeout)
{
bool isGoal = !isTimeout &&
(_ballPosition.X > _screenWidth * 0.375) &&
(_ballPosition.X < _screenWidth * 0.623);
ResetGame();
}
A bola deve estar completamente dentro do gol, a sua posição deve estar entre o primeiro poste do
gol (_screenWidth * 0.375) e o segundo poste (_screenWidth * 0.625 − _screenWidth * 0.02).
Agora é hora de atualizar o placar do jogo.
Desenvolvendo Jogos com MonoGame
17
Adicionando um Placar
Para adicionar um placar ao jogo, você deve adicionar um novo recurso: um spritefont com o fonte
usado no jogo. Um spritefont é um arquivo .xml descrevendo o fonte—a família do fonte, seu
tamanho e peso, juntamente com outras propriedades. Em um jogo, você pode usar um spritefont
como esse:
<?xml version="1.0" encoding="utf-8"?>
<XnaContent xmlns:Graphics="Microsoft.Xna.Framework.Content.Pipeline.Graphics">
<Asset Type="Graphics:FontDescription">
<FontName>Segoe UI</FontName>
<Size>24</Size>
<Spacing>0</Spacing>
<UseKerning>false</UseKerning>
<Style>Regular</Style>
<CharacterRegions>
<CharacterRegion>
<Start>&#32;</Start>
<End>&#127;</End>
</CharacterRegion>
</CharacterRegions>
</Asset>
</XnaContent>
Você deve compilar este arquivo .xml com o XNA Content Compiler e adicionar o arquivo .xnb
resultante à pasta Content do projeto; configure a opção build action para Content e Copy to
Output Directory para Copy if Newer. O fonte é carregado no método LoadContent:
_soccerFont = Content.Load<SpriteFont>("SoccerFont");
Em ResetWindowSize, reinicialize a posição do placar:
var scoreSize = _soccerFont.MeasureString(_scoreText);
_scorePosition = (int)((_screenWidth - scoreSize.X) / 2.0);
Para manter o resultado do jogo, declare duas variáveis: _userScore e _computerScore. A variável
_userScore é incrementada quando acontece um gol e _computerScore é incrementado quando a
bola vai para fora, o tempo expira ou o goleiro pega a bola:
if (_ballPosition.Y < _goalLinePosition || isTimeout)
{
bool isGoal = !isTimeout &&
(_ballPosition.X > _screenWidth * 0.375) &&
(_ballPosition.X < _screenWidth * 0.623);
if (isGoal)
_userScore++;
else
_computerScore++;
ResetGame();
18
Desenvolvendo Jogos com MonoGame
}
…
if (_goalkeeperRectangle.Intersects(_ballRectangle))
{
_computerScore++;
ResetGame();
}
ResetGame recria e reposiciona o placar:
private void ResetGame()
{
_ballPosition = new Vector2(_initialBallPosition.X, _initialBallPosition.Y);
_goalkeeperPositionX = (_screenWidth - _goalKeeperWidth) / 2;
_isBallMoving = false;
_isBallHit = false;
_scoreText = string.Format("{0} x {1}", _userScore, _computerScore);
var scoreSize = _soccerFont.MeasureString(_scoreText);
_scorePosition = (int)((_screenWidth - scoreSize.X) / 2.0);
while (TouchPanel.IsGestureAvailable)
TouchPanel.ReadGesture();
}
O método _soccerFont.MeasureString mede o string usando o fonte selecionado. Você irá usar
esta medida para calcular a posição do placar. O placar será desenhado no método Draw:
protected override void Draw(GameTime gameTime)
{
…
// Draw the score
_spriteBatch.DrawString(_soccerFont, _scoreText,
new Vector2(_scorePosition, _screenHeight * 0.9f), Color.White);
// End the sprite batch
_spriteBatch.End();
base.Draw(gameTime);
}
Ligando as Luzes do Estádio
Como um toque final, o jogo liga as luzes do estádio quando o nível de luz no ambiente está baixo.
Os novos dispositivos Ultrabook e 2 em 1 tem, em geral, sensores de luz que você pode usar para
determinar quanta luz há no ambiente e mudar a maneira que o fundo é desenhado.
Para aplicações desktop, você pode usar o Windows API Code Pack para o Microsoft .NET
Framework, uma biblioteca que permite acessar recursos dos sistemas operacionais Windows 7 e
mais novos. Para este jogo, iremos usar um outro caminho: as APIs de sensores WinRT. Embora
elas tenham sido escritas para Windows 8, elas também estão disponíveis para aplicações desktop
e podem ser usadas sem mudanças. Usando-as, você pode portar sua aplicação para Windows 8
Store sem mudar uma única linha de código.
Desenvolvendo Jogos com MonoGame
19
O Intel® Developer Zone (IDZ) tem um artigo sobre como usar as APIs WinRT em uma aplicação
desktop (veja a seção “Para Mais Informações”). Baseado nesta informação, você deve selecionar o
projeto no Solution Explorer, dar um clique com o botão direito e selecionar Unload Project.
Então, clique com o botão direito novamente e clique em Edit project. No primeiro
PropertyGroup, adicione um rótulo TargetPlatFormVersion:
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
…
<FileAlignment>512</FileAlignment>
<TargetPlatformVersion>8.0</TargetPlatformVersion>
</PropertyGroup>
Clique com o botão direito novamente e então clique em Reload Project. Visual Studio recarrega o
projeto. Quando você adicionar uma nova referência ao projeto, você poderá ver a aba Windows
no gerenciador de referências, como mostra a Figura 7.
Figure 7. A aba Windows* no Gerenciador de Referências
20
Desenvolvendo Jogos com MonoGame
Adicione a referência Windows ao projeto. Você também deve adicionar uma referência a
System.Runtime.WindowsRuntime.dll. Se não puder encontrar este assembly na lista, você pode
navegar para a pasta .Net Assemblies. Na minha máquina, esta pasta está em C:\Program Files
(x86)\Reference Assemblies\Microsoft\Framework\.NETCore\v4.5.
Agora, você pode escrever código para detector o sensor de luz:
LightSensor light = LightSensor.GetDefault();
if (light != null)
{
Se o sensor de luz estiver presente, o método GetDefault retorna uma variável não nula que você
pode usar para detector variações de luz. Você pode fazer isto manipulando o evento
ReadingChanged, como a seguir:
LightSensor light = LightSensor.GetDefault();
if (light != null)
{
light.ReportInterval = 0;
light.ReadingChanged += (s,e) => _lightsOn = e.Reading.IlluminanceInLux < 10;
}
Se a leitura estiver abaixo de 10, a variável _lightsOn é True, e você pode usá-la para desenhar o
fundo de outra maneira. Se você olhar o método Draw de spriteBatch, verá que o terceiro
parâmetro é uma cor. Até agora, você usou apenas branco. Esta cor é usada para colorir o bitmap.
Se usar branco, as cores do bitmap permanecem as mesmas; se usar preto, o bitmap será todo
preto. Qualquer outra cor colore o bitmap. Você pode usar esta cor para ligar as luzes, usando uma
cor verde quando as luzes estão desligadas e branco quando ligadas. No método Draw, mude o
desenho do fundo:
_spriteBatch.Draw(_backgroundTexture, rectangle, _lightsOn ? Color.White : Color.Green);
Agora, quando você executa o programa, verá um fundo verde escuro quando as luzes estão
desligadas e verde claro quando estão ligadas (Figura 8).
Desenvolvendo Jogos com MonoGame
21
Figura 8. O jogo finalizado
Agora você tem um jogo completo. Sem dúvida, ele não está acabado – ele necessita ainda muito
polimento (animações quando acontece um gol, bola retornando quando o goleiro pega a bola ou
atinge um poste)—mas eu deixo isto como lição de casa para você. O passo final é portar o jogo
para Windows 8.
Portando o Jogo para Windows 8
Portar um jogo MonoGame para outras plataformas é fácil. Você deve apenas criar um novo
projeto na solução, do tipo MonoGame Windows Store Project, e apagar o arquivo Game1.cs e
adicionar os quatro arquivos .xnb da pasta Content da app Windows Desktop para a pasta
Content do novo projeto. Você não irá adicionar novas cópias dos arquivos, mas irá adicionar links
para os arquivos originais. No Solution Explorer, clique com o botão direito na pasta Content do
novo projeto, clique em Add/Existing Files, selecione os quatro arquivos .xnb do projeto Desktop,
clique na seta para baixo ao lado do botão Add e selecione Add as link. O Visual Studio adiciona os
quatro links.
Em seguida, adicione o arquivo Game1.cs do velho projeto ao novo. Repita o procedimento que
você fez com os arquivos .xnb: clique com o botão direito no projeto, clique em Add/Existing
Files e selecione Game1.cs do outro projeto, clique na seta para baixo ao lado do botão Add e
22
Desenvolvendo Jogos com MonoGame
clique em Add as link. A última mudança a fazer está em Program.cs, aonde você deve mudar o
namespace para a classe Game1, porque você está usando a classe Game1 do projeto desktop.
Pronto—você criou um jogo para Windows 8!
Conclusão
Desenvolver jogos é uma tarefa difícil por si só. Você terá que lembrar suas aulas de geometria,
trigonometria e física e aplicar todos aqueles conceitos ao desenvolvimento do jogo (não seria
ótimo se os professores usassem jogos ao ensinar estes assuntos?).
MonoGame facilita um pouco esta tarefa. Você não precisa usar o DirectX, você pode usar o C#
para desenvolver seus jogos e você tem acesso completo ao hardware. Toque, som e sensores
estão disponíveis para seus jogos. Além disso, você pode desenvolver um jogo e portá-lo, com
pequenas alterações para Windows 8, Windows Phone, Mac OS X, iOS, ou Android. Este é um
bônus real quando você quer desenvolver jogos multiplataforma.
Para Mais Informações










Visite o web site do MonoDevelop em http://monodevelop.com.
Visite o web site Xamarin para o Xamarin Studio em http://xamarin.com.
Baixe o MonoGame em http://monoGame.net.
Baixe o XNA Game Studio em http://www.microsoft.com/enus/download/details.aspx?id=23714.
Aprenda mais sobre a instalação do XNA no Windows 8 com Visual Studio 2012 em
http://blogs.msdn.com/b/uk_faculty_connection/archive/2013/11/12/installing-xna-onwindows-8-with-visual-studio-2012.aspx.
Baixe o SDK Windows Phone 8 em http://www.microsoft.com/enus/download/details.aspx?id=35471.
Obtenha o XNA Content Compiler em http://xnacontentcompiler.codeplex.com.
Obtenha o schema XML do spritefont XML em http://msdn.microsoft.com/enus/library/bb447759.aspx.
O Windows API Code Pack para o Framework Microsoft .NET está disponível em
http://archive.msdn.microsoft.com/WindowsAPICodePack.
Veja “Using Windows 8* WinRT API from desktop applications” em
http://software.intel.com/en-us/articles/using-winrt-apis-from-desktop-applications.
Desenvolvendo Jogos com MonoGame
23
Sobre o Autor
Bruno Sonnino é um Microsoft Most Valuable Professional (MVP) no Brasil Ele é um
desenvolvedor, consultor e autor e escreveu 5 livros de Delphi, publicados em Português pela
Pearson Education Brazil e muitos artigos para revistas e web sites brasileiros e americanos.
Notices
INFORMATION IN THIS DOCUMENT IS PROVIDED IN CONNECTION WITH INTEL PRODUCTS. NO LICENSE, EXPRESS OR
IMPLIED, BY ESTOPPEL OR OTHERWISE, TO ANY INTELLECTUAL PROPERTY RIGHTS IS GRANTED BY THIS DOCUMENT. EXCEPT
AS PROVIDED IN INTEL'S TERMS AND CONDITIONS OF SALE FOR SUCH PRODUCTS, INTEL ASSUMES NO LIABILITY
WHATSOEVER AND INTEL DISCLAIMS ANY EXPRESS OR IMPLIED WARRANTY, RELATING TO SALE AND/OR USE OF INTEL
PRODUCTS INCLUDING LIABILITY OR WARRANTIES RELATING TO FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY,
OR INFRINGEMENT OF ANY PATENT, COPYRIGHT OR OTHER INTELLECTUAL PROPERTY RIGHT.
UNLESS OTHERWISE AGREED IN WRITING BY INTEL, THE INTEL PRODUCTS ARE NOT DESIGNED NOR INTENDED FOR ANY
APPLICATION IN WHICH THE FAILURE OF THE INTEL PRODUCT COULD CREATE A SITUATION WHERE PERSONAL INJURY OR
DEATH MAY OCCUR.
Intel may make changes to specifications and product descriptions at any time, without notice. Designers must not rely on
the absence or characteristics of any features or instructions marked "reserved" or "undefined." Intel reserves these for
future definition and shall have no responsibility whatsoever for conflicts or incompatibilities arising from future changes to
them. The information here is subject to change without notice. Do not finalize a design with this information.
The products described in this document may contain design defects or errors known as errata which may cause the
product to deviate from published specifications. Current characterized errata are available on request.
Contact your local Intel sales office or your distributor to obtain the latest specifications and before placing your product
order.
Copies of documents which have an order number and are referenced in this document, or other Intel literature, may be
obtained by calling 1-800-548-4725, or go to: http://www.intel.com/design/literature.htm
Software and workloads used in performance tests may have been optimized for performance only on Intel
microprocessors. Performance tests, such as SYSmark* and MobileMark*, are measured using specific computer systems,
components, software, operations, and functions. Any change to any of those factors may cause the results to vary. You
should consult other information and performance tests to assist you in fully evaluating your contemplated purchases,
including the performance of that product when combined with other products.
Any software source code reprinted in this document is furnished under a software license and may only be used or copied
in accordance with the terms of that license.
Intel, the Intel logo, and Ultrabook are trademarks of Intel Corporation in the U.S. and/or other countries.
24
Desenvolvendo Jogos com MonoGame
Copyright © 2014 Intel Corporation. All rights reserved.
*Other names and brands may be claimed as the property of others.
Download

Developing_games_with_Monogame-Port