How do I create a first person animation with models?

I want to add a sink to my game.

But the problem is, I have a first person game, and I want it animatable. But every single first person animation video I’ve found is for animating guns. What I want to do is have the character walk towards the sink, look down, and start washing their hands, triggered via a ProxPrompt. But I just don’t understand how to do it. Does anyone have any resources or an explanation? Thanks.

The way most games handle first person is to have a separate rig with an animationcontroller under the player’s CurrentCamera. That rig consists of only the hands and is welded to the character’s HumanoidRootPart and animations are played on it depending on the character’s current state (you could use Humanoid:GetState for that)

Usually not welded to character’s humanoid root part and is instead updated in a loop to be the camera’s cframe

Also for something like a sink you could just modify the local transparency modifier of the character to achieve this

There is a lot of ways you can do this, some are using my favorite “Development tricks”.

I’m making a game with a ton of first-person cutscenes, so I understand how to make them. There are 2 ways you can handle this.

1: Cinematic Cutscenes

Cinematic cutscenes can be done pretty easily. Usually, before the cutscene is started, you should have a screen fade into black, then fade back out of black to start the cutscene.

The first step is to copy the player model by ticking the archivable property to on.

PlayerModel.Archivable = true

Once copied, you want to write code to play the animation onto the model.

PlayerModel.Archivable = true

local cloneplayer = PlayerModel:Clone()


local animation = --put the animation object directory right here

local loadanimation = cloneplayer.Humanoid:LoadAnimation(animation)

loadanimation:Play()

Now the animation is playing, but the camera isn’t updating to it. This can be easily fixed by using RunService.

PlayerModel.Archivable = true

local cloneplayer = PlayerModel:Clone()


local animation = --put the animation object directory right here

local loadanimation = cloneplayer.Humanoid:LoadAnimation(animation)

loadanimation:Play()

local function updateCamera()
	Camera.CFrame = cloneplayer.Head.CFrame + Vector3.new(0,.2,.2)
	loadanimation.Stopped:Connect(function()
		
		Connection:Disconnect()
	end)
end
	
Connection = RunService.RenderStepped:Connect(updateCamera)

2: Loading Animations onto the Player Model

This is for things like interacting with the objects around the player. For instance, a sink. I’m not going to go into full like the last time, but do what @zhenjie2003 said. First, modify the local transparency modifier, tutorials on youtube can explain how to do this.

Load the animation onto the player humanoid, and use the update camera function I showed. Pretty simple :smiley: .

If I helped, it’d be greatly appreciated to leave me with solution. Thanks!

5 Likes

Sorry I’m late. I just want to preface this by saying I’m a beginner scripter. I’m not sure how to access the PlayerModel. Once I can do that I’ll continue with your explanation. Also, I have no clue what a local transparency modifier is :skull:

Totally understandable! Do you know how to use proximity prompts, and if you do, are you using them?

Yeah, I know how. I’m using one to activate the cutscene. Activation is as simple as

prompt.Activated:Connect(Function()

end)

correct?

Yes, almost correct. GUIObjects use “Activated” and Prompts use “Triggered”. To play the animation, use the code below. If you want to learn how to update the camera to go with this code, I’d be happy to show you :smiley: .

local prompt = script.Parent

prompt.Triggered:Connect(function(Player) --the player is the player who triggered the prompt
	local PlayerCharacter = Player.Character or Player.CharacterAdded:Wait() --want to add that just in case
	
	local Humanoid = PlayerCharacter:FindFirstChild("Humanoid")
	
	local animation = script.Animation --this is assuming you have an animation object inside of the script
	
	local loadanimation = Humanoid:LoadAnimation(animation)
	
	loadanimation:Play()
	
end)

robloxapp-20220811-1931316.wmv (1.0 MB)

The video above shows the code working.

I’d love if you could help me update the camera with the character. Thanks for this!

To update the camera, we are mainly going to be using 2 services. Replicated Storage and Run Service. Replicated storage can be explained pretty easily, it lets you access both the client and the server. For more about run service, I suggest you read this article: RunService

The reason we are going to be using replicated storage is so we can update the camera on the client, which you can’t do from the server. Add a remote event into replicated storage. This allows you to make a two-way communication between the server and the client. Put a remote event into the replicated storage, and call it what you want, but for this demonstration, I’m just going to call it animationevent.

remote event

Once you’ve done that, change the code from the server script to this:

local ReplicatedStorage = game:GetService("ReplicatedStorage") --getting the replicated storage to access the server and the client

local prompt = script.Parent

