Best approach to animating star trails?

It’s weird that particles don’t work for you, CPUParticles2D should work on all platforms.
Anyway, wanted to try achieve this effect in pure GDScript with elongating trails and dynamic opacity, so here my code:

extends Node2D
@onready var screen_size : Vector2 = get_viewport_rect().size
const SCR_MARGIN = 32 #how far away from screen borders to generate stars on x-axis
const STAR_COUNT = 64
const STAR_TRAIL = 300 #lenght of trail will be multiplied by star_pos.z
const MAX_SPEED : float = 1600.0
var offscreen_y : float #value for checking if star is offscreen on y axis, needs to be recalculated if viewport size changes
var star_pos : PackedVector3Array #array of stars position and speed modifier "z" value
var current_speed : float = 0.0
var delta_accumulator : float = 0.0
const ACCELERATION_TIME : float = 2.0 #time for full acceleration/deceleration in seconds
var speed_ease : float #calculated eased speed modifier
var acceleration_state : float = 1.0 #1.0 accelerating, -1.0 decelerating

func _ready() -> void:
	star_pos.resize(STAR_COUNT)
	offscreen_y = screen_size.y + (STAR_TRAIL * 1.5) #1.5 must be equal or more than max value of star_pos.z
	stars_init()

func _process(delta: float) -> void:
	if Input.is_action_just_pressed("ui_accept"): acceleration_toggle()
	if Input.is_action_just_pressed("ui_cancel"): stars_init()
	delta_accumulator += (delta * acceleration_state)
	speed_ease = ease((delta_accumulator / ACCELERATION_TIME), 2.4) #2.4 feels like best fit
	current_speed = MAX_SPEED * speed_ease
	if !current_speed: return #no need to calculate positions if speed is zero
	for i in star_pos.size():
		var pos_y = star_pos[i].y + (current_speed * star_pos[i].z) * delta
		if pos_y > offscreen_y:
			star_pos[i].y -= offscreen_y
			star_pos[i].x = randf_range(SCR_MARGIN, screen_size.x -SCR_MARGIN)
		else: star_pos[i].y = pos_y
		queue_redraw()

func _draw() -> void:
	if !current_speed: return # no need to draw if speed is zero
	for i in star_pos.size():
		var pos_start = Vector2(star_pos[i].x, star_pos[i].y)
		var pos_end = Vector2(star_pos[i].x, star_pos[i].y - STAR_TRAIL*(speed_ease*star_pos[i].z))
		draw_line( pos_start, pos_end, Color(1.0,1.0,1.0,current_speed/MAX_SPEED), 1.0, false) #antialiasing disabled for performance

func stars_init():
	delta_accumulator = 0.0
	current_speed = 0.0
	acceleration_state = 1.0
	for i in star_pos.size():
		star_pos[i].x = randf_range(SCR_MARGIN, screen_size.x -SCR_MARGIN)
		star_pos[i].y = randf_range(screen_size.y * -1.0, 0.0)
		star_pos[i].z = randf_range(0.5, 1.5)
		
func acceleration_toggle() -> void:
	if delta_accumulator > ACCELERATION_TIME: delta_accumulator = ACCELERATION_TIME
	elif delta_accumulator < 0.0: delta_accumulator = 0.0
	acceleration_state *= -1.0

Controls with default input layout:
ENTER - accelerate/decelerate
ESCAPE- reinitialize stars position

1 Like