SpaceCamera类

SpaceCamera类比SimpleCamera类复杂得多,赛车游戏的ChaseCamera看起来倒简单点。主要的原因这是我写的第一个空间相机类,其中使用到的四元数的数学知识并不那么容易。该相机支持三种模式:菜单画面的相机,游戏中的相机,单元测试中的自由相机。Rocket Command游戏的SpaceCamera有点简单,因为四元数被删除了,只允许俯仰和偏航火箭,而火箭绕Z轴旋转被引擎自动处理,这使得飞行起来容易些,不容易迷失方向。这个变化的主要原因是为了使用Xbox 360手柄也能很好地控制火箭,难度比初版Rocket Commander大。

快速浏览一下SpaceCamera中的重要方法,它们处理所有输入并更新相机位置:

/// <summary>
/// Handle player input for the game.
/// This is where all the input happens in the game.
/// </summary>
private void HandlePlayerInput()
{
  if (Player.lifeTimeMs < Player.LifeTimeZoomAndAccelerateMs)
  {
    float speedPercentage =
      Player.lifeTimeMs / (float)Player.LifeTimeZoomAndAccelerateMs;
    // Use quadradric product for better speed up effect
    Player.SetStartingSpeed(speedPercentage * speedPercentage);

    // Always move forward
    Translate(Player.Speed * BaseGame.MoveFactorPerSecond *
      Player.MovementSpeedPerSecond, MoveDirections.Z);
    if (Player.gameTimeMs < 100)
    {
      yawRotation = 0;
      pitchRotation = 0;
      pos = Vector3.Zero;
    } // if
  } // if

前面的代码增加火箭速度,如果游戏刚刚开始,同时也重置旋转角度和位置。接下来,您检查游戏是否结束和处理特殊情况使之能移动。然后处理鼠标和键盘输入。这些是我初次实现SpaceCamera类的原始代码,更多代码在以后添加以支持更多的输入设备:

#region Mouse/keyboard support
if (Input.MouseXMovement != 0.0f ||
  Input.MouseYMovement != 0.0f)
{
  float xMovement = Input.MouseXMovement;
  float yMovement = Input.MouseYMovement;
  Rotate(RotationAxis.Yaw, -xMovement * rotationFactor);
  Rotate(RotationAxis.Pitch, -yMovement * rotationFactor);
} // if (Mouse.left.Pressed)

// Use asdw (qwerty keyboard), aoew (dvorak keyboard) or
// cursor keys (all keyboards?) to move around.
// Note: If you want to change any keys, use Settings!
if (Input.Keyboard.IsKeyDown(moveForwardKey) ||
  Input.Keyboard.IsKeyDown(Keys.Up) ||
  Input.Keyboard.IsKeyDown(Keys.NumPad8))
{
  float oldPlayerSpeed = Player.Speed;
  Player.Speed += 0.75f * BaseGame.MoveFactorPerSecond;
} // if
if (Input.Keyboard.IsKeyDown(moveBackwardKey) ||
  Input.Keyboard.IsKeyDown(Keys.Down) ||
  Input.Keyboard.IsKeyDown(Keys.NumPad2))
{
  float oldPlayerSpeed = Player.Speed;
  Player.Speed -= 0.75f * BaseGame.MoveFactorPerSecond;
} // if

if (Player.speedItemTimeout > 0)
{
  Player.speedItemTimeout -= BaseGame.ElapsedTimeThisFrameInMs;
  if (Player.speedItemTimeout < 0)
  {
    Player.speedItemTimeout = 0;
    // Reduce to max. possible speed
    if (Player.Speed > Player.MaxSpeedWithoutItem)
      Player.Speed = Player.MaxSpeedWithoutItem;
  } // if
} // if

// Adjust current speed by the current player speed.
float moveFactor = Player.Speed * maxMoveFactor;
float slideFactor = maxSlideFactor;

// Always move forward
Translate(+moveFactor, MoveDirections.Z);

// Slide
if (Input.Keyboard.IsKeyDown(moveLeftKey) ||
  Input.Keyboard.IsKeyDown(Keys.Left) ||
  Input.Keyboard.IsKeyDown(Keys.NumPad4))
{
  consumedAdditionalFuel = true;
  Translate(-slideFactor, MoveDirections.X);
} // if
if (Input.Keyboard.IsKeyDown(moveRightKey) ||
  Input.Keyboard.IsKeyDown(Keys.Right) ||
  Input.Keyboard.IsKeyDown(Keys.NumPad6))
{
  consumedAdditionalFuel = true;
  Translate(+slideFactor, MoveDirections.X);
} // if
// Up/down
if (Input.Keyboard.IsKeyDown(Keys.F))
{
  Translate(+slideFactor, MoveDirections.Y);
} // if
if (Input.Keyboard.IsKeyDown(Keys.V))
{
  Translate(-slideFactor, MoveDirections.Y);
} // if
#endregion

为了支持Xbox 360下面的代码是在2006年初,这个游戏发布前的一天被添加的。实现Xinput很简单,我很高兴XNA在所有的输入类中都使用Xinput。把所有输入设备都放在一个命名空间中,这个主意很好,唯一的问题是Xbox 360运行时中没有鼠标类。就算这个类不支持,微软也应实现一个虚拟类,至少可以实现当鼠标可选时,用不着改变所有的输入代码。幸好,你以通过自己的Input类解决了这个问题。

下面是Rocket Commander中的X360控制代码:

#region Input support for the XBox360 controller
// 2006-03-09: Added Input support
rotationFactor = 3.0f * BaseGame.MoveFactorPerSecond;

// Change camera rotation when right thumb is used.
if (Input.GamePad.ThumbSticks.Right.X != 0.0f ||
  Input.GamePad.ThumbSticks.Right.Y != 0.0f)
{
  float xMovement = Input.GamePad.ThumbSticks.Right.X;
  float yMovement = Input.GamePad.ThumbSticks.Right.Y;
  Rotate(RotationAxis.Yaw, -xMovement * rotationFactor);
  Rotate(RotationAxis.Pitch, yMovement * rotationFactor);
} // if (Mouse.left.Pressed)

// Use left thumb for moving around
if (Input.GamePad.ThumbSticks.Left.Y != 0)
{
  float oldPlayerSpeed = Player.Speed;
  Player.Speed += 0.75f * Input.GamePad.ThumbSticks.Left.Y *
    BaseGame.MoveFactorPerSecond;

  // Only decrease fuel if change happened
  if (oldPlayerSpeed != Player.Speed)
    consumedAdditionalFuel = true;
} // if

// Slide
if (Input.GamePad.ThumbSticks.Left.X != 0)
{
  consumedAdditionalFuel = true;
    Translate(slideFactor * Input.GamePad.ThumbSticks.Left.X * 2,
      MoveDirections.X);
  } // if
  #endregion
} // HandlePlayerInput()

