How do I make a camera/movement camera system like Berserker

I was watching back to some of Aaroncy’s old berserker videos and I realised I really like the way the camera works and how it changes between in and out of combat and how it works in general. I understand how to make a change to the camera between combat and out of combat but from a general standpoint how would I make a camera system like that.

Here is a video for reference (warning there is swearing and blood):

2 Likes

Sorry for the late reply.
This looks like some simple lerps with a camera system
Is this what you are looking for?

Lerp time is a bit exaggerated for this example and the dot is placed for reference:

Sorry in advance for the not so great video quality, had to compress it.

You only wanted to know how the camera works so I didn’t bother adding a custom character system for locking character rotation, same goes for the camera combat state because you said you know how to implement that so I bound it to a key

1 Like

Yes I think that would be pretty cloes for what I want to make but would this also do the quick snap of the camera for example when he dodges?

Oh could you also explain how character rotation is locked in that video too please

If I’m understanding this correctly then yes
Here’s a showcase with a rushed dodge mechanic
Is this what you are looking for?

I gave the lerp a more regular speed in this example and after doing a few small dodges I did a large dodge to show that the camera does go faster the further away you go from it

If this is correct then I would gladly write an explanation with the source code!

Yes! Thanks You. Is it possible you could show me a psuedocode for the camera system used in the 2 videos you sent?

1 Like

Here’s the explanation, pseudocode and source!

Things to note:

  • We aren’t doing any collision checks so the camera could go through obstacles
  • The player and target should never be on the same X and Z positions which would possibly cause unexpected behavior
  • I am not the best in explaining things so the source code is at the bottom if you don’t understand

Explanation

Regular camera system

Do we need a custom camera system?

In this case it would be nice to have a custom camera.
A custom camera would make it easy to write the lock on system.
For this I am going to expand upon a camera system that I made some time ago.
This camera uses spherical coordinates.

https://mathinsight.org/spherical_coordinates

You can read about that from a previous post I made:

Make sure you read the edit and not the outdated source

Lock on camera

Why we needed this system

Lock on camera

The person in the video probably used 2 look at functions.
One for the player and after getting the back, up and right vector he set the camera position and look at.
Of course this is just an assumption.

I propose a different approach.
In this solution I’m

  1. setting the subject to the target.
  2. Get the distance from the player and target and add that to the view distance.
  3. After that I can set the offset to any number I want without it being further than I want it to be.

In other words this approach keeps the view distance consistent.

Personally I prefer this approach but you can use the other one if you want.
All up to preferences ofcourse.

Locking the player rotation?

Apparently you can just use a CFrame lookAt without making movement janky if you disable AutoRotate.
Simply just make a look at and apply the CFrame to the root part.

Heres what I did:

character.Humanoid.AutoRotate = false
		
local LookAt = CFrame.lookAt(character.PrimaryPart.Position, targetPosition, Vector3.yAxis)
		
character.PrimaryPart.CFrame = CFrame.fromMatrix(LookAt.Position, Vector3.new(LookAt.RightVector.X, 0, LookAt.RightVector.Z), Vector3.yAxis)

Pseudocode

Pseudocode
Initialize services: Players, RunService, UserInputService
Get the current camera and set its type to Scriptable

Initialize variables:
    subjectPosition, targetPosition as zero vectors
    locked as true
    lerpSensitivity, sensitivity, radius, theta, phi with given values

Define function SphericalCoordinate(r, theta, phi):
    Calculate Position, Hat_r, Hat_theta, Hat_phi using trigonometric functions
    Return a dictionary with Position, UpVector, LeftVector, FrontVector, DownVector, RightVector, BackVector

Define function LookAtSphericalCoordinate(r, eye, target, xOffset, yOffset):
    Adjust r using the distance between eye and target
    Calculate dx, dy, dz as differences between eye and target coordinates
    Calculate theta and phi with offsets
    Calculate Position, Hat_r, Hat_theta, Hat_phi using trigonometric functions
    Return a dictionary with Position, UpVector, LeftVector, FrontVector, DownVector, RightVector, BackVector

On UserInputService.InputBegan:
    If the input is processed, return
    Toggle locked if L key is pressed
    Move character right if E key is pressed
    Move character left if Q key is pressed
    If input is not MouseButton2, return
    Lock mouse position

On UserInputService.InputEnded:
    If the input is processed, return
    If input is not MouseButton2, return
    Set mouse behavior to default

On RunService.RenderStepped:
    If there is no local player character, return
    Get character and target
    Get mouse delta for movement

    Update theta and phi based on mouse delta

    If locked:
        Set targetPosition to target's position
        Lerp subjectPosition towards character's head position
        Disable character auto-rotate
        Calculate LookAt CFrame for character to face the target
        Set character's CFrame using LookAt matrix

        Calculate spherical coordinates using LookAtSphericalCoordinate
        Lerp camera's CFrame towards calculated spherical coordinates

    Else:
        Lerp subjectPosition towards character's head position
        Enable character auto-rotate
        Calculate spherical coordinates using SphericalCoordinate
        Set camera's CFrame using spherical coordinates and subjectPosition


Source and setup for reference

Source code

Here’s the source and how to set it up

This is here for reference the code is a little rushed. Make sure you do some clean up before making it an integral part of your game/experience!

This is our showcase setup:

  • Starter character is a default block rig.
  • The part in workspace is our target.

Inside the Camera local script:

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

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

local subjectPosition = Vector3.zero
local targetPosition = Vector3.zero

local locked = true
local lerpSensetivity = 0.1
local sensitivity = 0.5
local radius = 10
local theta = 0
local phi = 0

