How Create Angry Bird Style Game In Unity.
Unity is a powerful and widely-used game development platform and engine known for its versatility, ease of use, and ability to create stunning interactive experiences across various platforms. With Unity, developers can build games, interactive applications, simulations, and more for a wide range of platforms, including mobile devices, consoles, desktops, and even virtual reality (VR) and augmented reality (AR) devices. Its cross-platform capabilities make it a popular choice for game developers, indie creators, and large studios alike.
"Angry Birds" is a popular mobile game franchise developed by Rovio Entertainment. The game was first released in December 2009 and quickly gained widespread popularity due to its simple yet addictive gameplay and charming characters.
In "Angry Birds" game, players use a slingshot to launch various types of birds at structures populated by pigs. The objective is to eliminate all the pigs on each level using as few birds as possible. Each bird has its unique abilities, such as splitting into multiple birds, accelerating in mid-air, or causing explosions. The structures are designed with physics-based mechanics, and players must strategically aim and time their shots to maximize damage and pig elimination.
Creating an "Angry Birds" style game in Unity involves several steps, including setting up the project, designing the gameplay mechanics, creating the art assets, implementing physics, and coding the mechanics. Here's a general overview of how you can start working on such a game:
To create a project in Unity you will need to have Unity and Unity Hub installed. You can download it from Unity's website. To complete the project, follow this tutorial from start to end.
1. Create Project:
First, we open the Unity Hub and Create a 2D Project.
2. Creating Scenes:
In our scene, there are several components arranged as follows:
3. Program the Game:
With the visual elements in position, the next step involves incorporating the script responsible for managing the animation.
Camera Follow Code:
The camera follow script tracks the motion of the thrown bird, ensuring the camera moves in accordance with it. This script also enforces a constraint on the camera's position, guaranteeing that the camera's X-coordinate remains within the scene's boundaries.
using UnityEngine;
public class CameraFollow : MonoBehaviour
{
public Vector3 StartingPosition { get; private set; }
private const float MinCameraX = 0;
private const float MaxCameraX = 13;
public bool IsFollowing { get; private set; }
public Transform BirdToFollow { get; set; }
void Start()
{
StartingPosition = transform.position;
}
void Update()
{
if (IsFollowing)
{
if (BirdToFollow != null)
{
Vector3 birdPosition = BirdToFollow.transform.position;
float clampedX = Mathf.Clamp(birdPosition.x, MinCameraX, MaxCameraX);
Vector3 newPosition = new Vector3(clampedX, StartingPosition.y, StartingPosition.z);
transform.position = newPosition;
}
else
{
IsFollowing = false;
}
}
}
}
Camera Movement Code:
The script for camera movement empowers users to slide their view across the screen through touch or mouse interactions while the bird remains in its dormant phase, ready for launching. This feature grants users the ability to observe the entire scene, strategizing their shot accordingly. The script consistently monitors mouse coordinates, facilitating seamless adjustments to the camera's positioning. To replicate an agile dragging sensation, a dragSpeed parameter is progressively integrated each frame. Both the horizontal (X) and vertical (Y) coordinates of the camera are confined within the scene's boundaries, preventing users from dragging the camera outside of these limits.
using UnityEngine;
public class CameraMove : MonoBehaviour
{
private float dragSpeed = 0.01f;
private float timeDragStarted;
private Vector3 previousPosition = Vector3.zero;
public SlingShot SlingShot;
void Update()
{
if (SlingShot.slingshotState == SlingshotState.Idle && GameManager.CurrentGameState == GameState.Playing)
{
if (Input.GetMouseButtonDown(0))
{
timeDragStarted = Time.time;
dragSpeed = 0f;
previousPosition = Input.mousePosition;
}
else if (Input.GetMouseButton(0) && Time.time - timeDragStarted > 0.05f)
{
Vector3 input = Input.mousePosition;
float deltaX = (previousPosition.x - input.x) * dragSpeed;
float deltaY = (previousPosition.y - input.y) * dragSpeed;
float newX = Mathf.Clamp(transform.position.x + deltaX, 0, 13.36336f);
float newY = Mathf.Clamp(transform.position.y + deltaY, 0, 2.715f);
transform.position = new Vector3(newX, newY, transform.position.z);
previousPosition = input;
if (dragSpeed < 0.1f)
{
dragSpeed += 0.002f;
}
}
}
}
}
Bird Code:
The Bird script begins by deactivating the bird's trail renderer to prevent its display before launch. Subsequently, it sets the bird as kinematic to avoid gravitational effects prior to takeoff. The collider's radius is augmented, and the initial state of the bird is established.
using UnityEngine;
public class Bird : MonoBehaviour
{
void Start()
{
TrailRenderer trailRenderer = GetComponent<TrailRenderer>();
trailRenderer.enabled = false;
trailRenderer.sortingLayerName = "Foreground";
Rigidbody2D rigidbody = GetComponent<Rigidbody2D>();
rigidbody.isKinematic = true;
CircleCollider2D collider = GetComponent<CircleCollider2D>();
collider.radius = Constants.BirdColliderRadiusBig;
State = BirdState.BeforeThrown;
}
}
Within the FixedUpdate, we evaluate whether the bird's velocity has reached a minimal value and if the bird was launched from the slingshot. This scenario indicates the completion of its descent and signifies its stationary state. Subsequently, we initiate the removal of the bird after a brief delay of 2 seconds.
void FixedUpdate()
{
// Check if the bird has been thrown and its speed is extremely low
if (State == BirdState.Thrown &&
GetComponent<Rigidbody2D>().velocity.sqrMagnitude <= Constants.MinVelocity)
{
// Initiate bird destruction after 2 seconds
StartCoroutine(DestroyAfter(2));
}
}
The DestroyAfter coroutine;
IEnumerator DestroyAfter(float seconds)
{
yield return new WaitForSeconds(seconds);
Destroy(gameObject);
}
Furthermore, an OnThrow method is present (invoked by the bird launched script), serving to alter the bird's state. In this process, the bird transitions to a non-kinematic state, its collider radius is reduced, and the trail renderer is enabled.
public void OnThrow()
{
GetComponent<AudioSource>().Play();
GetComponent<TrailRenderer>().enabled = true;
GetComponent<Rigidbody2D>().isKinematic = false;
GetComponent<CircleCollider2D>().radius = Constants.BirdColliderRadiusNormal;
State = BirdState.Thrown;
}
Brick Code:
The brick script is responsible for monitoring collisions. It features a "health" variable that diminishes based on the velocity of the colliding object. Should the health value fall beneath zero, the brick is subsequently eliminated.
using UnityEngine;
public class Brick : MonoBehaviour
{
public float Health = 70f;
void OnCollisionEnter2D(Collision2D collision)
{
Rigidbody2D collisionRigidbody = collision.gameObject.GetComponent<Rigidbody2D>();
if (collisionRigidbody == null)
{
return;
}
float damage = collisionRigidbody.velocity.magnitude * 10;
if (damage >= 10)
{
GetComponent<AudioSource>().Play();
}
Health -= damage;
if (Health <= 0)
{
Destroy(gameObject);
}
}
}
Pig Code:
The Pig script examines collisions, when a bird collides with the pig, the pig is promptly eliminated. In the event of a collision with another object, like another pig or a brick, the script computes the inflicted damage. If the damage remains under a specified threshold, the pig's appearance is altered to display the version with blackened eyes. Similarly, should the health descend below zero, the pig is eradicated.
using UnityEngine;
public class Pig : MonoBehaviour
{
public float Health = 150f;
public Sprite SpriteShownWhenHurt;
private float ChangeSpriteHealth;
void Start()
{
ChangeSpriteHealth = Health - 30f;
}
void OnCollisionEnter2D(Collision2D collision)
{
Rigidbody2D collisionRigidbody = collision.gameObject.GetComponent<Rigidbody2D>();
if (collisionRigidbody == null)
{
return;
}
if (collision.gameObject.CompareTag("Bird"))
{
GetComponent<AudioSource>().Play();
Destroy(gameObject);
}
else
{
float damage = collisionRigidbody.velocity.magnitude * 10;
Health -= damage;
if (damage >= 10)
{
GetComponent<AudioSource>().Play();
}
if (Health < ChangeSpriteHealth)
{
GetComponent<SpriteRenderer>().sprite = SpriteShownWhenHurt;
}
if (Health <= 0)
{
Destroy(gameObject);
}
}
}
}
Destroying Code:
The Destroyer script examines collisions involving trigger interactions (recall that the destroyer possesses a trigger collider). It possesses the capability to eradicate any birds, pigs, or bricks it comes into contact with. One might question the inclusion of "Pig" or "Brick" since only "Birds" will make contact.
However, the rationale behind this approach is to account for potential unforeseen circumstances that could arise when the game is accessed by a vast number of users. By implementing this precaution, the script ensures comprehensive functionality and robustness.
using UnityEngine;
public class Pig : MonoBehaviour
{
public float Health = 150f;
public Sprite SpriteShownWhenHurt;
private float ChangeSpriteHealth;
void Start()
{
ChangeSpriteHealth = Health - 30f;
}
void OnCollisionEnter2D(Collision2D collision)
{
Rigidbody2D collisionRigidbody = collision.gameObject.GetComponent<Rigidbody2D>();
if (collisionRigidbody != null)
{
if (collision.gameObject.CompareTag("Bird"))
{
GetComponent<AudioSource>().Play();
Destroy(gameObject);
}
else
{
float damage = collisionRigidbody.velocity.magnitude * 10;
Health -= damage;
if (damage >= 10)
{
GetComponent<AudioSource>().Play();
}
if (Health < ChangeSpriteHealth)
{
GetComponent<SpriteRenderer>().sprite = SpriteShownWhenHurt;
}
if (Health <= 0)
{
Destroy(gameObject);
}
}
}
}
}
Recommended by LinkedIn
Main Program that manage the game:
In the context of the "Angry Birds" game, the GameManager plays a pivotal role in coordinating and managing various aspects of the game's mechanics and flow. Its responsibilities encompass:
using UnityEngine;
using System.Collections.Generic;
public class GameManager : MonoBehaviour
{
public CameraFollow cameraFollow;
private int currentBirdIndex;
public SlingShot slingshot;
[HideInInspector]
public static GameState CurrentGameState = GameState.Start;
private List<GameObject> Bricks;
private List<GameObject> Birds;
private List<GameObject> Pigs;
void Start()
{
InitializeGame();
slingshot.BirdThrown -= Slingshot_BirdThrown;
slingshot.BirdThrown += Slingshot_BirdThrown;
}
private void InitializeGame()
{
CurrentGameState = GameState.Start;
slingshot.enabled = false;
Bricks = new List<GameObject>(GameObject.FindGameObjectsWithTag("Brick"));
Birds = new List<GameObject>(GameObject.FindGameObjectsWithTag("Bird"));
Pigs = new List<GameObject>(GameObject.FindGameObjectsWithTag("Pig"));
}
private void Slingshot_BirdThrown(object sender, System.EventArgs e)
{
currentBirdIndex++;
if (currentBirdIndex < Birds.Count)
{
cameraFollow.BirdToFollow = Birds[currentBirdIndex].transform;
}
}
}
Within the Update method, the current gamestate enum value is assessed as follows:
void Update()
{
switch (CurrentGameState)
{
case GameState.Start:
if (Input.GetMouseButtonUp(0))
{
AnimateBirdToSlingshot();
}
break;
case GameState.BirdMovingToSlingshot:
// No action needed in this state
break;
case GameState.Playing:
if (slingshot.slingshotState == SlingshotState.BirdFlying &&
(BricksBirdsPigsStoppedMoving() || Time.time - slingshot.TimeSinceThrown > 5f))
{
slingshot.enabled = false;
AnimateCameraToStartPosition();
CurrentGameState = GameState.BirdMovingToSlingshot;
}
break;
case GameState.Won:
case GameState.Lost:
if (Input.GetMouseButtonUp(0))
{
Application.LoadLevel(Application.loadedLevel);
}
break;
default:
break;
}
}
This mechanism ensures that the game's flow and interactions align cohesively with its distinctive states and conditions.
The below function AllPigsDestroyed() is ued to check whether all pigs are destroyed or null
private bool AllPigsDestroyed()
{
return Pigs.All(x => x == null);
}
The AnimateCameraToStartPosition method orchestrates the camera's movement back to its initial position. This action takes place following the bird's launch and the cessation of all in-game movement. Once the camera's relocation is finalized, the game evaluates the current game state. If all pigs have been eliminated, the player secures victory. Alternatively, if victory hasn't been achieved, the game progresses to the next available bird for launching. However, if no more birds remain, this denotes defeat for the player.
private void AnimateCameraToStartPosition()
{
float distanceToStartPosition = Vector2.Distance(Camera.main.transform.position, cameraFollow.StartingPosition);
float duration = distanceToStartPosition / 10f;
if (duration == 0.0f) duration = 0.1f;
Camera.main.transform.positionTo(duration, cameraFollow.StartingPosition)
.setOnCompleteHandler((x) =>
{
cameraFollow.IsFollowing = false;
if (AllPigsDestroyed())
{
CurrentGameState = GameState.Won;
}
else if (currentBirdIndex == Birds.Count - 1)
{
CurrentGameState = GameState.Lost;
}
else
{
slingshot.slingshotState = SlingshotState.Idle;
currentBirdIndex++;
AnimateBirdToSlingshot();
}
});
}
The AnimateBirdToSlingshot function selects the subsequent bird intended for launch and positions it accurately within the slingshot. Upon completion, the slingshot functionality is activated, rendering the bird poised for release.
void AnimateBirdToSlingshot()
{
CurrentGameState = GameState.BirdMovingToSlingshot;
Birds[currentBirdIndex].transform.positionTo(
Vector2.Distance(Birds[currentBirdIndex].transform.position / 10,
slingshot.BirdWaitPosition.transform.position) / 10, // duration
slingshot.BirdWaitPosition.transform.position) // final position
.setOnCompleteHandler((animation) =>
{
animation.complete();
animation.destroy(); // destroy the animation
CurrentGameState = GameState.Playing;
slingshot.enabled = true; // enable the slingshot
slingshot.BirdToThrow = Birds[currentBirdIndex]; // set the current bird for throwing
});
}
The BirdThrown event handler informs the camera follow script that the bird currently in motion should be tracked. This ensures that the camera's movement responds to the bird's position.
private void Slingshot_BirdThrown(object sender, System.EventArgs e)
{
cameraFollow.BirdToFollow = Birds[currentBirdIndex].transform;
cameraFollow.IsFollowing = true;
}
Bird Launching Code:
The slingshot script manages all interactions associated with the bird being attached to the slingshot. It commences by initializing several variables.
public class SlingShot : MonoBehaviour
{
private Vector3 SlingshotMiddleVector;
public SlingshotState slingshotState;
public Transform LeftSlingshotOrigin, RightSlingshotOrigin;
public LineRenderer SlingshotLineRenderer1;
public LineRenderer SlingshotLineRenderer2;
public LineRenderer TrajectoryLineRenderer;
public GameObject BirdToThrow;
public Transform BirdWaitPosition;
public float ThrowSpeed;
public float TimeSinceThrown;
}
In the Start method, it configures the sorting layer for the line renderers (as this isn't currently achievable through the editor). Additionally, it computes the midpoint of the slingshot, which is the point equidistant from the two slingshot parts.
void Start()
{
SlingshotLineRenderer1.sortingLayerName = "Foreground";
SlingshotLineRenderer2.sortingLayerName = "Foreground";
TrajectoryLineRenderer.sortingLayerName = "Foreground";
slingshotState = SlingshotState.Idle;
SlingshotLineRenderer1.SetPosition(0, LeftSlingshotOrigin.position);
SlingshotLineRenderer2.SetPosition(0, RightSlingshotOrigin.position);
SlingshotMiddleVector = new Vector3(
(LeftSlingshotOrigin.position.x + RightSlingshotOrigin.position.x) / 2,
(LeftSlingshotOrigin.position.y + RightSlingshotOrigin.position.y) / 2,
0
);
}
The Update method is quite extensive, thus we'll break it down step by step. In the idle state of the slingshot:
void Update()
{
switch (slingshotState)
{
case SlingshotState.Idle:
InitializeBird();
DisplaySlingshotLineRenderers();
if (Input.GetMouseButtonDown(0))
{
Vector3 tapLocation = Camera.main.ScreenToWorldPoint(Input.mousePosition);
if (BirdToThrow.GetComponent<CircleCollider2D>().OverlapPoint(tapLocation))
{
slingshotState = SlingshotState.UserPulling;
}
}
break;
// Other cases...
}
}
During the "User Pulling" state:
case SlingshotState.UserPulling:
DisplaySlingshotLineRenderers();
if (Input.GetMouseButton(0))
{
Vector3 tapLocation = Camera.main.ScreenToWorldPoint(Input.mousePosition);
tapLocation.z = 0;
if (Vector3.Distance(tapLocation, SlingshotMiddleVector) > 1.5f)
{
Vector3 maxPosition = (tapLocation - SlingshotMiddleVector).normalized * 1.5f + SlingshotMiddleVector;
BirdToThrow.transform.position = maxPosition;
}
else
{
BirdToThrow.transform.position = tapLocation;
}
float distanceToMiddle = Vector3.Distance(SlingshotMiddleVector, BirdToThrow.transform.position);
DisplayTrajectoryLineRenderer2(distanceToMiddle);
}
break;
// Additional cases...
When the user releases the bird, we assess whether the pulling duration is sufficient. If it meets the requirement, the bird is launched. If not, the bird is smoothly animated back to its initial position.
else
{
SetTrajectoryLineRenderersActive(false);
TimeSinceThrown = Time.time;
float distanceToMiddle = Vector3.Distance(SlingshotMiddleVector, BirdToThrow.transform.position);
if (distanceToMiddle > 1)
{
SetSlingshotLineRenderersActive(false);
slingshotState = SlingshotState.BirdFlying;
ThrowBird(distanceToMiddle);
}
else // Not pulled long enough, reinitiate
{
float animationDuration = distanceToMiddle / 10; // Adjusted duration
BirdToThrow.transform.positionTo(animationDuration, BirdWaitPosition.transform.position)
.setOnCompleteHandler((x) =>
{
x.complete();
x.destroy();
InitializeBird();
});
}
}
The ThrowBird function calculates the throw's velocity based on the bird's drag during the pull. This velocity affects the bird's rigidbody property, resulting in the throw. Another approach could involve applying a force. Subsequently, the BirdThrown event is triggered, alerting the game manager about the bird's throw.
private void ThrowBird(float distance)
{
Vector3 velocity = SlingshotMiddleVector - BirdToThrow.transform.position;
BirdToThrow.GetComponent<Bird>().OnThrow();
Rigidbody2D birdRigidbody = BirdToThrow.GetComponent<Rigidbody2D>();
birdRigidbody.velocity = new Vector2(velocity.x, velocity.y) * ThrowSpeed * distance;
BirdThrown?.Invoke(this, EventArgs.Empty);
}
In the DisplaySlingshotLineRenderers function, the correct positioning of the slingshot's "strings" that hold the bird is established. The SetSlingshotLineRenderersActive and SetTrajectoryLineRenderers methods serve the purpose of enabling or disabling the relevant renderers.
void DisplaySlingshotLineRenderers()
{
Vector3 birdPosition = BirdToThrow.transform.position;
SlingshotLineRenderer1.SetPosition(1, birdPosition);
SlingshotLineRenderer2.SetPosition(1, birdPosition);
}
void SetSlingshotLineRenderersActive(bool active)
{
SlingshotLineRenderer1.enabled = active;
SlingshotLineRenderer2.enabled = active;
}
void SetTrajectoryLineRenderersActive(bool active)
{
TrajectoryLineRenderer.enabled = active;
}
The DisplayTrajectoryLineRenderer is used to display the trajectory of the bird when thrown.
void DisplayTrajectoryLineRenderer2(float distance)
{
SetTrajectoryLineRenderesActive(true);
Vector2 slingshotToBird = BirdToThrow.transform.position - SlingshotMiddleVector;
Vector2 initialVelocity = slingshotToBird * ThrowSpeed * distance;
int segmentCount = 15;
float segmentScale = 2;
Vector3[] segments = new Vector3[segmentCount];
// The first line point is wherever the player's cannon, etc. is
segments[0] = BirdToThrow.transform.position;
// Calculate trajectory segments
for (int i = 1; i < segmentCount; i++)
{
float time = i * Time.fixedDeltaTime * 5;
segments[i] = segments[0] + initialVelocity * time + 0.5f * Physics2D.gravity * Mathf.Pow(time, 2);
}
TrajectoryLineRenderer.positionCount = segmentCount;
TrajectoryLineRenderer.SetPositions(segments);
}
I trust you've found this tutorial valuable. Should you have any inquiries or thoughts to share, or perhaps you've ingeniously incorporated additional captivating effects using Unity's 2D techniques, feel free to share them with us in the comments section below. Your feedback and insights are greatly appreciated!
We also provide Services of 2D/3D Game Development, 2D/3D Animations, Video Editing and UI/UX Designing.
If you have questions or suggestions related to game or want something to build from us, Feel free to reach out to us anytime!
📱 Mobile: +971 544 614 238
📧 Email: wahhab_mirza@vectorlabzlimited.com