DEMIGUN (2020-)

DG_Gameplay1.gif

SCRAPTOWN (2019)

Scraptown was my capstone project that I worked on as a part of my final year of the RMIT Games course. I was the team’s sole programmer and responsible for building the game’s various systems from the ground up.

Branching / Conditional Dialogue + Node-Based Navigation

For Scraptown I created a system that allows for dialogue to change depending on the responses the player(s) choose.
‘Encounters’ are scriptable objects that store information such as the dialogue, character model to display and conditions that may be set upon triggering this encounter such as “met moustache kid before“.

A big part of my process when making this system was to ensure that it was easy to use for the rest of my team. This meant that all the game’s dialogue and narrative was implemented purely through Unity’s inspector. I wanted to avoid any scenarios that would require other members of my team to open and edit scripts.

Code Sample

The game’s dialogue system runs off a switch statement to keep each stage of the process isolated.
This is the section that runs once an option is chosen.

            case 3: //enact choice
                if (encounterInfo.buttonOutcomes.Length > 0) //if there's no subsequence encounters, skip
                {
                    baseE = false; //this is no longer the first panel of the conversation
                    if (selectedOpt == "opt1")
                    {
                        //  because there could be 2 elements in the array, check each if null
                        //  this way some choices can lead to more dialogue while others can end the encounter
                        if (encounterInfo.buttonOutcomes[0] != null) 
                        {
                            prev_opt.text = encounterInfo.button[0]; //display this as the previously chosen option
                            encounterInfo = encounterInfo.buttonOutcomes[0]; //set current encounter to this encounter
                            DialogueState = 1; //update UI
                        }
                        else
                        {
                            DialogueState = 4; //end conversation
                        }
                    }

Character Select Screen

A quick character selection screen that lets you choose your character before entering the game.
Your selection controls what model and sprite will display during dialogue and overworld navigation.

charSelSS.PNG

 axe throw VR (2019)

A personal project I worked on to experiment with VR development.
The aim of Axe Throw VR was to recreate the Leviathan Axe’s throw and retrieval mechanic from Santa Monica Studio’s ‘God of War’ (2018).

Code Sample

This is the code for apply force to objects that you throw. There’s also some checks for if you’re holding a ‘weapon’ or not.
If you are, then I added some juice to the throw by adding some extra force and disabling gravity temporarily.

if (throwing) //this triggers once you let go of an object
{
  if (rb != null) //make sure there's actually a rigidbody
  {
    if (rb.tag == "Pickup") //for items that aren't weapons e.g. a small cube
    {
      rb.velocity = vel.GetVelocityEstimate(); //apply the vive controller's velocity to the object
      rb.angularVelocity = vel.GetAngularVelocityEstimate();
      rb.maxAngularVelocity = rb.angularVelocity.magnitude;
    }
    else if (rb.tag == "Weapon") //if this object is defined as a weapon
    {
      currentWeapon = rb.gameObject;
      if (vel.GetVelocityEstimate().magnitude > velToWeaponThrow) //if the controller is moving fast enough, this is a throw
      {
        Vector3 angVel = vel.GetAngularVelocityEstimate(); //get velocity of vive controller
        rb.useGravity = false; //turn off gravity temporarily to make it go further
        rb.velocity = vel.GetVelocityEstimate() * 1.4f; //apply velocity of controller to the object
        rb.angularVelocity = angVel; //apply angular velocity of controller
        rb.AddTorque(rb.transform.TransformDirection(Vector3.right) * 1000, ForceMode.Impulse); //add extra force to throw to make you feel STRONG
        rb.maxAngularVelocity = rb.angularVelocity.magnitude; //set the max angular vel
      }
      else //dont add extra force/juice if you aren't actually 'throwing it'
      {
        rb.velocity = vel.GetVelocityEstimate();
        rb.angularVelocity = vel.GetAngularVelocityEstimate();
        rb.maxAngularVelocity = rb.angularVelocity.magnitude;
      }
    }
  }
  throwing = false;
}

the pit (2019)

Created as a part of my third year in the RMIT Games course.
This was one of 3 ‘Sprints’ where we were given 4 weeks to make a game.

Segmented Level Generation:

The Pit’s level generation system works off a sequence of room prefabs being loaded.
Each room has a beginning and ending ‘node’ that signifies the joining point for each room so that we can have rooms make any change in elevation and still allow them to line up.

Code Sample

void GenerateRooms()
    {
        for (int i = 0; i < howManyRooms; i++)
        {
            int randRoom = Random.Range(0, allRooms.Length);
            GameObject roomToCreate = Instantiate(allRooms[randRoom], spawnPosition, Quaternion.identity);
            Room roominfo = roomToCreate.GetComponent<Room>();
            spawnPosition = roominfo.EndNode.transform.position; //get where to spawn the next room from the end node of this room
            if (roominfo.type == Room.Type.connector)
            {
                //if the room chosen is a connector room, make sure that it is followed by a main room. this ensures that you won't get just a huge string of connector rooms.
                int randMainRoom = Random.Range(0, mainRooms.Length);
                GameObject MainRoomAfterConnector = Instantiate(mainRooms[randMainRoom], spawnPosition, Quaternion.identity);
                Room mainRoomInfo = MainRoomAfterConnector.GetComponent<Room>();
                spawnPosition = mainRoomInfo.EndNode.transform.position;
            }
        }
        //once all rooms are created, slap a saferoom on the end as a reward
        GameObject SafeRoom = Instantiate(safeRooms[Random.Range(0, safeRooms.Length)], spawnPosition, Quaternion.identity);
        Room SafeRoomInfo = SafeRoom.GetComponent<Room>();
        spawnPosition = SafeRoomInfo.EndNode.transform.position;
        NavSurface.BuildNavMesh(); //build navmesh at runtime
    }

Line of Sight AI:

Enemies have an internal visibility meter that builds as you are both within their range and line of sight.
Once this reaches a point they will pursuit you. If you break line of sight, they will move to your last known location.

Code Sample

void OnTriggerStay(Collider colInfo) //trigger defines FOV of AI
    {
        if (colInfo.gameObject == PlayerObj)
        {
            var heading = PlayerObj.transform.position - thisPos;
            var distToPlayer = heading.magnitude;
            var dirToPlayer = heading / distToPlayer; //get direction of player from here

            RaycastHit hitInfo;
            if (Physics.Raycast(thisPos, dirToPlayer, out hitInfo))
            {
                var hitHeading = hitInfo.point - thisPos;
                var hitDist = hitHeading.magnitude;
                var hitDir = hitHeading / hitDist; //get direction of hit for debug ray
                if (hitInfo.collider.gameObject == PlayerObj.gameObject) //are we hitting the player?
                {
                    inLOS = true;
                    lastKnownLoc = hitInfo.point; //last place player was seen
                } else {
                    inLOS = false; 
                }
                //draw the ray so we can see how its working
                Debug.DrawRay(thisPos, hitDir * Vector3.Distance(thisPos, hitInfo.point), Color.yellow);
            }
            if (playerFound == true) //this activates from a value that increases if 'inLOS' is true
            {
                navAgent.SetDestination(lastKnownLoc);
            }
        }
    }

Saving sorenia (2019)

Third Person Character/Camera, Dialogue and Item Index: