Modifying a 3D trail effect to function based on local movement rather than global movement

Godot Version

4.5

Question

I have some code in Godot 4.5 3D that will add a trail effect to an object when the object moves. The problem is, in some cases I don’t want to use the global movement, and instead want to use the movement relative to the parent object. For example on a sword object, I want the trail to follow the movement of the sword itself when it’s swinging, but not be affected by the movement of the player. Right now if I swing the sword while moving forward, the player will walk into the trail as it’s being created. If I walk to the right while swinging to the left, the trail left behind will almost be non-existent because the walking and swinging movement cancels each other out. I tried replacing code that uses get_global_transform( ) with get_transform( ) but that doesn’t seem to work. Is there an easy solution for this?

Here is the code:

class_name Trail3D extends MeshInstance3D

var _points = [] # Stores all 3D positions that will make up the trail
var _widths = [] # Stores all calculated widths using the positions of the points
var _lifePoints = [] # Stores all the trail points lifespans

@export var _trailEnabled : bool = true ## Set trail visibility

@export var _fromWidth : float = 0.5 ## Starting width of the trail
@export var _toWidth : float = 0.0 ## End width of the trail
@export_range(0.5, 1.5) var _scaleAcceleration : float = 1.0 ## Speed of the scaling

@export var _motionDelta : float = 0.1 ## Sets the smoothness of the trail, how long it will take for a new trail piece to be made
@export var _lifespan : float = 1.0 ## Sets the duration until this part of the trail is no longer used

@export var _startColor : Color = Color(1.0, 1.0, 1.0, 1.0) ## Starting color of the trail
@export var _endColor : Color = Color(1.0, 1.0, 1.0, 1.0) ## End color of the trail

var _oldPos : Vector3 # Get the previous position

func _ready(): # Set the current position and create the trail mesh
	_oldPos = get_global_transform().origin
	mesh = ImmediateMesh.new() # Mesh used for creating geometry manually
	AppendPoint()

func _process(delta): # Update the trail
	# If the distance between the previous position and the current position is more than the spawn threshhold
	# trails are allowed to be made:
	if (_oldPos - get_global_transform().origin).length() > _motionDelta and _trailEnabled:
		AppendPoint() # Create a new point
		_oldPos = get_global_transform().origin # Update the old position to the current position
	
	# Update the lifespan of every point, remove points when lifespan ends
	var p = 0
	var max_points = _points.size()
	while p < max_points: # Loop through every trail point
		_lifePoints[p] += delta
		if _lifePoints[p] > _lifespan: # If the lifespan of a point has ended, remove it from the list
			RemovePoint(p)
			p -= 1
			if (p < 0): p = 0 # Make sure p isn't set to anything less than zero
		
		max_points = _points.size()
		p += 1
	
	# Remove all surfaces from the mesh
	mesh.clear_surfaces()
	
	#If there are no more than 2 points, return and don't render the trail
	if _points.size() < 2:
		return
	
	# Render a new mesh based on the positions of each point and their current width
	mesh.surface_begin(Mesh.PRIMITIVE_TRIANGLE_STRIP)
	for i in range(_points.size()):
		var t = float(i) / (_points.size() - 1.0)
		var currentColor = _startColor.lerp(_endColor, 1 - t)
		mesh.surface_set_color(currentColor)
		
		var currentWidth = _widths[i][0] - pow(1 - t, _scaleAcceleration) * _widths[i][1]
		
		var t0 = i / _points.size()
		var t1 = t
		
		mesh.surface_set_uv(Vector2(t0,0))
		mesh.surface_add_vertex(to_local(_points[i] + currentWidth))
		mesh.surface_set_uv(Vector2(t1,1))
		mesh.surface_add_vertex(to_local(_points[i] - currentWidth))
	mesh.surface_end() # Set the mesh surface, making it visible

func AppendPoint(): # Set the trail points based on the set width, as well as the lifetime
	_points.append(get_global_transform().origin)
	_widths.append([
		get_global_transform().basis.x * _fromWidth,
		get_global_transform().basis.x * _fromWidth - get_global_transform().basis.x * _toWidth])
	_lifePoints.append(0.0)

func RemovePoint(p): # Remove specified trail point
	_points.remove_at(p)
	_widths.remove_at(p)
	_lifePoints.remove_at(p)

Parent the trail node to player node.

The trail node is already a child of the player node, if that’s what you’re referring to. The hierarchy is:

Player → Camera3D → Weapons_Manager → Sword → Trail_Effect

Then use local transforms instead of global

That’s kinda what I’m asking to begin with, because I’m not sure how to go about that. I’ve tried replacing get_global_transform( ) with get_transform( ) but that just results in the trail not showing up.

Print the positions.