top of page

Project Stealth

Description: 

Project Stealth is an isometric, action-adventure game inspired by the likes of Tunic and Death's Door but with a focus on stealth gameplay. The player has a variety of abilities to deal with enemy characters like distractions, sneaking up and disabling them, or teleporting past them all together. 

Minus.png

The class below creates a visual "ghost trail" effect by capturing snapshots of a character's skinned meshes and then fading their material alpha out over time.

 

In detail:

  • On initialization, it collects skinned mesh renderers from a specified mesh GameObject and uses them to bake mesh "snapshots".

  • It instantiates new GameObjects with these baked meshes, assigns them a trail material, and a class for lerping the alpha of the meshes materials when it is time to make the ghosts disappear.  

  • The class uses coroutines to schedule multiple ghost trail creations with delays, and after a set lifetime, it starts a fade-out effect before finally destroying the ghost objects.

​

With this approach, the "snapshots" of the character mesh that are created are not suitable candidates for object pooling. When the effect is over they cannot easily be reused and so they are destroyed, creating garbage collection. Keeping the number of snapshots low, this was perfectly fine for Project Stealth, which was intended for PC. 

 

In a more performance critical context, I could have tried creating a flipbook of animation frames (e.g. for rolling forward, rolling left and rolling right) in advance and spawned and despawned those pre-made frames using object pooling. This approach would be less flexible and more difficult to implement but likely more performant. 

Minus.png
Minus.png

In Project Stealth, the player can control an aiming crosshair independently of camera movement (like a twin stick shooter).  

​

I wanted to prevent the crosshair from going off screen and gradually decelerate its movement speed as it approaches the edge of the screen.   

​

This required first calculating the expected position of the character controller (given its current speed and direction) in viewport space. â€‹â€‹â€‹

​

The viewport position provides a

normalised position relative to the bottom left hand corner of the camera viewport. I needed a position relative to the middle of the camera viewport so I used Mathf.Min to get the lowest value between the viewport position and 1 minus the viewport position. In that way a viewport x position of 0.7, for example, becomes 0.3 and that value is used. 

​

And then I return the distance to the nearest screen edge (again using Mathf.Min to get the lowest value between the x and y coordinates if the modified viewport position). By doing this, rather than using the actual distance to the screen edge (e.g. viewportPosition.Magnitude), corners are treated the same as the middle of screen edges. 

 

The returned distance looks like this:                                                           Rather than like this: 

Minus.png

x

The method below manages the actual movement of the Character Controller. 

​

It uses Lerp to smoothy accelerate or decelerate between the current speed and a speed value multiplied by the magnitude of the input (from a gamepad stick). 

​

That speed value is then multiplied by a value obtained by evaluating an animation curve with the distance to the screen edge. Users can set up the animation curve as they wish to get the desired speed modification according to the distance to the screen edge. In my case if the distance is greater than 0.15 the current speed is multiplied by 1 (it is unchanged). And if it is less than 0.15 it smoothly declines to 0 (at a distance of 0). 

​​

Minus.png

x

Finally, Get Target Direction calculates the desired movement direction for the character controller, taking in to account the player's input and the orientation of the camera:

​​

Minus.png
bottom of page