Making an instant stop with a BodyForce

I’m attempting to achieve a studio-like camera system.

Using bodyForces the camera will slow down to a complete stop rather then instantly stopping, while this may be a cool effect it really wont work with what I want to use it for.

Here is what it currently looks like…

https://gfycat.com/LastAromaticCurlew.gif

Everything is wonderful expect you easily see the sliding that it has using a bodyForce. Would it be possible to make something that instantly stops the camera? I was thinking about deleting the bodyForce and then remaking it but I wasn’t sure if that would screw up the other directions as well.

Here is my current code

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Workspace = game:GetService("Workspace")
local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")

local CurrentCamera = Workspace.CurrentCamera
local Objects = ReplicatedStorage:WaitForChild("Objects")
local Camera = Objects:WaitForChild("Camera")

Camera = Camera:Clone()
Camera.Parent = Workspace

local BodyForce = Camera:WaitForChild("BodyForce")
local BodyGyro = Camera:WaitForChild("BodyGyro")
local BodyPosition = Camera:WaitForChild("BodyPosition")

local Position = Vector3.new(0,30,0)
local Direction = Vector3.new(0,0,0)

UserInputService.InputEnded:Connect(function(input, gameProcessed)
	if input.UserInputType == Enum.UserInputType.Keyboard then
		local keyPressed = input.KeyCode
		if keyPressed == Enum.KeyCode.W then
			Direction = Direction + Vector3.new(0, 0, -Direction.Z)
		elseif keyPressed == Enum.KeyCode.S then
			Direction = Direction + Vector3.new(0, 0, -Direction.Z)
		elseif keyPressed == Enum.KeyCode.D then
			Direction = Direction + Vector3.new(-Direction.X, 0, 0)
		elseif keyPressed == Enum.KeyCode.A then
			Direction = Direction + Vector3.new(-Direction.X, 0, 0)
		elseif keyPressed == Enum.KeyCode.Q then
			Direction = Direction + Vector3.new(0, -Direction.Y, 0)
		elseif keyPressed == Enum.KeyCode.E then
			Direction = Direction + Vector3.new(0, -Direction.Y, 0)
		end
	end
end)

UserInputService.InputBegan:Connect(function(input, gameProcessed)
	if input.UserInputType == Enum.UserInputType.Keyboard then
		local keyPressed = input.KeyCode
		if keyPressed == Enum.KeyCode.W then
			Direction = Direction + Vector3.new(0, 0, -150)
		elseif keyPressed == Enum.KeyCode.S then
			Direction = Direction + Vector3.new(0, 0, 150)
		elseif keyPressed == Enum.KeyCode.D then
			Direction = Direction + Vector3.new(150, 0, 0)
		elseif keyPressed == Enum.KeyCode.A then
			Direction = Direction + Vector3.new(-150, 0, 0)
		elseif keyPressed == Enum.KeyCode.Q then
			Direction = Direction + Vector3.new(0, -150, 0)
		elseif keyPressed == Enum.KeyCode.E then
			Direction = Direction + Vector3.new(0, 150, 0)
		end
	end
end)

RunService.RenderStepped:connect(function()
	CurrentCamera.CFrame = Camera.CFrame
	BodyPosition.Position = Position
	if Direction then
		BodyForce.Force = BodyForce.Force + Direction
	end
end)


Here is my Camera Object in the explorer.

