If I had a storyline module that works with dialogue gui, where should I place it?

The topic indicates the problem. A storyline that display on dialogue gui to every clients either in Singleplayer or in Multiplayer. Should I place it in ReplicatedStorage, ServerScriptService, StarterGui, or StarterPlayerScript?

Here’s the Module script containing texts that work with dialogue system:

return {
    -- {Communicator,Messages,WaitOnEnded,TimePerLetter,AdditionalCallback}
	["1"] = {
		"You",
		"I arrived here now, lets take a break for couple of hours I guess...",
		2,
		0.1,
		nil
	},
	
	["2"] = {
		"You",
		"I've been tiring for the whole day helping people in the incident, gotta go upstair to take some rest.",
		2,
		0.1,
		nil
	},
	
	["3"] = {
		"You",
		"Hope no one is going to give me a bunch of works to do.",
		2,
		0.1,nil
	}
	
}

image

(This is just a simple structure that later will be working on.)

Any help would be appreciated!

2 Likes

I think you should use ReplicatedStorage to hold the Storyline Module.

3 Likes

If you’re gonna have full cutscenes, including player camera movement and stuff, a LocalScript for all of them is a good idea, if you just want to flash text in the player’s screen a singular ModuleScript holding them is enough

2 Likes

Looks like (i mean it’s kinda guessing) the text would be send through RemoteEvent, so i doubt it will make a difference

2 Likes

I was thinking of doing some cutscene and some changes on server sided like set part’s anchor or something. Btw, thanks for the help!

I have a similar system. I used a module for each cutscene and put them into folders in replicatedstorage. Each module essentially returns a function. When a player loads in, the data containing the user’s current cutscene is fetched, and then the function is called.

An example of one of my cutscenes:

local TS = game:GetService("TweenService")
local movement = require(game.ReplicatedStorage.modules.movement)

local dialogue_handler = require(game.ReplicatedStorage.modules.dialogue)
dialogue_handler = dialogue_handler.new()

local spr = require(game.ReplicatedStorage.modules.spr)

local lighting = game:GetService("Lighting")

