Director, programmer, Technical artist

Southbound

 
 
 

It’s late in the year, when the copper leaves have started to spiral downwards and the blackberries are at their sweetest, but for all the autumnal beauty the chill in the air sends a clear warning: the first frost is coming. It’s time to move on. Either you do, trusting your inner compass and the strength that’s found in numbers, or you will die alone in the cold, dark night.

 
 
 

Overview

Southbound is a survival game focused around cooperative online multiplayer. Each player controls one starling in a much larger flock as they migrate south for the winter, only able to communicate through tweeting to their neighbors. As they face the various hazards of their journey they must make their decisions as a group based on their neighbor’s reactions—as a result, the game is very centered around attention, nonverbal cues, and a healthy dose of trust.

The game is being written in C# in Unity, using procedurally-generated environments for replayability along the journey from eastern Europe into northern Africa. To fill in the flock, I'm writing AI based on Craig Reynold's 1987 SIGGRAPH paper on flock modeling and subsequent boids algorithms such as Bajec's and Heppner's paper Organized Flight in Birds, combined with the behaviors needed for Southbound's survival mechanics. 

 

 

Code Excerpt

public class StarlingController : AvianController {
  
  [...]
   
   protected override void FixedUpdate () {
   base.FixedUpdate ();

   /*ROTATION*/

   //Pitch
   if (leftAnalogueVal.x != 0) {
   newPitch += leftAnalogueVal.x;
   }
   else if ((leftAnalogueVal.x == 0f) && (newPitch > 180f)) //if no input, level out in the closer direction
   newPitch = 360;
   else
   newPitch = 0;

   //Yaw
   newYaw -= leftAnalogueVal.y;

   //Roll
   if (leftAnalogueVal.y > 0) {
   bankingAngle = (triggerVal.x * 90) + (triggerVal.y * -90) + (leftAnalogueVal.y * 30);
   newRoll = Mathf.Clamp (bankingAngle, 0f, 90f);
   }
   else if (leftAnalogueVal.y < 0) {
   bankingAngle = (triggerVal.x * 90) + (triggerVal.y * -90) + (leftAnalogueVal.y * 30);
   newRoll = Mathf.Clamp (bankingAngle, -90f, 0f);
   }
   else {
   bankingAngle = (triggerVal.x * 90) + (triggerVal.y * -90);
   newRoll = Mathf.Clamp (bankingAngle, -90f, 90f);
   }

   //Rotation speed based on whether or not there's active controller input
   if (triggerVal.x != 0 || triggerVal.y != 0)
   rotSpeed = rotSpeedActive;
   else
   rotSpeed = rotSpeedInactive;

   //Create quaternion from the input angles and slerp the current rotation to it
   newRot = Quaternion.Euler (newPitch, newYaw, newRoll);
   rb.rotation = Quaternion.Slerp (rb.rotation, newRot, Time.deltaTime * rotSpeed);

   deltaRot = initRot - rb.rotation.eulerAngles;
   initRot = rb.rotation.eulerAngles;


   /*TRANSFORM*/

   //Forward -- a(z)
   if (System.Math.Abs (triggerVal.x) > 0 && System.Math.Abs (triggerVal.y) > 0) { //if both triggers are down
   accel.z = (triggerVal.x + triggerVal.y) * .001f / 2f - airResistance.z;
   } else
   accel.z = -1f * airResistance.z;

   SetSpeciesVars ();

   }
 
GermanySpruce2.png