точно есть дэшм
This commit is contained in:
@@ -0,0 +1,224 @@
|
||||
extends CharacterBody3D
|
||||
|
||||
@export var move_speed: float = 4.0 # Скорость преследования игрока
|
||||
@export var damage_to_player: float = 10.0 # Базовый урон за один выстрел
|
||||
@export var attack_cooldown: float = 1.0 # Интервал стрельбы ПОСЛЕ первого выстрела (в секундах)
|
||||
@export var vision_range: float = 25.0 # Радиус чутья моба (в метрах)
|
||||
|
||||
# --- НАСТРОЙКА ЗАДЕРЖКИ ПЕРЕД ВЫСТРЕЛОМ ---
|
||||
@export var lock_on_time: float = 2.5 # Время на прицеливание перед выстрелом (в секундах)
|
||||
var aiming_timer: float = 0.0 # Текущее накопленное время прицеливания
|
||||
|
||||
# --- НАСТРОЙКА УНИКАЛЬНОЙ МУЗЫКИ ДЛЯ МОБА ---
|
||||
@export var custom_battle_music : AudioStream
|
||||
|
||||
# --- 🖼 ВЫБОР КАРТИНКИ ВСПЫШКИ В ИНСПЕКТОРЕ ---
|
||||
@export var muzzle_flash_texture : Texture2D
|
||||
|
||||
# Переменные здоровья (работают напрямую, как у мишени)
|
||||
@export var health: float = 100.0
|
||||
var isDisabled: bool = false
|
||||
|
||||
var player_node: Node = null
|
||||
var is_player_in_range: bool = false
|
||||
var is_currently_seeing_player: bool = false
|
||||
|
||||
# Динамический узел вспышки, который код создаст сам
|
||||
var dynamic_flash_sprite : Sprite3D
|
||||
|
||||
# Таймер перезарядки оружия
|
||||
var attack_timer: float = 0.0
|
||||
|
||||
# Сила гравитации
|
||||
var gravity: float = ProjectSettings.get_setting("physics/3d/default_gravity")
|
||||
|
||||
func _ready() -> void:
|
||||
if not is_in_group("Enemies"):
|
||||
add_to_group("Enemies")
|
||||
print("[ВРАГ] Тактический моб со зрением через укрытия запущен. Здоровье: ", health)
|
||||
|
||||
# АВТО-СОЗДАНИЕ И НАСТРОЙКА КАРТИНКИ ВСПЫШКИ
|
||||
if muzzle_flash_texture != null:
|
||||
dynamic_flash_sprite = Sprite3D.new()
|
||||
dynamic_flash_sprite.texture = muzzle_flash_texture
|
||||
dynamic_flash_sprite.visible = false
|
||||
dynamic_flash_sprite.billboard = BaseMaterial3D.BILLBOARD_ENABLED # Поворот на игрока
|
||||
|
||||
# Пытаемся прикрепить к кончику ствола оружия, если есть контейнер, иначе к центру моба
|
||||
if has_node("WeaponManager/WeaponContainer"):
|
||||
get_node("WeaponManager/WeaponContainer").add_child(dynamic_flash_sprite)
|
||||
# Сдвигаем чуть вперед по оси Z, чтобы картинка не тонула в дуле
|
||||
dynamic_flash_sprite.position = Vector3(0, 0, -0.4)
|
||||
else:
|
||||
add_child(dynamic_flash_sprite)
|
||||
dynamic_flash_sprite.position = Vector3(0, 1.2, -0.5)
|
||||
|
||||
# ИНИЦИАЛИЗАЦИЯ ОРУЖИЯ ДЛЯ АНИМАЦИЙ ПРИ СТАРТЕ
|
||||
if has_node("WeaponManager/AnimationManager") and has_node("WeaponManager/WeaponContainer"):
|
||||
var anim_mgr = get_node("WeaponManager/AnimationManager")
|
||||
var weapon_model = get_node("WeaponManager/WeaponContainer")
|
||||
|
||||
var mock_weapon = {
|
||||
"tiltRotAmount": 0.05,
|
||||
"tiltRotSpeed": 5.0,
|
||||
"bobFreq": 0.005,
|
||||
"bobAmount": 0.05,
|
||||
"bobSpeed": 5.0,
|
||||
"onIdleBobFreqDivider": 2.0,
|
||||
"bobPos": Vector3.ZERO
|
||||
}
|
||||
anim_mgr.getCurrentWeapon(mock_weapon, weapon_model)
|
||||
|
||||
# --- СИСТЕМА ПОПАДАНИЙ КАК У МИШЕНИ ---
|
||||
|
||||
func hitscanHit(damageVal: float, _hitscanDir: Vector3, _hitscanPos: Vector3) -> void:
|
||||
if isDisabled: return
|
||||
health -= damageVal
|
||||
print("[ВРАГ] Хитскан попал в моба! Здоровье моба: ", health)
|
||||
if health <= 0.0: die()
|
||||
|
||||
func projectileHit(damageVal: float, _hitscanDir: Vector3) -> void:
|
||||
if isDisabled: return
|
||||
health -= damageVal
|
||||
print("[ВРАГ] Прожектайл попал в моба! Здоровье моба: ", health)
|
||||
if health <= 0.0: die()
|
||||
|
||||
func die() -> void:
|
||||
isDisabled = true
|
||||
print("[ВРАГ] Моб уничтожен!")
|
||||
if custom_battle_music and is_currently_seeing_player and has_node("/root/MusicManager"):
|
||||
get_node("/root/MusicManager").unregister_enemy_vision(self, custom_battle_music)
|
||||
queue_free()
|
||||
|
||||
# --- ФИЗИКА, ПРИЦЕЛИВАНИЕ И СТРЕЛЬБА ---
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
if isDisabled: return
|
||||
|
||||
if not is_on_floor():
|
||||
velocity.y -= gravity * delta
|
||||
else:
|
||||
velocity.y = 0
|
||||
|
||||
if attack_timer > 0:
|
||||
attack_timer -= delta
|
||||
|
||||
if player_node == null:
|
||||
player_node = get_tree().current_scene.find_child("PlayerCharacter", true, false)
|
||||
|
||||
if player_node != null:
|
||||
var distance = global_position.distance_to(player_node.global_position)
|
||||
var is_player_alive = true
|
||||
var p_health = player_node.find_child("HealthComponent", true, false)
|
||||
if p_health and "current_health" in p_health:
|
||||
is_player_alive = p_health.current_health > 0
|
||||
elif "health" in player_node:
|
||||
is_player_alive = player_node.health > 0
|
||||
|
||||
if distance <= vision_range and is_player_alive:
|
||||
is_player_in_range = true
|
||||
else:
|
||||
is_player_in_range = false
|
||||
else:
|
||||
is_player_in_range = false
|
||||
|
||||
if is_player_in_range and player_node != null:
|
||||
var distance = global_position.distance_to(player_node.global_position)
|
||||
var target_pos = player_node.global_position
|
||||
target_pos.y = global_position.y
|
||||
|
||||
if global_position.distance_to(target_pos) > 0.1:
|
||||
look_at(target_pos, Vector3.UP)
|
||||
|
||||
if distance > 2.5:
|
||||
var direction = (player_node.global_position - global_position).normalized()
|
||||
velocity.x = direction.x * move_speed
|
||||
velocity.z = direction.z * move_speed
|
||||
else:
|
||||
velocity.x = 0
|
||||
velocity.z = 0
|
||||
|
||||
var sees_now = has_clear_line_of_sight()
|
||||
|
||||
if custom_battle_music:
|
||||
if sees_now != is_currently_seeing_player:
|
||||
is_currently_seeing_player = sees_now
|
||||
if has_node("/root/MusicManager"):
|
||||
if is_currently_seeing_player:
|
||||
get_node("/root/MusicManager").register_enemy_vision(self, custom_battle_music)
|
||||
else:
|
||||
get_node("/root/MusicManager").unregister_enemy_vision(self, custom_battle_music)
|
||||
|
||||
if sees_now:
|
||||
if attack_timer <= 0:
|
||||
aiming_timer += delta
|
||||
if aiming_timer >= lock_on_time:
|
||||
enemy_shoot()
|
||||
aiming_timer = 0.0
|
||||
else:
|
||||
aiming_timer = 0.0
|
||||
else:
|
||||
velocity.x = 0
|
||||
velocity.z = 0
|
||||
aiming_timer = 0.0
|
||||
|
||||
if custom_battle_music and is_currently_seeing_player:
|
||||
is_currently_seeing_player = false
|
||||
if has_node("/root/MusicManager"):
|
||||
get_node("/root/MusicManager").unregister_enemy_vision(self, custom_battle_music)
|
||||
|
||||
move_and_slide()
|
||||
|
||||
func has_clear_line_of_sight() -> bool:
|
||||
if player_node == null: return false
|
||||
var start_pos = global_position + Vector3(0, 1.0, 0)
|
||||
var end_pos = player_node.global_position + Vector3(0, 1.0, 0)
|
||||
var space_state = get_world_3d().direct_space_state
|
||||
var query = PhysicsRayQueryParameters3D.create(start_pos, end_pos)
|
||||
|
||||
var exclusion_list = [get_rid()]
|
||||
for child in get_children():
|
||||
if child is CollisionObject3D: exclusion_list.append(child.get_rid())
|
||||
query.exclude = exclusion_list
|
||||
|
||||
query.collide_with_bodies = true
|
||||
query.collide_with_areas = false
|
||||
var result = space_state.intersect_ray(query)
|
||||
if result.is_empty(): return true
|
||||
var hit_node = result.collider
|
||||
if hit_node == player_node or hit_node.get_parent() == player_node or hit_node.is_in_group("Player"):
|
||||
return true
|
||||
return false
|
||||
|
||||
func enemy_shoot() -> void:
|
||||
attack_timer = attack_cooldown
|
||||
if player_node == null: return
|
||||
|
||||
# --- 🔊 ВОСПРОИЗВЕДЕНИЕ ЗВУКА ВЫСТРЕЛА ---
|
||||
var shoot_audio = get_node_or_null("%ShootSound")
|
||||
if shoot_audio == null: shoot_audio = find_child("ShootSound", true, false)
|
||||
if shoot_audio:
|
||||
shoot_audio.stop()
|
||||
shoot_audio.play()
|
||||
|
||||
# --- 🔥 ОТОБРАЖЕНИЕ КАРТИНКИ ВСПЫШКИ ВСПЛЫВАЮЩЕЙ НА ДОЛЮ СЕКУНДЫ ---
|
||||
if is_instance_valid(dynamic_flash_sprite):
|
||||
dynamic_flash_sprite.rotation.z = randf_range(0, TAU) # Каждый раз разный угол картинки
|
||||
dynamic_flash_sprite.visible = true
|
||||
get_tree().create_timer(0.06).timeout.connect(func():
|
||||
if is_instance_valid(dynamic_flash_sprite):
|
||||
dynamic_flash_sprite.visible = false
|
||||
)
|
||||
|
||||
# --- ЗАПУСКАЕМ АНИМАЦИЮ ВЫСТРЕЛА ТЕЛА ---
|
||||
if has_node("AnimationPlayer"):
|
||||
var anim = get_node("AnimationPlayer")
|
||||
anim.stop()
|
||||
anim.play("ShootAnimPistol")
|
||||
|
||||
# --- НАНЕСЕНИЕ УРОНА ИГРОКУ ---
|
||||
var player_health = player_node.find_child("HealthComponent", true, false)
|
||||
if player_health and "take_damage" in player_health:
|
||||
var reduced_damage = damage_to_player * 0.8
|
||||
print("[ВРАГ] Бах! Вспышка отрисована. Нанесено урона: ", reduced_damage)
|
||||
player_health.take_damage(reduced_damage)
|
||||
Reference in New Issue
Block a user