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)
所有相机类都要通过单元测试进行测试。如果您尝试创建自己的相机类请务必对其进行测试,避免出错。