Visual Novel Service Feedback

G’day devs! I’ve been trying to make a Visual Novel Module System (Which could also be used for Dialogue Systems) that will make some tedious tasks easier.

(I still haven’t decided if I should release it or keep it for personal use)

What I currently need help with:

  • Improvement in both performance and simplifying the local script side
  • Features/Ideas (Mainly how Visual Novels do it)

Do note that I for now only need ideas/feedback on the visual novel service’s dialogue, this still doesn’t include screen VFX or MSX as they will be in a separate module script and can be run using the command runner function.

Local Script Code
local DialogueModule = require(game:GetService("ReplicatedStorage").DialogueModule)
local Dialogue = DialogueModule.CoreFunctions
local Settings = require(game:GetService("ReplicatedStorage").DialogueModule.SettingsModule)

local DialogueArea = script.Parent.DialogueArea
local TxtLabel = DialogueArea.Template.TxtTemplate

local UIS = game:GetService("UserInputService")

local Types = Settings.Type()

local FunctionListTemplate = {
	{
		["Dialogue"] = {"Aaron", "X-Ray 09"},
		["ChangeTxtColor"] = function() 
			local x = Settings.TextSettings()
			x.TxtColor = Color3.fromRGB(37, 161, 255)
			return x
		end,
	},
	{
		["Dialogue"] = {"I'll just go back to my wonderful nap.", "Tactical airstrike in bound"},
		["ChangeTxtColor"] = function() 
			local x = Settings.TextSettings()
			x.TxtColor = Color3.fromRGB(255, 64, 67)
			return x
		end,
	},
	{
		["Dialogue"] = {"Alpha-1", "air support"},
		["ChangeTxtColor"] = function() 
			local x = Settings.TextSettings()
			x.TxtColor = Color3.fromRGB(134, 255, 41)
			return x
		end,
	},
	{
		["Dialogue"] = {"Alpha-1"},
		["ChangeTxtColor"] = function() 
			local x = Settings.TextSettings()
			x.TxtColor = Color3.fromRGB(0, 229, 255)
			return x
		end,
	},
}


local FunctionListTemplate222 = {
	{
		["Dialogue"] = {"Aaron", "X-Ray 09"},
		["ChangeTxtColor"] = function() 
			warn("EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE")
		end,
	},
}

local t = 0

local Characters = {
	["Character1"] = {
		["CharName"] = "Aaron",
		["CharID"] = workspace.Aaron,
		["CharDesc"] = "",
	},
	["Character2"] = {
		["CharName"] = "Bryan",
		["CharID"] = workspace.Bryan,
		["CharDesc"] = "",
	},
	["Character3"] = {
		["CharName"] = "S m i l e",
		["CharID"] = "rbxassetid://1803736786",
		["CharDesc"] = "",
	},
}

		--[[
		local txtcmdlist = Dialogue.CmdSchedule(Statement1, FunctionListTemplate, "Fill")

		Dialogue.NewCharacter(Characters["Character2"], "rbxassetid://16265865392")
		Dialogue.GenerateText(Statement1, Settings.TextSettings(), txtcmdlist, 1)
		Dialogue.TypeWriter(Statement1)
		]]

local CurrentlyRunning = false
local db = false
local function Reset()
	db = true
	Dialogue.EndDialogue()
	t = 0	
	db = false
	CurrentlyRunning = false
end

local Cooldown = false
local function CooldownFunc()
	task.spawn(function()
		if Cooldown == false then
			Cooldown = true
			wait(1)
			Cooldown = false
		end
	end)
end

