CLOUD DELIVERY GAME JAM

SOLO PROJECT

DURATION: 1 Month (10/2022)

MADE WITH: Unity, Blender

GITHUB: https://github.com/CommanderDomcrete/CloudDelivery

CORE SKILLS:

C# Programming

Game & Level Design

PERSONAL AIMS

Cloud Delivery was one of my solo game jam projects, the goal being to make a short playable experience in 1 month. I hadn’t had a proper look at Unity’s new input system and I wanted to try setting up a system to switch between a character and vehicle controls. I didn’t have a lot of time to get the project done (all work being done in my spare time). With this in mind I set a small and achievable pitch with stretch goals to push myself if I had extra time. Setting my self weekly milestones really helped me to knuckle down and push content and features before sunday rolled around.

GAME OVERVIEW

The objective of the game is to deliver four packages to four different locations before time runs out. The player will win if they successfully delivery all their packages.

The game is set in a clouded space with floating island habitats. The player has a ship they can get in and out of to fly between the islands to their delivery destinations. Flying through golden rings adds more seconds to the timer and lead the player around the islands.

END RESULT

INITIAL PLAN

I drew up rough paper plan of the core game experience. Doing this helped to keep the scope small and set the visual tone that I wanted to achieve. I wanted to keep the visuals simple to allow me to focus on the game systems and mechanics.

The ideas I wanted to explore with this Game Jam were:

  1.  A vehicle the player could enter and exit from
  2.  A physical hold for the ships cargo.
  3.  The capability to pick up physical objects.
PLAYER EXPERIENCE

1. The player must deliver packages to various destinations

2. The player will be able to pick up boxes to be delivered.

3. Place boxes into your ship’s hold.

4. The player will pilot the ship and fly to the appropriate destination.

5. The player will need to throw the packages to the desired location of the delivery destination.

6. If the player delivers all of their packages within the time limit they win!!

WHAT ACTUALLY HAPPENED

I successfully added the majority of the planed features into the game, with a few changes. I cut the players ability to throw to keep things simple and have all the players interactions mapped to one button. Instead the player would drop the package into mail box bins. Additionally, I added the time rings to lead the player around the map and to add a little more depth to the flying – the player needs to aim their ship to fly through the rings to earn a little time back.

The vehicle was the most complicated feature as it required implementing input switching. The input switch changes the player controls from a grounded player state to a flying ship state. This proved to be challenging in no small part due to my unfamiliarity with the new input system in Unity – something I was keen to get to grips with in this project.

CODE BREAKDOWN

INTERACTION CONTROLLER

The player needed to interact with a lot of different things in the game world. I found the easiest way to organise this was creating a separate script that would manage the player’s inputs based on the context of the situation.

public class InteractController : MonoBehaviour
{

    public GameObject holdPosition;
    private Rigidbody objRig;
    private float interactRange = 3f;
    public bool holding = false;
    public LayerMask pickUps;
    public LayerMask vehicles;

    public void OnInteract(InputAction.CallbackContext context)
    {
        if (context.performed)
        {
            if (objRig != null)
            {
                Drop();
            }
            else
            {
                InteractCheck();
            }
        }
    }
    
    public void InteractCheck()
    {
        Ray ray = new Ray();
        ray.direction = holdPosition.transform.forward;
        ray.origin = holdPosition.transform.position;
        RaycastHit hit;

        if (Physics.Raycast(ray, out hit, interactRange, pickUps))
        {
            objRig = hit.collider.gameObject.GetComponent<Rigidbody>();

            if (!holding)
            {
                PickUp();            
                Debug.Log("Picking Up Object");
            }
        }
        else if (Physics.Raycast(ray, out hit, interactRange, vehicles))
        {
            hit.collider.gameObject.GetComponent<EnterVehicle>().BoardVehicle();
        }
    }

    public void PickUp()
    {
        //Make Object a child of the HoldPosition and move it to default position
        objRig.transform.SetParent(holdPosition.transform);
        objRig.transform.localPosition = Vector3.zero;
        objRig.transform.localRotation = Quaternion.Euler(Vector3.zero);
        objRig.isKinematic = true;
        objRig.GetComponent<BoxCollider>().enabled = false;
        holding = true;
    }

    private void Drop()
    {
        //Set Parent to null
        objRig.transform.SetParent(null);
        objRig.isKinematic = false;
        objRig.GetComponent<BoxCollider>().enabled = true;
        objRig = null;
        holding = false;
    }
}

LINE 26 – INTERACT CHECK

This determines what the ‘interact’ button will do depending on what the player is looking at. Is the player holding something? If not, is there something the player can pick up or a vehicle to get into?

This project taught me to split up my code more into specific functions that fulfilled a specific purpose. Breaking the script up into manageable chunks helped me think through what I wanted to happen and when I wanted it to happen.

Looking back I think I could of integrated this script into the player controller to keep all player-centric actions together. If there were a lot more actions the player could do maybe I could’ve created an action manager script or something like that.

SHIP STORAGE

