[Flappy Bird] Create your first 2 Dimensional Gui Based Game!

šŸŽ® Create your first 2D Roblox Game

The Ultimate Guide

Greetings developers!

Today we will be learning about 2D game development! Specifically on Roblox, 3D games are very prevalent on Roblox, but have you ever wanted to go out of that universe, a step to the left, and develop actual 2 dimensional games? You are at the place!

Note: Roblox focuses specifically on 3D game development, hence there are not many features in Roblox studio itself that could help you make 2 dimensional physics, hence we will be using a few modules! [Sorry for the video qualities being a bit low.]

In this tutorial we will be making a famous 2 dimensional game - FlappyBird

Before we begin, here are some essentials you need to be familiar with before starting the tutorial:

  • Decent amount of scripting knowledge
  • TweenService
  • ModuleScripts
  • Working with Gui Instances
  • UserInputService

Overview

We will be making the famous single player flappy bird game using Gui instances! Weā€™ll be learning about Simple Gui Physics like gravitational pull, collisions etc! So lets get started!

Getting Started

Open Roblox Studio, and open a new Baseplate!

Head into StarterGui and insert a ScreenGui! Name it whatever you want, Iā€™ll be naming it as ā€˜Gameā€™ for this tutorial.
Head into the ScreenGuiā€™s properties and set IgnoreGuiInset to true

Create a new frame inside ā€˜Gameā€™ and Name it as ā€˜Canvasā€™. Set its size to {1, 0},{1, 0} and set its BackgroundTransparency to 1.
This frame will contain all our assets needed.

Now create a new Frame by the name ā€˜Skyā€™. Set its size to {1, 0},{1, 0} and set its BackgroundColor to any shade of blue you want!

Now we need to create another frame by the name Land. It is going to cover 1/8th of our sky at the bottom. Adjust its size and set its color to any shade of green you want!

Hereā€™s how its supposed to look!

image

You can add other objects such as the Sun as well!

Creating the Bird [Playerā€™s Character]

You can create a frame named ā€œBirdā€ and place all the objects such as the wings, eyes, beek etc inside of the Bird Frame so that everything moves firmly and in the correct place!

image
This is what our bird looks like!
image

You can get the bird module here if you want: https://www.roblox.com/library/7149311081/Bird

Creating Gravitational Pull

The next step would be to create a gravitational pull that is supposed to pull the bird down, just how it is like in flappy bird!

For that we will be using TweenService.

Create a LocalScript and place it inside the Bird. Name it ā€˜Movementā€™.
Lets create a new function that pulls the bird down to the ground!

local bird = script.Parent -- assuming the script is inside the bird
local TweenService = game:GetService("TweenService")

function pull (bird)
	local function callback(state)
		if state == Enum.TweenStatus.Completed then -- if tween completed
			pull(bird) -- pull the bird down again!
		end
	end

	local tweenInfo = TweenInfo.new(
		.5, 
		Enum.EasingStyle.Linear, 
		Enum.EasingDirection.Out, 
		-1, 
		false,
		0 
	)

	local rotate = TweenService:Create(bird, tweenInfo, {Rotation = 30})
	rotate:Play() -- rotating our bird

	local down = bird:TweenPosition(
		UDim2.new(bird.Position.X.Scale,bird.Position.X.Offset,bird.Position.Y.Scale + 0.1, 0),           
		Enum.EasingDirection.Out, 
		Enum.EasingStyle.Linear,   
		.2,                       
		true,                    
		callback        
	) -- pulling our bird down using tweens
end

pull(bird) -- pulling our bird down!

This creates our gravitational pull!

Lets test it out!


And we have our gravity working! Now lets make other movement controls like making the bird fly and not just fall endlessly!

Making Player Controls and Movement

The next step is to create flying physics! So lets get started.

For this we will be using UserInputService!
Head into the LocalScript named ā€˜Movementā€™

We create a new function named fly()

local TweenService = game:GetService("TweenService")

function fly()
	local tweenInfo = TweenInfo.new(
		.5, 
		Enum.EasingStyle.Linear, 
		Enum.EasingDirection.Out, 
		-1, 
		false,
		0 
	)

	local rotate = TweenService:Create(bird, tweenInfo, {Rotation = -15}) --
	rotate:Play() -- rotating our bird

	bird.Rotation = -15

	local up = bird:TweenPosition(			
		UDim2.new(bird.Position.X.Scale,bird.Position.X.Offset,bird.Position.Y.Scale - 0.2, 0),           
		Enum.EasingDirection.Out, 
		Enum.EasingStyle.Elastic,   
		1.9,                       
		true      
	) -- making our bird fly upwards
end

Next we use UserInputService to run the above function when the Space key is pressed on your keyboard!

In the same script:

local uis = game:GetService("UserInputService")

uis.InputBegan:Connect(function(input, gameprocessed)
	if input.KeyCode == Enum.KeyCode.Space then -- if space is pressed
		fly() -- make the bird fly
	end
end)

uis.InputEnded:Connect(function(input, gameprocessed)
	if input.KeyCode == Enum.KeyCode.Space then -- if the bird is not flying anymore
		Gravity.pull(bird) -- we pull the bird down
	end
end)

Woah! We have our movement done! Lets test it out! [Ignore the plugin errors in the output screen lol.]
fly ā€ Made with Clipchamp

But you will notice, we can endlessly fly up and endlessly get pulled downwards. So lets change that! We will make a few assets such as ā€œPlay Againā€, collisions etc!

Making gui collisions and a menu

Lets make a ā€œPlay Againā€ menu!

This menu will be shown on the screen when a player touches the ground, touches an obstacle (we will make the obstacles later in the tutorial) or goes to far above in the sky!

Here is my menu! You can make your menu however you want upto your desire.

image

Now set Visible to false in the properties of the menu!

Lets script collisions!

For making collisions we will be using a Module made by me: GuiCollisionService

After getting the module, place it in ReplicatedStorage.
Now lets use GuiCollisionService!

Head into the Movement localscript:

local GuiCollisionService = require(game.ReplicatedStorage.GuiCollisionService)
local group = GuiCollisionService.createCollisionGroup() -- creating a group for our colliders

group:addHitter(bird) -- bird will be the hitter
group:addCollider(script.Parent.Parent.Land) -- land will be a collider

local connection;

connection = group:getHitter(1).CollidersTouched.Event:Connect(function(hits) -- checking if the bird collides
	if hits -- if the colliders were hit
		script.Parent.Parent.PlayAgain.Visible = true -- displaying the menu
		connection:Disconnect() -- disconnect our event
        script.Disabled = true -- disabling our script to disable movement and tweening
	end	
end)

Now that this is done, lets actually make the menu functional!

Insert a localscript inside the Play Button present in the Menu.

image

Now lets code the MouseButton1Click function!

script.Parent.MouseButton1Click:Connect(function()
	local bird = script.Parent.Parent.Parent.Bird -- our bird
	bird.Position = UDim2.new(0.133, 0,0.276, 0) -- setting our birds position to the default start position
	script.Parent.Parent.Visible = false -- making the menu disappear
	bird.Movement.Disabled = false -- enabling movement script!
end)

Lets test it out!

In order to create a sky border. Place an invisible Frame, just the same as Land, and place it right above the screen. Add it as a collider using

group:addCollider(theskyframe)

And just like that, you have a sky border!

Now we are done with player movement!

Disabling Core Guis

Now we disable Core Guis to give the game more space since its a Single Player game!

Place a localscript inside StarterPlayer > StarterPlayerScripts and insrt the following code inside it!

game:GetService('StarterGui'):SetCoreGuiEnabled("PlayerList", false) -- disabling player list
game:GetService('StarterGui'):SetCoreGuiEnabled(Enum.CoreGuiType.All, false) -- disabling any other core gui
game:GetService('StarterGui'):SetCore("TopbarEnabled", false) -- disabling topbar

Making an scripting Obstacles!

This is the last part of the tutorial! Making Obstacles!
In flappy bird, the obstacles look like this:

To make your job easier, I made the obstacles for you in advance!

Get the obstacles here: https://www.roblox.com/library/7149540475/Obstacles

Place the Obstacles folder inside ReplicatedStorage!

image

Now, lets code the movement of the obstacles from right to left!

Again, we will be using TweenService!
Place a localscript inside ā€˜Canvasā€™:

local TweenService = game:GetService("TweenService")

while true do
	wait(2)

	local stages = game.ReplicatedStorage.Obstacles
	local randomStage = stages["Obstacle"..math.random(1, #stages:GetChildren())]

	local parts = randomStage:GetChildren()

	for _, st in ipairs(parts) do

		local part = st:Clone()

		part.ZIndex = 1
		part.Position = UDim2.new(1.1, 0, part.Position.Y.Scale, 0)
		part.Parent = script.Parent

		local function callback(state)
			if state == Enum.TweenStatus.Completed then
				part:Destroy()
			end
		end

		local move = part:TweenPosition(
			UDim2.new(-0.1, part.Position.X.Offset, part.Position.Y.Scale, 0),           
			Enum.EasingDirection.Out, 
			Enum.EasingStyle.Linear,   
			10,                       
			true,                    
			callback
		)
	end
end

Now we will make some edits to Movement and PlayAgain Menu.

Head into Movement script. We detect when obstacles are added, and after they are added, we declare them as colliders

script.Parent.Parent.ChildAdded:Connect(function(instance)
	if instance.Name == "Up" or instance.Name == "Down" then
		group:addCollider(instance) -- adding the obstacle to our colliders
	end
end)

Then we update our Collider event to -

connection = group:getHitter(1).CollidersTouched.Event:Connect(function(hits) -- checking if the bird collides
	if hits then -- if the collider is land
		print("collided!") -- the bird died!
		script.Parent.Parent.PlayAgain.Visible = true -- displaying the menu
		script.Parent.Parent.Obstacles.Disabled = true -- disable obstacle spawning
		connection:Disconnect() -- disconnect our event
		script.Disabled = true -- disabling our script to disable movement and tweening
	end	
end)

and then we update our PlayAgain script to:

script.Parent.MouseButton1Click:Connect(function()
	local bird = script.Parent.Parent.Parent.Bird -- our bird
	bird.Position = UDim2.new(0.133, 0,0.276, 0) -- setting our birds position to the default start position
	for _, obj in ipairs(script.Parent.Parent.Parent:GetChildren()) do
		if obj.Name == "Up" or obj.Name == "Down" then
			obj:Remove() -- remove obstacles
		end
	end
	script.Parent.Parent.Parent.Obstacles.Disabled = false -- enabling obstacle spawning
	script.Parent.Parent.Visible = false -- making the menu disappear
	bird.Movement.Disabled = false -- enabling movement script!
end)

And we are done! Lets test it out!

Here we go! You can add sound effects to it as well!
Now that isnā€™t the cleanest obstacle collision detection is it? Lets keep that for Part 2 of the tutorial!!! Because I donā€™t wanna take more of your time!

PART 2 OF THIS TUTORIAL WILL CONTAINT:

  • A main menu
  • Better and efficient collision detection with guis u cant go through?!?!?!
  • Adding Sound effects
  • Adding mobile support
Want a part 2 for this Tutorial?
  • Yes
  • No

0 voters

If you would like to play the game (with a few more edits):

If you have any doubts, would like to give feedback, reply down below!

I hope you enjoyed the tutorial, cya in part 2!

Check out other projects of mine: jaipack17 Ā· GitHub

Thanks! :cookie:

All Scripts

Movement

local bird = script.Parent -- assuming the script is inside the bird
local TweenService = game:GetService("TweenService")
local uis = game:GetService("UserInputService")
local GuiCollisionService = require(game.ReplicatedStorage.GuiCollisionService)
local group = GuiCollisionService.createCollisionGroup() -- creating a group for our colliders

group:addHitter(bird) -- bird will be the hitter
group:addCollider(script.Parent.Parent.Land) -- land will be a collider

script.Parent.Parent.ChildAdded:Connect(function(instance)
	if instance.Name == "Up" or instance.Name == "Down" then
		group:addCollider(instance) -- adding the obstacle to our colliders
	end
end)

local connection;

connection = group:getHitter(1).CollidersTouched.Event:Connect(function(hits) -- checking if the bird collides
	if hits then -- if the collider is land
		print("collided!") -- the bird died!
		script.Parent.Parent.PlayAgain.Visible = true -- displaying the menu
		script.Parent.Parent.Obstacles.Disabled = true
		connection:Disconnect() -- disconnect our event
		script.Disabled = true -- disabling our script to disable movement and tweening
	end	
end)

function pull (bird)
	local function callback(state)
		if state == Enum.TweenStatus.Completed then
			pull(bird)
		end
	end

	local tweenInfo = TweenInfo.new(
		.5, 
		Enum.EasingStyle.Linear, 
		Enum.EasingDirection.Out, 
		-1, 
		false,
		0 
	)

	local rotate = TweenService:Create(bird, tweenInfo, {Rotation = 30})
	rotate:Play()

	local down = bird:TweenPosition(
		UDim2.new(bird.Position.X.Scale,bird.Position.X.Offset,bird.Position.Y.Scale + 0.1, 0),           
		Enum.EasingDirection.Out, 
		Enum.EasingStyle.Linear,   
		.2,                       
		true,                    
		callback        
	)
end

pull(bird) -- pulling our bird down!

function fly()
	local tweenInfo = TweenInfo.new(
		.5, 
		Enum.EasingStyle.Linear, 
		Enum.EasingDirection.Out, 
		-1, 
		false,
		0 
	)

	local rotate = TweenService:Create(bird, tweenInfo, {Rotation = -15}) --
	rotate:Play()

	bird.Rotation = -15

	local up = bird:TweenPosition(			
		UDim2.new(bird.Position.X.Scale,bird.Position.X.Offset,bird.Position.Y.Scale - 0.2, 0),           
		Enum.EasingDirection.Out, 
		Enum.EasingStyle.Elastic,   
		1.9,                       
		true      
	)
end

uis.InputBegan:Connect(function(input, gameprocessed)
	if input.KeyCode == Enum.KeyCode.Space then		
		fly()
	end
end)

uis.InputEnded:Connect(function(input, gameprocessed)
	if input.KeyCode == Enum.KeyCode.Space then		
		pull(bird)
	end
end)

PlayAgain LocalScript

script.Parent.MouseButton1Click:Connect(function()
	local bird = script.Parent.Parent.Parent.Bird -- our bird
	bird.Position = UDim2.new(0.133, 0,0.276, 0) -- setting our birds position to the default start position
	for _, obj in ipairs(script.Parent.Parent.Parent:GetChildren()) do
		if obj.Name == "Up" or obj.Name == "Down" then
			obj:Remove()
		end
	end
	script.Parent.Parent.Parent.Obstacles.Disabled = false -- enabling obstacle spawning
	script.Parent.Parent.Visible = false -- making the menu disappear
	bird.Movement.Disabled = false -- enabling movement script!
end)

Disable Core Gui Script

game:GetService('StarterGui'):SetCoreGuiEnabled("PlayerList", false) -- disabling player list
game:GetService('StarterGui'):SetCoreGuiEnabled(Enum.CoreGuiType.All, false) -- disabling any other core gui
game:GetService('StarterGui'):SetCore("TopbarEnabled", false) -- disabling topbar

Obstacles

local TweenService = game:GetService("TweenService")

while true do
	wait(2)

	local stages = game.ReplicatedStorage.Obstacles
	local randomStage = stages["Obstacle"..math.random(1, #stages:GetChildren())]

	local parts = randomStage:GetChildren()

	for _, st in ipairs(parts) do

		local part = st:Clone()

		part.ZIndex = 1
		part.Position = UDim2.new(1.1, 0, part.Position.Y.Scale, 0)
		part.Parent = script.Parent

		local function callback(state)
			if state == Enum.TweenStatus.Completed then
				part:Destroy()
			end
		end

		local move = part:TweenPosition(
			UDim2.new(-0.1, part.Position.X.Offset, part.Position.Y.Scale, 0),           
			Enum.EasingDirection.Out, 
			Enum.EasingStyle.Linear,   
			10,                       
			true,                    
			callback
		)
	end
end

44 Likes

If you have any questions, doubts, or would like to give feedback, you can reply down below!

2 Likes

Found a bug, if you hold Spacebar it will keep the bird and prevent it from falling.

also when I press Spacebar it doesnā€™t seem to respond as fast as it should and somehow I kept falling for a split second and hit a pipe

4 Likes

I am on mobile right now, so donā€™t have access to studio right now. Iā€™ll fix it and add it to the tutorial as soon as possible, thanks for finding the bug!

2 Likes

Is mobile support going to be added?

Im getting this Error.

ReplicatedStorage.GuiCollisionService:244: argument must be a table

It is pretty easy to add. Make a gui that is an up arrow or something and in the movement script do

Yourbuttonhere.MouseButton1Click:Connect(function()
fly()
end)

When Is Part 2 Gonna Release? :skull:

4 Likes

I donā€™t see any module script called ā€œGuiCollisionServiceā€

I donā€™t like this tutorial. Your GuiCollisionService isnā€™t working. I am dissapointed.

There is an error in your module script. You canā€™t make something that doesnt work and tell everyone to use it for ur tutorial. fix it.

2 Likes