top of page

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

bottom of page