Path3D: A 3d Equivalent to Path2D

Sample Projects

All of these demos are available to download here* (un-copylocked place).

* The (incomplete) witch model is for one of my own projects, so while the code for the first demo is available, the model is not


What is Path3D?

Path3D is a custom, 3d alternative to the existing Path2D instance. With it you can easily create smooth, curved paths, similar to beams. These paths can then be used in animations to control an NPC's movement, used to create paths and other models, or anything else which needs a smoothly curving line.

Overview of the Module

Full documentation of the module can be found here.

The module offers two ways to create a Path3D instance, Path3D.fromPathString() or Path3D.fromAnchorTables(). If you own the editor, Path3D.fromPathString() offers a simpler way to create Path3D instances. Alternatively, Path3D.fromAnchorTables() can be used to create Path3D instances from Vector3’s, offering an alternative if you do not own the editor. For the sake of simplicity, these demos will use Path3D.fromPathString() but details on how to use Path3D.fromAnchorTables() can be found in the documentation.

This code samples demonstrates how you can use Path3D.fromPathString() to create a new Path3D instance.

local Path3D = require(script.Parent.Path3D) --Replace with the path to the module
local PATH_STRING = script.PathString.Value --Assuming a StringValue child

local path = Path3D.fromPathString(PATH_STRING)

From there, you can simply use Path3D:GetCFrameOnCurve() to get points along the path. For example, making a part move smoothly along the path is as simple as this:

local Path3D = require(script.Parent.Path3D)
local PATH_STRING = script.PathString.Value
local PATH_CENTER = Vector3.new(0, 0, 0)
local TRACKING_PART = workspace.TrackingPart

local path = Path3D.fromPathString(PATH_STRING, PATH_CENTER)

local tweenValue = Instance.new("NumberValue")
tweenValue.Value = 0
tweenValue.Changed:Connect(function(newValue)
    trackingPart:PivotTo(path:GetCFrameOnCurve(newValue))
end)

local tweenInfo = TweenInfo.new(
    5,
    Enum.EasingStyle.Linear,
    Enum.EasingDirection.In,
    0,
    false,
    0
)

local tweenGoal = {Value = 1}

local tween = game:GetService("TweenService"):Create(tweenValue, tweenInfo, tweenGoal)
tween:Play()

Additional properties and methods can be found in the module’s documentation found here.


Overview of the Editor

Along with the module, Path3D also comes with an editor to simplify the creation of the path strings. Although not required to use the module, the editor does simplify the process considerably. Below you'll find a quick overview of each of the features.

Settings

Settings

Always Display Paths: When enabled, the pivot and anchor points of paths will always be displayed regardless of whether or not they are actually selected.

Always Display Control Points: When enabled, control points will always be displayed as long as their path is selected or Always Display Paths is enabled, regardless of whether or not their anchor point is selected.

Enable Highlights: When enabled, highlights will displayed on the selected path's pivot point, as well as on the selected anchor and its control points.

Pivot Point Colour: An RGB value determining the colour of pivot points.

Anchor Point Colour: An RGB value determining the colour of anchor points.

Control Point Colour: An RGB value determining the colour of control points.

Path Actions

Path Actions

Create Path: This will create a new path for you to edit.

Insert Anchor Point: This will insert new anchor point after the selected anchor point, or at the end of the path if no anchor is selected.

Add Control Points: This will re-add an anchor's control points if you have previously deleted them (which the module does support).

Separate Control Points: This will separate the anchor's two control points, allowing you to move one without it updating the position of the other.

Link Control Points: This will link the anchor's two control points, causing them to mirror each other across the anchor. Generally, this results in smoother paths.

Link Output Value: In order to use this, you must select a path and a StringValue. When clicked, this will cause the path's output to link to the selected StringValue, resulting in any path string generated with the Generate Path String button to also be copied into that value. This helps so that you don't have to continually dig through scripts looking for which variable held the path string whenever you make a change, and also reduces the clutter too.

Generate Path String: When clicked, this will evaluate the selected path, generating a path string representation of it which will be available in the TextBox above the button, and if an output value is linked, it will also copy it there. The path string in the TextBox will automatically disappear after 5 seconds to ensure you don't accidentally copy an old path string.

