this is a older project for more recent work go to
Engine: Unity (with Probuilder, Shader Graph, Blender integration)
Role: Level Designer / Game Designer
Timeframe: 6 Weeks
Six weeks
Overview
Cloudbound is a flight-first exploration game that merges physics-driven movement with modular level design and environmental storytelling. The goal was to make flight itself fun and intuitive, while anchoring player curiosity through spatial layout, light-based progression, and soft narrative beats.
The game blends open-world vertical traversal with interior exploration across an abandoned sci-fi facility. All content was scoped to support short, satisfying play sessions with optional progression.
Key Contributions
- World Design from a Flight Perspective: Designed terrain and landmarks from the air first, encouraging exploration through elevation and visual cues.
- Custom Flight Mechanics: Built physics-based controls with
HandleLift,AutoLevel, andHandleCyclicfunctions to create a ship that feels natural to steer and land. - Modular Interior Design: Created station interiors that reward exploration and gradual light-based upgrades — darkness hides rarer materials and secrets.
- Player Flow & Pacing: Balanced large open-air zones with tight corridors and key-item puzzles to maintain exploration pacing.
- Design for Replayability: Implemented light-based gear progression to gate access and increase player return value.
- Visual Language & Readability: Used vegetation and terrain as both navigation aid and soft blockers (e.g., dense brush makes landing harder).
Tools & Tech
Unity • Probuilder • Shader Graph • Gaia • Blender • C# scripting
Design Philosophy
“The world should feel handcrafted from a pilot’s point of view. Logic matters less than experience — a level doesn’t need to be realistic, just believable within its fiction.”
This project explored physics-based movement, modular level design, and emotional pacing. I built custom systems instead of relying on plug-ins to better understand how terrain, propulsion limits, and AI behavior shape world design.

To enjoy the game, I wanted just flying around to be enough. Whenever you fly around, you can discover clues and explore the world. Among many indoor exploration elements, this facility is one. The interiors were designed to reward players who took their time to explore. Providing hints about the goal and then presenting the solutions.
The Mass Effect trilogy inspired me a lot, in particular the part when you get to scan planets for materials. The interior on this planet sets inside an abandoned space station, where the first mission is to activate the energy base. Light is a rare resource, and more rare items are placed in the darkest corners to encourage replayability. A player that upgrades their gear to cast light means they get more material. The interior balances open and narrow spaces to give the player a well paced experience navigating the station and rewarding curiosity.
In engine screenshots





In engine Images
The design should indicate what needs to be changed. Vegetation makes landing impossible!. Penalties should provide insight. Creating a game world according to what I want the player to experience is my job as a level designer. Experience matters more than logic. Realistic is not necessary, but it should appear reasonable in the world it builds. They’d feel almost successful even though they’d been pretty far off. It’s all about faking it sometimes!


Here are some sketches to illustrate some interactive game mechanics and interior.
Sketches




Scetches
Asset creation in Blender
I’ve experimented with Blender for a couple of years now, and when I got accepted at TGA I decided to try and create all the 3D modeling myself. I like how it gives me total control over what I can create, and I don’t have to depend on somebody else’s design.
Blender has been my go-to program for a couple of years now, and when I got accepted to TGA I decided to create all the 3D modeling myself. I like how it makes it possible to show not tell to my team about new Level Design ideas
Meshes from blender




