Vamos aprender XNA – Parte 15 – FPS + Creating a split screen

Publicado em 14/08/2011

1


Olá pessoal, como estamos?!

Hoje, vou mostrar como “dividir” a tela para que dois jogadores consigam “brincar” ao mesmo tempo. Observe:

image

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.

image

Quando não tem foco, temos um FPS próximo de 40

image

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 …

image

Pronto!

Por hoje, era isso.

Smiley piscando

Etiquetado:,
Publicado em: Post