Tips

Tips

Known Issues

Known Issues

These almost entirely relate to undo/redo

  • When performing an undo/redo action, you may occasionally get an error message saying something like “Maximum event re-entrancy depth exceeded.” This can be safely ignored, this seems to be caused by undo/redo not properly firing Selection.SelectionChanged, which causes issues for control points mirroring each other. While there are measures in place to reduce the likelihood of this, I have still seen it happen occasionally.
  • Also related to Selection.SelectionChanged not firing properly on an undo/redo, sometimes when performing an action after an undo/redo, you may get a warning saying “Selection became desynced due to undo/redo, please try again.” Simply pressing the button again should resolve this without any other consequences.
  • If you close studio, or start a play test session while a path is selected, that path will not become hidden, so make you deselect any paths before closing studio or starting the game. Unfortunately, Plugin.Unloading does not seem to fire under these circumstances, and I haven’t been able to find an event that does.

As well, the Path3D module also comes with another script which can be disabled with the EnableEditorCleanup attribute. When enabled, this deletes any paths that the editor has created, ensuring they do not appear for players. If you want to access any of the paths at runtime, you can disable this to clean them up yourself (they’re all stored under Workspace._Path3DEditorPaths).


Where to Get Them

The Path3D module is available for free here.
(Unfortunately I’m not able to distribute packages, as soon as I can I will update this to be a package).

The Path3D Editor is available for $4.99USD here. Note that the editor is not required to use the module, as Path3D.fromAnchorTables() provides an alternative constructor.


Closing

If you encounter any problems at all, with the module or with the editor, please let me know so that I can get them fixed as quickly as possible.
26 Likes

I can’t believe this appeared only a day before I decided to look for something like this, excellent work! I actually have a very strong use case for this sort of thing

This module is pretty cool but it feels extremely bloated. Why do you require strings instead of Vectors or CFrame, why is half of the module if statements and type-checking?

I’ll start with this first since this was a no-brainer for me.

Short answer: It makes debugging considerably easier.

Long Answer

Every time I take input from a developer, so really just any parameters passed to the module’s functions, I type check them to make sure that they won’t cause any errors later on in the module’s code. This ensures that should someone make a typo, for example, they type the wrong variable name, and instead of Path3D:GetPositionOnCurve() being passed a number, it gets passed a string from some other random variable. If I didn’t type check this, then when that code ran, they would get an output in the console saying something like “Attempt to perform arithmetic on number and string,” and an error pointing to a line of code within the module, even though the mistake is outside of the module. This isn’t great for debugging, as the error makes it seem like there’s some kind of mistake in the module, and the message doesn’t even remotely relate to the actual error that was made. By type checking all parameters first though, I can throw my own custom errors, pointing to the line of code that actually caused them. In this case, instead of getting something like “Attempt to perform arithmetic on number and string,” you would actually get “Unable to invoke Path3D:GetPositionOnCurve with given t value, expected number, got string,” something that tells you exactly what the mistake was. Not only that, but it also reduces the amount of people mistakenly believing that there’s something wrong with the module, reducing the amount of false bug reports I get.


Additional Context: This was posted before update 1.1.0, and Path3D.fromAnchorTables() did not exist. Since v1.1.0 however, Path3D.fromAnchorTables() is available which uses Vector3’s.

This was a bit more complicated, and I still go back and forth a bit on this.

Short answer: I thought it would make it easier for people to use the module who didn’t own the editor, but I’m open to providing an alternative.

Long Answer

As I said, I’m still going back and forth a bit on this, as I completely agree, using strings does feel a bit weird. In an ideal world, I’d create a new Instance that would exist in the DataModel hierarchy, but to my knowledge that isn’t possible, making any other possible solutions messier. Because of the editor, I needed something that could be generated and then used in multiple places, but I don’t think this really limited it too much. While in hindsight I could have used some form of structure using Vector3 and CFrame values, I wanted to make sure that the module was still easy to use for people who didn’t own the editor, and I think the specific naming and hierarchy of this would’ve been pretty convoluted. Ultimately that left with me with strings, which although not perfect, I tried to keep the formatting of as simple as possible. With that said however, internally the module does use Vector3’s, so if you or others wanted some other constructor that made use of Vector3’s instead, it wouldn’t be too hard for me to implement. I left this out of the module initially because the path strings aren’t too hard to generate from from Vector3’s or CFrames, but if anyone would like this please let me know and I’d be happy to implement it.


