gunnarmagnum@gmail.com
LinkedIn
GitHub
Gunnar Portfolio

PRAX

Source

This game was for the course Making Games, part of the MSc in Games education. We were a team of 6 people, 3 of us were programmers, my roles were programmer and art lead. Made with Unity.

The game is inspired by games like N++, that offer the player a fun and polished movement system in short challenging levels. The player is expected to try the same level over and over again until they succeed. Making sure the player movement felt good and responsive was foundational to making our game fun.

Player Movement

We wanted to make the player feel as much in control as possible. To achieve this, the movement is directly linked to the input.
// Positional movement (simplified)
public void Move(Vector2 inputDir) {
	_controller.Move(inputDir * _speed * Time.deltaTime);
}

On its own this can feel and look too rigid, but we balance it out with smooth visuals.

The robot's rotation does not affect the movement in any way. It is simply a way to show the direction the the player is moving, while also adding to the smoothness. Every update the UpdateMeshRotation function is called, smoothly rotating the robot towards the last direction of movement. This means that if the robot is facing to the left and the player moves to the right for just one frame, the robot will move right only in that frame, but the mesh will rotate smoothly over multiple frames until it faces towards the right.
// Smooth rotation (simplified)
private void UpdateMeshRotation() {
	Quaternion targetRotation = Quaternion.LookRotation(_lastMoveDir, Vector3.up);
 	_mesh.rotation = Quaternion.RotateTowards(
		_mesh.rotation,
		targetRotation,
		_meshRotationSpeed * Time.deltaTime);
}

Another way we add smoothness is by making the robot tilt forwards when moving. This also helps to show the player how fast they are going, which is especially important when using a joystick controller.
// Smooth leaning (simplified)
private void UpdateMeshTilt(float normalizedSpeed) {
	// Lean based on speed
	if (normalizedSpeed > 0.001f) {
		float targetAngle = normalizedSpeed * _maxMeshTiltAngle;
		_curTiltAngle = Mathf.Lerp(_curTiltAngle, targetAngle, 
			_meshTiltSpeed * Time.deltaTime);
	// Lean back if not moving
	} else if (_curTiltAngle > 0.001f) {
		_curTiltAngle = Mathf.Lerp(_curTiltAngle, 0.0f,
			_meshTiltSpeed * Time.deltaTime);
	}
  
	_tiltingMesh.localEulerAngles = new Vector3(-_curTiltAngle, 180.0f, 0.0f);
}

The robot has a dash ability that quickly moves them forwards with greater speed. For the dash effect I chose to use a trail renderer and a sprite animation for the thrusters. This works really well, since if the trail renderer doesn't show, like when dashing against a wall, the sprite animation will still show, giving the player feedback that, even without movement, they did indeed dash.

Outline Shader

We use an outline shader for our game to give it a more unique visual style. The ouline effect is accomplished using a fullscreen post-processing effect with an edge detection algorithm. The algorithm we use is called Robert's Cross, which compares the left and right pixels, and then the above and below pixels. The vertical and horizontal differences are summed together and compared to a threshold to see if the pixel is an edge.

There is a more accurate operator, called the Sobel operator. It works in a similar fashion, but also includes the diagonal pixels, comparing three pixels on each side, instead of only one, and applies weights based on how close a sample pixel is to the source pixel. Sobel is more accurate than Robert's Cross, but also more computationally expensive. Therefore, I started with Robert's Cross for simplicity and performance, and when the result was good enough, I decided that there was no reason to switch to Sobel.

To compare pixels we sample the color, normal, and depth of each pixel. Each sample has some strengths and weaknesses, so by combining them, we can more reliably detect edges.