return function()
	game:GetService("UserInputService").MouseIconEnabled = false
	lighting.TimeOfDay = "19:30:00"
	
	local atmosphere = Instance.new("Atmosphere", lighting)
	local bloom = Instance.new("BloomEffect", lighting)
	local blur = Instance.new("BlurEffect", lighting)
	
	blur.Size = 4
	
	bloom.Intensity = 5
	bloom.Size = 50
	bloom.Threshold = 1
	
	atmosphere.Density = 0.5
	atmosphere.Offset = 0
	
	movement:ToggleMovement(true)
	local char = game.Players.LocalPlayer.Character
	local scene_characters = game.ReplicatedStorage.cutscene_resources.characters

	char:PivotTo(workspace.cutscene.OpeningScene.Hideout.spawnParts.player.CFrame * CFrame.new(0, 1, 0))

	local commander = scene_characters.Commander:Clone()
	commander.Parent = workspace.cutscene
	commander:PivotTo(workspace.cutscene.OpeningScene.Hideout.spawnParts.commander.CFrame)

	local animation = commander['M4A1'].animations.idle
	animation = commander:FindFirstChild("Humanoid"):WaitForChild("Animator"):LoadAnimation(animation)

	animation:Play(0)

	local cam = workspace.CurrentCamera
	cam.CameraType = Enum.CameraType.Scriptable
	cam.CFrame = workspace.cutscene.OpeningScene.Hideout.cameraParts.camPart1.CFrame

	dialogue_handler:set({speaker = "Commander", text = char.Name..", over here!"})
	dialogue_handler:write()
	
	local callback = function()
		coroutine.wrap(function()
			task.wait(.3)
			spr.target(char.PrimaryPart, 1, 1, {CFrame = CFrame.lookAt(char.PrimaryPart.Position, commander.PrimaryPart.Position)})
			spr.completed(char, function() wait() end)
		end)()
		
		dialogue_handler:clear()
		task.wait(1.5)
		dialogue_handler.gui.main.Visible = false
	end
	
	dialogue_handler:completed(callback)

	spr.target(cam, 1, 1.5, {CFrame = workspace.cutscene.OpeningScene.Hideout.cameraParts.camPart2.CFrame})
	spr.completed(cam, function() wait() end) 

	dialogue_handler:set({speaker = "Commander", text = "The Bacons have taken over a city with a group of noob refuges, They're searching the entire city, and our people will be found"})
	dialogue_handler:write()
	dialogue_handler:completed(function() dialogue_handler:clear() task.wait(.75) dialogue_handler.gui.main.Visible = false end)
	
	dialogue_handler:set({speaker = "Commander", text = "Those refuges contain research on a new weapon that could potentially be used to win the war over the Bacons."})
	dialogue_handler:write()
	dialogue_handler:completed(function() dialogue_handler:clear() task.wait(.75) dialogue_handler.gui.main.Visible = false end)
	
	dialogue_handler:set({speaker = "Commander", text = "We are going to deploy you as the leader of a small task force with some other recruits, but before recapturing the city we need to do something else."})
	dialogue_handler:write()
	dialogue_handler:completed(function() dialogue_handler:clear() task.wait(1) dialogue_handler.gui.main.Visible = false end)
	
	spr.target(cam, 1, 1.5, {CFrame = workspace.cutscene.OpeningScene.Hideout.cameraParts.camPart3.CFrame})
	
	print("Move to started!") -- had to do some debugging
	char.Humanoid:MoveTo(workspace.cutscene.OpeningScene.Hideout.movementParts.playerOne.Position, workspace.cutscene.OpeningScene.Hideout.movementParts.playerOne)
	char.Humanoid.MoveToFinished:Wait()
	
	--[[spr.target(char.PrimaryPart, 1, 1, {CFrame = CFrame.lookAt(char.PrimaryPart.Position, commander.PrimaryPart.Position)})
	spr.completed(char.PrimaryPart, function() task.wait(1) end)]]

	dialogue_handler:set({speaker = char.Name, text = "What's the mission?"})
	dialogue_handler:write()
	dialogue_handler:completed(function() dialogue_handler:clear() task.wait(1) dialogue_handler.gui.main.Visible = false end)
	
	spr.target(cam, 1, 1, {CFrame = workspace.cutscene.OpeningScene.Hideout.cameraParts.camPart4.CFrame})
	spr.completed(cam, function() task.wait(.5) end)
	
	dialogue_handler:set({speaker = "Commander", text = "It's simple, but the area will be heavily fortified."})
	dialogue_handler:write()
	dialogue_handler:completed(function() dialogue_handler:clear() task.wait(.75) dialogue_handler.gui.main.Visible = false end)
	
	dialogue_handler:set({speaker = "Commander", text = "There's a Bacon base around 3000 studs from our location. Clear the base, and you'll find some blueprints."})
	dialogue_handler:write()
	dialogue_handler:completed(function() dialogue_handler:clear() task.wait(.75) dialogue_handler.gui.main.Visible = false end)
	
	dialogue_handler:set({speaker = "Commander", text = "Those are the city's blueprints. They are going to be the key to retreving the city again."})
	dialogue_handler:write()
	dialogue_handler:completed(function() dialogue_handler:clear() task.wait(.75) dialogue_handler.gui.main.Visible = false end)
	
	dialogue_handler:set({speaker = "Commander", text = "Go retrieve them and come back here and report to me, I'll give you the next steps"})
	dialogue_handler:write()
	dialogue_handler:completed(function() dialogue_handler:clear() task.wait(.75) dialogue_handler.gui.main.Visible = false end)
	
	coroutine.wrap(function()
		spr.target(cam, 1, 1, {CFrame = workspace.cutscene.OpeningScene.Hideout.cameraParts.camPart5.CFrame})
	end)()
	
	dialogue_handler:set({speaker = "Commander", text = "There are some weapons over at that table. Suit up and get ready, a helicopter will be here to pick your team up in 10 minutes. Good luck."})
	dialogue_handler:write()
	dialogue_handler:completed(function() dialogue_handler:clear() task.wait(1) dialogue_handler.gui.main.Visible = false end)
	
	spr.target(cam, 1, 1, {CFrame = char.PrimaryPart.CFrame * CFrame.new(char.Humanoid.CameraOffset)})
	spr.completed(cam, function() task.wait(1.5) end)
	
	cam.CameraType = Enum.CameraType.Custom
	
	task.wait(1)
	movement:ToggleMovement(false)
	
	return true -- if true isn't returned, the cutscene cut out somewhere or broke
