A Flappy Bird style game in Unity (source code included)

This tutorial is meant for educational purposes only to showcase how to build certain types of games. Please respect the copyrights/trademarks of others!

Update: If you’re trying the downloaded game in Unity 5, uncheck the “Apply Root Motion” on the Animator component on the flappy GameObject and everything should work like a charm.

Unless you’ve been living in a cave or visiting outer space, you have certainly played Flappy Bird, one of the most successful games for mobile devices. Hence, I decided to give it a shot and recreate it using Unity. Needless to mention that all graphics and sounds found in the tutorial are used only for educational purposes and do not intend to harm any intellectual property at all circumstances. The sprite sheet used was found here http://www.spriters-resource.com/mobile/flappybird/sheet/59894/  whereas the sounds were found here: https://www.sounds-resource.com/mobile/flappybird/sound/5309/

Game code was written in Visual Studio (check the free Community edition here). For debugging purposes, don’t forget to check the Visual Studio tools for Unity here.

So, let’s start  with the small tutorial! This game’s development has many aspects, we’ll attempt to touch all of them one by one. First of all, we used Unity’s spritesheet editor to “slice” the spritesheet and get references to specific sprites

image_2F509071.png

Then, we created a small animation to simulate the flight course of the bird (this can be easily accomplished if you drag-drop the relevant sprites into the scene).

image_6E47326E

Now, let’s take a more detailed look at our scene. We have various objects made children of our perspective camera and the Flappy bird.

image_05090AA9image_0D2036FE

The Background is a Sprite GameObject which has a RandomBackgroundScript attached. We provide the two backgrounds from our spritesheet as parameters.

image_0A7A0BFE.png

public class RandomBackgroundScript : MonoBehaviour {

	// Use this for initialization
	void Start () {
        (GetComponent() as SpriteRenderer).sprite = Backgrounds[Random.Range(0, Backgrounds.Length)];
	}

	// Update is called once per frame
	void Update () {

	}

    public Sprite[] Backgrounds;
}

The Ceiling (not visible during the game!) is a simple quad equipped with a BoxCollider2D component that basically prevents our bird from leaving our gameplay screen.

The Score is a GameObject that holds a reference to the ScoreManagerScript which contains score-handling code. The Script is provided the sprites for our numbers. It starts with a single sprite enabled (Units) and two other Sprites disabled, specifically Tens and Hundreds, which are activated when the player’s score is larger than 10 and 100, respectively. It also stores reference to our sprite digits.

image_37FDBC72.png

In order to save performance, the sprite/score calculation is achieved if and only if the score has changed from the previous update call. Then, the respective sprites are enabled and the relevant score is shown.

	void Update () {

        if (previousScore != Score) //save perf from non needed calculations
        {
            if(Score = 10 && Score = 100)
            {
                (Hundreds.gameObject as GameObject).SetActive(true);
                Hundreds.sprite = numberSprites[Score / 100];
                int rest = Score % 100;
                Tens.sprite = numberSprites[rest / 10];
                Units.sprite = numberSprites[rest % 10];
            }
        }

	}

The IntroGUI GameObject shows the sprites of the start screen whereas the DeathGUI (which is disabled at the start of the game) shows the sprites of the death screen.

image_1D53B10Aimage_74B54502

Let’s check the PipeColunnPrefab, which is identical to the PipeColumnPrefab2 with some sprite differences. The prefab is made by two sprites that are kinematic rigidbodies and a triggered BoxCollider2D. Both are tagged as “Pipe”. Plus, between the two pipes there is an empy GameObject with another BoxCollider2D, which is tagged as “pipeblank”. One can easily make out that when the bird hits the pipes, the player will lose whereas when it hits the “pipeblank” the score will increase by 1.

image_2CF64CCC.png

The Camera has a CameraFollow script attached that gets a reference to the Flappy bird transform. The Update script allows the Camera to update its position based on the bird’s position, slightly moved to the left. It’s customary for infinite runner games to have the camera not in the center but a bit left, in order to have more space on the right so that the player encounters incoming obstacles/blanks/whatever early enough (in our case, the pipes).

