Spectating on streaming enabled?

Hello devs, I’m maintaining the codebase for a somewhat popular obby game, and recently we’ve decided to make the switch to using Streaming Enabled, and haven’t had much problem refactoring all the client code.

However, I’ve been unable to come up with a reliable solution for a spectate system, and this is really important as some devproducts are tied to it.

I’ve got to mention that I did come up with a solution, but it has some specific problems that I’m hoping I can get feedback on.
It is a combined implementation based on these threads: (only ones existing about the topic)

The gist of how it works is that the the GUI picks a Target (player) and fires a remote to the server, then attempts to set the CameraSubject to that target every renderstep.

Afterwards, a separate, serversided script detects when this target has changed, and does two things:

  1. RequestStreamAroundAsync the area around the Target

  2. Sets the spectator’s ReplicationFocus to the Target (HumanoidRootPart)

Heres the scripts:

--[[SPECTATE GUI]]
	... --//inside RenderStepped
	local playerSpectating = playerlist[current] or player :: Player
		
	if playerSpectating == lastplayer then
		SetSubject(lastplayer) --camera.CameraSubject
		return
	end
	lastplayer = playerSpectating
		
	frame.Spectate.Current.Text = playerSpectating.DisplayName
	game.ReplicatedStorage.TargetPick:FireServer(playerSpectating.Name)
--[[SERVER SCRIPT]]
function StreamAroundTarget(plr:Player)
	local char = plr.Character or plr.CharacterAdded:Wait()

	local part = char.PrimaryPart or char:FindFirstChildOfClass('BasePart')
	plr:RequestStreamAroundAsync(part.Position,1.5)
	return part
end

game:GetService('Players').PlayerAdded:Connect(function(plr)
	local target:ObjectValue = plr:WaitForChild('Target',20)
	
	local lastcnc :RBXScriptSignal|nil = nil
	target.Changed:Connect(function(value: Instance)
		if not value or not value:IsA('Player') then return end
		if lastcnc then lastcnc:Disconnect(); lastcnc=nil end
		
		local focus = nil
		
		if value ~= plr then
			focus = StreamAroundTarget(value)
			if target.Value ~= value then return end
			
			lastcnc = value.CharacterAdded:Connect(function()
				print(`[{plr.Name}] refresh focus {value.Name}`)
				plr.ReplicationFocus = StreamAroundTarget(value)
			end)
		end
		
		plr.ReplicationFocus = focus
	end)
end)

The problem I seem to be having with this approach is that the CameraSubject isn’t changing to the target unless the target respawns, or is close enough so that they were already streamed in. Ocassionally, it seems to work as intended but severly delayed (10+ secs).

I am not entirely sure what precisely is causing the issue as I’m fairly new to using streaming enabled.

Finally, I’m greatful for any suggestions or help offered, and I hope this can also serve useful for people coming across this. (as spectating is fairly common in games)

I will also be posting a video shortly to further illustrate the problem.

4 Likes

As mentioned, heres the video showcasing the spectate problem:

Note that Player2 (right) is spectating Player1 (left), and is struggling to update the CameraSubject until Player1 respawns
(or atleast thats what I think is happening)

2 Likes

Quick update on this, I’ve come across one solution for this in a streamingEnabled game, by making all character models persistent.

While this does solve the issue, it comes at the cost of having players be visible “floating in the air”, i.e. visible in zones that aren’t streamed in. It also takes away the cost benefit of not having an entire player character streamed in at all times

You could set each character model’s StreamingMode to PersistentPerPlayer, and then use AddPersistentPlayer and RemovePersistentPlayer on the server to stream the character only when the player is spectating.

The documentation for these functions is lacking though (quite literally no explanations), so you’re going to have to do some testing.

Example code:

local characterImSpectating = workspace.CoolName
local myPlayer = Players.CoolName2

-- Stream in player (spectate has started)
characterImSpectating:AddPersistentPlayer(myPlayer)

-- Stream out player (spectate has ended)
characterImSpectating:RemovePersistentPlayer(myPlayer)

Sorry for replying so late, haven’t seen this post until now.

3 Likes

Thank you, I will definitely try that

1 Like

Did it work?

1 Like

Hey man!, it wasn’t working at first, because I had forgotten to change the character model’s ModelStreamingMode to PersistentPerPlayer haha.

Now it works perfectly fine! thank you very much for your help.

1 Like

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