From Unify Community Wiki
Revision as of 00:02, 12 January 2010 by Eric5h5 (Talk | contribs)

Jump to: navigation, search

Author: Eric Haines (Eric5h5)


This is an enhanced version of the FPSWalker script found in Standard Assets. It allows for both walking and running (either by toggle or by holding down a run key), the capability of detecting falling distances, optionally sliding down slopes (both those above the Slope Limit and/or objects specifically tagged "Slide"), optional air control, and special patented* Anti-Bump™ code that eliminates that irritating "bumpity bumpity bumpa bump" you normally get when trying to walk down moderately inclined slopes, as the player constantly toggles between walking and falling.

*It's not really patented...it's just one tiny change actually.


In any situation where you'd normally use the standard FPSWalker script, you can replace it with this instead. The original parameters are still there and work as usual (although "speed" is now "walk speed").

  • Walk Speed: how fast the player moves when walking (the default).
  • Run Speed: how fast the player moves when running.
  • Toggle Run: If checked, the player can toggle between running and walking by pressing the run button (there must be a button set up in the Input Manager called "Run"). If not checked, the player normally walks unless holding down the run button.
  • Jump Speed: How high the player jumps when hitting the jump button (there must be a button set up in the Input Manager called "Jump", which there is by default).
  • Gravity: How fast the player falls when not standing on anything.
  • Falling Damage Threshold: How many units the player can fall before taking damage when landing. The script as-is merely prints the total number of units that the player fell if this happens, but the FallingDamageAlert function can be changed to do anything the programmer desires. The "fallDistance" local variable in this function contains the number of vertical units between the initial point of "catching air" and the final impact point. If falling damage is irrelevant, change the number to "Infinity" in the inspector, and the function will never be called.
  • Slide When Over Slope Limit: If checked, the player will slide down slopes that are greater than the Slope Limit as defined by the Character Controller. Attempting to jump up such slopes will also fail. The player has no control until resting on a surface that is under the Slope Limit.
  • Slide On Tagged Objects: If checked, the player will slide down any objects tagged "Slide" when standing on them, regardless of how much or little they are sloped. (The tag "Slide" must be added to the Tag Manager.) This can be used to create chutes, icy surfaces, etc. Note that tagged objects with zero slope will cause sliding in an undefined direction.
  • Slide Speed: How fast the player slides when on slopes as defined above.
  • Air Control: If checked, the player will be able to control movement while in the air, except when Slide When Over Slope Limit is enabled and the player is jumping off a slope over the slope limit (otherwise the player would be able to jump up supposedly inaccessible slopes).

The slope sliding is quite basic: the player is either sliding or not, and has no lateral control when sliding. One issue that may surface is that, under some circumstances, attempting to force oneself up a slippery slope will result in annoying jittering, as the character controller moves forward a bit one frame only to slide back the next frame, then moves forward a bit again, etc. Possible solutions are: add code to the script so this doesn't happen, design levels so as to minimize this, or ignore it and repeat to yourself "If it's good enough for UT2004, it's good enough for me." It's also not a good idea to create situations where the player slides into a V-shaped wedge valley where both slopes are unscalable, since the player will be stuck at the bottom, jittering for eternity. Not fun.

JavaScript - FPSWalkerEnhanced.js

<javascript>var walkSpeed = 6.0; var runSpeed = 11.0;

// If checked, the run key toggles between running and walking. Otherwise player runs if the key is held down and walks otherwise // There must be a button set up in the Input Manager called "Run" var toggleRun = false;

var jumpSpeed = 8.0; var gravity = 20.0;

// Units that player can fall before a falling damage function is run. To disable, type "infinity" in the inspector var fallingDamageThreshold = 10.0;

// If the player ends up on a slope which is at least the Slope Limit as set on the character controller, then he will slide down var slideWhenOverSlopeLimit = false;

// If checked and the player is on an object tagged "Slide", he will slide down it regardless of the slope limit var slideOnTaggedObjects = false;

var slideSpeed = 12.0;

// If checked, then the player can change direction while in the air var airControl = false;

