Godot4实现滑动列表和鱼眼效果

滑动列表代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
extends Control

# 必须要有一个control子集包含卡片列表
@onready var control = get_child(0)

# 获取卡片列表
var card_list = func() -> Array[Node]:return control.get_children(false)

# 卡片有多少
var card_len = func() -> int:return len(card_list.call())

############### EXPORT ###############

# 滑动方向(不要运行时更改此变量)
@export_enum("Horizontal","Vertical") var slide_direction = 0

# Card的间隔(至少为80)
@export var card_separation : float = 80.0

# 明度变化
@export var dynamic_brightness : bool = false

# 缩放变化
@export var dynamic_scale : bool = false

######################################

# 界面基准位置
var interface_center : Callable = func() -> Vector2:return size / 2

func _ready():
control.connect("child_entered_tree",func(node : Node):
if !card_list.call().is_empty() and node is Control:
if slide_direction == 0:
node.position.x = current_card.position.x + (node.size.x + card_separation) * (card_len.call()-1)
else:
node.position.y = current_card.position.y + (node.size.y + card_separation) * (card_len.call()-1)
)
connect("child_exiting_tree",func(node : Node):)
await Engine.get_main_loop().process_frame
await Engine.get_main_loop().process_frame
current_card.connect("gui_input",
func(event : InputEvent):
if event is InputEventScreenTouch and event.is_pressed():
var wheel = FortuneWheel.new()
print(wheel.spin_batch([0.6,0.3,0.1],5))
)

# 当前卡片变化信号
signal current_card_changed(current_card : Node)
# 当前card
var current_card : Node:
set(v):
current_card = v
if current_card != v:
current_card_changed.emit(current_card)

# 插值目标值
var target_x : float
var target_y : float
func _process(delta):
var node_x_arr : Array
var node_y_arr : Array
# [位置,Node]
if slide_direction == 0:
for node in card_list.call():
node_x_arr.append([node.global_position.x + node.size.x / 2.0,node])
else:
for node in card_list.call():
node_y_arr.append([node.global_position.y + node.size.y / 2.0,node])
# 筛选最近的卡片 并实时赋值当前选中的卡片current_card
if slide_direction == 0:
node_x_arr.sort_custom(
func(a,b):
if abs(a[0] - interface_center.call().x) < abs(b[0] - interface_center.call().x):
return true
return false
)
current_card = node_x_arr[0][1]
else:
node_y_arr.sort_custom(
func(a,b):
if abs(a[0] - interface_center.call().y) < abs(b[0] - interface_center.call().y):
return true
return false
)
current_card = node_y_arr[0][1]

##### 特效区 #####
if slide_direction == 0:
for item in node_x_arr:
# 生成动态基准参考值
var s_value : float = 1.0 - percentage(0.0,card_separation * 10.,abs(item[0] - interface_center.call().x))
if s_value >= 0.95:
s_value = roundf(s_value)
# 控制明度
if dynamic_brightness:
item[1].modulate.r = clampf(s_value,0.3,1.0)
item[1].modulate.g = clampf(s_value,0.3,1.0)
item[1].modulate.b = clampf(s_value,0.3,1.0)
# 控制缩放
if dynamic_scale:
item[1].scale.x = clampf(s_value,0.8,1.0)
item[1].scale.y = clampf(s_value,0.8,1.0)
else:
for item in node_y_arr:
# 生成动态基准参考值
var s_value : float = 1.0 - percentage(0.0,card_separation * 10.,abs(item[0] - interface_center.call().y))
if s_value >= 0.95:
s_value = roundf(s_value)
# 控制明度
if dynamic_brightness:
item[1].modulate.r = clampf(s_value,0.3,1.0)
item[1].modulate.g = clampf(s_value,0.3,1.0)
item[1].modulate.b = clampf(s_value,0.3,1.0)
# 控制缩放
if dynamic_scale:
item[1].scale.x = clampf(s_value,0.8,1.0)
item[1].scale.y = clampf(s_value,0.8,1.0)

## 最终插值
if !Input.is_action_pressed("mouse_left"):
if slide_direction == 0:
target_x = (-current_card.position.x) + (interface_center.call().x) - (card_separation * 1.5)
control.position.x = lerp(control.position.x, target_x,0.4)
else:
target_y = (-current_card.position.y) + (interface_center.call().y) - (card_separation * 1.)
control.position.y = lerp(control.position.y, target_y,0.4)

func _gui_input(event):
if event is InputEventScreenDrag:
if slide_direction == 0:
var velocity : float = percentage(0.0,card_separation,abs(current_card.global_position.x + current_card.size.x / 2.0 - interface_center.call().x)) / 4
velocity = clampf(velocity,0.4,1.0)
control.position.x += (event.relative.x * velocity) * 2
else:
var velocity : float = percentage(0.0,card_separation,abs(current_card.global_position.y + current_card.size.y / 2.0 - interface_center.call().y)) / 4
velocity = clampf(velocity,0.4,1.0)
control.position.y += (event.relative.y * velocity) * 2
pass

static func percentage(min : float,max : float,value : float) -> float:
return float(value - min) / float(max - min)

鱼眼着色器代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
shader_type canvas_item;

uniform float coeff : hint_range(0, .5);
uniform sampler2D SCREEN_TEXTURE : hint_screen_texture, filter_linear_mipmap;

void fragment(){
vec2 suv = SCREEN_UV;
float side = (SCREEN_UV.y * 2.0) - 1.0;
float mountain = -abs((SCREEN_UV.x * 2.0) - 1.0) + 1.0;
mountain = mountain * PI/2.0;
float newv = coeff * sin(mountain);
suv.y += ((newv * side) - (coeff*side));

//updates the texture
COLOR = texture(SCREEN_TEXTURE, suv);
}

总结

效果如下:

Untitled