end

Some of the characters and maps stored for the cutscenes:
Screenshot 2025-03-24 at 10.00.07 PM

This is all run through a cutscene controller, which is just a tiny module script (probably not necessary but it does come in handy)

local handler = {}

function handler:Play(name)
	local map = game.ReplicatedStorage.cutscene_resources.maps.OpeningScene
	local cutscene = game.ReplicatedStorage.cutscenes:FindFirstChild(name)
	map = map:Clone()
	
	map.Parent = workspace.cutscene
	local finished = require(cutscene)()
	if not finished then return end
	
	return true
end

function handler.CleanUp()
	for i, v in pairs(workspace.cutscene:GetChildren()) do
		v:Destroy()
	end
end

return handler

This is just how I handled it, and this is all run locally, as there is no point in running any of this on the server. You could just replicate the same cutscene on all clients if you need to play the cutscene for everybody in a group, whether it be through a loop or whatever

Oh yeah, the dialogue handler is just a module of mine to create a typewriter effect on the gui, nothing important here

3 Likes

I like what your doing here, but it’s kinda complicated for me. I wanna ask something that which script did you use to run them?

Sure, lemme send it here. Also, feel free to ask about your doubts, I can clear them up for you!
Anyways:
So, when a player joins the server, I have a script in SSS which checks if the player has played the game before. If they haven’t, they shouldn’t have a currentScene stored

if DS:GetAsync(plr.UserId.."-currentScene") then
		scene = game.ReplicatedStorage.scenes.acts.one.One -- placeholder, my datastore isn't active as of now
	else
		scene = game.ReplicatedStorage.scenes.acts.one.One
	end

then, this is then returned to another module script, which is basically my game controller (The game is an fps framework, so it’s the framework handler as well)

Now, in the modulescript:

function handler.new()
	local loadout, joined = events.new:InvokeServer()
    ...
    return (data), joined -- joined is the current cutscene, which is the position of the modulescript of the cutscene in replicated storage
end

Now, this gets sent back to a localscript inside of startercharacterscripts, which then requires the module and runs the function

local framework = require(modules:WaitForChild("framework"))
local weapon, currentScene = framework.new()

local check = require(currentScene)() -- remember, the cutscene returns 'true' after it has been played successfully, so this can be used to wait until the cutscene has finished playing.

Hope this helped! Reply to this if you need any further doubt clarification, but I may respond after a while, as it’s pretty late and I’m gonna head to sleep.

1 Like

So the cutscene plays on client? How about a story game where all players do the same task at the same time as other client does. Do I need to place a script or a localscript inside a ServerScriptService or in the StarterCharacterScript/StarterGui as a main script?

(A script that handles in both singleplayer and multiplayer.)

Ok, so if I understand correctly, every client needs to be able to see this.

Basically, we’re gonna use :FireAllClients()
Lets say a player completes a task that the entire server is tasked to do together. Now, when he completes a task, we can fire a remote to the server handler. Then, we can then register this event being called on the server and call the according cutscene to all clients. A sample code would be:

-- StarterCharacterScripts
local part = workspace.Part
local remote = game.ReplicatedStorage.RemoteEvent

part.Touched:Connect(function(hit) -- Do NOT use touched events in real practice, use raycasting or other forms of touch detection (touched is very unreliable)
    if game.Players:FindFirstChild(hit.Parent) then
        remote:FireServer(cutscene_name)
end)

remote.OnClientEvent:Connect(function(name)
    require(game.ReplicatedStorage.cutscenes:FindFirstChild(name))()
end)

Server script:

-- Server Script Service
local remote = game.ReplicatedStorage.RemoteEvent

remote.OnServerEvent:Connect(function(name)
    remote:FireAllClients(name)
end)

Also, I’ll only be able to reply after ~15 hours, sorry for the delay if you have any doubts.
Btw, you can use BindableEvents instead, I forgot to implement them here, but they’re probably the best way to handle this.

2 Likes

Thanks for the information, this is understandable.

1 Like

Glad you were able to get it! Also, if you’re looking to close this discussion thread, make sure to mark one of the posts as a solution (either mine, yours, or anybody else’s)

Definitely replicated storage

Summary

This text will be hidden

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