Olá pessoal, como estamos?
Nesse 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 agora.
Como sempre, você pode baixar todo o código-fonte.
O que há de errado?
O código que escrevemos no post anterior atingiu seu objetivo. Ou seja, temos um game rodando, com split-screen, e controle de dois jogadores:
Entretanto, considere:
- a lógica para movimentação das duas naves ficou duplicada, rompendo o princípio de DRY, com mudança apenas nas “teclas esperadas”;
- são criadas e mantidas duas câmeras, uma para cada jogador, tudo dentro da classe game e com duplicação de lógica;
- a lógica para renderização está duplicada, uma para cada viewport;
- alguns métodos estão muito “grandes” (fazendo muita coisa);
- a classe Game1, como um todo, está assumindo mais responsabilidades do que o desejável.
Separando responsabilidades…
Fica claro que precisamos remover a lógica das “naves” da classe Game1. Além disso, fica claro que as câmeras devem ter ligação mais forte com as respectivas naves. Para isso, criei um novo GameComponent, chamado Spaceship, que tem a responsabilidade de manter o modelo e atualizar a nave. Observe:
class Spaceship : GameComponent
{
public ChaseCamera Camera { get; private set; }
public GameModel Model { get; private set; }
public Keys Left { get; set; }
public Keys Right { get; set; }
public Keys Forward { get; set; }
public Keys Backward { get; set; }
public Keys Up { get; set; }
public Keys Down { get; set; }
public Spaceship(
Game game,
GameModel model,
Viewport viewport
)
: base(game)
{
this.Model = model;
this.Camera = new ChaseCamera(
new Vector3(0, 400, 1500),
new Vector3(0, 200, 0),
new Vector3(0, 0, 0),
game.GraphicsDevice,
model.Position, viewport);
}
public override void Update(GameTime gameTime)
{
UpdateCamera();
UpdatePosition(gameTime);
base.Update(gameTime);
}
void UpdateCamera()
{
this.Camera.Move(
Model.Position,
Model.Rotation
);
this.Camera.Update();
}
void UpdatePosition(GameTime gameTime)
{
KeyboardState keyState = Keyboard.GetState();
Vector3 rotChange = new Vector3(0, 0, 0);
if (keyState.IsKeyDown(Up))
rotChange += new Vector3(1, 0, 0);
if (keyState.IsKeyDown(Down))
rotChange += new Vector3(-1, 0, 0);
if (keyState.IsKeyDown(Left))
rotChange += new Vector3(0, 1, 0);
if (keyState.IsKeyDown(Right))
rotChange += new Vector3(0, -1, 0);
Model.Rotation += rotChange * .025f;
if (!keyState.IsKeyDown(Forward) &&
!keyState.IsKeyDown(Backward))
return;
Matrix rotation = Matrix.CreateFromYawPitchRoll(
Model.Rotation.Y, Model.Rotation.X, Model.Rotation.Z
);
Model.Position +=
Vector3.Transform(
keyState.IsKeyDown(Forward) ? Vector3.Forward : Vector3.Backward,
rotation
)
* (float)gameTime.ElapsedGameTime.TotalMilliseconds * 4;
}
}
Repare como essa implementação simplifica, inclusive, os controles (entradas dos usuários). Entre as melhorias mais significativas, está a criação e manutenção da câmera.
A classe Game1, devidamente organizada
Agora que já extraímos da classe principal a lógica para manutenção da câmera e movimentação das naves, podemos dar uma “organizada” no ´seu código.
Para começar, vamos criar métodos independentes para criar cada “nave”. Repare:
private void LoadSpaceship2()
{
spaceship2 = new Spaceship(this, 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)
}, viewport2)
{
Up = Keys.R,
Down = Keys.F,
Left = Keys.A,
Right = Keys.D,
Forward = Keys.W,
Backward = Keys.S
};
this.Components.Add(spaceship2);
Models.Add(spaceship2.Model);
}
private void LoadSpaceship1()
{
spaceship1 = new Spaceship(this, 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)
}, viewport1)
{
Up = Keys.PageUp,
Down = Keys.PageDown,
Left = Keys.Left,
Right = Keys.Right,
Forward = Keys.Up,
Backward = Keys.Down
};
this.Components.Add(spaceship1);
Models.Add(spaceship1.Model);
}
A adição das naves na coleção de componentes garante que o XNA “converse” com elas.
Além disso, também separamos em um método o código que cria nosso “terreno”. Observe:
private void LoadGround()
{
var effect = Content.Load<Effect>("BasicTerrainEffect");
effect.Parameters["Texture"].SetValue(Content.Load<Texture2D>("grass"));
ground = new Terrain(
Content.Load<Texture2D>("heightmap1"),
effect,
30, 4800, this.GraphicsDevice
);
Models.Add(ground);
}
Fizemos o mesmo com o código que configura as viewports:
private void SetupViewports()
{
var defaultViewport = graphics.GraphicsDevice.Viewport;
viewport1 = defaultViewport;
viewport2 = defaultViewport;
viewport1.Width /= 2;
viewport1.Width--;
viewport2.Width /= 2;
viewport2.Width--;
viewport1.X =
Window.ClientBounds.Width - viewport2.Width;
}
Assim, simplificamos muito o código LoadContent.
protected override void LoadContent()
{
#if DEBUG
this.Components.Add(new FpsGameComponent(this, graphics));
#endif
SetupViewports();
LoadSpaceship1();
LoadSpaceship2();
LoadGround();
}
Como toda a lógica de atualização ficou dentro do componente, tiramos a sobrecarga para o método Update.
Por fim, gostaria de apresentar a nova lógica de Draw:
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
DrawScene(spaceship1.Camera);
DrawScene(spaceship2.Camera);
base.Draw(gameTime);
}
public void DrawScene(Camera camera)
{
GraphicsDevice.Viewport = camera.Viewport;
foreach (IDrawableModel model in Models)
model.Draw(camera.View, camera.Projection);
}
Tantinho melhor! Pagamos parte da dívida.
O que você mudaria?!
![]()






Publicado em 15/08/2011
0