You don’t have to. Are you referring to all those scripts under Effects? Refer to the above post I made, the third quoted excerpt. Our cutscene module calls a function common amongst all these ModuleScripts, monitor, which tracks a model and a loaded animation. Then just before we play the animation we hook certain functions to occur when each marker of the animation is reached.
Example: if a “FadeOut” marker exists on any point of the camera’s animation, we apply a fade to our target item (in this case it’s for music).
You can still keep your current setup of using only a single script to control the way your cutscene works as well as your folder of resources relevant to the cutscene, but your best bet is to find a way to make your system event-driven so you don’t have to deal with all the headaches of loops/unable to yield a coroutine outside of itself/how to string all this together.
Okay, I must be some sort of idiot, I still have no idea how I’m meant to go about this. Is what you’re saying that a module script called from a local script that edits something in the explorer will still only edit on the client? (I know that’s not all you’re saying but that’s all that I could get from that.)
LocalScript calls Cutscene module → Cutscene module disables anything that should not be active during the cutscene (Guis, music, etc) → We call another module that lets us track all the cutscene’s resources and its state → This second module runs scripts that help us track all animations involved in an animation and have things happen when markers are reached in an animation → The cutscene is then played, where all animations are ran at one time → The camera’s animation has a special marker, “FadeOut”, that flags the cutscene as “done” and cleans itself up
For skipping, we implement the same logic as the ended flag from the camera’s animation where we just destroy all the active resources and run the finished actions early, camera resets and all.
tl;dr Yes. You understood me correctly.
Anyhow, on another note, I did a bit of looking into your coroutine stuff. If you’ve already been trying to adopt my solution then I hope it’s not my explanations that are confusing or anything. It’s just in trying to answer your question I’m really advocating for you to go with an event-based solution instead because it’s significantly better than trying to bruteforce the loops through.
That being said, pretty sure I’ve thought of a way you can handle that if you really insist and because my own method’s too difficult to understand. Just use a boolean value to end your loops early. At the top around where you declare your counters, declare a flag too like “local skipped = false”. The skip button’s responsibility should just be to set this to true. As for your repeat loops in the coroutines, just add the condition “or skipped == true” after GetChildren.
Don’t think this a full solution though so if that doesn’t work to the fullest extent, you know why, I told you in advance.
So, instead of Tweening the camera, I use an animation that has keyframes at each camera position in the cutscene? Either way, I still don’t really get how I would use the other things without the loop, as the only other way I can think of would be to just have a bunch of waits and then changing things, which isn’t very efficient.
Yes. If you need some more reference as to how it’d look in production, I can offer more reference material. This is a cutscene from our experience. Everything you see is triggered by an event based on the animation. The only loops we use are to set up the cutscene, not to advance it. Advancing is controlled purely by events and natural animation running. It is possible.
So, I can animate the dialogue? How would I do that? Would I have a separate animation for enabling and disabling the dialogue gui? Honestly, I don’t even know how to animate things other than humanoids.
For dialogue we got a little lazy and just added a marker to the camera’s animation for that too lol. The camera is basically the main part of the cutscene that controls everything else and the other NPCs follow along with a high degree of accuracy, if not pinpoint. Nigh parallel if you will.
When a dialogue marker in the camera’s animation is reached, we then send that information to a script that’s responsible for displaying dialogue on the screen (not only cutscenes can trigger dialogue which is why we keep it separate). Just like that. Fades, dialogue and all that are all handled in the exact same manner: as marker events on the camera’s animation.
So, there would be a script somewhere else that writes out the text that is given to it when the animation reaches that keyframe? I do have a moduleScript in ServerScriptService that can do that. Another thing, addMarker() is a built-in function, right? I’ve never made an animation using a script before, I don’t really know anything about it.
Yes, that animateText function passes the arguments over to another module that writes out the text at the bottom of the screen. As for addMarker, that’s a function we created which abstracts away adding keyframe markers to an animation. This is what it’s comprised of:
local function addMarker(markerName, callback)
loadedAnimation:GetMarkerReachedSignal(markerName):Connect(callback)
end
Okay, looks like I’m going to have to do some research on how the heck animations within scripts work, but either way, I should hopefully be able to take it from here, but I just have one last question. How is the moduleScript structured? I only know one way to structure them, but I don’t know if it’s the way I should be.
moduleThing.thisFunction = function(arguments)
--Code goes here
end
Our syntax is kind of wonk and improper, but yeah basically like that, just with colon syntax.
-- Any variables the module needs here
local module = {}
function module:monitor(model, loadedAnimation)
local function addMarker(markerName, callback)
loadedAnimation:GetMarkerReachedSignal(markerName):Connect(callback)
end
-- addMarker called as many times as needed here
end
return module
This module is called by the cutscene module on a new rig as well as an animation we run on it, passed by LoadAnimation.
So there’s just a random animation sitting around that doesn’t do anything except reach keyframes that cause things to happen? Is loadedAnimation said animation, or is it just some empty animation that gets keyframes added? Either way, how would you even name keyframes?
EDIT: Oh yeah, turns out I can’t really take it from there lol.
The animation isn’t random. In the video a bit above as well as from my explanations in posts previously, I mentioned that we animate a dummy object that serves as the camera’s position. All the camera work seen in that video is done by an object representing the camera. The camera’s CFrame is updated to that dummy object to get the screen effect you see.
Beyond that, yes, loadedAnimation is said animation. Each rig (camera and NPCs) each exist as separate entities in the cutscene’s resource folder and have animations played on them after being loaded with LoadAnimation. Before we do that, we require a ModuleScript of the same name as the rig that contains the same boilerplate in my previous post. The rig as well as the loaded animation are then given to the monitor function, then we call Play on the loaded animation.
Well, we’re able to do all this because all of a character’s actions in a cutscene are just one long animation, not several.
Each of those ModuleScripts handles some kind of visual effect in the cutscene; for example there might be some sounds or particles we want to be heard or shown as the cutscene plays. The camera’s module, on the other hand, controls meta properties for the cutscene (dialogue, transitions, background music, finished flag, etc).
We make our animations in Blender and import them into Roblox, since it allows us more flexibility and we can also animate multiple rigs at one time easily. The keyframe markers are added through the Roblox Animation Editor or Moon Animation Suite (we do not name our keyframes, at least anymore). The main idea behind the animation is that you animate the whole cutscene as one cutscene.
Sounds painful? Yeah maybe a bit. It’s definitely saved our team in the long run though for being able to implement nice looking cutscenes in the first place with skipping functionality and everything.