How to make a roblox game 2D Pixlated (Horror game )

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

Background2

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)
Walk2
Character Walks Right (InputBegan code D)
Walk1
Character Stops Walking Left Idle (InputEnded code A)
Idle1
Character Stops Walking Right Idle (InputEnded code D)
Idle2

**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

Interact

Back Button

  • 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:


Example2

15 Likes