Dialogue.LaunchDialogueModule(script.Parent.Parent, script.Parent.DialogueArea, script.Parent.CharacterFrame, script.Parent.Parent.NameFrame, script.Parent.Parent.ChoicesFrame)
UIS.InputBegan:Connect(function(inp)
	
	if inp.KeyCode == Enum.KeyCode.Two then
		Dialogue.SkipTypeWriting()
	end
	
	if Cooldown == true then return end

	if inp.KeyCode == Enum.KeyCode.RightBracket then
		CooldownFunc()
		
		if db == true then return end
		if CurrentlyRunning then Reset() return end
		CurrentlyRunning = true
		db = true
		
		local Target =  script.Parent.DialogueArea:FindFirstChild("CharacterFrame[2]")
		if Target then Target:Destroy() end

		Dialogue.StartDialogue()

		Dialogue.NewDialogue("Tactical airstrike in bound! This is Alpha-1, We request air support!!! HQ come in!", {["TxtFunctionList"] = FunctionListTemplate}, Characters["Character2"], {["AnimationPose"] = "rbxassetid://16265865392"})
		if Dialogue.DialogueWait("10", true) then return end -- 2
		Dialogue.ContinueDialogue(" HQ? HQ? Aaron! AAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAA AAAAAAAA AAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAA AAAAAAAAAAAAAA AAAAAAAAAA AAAAAAAAAAAAA AAAAAAA",{["TxtFunctionList"] = FunctionListTemplate}, Characters["Character2"], {["AnimationPose"] = "rbxassetid://16265872688"})
		if Dialogue.DialogueWait("25", true) then return end -- 5
		Dialogue.NewDialogue("Wait, huh?", {["TxtFunctionList"] = FunctionListTemplate}, Characters["Character1"], {["AnimationPose"] = "rbxassetid://16265869308"})
		if Dialogue.DialogueWait(10, true) then return end -- 2
		Dialogue.NewDialogue("HQ I repeat, Tactical airstrike in bound, we need air support! ", {["TxtFunctionList"] = FunctionListTemplate}, Characters["Character2"], {["AnimationPose"] = "rbxassetid://16265865392"})
		if Dialogue.DialogueWait(5, true) then return end -- 5
		Dialogue.NewDialogue("Uhm...", {["TxtFunctionList"] = FunctionListTemplate}, Characters["Character1"], {["AnimationPose"] = "rbxassetid://16265869308"})


		local Mad = Settings.TextSettings()
		Mad.TxtColor = Color3.fromRGB(255, 67, 70)
		local ChoicesText = {
			{
				["ChoiceTxt"] = "Go back to sleep.",
				["OutputKey"] = "Sleep",
				["TxtSettings"] = Mad,
			},
			{
				["ChoiceTxt"] = "Help an ally!",
				["OutputKey"] = "War",
			},
		}
		
		Dialogue.GenerateChoices(ChoicesText, 3)
		Dialogue.ChoicesTransition("Entrance")

		Dialogue.ChoicesSelection(function(callbackResult)
			if ChoicesText[1]["OutputKey"] == callbackResult then
				Dialogue.ChoicesTransition("Exit")
				Dialogue.ContinueDialogue(" This ain't my problem. I'll just go back to my wonderful nap.",{["TxtFunctionList"] = FunctionListTemplate}, Characters["Character1"], {["AnimationPose"] = "rbxassetid://16265872688"})
				if Dialogue.DialogueWait(2, true) then return end
				Dialogue.NewDialogue("Aaron went back to sleep.",{["TxtFunctionList"] = FunctionListTemplate})
				CurrentlyRunning = false
				db = false
			elseif ChoicesText[2]["OutputKey"] == callbackResult then
				Dialogue.ChoicesTransition("Exit")
				Dialogue.NewDialogue("Aaron stood up and got towards the radio.", {["TxtFunctionList"] = FunctionListTemplate})
				if Dialogue.DialogueWait(3, true) then return end
				Dialogue.NewDialogue("This is HQ, X-Ray 09, we're sending air support right away!",{["TxtFunctionList"] = FunctionListTemplate}, Characters["Character1"], {["AnimationPose"] = "rbxassetid://16265872688"})
				CurrentlyRunning = false
				db = false
			else
				warn("Tried to call nil")
			end
		end)
	end
	if inp.KeyCode == Enum.KeyCode.LeftBracket then
		Reset()
		if Cooldown == false then
			CooldownFunc()
			Reset()
		end
	end


end)
Current Features
  • Responsive UI (This has a custom responsive UI for the texts)
  • Extremely customizable text in dialogue
  • Command based on word/phrase/sentence reference (This can be repeated and set a limit)
  • Supports both 2D and 3D Characters (it can even be a whole group of 3D characters and if it is a humanoid it supports animation poses or full on animations on general)
  • Choices that can branch out
  • Extremely customizable punctuation
  • Can change emotions/pose (Aka it doesn’t do the character opening transition every time you want to change the emotion/pose)
  • Ability to skip dialogue
  • ability to continue the dialogue without resetting the previous dialogue
  • Cross-platform support (including console)