For the ship storage I used a public array in which I could place empty game objects or storage slots. Then I used a loop to check each index slot for a matching tag. Once a match is found, the package is parented to that storage slot.

GameJam2Storage
public class ShipStorage : MonoBehaviour
{
    public GameObject[] holdSlots;
    private int slotCheckLoop;
    private bool matchFound;

    private void OnTriggerEnter(Collider col)
    {
        matchFound = false;
        slotCheckLoop = 0;
        
        while(slotCheckLoop < holdSlots.Length && !matchFound)
        {
            MatchSlot(col);
            slotCheckLoop++;
        }
    }
    void MatchSlot(Collider other)
    {
        int slotIndex = slotCheckLoop;
        GameObject currentHoldSlot = holdSlots[slotIndex];
        GameObject obj = other.gameObject;

        if (obj.CompareTag(currentHoldSlot.tag))
        {
            obj.transform.SetParent(currentHoldSlot.transform);
            obj.transform.localPosition = Vector3.zero;
            obj.transform.localRotation = Quaternion.Euler(Vector3.zero);
            obj.GetComponent<Rigidbody>().isKinematic = true;
            matchFound = true;
        }
        else
        {
            matchFound = false;
        }
    }
}

SHIP LANDING AND TAKE-OFF

I wanted to create an experience in which flying the ship was simple and straight forward but also allowed the player to land and take off with control and ease. To this end the ship has three states it can be in;
  • taking off
  • landing
  • flying
  • (plus stationary, so I guess four)
When the ship is taking off it hovers up into the air for a short time to get clear of the ground, then the thrusters turn on to move the ship forward at a set speed. The player can then turn and roll the ship guiding it to their next destination. 
 
When the ship lands, it stops any forward motion and descends down until a ray-cast hits a collider. At this point it stops and the player is able to get out.
public class SpaceshipController : MonoBehaviour
{

...

    IEnumerator TakeOff()
    {
        timePassed = 0;
        while (timePassed < 2)
            {
                shipRb.velocity = transform.up * verticalThrust;
                timePassed += Time.deltaTime;

                yield return null;
            }
        inFlight = true;
        shipRb.useGravity = false;
    }

    IEnumerator Land()
    { 
        inFlight = false;
        while (!IsGrounded())
        {
            shipRb.velocity = transform.up * -verticalThrust;
            yield return null;
        }
        shipRb.useGravity = true;
        shipEngineAudio_01.Stop();
    }

    private void Thrust()
    {
        if (inFlight)
        {
            maxThrust = 20 + boost;

            shipRb.velocity = transform.forward * currentThrust;

            currentThrust += acceleration * Time.deltaTime;
            if (currentThrust > maxThrust)
                currentThrust = maxThrust;
        }
        else
        {
            currentThrust = 0;
        }
    }
    
    private bool IsGrounded()
    {
        if(Physics.Raycast(transform.position, Vector3.down, out hit, 2.5f))
            {
            }
            return hit.collider != null;
    }

    private void Rotate()
    {
        angularVel = new Vector3(look.y * sensitivity, look.x * sensitivity, roll * rollSens);
        shipRb.AddRelativeTorque(angularVel);
        Quaternion deltaRotation = Quaternion.Euler(angularVel * Time.deltaTime);
        shipRb.MoveRotation(shipRb.rotation * deltaRotation);
    }

}

Despite getting all the features around the ship working properly, from climbing in and out to the package storage (even a little door for the storage compartment to open and close on landing and takeoff), there was a bug. The bug would occur after the thrusters engaged and the ship would spiral out of the players control rendering it impossible to fly. This bug only occurs when packages are in the storage hold leading me to believe it is the result of having rigidbodies inside other rigidbodies. This was a difficult bug to troubleshoot as it only occurred in build and not in editor. This reinforces the importance of not just testing the game in editor but also testing the game in a packaged build.

If I was to make a vehicle that behaves like this again, I wouldn’t use a rigidbody. The ship does not need to interact with the world in a physical way. It also would have avoided any issues I would have had with rigidbodies interacting with each other in undesirable ways.

CONCLUSION

For this project I planned things out to cover the whole time I would be working on it and not only did it keep my in scope it allowed me to complete the project to a standard I am happy with and which I am proud to look back at.

Referring to documentation and tutorials I successfully made a player controller using the new input system and took advantage of input switching for flying the ship. It was really fun building the two controllers and eventually getting them to interact with eachother. It also made setting up different controller bindings easy.

It was great being able to flex my art skills in a more minimalistic way whilst contributing to all parts of the game from the environment and vehicles to the clouds and time rings. I find looking at cloud delivery really pleasing and I’m impressed with what I achieved with so little in even less time.

Unfortunately I did not have a complete grasp of how the physics in Unity worked when I made the ship storage system which led to bugs in flying the ship. Building a system which does not involve multiple rigid bodies moving together would probably be the easier path to take next time but this experience still be incredibly useful and will likely be key to any future attempts at making a vehicle system.

 

THANKS!