MC2 – Technical – The Importance of Having Mass

When I began developing MC2 I decided that there had to be some kind of way for every match to get more exciting over time. While other games with similar round arenas make it so that the arena itself shrinks over time, or similar, this is not what I wanted to explore at the time. I wanted to explore the concept of damage instead. I wanted damage to have an effect on my player.

There are many games that have tried this before in various ways. For instance, in the Resident Evil series having sustained damage makes your character walk slowly and clumsily. I didn’t want the player’s mobility to be impacted by the lowered health. I wanted the player to be just as capable of moving when damaged. So I thought of a system similar to the Super Smash series, but I wanted it to be less abstract. So I decided that the spheres would have an armour, like a cage, and that this armour could fall to pieces.

The Art

1. I modeled a carved sphere in Blender, like this:

2. I applied the Cell Fracture plugin on it to split it:

3. Now pieces can fall off, like this:

The code

Note that the C# code below is extrapolated from my entire structure so you can understand it. It’s not exactly representative of what’s in there, but it should give you an idea of what I built. I’m also not explaining ABSOLUTELY everything. The following content presumes substantial knowledge of C# and Unity3D.

WARNING, THIS GETS TECHNICAL!

When a collision with a Player is detected...

void OnCollisionEnter(Collision c) {
  if (c.transform.tag == "_Player") {
    // Store the player info
    PlayerBall p = c.transform.GetComponent<PlayerBall>();
    // Sending information from the opponent I just
    // collided with to the function dedicated
    // to calculate the damage
    CalculateDamage(
      p.myVelocityMagnitude,
      p.playerAttributes.maxHP
    );
  }
}
Now let's calculate the damage!
void CalculateDamage(
  float opponentVelocityMagnitude,
  float opponentOriginalMass
) {
  // Calculate the damage percentage
  // based on the maximum amount of health
  // this sphere has. Note that a sphere's
  // maxHP is also its mass. So a 40 HP
  // sphere is 40 Kg heavy, originally
  float damageRawValue = (
    opponentVelocityMagnitude +
    opponentVelocityMagnitude *
    ((opponentOriginalMass / playerAttributes.maxHP) *
    GameManager.singleton.massDifferetialDamageInfluence) -
    myVelocityMagnitude +
    myVelocityMagnitude *
    ((playerAttributes.maxHP / opponentOriginalMass) *
    GameManager.singleton.massDifferetialDamageInfluence)
  );
  // Notice that damageRawValue will
  // be >0f if this sphere's
  // opponent had a higher velocity
  // before impact (as well as a higher
  // original mass, multiplied by the
  // massDifferentialDamageInfluence
  // variable kept in my GameManager
  // If the damage is <0f, then
  // it means this sphere took no damage.
  // The other one must have
  damageRawValue = Mathf.Clamp(
    damageRawValue,
    0f,
    Mathf.Abs(damageRawValue)
  ) / playerAttributes.maxHP;
  // Now that we calculated the damage
  // percentage, time to apply it
  TakeDamage(damageRawValue);
}
Applying the damage and lowering the mass
void TakeDamage(float damagePercent) {
  // Lower the HP by the percent
  // calculated before
  playerAttributes.currentHP -=
    damagePercent * playerAttributes.maxHP;
  // Clamp the HP between 0f and maxHP
  playerAttributes.currentHP = Mathf.Clamp(
    playerAttributes.currentHP,
    0f,
    playerAttributes.maxHP
  );
  // Change the mass to be the same as
  // currentHP
  playerStructure.mainRigidBody.mass =
    playerAttributes.currentHP;
  // On to detach things!
  DetachParts();
}
Lastly, detaching the pieces.
void DetachParts() {
  // We need a counted for a loop
  // to detach parts, starting at 0
  int detachCount = 0;
  // We need to calculate how many pieces
  // we have to detach. Note that I need a
  // CeilToInt because HP is a float, not
  // an int. Also, note that I already have
  // two lists for the parts that I initialised
  // and populated earlier. list_rigidBodies
  // contains the rigidbodies of all of the
  // parts that belong to this player.
  // list_gameRigidBodies contains the
  // rigidbodies of all of the parts that are
  // still attached to the player
  int partsToDetach = Mathf.CeilToInt(
    (
      playerStructure.list_gameRigidBodies.Count /
      playerStructure.list_rigidBodies.Count -
      playerAttributes.currentHP /
      playerAttributes.maxHP
    ) * playerStructure.list_rigidBodies.Count
  );
  // I'm going to need an index randomiser
  int tempRand;
  // As long as there are parts to detach and
  // the detachCount hasn't reached its target...
  while (
    playerStructure.list_gameRigidBodies.Count > 0 &&
    detachCount < partsToDetach
  ) {
    // Assign the random ID for the part
    tempRand = Random.Range(0, playerStructure.list_gameRigidBodies.Count);
    // Activate rigidbody and collider
    playerStructure.list_gameRigidBodies[tempRand].isKinematic =
      false;
    playerStructure.list_gameBoxColliders[tempRand].enabled =
      true;
    // Change the layer so the detached parts
    // don't damage the surrounding players
    playerStructure.list_gameRigidBodies[tempRand].gameObject.layer =
      GameManager.singleton.debrisSpentLayer;
    // Unparent the chosen part
    playerStructure.list_gameRigidBodies[tempRand].transform.parent =
      null;
    // Make if fly away from the Player
    // using a force that originates at
    // the center of the player, with a
    // random magnitude between two constants
    // that I keep in my GameManager singleton
    // ignoring the mass of each part (VelocityChange)
    playerStructure.list_gameRigidBodies[tempRand].AddForce(
      (playerStructure.list_gameRigidBodies[tempRand].transform.position - transform.position).normalized *
      Random.Range(GameManager.singleton.debrisForces[0], GameManager.singleton.debrisForces[1]),
      ForceMode.VelocityChange
    );
    // Remove the chosen part from the game
    // lists
    playerStructure.list_gameRigidBodies.RemoveAt(tempRand);
    playerStructure.list_gameBoxColliders.RemoveAt(tempRand);
    // Increment the counter
    detachCount++;
  }
}

The result

There, the sphere’s armour is falling apart because some of its parts are flying off. I quite like this effect myself.

Wait a second champ!

You may now ask: so what’s the deal with mass? Well, I actually don’t need to do anything about it really. Each of the collisions in the game is controlled by the Unity physics engine, which already accounts for mass when two objects collide. Since I’ve lowered the mass of the green sphere via code, it will be lighter next time it’s hit by the red sphere, which will make the green one fly further when hit. Neat, eh?

Till next time!

– Mike

Leave a Reply

Your email address will not be published. Required fields are marked *