On an unrelated note, you seem to have made a few posts on the dev forum, do you have any idea why my links weren't working? I've got one of the broken ones still at the top of the module's section, and it's a direct copy-paste of the one at the end.

Interesting. You’ve done a lot of work here, so I assume it’s performant. I’ll bookmark this page, as I might actually use it. Saves quite a bit of time.

I haven’t actually read properly all the way through your post, so you may have already answered what I’m going to ask; Can you trigger it whenever you like? So say for instance; the second clip, if a player walks out of a blacked-out building right, it plays. When they go in, it stops, and all the moving objects during the Path3D animation get removed (unrendered).

If you’re familiar with how Path2D works, this is the same. The main thing that the module provides is simply Path3D:GetPositionOnCurve(), the animations in the demos are simply me tweening a NumberValue, and then calling Path3D:GetPositionOnCurve() whenever it changes. So in order to stop it, all you would have to do is stop that tween, clean up the NumberValue that was being tweened, and remove whatever part you’re moving. As well, the source code for the demos is available in an un-copylocked place (link right beneath them) if you’d like to check it out more.

Regarding performance, while I haven’t put much thought into it, the module isn’t doing anything complicated. The only thing I would be slightly concerned about is the initial calculation of the Path3D’s length, but that only happens once for each Path3D (and I don’t think that even is a problem).

How am I supposed to make my own path string? It’s basically forcing me to use the plugin

1 Like

Additional Context: This was posted before update 1.1.0, and Path3D.fromAnchorTables() did not exist. Since v1.1.0 however, Path3D.fromAnchorTables() is available which uses Vector3’s.

I’ve got a full breakdown of the formatting of the path string in the documentation, but I’ve been considering an alternative constructor using Vector3’s, so if you’d be interested in that please let me know. Internally the module uses Vector3’s, so it I don’t think it’d be too difficult.

2 Likes

The plugin doesn’t show itself, the script is disabled.

Sorry, could you please elaborate on that, I’m not sure what you mean. Are you having issues with the module, is the editor panel not opening, are you unable to find the editor’s paths folder, something else?

Your plugin’s script is disabled.
Pictures of the plugin inserted into workspace via insertservice after purchasing:
image
image

Sorry about that, should be fixed now. Thank you for letting me know!

1 Like

Path3D v1.1.0

I’ve just released v1.1.0 for the module, with the main additions being Path3D.fromAnchorTables(), Path3D:GetRotationOnCurve(), and Path3D:GetCFrameOnCurve(). A full changelog and further information on each of these methods is available in the documentation.

Path3D.fromAnchorTables()

This method offers an alternative constructor to Path3D.fromPathString() using Vector3’s, making it easier to construct Path3D instances if you don’t own the editor.

Path3D:GetRotationOnCurve()

This method offers a way to get the rotation or tangent on curve, removing the need for any weird “look aheads” and the weird problems those had at t=1.

Path3D:GetCFrameOnCurve()

Similar to Path3D:GetRotationOnCurve(), this bundles together Path3D:GetPositionOnCurve() and Path3D:GetRotationOnCurve() into a single method call, making it easier to have parts that change their orientation to follow the path.

More Too!

There’s also been a couple other minor tweaks which can be found in the changelog.

Closing

As usual, please let me know if you encounter any issues and I'll fix them as quickly as I can. As well, I'm also working on an update to the editor, allowing you to rename, group, and hide paths, as well as a warning if you're using an outdated version of the module, at least until packages on the creator store get released. Enjoy!

Sorry, I just realised I forgot to enable v1.1.0 for distribution on the Creator Store, if you were previously unable to get the latest version, the links in the main post should now be working. Sorry about that!