Godot Version
3.5
Question
`
-
I made a custom ribbon emitter for godot 3.5 because it doesn’t have one built in.
-
It works fine except for when the emitter is made a child of child. When this happens it’s coordinates start getting mirrored off global origin. I’m not a programmer by trade so I’m super confused on how my coordinates get flipped.
*The emitter works in conjunction with an immediategeometry node. The emitter releases a breadcrumb of reference nodes and instructs the immediategeometry to draw vertex pairs on the reference nodes.
*Is there something wrong with how I’m using global_transform to create the nodes?
`
extends Spatial
#Parent Node Reference
onready var Parent_Node = get_parent()
Ribbon Generator Tells The Immediate Geometry RibbonMesh to draw itself
RibbonMesh has code to make itself top level so as to draw at global coordinates
onready var ribbonMesh = $RibbonMesh
These reference nodes are instanced to create positions for the ribbon node’s vertices
export (PackedScene) var ref_node
#Parameters listed here and exported to inspector
export(float, 0, 9999) var segment_width := .5
export(int, 1, 9999) var segments := 16
export(float, .000001, 10) var lifetime := .05
export(Gradient) var gradient_color
export(bool) var head_faces_direction
#Player Engine Color Parameters
#var engine_color = GlobalStats.g_custom_engine_color
#Default Gradient to Prevent Errors
var defaultGradient: Gradient
#Ties lifetime and segments together to behave like a particle emitter.
#Removal Timer is based on lifetime
var referenceNodeRemovalInterval := lifetime
var referenceNodeCreationInterval := lifetime / segments
#Container to track referenceNodes
var referenceNodes: Array =
#Variables to track timers
var referenceNodeCreationTimer: Timer
var referenceNodeRemovalTimer: Timer
func _ready() → void:
#set_as_toplevel(true)
#Refresh to load gradient
gradient_color = gradient_color
#Test#
#Load lifetime values from inspecter
referenceNodeRemovalInterval = lifetime
referenceNodeCreationInterval = lifetime / segments
#Create first two constant reference points
#call_deferred(“addReferenceObject”)
#call_deferred(“addReferenceObject”)
#Create and start timers
referenceNodeCreationTimer = Timer.new()
add_child(referenceNodeCreationTimer)
referenceNodeCreationTimer.one_shot = false
referenceNodeCreationTimer.wait_time = referenceNodeCreationInterval
referenceNodeCreationTimer.connect("timeout", self, "addReferenceObject")
referenceNodeCreationTimer.start()
#Create and start deletion timer
referenceNodeRemovalTimer = Timer.new()
add_child(referenceNodeRemovalTimer)
referenceNodeRemovalTimer.one_shot = false
referenceNodeRemovalTimer.wait_time = referenceNodeRemovalInterval
referenceNodeRemovalTimer.connect("timeout", self, "removeReferenceObject")
referenceNodeRemovalTimer.start()
#Default Graident FallBack
defaultGradient = Gradient.new()
defaultGradient.add_point(0, Color(1,0,0))
defaultGradient.add_point(1, Color(0,1,0))
#Load the gradient
if gradient_color == null:
gradient_color = defaultGradient
#This makes the reference nodes
func addReferenceObject() → void:
#Reference Node Limiter. Will determine segment numbers
if referenceNodes.size() >= segments:
return
#Creates it’s own reference node
var referenceNode = Spatial.new()
if referenceNodes.empty():
#Insert at index 0 if the array is empty
referenceNodes.insert(0, referenceNode)
add_child(referenceNode)
#get_tree().get_root().add_child(referenceNode)
#referenceNode.transform = transform
else:
#Insert at index 1 if array is not empty
referenceNodes.insert(1, referenceNode)
#Get position of parent
referenceNode.global_transform = referenceNodes[0].global_transform
get_tree().get_root().add_child(referenceNode)
#Command to Remove Reference Node
func removeReferenceObject() → void:
if referenceNodes.size() > 2:
var referenceNode = referenceNodes.pop_back()
referenceNode.queue_free()
func _physics_process(_delta: float) → void:
#Inherit Parent transform
#global_rotation = Parent_Node.global_rotation
if head_faces_direction:
update_reference_node()
create_plane()
func create_plane():
# Clear the ribbon mesh and begin drawing
ribbonMesh.clear()
ribbonMesh.begin(Mesh.PRIMITIVE_TRIANGLE_STRIP)
var width_offset = segment_width / 2.0
var vertices =
var numReferenceNodes = referenceNodes.size()
# Check if there are at least 2 reference nodes
if numReferenceNodes >= 2:
for i in range(numReferenceNodes):
# Get the current reference node and its position
var referenceNode = referenceNodes[i]
#var position = referenceNode.transform.basis
var position = referenceNode.global_transform.origin
# Calculate the rotation offset
#var rotation_offset = referenceNode.transform.basis.xform(Vector3(width_offset, 0, 0))
var rotation_offset = referenceNode.global_transform.basis.xform(Vector3(width_offset, 0, 0))
# Add vertices with accounting for thickness
vertices.append(position - rotation_offset)
vertices.append(position + rotation_offset)
for i in range(0, vertices.size(), 2):
var vertexIndex = i
#Calculate color
var interpolationFactor = float(vertexIndex) / float(numReferenceNodes)
var prev_interpolationFactor = float(vertexIndex-2) / float(numReferenceNodes)
var color1 = gradient_color.interpolate(interpolationFactor)
var color2 = gradient_color.interpolate(prev_interpolationFactor)
var color = color2.linear_interpolate(color1, .5)
#set color and add vertices
ribbonMesh.set_color(color)
ribbonMesh.add_vertex(vertices[i])
ribbonMesh.add_vertex(vertices[i + 1])
ribbonMesh.end()
func update_reference_node() → void:
#This makes ribbon face direction of movement
if referenceNodes.size() >= 2:
#Tracks current position and previous position
var origin = referenceNodes[0].global_transform.origin
var goal = referenceNodes[1].global_transform.origin
#Tracks if player is standing still, preventing an error.
if origin != goal:
var direction = (origin - goal).normalized()
#Calculate rotation based on direction
var rotation = Basis(Vector3(0, atan2(direction.x, direction.z), 0))
#Set Rotation Directionly
#referenceNodes[0].transform.basis = rotation
referenceNodes[0].global_transform.basis = rotation
#var dot_product = direction.dot(Vector3.UP)
#referenceNodes[0].look_at(origin + direction, Vector3.UP)
else:
# Handle the case when the direction vector is too small
referenceNodes[0].rotation_degrees = Vector3.ZERO
#func apply_player_engine_color() ->void: