diff --git a/FPSController.cs b/FPSController.cs
new file mode 100644
index 0000000000000000000000000000000000000000..57c8c7d52e7865a3b96cce210d56d3a790443373
--- /dev/null
+++ b/FPSController.cs
@@ -0,0 +1,215 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+
+// Generic First-Person-Shooter-like Controller 
+[RequireComponent(typeof(CharacterController))]
+public class FPSController : MonoBehaviour
+{
+    [Tooltip("How fast the player moves when walking (default move speed).")]
+    [SerializeField]
+    private float movementWalkSpeed = 6.0f;
+ 
+    [Tooltip("How fast the player moves when running.")]
+    [SerializeField]
+    private float movementRunSpeed = 11.0f;
+ 
+    [Tooltip("If true, diagonal speed (when strafing + moving forward or back) can't exceed normal move speed; otherwise it's about 1.4 times faster.")]
+    [SerializeField]
+    public bool movementLimitDiagonalSpeed = true;
+ 
+    [Tooltip("If checked, the run key toggles between running and walking. Otherwise player runs if the key is held down.")]
+    [SerializeField]
+    private bool movementToggleRun = false;
+ 
+    [Tooltip("How high the player jumps when hitting the jump button.")]
+    [SerializeField]
+    private float movementJumpSpeed = 8.0f;
+ 
+    [Tooltip("How fast the player falls when not standing on anything.")]
+    [SerializeField]
+    private float movementGravity = 20.0f;
+ 
+    [Tooltip("Units that player can fall before a falling function is run. To disable, type \"infinity\" in the inspector.")]
+    [SerializeField]
+    private float movementFallingThreshold = 10.0f;
+ 
+    [Tooltip("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.")]
+    [SerializeField]
+    private bool movementSlideWhenOverSlopeLimit = false;
+ 
+    [Tooltip("If checked and the player is on an object tagged \"Slide\", he will slide down it regardless of the slope limit.")]
+    [SerializeField]
+    private bool movementSlideOnTaggedObjects = false;
+ 
+    [Tooltip("How fast the player slides when on slopes as defined above.")]
+    [SerializeField]
+    private float movementSlideSpeed = 12.0f;
+ 
+    [Tooltip("If checked, then the player can change direction while in the air.")]
+    [SerializeField]
+    private bool movementAirControl = false;
+ 
+    [Tooltip("Small amounts of this results in bumping when walking down slopes, but large amounts results in falling too fast.")]
+    [SerializeField]
+    private float movementAntiBumpFactor = .75f;
+ 
+    [Tooltip("Player must be grounded for at least this many physics frames before being able to jump again; set to 0 to allow bunny hopping.")]
+    [SerializeField]
+    private int movementAntiBunnyHopFactor = 1;
+ 
+    private Vector3 movementMoveDirection = Vector3.zero;
+    private bool movementGrounded = false;
+    private CharacterController movementController;
+    private Transform movementTransform;
+    private float movementSpeed;
+    private RaycastHit movementHit;
+    private float movementFallStartLevel;
+    private bool movementFalling;
+    private float movementSlideLimit;
+    private float movementRayDistance;
+    private Vector3 movementContactPoint;
+    private bool movementPlayerControl = false;
+    private int movementJumpTimer;
+ 
+ 
+    private void Start()
+    {
+        // Saving component references to improve performance.
+        movementTransform = GetComponent<Transform>();
+        movementController = GetComponent<CharacterController>();
+ 
+        // Setting initial values.
+        movementSpeed = movementWalkSpeed;
+        movementRayDistance = movementController.height * .5f + movementController.radius;
+        movementSlideLimit = movementController.slopeLimit - .1f;
+        movementJumpTimer = movementAntiBunnyHopFactor;
+    }
+ 
+ 
+    private void 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 (movementToggleRun && movementGrounded && Input.GetButtonDown("Run"))
+        {
+            movementSpeed = (movementSpeed == movementWalkSpeed ? movementRunSpeed : movementWalkSpeed);
+        }
+    }
+ 
+ 
+    private void FixedUpdate()
+    {
+        float inputX = Input.GetAxis("Horizontal");
+        float inputY = Input.GetAxis("Vertical");
+ 
+        // If both horizontal and vertical are used simultaneously, limit speed (if allowed), so the total doesn't exceed normal move speed
+        float inputModifyFactor = (inputX != 0.0f && inputY != 0.0f && movementLimitDiagonalSpeed) ? .7071f : 1.0f;
+ 
+        if (movementGrounded)
+        {
+            bool 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(movementTransform.position, -Vector3.up, out movementHit, movementRayDistance))
+            {
+                if (Vector3.Angle(movementHit.normal, Vector3.up) > movementSlideLimit)
+                {
+                    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(movementContactPoint + Vector3.up, -Vector3.up, out movementHit);
+                if (Vector3.Angle(movementHit.normal, Vector3.up) > movementSlideLimit)
+                {
+                    sliding = true;
+                }
+            }
+ 
+            // If we were falling, and we fell a vertical distance greater than the threshold, run a falling damage routine
+            if (movementFalling)
+            {
+                movementFalling = false;
+                if (movementTransform.position.y < movementFallStartLevel - movementFallingThreshold)
+                {
+                    OnFell(movementFallStartLevel - movementTransform.position.y);
+                }
+            }
+ 
+            // If running isn't on a toggle, then use the appropriate speed depending on whether the run button is down
+            if (!movementToggleRun)
+            {
+                movementSpeed = Input.GetKey(KeyCode.LeftShift) ? movementRunSpeed : movementWalkSpeed;
+            }
+ 
+            // 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 && movementSlideWhenOverSlopeLimit) || (movementSlideOnTaggedObjects && movementHit.collider.tag == "Slide"))
+            {
+                Vector3 hitNormal = movementHit.normal;
+                movementMoveDirection = new Vector3(hitNormal.x, -hitNormal.y, hitNormal.z);
+                Vector3.OrthoNormalize(ref hitNormal, ref movementMoveDirection);
+                movementMoveDirection *= movementSlideSpeed;
+                movementPlayerControl = false;
+            }
+            // Otherwise recalculate moveDirection directly from axes, adding a bit of -y to avoid bumping down inclines
+            else
+            {
+                movementMoveDirection = new Vector3(inputX * inputModifyFactor, -movementAntiBumpFactor, inputY * inputModifyFactor);
+                movementMoveDirection = movementTransform.TransformDirection(movementMoveDirection) * movementSpeed;
+                movementPlayerControl = true;
+            }
+ 
+            // Jump! But only if the jump button has been released and player has been grounded for a given number of frames
+            if (!Input.GetButton("Jump"))
+            {
+                movementJumpTimer++;
+            }
+            else if (movementJumpTimer >= movementAntiBunnyHopFactor)
+            {
+                movementMoveDirection.y = movementJumpSpeed;
+                movementJumpTimer = 0;
+            }
+        }
+        else
+        {
+            // If we stepped over a cliff or something, set the height at which we started falling
+            if (!movementFalling)
+            {
+                movementFalling = true;
+                movementFallStartLevel = movementTransform.position.y;
+            }
+ 
+            // If air control is allowed, check movement but don't touch the y component
+            if (movementAirControl && movementPlayerControl)
+            {
+                movementMoveDirection.x = inputX * movementSpeed * inputModifyFactor;
+                movementMoveDirection.z = inputY * movementSpeed * inputModifyFactor;
+                movementMoveDirection = movementTransform.TransformDirection(movementMoveDirection);
+            }
+        }
+ 
+        // Apply gravity
+        movementMoveDirection.y -= movementGravity * Time.deltaTime;
+ 
+        // Move the controller, and set grounded true or false depending on whether we're standing on something
+        movementGrounded = (movementController.Move(movementMoveDirection * Time.deltaTime) & CollisionFlags.Below) != 0;
+    }
+ 
+ 
+    // Store point that we're in contact with for use in FixedUpdate if needed
+    private void OnControllerColliderHit(ControllerColliderHit hit)
+    {
+        movementContactPoint = hit.point;
+    }
+ 
+ 
+    // This is the place to apply things like fall damage. You can give the player hitpoints and remove some
+    // of them based on the distance fallen, play sound effects, etc.
+    private void OnFell(float fallDistance)
+    {
+        print("Ouch! Fell " + fallDistance + " units!");
+    }
+}
\ No newline at end of file