PcoWSkbVqDnWTu_dm2ix
We use cookies on this site to enhance your user experience

Top Down Action: Camera

Top Down Action: Camera

Jul 30 2018, 2:19 PM PST 5 min

We first need to overwrite the default camera controls. The camera in our game will track the player from above, but at an angle, similar to an isometric camera. To keep things simple, we will keep the angle fixed and not allow the player to rotate it.

To do this, insert a LocalScript into StarterPlayerScripts and name it CameraScript. This script is where we can enter custom code to define our camera.

TDS_Camera_StarterPlayer.png

In order to start coding our camera, it is important to understand how rendering works in Roblox. The camera defines where exactly in the 3D scene we want the player to see, which then determines what renders on the screen. The Roblox engine refreshes this rendering about every 1/60th of a second. Rendering this quickly means the viewer sees a smooth transition between frames. In our game, we want the camera to follow the player. To make sure that this tracking is smooth, we need to make sure the camera’s position is updated every time this render refresh occurs. Fortunately, this is very easy with a function called RunService/BindToRenderStep|BindToRenderStep.

Enter the following code into the LocalScript:

local RunService = game:GetService("RunService")
local function onRenderStep()
	
end

RunService:BindToRenderStep("Camera", Enum.RenderPriority.Camera.Value, onRenderStep)

This code first makes a variable for the game’s RunService|RunService, a service that manages the various timers that the Roblox engine uses. BindToRenderStep allows us to call a function every time the screen refreshes. The first argument to the function is simply a name which we can use later to detach this function. The next argument determines when during the render refresh the function should be called. Since we are dealing with the camera, it is natural to have our code run during the camera update stage (in later functions will will bind functions to other stages of the render step). Lastly, we need to provide the function that will be called.

Now we need to define what our onRenderStep function will do. We want to keep the camera at a fixed distance from the character, so we need to define this distance and then move the camera by that distance in the onRenderStep function:

local RunService = game:GetService("RunService")
local Players = game:GetService("Players")

local OFFSET = Vector3.new(40,40,40)

local player = Players.LocalPlayer
local camera = game.Workspace.CurrentCamera

local function onRenderStep()
	local character = player.Character
	if character then
		local humanoidRootPart = character:FindFirstChild("HumanoidRootPart")
		if humanoidRootPart then
			local playerPosition = humanoidRootPart.Position
			local cameraPosition = playerPosition + OFFSET
			camera.CoordinateFrame = CFrame.new(cameraPosition, playerPosition)
		end
	end
end

RunService:BindToRenderStep("Camera", Enum.RenderPriority.Camera.Value, onRenderStep)

First we declare a variable for the offset. Since we are using numbers that we may want to tweak later, we’ve declared it at the top of the script to make it easy to find. Next, we need variables for the local player and local camera. In the onRenderStep function, get the player’s position based on their Character’s Humanoid/RootPart. We can then add the offset to the player’s position to figure out where the camera should be. Lastly, we set the Camera/CFrame|CoordinateFrame of the camera. If you haven’t encountered it before, a Coordinate Frame (often abbreviated as CFrame) contains information on both the position and orientation of an object in 3D space. The .new function of CFrame lets us define a new frame that is positioned at the first argument and pointing at the second argument.

Now we have a camera that tracks the player. The last thing we will do is reduce the field of view which will both constrain how much of the level the player will see, as well as giving the illusion of the level being “flatter” and closer to an isometric view.

local RunService = game:GetService("RunService")
local Players = game:GetService("Players")

local OFFSET = Vector3.new(40,40,40)
local FIELD_OF_VIEW = 30

local player = Players.LocalPlayer
local camera = game.Workspace.CurrentCamera

camera.FieldOfView = FIELD_OF_VIEW

local function onRenderStep()
	local character = player.Character
	if character then
		local humanoidRootPart = character:FindFirstChild("HumanoidRootPart")
		if humanoidRootPart then
			local playerPosition = humanoidRootPart.Position
			local cameraPosition = playerPosition + OFFSET
			camera.CoordinateFrame = CFrame.new(cameraPosition, playerPosition)
		end
	end
end

RunService:BindToRenderStep("Camera", Enum.RenderPriority.Camera.Value, onRenderStep)

TDS_Camera_Example.png