Fennec Peak

Futuregames


Platform

Engine

Version Control

Language

Development Time

Team Size

My Role

PC

Unity

Perforce

C#

7 weeks

12 (3 Programmers)

Lead Programmer

Project Overview

Fennec Peak is a 2-4 player, local multiplayer game. It was created in a seven week project in a team of twelve. In the game, players fight each other to be the last one standing by knocking each other off of the steep edges that surround the level. To reach that goal, they must make use of their snappy movement, attacks and objects in the environment.

My Role

The most notable points I was responsible for as a programmer were:

The Player Component Structure

The UI system

The Camera system

Together with other miscellaneous tasks such as implementing the new input system for Unity and bug-fixing.

The Player Component Structure

From the planning stages during the project, it has become apparent that the player game object will have many funcionalities connected to it.

With the above structure, everyone could work on each of the components without having to work in the same file. The structure allowed for easier access to reach variables needed by outside scripts like the HUD elements. It also opened up a possibility to change the playable character without a need for a new GameObject.

UI System

The UI system has been written with the goal of allowing other team members to quickly set up fully working UI elements. It consists of multiple scripts which take care of displaying and hiding selected UI pages, and providing basic menu Functionality like pressing a "back" button to return to a previously opened menu page.

Creating a script which inherits from the Menu class automatically allows the object to act as a new menu page. This makes it possible to create a page with custom functionality such as a Character Selection menu.

public class CharacterSelection : Menu 
{ 
	[Header("Loading")] 
	[SerializeField] string nextLevel; 
	[SerializeField] Menu nextMenu; 
	
	[Header("Layout")] 
	[SerializeField] GameObject playerSelector; 
	List<MenuNavigation> registeredPlayers = new List<MenuNavigation>(); 
	[SerializeField] Vector2 offset; 
	[SerializeField] List<GameObject> availableCharacters = new List<GameObject>(); 
	
	public int PlayerCount => registeredPlayers.Count; 
	
	private void OnEnable() 
	{ 
		if (menuManager && menuManager.currentMenu is CharacterSelection) 
		{ 
			for (int i = 0; i < MenuPlayerHandler.playerList.Count; i++) 
			{ 
				Destroy(MenuPlayerHandler.playerList[i].GetComponent<MenuNavigation>().playerCharacterStruct); 
			} 
		} 
	} 
	
	void Update() 
	{ 
		//Add unregistered players to list 
		for (int i = 0; i < MenuPlayerHandler.playerList.Count; i++) 
		{ 
			MenuNavigation playerInput = MenuPlayerHandler.playerList[i].GetComponent<MenuNavigation>(); 
			
			if (playerInput.hasJoined) 
			{ 
				if (!registeredPlayers.Contains(playerInput)) 
				{ 
					registeredPlayers.Add(playerInput); 
					playerInput.playerPrefab = availableCharacters[registeredPlayers.Count - 1]; 
				} 
			} 
			else 
			{
				if (registeredPlayers.Contains(playerInput)) 
				{ 
					registeredPlayers.Remove(playerInput); 
				} 
			} 
		} 

		//Check game ready status 
		int readyPlayers = 0; 
		for (int i = 0; i < registeredPlayers.Count; i++) 
		{ 
			readyPlayers += (registeredPlayers[i].isReady) ? 1 : 0; 
		} 
		
		if (readyPlayers >= 2 && readyPlayers == registeredPlayers.Count) 
		{ 
			PlayerHandler.playerList.Clear(); 
			
			for (int i = 0; i < registeredPlayers.Count; i++) 
			{ 
				PlayerHandler.playerList.Add(registeredPlayers[i].gameObject); 
				gameObject.name = "Player " + (PlayerHandler.playerList.Count); 
			} 
			
			SceneManager.LoadScene(nextLevel); 
			menuManager.EnterMenu(nextMenu); 
			
			for (int i = 0; i < registeredPlayers.Count; i++) 
			{ 
				registeredPlayers[i].Spawn(); 
			} 
			
			MenuManager.isPaused = false; 
		} 
	} 
}

Camera System

The goal of the camera was to show all the players at once while maintaining a distance which provides a good view of the surrounding area.

public class AutoCamera : MonoBehaviour 
{ 
	[SerializeField] GameObject[] targetsToFollow;
	[SerializeField] float distanceMultiplier = 2; 
	[SerializeField] public float shortestDistance = 10; 
	[SerializeField, Range(1, 10)] public float camSpeed = 1; 
	float currentDistance; 
	
	void Update() 
	{ 
		if (targetsToFollow.Length > 0) 
		{ 
			//Find the current longest distance between the players the furtherest away 
			Vector3 finalPosition = Vector3.zero; 
			float longestDistance = 0; 
			
			if (targetsToFollow.Length > 1) 
			{ 
				for (int i = 0; i < targetsToFollow.Length; i++) 
				{ 
					for (int j = 0; j < targetsToFollow.Length; j++) 
					{ 
						if (i != j) 
						{ 
							float distance = Vector3.Distance(targetsToFollow[i].transform.position, targetsToFollow[j].transform.position); 
							
							if (distance > longestDistance) 
							{ 
								longestDistance = distance;
								
								//Set center position 
								finalPosition = (targetsToFollow[i].transform.position + targetsToFollow[j].transform.position) / 2;
							} 
						} 
					} 
				} 
			} 
			else 
			{ 
				finalPosition = targetsToFollow[0].transform.position; 
			} 
			
			currentDistance = Mathf.Max(longestDistance * distanceMultiplier, shortestDistance); 
			finalPosition += transform.forward * -currentDistance; 
			transform.position = Vector3.Lerp(transform.position, finalPosition, (float)((camSpeed * Time.timeScale) / 100));
		} 
	} 
}