How would i make an rts camera for mouse?

How would I make an RTS camera similar to warcraft 3? Where your mouse moving in any direction would then move your camera once you reach the edge of the screen?

Im not sure where to start with this, is it possible to detect mouse movement and direction like this? Thank you.

You could get the position of mouse , and then compare it by using crocodiles like this >= <= > < from centre

How would I get the direction of the mouse and convert that to a vector 3 movement for my camera?

Why do you need to convert that into vector3, sorry just asking because I don’t play warcraft3

I need to pan the camera in the direction of the mouse. So id need to get the direction somehow. Comparing center of screen and mouse only gets me distance.

I quickly came up with this, I think it is what you want, but I have never played warcraft 3, so I just pulled up a vid to see how it works. Comments should explain what it does.

local uis = game:GetService("UserInputService") -- used to get the mouse position

local camera = workspace.CurrentCamera -- the current camera
local maxDist = 100 -- 100 studs away from center
local camSpeed = 1 * 2 -- the max speed of the cam, multiplied by two for reason seen later
local angle = math.rad(-75) -- the angle of the camera pointing down

local x, z = 0, 0 -- the curent positions of the x and z

game:GetService("RunService").RenderStepped:Connect(function()
	camera.CameraType = Enum.CameraType.Scriptable
	local viewport = camera.ViewportSize -- the size of the cam viewport for percentages for the mouse
	local mousePos = uis:GetMouseLocation()
	local perX = (mousePos.X / viewport.X - 0.5) * camSpeed -- get the percentage that the mouse is away from the center, that is why there is a -0.5 and * 2 for cam speed
	local perZ = (mousePos.Y / viewport.Y - 0.5) * camSpeed -- without the -0.5, it would only move positively
	x = math.clamp(x + perX, -maxDist, maxDist) -- clamp the x value between the max distance
	z = math.clamp(z + perZ, -maxDist, maxDist) -- clamp the z value between the max distance

	local currentPos = CFrame.new(x, 50, z) -- set the current position
	
	camera.CFrame = currentPos * CFrame.Angles(angle, 0, 0) -- apply and angle
end)

Yes, using Roblox’s built-in UserInputService and Workspace services, you can make an RTS camera that functions like the camera in Warcraft 3. An illustration of how you could achieve this is as follows:

You must first write a script that will control how the camera is moved. To operate the camera, you can either build a separate object or attach this script to the camera itself.

The UserInputService must be used in the script to determine when the mouse is getting close to the screen’s edge. The ViewSizeX and ViewSizeY parameters of the Workspace can be used to calculate how far the mouse is from the screen’s edge after using the GetMouseLocation function to obtain the position of the mouse right now.

Once you’ve established that the mouse is close to the edge of the screen, you can move the camera in that direction by using the Move function. To find out how quickly the mouse is moving, multiply the unit vector you obtain from the GetNormalizedVector function of the Vector2 class by the speed value.

This is perfect! But one issue, in WC3 at least. The camera only pans if your mouse reaches the edges of the screen. I’m not sure how would I go about adding this feature? I thought to only jump the camera if it’s in those regions, but if I do that wouldn’t the camera all of a sudden jump and it not be smooth?

Any idea?

Yeah, it is possible. Here is what I would do to make this work. First, you need some value to represent how far from the center the mouse has to be to activate the camera. Then you need to check if the mouse is actually that far in the positive or negative direction. After that, there is some complicated math IDK if it is actually complicated, but I just got off of work and I don’t really want to think it through right now to figure out how far from that edge it is. Then you can just use that in the other code I provided. Here is what I did. It works perfectly if you have 1/2 as both the mins for X and Z, but if you change some values around, it can be pretty smooth for others also.
If someone else wants to do the correct math, then feel free.

local uis = game:GetService("UserInputService") -- used to get the mouse position

local camera = workspace.CurrentCamera -- the current camera
local maxDist = 100 -- 100 studs away from center
local camSpeed = 1 -- the max speed of the cam
local angle = math.rad(-75) -- the angle of the camera pointing down

local minPerX, minPerZ = 1 / 2, 1 / 2 -- the minimum percent that the mouse must be at to activate
local x, z = 0, 0 -- the curent positions of the x and z

-- start of stuff
camSpeed = camSpeed / minPerX
game:GetService("RunService").RenderStepped:Connect(function()
	camera.CameraType = Enum.CameraType.Scriptable
	local viewport = camera.ViewportSize -- the size of the cam viewport for percentages for the mouse
	local mousePos = uis:GetMouseLocation()
	local perX =  2 * (mousePos.X / viewport.X - 0.5) -- get the percentage that the mouse is away from the center, that is why there is a -0.5 and * 2 for cam speed
	local perZ = 2 * (mousePos.Y / viewport.Y - 0.5) -- without the -0.5, it would only move positively
	if perX >= minPerX or perX <= - minPerX then -- if it is in the activation zone
		perX = (perX - (math.sign(perX) * minPerX)) / minPerX -- set the percentage from the screen. works perfect for 1/2 as minPerX
		x = math.clamp(x + perX, -maxDist, maxDist) -- clamp it
	end
	if perZ >= minPerZ or perZ <= -minPerZ then -- same as above
		perZ = (perZ - (math.sign(perZ) * minPerZ)) / minPerZ
		print(perZ)
		z = math.clamp(z + perZ, -maxDist, maxDist)
	end

	local currentPos = CFrame.new(x, 50, z) -- set the current position

	camera.CFrame = currentPos * CFrame.Angles(angle, 0, 0) -- apply and angle
end)
2 Likes

