Game Trailer that I made for the game
A project that me and a couple of students have been developing I started with creating most of the base prototype in Unity before the group decided to use Unreal Engine 5.
In the Unity version, I created:
Character swap mechanic.
Player controls, such as movement, double jump, go into stealth.
A character "merge" ability (they merge into one character).
The functionality of the laser and mirrors.
That enemies and objects can be destroyed by the laser.
A basic AI that would patrol and then follow the player if spotted.
A small glimps of the prototype
In the Unreal Engine 5 version of the project, I’ve developed a wide range of gameplay and visual systems, including:
Disintegration and laser materials – custom shaders for visual feedback and energy effects.
Laser components and functionality – handling destruction, reflection, and laser spawning logic.
Pushable rock mechanics – fully physics-based interaction system for environmental puzzles.
Mirror puzzle elements – interactive mirrors that redirect laser beams for puzzle-solving.
Pinball-inspired physics puzzle – complete with a manager that controls checkpoints and interaction logic.
Cogwheel systems – interactive gears that the player can manipulate directly or that drive connected mechanisms such as platforms, elevators, and other actors.
Interactable fire mechanic – allows players to throw a paper airplane through a flame, causing it to ignite and spread to other paper objects or campfires, including custom visual effects for the burning process.
Physics-based pickup and throw system – enabling players to lift and toss objects.
Pushing the puzzle rock
The disintigrating material
The reflection of the laser
using UnityEngine;
public class PlayerMovement : MonoBehaviour
{
Animator animator;
Rigidbody rb;
public int currentPlayer;
[Header("Camera Springarm")]
[SerializeField] private CameraSpringarm cameraSpringarm;
[Header("Movement")]
[SerializeField] public float movementSpeed = 10f;
[SerializeField] private float rotationSpeed = 10f;
[SerializeField] private float disabledTime = 0.4f;
private bool canMove = true;
[Header("Jumps")]
[SerializeField] private float jumpForce = 8f;
[SerializeField] private float doubleJumpForce = 8f;
[SerializeField] private float doubleJumpCooldown = 0.8f;
[SerializeField] private bool jumping = false;
[SerializeField] private bool doubleJumpAvailable = false;
private bool isFalling;
private bool isGrounded;
private int doubleJumpVariationIndex = 0;
private float doubleJumpCooldownTimer = 0;
[Header("Paraglider")]
[SerializeField] private float glidingGravity = 0.5f;
[SerializeField] private float glidingMass = 0.5f;
[SerializeField] private float glidingDrag = 15f;
[SerializeField] private float glidingAngularDrag = 15f;
private float originalDrag;
private float originalAngularDrag;
private float originalMass;
public bool gliding = false;
[Header("Stealth")]
[SerializeField] private float stealthCooldown = 5;
[SerializeField] private int detected = 0;
private bool stealthed = false;
private bool stealthButtonPressed = false;
private void Start()
{
originalDrag = rb.drag;
originalAngularDrag = rb.angularDrag;
originalMass = rb.mass;
}
private void Awake()
{
rb = GetComponent<Rigidbody>();
animator = GetComponent<Animator>();
}
private void FixedUpdate()
{
MoveCharacter();
DoubleJumpTimer();
}
private void Update()
{
if (rb.velocity.magnitude > 0) animator.SetFloat("walkBlend", 1);
if (rb.velocity.magnitude == 0) animator.SetFloat("walkBlend", 0);
}
private void MoveCharacter()
{
if (canMove)
{
Vector3 direction = Vector3.zero;
if (Input.GetKey(KeyCode.W))
{
rb.MovePosition(direction += cameraSpringarm.GetCameraForward());
}
if (Input.GetKey(KeyCode.S))
{
rb.MovePosition(direction -= cameraSpringarm.GetCameraForward());
}
if (Input.GetKey(KeyCode.A))
{
rb.MovePosition(direction -= cameraSpringarm.GetCameraRight());
}
if (Input.GetKey(KeyCode.D))
{
rb.MovePosition(direction += cameraSpringarm.GetCameraRight());
}
if (direction.magnitude > 0)
{
direction.Normalize();
Vector3 move = direction * movementSpeed * Time.deltaTime;
rb.MovePosition(transform.position + move);
// Rotate the player smoothly in the direction of movement
Quaternion toRotation = Quaternion.LookRotation(direction, Vector3.up);
rb.MoveRotation(Quaternion.Lerp(transform.rotation, toRotation, rotationSpeed * Time.deltaTime));
}
CheckVelocityForJumping();
if (Input.GetKey(KeyCode.Space))
{
if (!jumping && currentPlayer != 2 && isGrounded)
{
animator.SetTrigger("t_jump");
jumping = true;
Jump();
doubleJumpCooldownTimer = doubleJumpCooldown;
}
else if (jumping && currentPlayer == 1 && doubleJumpAvailable)
{
doubleJumpAvailable = false;
DoubleJump();
}
}
if (Input.GetKey(KeyCode.Q) && detected == 0 && currentPlayer == 2 && !stealthButtonPressed)
{
stealthButtonPressed = true;
if (!stealthed)
{
StartStealth();
}
else if (stealthed)
{
EndStealth();
}
}
if (Input.GetKeyDown(KeyCode.LeftControl))
{
if (currentPlayer == 1)
{
if (gliding)
{
rb.AddForce(Vector3.down * glidingGravity * Time.deltaTime);
}
if (!isGrounded)
{
if (!gliding)
{
StartGlide();
}
}
}
}
if (Input.GetKeyUp(KeyCode.LeftControl))
{
EndGlide();
}
}
}
private void StealthCooldown()
{
stealthButtonPressed = false;
}
public void DisableMovement()
{
canMove = false;
Invoke("StartMovement", disabledTime);
}
private void StartMovement()
{
canMove = true;
}
private void StartStealth()
{
stealthed = true;
Debug.Log("Start Stealth");
gameObject.layer = 9;
Invoke("StealthCooldown", stealthCooldown);
}
public void EndStealth()
{
stealthed = false;
Debug.Log("End Stealth");
gameObject.layer = 8;
Invoke("StealthCooldown", stealthCooldown);
}
public void Jump()
{
rb.velocity = new Vector3(rb.velocity.x, 0f, rb.velocity.z); // Reset vertical velocity
rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
animator.SetTrigger("t_jump");
isGrounded = false;
}
public void DoubleJump()
{
DoubleJumpAnimation();
rb.velocity = new Vector3(rb.velocity.x, 0f, rb.velocity.z); // Reset vertical velocity
rb.AddForce(Vector3.up * doubleJumpForce, ForceMode.Impulse);
}
private void DoubleJumpTimer()
{
if (doubleJumpCooldownTimer >= 0 && doubleJumpCooldownTimer <= doubleJumpCooldown)
{
doubleJumpAvailable = false;
doubleJumpCooldownTimer -= Time.deltaTime;
}
if (doubleJumpCooldownTimer < 0)
{
doubleJumpAvailable = true;
doubleJumpCooldownTimer = doubleJumpCooldown + 2f;
}
}
private void DoubleJumpAnimation()
{
if (doubleJumpVariationIndex % 2 == 0) animator.SetTrigger("t_doubleJump");
else animator.SetTrigger("t_doubleJumpSpin");
doubleJumpVariationIndex++;
}
private void StartGlide()
{
animator.SetBool("b_isFlying", true);
gliding = true;
rb.velocity = Vector3.down * glidingGravity;
rb.drag = glidingDrag;
rb.angularDrag = glidingAngularDrag;
rb.mass = glidingMass;
}
private void EndGlide()
{
animator.SetBool("b_isFlying", false);
gliding = false;
rb.useGravity = true;
rb.drag = originalDrag;
rb.angularDrag = originalAngularDrag;
rb.mass = originalMass;
}
private void CheckVelocityForJumping()
{
// Check if player is falling (velocity.y < 0) and disable jump
if (rb.velocity.y < 0)
{
isGrounded = false;
isFalling = true;
}
// Check if player has landed (velocity.y approximately 0) and enable jump
if (Mathf.Approximately(rb.velocity.y, 0f) && isFalling)
{
isGrounded = true;
isFalling = false;
jumping = false;
EndGlide();
}
}
}
Due to the small size of our team, flexibility has been one of my key strengths. In addition to ensuring the game functions smoothly and implementing features such as sound and visual effects, I’ve also contributed significantly to world design. I designed and built the final puzzle of the second level — the last challenge players face before completing the game’s demo. Following that, I also designed and set-dressed the “Credits Level.” Internally, we’ve discussed adding another level, for which I’ve already created and designed a blockout to experiment with new mechanics and puzzle concepts.
The "Credits Level" where the player can find some hidden easter eggs and unused art assets
The other side of the "Credits Level", where the final puzzle is contained in the dark room
Beginning of the level, showcasing my dynamic bridges and my new elevator that is raised using cogwheels
A puzzle in a dark cave where one character has to light the way for the other character
The player has to climp up from the side of a mountain before being chased down the slope by an avalance