The Void Above
Ahnaf An Nafee / April 01, 2021
unity
csharp
photoshop
figma
illustrator
maya
perforce
🚀 The Void Above is a new action/adventure game from the developers, Void Gaming.
Overview
The Void Above is a new action/adventure game from the developers, Void Gaming.
The Void Above takes aspects from Journey and Death Stranding. Our game will have a focus on both action and adventure where the player can explore the beautiful landscape while fighting enemies along the way.
Responsibilities
-
As the producer, I was in charge of overlooking all aspects of development and implementation within the game. I held weekly scrum meetings with my team every week to ensure all features were being delivered on time. My firm philosophy of iterating through implementing and playtesting from the early design phase helped ensure all parts of the game was always up to code.
-
I was also the lead UI designer for the game, so every UI was singularly crafted by me with user experience in mind. Throughout the weekly play tests, I noted each issue and features that players were asking for from the interface standpoint and implemented them based off that.
-
As the project lead, I was familiar with every aspect of development, from the programming to the art side. I analyzed the existing code and applied efficient tweaks and solutions, reducing draw calls by more than 60%. From the modelling standpoint, I UV’d existing models in the game and reduced their mesh to boost performance in-game; utilized LODs for this process.
-
I developed the HUD manager within the game which utilized multiple vector calculations to draw the GUI in the proper area of the camera view. The HUD was also made modular so that it could be applied to any GameObject in the scene.
Blog Links
Code Snippets
Code for
OffScreen GUI Indicator
void OffScreen(int i)
{
//if transform destroy, then remove from list
if (huds[i].m_Target == null)
{
huds.Remove(huds[i]);
return;
}
if (huds[i].Arrow.ArrowIcon != null && huds[i].Arrow.ShowArrow)
{
//Check target if OnScreen
if (!HudUtility.isOnScreen(HudUtility.ScreenPosition(huds[i].m_Target), huds[i].m_Target))
{
//Get the relative position of arrow
Vector3 ArrowPosition = huds[i].m_Target.position + huds[i].Arrow.ArrowOffset;
Vector3 pointArrow = HudUtility.mCamera.WorldToScreenPoint(ArrowPosition);
pointArrow.x = pointArrow.x / HudUtility.mCamera.pixelWidth;
pointArrow.y = pointArrow.y / HudUtility.mCamera.pixelHeight;
Vector3 mForward = huds[i].m_Target.position - HudUtility.mCamera.transform.position;
Vector3 mDir = HudUtility.mCamera.transform.InverseTransformDirection(mForward);
mDir = mDir.normalized / 5;
pointArrow.x = 0.5f + mDir.x * 20f / HudUtility.mCamera.aspect;
pointArrow.y = 0.5f + mDir.y * 20f;
if (pointArrow.z < 0)
{
pointArrow *= -1f;
pointArrow *= -1f;
}
//Arrow
GUI.color = huds[i].m_Color;
float Xpos = HudUtility.mCamera.pixelWidth * pointArrow.x;
float Ypos = HudUtility.mCamera.pixelHeight * (1f - pointArrow.y);
//palpating effect
if (huds[i].isPalpitin)
{
Palpating(huds[i]);
}
//Calculate area to rotate guis
float mRot = HudUtility.GetRotation(HudUtility.mCamera.pixelWidth / (2), HudUtility.mCamera.pixelHeight / (2), Xpos, Ypos);
//Get pivot from area
Vector2 mPivot = HudUtility.GetPivot(Xpos, Ypos, huds[i].Arrow.ArrowSize);
//Arrow
Matrix4x4 matrix = GUI.matrix;
GUIUtility.RotateAroundPivot(mRot, mPivot);
GUI.DrawTexture(new Rect(mPivot.x - HudUtility.HalfSize(huds[i].Arrow.ArrowSize), mPivot.y - HudUtility.HalfSize(huds[i].Arrow.ArrowSize), huds[i].Arrow.ArrowSize, huds[i].Arrow.ArrowSize), huds[i].Arrow.ArrowIcon);
GUI.matrix = matrix;
float ClampedX = Mathf.Clamp(mPivot.x, 20, (Screen.width - offScreenIconSize) - 20);
float ClampedY = Mathf.Clamp(mPivot.y, 20, (Screen.height - offScreenIconSize) - 20);
GUI.DrawTexture(HudUtility.ScalerRect(new Rect(ClampedX, ClampedY, offScreenIconSize, offScreenIconSize)), huds[i].m_Icon);
Vector2 ClampedTextPosition = mPivot;
//Icons and Text
if (!huds[i].ShowDistance)
{
if (!string.IsNullOrEmpty(huds[i].m_Text))
{
Vector2 size = TextStyle.CalcSize(new GUIContent(huds[i].m_Text));
ClampedTextPosition.x = Mathf.Clamp(ClampedTextPosition.x, (size.x + offScreenIconSize) + 30, ((Screen.width - offScreenIconSize)- 10) - size.x);
ClampedTextPosition.y = Mathf.Clamp(ClampedTextPosition.y, (size.y + offScreenIconSize) + 35, ((Screen.height - size.y) - offScreenIconSize) - 20);
GUI.Label(HudUtility.ScalerRect(new Rect(ClampedTextPosition.x - (size.x / 2), ClampedTextPosition.y - (size.y / 2), size.x, size.y)), huds[i].m_Text, TextStyle);
}
}
else
{
float Distance = Vector3.Distance(localPlayer.position, huds[i].m_Target.position);
if (!string.IsNullOrEmpty(huds[i].m_Text))
{
string text = huds[i].m_Text + "\n <color=white>[" + string.Format("{0:N0}m", Distance) + "]</color>";
Vector2 size = TextStyle.CalcSize(new GUIContent(text));
ClampedTextPosition.x = Mathf.Clamp(ClampedTextPosition.x, (size.x + offScreenIconSize) + 30, ((Screen.width - offScreenIconSize) - 10) - size.x);
ClampedTextPosition.y = Mathf.Clamp(ClampedTextPosition.y, (size.y + offScreenIconSize) + 35, ((Screen.height - size.y) - offScreenIconSize) - 20);
GUI.Label(HudUtility.ScalerRect(new Rect(ClampedTextPosition.x - (size.x / 2), (ClampedTextPosition.y - (size.y / 2)), size.x, size.y)), text, TextStyle);
}
else
{
string text = "<color=white>[" + string.Format("{0:N0}m", Distance) + "]</color>";
Vector2 size = TextStyle.CalcSize(new GUIContent(text));
ClampedTextPosition.x = Mathf.Clamp(ClampedTextPosition.x, (size.x + offScreenIconSize) + 30, ((Screen.width - offScreenIconSize) - 10) - size.x);
ClampedTextPosition.y = Mathf.Clamp(ClampedTextPosition.y, (size.y + offScreenIconSize) + 35, ((Screen.height - size.y) - offScreenIconSize) - 20);
GUI.Label(HudUtility.ScalerRect(new Rect(ClampedTextPosition.x - (size.x / 2) , (ClampedTextPosition.y - (size.y / 2)), size.x, size.y)),text, TextStyle);
}
}
}
GUI.color = Color.white;
}
}
Shoot function excerpt
public void Shoot()
{
//Find the exact hit position using a raycast
Ray ray = mainCam.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0));
RaycastHit hit;
//check if ray hits something
Vector3 targetPoint;
//
if (Physics.Raycast(ray, out hit, Mathf.Infinity, layerMask))
{
targetPoint = hit.point;
}
else
{
Physics.Raycast(ray, out hit, Mathf.Infinity);
targetPoint = ray.origin + ray.direction * 10000.0f;
}
//Calculate direction from attackPoint to targetPoint
var position = attackPoint.position;
Vector3 directionWithoutSpread = targetPoint - position;
// For debugging bullet path
Debug.DrawRay(position, directionWithoutSpread, Color.red, 7, false);
//Calculate spread
float x = Random.Range(-spread, spread);
float y = Random.Range(-spread, spread);
//Calculate new direction with spread
Vector3 directionWithSpread = directionWithoutSpread + new Vector3(x, y, 0);
//Instantiate bullet/projectile
var currentBullet = Instantiate(GameObject.Find("Player").GetComponent<Player>().isPowered() ? poweredBullet : bullet, position, Quaternion.identity);
GameObject.Find("Player").GetComponent<Player>().Shoot();
currentBullet.transform.forward = directionWithSpread.normalized;
currentBullet.GetComponent<Rigidbody>().AddForce(directionWithoutSpread.normalized * shootForce, ForceMode.Impulse);
currentBullet.GetComponent<Rigidbody>().AddForce(mainCam.transform.up * upwardForce, ForceMode.Impulse);
AkSoundEngine.PostEvent("shoot_event",this.gameObject);
StartCoroutine(DelayTrail(currentBullet, 0.01f));
}
Screenshots