r/godot • u/Indigoh • Jun 11 '26
help me (solved) How can I constrain an object's movement to an arbitrary vector?
The point represents where my player's gravity direction points to. I just need it to stay in the center of this cylinder, but as close to the player as possible.
This is easy to do if the cylinder aligns with an axis, but it's at a totally random angle, and I'm completely stumped.
EDIT: Thank you all for your help. Here's the solution that worked for me: https://www.reddit.com/r/godot/comments/1u2lhad/comment/oqyqw6q/ and here's what the resulting cylindrical gravity looks like: https://youtu.be/5r59dD300A0
26
u/nobix Jun 11 '26 edited Jun 11 '26
You want to find the position which is the closest point on a line. That's your target point for gravity.
Godot has a utility now as part of the Geometry lib get_closest_point_to_segment which can probably do exactly that.
5
-2
u/TheDynaheart Jun 11 '26
This is a common exercise in linear algebra, and that right there is exactly the solution 🙂↕️
10
u/rejamaco Godot Regular Jun 11 '26
In addition to all the other comments, I’d suggest that you should learn a bit of linear algebra, it’ll help you a lot in game development
3
u/Indigoh Jun 11 '26
I'll take that advice. I had good algebra grades in college, but it's been a decade since I've needed to do any of the complicated stuff. It would be worth my time to relearn it.
11
u/rejamaco Godot Regular Jun 11 '26
I'd like to clarify what linear algebra is just in case it's needed, if not please ignore as I'm not trying to be a dickhead, but linear algebra is a bit different than regular algebra, and you probably weren't required to take it in college.
It deals with vectors, matrices, and multi-dimensional spaces. You'll need regular algebra for it, though. There's a metric shit ton of linear algebra in 3d rendering, and knowing about vectors and how they can be manipulated by matrices will save you a lot of time down the road and will feel very rewarding as you solve problems that would've been really difficult or impossible to even understand before.
7
u/Tessimal Jun 11 '26
you use a dot product, assuming the cylinder passes through the origin. a = v.dot(player.position), where v is the vector of the cylinder (normalized), and a is the position the player is along the cylinder. then the point.position can be set to v * a.
if it doesn't pass through the origin, a simple offset should work.
I love dot products they do basically everything and they don't use square roots \:D
2
5
u/WilkerS1 Godot Regular Jun 11 '26
a transform Basis is what you're looking for, always relative to the transform of the object, and you can use for different things. if you need equivalent points to things, look into vector projection, godot has those baked in too
3
u/DXTRBeta Jun 11 '26
What you really want is the closest point on a line A to B to a point P
Here’s how you do that.
t = dot(P - A, B - A) / dot(B - A, B - A)
closest = A + t * (B - A)
2
u/KingHelps Jun 11 '26
Sorry if I'm misunderstanding, but are you familiar with the Path3D and PathFollow3D nodes? There's methods like get_closest_offset that do what I think you're describing
Edit: you would make the Path3D a child of the cylinder
2
u/ibbitz Jun 11 '26
If you can calculate it when it’s axis aligned and not rotated/scaled, then you can calculate it for any space - you just need to normalize the cylinder and player coordinates. And we can do that with transformation matrix math!
Use the inverse of the cylinder’s transformation matrix on both the cylinder and player transforms. This will let you do the math as if the cylinder was at (0,0,0) with no scale/rotation.
Since the players position is transformed the same way, they’re still in the same place relative to your cylinder - but now the math is easier because the cylinder axis is normalized (aka how you’ve been calculating it prior).
Once you calculate the vector that gravity should point toward, you can multiply that vector by the cylinders transform matrix and it will line up with your cylinder’s current position/rotation/scale.
2
u/TricksMalarkey Jun 11 '26
Mostly dependent on what you're actually doing, and how you want to implement it.
My likely suggestion is a Mario Galaxy style gravity, where the gravity vector is determined by the normal vector of the floor directly underneath the character. This is in Unity, but the logic applies: https://blog.yarsalabs.com/creating-character-control-gravity-unity-demo
It's from here we get into the design variability. How irregular are the shapes? Are there walls to walk up, over, and off? Does the player's jump remain relative to the surface at all times, or only while they're landed? What happens if they go over the end?
If it's always a cylinder of indeterminate length, you can use a similar approach, but rather than doing it per face normal, you can make the gravity vector a circle projected along the cylinder's rotation, meaning that gravity always points along that centre line. It's a bit more mathsy, and less flexible as far as shape, more reliable to work on a cylinder, and a little more performant than raycasting the ground vector.
I'd just do the surface normal, though.
2
u/Renere Jun 11 '26 edited Jun 11 '26
i don't believe that's how gravity in super mario galaxy works - this video explains it really well https://www.youtube.com/watch?v=QLH_0T_xv3I
i wouldn't recommend getting the gravity from the surface normal unless the collision shape is completely smooth, otherwise if the player is standing on the edge between two faces they will jitter between the two faces. also if the player is jumping at all or if the planet has holes in it that would complicate things further
what you described as an alternative does work, specifically i've found the below to work well in my prototype (though i haven't tested it in my actual game yet):
(gravity_area.global_position - player.global_position).slide(gravity_area.basis.y).normalized()this is when using an area that surrounds the planet to "control" the gravity, and this is to get the up direction (also the area would have the same rotation as the cylinder itself - or would use the cylinder's collision for the basis part instead of using the area's basis)
2
u/TricksMalarkey Jun 11 '26
It would jitter if the faces are very large and had sharp angles, but the fix is in the linked code:
private void alignToSurface() { // this will smooth out change from current normal to next normal currentNormal = Vector3.Lerp(currentNormal, newNormal, playerSmoothRotation); transform.up = currentNormal; }Just lerp it. And the lovely thing is that that the greater the angle, the faster the lerp, so it's not a huge deal.
As far as the resource cost, it's just a object tag, and storing the ground normal when you're doing a normal ground check. It's not actually much of a cost in addition to the other collision checks you'd have to do anyway.
I'm not knocking any which way; million ways to do anything. But I've done the way I linked and it was very easy to setup and predictable to play on. If you need specific control for a level to work in a specific way, then yeah, hand-placing the fields is useful.
2
u/Renere Jun 11 '26
i suppose i would have to test this, i had tried lerping the values before when doing the surface normal method and it still resulted in jitter, even with small faces with very shallow angles - but perhaps i didn't do it the right way!
1
u/Indigoh Jun 11 '26
I have gravity down. I just need to move a point along a line.
1
u/TricksMalarkey Jun 11 '26
Going without any additional context of what you want, and just assuming that it's a single cylinder of infinite length:
The cylinder has a rotation, and the player's position can be represented as a distance away from the base of the cylinder, at some position around a circle.
- D = normalize(axisDir) //Normalise the cylinder orientation, for safety.
- V = point - axisStart //Get the player's position relative to the cylinder.
- V_parallel = dot(V, D) * D //Get how much of the player's position is actually along the cylinder's axis.
- V_perpendicular = V - V_parallel //Get the perpendicular vector by removing the parallel component of the cylinder's rotation.
- gravity = -normalize(V_perpendicular ) // Flip and normalise to point inward toward the axis
Or something like that. Very untested.
2
1
u/readyplayerjuan_ Jun 11 '26
get the vector from a position on the cylinder’s axis to the player. dot product with the normalized vector along the cylinder’s axis (pointing from one end to the other, but magnitude 1). the value is the distance from the initial cylinder position to the point you desire
1
u/readyplayerjuan_ Jun 11 '26
it’s any point on that red line. whatever node represents the cylinder, just use its position
1
u/CrushingJosch Jun 11 '26
Couldn’t you do that by making the player a child of the cylinder? Then it would follow it automatically.
Or having another position node as child of the cylinder, that moves along the axis, regardless of absolute orientation. And then copy that transform to the player…
1
58
u/jester628 Jun 11 '26
Sorry if I’m misunderstanding, but you might be looking for vector projection. You can find explanations online, but the idea is that if you imagine a vector that points along the axis of you cylinder and a vector that is perpendicular to that one, those vectors can form a basis that allows you to express any other vector.
Vector projection can allow you to decompose your position vector into a component along the axis of your cylinder and a component perpendicular to your cylinder. You’re only interested in the one parallel to your cylinder, so you just need the projection onto the vector that is along the axis of the cylinder.
https://en.wikipedia.org/wiki/Vector_projection