Sky
During a picnic in the park, your son was snatched into the sky by a deranged eagle. You hop into your balloon basket and pack in your spears, killing every bird you encounter until you get your son back.
Two-man collaboration with Nathan Rivera-Melo (my brother) created in Unity with C# for iOS in 3 weeks:
Nathan is responsible for
-
Beautiful Pixel Graphics
-
SFX
-
Conceptual Development
Development Experience


Gameplay Mechanics
Wave Design

*AI

Data Saving
*Code Sample
+ Duck Flying V Formation
+ Ducks Re-Order Themselves & Scatter when the Lead Duck is killed
using UnityEngine;
using System.Collections.Generic;
using GenericFunctions;
public interface IDuckToLeader {
void OrganizeDucks(ILeaderToDuck deadDuck);
Transform[] FormationTransforms{get;}
}
// The Lead Duck communicates with Ducks through this interface
public class LeadDuck : Bird, IDuckToLeader {
// The Lead Duck ensures all ducks follow as closely behind in an evenly distributed Flying V Formation
// The Lead Duck will fly linearly across the screen
[SerializeField] Duck[] duckScripts; List<ILeaderToDuck> ducks;
[SerializeField] Transform[] formationTransforms;
protected override void Awake () {
base.Awake();
ducks = new List<ILeaderToDuck>(duckScripts);
bool goLeft = transform.position.x > 0;
transform.FaceForward(goLeft);
rigbod.velocity = new Vector2 (goLeft ? -1 : 1, 0) * 2.5f;
SetDuckFormation (goLeft);
}
// Set ducks into the "Flying V" Formation, like so:
// [4]
// [2]
// [0]
// [leader] --->
// [1]
// [3]
// [5]
void SetDuckFormation(bool goLeft){
Vector2 topSide = ConvertAnglesAndVectors.ConvertAngleToVector2 (goLeft ? 30 : 150);
Vector2 bottomSide = ConvertAnglesAndVectors.ConvertAngleToVector2 (goLeft ? -30 : 210);
float separationDistance = 0.15f;
for (int i=0; i<formationTransforms.Length; i++){
formationTransforms[i].localPosition = (goLeft ? 1:-1) * (i%2==0 ? topSide : bottomSide) * (Mathf.Floor(i/2)+1) * separationDistance;
ducks[i].FormationIndex = i;
}
}
#region IDuckToLeader
Transform[] IDuckToLeader.FormationTransforms {get{return formationTransforms;}}
void IDuckToLeader.OrganizeDucks(ILeaderToDuck deadDuck){
int deadNumber = deadDuck.FormationIndex;
int topCount=0;
int bottomCount=0;
for (int i=0; i<ducks.Count; i++){
topCount += ducks[i].FormationIndex % 2 ==0 ? 1:0; //birds on top use even indices
bottomCount += ducks[i].FormationIndex % 2 !=0 ? 1:0; // bottom use odd indices
if (ducks[i].FormationIndex>deadNumber && ducks[i].FormationIndex % 2 == deadNumber % 2){
ducks[i].FormationIndex -= 2;
}
}
if (topCount<bottomCount && deadNumber % 2 == 0){
int highestOdd = bottomCount*2-1;
ducks.Find(duck => duck.FormationIndex==highestOdd).FormationIndex -= 3;
}
else if (bottomCount<topCount && deadNumber % 2 != 0){
int highestEven = (topCount-1)*2;
ducks.Find(duck => duck.FormationIndex==highestEven).FormationIndex -= 1;
}
ducks.Remove(deadDuck);
}
#endregion
// Remember that final action some "Bird"s need to perform?
// The DuckLeader Breaks the Flying V Formation and tells each Duck to Scatter when he/she dies
protected override void DieUniquely (){
BreakTheV();
base.DieUniquely();
}
void BreakTheV(){
transform.DetachChildren ();
ducks.ForEach(duck => duck.Scatter());
for (int i=0; i<formationTransforms.Length; i++){
Destroy(formationTransforms[i].gameObject);
}
}
}
using UnityEngine;
using GenericFunctions;
public interface ILeaderToDuck {
void Scatter();
int FormationIndex{get;set;}
}
// The Duck receives communication from the DuckLeader through this interface
public enum XDirection{
UpRight=0,
UpLeft=1,
DownRight=2,
DownLeft=3
}
public interface IDirectable{
void SetDuckDirection(XDirection scatterDirection);
}
public class Duck : Bird, ILeaderToDuck, IDirectable {
// The Duck will follow his/her DuckLeader, until the DuckLeader dies.
// Then, the Duck will aimlessly bounce around the screen until killed
[SerializeField] PixelArtRotation.PixelRotation rotator;
[SerializeField] LeadDuck leaderScript; IDuckToLeader leader;
Transform myFormationTransform;
Vector2[] scatterDir = new Vector2[]{
new Vector2 (1,1).normalized,
new Vector2 (-1,1).normalized,
new Vector2 (1,-1).normalized,
new Vector2 (-1,-1).normalized,
new Vector2 (1,1).normalized,
new Vector2 (-1,1).normalized
};
Vector2 CurrentVelocity{
set{rigbod.velocity = value;
transform.FaceForward(rigbod.velocity.x<0);
rotator.Angle = GetAngle(rigbod.velocity);
}
}
const float moveSpeed = 2.5f;
const float maxSpeed = 4f;
int formationIndex;
bool bouncing;
protected override void Awake () {
base.Awake();
if (transform.parent) {
leader = leaderScript;
}
else{
Scatter();
}
}
void Update(){
if (bouncing){
BounceOnTheWalls ();
}
else{
StayInFormation();
}
}
// This function restricts a duck's movement to the confines of the screen- an omage to DuckHunt
// Although it seems that flipping the sign of the duck's x or y velocity when the duck's position exceed the WorldDimensions
// would eliminate extra lines of code, this is not the case.
// In that scenario, the duck often travels far beyond the WorldDimension, reversing direction and velocity,
// only to be trapped flipping its velocity each frame for some time.
// This solution eliminates that concern.
void BounceOnTheWalls(){
bool overX = transform.position.x> Constants.WorldDimensions.x;
bool underX= transform.position.x<-Constants.WorldDimensions.x;
bool overY = transform.position.y> Constants.WorldDimensions.y;
bool underY= transform.position.y<-Constants.WorldDimensions.y;
if (overX || underX || overY || underY){
if (overX) {
transform.position = new Vector2(Constants.WorldDimensions.x, transform.position.y);
}
else if (underX) {
transform.position = new Vector2(-Constants.WorldDimensions.x, transform.position.y);
}
else if (overY) {
transform.position = new Vector2(transform.position.x, Constants.WorldDimensions.y);
}
else if (underY) {
transform.position = new Vector2(transform.position.x, -Constants.WorldDimensions.y);
}
CurrentVelocity = new Vector2 (
underX ? 1 : overX ? -1 : Mathf.Sign(rigbod.velocity.x),
underY ? 1 : overY ? -1 : Mathf.Sign(rigbod.velocity.y)).normalized * moveSpeed;
}
}
int GetAngle(Vector2 moveDir) {
if (moveDir.y > 0) {
return -45;
}
else {
return 45;
}
}
void StayInFormation(){
transform.position = Vector3.MoveTowards(transform.position, myFormationTransform.position, maxSpeed * Time.deltaTime);
}
#region IDirectable
void IDirectable.SetDuckDirection(XDirection scatterDirection){
CurrentVelocity = scatterDir[(int)scatterDirection] * moveSpeed;
}
#endregion
#region ILeaderToDuck
void ILeaderToDuck.Scatter(){
ScoreSheet.Tallier.TallyThreat(Threat.FreeDuck);
Scatter();
}
void Scatter() {
CurrentVelocity = scatterDir[formationIndex] * moveSpeed;
birdStats.ModifyForEvent(3);
bouncing = true;
}
int ILeaderToDuck.FormationIndex {
get{return formationIndex;}
set{formationIndex = value;
myFormationTransform = leader.FormationTransforms[formationIndex];
}
}
#endregion
// Remember that final action some "Bird"s need to perform?
// When a Duck with a DuckLeader dies, he/she lets the DuckLeader know to reorganize the Flying V formation
protected override void DieUniquely (){
if (leaderScript){
leader.OrganizeDucks(this);
}
base.DieUniquely();
}
}
using UnityEngine;
public interface IHurtable {
void GetHurt(ref WeaponStats weaponStats);
}
public abstract class Bird : MonoBehaviour, IHurtable {
protected BirdStats birdStats; public BirdStats MyBirdStats{get{return birdStats;}}
[SerializeField] BirdType myBirdType;
[SerializeField] protected Rigidbody2D rigbod;
[SerializeField] protected Collider2D birdCollider;
[SerializeField] protected GameObject guts;
protected virtual void Awake(){
birdStats = new BirdStats(myBirdType);
ScoreSheet.Tallier.TallyBirth(ref birdStats);
ScoreSheet.Tallier.TallyBirdThreat(ref birdStats, BirdThreat.Spawn);
}
void IHurtable.GetHurt(ref WeaponStats weaponStats) {
birdStats.DamageTaken = TakeDamage(ref weaponStats);
birdStats.BirdPosition = transform.position;
birdStats.ModifyForStreak(ScoreSheet.Streaker.GetHitStreak());
birdStats.ModifyForCombo(weaponStats.BirdsHit);
ScoreSheet.Tallier.TallyPoints (ref birdStats);
ScoreSheet.Tallier.TallyBirdThreat(ref birdStats, BirdThreat.Damage);
if (birdStats.Health<=0){
//GameClock.Instance.SlowTime(.025f,.75f);
ScoreSheet.Tallier.TallyKill (ref birdStats);
DieUniquely();
}
}
protected virtual int TakeDamage(ref WeaponStats weaponStats){
int damageDealt = Mathf.Clamp(weaponStats.Damage, 0, birdStats.Health);
birdStats.Health -= damageDealt;
(Instantiate (guts, transform.position, Quaternion.identity) as GameObject).GetComponent<IBleedable>().GenerateGuts(ref birdStats, weaponStats.Velocity);
return damageDealt;
}
protected virtual void DieUniquely(){
Destroy(gameObject);
}
protected void OnDestroy(){
if (ScoreSheet.Instance){
ScoreSheet.Tallier.TallyDeath (ref birdStats);
if (birdStats.Health > 0) {
ScoreSheet.Tallier.TallyBirdThreat(ref birdStats, BirdThreat.Leave);
}
}
StopAllCoroutines();
}
}
For complete source code, visit the GitHub page for Sky:
Development Experience Icons sourced from thenounproject.com and created by:
-
Edward Boatman
-
Jacob Eckert
-
Jon Trillana
-
Michal Beno