雨与水花

雨滴的 lifetime 按落差设定,地面线上还有一组 one_shot 发射器负责溅起水花。点击可以自己加一朵。

上方的编辑器正在运行这个项目。改一行代码,它就会重新加载。

用到的 Godot 类

CPUParticles2DTimerImageTexture

代码

scripts/main.gd
extends Node

const RAIN_DIR: Vector2 = Vector2(0.16, 1.0)
const RAIN_SPEED: float = 580.0
const TOP_Y: float = -40.0
const FLOOR_RATIO: float = 0.85
const SPLASH_INTERVAL: float = 0.06
const POOL: int = 12

var splashes: Array[CPUParticles2D] = []
var next_splash: int = 0
var width: float = 0.0
var floor_y: float = 0.0


func _ready() -> void:
	_add_background()

	var view: Vector2 = get_viewport().get_visible_rect().size
	width = view.x
	floor_y = view.y * FLOOR_RATIO

	_add_floor(view)

	var rain: CPUParticles2D = CPUParticles2D.new()
	rain.position = Vector2(view.x * 0.5 - 60, TOP_Y)
	rain.texture = _make_bar(3, 24)
	rain.amount = 320
	# cpu particles report no collision to the script
	rain.lifetime = (floor_y - TOP_Y) / (RAIN_DIR.normalized().y * RAIN_SPEED)
	rain.preprocess = rain.lifetime
	rain.emission_shape = CPUParticles2D.EMISSION_SHAPE_RECTANGLE
	rain.emission_rect_extents = Vector2(view.x * 0.62, 12)
	rain.direction = RAIN_DIR
	rain.spread = 0.0
	rain.gravity = Vector2.ZERO
	rain.initial_velocity_min = RAIN_SPEED
	rain.initial_velocity_max = RAIN_SPEED
	rain.angle_min = rad_to_deg(RAIN_DIR.angle()) - 90.0
	rain.angle_max = rain.angle_min
	rain.scale_amount_min = 0.6
	rain.scale_amount_max = 1.4
	rain.color = Color(0.55, 0.65, 0.95, 0.85)
	add_child(rain)

	var dot: GradientTexture2D = _make_dot()

	for _i in POOL:
		splashes.append(_add_splash(dot))

	var timer: Timer = Timer.new()
	timer.wait_time = SPLASH_INTERVAL
	timer.timeout.connect(_splash_somewhere)
	add_child(timer)
	timer.start()

	print('click anywhere to burst an extra splash on the floor line')


func _unhandled_input(event: InputEvent) -> void:
	if event is InputEventMouseButton and event.pressed:
		_splash_at((event as InputEventMouseButton).position.x)


func _splash_somewhere() -> void:
	_splash_at(randf_range(0.0, width))


func _splash_at(x: float) -> void:
	var splash: CPUParticles2D = splashes[next_splash]
	next_splash = (next_splash + 1) % splashes.size()
	splash.position = Vector2(x, floor_y)
	splash.restart()


func _add_splash(dot: GradientTexture2D) -> CPUParticles2D:
	var splash: CPUParticles2D = CPUParticles2D.new()
	splash.texture = dot
	splash.local_coords = false
	splash.emitting = false
	splash.one_shot = true
	splash.explosiveness = 1.0
	splash.amount = 16
	splash.lifetime = 0.4
	splash.direction = Vector2(0, -1)
	splash.spread = 62.0
	splash.initial_velocity_min = 110.0
	splash.initial_velocity_max = 260.0
	splash.gravity = Vector2(0, 900)
	splash.scale_amount_min = 0.25
	splash.scale_amount_max = 0.7
	splash.color_ramp = _make_ramp(
		PackedFloat32Array([0.0, 0.6, 1.0]),
		PackedColorArray([Color('#e6ecff'), Color('#8da5f3'), Color(0.55, 0.65, 0.95, 0.0)])
	)
	add_child(splash)

	return splash


func _add_floor(view: Vector2) -> void:
	var ground: ColorRect = ColorRect.new()
	ground.color = Color('#2a2f3d')
	ground.position = Vector2(0, floor_y)
	ground.size = Vector2(view.x, view.y - floor_y)
	add_child(ground)

	var edge: ColorRect = ColorRect.new()
	edge.color = Color('#8da5f3')
	edge.position = Vector2(0, floor_y)
	edge.size = Vector2(view.x, 2)
	add_child(edge)


func _make_bar(bar_width: int, bar_height: int) -> ImageTexture:
	var image: Image = Image.create(bar_width, bar_height, false, Image.FORMAT_RGBA8)
	image.fill(Color.WHITE)

	return ImageTexture.create_from_image(image)


func _make_ramp(offsets: PackedFloat32Array, colors: PackedColorArray) -> Gradient:
	var ramp: Gradient = Gradient.new()
	ramp.offsets = offsets
	ramp.colors = colors

	return ramp


func _make_dot() -> GradientTexture2D:
	var gradient: Gradient = Gradient.new()
	gradient.set_color(0, Color(1, 1, 1, 1))
	gradient.set_color(1, Color(1, 1, 1, 0))

	var dot: GradientTexture2D = GradientTexture2D.new()
	dot.gradient = gradient
	dot.fill = GradientTexture2D.FILL_RADIAL
	dot.fill_from = Vector2(0.5, 0.5)
	dot.fill_to = Vector2(1.0, 0.5)
	dot.width = 32
	dot.height = 32

	return dot


func _add_background() -> void:
	var background: ColorRect = ColorRect.new()
	background.color = Color('#12141a')
	add_child(background)
	# ColorRect defaults to MOUSE_FILTER_STOP and would eat the click before _unhandled_input
	background.mouse_filter = Control.MOUSE_FILTER_IGNORE
	background.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)

更多粒子示例