184 lines
5.7 KiB
GDScript
184 lines
5.7 KiB
GDScript
@tool
|
||
extends Node2D
|
||
|
||
@export var enabled := true
|
||
# 老鼠活动区域
|
||
@export var action_area := Vector2(200, 30):
|
||
set(val):
|
||
action_area = val
|
||
queue_redraw()
|
||
@export var gizmo_outline_color := Color(0.565, 0.271, 0.318, 0.8):
|
||
set(val):
|
||
gizmo_outline_color = val
|
||
queue_redraw()
|
||
@export var move_speed := 70.0
|
||
@export var distance_to_player_range := Vector2(50, 270)
|
||
@export var random_wait_time_range := Vector2(0.5, 1.0)
|
||
@export var random_wait_possibility := 0.4
|
||
@export var random_mov_distace_range := Vector2(20, 50)
|
||
@export var scatter_on_start := false
|
||
@export var debug_scatter := false:
|
||
set(val):
|
||
debug_scatter = false
|
||
if Engine.is_editor_hint():
|
||
_init_mice(true)
|
||
|
||
var mice = []
|
||
# 大于 0 时等待,小于 0 时移动,abs()<0.1 时重新生成时间
|
||
var random_wait_time: Array[float] = []
|
||
var random_directions: Array[Vector2] = []
|
||
var random_positions: Array[Vector2] = []
|
||
var player
|
||
|
||
|
||
func _ready() -> void:
|
||
if Engine.is_editor_hint():
|
||
queue_redraw()
|
||
_init_mice()
|
||
visibility_changed.connect(_on_visibility_changed)
|
||
|
||
|
||
func _on_visibility_changed() -> void:
|
||
if visible and is_node_ready() and not Engine.is_editor_hint():
|
||
_init_mice()
|
||
|
||
|
||
func _enter_tree() -> void:
|
||
if is_node_ready() and not Engine.is_editor_hint():
|
||
_init_mice()
|
||
|
||
|
||
func flush_right_and_disable(_res = null) -> void:
|
||
enabled = false
|
||
var tween = create_tween()
|
||
for i in range(mice.size()):
|
||
var m = mice[i]
|
||
m.flip_h = true
|
||
if i == 0:
|
||
tween.tween_property(m, "position:x", m.position.x + 10000, 100.0)
|
||
else:
|
||
tween.parallel().tween_property(m, "position:x", m.position.x + 10000, 100.0)
|
||
|
||
|
||
func _init_mice(scatter := scatter_on_start) -> void:
|
||
mice.clear()
|
||
for c in get_children():
|
||
if c is Node2D:
|
||
mice.append(c)
|
||
random_wait_time.resize(mice.size())
|
||
random_directions.resize(mice.size())
|
||
random_positions.resize(mice.size())
|
||
for i in range(mice.size()):
|
||
var m = mice[i]
|
||
# clamp to action area
|
||
if scatter:
|
||
var pos = Vector2(randf() * action_area.x, randf() * action_area.y)
|
||
m.position = pos
|
||
random_positions[i] = m.position
|
||
random_directions[i] = Vector2.ONE
|
||
random_wait_time[i] = 0
|
||
|
||
|
||
func _physics_process(delta: float) -> void:
|
||
if Engine.is_editor_hint() or not enabled or not visible:
|
||
return
|
||
if not player:
|
||
player = SceneManager.get_player()
|
||
if not player:
|
||
return
|
||
var player_global_position = player.global_position
|
||
# clamp to action area, 允许稍微大一点,防止卡位
|
||
player_global_position.x = clampf(
|
||
player_global_position.x, global_position.x - 2, global_position.x + action_area.x + 4
|
||
)
|
||
for i in range(mice.size()):
|
||
var m = mice[i]
|
||
var m_pos = m.position
|
||
var m_global_pos = m.global_position
|
||
var squared_distance = m_global_pos.distance_squared_to(player_global_position)
|
||
var facing_right := true
|
||
var direction = m_global_pos.direction_to(player_global_position)
|
||
if squared_distance < pow(distance_to_player_range.x, 2):
|
||
# move away
|
||
facing_right = direction.x < 0
|
||
if facing_right:
|
||
m.position.x += move_speed * delta
|
||
else:
|
||
m.position.x -= move_speed * delta
|
||
m.position.y -= direction.y * move_speed * delta
|
||
random_wait_time[i] = -0.5 # 进入运动状态
|
||
random_positions[i] = m.position
|
||
elif squared_distance > pow(distance_to_player_range.y, 2):
|
||
# move closer
|
||
facing_right = direction.x > 0
|
||
if facing_right:
|
||
m.position.x += move_speed * delta
|
||
else:
|
||
m.position.x -= move_speed * delta
|
||
m.position.y += direction.y * move_speed * delta
|
||
random_wait_time[i] = -0.5 # 进入运动状态
|
||
random_positions[i] = m.position
|
||
else:
|
||
if abs(random_wait_time[i]) < 0.1:
|
||
random_wait_time[i] = (
|
||
random_wait_time_range.x
|
||
+ randf() * (random_wait_time_range.y - random_wait_time_range.x)
|
||
)
|
||
if randf() > random_wait_possibility:
|
||
random_wait_time[i] *= -1
|
||
# 一半概率等待,一半概率移动
|
||
elif random_wait_time[i] > 0:
|
||
random_wait_time[i] -= delta
|
||
# random move, 每次移动后随机生成新的目标位置,避免老鼠在原地打转
|
||
else:
|
||
random_wait_time[i] += delta
|
||
var target = random_positions[i]
|
||
if target.distance_squared_to(m_pos) < 9:
|
||
direction.y = randf_range(-0.5, 0.5)
|
||
var x_diff = player_global_position.x - m_global_pos.x
|
||
var diff_sign = signf(x_diff)
|
||
# 大概率向 distance_to_player_range 的中间移动
|
||
x_diff = (
|
||
x_diff
|
||
- (
|
||
diff_sign
|
||
* (distance_to_player_range.x + distance_to_player_range.y)
|
||
* 0.5
|
||
)
|
||
)
|
||
# 75% 的概率向 x_diff 移动
|
||
if signf(x_diff) > 0:
|
||
direction.x = 1 if randf() < 0.7 else -1
|
||
else:
|
||
direction.x = -1 if randf() < 0.7 else 1
|
||
random_directions[i] = direction
|
||
var random_dis = (
|
||
random_mov_distace_range.x
|
||
+ randf() * (random_mov_distace_range.y - random_mov_distace_range.x)
|
||
)
|
||
target = direction * random_dis + m_pos
|
||
# clamp to action area,稍小于 action_area,避免老鼠在边缘打转
|
||
# random_positions[i] = target.clamp(Vector2(2, 2), action_area - Vector2(4, 4))
|
||
random_positions[i] = target.clamp(Vector2.ZERO, action_area)
|
||
facing_right = target.x > m_pos.x
|
||
var delta_max = move_speed * delta
|
||
m_pos = m.position
|
||
m.position.x = move_toward(m_pos.x, target.x, delta_max)
|
||
m.position.y = move_toward(m_pos.y, target.y, delta_max)
|
||
# set flip h
|
||
m.flip_h = not facing_right
|
||
# clamp to action area
|
||
m.position = m.position.clamp(Vector2.ZERO, action_area)
|
||
|
||
|
||
func _draw() -> void:
|
||
if Engine.is_editor_hint():
|
||
# draw gizmo: 老鼠活动区域
|
||
var action_area_rect = Rect2(Vector2.ZERO, action_area)
|
||
# fill
|
||
var fill_color = gizmo_outline_color
|
||
fill_color.a = 0.4
|
||
draw_rect(action_area_rect, fill_color)
|
||
# outline
|
||
draw_rect(action_area_rect, gizmo_outline_color, false, 1.0)
|