xiandie/scene/entity/npc.gd

183 lines
5.2 KiB
GDScript

@tool
class_name Npc2D extends AnimatedSprite2D
signal interacted
# <0 means no walk to edge
@export var snap_to_edge := true
@export var walk_to_edge_width := 25.0
@export var action_key := 4
@export var enabled := true:
set(val):
enabled = val
if is_node_ready():
_align_signs_status()
@export var sign_mark_height := 10.0:
set(val):
sign_mark_height = val
if is_node_ready():
sign_mark.sign_mark_offset.y = -sign_mark_height
@export var speaking_sign_height := 60.0:
set(val):
speaking_sign_height = val
if is_node_ready():
speaking_sign.position.y = -speaking_sign_height
@export var sign_x_offset := 0.0:
set(val):
sign_x_offset = val
if is_node_ready():
speaking_sign.position.x = sign_x_offset
sign_mark.position.x = sign_x_offset
@export var collision_width_and_x := Vector2(20.0, 0):
set(val):
collision_width_and_x = val
if is_node_ready():
var shape = area2d.get_node("CollisionShape2D").shape
shape.size.x = collision_width_and_x.x
area2d.position.x = collision_width_and_x.y
@onready var speaking_animation = %SpeakingAnimationPlayer
@onready var speaking_sign = %SpeakingSign2D as Node2D
@onready var sign_mark = %Sign as Sign
@onready var sign_snapper = %SignSnapper as SignSnapper
@onready var area2d = %Area2D as Area2D
var ground_archive: GroundArchive
# 尝试互动的次数
var icount: int:
set(val):
icount = val
ground_archive.set_pair(name, "icount", val)
_align_signs_status()
var dialogue_title := ""
var dialogue_res = preload("res://asset/dialogue/npc.dialogue")
var base_scale := Vector2.ONE
var base_mod := Color.WHITE_SMOKE
var speaking_sign_tween: Tween
# 0 hide; 1 silent; 2 speaking
var speaking_sign_mode := 0:
set(val):
if speaking_sign_mode != val:
speaking_sign_mode = val
if speaking_sign_tween and speaking_sign_tween.is_valid():
speaking_sign_tween.kill()
speaking_sign_tween = create_tween()
if val == 0:
speaking_sign_tween.tween_property(speaking_sign, "modulate:a", 0.0, 0.3)
elif val == 1:
speaking_sign_tween.tween_property(speaking_sign, "modulate", base_mod, 0.3)
speaking_sign_tween.parallel().tween_property(
speaking_sign, "scale", base_scale, 0.3
)
speaking_animation.play("speaking")
elif val == 2:
speaking_sign_tween.tween_property(speaking_sign, "modulate", Color.WHITE, 0.3)
speaking_sign_tween.parallel().tween_property(
speaking_sign, "scale", base_scale * 1.3, 0.3
)
speaking_animation.play("speaking")
func _ready() -> void:
# sign position
sign_mark.sign_mark_offset.y = -sign_mark_height
speaking_sign.position.y = -speaking_sign_height
sign_mark.position.x = sign_x_offset
speaking_sign.position.x = sign_x_offset
# collisiong shape
var shape = area2d.get_node("CollisionShape2D").shape
shape.size.x = collision_width_and_x.x
area2d.position.x = collision_width_and_x.y
sign_snapper.action_on_arrived = action_key
sign_snapper.radius = walk_to_edge_width
sign_snapper.enabled = snap_to_edge
# 设置 speaking_sign 默认值
base_scale = speaking_sign.scale
base_mod = speaking_sign.modulate
speaking_sign.modulate.a = 0.0
if Engine.is_editor_hint():
# editor 下都显示
speaking_sign.visible = true
speaking_sign.modulate.a = 1.0
speaking_sign.get_node("Sprite2D").position.x = -60.0
speaking_sign.get_node("Sprite2D").frame = 2
sign_mark.display_sign = true
return
# setup default value
ground_archive = ArchiveManager.archive.ground_archive()
icount = ground_archive.get_value(name, "icount", 0)
if snap_to_edge:
sign_snapper.arrived.connect(_on_interacted)
else:
sign_mark.interacted.connect(_on_interacted)
# sign_mark.cancel.connect(_stop_speaking)
sign_mark.toggle_active.connect(_on_toggle_active)
visibility_changed.connect(_on_visibility_changed)
if sprite_frames and animation:
play()
func _on_visibility_changed() -> void:
_align_signs_status()
func _align_signs_status():
sign_mark.enabled = enabled and is_visible_in_tree()
sign_mark.display_sign = icount == 0
speaking_sign.visible = enabled and icount > 0
func _on_toggle_active(activated: bool) -> void:
if activated and speaking_sign_mode == 0:
speaking_sign_mode = 1
elif not activated and speaking_sign_mode > 0:
speaking_sign_mode = 0
func _on_interacted() -> void:
# play dialogue
if dialogue_title:
if GlobalConfig.DEBUG:
print("[" + name + "] call lock")
SceneManager.lock_player(0, action_key)
icount += 1
ground_archive.set_pair(name, "icount", icount)
DialogueManager.show_dialogue_balloon(dialogue_res, dialogue_title)
interacted.emit()
speaking_sign_mode = 2
await DialogueManager.dialogue_ended
speaking_sign_mode = 1
if GlobalConfig.DEBUG:
print("[" + name + "] call lock")
SceneManager.unlock_player()
func _get(property: StringName) -> Variant:
if property == "dialogue_title":
return dialogue_title
return null
func _set(property: StringName, value: Variant) -> bool:
if property == "dialogue_title":
dialogue_title = value
return true
return false
func _get_property_list() -> Array[Dictionary]:
var hint_str = ""
if Engine.is_editor_hint():
hint_str = ",".join(dialogue_res.get_ordered_titles())
return [
{
"name": "dialogue_title",
"type": TYPE_STRING,
"hint": PROPERTY_HINT_ENUM_SUGGESTION,
"hint_string": hint_str
}
]