雨与水花
雨滴的 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)