xiandie/scene/entity/ux/刮刮乐.gd

173 lines
4.9 KiB
GDScript

@tool
extends TextureRect
signal shaven(progress: float)
signal key_area_shaven(progress: float)
@export var brush_width := 9
@export var key_area := Rect2i():
set(val):
key_area = val
queue_redraw()
# points inside radius 9 circle(必须>5)
var brush_points: PackedVector2Array
var area_size := Vector2.ZERO
var image: Image
# bit_mask is the bottom
var bit_mask_key_area: BitMap
var bit_mask := BitMap.new()
var key_area_total_pixels := 0
var total_pixels := 0
# 性能优化相关变量
var update_timer := 0.0
var update_interval := 0.02 # 约50fps
var pending_update := false
func _ready() -> void:
if Engine.is_editor_hint():
return
brush_points = generate_brush_points(brush_width)
if not texture:
printerr("texture not found")
return
area_size = texture.get_size()
image = texture.get_image()
# 确保图像具有alpha通道
if image.get_format() != Image.FORMAT_RGBA8:
image.convert(Image.FORMAT_RGBA8)
texture = ImageTexture.create_from_image(image)
# 创建 bit_mask
bit_mask.create_from_image_alpha(image)
total_pixels = bit_mask.get_true_bit_count()
# 创建 bit_mask_key_area
if key_area.size.x > 0 and key_area.size.y > 0:
bit_mask_key_area = BitMap.new()
bit_mask_key_area.create(area_size)
# clamp key_area
key_area.position = key_area.position.clamp(Vector2.ZERO, area_size - Vector2.ONE)
key_area.end = key_area.end.clamp(key_area.position, area_size - Vector2.ONE)
# 只保留 key_area 区域中的 bit_mask_key_area
for y in range(key_area.position.y, key_area.end.y):
for x in range(key_area.position.x, key_area.end.x):
var pos = Vector2i(x, y)
if bit_mask.get_bitv(pos):
bit_mask_key_area.set_bitv(pos, true)
key_area_total_pixels = bit_mask_key_area.get_true_bit_count()
if GlobalConfig.DEBUG:
prints("key_area after clamp:", key_area, "area_size:", area_size, "pixels:", key_area_total_pixels)
# a solid circle brush, center = (0, 0)
func generate_brush_points(radius: int) -> PackedVector2Array:
var points := PackedVector2Array()
# Top points
for x in range(-radius, radius + 1):
for y in range(-radius, radius + 1):
# If point is within circle radius
if x * x + y * y <= radius * radius:
points.append(Vector2(x, y))
# mid points
radius = int(radius / 3.0)
for x in range(-radius, radius + 1):
for y in range(-radius, radius + 1):
# If point is within circle radius
if x * x + y * y <= radius * radius:
points.append(Vector2(x, y))
# bottom points
points.append(Vector2(0, 0))
points.append(Vector2(1, 0))
points.append(Vector2(0, 1))
points.append(Vector2(1, 1))
return points
var last_pos := Vector2(-1, -1)
func _process(delta: float) -> void:
if pending_update:
update_timer += delta
if update_timer >= update_interval:
_perform_texture_update()
update_timer = 0.0
func _shave() -> void:
if not mouse_pressing:
return
var mouse_pos := get_local_mouse_position()
# 添加距离检查,避免重复处理相同位置
if last_pos.distance_to(mouse_pos) < 1.0:
return
last_pos = mouse_pos
var updated = false
# 批量处理像素
for point in brush_points:
var pos = mouse_pos + point
if pos.x < 0 or pos.x >= area_size.x or pos.y < 0 or pos.y >= area_size.y:
continue
if bit_mask.get_bitv(pos):
updated = true
var color = image.get_pixelv(pos)
color.a = clampf(color.a - 0.15, 0.0, 1.0)
if color.a == 0.0:
bit_mask.set_bitv(pos, false)
if bit_mask_key_area:
bit_mask_key_area.set_bitv(pos, false)
image.set_pixelv(pos, color)
if updated:
pending_update = true
func _perform_texture_update() -> void:
if pending_update:
(texture as ImageTexture).update(image)
_send_signal()
pending_update = false
func _send_signal() -> void:
if total_pixels > 0:
var true_bit_count = bit_mask.get_true_bit_count()
var progress = 1.0 - float(true_bit_count) / total_pixels
shaven.emit(progress)
if key_area_total_pixels > 0:
var key_area_true_bit_count = bit_mask_key_area.get_true_bit_count()
var key_area_progress = 1.0 - float(key_area_true_bit_count) / key_area_total_pixels
key_area_shaven.emit(key_area_progress)
var mouse_pressing = false
# 可以刮出区域
func _gui_input(event: InputEvent) -> void:
if Engine.is_editor_hint():
return
if not is_visible_in_tree():
return
if event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
mouse_pressing = true
# 重置位置以确保首次点击生效
last_pos = Vector2(-1, -1)
_shave()
elif event.button_index == MOUSE_BUTTON_LEFT and not event.pressed:
mouse_pressing = false
# 立即执行最后的更新
if pending_update:
_perform_texture_update()
accept_event()
elif event is InputEventMouseMotion and mouse_pressing:
_shave()
accept_event()
func _draw() -> void:
# draw key area if exists
if Engine.is_editor_hint() and key_area.size.x > 0 and key_area.size.y > 0:
draw_rect(key_area, Color.RED, false, 2.0)