Thank you so much, now just to wrap my head around this math and how it works haha

Appreciate it

1 Like

Hey, just trying to understand this. How does the camera not just jump to the next position.

Like if you are moving your mouse around the center and that doesnt activate the camera cframe adjustments. But then all of a sudden you reach the corners of the screen in your code its not just teleporting the camera?

Like when i did this before you posted. I had a similar approach that detected when you reached the corners and then would assign the camera CFrame but youd see a sharp jump where the camera teleports to the new mouse location.

Im having trouble understanding your code how this accounts for that jump and is smooth? Thank you.

Likely what you were doing is using the position of the mouse and directly converting it to a point in world space. What I did was use how close the mouse was to the edge of the screen to decide how much to move the camera each frame. As for the math, here is my detailed explanation of each part, and it now works with any percentage as the margin of activation.

First, you need some sort of value to represent the minimum percentage of the screen away the mouse must be to activate the camera. It’ll look something like this:

local minPerX, minPerZ = 1 / 3, 1 / 4

This will be used later to decide when to move the camera based on the position of the mouse.
(Shows what a few places of activation are based on your minPerX. Just flip vertically for Y)

You also need the reciprocal of those two numbers minus 1 for later:

local minXActivation = 1 / minPerX - 1
local minZActivation = 1 / minPerZ - 1

From here on out, this will be in a render stepped loop.
You need the viewportSize and the mousePosition:

local viewport = camera.ViewportSize
local mousePos = uis:GetMouseLocation()

You need to get the ratio of the viewport size and the minPerX and minPerZ by doing something like this:

local viewX, viewZ = viewport.X * minPerX, viewport.Y * minPerZ

This gives the viewport as if it was only 1/3 or 1/4 (or whatever ratio you chose) the size. That means that if the mouse is at the far edge of the screen and you divide the mouse location by it as is done later, you will get a number near the reciprocal of the ratio.

Now is where the real math comes in.
You need to know what percent of the way the mouse is based on the percent of the viewport. If it is less than one, it is on the left side ready to activate, but if it is more than the reciprocal of the ratio-1 then it is on the right side.

The code for this would look like this:

local perX = mousePos.X / viewX

Now there are just some checks to see which side of the screen it is on.

if perX < 1 then
-- on the left side
elseif perX > minXActivation then
-- on the right side
end

Now to find the actual percent away from there, it depends on which side of the screen it is at. For the left side it will just be equal to 1-perX because perX is one when it is exactly on the viewX ratio. 1-perX flips it and gives the distance from the boundary not the screen.

If it is on the left side, there is a different step. It will be perX - minXActivation. This means that we take the perX, which is going to be more than the minXActivation, but less than the full reciprocal, and subtract minXActivation from it to get a number between 0 and 1, where 1 is at the far-right edge, and 0 is on the boundary line.

Now just multiply the perX by the speed value, clamp the value, and apply!
(Then do the same thing for the Horizontal axis.)

local uis = game:GetService("UserInputService")

local camera = workspace.CurrentCamera
local maxDist = 500
local camSpeed = 100 -- in studs/sec
local angle = math.rad(-75) 

local minPerX, minPerZ = 1 / 3, 1 / 4
local x, z = 0, 0 

local minXActivation = 1 / minPerX - 1
local minZActivation = 1 / minPerZ - 1
game:GetService("RunService").RenderStepped:Connect(function(dt)
	camera.CameraType = Enum.CameraType.Scriptable
	local viewport = camera.ViewportSize
	local mousePos = uis:GetMouseLocation()
	
	local viewX, viewZ = viewport.X * minPerX, viewport.Y * minPerZ
	local perX = mousePos.X / viewX
	if perX < 1 then
		-- it is on the left side of the margin
		perX = (1 - perX) * (camSpeed * dt)
		x = math.clamp(x - perX, -maxDist, maxDist)
	elseif perX > minXActivation then
		-- right side
		perX = (perX - minXActivation) * (camSpeed * dt)
		x = math.clamp(x + perX, -maxDist, maxDist)
	end
	
	local perZ = mousePos.Y / viewZ
	if perZ < 1 then
		perZ = (1 - perZ) * (camSpeed * dt)
		z = math.clamp(z - perZ, -maxDist, maxDist)
	elseif perZ > minZActivation then
		perZ = (perZ - minZActivation) * (camSpeed * dt)
		z = math.clamp(z + perZ, -maxDist, maxDist)
	end
	
	
	local currentPos = CFrame.new(x, 50, z)

	camera.CFrame = currentPos * CFrame.Angles(angle, 0, 0)
end)
3 Likes

Thank you for explaining that! <3

I was never great with math, so it’s a bit hard to comprehend all of it. But I get the idea though, thanks again!

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.