Scripting
A big part of the game’s fun is the flight controller. My goal was to make flying around and landing enjoyable for novice players. The spaceship is controlled like a helicopter, so whenever the ship leans toward a direction, it will follow that direction. Players can land and exit the ship to explore gated areas. I spent a lot of time tweaking variables for the movement controller to make it feel engaging. Next step is to add enemy encounters and environmental hazards to this project.
HandleLift function
The first function I wrote was to make the spaceship lift, it felt like a good start. I designed the terrain after testing different heights.
Script Handle lift
protected virtual void HandleLift(Rigidbody rb, KeyboardInput input)
{
// handles the vertical movement of the ship, make sure that the spaceship can have "room to move"
RaycastHit groundHit;
Ray groundRay = new Ray(transform.position, Vector3.down);
if (Physics.SphereCast(groundRay, .1f, out groundHit, Mathf.Infinity, enviormentMask))
{
currentWantedPlayerHeight = groundHit.distance;
currentUsablePlayerHeight = Mathf.Lerp(currentUsablePlayerHeight ,currentWantedPlayerHeight , Time.deltaTime);
RaycastHit playerExitShipHit;
Ray playerExitShipRay = new Ray(transform.position, Vector3.down);
if (Physics.SphereCast(playerExitShipRay, .1f, out playerExitShipHit, Mathf.Infinity))
if (playerExitShipHit.distance < minGroundDistanceForDeloadPlayer)
{
playerPoint = playerExitShipHit.point + transform.up * 6f;
if (Input.GetKey(KeyCode.C))
FPSCam.SetActive(true);
FPSCam.gameObject.transform.position = playerPoint;
if (!canSpawn)
canSpawn = true;
}else canSpawn =false;
RaycastHit forwardHit;
Ray forwardRay = new Ray(groundHit.point, -flatFwd);
if (Physics.Raycast(forwardRay, out forwardHit, maxDiffDistance, enviormentMask))
{
distanceToEnv = forwardHit.distance;
Ray upRay = new Ray(forwardHit.point, Vector3.up);
RaycastHit upHit;
if (Physics.SphereCast(upRay, .5f, out upHit, Mathf.Infinity, topMask))
{
currentEnvAimHeight = upHit.distance + minConsistantHeight;
Ray topRay = new Ray(upHit.point, Vector3.up);
RaycastHit topHit;
if (Physics.SphereCast(topRay, rayRadius, out topHit, Mathf.Infinity, topMask))
{
top = topHit.distance;
currentEnvAimHeight += top / 2;
}
heightModifier = currentEnvAimHeight - currentUsablePlayerHeight + minConsistantHeight;
dir = heightModifier / Mathf.Abs(heightModifier);
powerMuliplier = heightModifier / maxDiffHeight;
}
else
{
heightModifier = maxDiffHeight - currentUsablePlayerHeight + minConsistantHeight;
dir = heightModifier / Mathf.Abs(heightModifier);
powerMuliplier = heightModifier / maxDiffHeight;
}
}
else
{
heightModifier = maxDiffHeight - currentUsablePlayerHeight + minConsistantHeight;
dir = heightModifier / Mathf.Abs(heightModifier);
powerMuliplier = heightModifier / maxDiffHeight;
}
}
power = Mathf.Pow(powerMuliplier, pow) * maxLiftForce * dir;
power = Mathf.Clamp(power, 0, maxLiftForce);
Debug.Log(power);
Vector3 liftForce = (transform.up * (Physics.gravity.magnitude + power) * rb.mass);
rb.AddForce(liftForce * input.StickeyCollectiveInput, ForceMode.Force);
}CalculateAngles function