你可以看到代码使用了大量的辅助方法,像Rotate,Translate、Input类中频繁使用的属性和从BaseGame类获得的有用的数值,如MoveFactorPerSecond。

Translate方法沿着x , y ,或z轴移动目前的相机。 x轴是用来左右移动,在Rocket Commander中使用A和D键或方向键实现。 y轴用来上下移动而z轴用来前进后退。在Rocket Commander中z轴是最重要的,你沿着这根轴以极高的速度移动。

/// <summary>
/// Translate into x, y or z axis with a specfic amount.
/// </summary>
/// <param name="amount">Amount</param>
/// <param name="direction">Direction</param>
private void Translate(float amount, MoveDirections direction)
{
  Vector3 dir =
    direction == MoveDirections.X ? XAxis :
    direction == MoveDirections.Y ? YAxis : ZAxis;
  pos += dir * amount;
} // Translate(amount, direction)

最后,Rotate旋转相机。Yaw是左右转动和Picth让你上下转动。原版本的Rocket Commander使用四元数,它还允许你翻滚火箭。在XNA版本的Rocket Commander中火箭自动调整,这样在Xbox 360上更容控制。

/// <summary>
/// Rotate around pitch, roll or yaw axis.
/// </summary>
/// <param name="axis">Axis</param>
/// <param name="angle">Angle</param>
private void Rotate(RotationAxis axis, float angle)
{
  if (axis == RotationAxis.Yaw)
    yawRotation -= angle;
  else
    pitchRotation -= angle;
} // Rotate(axis, angle)

所有相机类都要通过单元测试进行测试。如果您尝试创建自己的相机类请务必对其进行测试,避免出错。