The easiest way to do it would probably be to briefly anchor the part. I haven’t done this before, but anchoring the part should bring the part’s velocity to 0 (i.e. as long as the magnitude of force is 0, you won’t start moving after the part unanchors.

edit: This will only work if all inputs are ended. If you want to stop one direction’s movement while retaining another this won’t work. You could try manually adjusting the part’s velocity (part.Velocity = part.Velocity - direction), however I’m not sure if this will work properly.

1 Like

You would need to set the BodyMover’s D property.

From the Roblox Wiki:

D is the amount of damping that will be used. Damping will stop the object from surpassing its goal and having to turn around. By setting a good D, it will start to slow down as it reaches its goal so that it does not go past it. The higher the D value, the more it will slow down.

1 Like

I would really suggest that you do not use a part to control your camera movement. You can simply set the Camera CFrame directly. Given a key press, you can calculate the desired velocity (with acceleration or without), and then transform your camera CFrame by that velocity. This will give you much more precise control over your camera, as well as save resources and prevent undefined behavior.

3 Likes

Yeah, I would definitely use TweenService here, using Enum.EasingDirection.Out and either Enum.EasingStyle.Quad or Enum.EasingStyle.Quint to get the same effect but with way less resources, jitter, etc.

I was bored and I made CameraScripts before, so I made this fully functional snippet of code that behaves like studio’s default camera system for you:

local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")
local TweenService = game:GetService("TweenService")

local camera = workspace.CurrentCamera
camera.CameraType = Enum.CameraType.Scriptable

local KEY_DIRECTIONS = table.freeze({
	[Enum.KeyCode.A] = Vector3.new(-1, 0, 0),
	[Enum.KeyCode.D] = Vector3.new(1, 0, 0),
	[Enum.KeyCode.S] = Vector3.new(0, 0, 1),
	[Enum.KeyCode.W] = Vector3.new(0, 0, -1),
	[Enum.KeyCode.Q] = Vector3.new(0, -1, 0),
	[Enum.KeyCode.E] = Vector3.new(0, 1, 0)
})

local TURN_SPEED = 1/90 -- in radians/pixel
local SHIFT_SPEED = 6 -- in studs/second
local MOVE_SPEED = 60 -- in studs/second

local shiftPressed = false

local activeDirections = {}

local x = 0
local y = 0
local angle = CFrame.Angles(0, x, 0) * CFrame.Angles(y, 0, 0)
local position = Vector3.new(0, 0, 0)

UserInputService.InputBegan:Connect(function(input)
	if KEY_DIRECTIONS[input.KeyCode] then
		activeDirections[input.KeyCode] = KEY_DIRECTIONS[input.KeyCode]
	elseif input.UserInputType == Enum.UserInputType.MouseButton2 then
		UserInputService.MouseBehavior = Enum.MouseBehavior.LockCurrentPosition
	elseif input.KeyCode == Enum.KeyCode.LeftShift then
		shiftPressed = true
	end
end)

UserInputService.InputEnded:Connect(function(input)
	if KEY_DIRECTIONS[input.KeyCode] then
		activeDirections[input.KeyCode] = nil
	elseif input.UserInputType == Enum.UserInputType.MouseButton2 then
		UserInputService.MouseBehavior = Enum.MouseBehavior.Default
	elseif input.KeyCode == Enum.KeyCode.LeftShift then
		shiftPressed = false
	end
end)

UserInputService.InputChanged:Connect(function(input)
	if input.UserInputType == Enum.UserInputType.MouseMovement then
		x = (x - input.Delta.X * TURN_SPEED)%(2*math.pi)
		y = math.clamp(y - input.Delta.Y * TURN_SPEED, math.rad(-89), math.rad(89))
		angle = CFrame.Angles(0, x, 0) * CFrame.Angles(y, 0, 0)
	elseif input.UserInputType == Enum.UserInputType.MouseWheel then
		position += angle * Vector3.new(0, 0, 5*input.Position.Z)
	end
end)

local speedBonus = 0
RunService.RenderStepped:Connect(function(dt)
	local move = Vector3.zero
	for _, v in pairs(activeDirections) do
		move += v
	end

	if move ~= Vector3.zero then
		speedBonus += 0.1
	else
		speedBonus = 0
	end
	
	local speed = if shiftPressed then SHIFT_SPEED else MOVE_SPEED + speedBonus
	
	position += angle * (move * speed * dt)
	camera.CFrame = angle + position
end)

It would be great if I just went on to create a custom-made studio camera that works in-game.

It’s been three days, so I am probably replying too late, but I hope this helps.

40 Likes

You definitely still want to update your camera CFrame on RenderStep, which hopefully is still the case. There is nothing inherently wrong with using a part in world to represent it, and it does get you some things for free, like not having to write your own PID controller or spring simulation in Lua. But you can’t use BodyForce to get an instant stop. Stopping instantly violates the laws of physics for starters, and secondly applying a large corrective force is prone to overcorrection, because it becomes very sensitive to the precise duration of the physics time step. Also, I would recommend using either BodyPosition, BodyVelocity, or BodyForce, but not a combination. They will have competing goals, since they are all ultimately trying to control the same thing (the position of the part), they differ only in which derivative you’re specifying.

If you want to stop the camera instantly, just stop updating its CFrame.

1 Like

You wouldn’t necessarily need to write a PID Controller or Spring simulation in order to have the desired camera movements. He could simply have an acceleration vector, which he changes, and that acceleration variable is integrated to position in his camera loop. Whenever the player desires to move around, simply update the acceleration to some desired value, and then cap velocity to a maximum.

I think this approach is simple enough to implement given an individual has knowledge of basic physics concepts. Yet, it is still expandable enough to allow someone to later on implement whatever control law they want. Furthermore, it allows you to instantly ‘stop’ the Camera’s position in an abstracted physical manner (ie; achieving zero velocity directly without worrying about manually updating the camera CFrame)