I could not find the direction I needed to push the spacecraft in based on the world location since it ignored forward and backward motion. Local angle was straight down. As a result, I got a flat angle.
Calculate angles script
private void CalculateAngles()
{
// Calculate the flat angle in world space
flatRight = transform.right;
flatRight.y = 0f;
flatRight = flatRight.normalized;
Debug.DrawRay(transform.position, flatRight, Color.red);
flatFwd = transform.forward;
flatFwd.y = 0f;
flatFwd = flatFwd.normalized;
Debug.DrawRay(transform.position, flatFwd, Color.blue);
forwardDot = Vector3.Dot(transform.up, flatFwd);
rightDot = Vector3.Dot(transform.up, flatRight);
}A
HandleCyclic function
This function handles spaceship force. If anything blocks the path, ship power is reduced. It acts like an automatic break, making maneuvering easier for novice players
Handle Cyclic
protected virtual void HandleCyclic(Rigidbody rb, KeyboardInput input)
{
// Handle the amount of stability the player receives, decrease stability when any input is received to give more control to the steering of the ship.
if (Input.anyKey)
stability = Mathf.Lerp(maxStability, 0.2f, 5 * Time.deltaTime);
else
stability = Mathf.Lerp(maxStability, maxStability, 5 * Time.deltaTime);
//Handle the tilting of the spaceship in forward / backward directions
float cyclicXForce = (MathF.Pow(input.CyclicInput.y, pow) * (cyclicForce * cyclicForceMultiplier));
rb.AddRelativeTorque(Vector3.right * (cyclicXForce * input.CyclicInput.y), ForceMode.Acceleration);
//Handle the tilting of the spaceship in left / right directions
float cyclicZForce = MathF.Pow(input.CyclicInput.x, pow) * (cyclicForce * cyclicForceMultiplier);
rb.AddRelativeTorque(Vector3.forward * (cyclicZForce * input.CyclicInput.x), ForceMode.Acceleration);
Vector3 forwardVec = (flatFwd * forwardDot);
Vector3 rightVec = (flatRight * rightDot);
// calculates the final angle to apply force in.
cyclicDir = Vector3.ClampMagnitude(forwardVec + rightVec, 1f) * (forwardPower * cyclicForce);
RaycastHit hit;
Ray ray = new Ray(transform.position, cyclicDir);
if (Physics.SphereCast(ray, .2f, out hit, maxDiffDistance, enviormentMask))
{
// Calculate the distance to the closest object in the world in the direction witch the ship is going to move toward
distanceModifier = maxDiffDistance - hit.distance;
distanceForceMultiplier = distanceModifier / maxDiffDistance;
if(hit.distance > maxDiffDistance *.1f)
{
forwardPower = (1 - distanceForceMultiplier) * Mathf.Pow(distanceForceMultiplier, pow) * maxForwardPowerMuliplier;
rb.AddForce(cyclicDir, ForceMode.Force);
}
else
{
// handle the "breaking" of the ship by reversing the power when the ship is less than 10m away from the object
forwardPower = distanceForceMultiplier * Mathf.Pow(distanceForceMultiplier, pow) * maxForwardPowerMuliplier;
rb.AddForce(-cyclicDir, ForceMode.Force);
}
// apply force in the direction wich the ship is tilting toward
forwardPower = Mathf.Clamp(forwardPower, 0, maxForwardPowerMuliplier);
}
else {
// apply force in the direction wich the ship is tilting toward
forwardPower = maxForwardPowerMuliplier;
rb.AddForce(cyclicDir, ForceMode.Force);
}
}Autolevel Script
private void AutoLevel(Rigidbody rb)
{
// Calculate in what direction the spaceship need to apply force to be able to stay steady in the air
Vector3 predictedUp = Quaternion.AngleAxis(
rb.angularVelocity.magnitude * Mathf.Rad2Deg * stability / maxStability,
rb.angularVelocity
) * transform.up;
Vector3 torqueVector = Vector3.Cross(predictedUp, Vector3.up);
rb.AddTorque(torqueVector * maxStability * stability);
Quaternion corrAngle = Quaternion.Euler(0f, 0f, 0f);
float rightForce = -forwardDot * autolevelForce;
float forwardForce = rightDot * autolevelForce;
rb.AddRelativeTorque(Vector3.right * rightForce, ForceMode.Acceleration);
rb.AddRelativeTorque(Vector3.forward * forwardForce, ForceMode.Acceleration);
}Autolevel
AutoLevel function
The force applied to the spaceship is handled by this function. The player will get less power if anything blocks the path. Player input determines auto-level force. A spaceship’s pitch is less controlled since it auto-levels. When a player hits something and begins to wobble, they can take their hands off the keyboard and let the autoleveling do its thing
HandlePlasmaCanon function
The cannon moves slowly towards points in the world where the mouse hovers. A projectile moves forward and checks for collisions. After a hit happens, collision effects are spawned.
HandlePlastmaCannon Script
void HandlePlasmaCanon()
{
firePos.LookAt(lookAt);
Quaternion OriginalRot = gunPos.localRotation;
gunPos.LookAt(lookAt.position);
Quaternion NewRot = gunPos.localRotation;
gunPos.localRotation = OriginalRot;
gunPos.localRotation = Quaternion.Lerp(gunPos.localRotation, NewRot, rotSpeed * Time.deltaTime);
foreach (Projectile projectile in projectileList)
{
RaycastHit projectileHit;
Ray projectileRay = new Ray(projectile.transform.position, projectile.transform.forward);
if (Physics.SphereCast(projectileRay, .2f, out projectileHit, Mathf.Infinity))
{
Debug.DrawLine(projectile.transform.position, projectileHit.point, Color.cyan, 2000000f);
if (projectileHit.distance < 1f && projectileHit.distance > .5f)
{
StartCoroutine(InstansiateHitPlane(projectileHit.point));
Debug.Log("Stat");
StartCoroutine(InstansiateHitEffect(projectileHit.point));
Destroy(projectile.gameObject);
projectileList.Clear();
}
}
}
RaycastHit hit;
Ray ray = new Ray(firePos.position, gunPos.forward);
if (Physics.SphereCast(ray, 0.2f, out hit, shootRange, gunMask))
{
aim.position = hit.point;
aim.rotation = Quaternion.FromToRotation(aim.up, hit.normal) * aim.rotation;
hitEffectPos = aim;
}
if (Input.GetButton("Fire1") && canShoot)
{
Shoot();
}
}
IEnumerator InstantiateHitPlane(Vector3 pos)
{
GameObject planeInstance = Instantiate(impactPlane,pos, hitEffectPos.rotation);
yield return new WaitForSeconds(.1f);
Destroy(planeInstance);
StopCoroutine(InstantiateHitPlane(pos));
}
IEnumerator InstantiateHitEffect(Vector3 pos)
{
VisualEffect hitEffectInsante = Instantiate(impactVFX, pos, hitEffectPos.rotation);
hitEffectInsante.transform.localScale = new Vector3(5, 5, 5);
vFXList.Add(hitEffectInsante);
yield return new WaitForSeconds(hitEffectInsante.playRate);
foreach(VisualEffect vfx in vFXList)
{
Destroy(vfx.gameObject);
}
vFXList.Clear();
StopCoroutine(InstantiateHitEffect(pos));
}
void Shoot()
{
RaycastHit hit;
Ray ray = new Ray(firePos.position, gunPos.forward);
if (Physics.SphereCast(ray, 0.2f, out hit, shootRange, gunMask))
{
Projectile projectileInstance = Instantiate(projectile, firePos.position, gunPos.rotation);
projectileList.Add(projectileInstance);
VisualEffect muzzleFlash = Instantiate(muzzleFlashVFX, firePos);
muzzleFlash.transform.localPosition = new Vector3(0, 0, 0);
muzzleFlash.transform.localScale = new Vector3(5, 5, 5);
}
StartCoroutine(Timer());
}
IEnumerator Timer()
{
canShoot = false;
yield return new WaitForSeconds(1f);
canShoot = true;
StopCoroutine(Timer());
}a
HandlePedal function
This function rotates the spacecraft horizontally.
HandlePadal Script
protected virtual void HandlePedal(Rigidbody rb, KeyboardInput input)
{
rb.AddTorque(Vector3.up * input.PedalInput * tailForce, ForceMode.Acceleration);
}HandlePandlePadal Scriopt
FPSController function
This function controls the camera movement when the player exits the spacecraft to explore.
FPS Controller
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Spaceship
{
public enum playerMoveStatus { notMoving, crouching, walking, running, notGrounded, landing }
public enum CurvedControlledBobCallBackType {horizontal, vertical}
public delegate void CurvedControlledBobCallback();
[System.Serializable]
public class CurvedControlledBobEvent
{
public float time = 0f;
public CurvedControlledBobCallback Function = null;
public CurvedControlledBobCallBackType Type = CurvedControlledBobCallBackType.vertical;
}
[System.Serializable]
public class CurvedControlledBob
{
[SerializeField] AnimationCurve bobCurve = new AnimationCurve(new Keyframe(0f,0f),new Keyframe(0.5f, 1f)
,new Keyframe(1f, 0f), new Keyframe(1.5f, -1f), new Keyframe(2f, 0f));
[SerializeField] float horizontalMultiplier = 0.01f;
[SerializeField] float verticalMultiplier = 0.02f;
[SerializeField] float verticaltoHorizontalSpeedRatio = 2.0f;
[SerializeField] float baseInterval = 1f;
private float prevXPlayHead;
private float prevYPlayHead;
private float xPlayHead;
private float yPlayHead;
private float cureveEndTime;
private List<CurvedControlledBobEvent> events = new List<CurvedControlledBobEvent>();
public void Initialize ()
{
cureveEndTime = bobCurve[bobCurve.length - 1].time;
xPlayHead = 0f;
yPlayHead = 0f;
prevXPlayHead = 0f;
prevYPlayHead = 0f;
}
public void RegisterEventCallback(float time, CurvedControlledBobCallback function, CurvedControlledBobCallBackType type)
{
CurvedControlledBobEvent ccbeEnvet = new CurvedControlledBobEvent();
ccbeEnvet.time = time;
ccbeEnvet.Function = function;
ccbeEnvet.Type = type;
events.Add(ccbeEnvet);
events.Sort(
delegate(CurvedControlledBobEvent t1, CurvedControlledBobEvent t2)
{
return (t1.time.CompareTo (t2.time));
}
);
}
public Vector3 GetVectorOffset(float speed)
{
xPlayHead += (speed * Time.deltaTime)/ baseInterval;
yPlayHead += ((speed * Time.deltaTime) / baseInterval)* verticaltoHorizontalSpeedRatio;
if(xPlayHead > cureveEndTime)
xPlayHead -= cureveEndTime;
if (yPlayHead > cureveEndTime)
yPlayHead -= cureveEndTime;
for (int i = 0; i < events.Count; i++)
{
CurvedControlledBobEvent ev = events[i];
if(ev!=null)
if(ev.Type == CurvedControlledBobCallBackType.vertical)
if(prevYPlayHead < ev.time && yPlayHead >= ev.time || prevYPlayHead > yPlayHead && (ev.time > prevYPlayHead || ev.time <= yPlayHead))
ev.Function();
if (ev.Type == CurvedControlledBobCallBackType.vertical)
if (prevXPlayHead < ev.time && xPlayHead >= ev.time || prevXPlayHead > xPlayHead && (ev.time > prevXPlayHead || ev.time <= xPlayHead))
ev.Function();
}
float xPos = bobCurve.Evaluate(xPlayHead)*horizontalMultiplier;
float yPos = bobCurve.Evaluate(yPlayHead)*verticalMultiplier;
prevXPlayHead = xPlayHead;
prevYPlayHead = yPlayHead;
return new Vector3(xPos,yPos,0f);
}
}
[RequireComponent(typeof(CharacterController))]
public class FPSController : MonoBehaviour
{
public List<AudioSource> audioSource = new List<AudioSource>();
private int audioToUse = 0;
public Vector3 spawnPoint { get; set; }
#region Gameplay
[SerializeField] private float crouchSpeed = 2.5f;
[SerializeField] private float walkSpeed = 1f;
[SerializeField] private float runSpeed = 4.5f;
[SerializeField] private float jumpSpeed = 7.5f;
[SerializeField] private float stickToGroundForce = 5f;
[SerializeField] private float gravityMultiplier = 2.5f;
[SerializeField] private float runStepLengthen = 0.75f;
[SerializeField] private float walkStepLengthen = 0.75f;
[SerializeField] private float staminaDepletion = 5f;
[SerializeField] private float staminaRecovery = 10f;
[SerializeField] private MouseLook mouseLook;
[SerializeField] private CurvedControlledBob headBob = new CurvedControlledBob();
// [SerializeField] SpaceShipCharacteristics charactaristics;
const float interactDistance = 20f;
public Camera fpsCamera = null;
private bool isJumpButtonPressed = false;
private Vector2 input = Vector2.zero;
private Vector3 moveDiection = Vector2.zero;
private bool pervGrounded;
private bool isWalking = false;
private bool isJumping = false;
private bool isCrouching = false;
private float fallingTimer = 0f;
private float controllerHeight = 0f;
private float stamina = 100f;
private Vector3 localSpaceCameraPos = Vector3.zero;
private CharacterController characterController = null;
private playerMoveStatus movementStatus = playerMoveStatus.notMoving;
public LayerMask groundMask;
public playerMoveStatus movemeStatus { get { return movementStatus; }}
public float WalkSpeed { get { return walkSpeed; }}
public float RunSpeed { get { return runSpeed; }}
[SerializeField] LayerMask interactebleMask;
float vertical;
float horizontal;
void Update ()
{
// if(Input.GetButtonDown("Interact"))
// Interact();
if (characterController.isGrounded) fallingTimer = 0f;
else
fallingTimer += Time.deltaTime;
if(Time.timeScale>Mathf.Epsilon)
mouseLook.LookRotation(transform, fpsCamera.transform);
if (!isJumpButtonPressed && !isCrouching)
isJumpButtonPressed = Input.GetButtonDown("Jump");
if(Input.GetButtonDown("Crouch"))
{
isCrouching = !isCrouching;
characterController.height = isCrouching == true ? controllerHeight/2f : controllerHeight;
}
if (!pervGrounded && characterController.isGrounded)
{
if (fallingTimer > 0.5f)
{
}
moveDiection.y = 0f;
isJumping = false;
movementStatus = playerMoveStatus.landing;
}
else if (characterController.velocity.sqrMagnitude < 0.01f)
movementStatus = playerMoveStatus.notMoving;
else if (isCrouching)
movementStatus = playerMoveStatus.crouching;
else if (isWalking)
movementStatus = playerMoveStatus.walking;
else
movementStatus = playerMoveStatus.running;
pervGrounded = characterController.isGrounded;
if(movementStatus == playerMoveStatus.running)
stamina = Mathf.Max(stamina - staminaDepletion * Time.deltaTime, 0f);
else
stamina = Mathf.Min(stamina + staminaRecovery * Time.deltaTime, 100f);
}
private void FixedUpdate()
{
horizontal = Input.GetAxis("Horizontal");
vertical = Input.GetAxis("Vertical");
bool wasWalking = isWalking;
isWalking = !Input.GetKey(KeyCode.LeftShift);
float speed = isCrouching ? crouchSpeed : isWalking ? walkSpeed : Mathf.Lerp(walkSpeed,runSpeed, stamina/100f);
input = new Vector2(horizontal, vertical);
if (input.sqrMagnitude > 1) input.Normalize();
Vector3 desierdMove = transform.forward * input.y + transform.right * input.x;
RaycastHit hit;
Ray ray = new Ray(transform.position, Vector3.down);
if (Physics.SphereCast(ray, characterController.radius, out hit, characterController.height / 2f, groundMask))
desierdMove = Vector3.ProjectOnPlane(desierdMove, hit.normal).normalized;
moveDiection.x = desierdMove.x * speed;
moveDiection.z = desierdMove.z * speed;
if (characterController.isGrounded)
{
moveDiection.y = -stickToGroundForce;
if (isJumpButtonPressed)
{
moveDiection.y = jumpSpeed;
isJumpButtonPressed = false;
isJumping = true;
}
}
else
moveDiection += Physics.gravity * gravityMultiplier * Time.fixedDeltaTime;
characterController.Move(moveDiection * Time.fixedDeltaTime);
Vector3 speedXZ = new Vector3(characterController.velocity.x, 0f, characterController.velocity.z);
if(speedXZ.magnitude > 0.01f)
fpsCamera.transform.localPosition = new Vector3(0f,0f,0f) + headBob.GetVectorOffset(speedXZ.magnitude * (isWalking || isCrouching? walkStepLengthen : runStepLengthen));
else fpsCamera.gameObject.transform.localPosition = new Vector3(0f, 0f, 0f);
}
#endregion
public float Stamina { get{return stamina; }}
private void Start()
{
characterController = GetComponent<CharacterController>();
controllerHeight = characterController.height;
fpsCamera = GetComponentInChildren<Camera>();
mouseLook.Init(transform, fpsCamera.transform);
headBob.Initialize();
headBob.RegisterEventCallback(1.5f, PlayFootstepSound, CurvedControlledBobCallBackType.vertical);
}
void PlayFootstepSound()
{
if(isCrouching) return;
audioSource[audioToUse].Play();
audioToUse = (audioToUse == 0) ? 1 : 0;
}
// void Interact ()
// {
// RaycastHit hit;
// Ray ray = new Ray (transform.position, transform.forward);
// if(Physics.SphereCast(ray, .1f, out hit, interactDistance))
// {
// if(hit.transform.gameObject.GetComponent<IInteracteble>() != null)
// {
// Debug.Log(hit.transform.gameObject);
// hit.transform.gameObject.GetComponent<IInteracteble>().OnInteract();
// }
// }
// }
private void OnEnable()
{
GameObject spaceship = GameObject.FindGameObjectWithTag("spaceship");
transform.position = spaceship.transform.position;
Debug.Log("iscalled");
characterController = GetComponent<CharacterController>();
controllerHeight = characterController.height;
fpsCamera = GetComponentInChildren<Camera>();
mouseLook.Init(transform, fpsCamera.transform);
headBob.Initialize();
headBob.RegisterEventCallback(1.5f, PlayFootstepSound, CurvedControlledBobCallBackType.vertical);
}
}
}
FPS controller
