/// <summary> /// Constructs a new step manager for a character. /// </summary> /// <param name="characterBody">The character's body.</param> /// <param name="contactCategorizer">Contact categorizer used by the character.</param> /// <param name="supportFinder">Support finder used by the character.</param> /// <param name="queryManager">Query provider to use in checking for obstructions.</param> /// <param name="horizontalMotionConstraint">Horizontal motion constraint used by the character. Source of 3d movement direction.</param> public StepManager(Cylinder characterBody, CharacterContactCategorizer contactCategorizer, SupportFinder supportFinder, QueryManager queryManager, HorizontalMotionConstraint horizontalMotionConstraint) { this.characterBody = characterBody; currentQueryObject = new ConvexCollidable <CylinderShape>(characterBody.CollisionInformation.Shape); ContactCategorizer = contactCategorizer; SupportFinder = supportFinder; QueryManager = queryManager; HorizontalMotionConstraint = horizontalMotionConstraint; //The minimum step height is just barely above where the character would generally find the ground. //This helps avoid excess tests. minimumUpStepHeight = CollisionDetectionSettings.AllowedPenetration * 1.1f;// Math.Max(0, -.01f + character.Body.CollisionInformation.Shape.CollisionMargin * (1 - character.SupportFinder.sinMaximumSlope)); }
/// <summary> /// Constructs a new character controller. /// </summary> /// <param name="position">Initial position of the character.</param> /// <param name="height">Height of the character body while standing.</param> /// <param name="crouchingHeight">Height of the character body while crouching.</param> /// <param name="radius">Radius of the character body.</param> /// <param name="margin">Radius of 'rounding' applied to the cylindrical body. Higher values make the cylinder's edges more rounded. /// The margin is contained within the cylinder's height and radius, so it must not exceed the radius or height of the cylinder.</param> /// <param name="mass">Mass of the character body.</param> /// <param name="maximumTractionSlope">Steepest slope, in radians, that the character can maintain traction on.</param> /// <param name="maximumSupportSlope">Steepest slope, in radians, that the character can consider a support.</param> /// <param name="standingSpeed">Speed at which the character will try to move while crouching with a support that provides traction. /// Relative velocities with a greater magnitude will be decelerated.</param> /// <param name="crouchingSpeed">Speed at which the character will try to move while crouching with a support that provides traction. /// Relative velocities with a greater magnitude will be decelerated.</param> /// <param name="tractionForce">Maximum force that the character can apply while on a support which provides traction.</param> /// <param name="slidingSpeed">Speed at which the character will try to move while on a support that does not provide traction. /// Relative velocities with a greater magnitude will be decelerated.</param> /// <param name="slidingForce">Maximum force that the character can apply while on a support which does not provide traction</param> /// <param name="airSpeed">Speed at which the character will try to move with no support. /// The character will not be decelerated while airborne.</param> /// <param name="airForce">Maximum force that the character can apply with no support.</param> /// <param name="jumpSpeed">Speed at which the character leaves the ground when it jumps</param> /// <param name="slidingJumpSpeed">Speed at which the character leaves the ground when it jumps without traction</param> /// <param name="maximumGlueForce">Maximum force the vertical motion constraint is allowed to apply in an attempt to keep the character on the ground.</param> public CharacterController( Vector3 position = new Vector3(), float height = 1.7f, float crouchingHeight = 1.7f * .7f, float radius = 0.6f, float margin = 0.1f, float mass = 10f, float maximumTractionSlope = 0.8f, float maximumSupportSlope = 1.3f, float standingSpeed = 8f, float crouchingSpeed = 3f, float tractionForce = 1000, float slidingSpeed = 6, float slidingForce = 50, float airSpeed = 1, float airForce = 250, float jumpSpeed = 4.5f, float slidingJumpSpeed = 3, float maximumGlueForce = 5000 ) { if (margin > radius || margin > crouchingHeight || margin > height) { throw new ArgumentException("Margin must not be larger than the character's radius or height."); } Body = new Cylinder(position, height, radius, mass); Body.IgnoreShapeChanges = true; //Wouldn't want inertia tensor recomputations to occur when crouching and such. Body.CollisionInformation.Shape.CollisionMargin = margin; //Making the character a continuous object prevents it from flying through walls which would be pretty jarring from a player's perspective. Body.PositionUpdateMode = PositionUpdateMode.Continuous; Body.LocalInertiaTensorInverse = new Matrix3x3(); //TODO: In v0.16.2, compound bodies would override the material properties that get set in the CreatingPair event handler. //In a future version where this is changed, change this to conceptually minimally required CreatingPair. Body.CollisionInformation.Events.DetectingInitialCollision += RemoveFriction; Body.LinearDamping = 0; ContactCategorizer = new CharacterContactCategorizer(maximumTractionSlope, maximumSupportSlope); QueryManager = new QueryManager(Body, ContactCategorizer); SupportFinder = new SupportFinder(Body, QueryManager, ContactCategorizer); HorizontalMotionConstraint = new HorizontalMotionConstraint(Body, SupportFinder); HorizontalMotionConstraint.PositionAnchorDistanceThreshold = radius * 0.25f; VerticalMotionConstraint = new VerticalMotionConstraint(Body, SupportFinder, maximumGlueForce); StepManager = new StepManager(Body, ContactCategorizer, SupportFinder, QueryManager, HorizontalMotionConstraint); StanceManager = new StanceManager(Body, crouchingHeight, QueryManager, SupportFinder); PairLocker = new CharacterPairLocker(Body); StandingSpeed = standingSpeed; CrouchingSpeed = crouchingSpeed; TractionForce = tractionForce; SlidingSpeed = slidingSpeed; SlidingForce = slidingForce; AirSpeed = airSpeed; AirForce = airForce; JumpSpeed = jumpSpeed; SlidingJumpSpeed = slidingJumpSpeed; //Enable multithreading for the characters. IsUpdatedSequentially = false; //Link the character body to the character controller so that it can be identified by the locker. //Any object which replaces this must implement the ICharacterTag for locking to work properly. Body.CollisionInformation.Tag = new CharacterSynchronizer(Body); }
/// <summary> /// Constructs a new character controller. /// </summary> /// <param name="position">Initial position of the character.</param> /// <param name="radius">Radius of the character body.</param> /// <param name="mass">Mass of the character body.</param> /// <param name="maximumTractionSlope">Steepest slope, in radians, that the character can maintain traction on.</param> /// <param name="maximumSupportSlope">Steepest slope, in radians, that the character can consider a support.</param> /// <param name="speed">Speed at which the character will try to move while crouching with a support that provides traction. /// Relative velocities with a greater magnitude will be decelerated.</param> /// <param name="tractionForce">Maximum force that the character can apply while on a support which provides traction.</param> /// <param name="slidingSpeed">Speed at which the character will try to move while on a support that does not provide traction. /// Relative velocities with a greater magnitude will be decelerated.</param> /// <param name="slidingForce">Maximum force that the character can apply while on a support which does not provide traction</param> /// <param name="airSpeed">Speed at which the character will try to move with no support. /// The character will not be decelerated while airborne.</param> /// <param name="airForce">Maximum force that the character can apply with no support.</param> /// <param name="jumpSpeed">Speed at which the character leaves the ground when it jumps</param> /// <param name="slidingJumpSpeed">Speed at which the character leaves the ground when it jumps without traction</param> /// <param name="maximumGlueForce">Maximum force the vertical motion constraint is allowed to apply in an attempt to keep the character on the ground.</param> public SphereCharacterController( Vector3 position = new Vector3(), float radius = .85f, float mass = 10f, float maximumTractionSlope = 0.8f, float maximumSupportSlope = 1.3f, float speed = 8f, float tractionForce = 1000, float slidingSpeed = 6, float slidingForce = 50, float airSpeed = 1, float airForce = 250, float jumpSpeed = 4.5f, float slidingJumpSpeed = 3, float maximumGlueForce = 5000) { Body = new Sphere(position, radius, mass); Body.IgnoreShapeChanges = true; //Wouldn't want inertia tensor recomputations to occur if the shape changes. //Making the character a continuous object prevents it from flying through walls which would be pretty jarring from a player's perspective. Body.PositionUpdateMode = PositionUpdateMode.Continuous; Body.LocalInertiaTensorInverse = new Matrix3x3(); //TODO: In v0.16.2, compound bodies would override the material properties that get set in the CreatingPair event handler. //In a future version where this is changed, change this to conceptually minimally required CreatingPair. Body.CollisionInformation.Events.DetectingInitialCollision += RemoveFriction; Body.LinearDamping = 0; ContactCategorizer = new CharacterContactCategorizer(maximumTractionSlope, maximumSupportSlope); QueryManager = new QueryManager(Body, ContactCategorizer); SupportFinder = new SupportFinder(Body, QueryManager, ContactCategorizer); HorizontalMotionConstraint = new HorizontalMotionConstraint(Body, SupportFinder); HorizontalMotionConstraint.PositionAnchorDistanceThreshold = (3f / 17f) * radius; VerticalMotionConstraint = new VerticalMotionConstraint(Body, SupportFinder, maximumGlueForce); PairLocker = new CharacterPairLocker(Body); Speed = speed; TractionForce = tractionForce; SlidingSpeed = slidingSpeed; SlidingForce = slidingForce; AirSpeed = airSpeed; AirForce = airForce; JumpSpeed = jumpSpeed; SlidingJumpSpeed = slidingJumpSpeed; //Enable multithreading for the sphere characters. //See the bottom of the Update method for more information about using multithreading with this character. IsUpdatedSequentially = false; //Link the character body to the character controller so that it can be identified by the locker. //Any object which replaces this must implement the ICharacterTag for locking to work properly. Body.CollisionInformation.Tag = new CharacterSynchronizer(Body); }
void IBeforeSolverUpdateable.Update(float dt) { //Someone may want to use the Body.CollisionInformation.Tag for their own purposes. //That could screw up the locking mechanism above and would be tricky to track down. //Consider using the making the custom tag implement ICharacterTag, modifying LockCharacterPairs to analyze the different Tag type, or using the Entity.Tag for the custom data instead. Debug.Assert(Body.CollisionInformation.Tag is ICharacterTag, "The character.Body.CollisionInformation.Tag must implement ICharacterTag to link the CharacterController and its body together for character-related locking to work in multithreaded simulations."); SupportData supportData; HorizontalMotionConstraint.UpdateMovementBasis(ref viewDirection); //We can't let multiple characters manage the same pairs simultaneously. Lock it up! PairLocker.LockCharacterPairs(); try { CorrectContacts(); bool hadSupport = SupportFinder.HasSupport; SupportFinder.UpdateSupports(ref HorizontalMotionConstraint.movementDirection3d); supportData = SupportFinder.SupportData; //Compute the initial velocities relative to the support. Vector3 relativeVelocity; ComputeRelativeVelocity(ref supportData, out relativeVelocity); float verticalVelocity = Vector3.Dot(supportData.Normal, relativeVelocity); //Don't attempt to use an object as support if we are flying away from it (and we were never standing on it to begin with). if (SupportFinder.HasSupport && !hadSupport && verticalVelocity < 0) { SupportFinder.ClearSupportData(); supportData = new SupportData(); } //Attempt to jump. if (tryToJump && StanceManager.CurrentStance != Stance.Crouching) //Jumping while crouching would be a bit silly. { //In the following, note that the jumping velocity changes are computed such that the separating velocity is specifically achieved, //rather than just adding some speed along an arbitrary direction. This avoids some cases where the character could otherwise increase //the jump speed, which may not be desired. if (SupportFinder.HasTraction) { //The character has traction, so jump straight up. float currentDownVelocity = Vector3.Dot(Down, relativeVelocity); //Target velocity is JumpSpeed. float velocityChange = Math.Max(jumpSpeed + currentDownVelocity, 0); ApplyJumpVelocity(ref supportData, Down * -velocityChange, ref relativeVelocity); //Prevent any old contacts from hanging around and coming back with a negative depth. foreach (var pair in Body.CollisionInformation.Pairs) { pair.ClearContacts(); } SupportFinder.ClearSupportData(); supportData = new SupportData(); } else if (SupportFinder.HasSupport) { //The character does not have traction, so jump along the surface normal instead. float currentNormalVelocity = Vector3.Dot(supportData.Normal, relativeVelocity); //Target velocity is JumpSpeed. float velocityChange = Math.Max(slidingJumpSpeed - currentNormalVelocity, 0); ApplyJumpVelocity(ref supportData, supportData.Normal * -velocityChange, ref relativeVelocity); //Prevent any old contacts from hanging around and coming back with a negative depth. foreach (var pair in Body.CollisionInformation.Pairs) { pair.ClearContacts(); } SupportFinder.ClearSupportData(); supportData = new SupportData(); } } tryToJump = false; //Try to step! Vector3 newPosition; //Note: downstepping is often not required. //It's only really there for games that expect to be able to run down stairs at 40 miles an hour without zipping off into the void. //Most of the time, you can just comment out downstepping, and so long as the character is running at a reasonable speed, //gravity will do the work. //If your game would work without teleportation-based downstepping, it's probably a good idea to comment it out. //Downstepping can be fairly expensive. //You can also avoid doing upstepping by fattening up the character's margin, turning it into more of a capsule. //Instead of teleporting up steps, it would slide up. //Without teleportation-based upstepping, steps usually need to be quite a bit smaller (i.e. fairly normal sized, instead of 2 feet tall). if (StepManager.TryToStepDown(out newPosition) || StepManager.TryToStepUp(out newPosition)) { supportData = TeleportToPosition(newPosition, dt); } if (StanceManager.UpdateStance(out newPosition)) { supportData = TeleportToPosition(newPosition, dt); } } finally { PairLocker.UnlockCharacterPairs(); } //Tell the constraints to get ready to solve. HorizontalMotionConstraint.UpdateSupportData(); VerticalMotionConstraint.UpdateSupportData(); //Update the horizontal motion constraint's state. if (supportData.SupportObject != null) { if (SupportFinder.HasTraction) { HorizontalMotionConstraint.MovementMode = MovementMode.Traction; HorizontalMotionConstraint.TargetSpeed = StanceManager.CurrentStance == Stance.Standing ? standingSpeed : crouchingSpeed; HorizontalMotionConstraint.MaximumForce = tractionForce; } else { HorizontalMotionConstraint.MovementMode = MovementMode.Sliding; if (StanceManager.CurrentStance == Stance.Standing) { HorizontalMotionConstraint.TargetSpeed = Math.Min(standingSpeed, slidingSpeed); HorizontalMotionConstraint.MaximumForce = Math.Min(tractionForce, slidingForce); } else { HorizontalMotionConstraint.TargetSpeed = Math.Min(crouchingSpeed, slidingSpeed); HorizontalMotionConstraint.MaximumForce = Math.Min(tractionForce, slidingForce); } } } else { HorizontalMotionConstraint.MovementMode = MovementMode.Floating; HorizontalMotionConstraint.TargetSpeed = airSpeed; HorizontalMotionConstraint.MaximumForce = airForce; } HorizontalMotionConstraint.TargetSpeed *= SpeedScale; }
void IBeforeSolverUpdateable.Update(float dt) { //Someone may want to use the Body.CollisionInformation.Tag for their own purposes. //That could screw up the locking mechanism above and would be tricky to track down. //Consider using the making the custom tag implement ICharacterTag, modifying LockCharacterPairs to analyze the different Tag type, or using the Entity.Tag for the custom data instead. Debug.Assert(Body.CollisionInformation.Tag is ICharacterTag, "The character.Body.CollisionInformation.Tag must implement ICharacterTag to link the SphereCharacterController and its body together for character-related locking to work in multithreaded simulations."); SupportData supportData; HorizontalMotionConstraint.UpdateMovementBasis(ref viewDirection); //We can't let multiple characters manage the same pairs simultaneously. Lock it up! PairLocker.LockCharacterPairs(); try { bool hadSupport = SupportFinder.HasSupport; SupportFinder.UpdateSupports(ref HorizontalMotionConstraint.movementDirection3d); supportData = SupportFinder.SupportData; //Compute the initial velocities relative to the support. Vector3 relativeVelocity; ComputeRelativeVelocity(ref supportData, out relativeVelocity); float verticalVelocity = Vector3.Dot(supportData.Normal, relativeVelocity); //Don't attempt to use an object as support if we are flying away from it (and we were never standing on it to begin with). if (SupportFinder.HasSupport && !hadSupport && verticalVelocity < 0) { SupportFinder.ClearSupportData(); supportData = new SupportData(); } //Attempt to jump. if (tryToJump) //Jumping while crouching would be a bit silly. { //In the following, note that the jumping velocity changes are computed such that the separating velocity is specifically achieved, //rather than just adding some speed along an arbitrary direction. This avoids some cases where the character could otherwise increase //the jump speed, which may not be desired. if (SupportFinder.HasTraction) { //The character has traction, so jump straight up. float currentDownVelocity; Vector3.Dot(ref down, ref relativeVelocity, out currentDownVelocity); //Target velocity is JumpSpeed. float velocityChange = Math.Max(jumpSpeed + currentDownVelocity, 0); ApplyJumpVelocity(ref supportData, down * -velocityChange, ref relativeVelocity); //Prevent any old contacts from hanging around and coming back with a negative depth. foreach (var pair in Body.CollisionInformation.Pairs) { pair.ClearContacts(); } SupportFinder.ClearSupportData(); supportData = new SupportData(); } else if (SupportFinder.HasSupport) { //The character does not have traction, so jump along the surface normal instead. float currentNormalVelocity = Vector3.Dot(supportData.Normal, relativeVelocity); //Target velocity is JumpSpeed. float velocityChange = Math.Max(slidingJumpSpeed - currentNormalVelocity, 0); ApplyJumpVelocity(ref supportData, supportData.Normal * -velocityChange, ref relativeVelocity); //Prevent any old contacts from hanging around and coming back with a negative depth. foreach (var pair in Body.CollisionInformation.Pairs) { pair.ClearContacts(); } SupportFinder.ClearSupportData(); supportData = new SupportData(); } } tryToJump = false; } finally { PairLocker.UnlockCharacterPairs(); } //Tell the constraints to get ready to solve. HorizontalMotionConstraint.UpdateSupportData(); VerticalMotionConstraint.UpdateSupportData(); //Update the horizontal motion constraint's state. if (supportData.SupportObject != null) { if (SupportFinder.HasTraction) { HorizontalMotionConstraint.MovementMode = MovementMode.Traction; HorizontalMotionConstraint.TargetSpeed = speed; HorizontalMotionConstraint.MaximumForce = tractionForce; } else { HorizontalMotionConstraint.MovementMode = MovementMode.Sliding; HorizontalMotionConstraint.TargetSpeed = slidingSpeed; HorizontalMotionConstraint.MaximumForce = slidingForce; } } else { HorizontalMotionConstraint.MovementMode = MovementMode.Floating; HorizontalMotionConstraint.TargetSpeed = airSpeed; HorizontalMotionConstraint.MaximumForce = airForce; } HorizontalMotionConstraint.TargetSpeed *= SpeedScale; }