
Qiyu
Unity Game 3
A third-person Unity game telling the survival crisis of penguins.
Chapter 1: Main Character
Protagonist
The Researcher
The protagonist is a young researcher who works in a time travel lab. He is responsible for traveling back in time to research the living conditions of penguins in the past, and to find out the reason why penguins are facing extinction in the future.



Gears
The scanner on his right waist can capture detailed stats of penguins, and the data will be sent back to lab for analysis. His specialized glasses also have scanning function, and can identify encountered penguin types.
His all-in-one suit can protect him from all extreme climates, making him survive the icy water of polar region and immune to any fall damage.


Penguins' Soul
The mentor
Penguins' Soul is the only NPC in the game, who taught the protagonist a lot about the past of penguins, and the reason why they are facing extinction in the future. Also, throughout the adventure, the protagonist will receive numerous important hints from Penguins' Soul.


Chapter 2: Level Design
Time Travel Lab
Time Setting: 3022
This is an underground time travel lab, also the entrance level of the game, where players get to know about the background story of the game. After we clearly inform the players about their duties, they step into the machine onto a trip to 200,000 years ago.


Zealandia
Time Setting: 200,000 years ago
This is a much brighter larger outdoor scene. The player can freely explore the world and research for the habitat patterns of the earliest species of penguins.
Also, upon entering the world, the player will get to meet the first NPC of the game -- Penguins' Soul, who will guide the player through the game.



Antarctica
Time Setting: 2122
This level was to portray how the polar region would look like a hundred years later. There are few floating ice on the ocean face as ice are melting due to extreme climates. As a result, it can be clearly seen throughout the scene that: penguins' living space is exploited and their living condition is worsening.



Camera Zoom
Zooms the Third-Person-Cam based on player's sprinting status or mouse scroll input. It also helps focus on essential elements in the scene.
using UnityEngine;
using DG.Tweening;
public class CameraZoom : MonoBehaviour
{
[Header("FOV")]
public float fovRange = 8f;
public float fovDelta = 4f;
[Header("Camera Distance")]
public float distanceRange = 0.25f;
public float distanceDelta = 0.2f;
[Header("Factors")]
public float zoomDuration = 0.3f;
public float zoomInFactor = 1.5f;
public float rightOffsetFactor = 0.3f;
public float scrollFactor = 0.025f;
private vThirdPersonCamera cam;
private float FOV, FOVmin, FOVmax, FOVdefault, tempFOV, tempDistance, defaultDistance, defaultRightOffset;
private void Start()
{
cam = GetComponent<vThirdPersonCamera>();
defaultDistance = cam.defaultDistance;
tempDistance = defaultDistance;
defaultRightOffset = cam.rightOffset;
FOV = GetComponent<Camera>().fieldOfView;
FOVdefault = FOV;
tempFOV = FOV;
SetMinMax();
}
private void Update()
{
if (Input.GetKey(KeyCode.W) || Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.S) || Input.GetKey(KeyCode.D))
if (Input.GetKeyDown(KeyCode.LeftShift))
ZoomOut();
if (Input.GetKeyUp(KeyCode.LeftShift))
ZoomToDefault();
if (Input.GetMouseButtonDown(1))
ZoomIn();
if (Input.GetMouseButtonUp(1))
ZoomToDefault();
if (Input.mouseScrollDelta.y != 0)
ZoomOnMouseScroll();
}
private void ZoomIn()
{
tempFOV = FOV;
FOVmin -= fovDelta * 2f;
DOTween.To(() => FOV, x => FOV = x, FOV - fovDelta * 2f, zoomDuration * zoomInFactor)
.OnUpdate(() => GetComponent<Camera>().fieldOfView = FOV);
DOTween.To(() => tempDistance, x => tempDistance = x, tempDistance - distanceDelta * 1.75f, zoomDuration * zoomInFactor)
.OnUpdate(() => cam.defaultDistance = tempDistance);
DOTween.To(() => defaultRightOffset, x => defaultRightOffset = x, defaultRightOffset + distanceDelta * rightOffsetFactor, zoomDuration * zoomInFactor)
.OnUpdate(() => cam.rightOffset = defaultRightOffset);
}
private void ZoomOut()
{
tempFOV = FOV;
FOVmax += fovDelta;
DOTween.To(() => FOV, x => FOV = x, FOV + fovDelta, zoomDuration)
.OnUpdate(() => GetComponent<Camera>().fieldOfView = FOV);
DOTween.To(() => tempDistance, x => tempDistance = x, tempDistance - distanceDelta, zoomDuration)
.OnUpdate(() => cam.defaultDistance = tempDistance);
}
private void ZoomToDefault()
{
DOTween.To(() => FOV, x => FOV = x, tempFOV, zoomDuration)
.OnUpdate(() => GetComponent<Camera>().fieldOfView = FOV);
DOTween.To(() => cam.defaultDistance, x => cam.defaultDistance = x, tempDistance, zoomDuration)
.OnUpdate(() => cam.defaultDistance = tempDistance);
DOTween.To(() => cam.rightOffset, x => cam.rightOffset = x, defaultRightOffset, zoomDuration).OnUpdate(() => cam.rightOffset = defaultRightOffset);
SetMinMax();
}
private void ZoomOnMouseScroll()
{
float scrollDelta = Input.mouseScrollDelta.y;
tempDistance -= scrollDelta * scrollFactor;
tempDistance = Mathf.Clamp(tempDistance, defaultDistance - distanceRange, defaultDistance + distanceRange);
cam.defaultDistance = tempDistance;
FOV -= scrollDelta * scrollFactor * 20;
FOV = Mathf.Clamp(FOV, FOVmin, FOVmax);
GetComponent<Camera>().fieldOfView = FOV;
}
private void SetMinMax()
{
FOVmax = FOVdefault + fovRange;
FOVmin = FOVdefault - fovRange * 0.75f;
}
}
Typewriter Effect
- Takes user input as a string array.
- Advances through the array index on internal timing or mouse click.
- Breaks down the input string into characters.
- Uses IEnumerator for gradual character display with minor interval.
using UnityEngine;
using System.Collections;
using System;
using TMPro;
public class DialogPlayer : MonoBehaviour
{
public Dialog[] dialogs;
public TextMeshProUGUI dialogText, nameText;
public float characterInterval = 0.05f, autoNextLineTime = 2.5f;
private float _elapsedTime;
private int _index;
private void Start()
{
dialogText.text = string.Empty;
nameText.text = string.Empty;
StartCoroutine(TypeLine());
}
private void Update()
{
_elapsedTime += Time.deltaTime;
if (Input.GetMouseButtonDown(0) || Input.GetKeyDown(KeyCode.Return) || _elapsedTime > autoNextLineTime) SwitchLine();
}
private void SwitchLine()
{
_elapsedTime = 0;
if (dialogText.text == dialogs[_index].dialog)
{
NextLine();
}
else
{
StopAllCoroutines();
dialogText.text = dialogs[_index].dialog;
}
}
private void NextLine()
{
dialogText.text = string.Empty;
if (_index < dialogs.Length - 1)
{
_index++;
StartCoroutine(TypeLine());
nameText.text = dialogs[_index].name;
}
else
{
gameObject.SetActive(false);
}
}
private IEnumerator TypeLine()
{
foreach (char x in dialogs[_index].dialog)
{
dialogText.text += x;
yield return new WaitForSeconds(characterInterval);
}
}
[Serializable]
public struct Dialog
{
public string name;
public string dialog;
}
}