Gravity Is Broken

Overview

  • Release Date: May 2015
  • Platform: PC
  • Team Size: 6
  • Project Length: 9 Months
  • Engine: Unity
  • Language: C#
  • Tools: Adobe Suite, Maya

Near the end of my time at Becker College, I, like all students in my program, was required to create a game as part of the final "Senior Game Project" class. This was a full-year class which tasked us with creating a game as a team. Gravity Is Broken is the result of that class.

The game puts players on a hostile alien world. Something has gone wrong with the planet's gravity, causing your ship to crash land there. The world is difficult to navigate, and your only tool is your Gravity Gun, which can create miniature gravity fields to help move you along its path. You can use these fields to navitate the world, or to push enemies to their demise.

The game was featured at Becker College's PAX East booth in 2015, and received much acclaim there and outside the event. I also learned a lot about working on a team and meeting deadlines as a result of this project. My one regret with this project is that we used both C# and UnityScript in the same project - knowing what I know now, I would push to use one or the other, since it made communicating between the two systems very difficult.

Responsibilities

  • Programmed Gravity Gun mechanics and physics, and integrated gun animations
  • Programmed and greyboxed puzzle mechanics, including pressure switches and moving platforms
  • Setup enemies, including animation controller and finite state machine AI
  • Created particle system that "warped" players to the next level
  • Setup Perforce server for source control and collaboration between team members
  • Added leaderboards for PAX East 2015 appearance

Gravity Field Code

We initially tried using a separate script on each gravity field which applied independent forces to an object. This worked when the object was influenced by only one gravity field, however when it was affected by multiple fields the forces would fight against each other, resulting in stuttery movement.

GravityFieldManager is a central class which manages all the gravity fields in the scene. The below code combines the forces of whatever gravity fields are affecting an object and applies them together, resulting in a smooth movement effect.

void FixedUpdate () {
    // iterate through each object in the scene THAT HAS A RIGIDBODY (this will exclude environment meshes)
    for (int i = 0; i < sceneRigidbodies.Length; i++) {
      Vector3 combinedGravityVector = Vector3.zero;
      Vector3 combinedMaxVelocityChangeVector = Vector3.zero;
      bool objectIsInTransitionField = false;

      for (int j = 0; j < activeGravityFields.Count; j++) {
        if (activeGravityFields[j] != null && activeGravityFields[j].GetComponentInChildren()) {
          GravityField currentField = activeGravityFields[j].GetComponentInChildren();

          // since we are updating sceneRigidbodies in Update on a timer, it is possible for FixedUpdate to run while that array is being updated
          // because of that, check to make sure we are not trying to access a rigidbody that was replaced
          if (sceneRigidbodies[i] != null && currentField.ContainsObject(sceneRigidbodies[i].gameObject)) {
            if (currentField.levelTransitionField) {
              objectIsInTransitionField = true;
              
              Vector3 velocityWithoutMovingToCenter = currentField.MultipliedLocalGravityVector(sceneRigidbodies[i].gameObject, false);
              sceneRigidbodies[i].velocity = velocityWithoutMovingToCenter;
            }
            
            combinedGravityVector += currentField.MultipliedLocalGravityVector(sceneRigidbodies[i].gameObject, true);
            combinedMaxVelocityChangeVector += currentField.MaxVelocityChangeVector();
          }
        }
      }

      // don't apply 0 force to the object
      if (combinedGravityVector != Vector3.zero) {
        Vector3 velocity = sceneRigidbodies[i].velocity;
        Vector3 targetVelocity = combinedGravityVector;
        Vector3 velocityChange = targetVelocity - velocity; // should be the velocity in local coordinates

        Vector3 maxVelocityChangeVector = new Vector3(30.0f, 0.3f, 30.0f);

        velocityChange.x = Mathf.Clamp(velocityChange.x, -maxVelocityChangeVector.x, maxVelocityChangeVector.x);
        velocityChange.z = Mathf.Clamp(velocityChange.z, -maxVelocityChangeVector.z, maxVelocityChangeVector.z);
        velocityChange.y = Mathf.Clamp(velocityChange.y, -maxVelocityChangeVector.y, maxVelocityChangeVector.y);

        if (!objectIsInTransitionField) {
          sceneRigidbodies[i].AddForce(velocityChange, ForceMode.VelocityChange);
        }
      }
    }
  }