Switch FPS Engine´s Controller
FPS Engine includes a fully configured, professional controller packed with advanced movement mechanics and a wide range of customizable features.
However, there may be cases where you want to replace FPS Engine's built-in controller with your own, for example, if you want your player to fly or swim.
To keep things simple, let’s start with a basic example: a simple custom controller that lets us move a Rigidbody using the WASD keys.
using System;
using UnityEngine;
public class PlayerTest : MonoBehaviour
{
private Rigidbody rb;
private void Awake()
{
rb = GetComponent<Rigidbody>();
}
private void FixedUpdate()
{
float moveX = InputManager.x;
float moveZ = InputManager.y;
Vector3 move = new Vector3(moveX, 0f, moveZ) * CurrentSpeed;
rb.velocity = new Vector3(move.x, rb.velocity.y, move.z);
}
}
At the end of this section there is a table listing various scripts. Since we're focusing on player movement, we'll need to integrate the player switch logic into our existing PlayerTest
script.
Let’s go through the changes step by step:
public class PlayerTest : MonoBehaviour,
IPlayerMovementActionsProvider,
IPlayerMovementEventsProvider,
IPlayerMovementStateProvider
{
Your new Movement
script must implement the following three interfaces: IPlayerMovementActionsProvider
, IPlayerMovementEventsProvider
, and IPlayerMovementStateProvider
.
When you compile the script after adding these interfaces, you'll likely encounter several errors. That’s expected, interfaces define a set of required properties and methods that any implementing class must provide. Since our PlayerTest
script doesn’t implement these members yet, it will throw errors.
Below is a list of all the required properties specified by the interfaces.
private PlayerOrientation orientation;
public PlayerOrientation Orientation => orientation;
public float CurrentSpeed => WalkSpeed;
public float RunSpeed => 10;
public float WalkSpeed => 5;
public float CrouchSpeed => 3;
public bool Grounded => false;
public bool IsCrouching => false;
public bool IsClimbing => false;
public bool WallRunning => false;
public bool Dashing => false;
public bool CanShootWhileDashing => false;
public bool DamageProtectionWhileDashing => false;
public float NormalFOV => 90;
public float FadeFOVAmount => 1;
public float WallRunningFOV => 100;
public bool AlternateSprint => false;
public bool AlternateCrouch => false;
// Call these events to keep CameraEffects & WeaponEffects working. (Ex.: OnJump?.Invoke();)
public event Action OnJump;
public event Action OnLand;
public event Action OnCrouch;
public event Action OnUncrouch;
public void AddJumpListener(Action listener) => OnJump += listener;
public void RemoveJumpListener(Action listener) => OnJump -= listener;
public void AddLandListener(Action listener) => OnLand += listener;
public void RemoveLandListener(Action listener) => OnLand -= listener;
public void AddCrouchListener(Action listener) => OnCrouch += listener;
public void RemoveCrouchListener(Action listener) => OnCrouch -= listener;
public void AddUncrouchListener(Action listener) => OnUncrouch += listener;
public void RemoveUncrouchListener(Action listener) => OnUncrouch -= listener;
public void TeleportPlayer(Vector3 position, Quaternion rotation, bool resetStamina, bool resetDashes) { }
Some of these variables are currently hard-coded, such as CurrentSpeed
, RunSpeed
, WalkSpeed
, and others. However, you're free to connect them to your own custom variables.
For example, if you already have a variable that defines your walking speed, you can simply hook into it like this:
public float WalkSpeed => myCustomWalkSpeed;
This approach allows you to integrate your existing logic while still satisfying the interface requirements.
Recommendations & Considerations
PLAYER ORIENTATION
PlayerOrientation
defines the direction the player is currently facing, so the following piece of code is essential and must be included:
private PlayerOrientation orientation;
public PlayerOrientation Orientation => orientation;
Now, we need a way to initialize PlayerOrientation
when the game starts.
private void Awake()
{
orientation = new PlayerOrientation(transform.position + Vector3.up * 2, Quaternion.identity);
}
We also need a way to update PlayerOrientation
each frame to reflect the player's current view direction.
private void Update()
{
// Adapt yaw to your Look or Camera Rotation system
float yaw = transform.eulerAngles.y;
orientation.UpdateOrientation(transform.position, yaw);
}
CURRENT SPEED
It’s strongly recommended to link CurrentSpeed
to your player’s actual movement speed, if you’re tracking it. For example, whether the player is walking, running, or moving at variable speeds, make sure CurrentSpeed
reflects those changes.
The same principle applies to WalkSpeed
, RunSpeed
, and CrouchSpeed
, if your setup includes them.
GROUNDED LOGIC
The Grounded
property should be tied to your custom ground detection logic.
Likewise, ensure that states such as IsCrouching
, Wallrunning
, and IsClimbing
are correctly linked to your own gameplay systems.
FIELD OF VIEW
NormalFOV
controls your camera’s field of view only if you’re still using the FPS Engine’s camera system along with the CameraFOVManager
.
TELEPORT PLAYER
You can implement your own custom teleportation logic inside the TeleportPlayer()
method, giving you full control over how and where the player is moved.
Below, you´ll find the full reference scripts for switching different systems in FPS Engine
You´ll need to remove both PlayerMovement & PlayerStates from the Player.
using System;
using cowsins;
using UnityEngine;
public class PlayerTest : MonoBehaviour, IPlayerMovementActionsProvider, IPlayerMovementEventsProvider, IPlayerMovementStateProvider
{
private PlayerOrientation orientation;
public PlayerOrientation Orientation => orientation;
public float CurrentSpeed => WalkSpeed;
public float RunSpeed => 10;
public float WalkSpeed => 5;
public float CrouchSpeed => 3;
public bool Grounded => false;
public bool IsCrouching => false;
public bool IsClimbing => false;
public bool WallRunning => false;
public bool Dashing => false;
public bool CanShootWhileDashing => false;
public bool DamageProtectionWhileDashing => false;
public float NormalFOV => 90;
public float FadeFOVAmount => 1;
public float WallRunningFOV => 100;
public bool AlternateSprint => false;
public bool AlternateCrouch => false;
// Call these events to keep CameraEffects & WeaponEffects working. (Ex.: OnJump?.Invoke();)
public event Action OnJump;
public event Action OnLand;
public event Action OnCrouch;
public event Action OnUncrouch;
public void AddJumpListener(Action listener) => OnJump += listener;
public void RemoveJumpListener(Action listener) => OnJump -= listener;
public void AddLandListener(Action listener) => OnLand += listener;
public void RemoveLandListener(Action listener) => OnLand -= listener;
public void AddCrouchListener(Action listener) => OnCrouch += listener;
public void RemoveCrouchListener(Action listener) => OnCrouch -= listener;
public void AddUncrouchListener(Action listener) => OnUncrouch += listener;
public void RemoveUncrouchListener(Action listener) => OnUncrouch -= listener;
public void TeleportPlayer(Vector3 position, Quaternion rotation, bool resetStamina, bool resetDashes) { }
private Rigidbody rb;
private void Awake()
{
orientation = new PlayerOrientation(transform.position + Vector3.up * 2, Quaternion.identity);
rb = GetComponent<Rigidbody>();
}
private void Update()
{
// Adapt yaw to your Look or Camera Rotation system
float yaw = transform.eulerAngles.y;
orientation.UpdateOrientation(transform.position, yaw);
}
private void FixedUpdate()
{
float moveX = InputManager.x;
float moveZ = InputManager.y;
Vector3 move = new Vector3(moveX, 0f, moveZ) * CurrentSpeed;
rb.velocity = new Vector3(move.x, rb.velocity.y, move.z);
}
}
Last updated