Created
August 22, 2022 12:25
-
-
Save stylemistake/919e3a9c373777d7bb562601ce9b2734 to your computer and use it in GitHub Desktop.
Trail emitter, Godot 4
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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