The Floor GameObject holds 2 sprites of the floor, has a BoxCollider 2D and a FloorMove script and is tagged as “Floor”. When the first floor moves too much to the left, the script makes the object move to the right, to simulate that the bird is moving to the right. You may notice while playing that the “fix” of the floor movement is somewhat clumsy. One could work that one out with the –3.9f value modification. For a better solution, I attempted to utilize mainTextureOffset for smoother scrolling, but it is not supported by the SpriteRenderer. This would require to have the floor sprite in another file, import it in Unity as a Texture and set the mainTextureOffset. This can be done but I felt that it was too much for the purposes of the tutorial. If you have a better solution, sound off in the comments section!

image_0F17C8CB

    void Update()
    {
        if (transform.localPosition.x < -3.9f)
        {
            transform.localPosition = new Vector3(0, transform.localPosition.y, transform.localPosition.z);
        }
        transform.Translate(-Time.deltaTime, 0, 0);
    }

The SpawnerObject GameObject has the SpawnerScript which in turn has the duty of spawning the pipe prefabs. We provide the two prefabs as parameters plus a minimum and maximum time for the spawn. Let’s check the code!

image_6C56910D

First, we randomly select the prefab that we’ll continuously instantiate throughout our game. Then, we use the Spawn method which checks for the game state (more on that later). If we are in the playing state, we Instantiate a new pipe, on a random point at the y axis (to simulate the pipes’ height being differently set) and then we call the Spawn method at a later random time (via use of the Invoke method). Easy enough?

public class SpawnerScript : MonoBehaviour
{

    // Use this for initialization
    void Start()
    {
        SpawnObject = SpawnObjects[Random.Range(0, SpawnObjects.Length)];
        Spawn();
    }

    void Spawn()
    {
        if (GameStateManager.GameState == GameState.Playing)
        {
            //random y position
            float y = Random.Range(-0.5f, 1f);
            GameObject go = Instantiate(SpawnObject, this.transform.position + new Vector3(0, y, 0), Quaternion.identity) as GameObject;
        }
        Invoke("Spawn", Random.Range(timeMin, timeMax));
    }

    private GameObject SpawnObject;
    public GameObject[] SpawnObjects;

    public float timeMin = 0.7f;
    public float timeMax = 2f;
}

The PipeDestroyer GameObject does the job of destroying (and thus, saving performance and memory) the pipes that our bird surpasses. The script code destroys the parent of the Pipe or Pipeblank, i.e. the entire prefab.

    class PipeDestroyerScript : MonoBehaviour
    {
        void OnTriggerEnter2D(Collider2D col)
        {
            if (col.tag == "Pipe" || col.tag == "Pipeblank")
                Destroy(col.gameObject.transform.parent.gameObject); //free up some memory
        }
    }

Before we dive in to the Flappy script, let’s take a look at the self-documented code of the GameState class

public enum GameState
{
    Intro,
    Playing,
    Dead
}

public static class GameStateManager
{
    public static GameState GameState { get; set; }

    static GameStateManager ()
    {
        GameState = GameState.Intro;
    }

}

The Flappy Bird is a Rigidbody2D, has a pretty basic Circle Collider and passes some parameters to the Flappy Script.

image_225DA0CC.png

Onto the biggest script of our Game, the FlappyScript!

In order to move our Bird to the right, we use this method

    void MoveBirdOnXAxis()
    {
        transform.position += new Vector3(Time.deltaTime * XSpeed, 0, 0);
    }

On the released game, one thing to notice is that during gameplay, the bird begins to look downwards as it falls and looks up when jumping. Consequently, the bird has two states, one going up and one going down.

    FlappyYAxisTravelState flappyYAxisTravelState;

    enum FlappyYAxisTravelState
    {
        GoingUp, GoingDown
    }

We use the below method to make sure that the Flappy bird always has the proper rotation on the z axis.

    private void FixFlappyRotation()
    {
        if (GetComponent().velocity.y > 0) flappyYAxisTravelState = FlappyYAxisTravelState.GoingUp;
        else flappyYAxisTravelState = FlappyYAxisTravelState.GoingDown;

        float degreesToAdd = 0;

        switch (flappyYAxisTravelState)
        {
            case FlappyYAxisTravelState.GoingUp:
                degreesToAdd = 6 * RotateUpSpeed;
                break;
            case FlappyYAxisTravelState.GoingDown:
                degreesToAdd = -3 * RotateDownSpeed;
                break;
            default:
                break;
        }
        //solution with negative eulerAngles found here: http://answers.unity3d.com/questions/445191/negative-eular-angles.html

        //clamp the values so that -90<rotation<45 *always*
        birdRotation = new Vector3(0, 0, Mathf.Clamp(birdRotation.z + degreesToAdd, -90, 45));
        transform.eulerAngles = birdRotation;
    }