Current System work in progress:

  • Extremely Customizable UI animation/tweening.
8 Likes

Since it has a skip dialogue feature, a prompt that appears that tells you that you can skip would be good.

1 Like

could you elaborate further on how this will function? Thanks

1 Like

I think he meant by adding a reminder (TextLabel) that you can skip the dialogue

1 Like

Hm, maybe I’ll make a signal:boolean if it’s a clear go for the dialogue, as there is an option where the automatic next switch can either be skippable or not, (this is used for multiplayer balance like in those story games but still having the ability to skip the typewriter effect)

This visual novel service looks VERY GOOD! Nice job on it!

2 Likes

Okay, so I’ve kind of remade how it is mostly structured and added the flexible option to change the position of the transition UIs (like the transition for the opening Dialogue box and Choices Box)

For the character frame, I’ve decided to remake how the character will be displayed, now we can put as many characters as we want!

Moreover, I’m starting to make the translator Module which transforms the current structure (an eye-sore) into something more readable.

(also fixed the previous dialogue from being unable to save to fully save due to it being stored as DATA, so it is now possible to go back in time.)

Video:

The Structure of the Dialogue DATA:
local DataTblSetup = {
				[1] = {
					["Saved_ColumnSkip"]: number,
					["Saved_txtPadding"]: number,
					["TxtGenerationCount"]: number,
					["Saved_TextSettings"]: SettingsModule.TextSettings(),
					["Saved_TextData"] = {
						[1] = {
							["Text"]: "Hello,"
							["Saved_TxtEditCmd"]: SettingsModule.TextSettings()
							},
						[2] = {
							["Text"]: " "
							["Saved_TxtEditCmd"]: SettingsModule.TextSettings()
						},
						[3] = {
							["Text"]: "World!"
							["Saved_TxtEditCmd"]: SettingsModule.TextSettings()
						},
					},
					["TxtGenerationCount"]: number,
				},
				[2] = {...}
			}
1 Like

[!Update!]
When creating a new Dialogue, it is now much cleaner compared to last time,
additionally, I added new features and fixed some issues/bugs I’ve encountered.

Video:

Before:

After (This is also the exact same dialogue function call as the video):

Some features I’ve added are more on the quality side.
I’ve added that the CanvasPosition will follow the latest generation of text, you can also detach from this by simply moving up, if you want to go back/revert to the automatic just scroll down until you reach the still generating bottom.]

The Translator Function’s calls:

DialogueTbl:{
				[number]:string | { -- everything in the table does not overwrite anything even with over write boolean is on.
					["Wait"]: number, -- how much time you should wait before skipping.
					["SpeakingMood"]: string, -- the character sprite/mood/animation when talking.
					["NormalSpeed"]: number, -- the speed of the dialogue multiplier. Default: 1
					["ChangeName"]: string, -- This changes the name of the name box.
					["TriggerEvent"]: () -> (), -- this is a trigger event, basically if you want to summon a giant snail. (but if you have like a spell system it is recomend to use the command scheduler so you wont have to repeat doing this again and again.)
				}?
			},
			ShowCharacter:boolean?, -- this option so you don't have to re call a function to show a character.
			NewString_NewLine:boolean?, -- this is the option if you dont want to auto make a new line every time you make a new string.
			NameLabel:string?, -- this will re write the current name available.
			-----------------------------------[This is is the variables that overwrite boolean will overwrite \/]
			DuringMood:string?, -- This is the mood of the character when the dialogue is on. Default: DefaultMood
			LinePadding:number?, -- this is the padding of every line.
			Overwrite:boolean? -- this is a boolean if it should overwrite the data to the data given in this function. Default: false

Mainly everything is on the side where in generation it will do the stuff we can’t do while still on the dialogue mode.

For custom tweens on the character etc, it’ll be in a separate table, and it’ll be the “Defaults” for that character, and of course, you can change it.

Once the Dialogue Module is finished and refined, [1/3] of the Visual Novel Service/builder (whatever you want to call it) is completed.

What are the remaining?
VFX – Aka 2D background/camera manipulation/
Extras – Such as Datastore, Security, etc.

1 Like