function SphericalCoordinate(r: number, theta: number, phi: number)
	local Position = Vector3.new(
		r * math.sin(phi) * math.cos(theta),
		r * math.cos(phi),
		r * math.sin(phi) * math.sin(theta)
	)

	local Hat_r = Vector3.new(
		math.sin(phi) * math.cos(theta),
		math.cos(phi),
		math.sin(phi) * math.sin(theta)
	)

	local Hat_theta = Vector3.new(
		-math.sin(theta),
		0,
		math.cos(theta)
	)

	local Hat_phi = Vector3.new(
		math.cos(phi) * math.cos(theta),
		-math.sin(phi),
		math.cos(phi) * math.sin(theta)
	)

	return {
		Position = Position,
		UpVector = -Hat_phi,
		LeftVector = Hat_theta,
		FrontVector = -Hat_r,
		DownVector = Hat_phi,
		RightVector = -Hat_theta,
		BackVector = Hat_r
	}
end

function LookAtSphericalCoordinate(r: number, eye: Vector3, target: Vector3, xOffset: number, yOffset: number)
	r += (eye - target).Magnitude

	local dx = eye.X - target.X
	local dy = eye.Y - target.Y
	local dz = eye.Z - target.Z

	local theta = math.atan2(dz, dx) - xOffset * math.pi / 180
	local phi = math.acos(dy / r) - yOffset * math.pi / 180

	local Position = Vector3.new(
		r * math.sin(phi) * math.cos(theta),
		r * math.cos(phi),
		r * math.sin(phi) * math.sin(theta)
	)

	local Hat_r = Vector3.new(
		math.sin(phi) * math.cos(theta),
		math.cos(phi),
		math.sin(phi) * math.sin(theta)
	)

	local Hat_theta = Vector3.new(
		-math.sin(theta),
		0,
		math.cos(theta)
	)

	local Hat_phi = Vector3.new(
		math.cos(phi) * math.cos(theta),
		-math.sin(phi),
		math.cos(phi) * math.sin(theta)
	)

	return {
		Position = Position,
		UpVector = -Hat_phi,
		LeftVector = Hat_theta,
		FrontVector = -Hat_r,
		DownVector = Hat_phi,
		RightVector = -Hat_theta,
		BackVector = Hat_r
	}
end

UserInputService.InputBegan:Connect(function(input: InputObject, gameProcessedEvent: boolean)
	if gameProcessedEvent then return end
	if input.KeyCode == Enum.KeyCode.L then locked = not locked end
	if input.KeyCode == Enum.KeyCode.E then
		Players.LocalPlayer.Character.PrimaryPart.AssemblyLinearVelocity = Players.LocalPlayer.Character.PrimaryPart.CFrame.RightVector * 120
	end
	if input.KeyCode == Enum.KeyCode.Q then
		Players.LocalPlayer.Character.PrimaryPart.AssemblyLinearVelocity = -Players.LocalPlayer.Character.PrimaryPart.CFrame.RightVector * 120
	end
	if input.UserInputType ~= Enum.UserInputType.MouseButton2 then return end

	UserInputService.MouseBehavior = Enum.MouseBehavior.LockCurrentPosition
end)

UserInputService.InputEnded:Connect(function(input: InputObject, gameProcessedEvent: boolean)
	if gameProcessedEvent then return end
	if input.UserInputType ~= Enum.UserInputType.MouseButton2 then return end

	UserInputService.MouseBehavior = Enum.MouseBehavior.Default
end)

RunService.RenderStepped:Connect(function()
	if not Players.LocalPlayer.Character then
		return
	end
	
	local character = Players.LocalPlayer.Character
	local target = workspace.Part
	
	local delta = UserInputService:GetMouseDelta()
	
	theta = (theta + delta.X * sensitivity) % 360
	phi = math.clamp((phi + -delta.Y * sensitivity), 0, 180)

	if locked then
		targetPosition = target.Position
		
		subjectPosition = subjectPosition:Lerp(character.Head.Position, lerpSensetivity)
		
		character.Humanoid.AutoRotate = false
		
		local LookAt = CFrame.lookAt(character.PrimaryPart.Position, targetPosition, Vector3.yAxis)
		
		character.PrimaryPart.CFrame = CFrame.fromMatrix(LookAt.Position, Vector3.new(LookAt.RightVector.X, 0, LookAt.RightVector.Z), Vector3.yAxis)
		
		local sphericalCoordinate = LookAtSphericalCoordinate(
			radius,
			subjectPosition,
			targetPosition,
			15,
			15
		)

		currentCamera.CFrame = currentCamera.CFrame:Lerp(CFrame.fromMatrix(
			sphericalCoordinate.Position + targetPosition,
			sphericalCoordinate.RightVector,
			sphericalCoordinate.UpVector, 
			sphericalCoordinate.BackVector
			), lerpSensetivity)
	else
		subjectPosition = subjectPosition:Lerp(character.Head.Position, lerpSensetivity)
		
		character.Humanoid.AutoRotate = true
		
		local sphericalCoordinate = SphericalCoordinate(
			radius,
			theta * math.pi / 180,
			phi * math.pi / 180
		)

		currentCamera.CFrame = CFrame.fromMatrix(
			sphericalCoordinate.Position + subjectPosition,
			sphericalCoordinate.RightVector, 
			sphericalCoordinate.UpVector, 
			sphericalCoordinate.BackVector
		)
	end
end)

I hope this helped!
Have a nice day making games/experiences!
:grin:

3 Likes

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