[Self note: this thread might contain a better solution: http://forum.unity3d.com/threads/transform-lookat-or-quaternion-lookrotation-on-1-axis-only.36377/]

While on the playing state, we give a boost on the bird each time the user clicks on taps the screen.

    void BoostOnYAxis()
    {
        GetComponent().velocity = new Vector2(0, VelocityPerJump);
        GetComponent().PlayOneShot(FlyAudioClip);
    }

We use this method to get if the user touched or clicked, FYI we could also use Input.GetButtonDown(“Fire1”) that works for both touch and mouse.

    bool WasTouchedOrClicked()
    {
        if (Input.GetButtonUp("Jump") || Input.GetMouseButtonDown(0) ||
            (Input.touchCount > 0 && Input.touches[0].phase == TouchPhase.Ended))
            return true;
        else
            return false;
    }

On the FixedUpdate method, we give the bird a bit of a boost on the Y axis each time the bird starts to drop(to simulate up and down movement). This is accomplished in this way (even though as I’m writing this I’m quite sure that a Sin position transformation could work).

    void FixedUpdate()
    {
        //just jump up and down on intro screen
        if (GameStateManager.GameState == GameState.Intro)
        {
            if (GetComponent().velocity.y < -1) //when the speed drops, give a boost
                GetComponent().AddForce(new Vector2(0, GetComponent().mass * 5500 * Time.deltaTime)); //lots of play and stop
                                                        //and play and stop etc to find this value, feel free to modify
        }
        else if (GameStateManager.GameState == GameState.Playing || GameStateManager.GameState == GameState.Dead)
        {
            FixFlappyRotation();
        }
    }

Floor is a non-trigger collider so we use the OnCollision2D method that sets the game as over

    void OnCollisionEnter2D(Collision2D col)
    {
        if (GameStateManager.GameState == GameState.Playing)
        {
            if (col.gameObject.tag == "Floor")
            {
                FlappyDies();
            }
        }
    }

whereas our pipes prefab is made of trigger colliders, thus we use the OnTriggerEnter2D method

    void OnTriggerEnter2D(Collider2D col)
    {
        if (GameStateManager.GameState == GameState.Playing)
        {
            if (col.gameObject.tag == "Pipeblank") //pipeblank is an empty gameobject with a collider between the two pipes
            {
                GetComponent().PlayOneShot(ScoredAudioClip);
                ScoreManagerScript.Score++;
            }
            else if (col.gameObject.tag == "Pipe")
            {
                FlappyDies();
            }
        }
    }

The Flappy dies method sets the proper game state, activates the DeathGUI and plays the death sound

    void FlappyDies()
    {
        GameStateManager.GameState = GameState.Dead;
        DeathGUI.SetActive(true);
        GetComponent().PlayOneShot(DeathAudioClip);
    }

Last but not least, we’ll see the Update method!

  • The Escape key is bound to the Back Windows Phone key, so we must handle it. We could (and should!) display a message here “Do you want to exit the game” and pause it, for a better user experience. We do not want the user to press the back key by mistake when his score is huge, right?
  • On the intro state, we just move the bird. On Tap we give the bird a boost, change the state, deactivate the introGUI and reset the score
  • On the playing state, we move the bird, and we boost it on the y axis
  • On the dead state, we do a Physics.OverlapPoint to check if the user has pressed on the restart button on the DeathGUI. If so, we reset the game state and reload the scene, in order for the game to begin again.
    void Update()
    {
        //handle back key in Windows Phone
        if (Input.GetKeyDown(KeyCode.Escape))
            Application.Quit();

        if (GameStateManager.GameState == GameState.Intro)
        {
            MoveBirdOnXAxis();
            if (WasTouchedOrClicked())
            {
                BoostOnYAxis();
                GameStateManager.GameState = GameState.Playing;
                IntroGUI.SetActive(false);
                ScoreManagerScript.Score = 0;
            }
        }

        else if (GameStateManager.GameState == GameState.Playing)
        {
            MoveBirdOnXAxis();
            if (WasTouchedOrClicked())
            {
                BoostOnYAxis();
            }

        }

        else if (GameStateManager.GameState == GameState.Dead)
        {
            Vector2 contactPoint = Vector2.zero;

            if (Input.touchCount > 0)
                contactPoint = Input.touches[0].position;
            if (Input.GetMouseButtonDown(0))
                contactPoint = Input.mousePosition;

            //check if user wants to restart the game
            if (restartButtonGameCollider == Physics2D.OverlapPoint
                (Camera.main.ScreenToWorldPoint(contactPoint)))
            {
                GameStateManager.GameState = GameState.Intro;
                Application.LoadLevel(Application.loadedLevelName);
            }
        }

    }

Thanks for reading! I promised the source code, so here it is! Check the project on GitHub https://github.com/dgkanatsios/FlappyBirdStyleGame

 

24 thoughts on “A Flappy Bird style game in Unity (source code included)

  1. I did uncheck Apply Root Motion and my bird does the same thing :(

    I downloaded this to learn Unity. So far it’s been amazing. But I’m definitely still learning.

    I’m using Unity 5.35 so that could explain why this didn’t work for me. Any help would be appreciated.

    Funny thing is that the game is a lot easier when you can’t flap the flappy bird!

    Like

  2. Hi, can you tell me how to change the bird sprite just say if it hit obstacle for second to dead bird sprite and then to game over scene? I mean how do i write the script for that?

    Like

    • Hi, not sure I understand your question. To change the bird sprite, you need to get the renderer object, cast it as SpriteRenderer and change its sprite property. Take a look at how I change the background, in RandomBackgroundScript

      Like

  3. Sir dynamics (gravity, thrust, and speed) of the bird is very strange kindly can you help me to adjust the dynamics of the bird , give me exact values of the component so it become normal and smae like original flappy bird

    Like

  4. I saw your apps not written in c# and i’m just a student who doesnt have basic programming. How do i write into c# from your script? Especially that script to control the bird?

    Like

  5. Sorry, stupid question. Can i have multiple language in my apps? Just say in folder script i have some script written in C#, and some script in C++ and the other is jacascript? Can it run together? I never try before but i want to answer for my curiousity.

    Like

  6. Hi sir, i have a question but it is not related to flappy bird. Im using this code as stopwatch into my apps.

    function OnGUI() { GUILayout.Label(System.TimeSpan.FromSeconds(Time.time).ToString()); }

    It show no problem as hours:minutes:second:millisecond ex:12:31:22’01 but i really dont have idea how do i show only in second:millisecond as text, ex: 76 second and 21 millisecond show as 7621. I dont have any place to ask, so im looking for you to learn something. I am sorry for my english.

    Like

  7. halo sir,
    thankyou to share this post. i’m new in unity and try to learn this, my question is how to properly open your source code flappy bird clone in unity, i have been download unity 4 v.4.6.0f3. i read other blog how to open unity project by open .unity and i find it \KinectFlappyBird-master\Assets\Scenes\mainGame.unity. but when i open it there are error message :
    Assets/Enable Games Development Library/AbleTracking/Replay/ReplayModules/BodyReplayer.cs(51,49): warning CS0219: The variable `joints’ is assigned but its value is never used
    Assets/Enable Games Development Library/AbleTracking/Tracker/TrackerHeader.cs(37,37): error CS0117: `UnityEngine.Application’ does not contain a definition for `version’
    Assets/Enable Games Development Library/AbleTracking/Tracker/TrackerHeader.cs(38,35): error CS0117: `UnityEngine.Application’ does not contain a definition for `productName’

    kindly to help me for this question. thank you sir.

    Like

    • What do you want to accomplish? Score right now is an integer because it counts the number of time the bird passes though between each set of pipes. Score is always a logical implementation of something :) If you would like it to be a float, maybe you could add to it the amount of time that has passed, e.g. float score = 0;

      //on each Update
      score += Time.deltaTime;

      Like

  8. I was wondering how to add some background music. I added a public option to select a file, but I don’t know how to implement that so that it plays during the game. Not quite sure how to code that. Any tips? Thanks!

    Like

Leave a comment