š® Create your first 2D Roblox Game
The Ultimate GuideGreetings 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!
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!
This is what our bird looks like!
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.]
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.
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.
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!
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
- 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!
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