Olá pessoal, como estamos?!
Hoje, vou mostrar como “dividir” a tela para que dois jogadores consigam “brincar” ao mesmo tempo. Observe:
Além disso, mostro como criar um componente para medir FPS.
Para pegar todo o código fonte, acesse https://github.com/ElemarJR/VamosAprenderXNA
Medindo FPS
Contar o número de Frames per second (FPS) é importante para identificar eventuais “falhas” de programação que estejam levando a falhas de performance.
Para contar FPS, desenvolvi um pequeno componente. Observe:
public class FpsGameComponent : DrawableGameComponent
{
readonly GameWindow Window;
public FpsGameComponent(Game game, GraphicsDeviceManager manager)
: base(game)
{
this.Window = game.Window;
manager.SynchronizeWithVerticalRetrace = false;
game.IsFixedTimeStep = true;
}
float FramesCount = 0f;
float TimeSinceLastUpdate = 0f;
float Fps;
public override void Draw(GameTime gameTime)
{
float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds;
this.FramesCount++;
this.TimeSinceLastUpdate += elapsed;
if (TimeSinceLastUpdate >= 1)
{
this.Fps = FramesCount / TimeSinceLastUpdate;
this.Window.Title =
string.Format("FPS: {0}", Fps);
this.FramesCount = 0;
TimeSinceLastUpdate -= 1f;
}
}
}
Observe:
- conto quantas vezes o método Draw é chamado no intervalo de 1 segundo;
- utilizo DrawableGameComponent que é uma especialização de GameComponent.
- desligo a “sincronia” que o XNA faz com o refresh rate do monitor.
Para utilizar esse componente, basta modificar o método LoadContent na classe Game . Observe:
protected override void LoadContent()
{
this.Components.Add(new FpsGameComponent(this, graphics));
//...
}
Uma conclusão interessante. Quando o game tem foco, temos um FPS próximo de 60.
Quando não tem foco, temos um FPS próximo de 40
Criando objetos para Split Screen
Dividir a tela (para mim) só faz sentido se estivermos suportando dois jogadores. Para isso, vamos alterar a carga de conteúdo para suportar “duas naves” e duas câmeras. Observe:
protected override void LoadContent()
{
this.Components.Add(new FpsGameComponent(this, graphics));
var device = graphics.GraphicsDevice;
spriteBatch = new SpriteBatch(GraphicsDevice);
spaceship1 = new GameModel(Content.Load<Model>("spaceship"))
{
Position = new Vector3(0, 3700, -2800),
Scale = new Vector3(50f),
BaseRotation = new Vector3(0, MathHelper.Pi, 0),
Rotation = new Vector3(0, MathHelper.Pi, 0)
};
models.Add(spaceship1);
spaceship2 = new GameModel(Content.Load<Model>("ship"))
{
Position = new Vector3(0, 3500, 4000),
Scale = new Vector3(0.7f),
BaseRotation = new Vector3(0, 0, 0),
Rotation = new Vector3(0, 0, 0)
};
models.Add(spaceship2);
var effect = Content.Load<Effect>("BasicTerrainEffect");
effect.Parameters["Texture"].SetValue(Content.Load<Texture2D>("grass"));
ground = new Terrain(
Content.Load<Texture2D>("heightmap1"),
effect,
30, 4800, device
);
models.Add(ground);
defaultViewport = graphics.GraphicsDevice.Viewport;
topViewport = defaultViewport;
bottomViewport = defaultViewport;
topViewport.Height /= 2;
topViewport.Height--;
bottomViewport.Height /= 2;
bottomViewport.Height--;
bottomViewport.Y =
Window.ClientBounds.Height - bottomViewport.Height;
camera1 = new ChaseCamera(
new Vector3(0, 400, 1500),
new Vector3(0, 200, 0),
new Vector3(0, 0, 0),
GraphicsDevice,
new Vector3(0, 3700, -2800), topViewport);
camera2 = new ChaseCamera(
new Vector3(0, 400, 1500),
new Vector3(0, 200, 0),
new Vector3(0, 0, 0),
GraphicsDevice,
new Vector3(0, 3500, 4000), bottomViewport);
}
Repare também como crio “duas viewports”. Uma para a metade superior, outra para a metade inferior.
Modifiquei a câmera para aceitar uma viewport e computar melhor o aspecto. Mais tarde, essas mesmas viewport são utilizadas para “pintar” a tela do jogo.
Dois jogadores, controles independentes
Se temos dois jogadores, então, precisamos ter controles independentes. Isso ocorre através do método Updade (que também atualiza a câmera).
protected override void Update(GameTime gameTime)
{
UpdateSpaceship1(gameTime);
UpdateSpaceship2(gameTime);
UpdateCamera(gameTime);
base.Update(gameTime);
}
void UpdateSpaceship1(GameTime gameTime)
{
KeyboardState keyState = Keyboard.GetState();
Vector3 rotChange = new Vector3(0, 0, 0);
if (keyState.IsKeyDown(Keys.PageUp))
rotChange += new Vector3(1, 0, 0);
if (keyState.IsKeyDown(Keys.PageDown))
rotChange += new Vector3(-1, 0, 0);
if (keyState.IsKeyDown(Keys.Left))
rotChange += new Vector3(0, 1, 0);
if (keyState.IsKeyDown(Keys.Right))
rotChange += new Vector3(0, -1, 0);
spaceship1.Rotation += rotChange * .025f;
if (!keyState.IsKeyDown(Keys.Up) &&
!keyState.IsKeyDown(Keys.Down))
return;
Matrix rotation = Matrix.CreateFromYawPitchRoll(
spaceship1.Rotation.Y, spaceship1.Rotation.X, spaceship1.Rotation.Z
);
spaceship1.Position +=
Vector3.Transform(
keyState.IsKeyDown(Keys.Up) ? Vector3.Forward : Vector3.Backward,
rotation
)
* (float)gameTime.ElapsedGameTime.TotalMilliseconds * 4;
}
void UpdateSpaceship2(GameTime gameTime)
{
KeyboardState keyState = Keyboard.GetState();
Vector3 rotChange = new Vector3(0, 0, 0);
if (keyState.IsKeyDown(Keys.R))
rotChange += new Vector3(1, 0, 0);
if (keyState.IsKeyDown(Keys.F))
rotChange += new Vector3(-1, 0, 0);
if (keyState.IsKeyDown(Keys.A))
rotChange += new Vector3(0, 1, 0);
if (keyState.IsKeyDown(Keys.D))
rotChange += new Vector3(0, -1, 0);
spaceship2.Rotation += rotChange * .025f;
if (!keyState.IsKeyDown(Keys.W) &&
!keyState.IsKeyDown(Keys.S))
return;
Matrix rotation = Matrix.CreateFromYawPitchRoll(
spaceship2.Rotation.Y, spaceship2.Rotation.X, spaceship2.Rotation.Z
);
spaceship2.Position +=
Vector3.Transform(
keyState.IsKeyDown(Keys.W) ? Vector3.Forward : Vector3.Backward,
rotation
)
* (float)gameTime.ElapsedGameTime.TotalMilliseconds * 4;
}
void UpdateCamera(GameTime gameTime)
{
KeyboardState keyState = Keyboard.GetState();
var chase = camera1 as ChaseCamera;
chase.Move(
spaceship1.Position,
spaceship1.Rotation
);
camera1.Update();
chase = camera2 as ChaseCamera;
chase.Move(
spaceship2.Position,
spaceship2.Rotation
);
camera2.Update();
}
Lindo, não! Utilizo as setas direcionais (mais PageUp e PageDown) para controlar uma das naves e as teclas A, D, W, S, R e F para controlar a outra. Mais que isso, atualizo as duas câmeras conforme a posição das naves.
Mesmo ambiente, duas viewports
Agora que temos nossos modelos e câmeras carregados e atualizados, temos que desenhar nosso jogo. Vamos dar uma olhada no método Draw.
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Viewport = topViewport;
GraphicsDevice.Clear(Color.CornflowerBlue);
foreach (IDrawableModel model in models)
model.Draw(camera1.View, camera1.Projection);
GraphicsDevice.Viewport = bottomViewport;
foreach (IDrawableModel model in models)
model.Draw(camera2.View, camera2.Projection);
base.Draw(gameTime);
}
Como você pode ver, para termos duas viewports, precisamos repetir o processo de desenho duas vezes. Repare que a principal modificação é a câmera.
Colocando as viewports lado-a-lado
Para encerrar esse post, vamos colocar as Viewports lado-a-lado.
Para fazer isso, da forma como modelamos nosso game, basta mudar o cálculo de posicionamento das viewports que escrevemos em InitializeComponent. Observe:
topViewport.Width /= 2;
topViewport.Width--;
bottomViewport.Width /= 2;
bottomViewport.Width--;
bottomViewport.X =
Window.ClientBounds.Width - bottomViewport.Width;
E …
Pronto!
Por hoje, era isso.
![]()






agosto 15th, 2011 → 22:28
[...] post, “pago” dívidas técnicas contraídas no post anterior dessa série. Se você ainda não leu o post anterior, recomendo que leia [...]