/// <summary> /// Handles the input and movement of the character. /// </summary> /// <param name="dt">Time since last frame in simulation seconds.</param> /// <param name="previousKeyboardInput">The last frame's keyboard state.</param> /// <param name="keyboardInput">The current frame's keyboard state.</param> /// <param name="previousGamePadInput">The last frame's gamepad state.</param> /// <param name="gamePadInput">The current frame's keyboard state.</param> public void Update(float dt) { if (IsActive) { //Note that the character controller's update method is not called here; this is because it is handled within its owning space. //This method's job is simply to tell the character to move around based on the Camera and input. ////Rotate the camera of the character based on the support velocity, if a support with velocity exists. ////This can be very disorienting in some cases; that's why it is off by default! //if (CharacterController.SupportFinder.HasSupport) //{ // SupportData? data; // if (CharacterController.SupportFinder.HasTraction) // data = CharacterController.SupportFinder.TractionData; // else // data = CharacterController.SupportFinder.SupportData; // EntityCollidable support = data.Value.SupportObject as EntityCollidable; // if (support != null && !support.Entity.IsDynamic) //Having the view turned by dynamic entities is extremely confusing for the most part. // { // float dot = Vector3.Dot(support.Entity.AngularVelocity, CharacterController.Body.OrientationMatrix.Up); // Camera.Yaw += dot * dt; // } //} if (UseCameraSmoothing) { //First, find where the camera is expected to be based on the last position and the current velocity. //Note: if the character were a free-floating 6DOF character, this would need to include an angular velocity contribution. //And of course, the camera orientation would be based on the character's orientation. Camera.Position = Camera.Position + CharacterController.Body.LinearVelocity * dt; //Now compute where it should be according the physical body of the character. Vector3 up = CharacterController.Body.OrientationMatrix.Up; Vector3 bodyPosition = CharacterController.Body.Position; Vector3 goalPosition = bodyPosition + up * (CharacterController.StanceManager.CurrentStance == Stance.Standing ? StandingCameraOffset : CrouchingCameraOffset); //Usually, the camera position and the goal will be very close, if not matching completely. //However, if the character steps or has its position otherwise modified, then they will not match. //In this case, we need to correct the camera position. //To do this, first note that we can't correct infinite errors. We need to define a bounding region that is relative to the character //in which the camera can interpolate around. The most common discontinuous motions are those of upstepping and downstepping. //In downstepping, the character can teleport up to the character's MaximumStepHeight downwards. //In upstepping, the character can teleport up to the character's MaximumStepHeight upwards, and the body's CollisionMargin horizontally. //Picking those as bounds creates a constraining cylinder. Vector3 error = goalPosition - Camera.Position; float verticalError = Vector3.Dot(error, up); Vector3 horizontalError = error - verticalError * up; //Clamp the vertical component of the camera position within the bounding cylinder. if (verticalError > CharacterController.StepManager.MaximumStepHeight) { Camera.Position -= up * (CharacterController.StepManager.MaximumStepHeight - verticalError); verticalError = CharacterController.StepManager.MaximumStepHeight; } else if (verticalError < -CharacterController.StepManager.MaximumStepHeight) { Camera.Position -= up * (-CharacterController.StepManager.MaximumStepHeight - verticalError); verticalError = -CharacterController.StepManager.MaximumStepHeight; } //Clamp the horizontal distance too. float horizontalErrorLength = horizontalError.LengthSquared(); float margin = CharacterController.Body.CollisionInformation.Shape.CollisionMargin; if (horizontalErrorLength > margin * margin) { Vector3 previousHorizontalError = horizontalError; Vector3.Multiply(ref horizontalError, margin / (float)Math.Sqrt(horizontalErrorLength), out horizontalError); Camera.Position -= horizontalError - previousHorizontalError; horizontalErrorLength = margin * margin; } //Now that the error/camera position is known to lie within the constraining cylinder, we can perform a smooth correction. //This removes a portion of the error each frame. //Note that this is not framerate independent. If fixed time step is not enabled, //a different smoothing method should be applied to the final error values. //float errorCorrectionFactor = .3f; //This version is framerate independent, although it is more expensive. float errorCorrectionFactor = (float)(1 - Math.Pow(.000000001, dt)); Camera.Position += up * (verticalError * errorCorrectionFactor); Camera.Position += horizontalError * errorCorrectionFactor; } else { Camera.Position = CharacterController.Body.Position + (CharacterController.StanceManager.CurrentStance == Stance.Standing ? StandingCameraOffset : CrouchingCameraOffset) * CharacterController.Body.OrientationMatrix.Up; } Vector2 totalMovement = Vector2.Zero; #if XBOX360 Vector3 forward = Camera.WorldMatrix.Forward; forward.Y = 0; forward.Normalize(); Vector3 right = Camera.WorldMatrix.Right; totalMovement += gamePadInput.ThumbSticks.Left.Y * new Vector2(forward.X, forward.Z); totalMovement += gamePadInput.ThumbSticks.Left.X * new Vector2(right.X, right.Z); CharacterController.HorizontalMotionConstraint.MovementDirection = Vector2.Normalize(totalMovement); CharacterController.StanceManager.DesiredStance = gamePadInput.IsButtonDown(Buttons.RightStick) ? Stance.Crouching : Stance.Standing; //Jumping if (previousGamePadInput.IsButtonUp(Buttons.LeftStick) && gamePadInput.IsButtonDown(Buttons.LeftStick)) { CharacterController.Jump(); } #else //Collect the movement impulses. Vector3 movementDir; if (Input.ControlScheme == ControlScheme.Keyboard) { if (Input.KeyboardState.IsKeyDown(Keys.W)) { movementDir = Camera.World.Forward; totalMovement += Vector2.Normalize(new Vector2(movementDir.X, movementDir.Y)); } if (Input.KeyboardState.IsKeyDown(Keys.S)) { movementDir = Camera.World.Forward; totalMovement -= Vector2.Normalize(new Vector2(movementDir.X, movementDir.Y)); } if (Input.KeyboardState.IsKeyDown(Keys.A)) { movementDir = Camera.World.Left; totalMovement += Vector2.Normalize(new Vector2(movementDir.X, movementDir.Y)); } if (Input.KeyboardState.IsKeyDown(Keys.D)) { movementDir = Camera.World.Right; totalMovement += Vector2.Normalize(new Vector2(movementDir.X, movementDir.Y)); } } else if (Input.ControlScheme == ControlScheme.XboxController) { totalMovement += Vector2.Transform(Input.CurrentPad.ThumbSticks.Left, Quaternion.CreateFromAxisAngle(Vector3.UnitZ, Camera.Yaw)); } if (totalMovement == Vector2.Zero) { CharacterController.HorizontalMotionConstraint.MovementDirection = Vector2.Zero; } else { CharacterController.HorizontalMotionConstraint.MovementDirection = Vector2.Normalize(totalMovement); } CharacterController.StanceManager.DesiredStance = Input.KeyboardState.IsKeyDown(Keys.LeftShift) || Input.CurrentPad.IsButtonDown(Buttons.LeftTrigger) ? Stance.Crouching : Stance.Standing; //Jumping if (Input.CheckKeyboardJustPressed(Keys.Space) || Input.CurrentPad.IsButtonDown(Buttons.A)) { CharacterController.Jump(); } // rotate //CharacterController.Body.Orientation = Quaternion.CreateFromAxisAngle(Vector3.UnitZ, Camera.Pitch); #endif } }
/// <summary> /// Handles the input and movement of the character. /// </summary> /// <param name="dt">Time since last frame in simulation seconds.</param> /// <param name="previousKeyboardInput">The last frame's keyboard state.</param> /// <param name="keyboardInput">The current frame's keyboard state.</param> /// <param name="previousGamePadInput">The last frame's gamepad state.</param> /// <param name="gamePadInput">The current frame's keyboard state.</param> public void Update(float dt) { if (IsActive) { //Note that the character controller's update method is not called here; this is because it is handled within its owning space. //This method's job is simply to tell the character to move around. CameraControlScheme.Update(dt); Vector2 totalMovement = Vector2.Zero; if (Input.ControlScheme == ControlScheme.XboxController) { totalMovement += new Vector2(Input.CurrentPad.ThumbSticks.Left.X, Input.CurrentPad.ThumbSticks.Left.Y); CharacterController.HorizontalMotionConstraint.SpeedScale = Math.Min(totalMovement.Length(), 1); //Don't trust the game pad to output perfectly normalized values. CharacterController.HorizontalMotionConstraint.MovementDirection = totalMovement; CharacterController.StanceManager.DesiredStance = Input.CurrentPad.IsButtonDown(Buttons.B) ? Stance.Crouching : Stance.Standing; //Jumping if (Input.CurrentPadLastFrame.IsButtonUp(Buttons.A) && Input.CurrentPad.IsButtonDown(Buttons.A)) { CharacterController.Jump(); } } else if (Input.ControlScheme == ControlScheme.Keyboard) { //Collect the movement impulses. if (Input.KeyboardState.IsKeyDown(Keys.W)) { totalMovement += new Vector2(0, 1); } if (Input.KeyboardState.IsKeyDown(Keys.S)) { totalMovement += new Vector2(0, -1); } if (Input.KeyboardState.IsKeyDown(Keys.A)) { totalMovement += new Vector2(-1, 0); } if (Input.KeyboardState.IsKeyDown(Keys.D)) { totalMovement += new Vector2(1, 0); } if (totalMovement == Vector2.Zero) { CharacterController.HorizontalMotionConstraint.MovementDirection = Vector2.Zero; } else { CharacterController.HorizontalMotionConstraint.MovementDirection = Vector2.Normalize(totalMovement); } CharacterController.StanceManager.DesiredStance = Input.KeyboardState.IsKeyDown(Keys.LeftShift) ? Stance.Crouching : Stance.Standing; //Jumping if (Input.KeyboardLastFrame.IsKeyUp(Keys.Space) && Input.KeyboardState.IsKeyDown(Keys.Space)) { CharacterController.Jump(); } } CharacterController.ViewDirection = Camera.WorldMatrix.Forward; #region Grabber Input //Update grabber if (((Input.ControlScheme == ControlScheme.XboxController && Input.CheckXboxJustPressed(Buttons.X)) || (Input.ControlScheme == ControlScheme.Keyboard && Input.CheckKeyboardPress(Keys.E))) && !grabber.IsUpdating) { //Find the earliest ray hit RayCastResult raycastResult; if (Space.RayCast(new Ray(Renderer.Camera.Position, Renderer.Camera.WorldMatrix.Forward), 6, RayCastFilter, out raycastResult)) { var entityCollision = raycastResult.HitObject as EntityCollidable; //If there's a valid ray hit, then grab the connected object! if (entityCollision != null) { var tag = entityCollision.Entity.Tag as GameModel; if (tag != null) { tag.Texture.GameProperties.RayCastHit(tag.Texture.GameProperties); if (!tag.Texture.Wireframe && tag.Texture.GameProperties.Grabbable) { grabber.Setup(entityCollision.Entity, raycastResult.HitData.Location); try { CollisionRules.AddRule(entityCollision.Entity, CharacterController.Body, CollisionRule.NoBroadPhase); } catch { } grabDistance = raycastResult.HitData.T; } else { Program.Game.Loader.InvalidApplicationRemovalGrab.Play(); } } else { Program.Game.Loader.InvalidApplicationRemovalGrab.Play(); } } } else { Program.Game.Loader.InvalidApplicationRemovalGrab.Play(); } } else if (grabber.IsUpdating) { if ((Input.ControlScheme == ControlScheme.XboxController && Input.CheckXboxJustPressed(Buttons.X)) || (Input.ControlScheme == ControlScheme.Keyboard && Input.CheckKeyboardPress(Keys.E))) { try { CollisionRules.RemoveRule(grabber.Entity, CharacterController.Body); } catch { } grabber.Release(); } grabber.GoalPosition = Renderer.Camera.Position + Renderer.Camera.WorldMatrix.Forward * grabDistance; } #endregion } }