prompt.Triggered:Connect(function(Player) --the player is the player who triggered the prompt
	prompt.Enabled = false
	
	local PlayerCharacter = Player.Character or Player.CharacterAdded:Wait() --want to add that just in case
	
	local Humanoid = PlayerCharacter:FindFirstChild("Humanoid")
	
	local animation = script.Animation --this is assuming you have an animation object inside of the script
	
	local loadanimation = Humanoid:LoadAnimation(animation)
	
	loadanimation:Play()
	
	--fire the remote event
	
	ReplicatedStorage.AnimationEvent:FireClient(Player) --firing it to the player who triggered the prompt 
	
	loadanimation.Stopped:Wait()
	
	prompt.Enabled = true
	
	ReplicatedStorage.AnimationEvent:FireClient(Player) --firing it again to kill the updatecamera
	
end)

Next, you need to make a local script. This can be done from anywhere on the client side, but for now just put it in starterplayerscripts.

client side

Copy this code for the client-side script!

local ReplicatedStorage = game:GetService("ReplicatedStorage") --getting the replicated storage

local RunService = game:GetService("RunService") --getting the run service

local Player = game.Players.LocalPlayer --the client

local Character = Player.Character or Player.CharacterAdded:Wait() --the clients character

local Camera = workspace.CurrentCamera --getting the camera

local playinganimation = false --think of this as a debounce

local Connection --will use later

ReplicatedStorage.AnimationEvent.OnClientEvent:Connect(function(Player)
	if not playinganimation then
		playinganimation = true
		
		local Head = Character:FindFirstChild("Head")


		local function updateCamera()
			Camera.CFrame = Head.CFrame + Vector3.new(0,.2,.2)
		end
		Connection = RunService.RenderStepped:Connect(updateCamera)
	else
		--if playing animation is true and the remote event was fired again
		Connection:Disconnect()
		playinganimation = false
	end
	
end)

Sorry if I didn’t explain it well, but if I solved the problem please mark me with a solution :smiley: .

(The animation I used has a different purpose just used it for this code, hints why the camera shakes so much.)

robloxapp-20220811-2010469.wmv (1.3 MB)

1 Like

Thank you so much for this, but it seems like my character isn’t locked in place when I activate the prompt. How should I do that?

robloxapp-20220811-2120440.wmv (1.0 MB)
Here’s what happens by the way

Sorry about being late, thats really strange. If you are moving around, this should fix it:

local ReplicatedStorage = game:GetService("ReplicatedStorage") --getting the replicated storage to access the server and the client

local prompt = script.Parent

prompt.Triggered:Connect(function(Player) --the player is the player who triggered the prompt
	prompt.Enabled = false
	
	local PlayerCharacter = Player.Character or Player.CharacterAdded:Wait() --want to add that just in case
	
	local Humanoid = PlayerCharacter:FindFirstChild("Humanoid")
	
	
	
	local animation = script.Animation --this is assuming you have an animation object inside of the script
	
	local loadanimation = Humanoid:LoadAnimation(animation)
	
	loadanimation:Play()
	
	Humanoid.WalkSpeed = 0
	
	--fire the remote event
	
	ReplicatedStorage.AnimationEvent:FireClient(Player) --firing it to the player who triggered the prompt 
	
	loadanimation.Stopped:Wait()
	
	Humanoid.WalkSpeed = 16 --default walk speed
	
	prompt.Enabled = true
	
	ReplicatedStorage.AnimationEvent:FireClient(Player) --firing it again to kill the updatecamera
	
end)```

Also, send a video of just the animation playing, if it has really fast head movements, it might not look good.

That doesn’t seem to move the camera with the character or lock the character in place :confused:

Also, here’s the animation, its a very simple test one.
robloxapp-20220812-1153541.wmv (474.1 KB)

I made a very simple error. I forgot to change the camera type to scriptable. That’s my fault, it should work using this code in the local script. Sorry for this small mistake.

local ReplicatedStorage = game:GetService("ReplicatedStorage") --getting the replicated storage

local RunService = game:GetService("RunService") --getting the run service

local Player = game.Players.LocalPlayer --the client

local Character = Player.Character or Player.CharacterAdded:Wait() --the clients character

local Camera = workspace.CurrentCamera --getting the camera

local playinganimation = false --think of this as a debounce

local Connection --will use later

ReplicatedStorage.AnimationEvent.OnClientEvent:Connect(function(Player)
	if not playinganimation then
		playinganimation = true
		
		local Head = Character:FindFirstChild("Head")

		local function updateCamera()
			Camera.CameraType = Enum.CameraType.Scriptable
			Camera.CFrame = Head.CFrame + Vector3.new(0,.2,.2)
		end
		Connection = RunService.RenderStepped:Connect(updateCamera)
	else
		--if playing animation is true and the remote event was fired again
		Camera.CameraType = Enum.CameraType.Custom
		Connection:Disconnect()
		playinganimation = false
	end
	
end)

I’m sorry but this still doesn’t seem to be working, my character’s camera is spun very quickly and I’m not locked in place. You don’t have to keep helping me if you don’t want to, thanks for everything.