Skip to content

Instantly share code, notes, and snippets.

@stylemistake
Created August 22, 2022 12:25
Show Gist options
  • Save stylemistake/919e3a9c373777d7bb562601ce9b2734 to your computer and use it in GitHub Desktop.
Save stylemistake/919e3a9c373777d7bb562601ce9b2734 to your computer and use it in GitHub Desktop.
Trail emitter, Godot 4
class_name Trail
extends Node3D
@export var emitting := false
@export_range(0.1, 100, 0.1, 'or_greater') var distance := 1.0
@export_range(0.1, 10, 0.1, 'or_greater') var lifetime := 1.0
@export_range(0.01, 10, 0.01, 'or_greater') var width_scale := 1.0
@export var width_curve: Curve
@export_range(0.01, 1, 0.01) var color_alpha := 1.0
@export var color_gradient: Gradient
@export var fade_based_on_lifetime := true
class Point:
var instance_id := 0
var transform := Transform3D()
var age := 0.0
var _was_emitting := false
var instance_id_counter := 0
var points: Array[Point] = []
var mesh: ImmediateMesh
var mat: StandardMaterial3D
func _ready() -> void:
if not is_instance_valid(color_gradient):
color_gradient = Gradient.new()
color_gradient.set_color(0, Color.WHITE)
color_gradient.set_color(1, Color.WHITE)
mat = StandardMaterial3D.new()
mat.shading_mode = StandardMaterial3D.SHADING_MODE_UNSHADED
mat.transparency = true
mat.vertex_color_use_as_albedo = true
mesh = ImmediateMesh.new()
var node := MeshInstance3D.new()
node.set_name('Geometry')
node.mesh = mesh
add_child(node)
node.top_level = true
node.global_transform = Transform3D()
func _process(delta: float) -> void:
_process_emitting()
_process_point_lifetime(delta)
_render()
func _process_emitting() -> void:
if emitting != _was_emitting:
_was_emitting = emitting
if emitting:
instance_id_counter += 1
if not emitting:
return
var should_create := false
if len(points) == 0:
should_create = true
else:
var last_point := points.back() as Point
should_create = (
last_point.transform.origin
.distance_squared_to(global_transform.origin)
>= distance * distance
)
if should_create:
var point := Point.new()
point.instance_id = instance_id_counter
point.transform = global_transform
point.age = lifetime
points.push_back(point)
func _process_point_lifetime(delta: float) -> void:
if len(points) == 0:
return
for point in points:
point.age -= delta
points = points.filter(func (point: Point): return point.age > 0.0)
func _render() -> void:
if len(points) < 2:
return
var camera := get_viewport().get_camera_3d()
var processing_instance_id := -1
mesh.clear_surfaces()
var points_batch: Array[Point] = []
for point in points:
if point.instance_id != processing_instance_id:
if processing_instance_id != -1:
_render_trail_instance(camera, points_batch)
points_batch.clear()
processing_instance_id = point.instance_id
points_batch.push_back(point)
_render_trail_instance(camera, points_batch)
points_batch.clear()
func _render_trail_instance(camera: Camera3D, points: Array[Point]) -> void:
if len(points) <= 2:
return
var last_point := points.back() as Point
var camera_pos := camera.global_position
mesh.surface_begin(Mesh.PRIMITIVE_TRIANGLE_STRIP, mat)
for i in range(1, len(points)):
var point := points[i]
var prev_point := points[i - 1]
var t := 1.0 - (point.age / lifetime)
var t_relative := Math.scale(t, 1.0 - (last_point.age / lifetime), 1.0)
var half_unit := width_scale * 0.5
if is_instance_valid(width_curve):
half_unit *= width_curve.interpolate(t)
var path_direction := (
(point.transform.origin - prev_point.transform.origin).normalized()
)
var normal := (
(camera_pos - (point.transform.origin + prev_point.transform.origin) / 2)
.cross(path_direction)
.normalized()
)
var color := color_gradient.interpolate(t_relative)
color.a *= color_alpha
if fade_based_on_lifetime:
color.a *= 1.0 - t
mesh.surface_set_color(color)
mesh.surface_set_uv(Vector2(1.0, 0.0))
mesh.surface_add_vertex(point.transform.origin - normal * half_unit)
mesh.surface_set_uv(Vector2(1.0, 1.0))
mesh.surface_add_vertex(point.transform.origin + normal * half_unit)
mesh.surface_end()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment