CLOUD DELIVERY GAME JAM
SOLO PROJECT
DURATION: 1 Month (10/2022)
MADE WITH: Unity, Blender
CORE SKILLS:
C# Programming
Game & Level Design
PERSONAL AIMS
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:
- A vehicle the player could enter and exit from
- A physical hold for the ships cargo.
- 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.
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.
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
- taking off
- landing
- flying
- (plus stationary, so I guess four)
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.