This tutorial is showing you how I made my game pixelated and a horror genre!
Game here: (36) Don’t Look - Roblox
BTW the game is still unfinished, since I have many other 2D pixelated project. But I am making a tutorial on it anyways!
**First we will be making the background for the player spawn.**
Step 1: Draw your pixle art background for your game using a pixle art website or app. (I use Pixleart)
Canvas Information:
- 144x72 / Width x Height
Step 2: Upload your background to Roblox.
Step 3: Open your game and add a new [ Part ] into workspace | and name it Background
Step 4: Add a **[ BillboardGui] ** into the [ Part ]
BillBoardGui Proporties:
Size ---- {50, 0},{20, 0}
Max Distance ---- 100
Step 5: Add a [ ImageLabel ] Into the [ BillboardGui ]
ImageLabel Proporties:
Size ---- {1, 0},{1, 0}
ResembledType ---- Pixlated
ScaleType ---- Stretch
Image ---- {Your background URL ID}
or
My Example Image ---- {rbxassetid://14543747366}
Step 6: Add a new [ Part ] workspace | and name it Border
step 6 will be the black edges that you see in pixle games.
Step7: Add a **[ BillboardGui] ** into the [ Border ] | and move the Border Part 2 spaces behind the Background part.
BillBoardGui Proporties:
Size ---- {120, 0},{50, 0}
Max Distance ---- 100
Don’t forget to duplicate the ImageLabel from the Background Part, and place the ImageLabel into the Border BillboardGui.
Image Label ID ---- {rbxassetid://12781220488}
The ID above is just a black solid color as the Border
Now its time to draw the character movements (Pixleart)
**Canvas Size:**
Width x Height | 30 x 30
Examples of my character in my game:
Character Walks Left (InputBegan code A)
Character Walks Right (InputBegan code D)
Character Stops Walking Left Idle (InputEnded code A)
Character Stops Walking Right Idle (InputEnded code D)
**Now that your all set! it's time to make the player movement for pc.**
Step 1: make a box and resize it to fit the Background
Step 1 is going to act as a forcefield for the player.
Step 2: Add a [ Spawn ] inside of the [ forcefield Box ]
Step 3 will be kind of tricky
Step 3: Push play to test the game. | Go into the StarterPlayerScripts and find [ PlayerModule ] |
Step 4: Stop the Game and Copy and Paste the [ PlayerModule ] into | StarterPlayer > StarterPlayerScripts
Step 5 is changing the PC controls in the PlayerModule
Step 5: PlayerModule > ControlModule > Keyboard | Open the Keyboard Script
Step 6: Copy and paste this script into the Keyboard Script
We only need the player to walk Left and Right
Player Module Script:
--!nonstrict
--[[
Keyboard Character Control - This module handles controlling your avatar from a keyboard
2018 PlayerScripts Update - AllYourBlox
--]]
--[[ Roblox Services ]]--
local UserInputService = game:GetService("UserInputService")
local ContextActionService = game:GetService("ContextActionService")
--[[ Constants ]]--
local ZERO_VECTOR3 = Vector3.new(0,0,0)
--[[ The Module ]]--
local BaseCharacterController = require(script.Parent:WaitForChild("BaseCharacterController"))
local Keyboard = setmetatable({}, BaseCharacterController)
Keyboard.__index = Keyboard
function Keyboard.new(CONTROL_ACTION_PRIORITY)
local self = setmetatable(BaseCharacterController.new() :: any, Keyboard)
self.CONTROL_ACTION_PRIORITY = CONTROL_ACTION_PRIORITY
self.textFocusReleasedConn = nil
self.textFocusGainedConn = nil
self.windowFocusReleasedConn = nil
self.leftValue = 0
self.rightValue = 0
self.jumpEnabled = false
return self
end
function Keyboard:Enable(enable: boolean)
if not UserInputService.KeyboardEnabled then
return false
end
if enable == self.enabled then
-- Module is already in the state being requested. True is returned here since the module will be in the state
-- expected by the code that follows the Enable() call. This makes more sense than returning false to indicate
-- no action was necessary. False indicates failure to be in requested/expected state.
return true
end
self.leftValue = 0
self.rightValue = 0
self.moveVector = ZERO_VECTOR3
self.jumpRequested = false
self:UpdateJump()
if enable then
self:BindContextActions()
self:ConnectFocusEventListeners()
else
self:UnbindContextActions()
self:DisconnectFocusEventListeners()
end
self.enabled = enable
return true
end
function Keyboard:UpdateMovement(inputState)
if inputState == Enum.UserInputState.Cancel then
self.moveVector = ZERO_VECTOR3
else
self.moveVector = Vector3.new(self.leftValue + self.rightValue, 0)
end
end
function Keyboard:UpdateJump()
self.isJumping = self.jumpRequested
end
function Keyboard:BindContextActions()
-- Note: In the previous version of this code, the movement values were not zeroed-out on UserInputState. Cancel, now they are,
-- which fixes them from getting stuck on.
-- We return ContextActionResult.Pass here for legacy reasons.
-- Many games rely on gameProcessedEvent being false on UserInputService.InputBegan for these control actions.
local handleMoveLeft = function(actionName, inputState, inputObject)
self.leftValue = (inputState == Enum.UserInputState.Begin) and -1 or 0
self:UpdateMovement(inputState)
return Enum.ContextActionResult.Pass
end
local handleMoveRight = function(actionName, inputState, inputObject)
self.rightValue = (inputState == Enum.UserInputState.Begin) and 1 or 0
self:UpdateMovement(inputState)
return Enum.ContextActionResult.Pass
end
local handleJumpAction = function(actionName, inputState, inputObject)
self.jumpRequested = self.jumpEnabled and (inputState == Enum.UserInputState.Begin)
self:UpdateJump()
return Enum.ContextActionResult.Pass
end
-- TODO: Revert to KeyCode bindings so that in the future the abstraction layer from actual keys to
-- movement direction is done in Lua
ContextActionService:BindActionAtPriority("moveLeftAction", handleMoveLeft, false,
self.CONTROL_ACTION_PRIORITY, Enum.PlayerActions.CharacterLeft)
ContextActionService:BindActionAtPriority("moveRightAction", handleMoveRight, false,
self.CONTROL_ACTION_PRIORITY, Enum.PlayerActions.CharacterRight)
ContextActionService:BindActionAtPriority("jumpAction", handleJumpAction, false,
self.CONTROL_ACTION_PRIORITY, Enum.PlayerActions.CharacterJump)
end
function Keyboard:UnbindContextActions()
ContextActionService:UnbindAction("moveLeftAction")
ContextActionService:UnbindAction("moveRightAction")
ContextActionService:UnbindAction("jumpAction")
end
function Keyboard:ConnectFocusEventListeners()
local function onFocusReleased()
self.moveVector = ZERO_VECTOR3
self.forwardValue = 0
self.backwardValue = 0
self.leftValue = 0
self.rightValue = 0
self.jumpRequested = false
self:UpdateJump()
end
local function onTextFocusGained(textboxFocused)
self.jumpRequested = false
self:UpdateJump()
end
self.textFocusReleasedConn = UserInputService.TextBoxFocusReleased:Connect(onFocusReleased)
self.textFocusGainedConn = UserInputService.TextBoxFocused:Connect(onTextFocusGained)
self.windowFocusReleasedConn = UserInputService.WindowFocused:Connect(onFocusReleased)
end
function Keyboard:DisconnectFocusEventListeners()
if self.textFocusReleasedConn then
self.textFocusReleasedConn:Disconnect()
self.textFocusReleasedConn = nil
end
if self.textFocusGainedConn then
self.textFocusGainedConn:Disconnect()
self.textFocusGainedConn = nil
end
if self.windowFocusReleasedConn then
self.windowFocusReleasedConn:Disconnect()
self.windowFocusReleasedConn = nil
end
end
return Keyboard
Step 7: Add a rig into the game using [ Rig Builder ] | Name it StarterCharacter
Step 8: Add a part and name it Cube
Part Proporties:
Size ---- {1, 1, 1}
Anchord ---- false
Cancollide ---- false
Step 9: Add a [ Weld ] into the Cube | Part 0 - Cube | Part 1 - HumanoidRootPart
Step 10: Add a [ Attachment ] into the [ Cube ] | Then add a *[ BillboardGui ] into the [ Attachment ] | Next add a [ ImageLabel ] Into the [ BillboardGui ]
BillBoardGui Proporties:
Size ---- {10, 0},{10, 0}
MaxDistance ---- 30
AlwaysInFront ---- true
ImageLabel Proporties:
Size ---- {1, 0},{1, 0}
MaxDistance ---- 30
ResembledType ---- Pixlated
ScaleType ---- Fit
Step 11: Add a Local Script into StarterCharacterScripts | Name the Script PlayerControls
Step 12: Copy and Paste this script into the PlayerContols Script
This will make the starter character image look like the character is moving Left and Right
PlayerControls Script:
local UserInputService = game:GetService("UserInputService")
local player = game.Players.LocalPlayer
local humanoid = player.Character:FindFirstChildOfClass("Humanoid")
UserInputService.InputBegan:Connect(function(input, processed)
if input.KeyCode == Enum.KeyCode.A then
local Cube = player.Character:WaitForChild("Cube")
Cube.Attachment.BillboardGui.ImageLabel.Image = "Your Character Walk Left Image URL"
end
if input.KeyCode == Enum.KeyCode.D then
local Cube = player.Character:WaitForChild("Cube")
Cube.Attachment.BillboardGui.ImageLabel.Image = "Your Character Walk Right Image URL"
end
end)
UserInputService.InputEnded:Connect(function(input, processed)
if input.KeyCode == Enum.KeyCode.A then
local Cube = player.Character:WaitForChild("Cube")
Cube.Attachment.BillboardGui.ImageLabel.Image = "Your Character Stops Walking Left Idle Image URL"
end
if input.KeyCode == Enum.KeyCode.D then
local Cube = player.Character:WaitForChild("Cube")
Cube.Attachment.BillboardGui.ImageLabel.Image = "Your Character Stops Walking Right Idle Image URL"
end
end)
Time to make the CameraHandler | This will make the 2D look in your game
Step 1: Add a Local Script in StarterCharacterScripts | Name the Script CameraHandler
Step 2: Copy and Paste this into the CameraHandlerScript
CameraHandler Script:
-- Services
local rS = game:GetService("RunService")
local tS = game:GetService("TweenService")
-- Variables
local camera = game.Workspace.CurrentCamera
local root = script.Parent:WaitForChild("HumanoidRootPart")
local cameraPart = Instance.new("Part")
local newPos
-- Script
cameraPart.Anchored = true
cameraPart.CanTouch = false
cameraPart.CanCollide = false
cameraPart.Parent = game.Workspace
cameraPart.CFrame = CFrame.lookAt(root.CFrame.Position + Vector3.new(0, 90, 90), root.Position)
cameraPart.Orientation = Vector3.new(0,180,0)
camera.CameraType = Enum.CameraType.Scriptable
rS.PostSimulation:Connect(function(dt)
cameraPart.Position = cameraPart.Position:Lerp(root.Position + Vector3.new(0, 0, -17), dt * 5)-- You can adjust the speed and "smoothness" by increasing or decreasing the "0.8", just make sure its a value between 0 and 1
camera.CFrame = cameraPart.CFrame -- camera.CFrame = cameraPart.CFrame -- Set the camera's CFrame to the part's Cframe AFTER you have moved the part, so the camera gets moved correctly
end)
You can add this part if you want) a Core player gui disabler
This will make the following roblox items not show on screen| Backpack | PlayerList | Health | Chat | EmotesMenu
Add a script into StarterCharacterScripts | Name it Disable | Copy and paste this into the Disable Script.
wait()
game.StarterGui:SetCoreGuiEnabled(Enum.CoreGuiType.PlayerList, false) --change this to false if you want to delete Player list the player Names That appear top right
game:GetService("StarterGui"):SetCoreGuiEnabled(Enum.CoreGuiType.Backpack, false) --change this to false to disable player backpack(and gear)
game.StarterGui:SetCoreGuiEnabled(Enum.CoreGuiType.Health, false) --change this to false to delete players health bar
game.StarterGui:SetCoreGuiEnabled(Enum.CoreGuiType.Chat, false) --change this to false to delete the chat
game.StarterGui:SetCoreGuiEnabled(Enum.CoreGuiType.EmotesMenu, false) --change this to false to delete Emotes Menu(/e dance,/e point,/e wave etc.)
game.StarterGui:SetCore("ResetButtonCallback",false)
You’re all finished! happy 2D game making, here are some notes
Notes:
-
When drawing backgrounds you should make the background bright first, because you can change the color image in roblox studio when your done. To make it look brighter and darker.
I use the Image Color | 100, 100, 100 | because its the perfect amount of brightness and darkness. -
When Billboards have Always infront enabled, it makes the image bright, and infront of other images always.
-
When trying to make things interactive, use a BillboardGui but put it in StarterGui then Adornee it to a part. That will make the imagebutton interactable.
-
Make sure that you are drawing the objects in the game instead of taking it from other games, so there is no copywrite!
-
To make your character stop turning rotating when moving left and right. Go into the humanoid to disable AutoRotate
-
Even though you are making your game out of BillboardGui’s it still works the same as 3D games, so you can teleport, use tools and sit, and more!
-
Here’s an example of my interactive buttons in my game, they are both 16x16
-
If your images in roblox studio are blurry just change the ResembledType in your Image to Pixlated.
-
Using too many billboard gui’s (Like thousands ) will make your game laggy! if you are making some kind of 2D open world game or just a large 2D game in general, I suggest using surface Gui’s on parts.
Good Luck!
My 2D Horror game: