Skip to content
game

The Void Above

Ahnaf An Nafee

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.

The Void Above

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.

Code Snippets

Code for OffScreen GUI Indicator

CSHARP
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

CSHARP
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

Screenshot 1 Screenshot 2