private var moveDirection = Vector3.zero; private var grounded = false; private var controller : CharacterController; private var myTransform : Transform; private var speed : float; private var hit : RaycastHit; private var fallStartLevel : float; private var falling = false; private var slideLimit : float; private var rayDistance : float; private var contactPoint : Vector3; private var playerControl = false;

function Start () { controller = GetComponent(CharacterController); myTransform = transform; speed = walkSpeed; rayDistance = controller.height * .5 + controller.radius; slideLimit = controller.slopeLimit - .1; }

function FixedUpdate() { if (grounded) { var sliding = false; // See if surface immediately below should be slid down. We use this normally rather than a ControllerColliderHit point, // because that interferes with step climbing amongst other annoyances if (Physics.Raycast(myTransform.position, -Vector3.up, hit, rayDistance)) { if (Vector3.Angle(hit.normal, Vector3.up) > slideLimit) sliding = true; } // However, just raycasting straight down from the center can fail when on steep slopes // So if the above raycast didn't catch anything, raycast down from the stored ControllerColliderHit point instead else { Physics.Raycast(contactPoint + Vector3.up, -Vector3.up, hit); if (Vector3.Angle(hit.normal, Vector3.up) > slideLimit) sliding = true; }

// If we were falling, and we fell a vertical distance greater than the threshold, run a falling damage routine if (falling) { falling = false; if (myTransform.position.y < fallStartLevel - fallingDamageThreshold) FallingDamageAlert (fallStartLevel - myTransform.position.y); }

// If running isn't on a toggle, then use the appropriate speed depending on whether the run button is down if (!toggleRun) speed = Input.GetButton("Run")? runSpeed : walkSpeed;

// If sliding (and it's allowed), or if we're on an object tagged "Slide", get a vector pointing down the slope we're on if ( (sliding && slideWhenOverSlopeLimit) || (slideOnTaggedObjects && hit.collider.tag == "Slide") ) { var hitNormal = hit.normal; moveDirection = Vector3(hitNormal.x, -hitNormal.y, hitNormal.z); Vector3.OrthoNormalize (hitNormal, moveDirection); moveDirection *= slideSpeed; playerControl = false; } // Otherwise recalculate moveDirection directly from axes, adding a bit of -y to avoid bumping down inclines else { moveDirection = Vector3(Input.GetAxis("Horizontal"), -.7, Input.GetAxis("Vertical")); moveDirection = myTransform.TransformDirection(moveDirection) * speed; playerControl = true; }

// Jump! if (Input.GetButton("Jump")) moveDirection.y = jumpSpeed; } else { // If we stepped over a cliff or something, set the height at which we started falling if (!falling) { falling = true; fallStartLevel = myTransform.position.y; }

// If air control is allowed, check movement but don't touch the y component if (airControl && playerControl) { moveDirection.x = Input.GetAxis("Horizontal") * speed; moveDirection.z = Input.GetAxis("Vertical") * speed; moveDirection = myTransform.TransformDirection(moveDirection); } }

// Apply gravity moveDirection.y -= gravity * Time.deltaTime;

// Move the controller, and set grounded true or false depending on whether we're standing on something grounded = (controller.Move(moveDirection * Time.deltaTime) & CollisionFlags.CollidedBelow) != 0; }

function Update () { // If the run button is set to toggle, then switch between walk/run speed. (We use Update for this... // FixedUpdate is a poor place to use GetButtonDown, since it doesn't necessarily run every frame and can miss the event) if (toggleRun && grounded && Input.GetButtonDown ("Run")) speed = (speed == walkSpeed? runSpeed : walkSpeed); }

// Store point that we're in contact with for use in FixedUpdate if needed function OnControllerColliderHit (hit : ControllerColliderHit) { contactPoint = hit.point; }

// If falling damage occured, this is the place to do something about it. You can make the player // have hitpoints and remove some of them based on the distance fallen, add sound effects, etc. function FallingDamageAlert (fallDistance : float) { Debug.Log ("Ouch! Fell " + fallDistance + " units!"); }

@script RequireComponent(CharacterController) </javascript>

Personal tools