Souljack/Assets/_Project/Scripts/PlayerUtils/PlayerPossession.cs

274 lines
10 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections;
using UnityEngine;
using UnityEngine.InputSystem;
namespace _Project.Scripts.PlayerUtils
{
public class PlayerPossession : MonoBehaviour, IDamageable
{
[Header("Профиль Духа (Форма по умолчанию)")]
[SerializeField] private BodyProfile spiritProfile;
[Header("Настройки Вселения (Possession)")]
[Tooltip("Радиус зоны вселения в метрах")]
[SerializeField] private float possessRange = 12f;
[Tooltip("С какого максимального расстояния прицел замечает врага и показывает сферу зоны")]
[SerializeField] private float maxAimLookDistance = 60f;
[SerializeField] private float possessDashDuration = 0.15f;
[SerializeField] private LayerMask possessRayMask = ~0;
[Header("Визуал Сферы Зоны Вселения")]
[Tooltip("Если true — сфера рисуется вокруг врага (показывая зону, куда надо войти). Если false — вокруг игрока.")]
[SerializeField] private bool centerSphereOnTarget = true;
[SerializeField] private Color zoneReadyColor = new Color(0.1f, 0.95f, 1f, 0.10f);
[SerializeField] private Color zoneOutOfRangeColor = new Color(1f, 0.35f, 0.35f, 0.06f);
[Header("Замедление времени в форме Духа")]
[SerializeField, Range(0.1f, 1f)] private float spiritTimeScale = 0.45f;
// Ивент окончательной смерти игрока (когда убили в форме Духа)
public event Action OnPlayerDied;
public BodyProfile CurrentProfile { get; private set; }
public bool IsInSpiritForm => CurrentProfile != null && CurrentProfile.bodyClass == BodyClass.Spirit;
public bool IsDead { get; private set; }
public float CurrentHealth { get; private set; }
public float MaxHealth => CurrentProfile != null ? CurrentProfile.maxHealth : 25f;
public EnemyAI AimedEnemy { get; private set; }
public bool IsTargetInPossessZone { get; private set; }
public float PlayerTimeMultiplier =>
IsInSpiritForm ? (1f / Mathf.Max(0.1f, Time.timeScale)) * 0.85f : 1f;
private Transform _camTransform;
private bool _isTransitioning;
private float _invulnerableTimer;
private GameObject _zoneSphere;
private Renderer _zoneRenderer;
private Material _zoneMaterial;
private void Awake()
{
if (spiritProfile == null)
spiritProfile = BodyProfile.CreateDefault(BodyClass.Spirit);
CreatePossessionZoneSphere();
}
private void Start()
{
_camTransform = Player.Instance.CameraController.CameraTransform;
EnterSpiritForm(false);
}
private void CreatePossessionZoneSphere()
{
_zoneSphere = GameObject.CreatePrimitive(PrimitiveType.Sphere);
_zoneSphere.name = "PossessionZoneSphere";
var col = _zoneSphere.GetComponent<Collider>();
if (col != null) Destroy(col);
_zoneRenderer = _zoneSphere.GetComponent<Renderer>();
_zoneRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
_zoneRenderer.receiveShadows = false;
Shader shader = Shader.Find("Sprites/Default");
_zoneMaterial = new Material(shader);
_zoneMaterial.color = Color.clear;
_zoneRenderer.material = _zoneMaterial;
_zoneSphere.SetActive(false);
}
private void Update()
{
if (IsDead) return;
if (_invulnerableTimer > 0f) _invulnerableTimer -= Time.unscaledDeltaTime;
if (_isTransitioning)
{
if (_zoneSphere != null) _zoneSphere.SetActive(false);
return;
}
UpdateAimedTarget();
UpdateZoneVisual();
HandleInput();
}
private void UpdateAimedTarget()
{
AimedEnemy = null;
IsTargetInPossessZone = false;
if (_camTransform == null) return;
Ray ray = new Ray(_camTransform.position, _camTransform.forward);
if (Physics.SphereCast(ray, 0.5f, out RaycastHit hit, maxAimLookDistance, possessRayMask, QueryTriggerInteraction.Ignore))
{
var enemy = hit.collider.GetComponentInParent<EnemyAI>();
if (enemy != null && enemy.CanBePossessed)
{
AimedEnemy = enemy;
float dist = Vector3.Distance(transform.position, enemy.transform.position);
IsTargetInPossessZone = dist <= possessRange;
}
}
}
private void UpdateZoneVisual()
{
if (_zoneSphere == null) return;
if (AimedEnemy != null)
{
if (!_zoneSphere.activeSelf) _zoneSphere.SetActive(true);
float diameter = possessRange * 2f;
_zoneSphere.transform.localScale = new Vector3(diameter, diameter, diameter);
Vector3 center = centerSphereOnTarget ? AimedEnemy.transform.position : transform.position;
_zoneSphere.transform.position = center;
Color targetColor = IsTargetInPossessZone ? zoneReadyColor : zoneOutOfRangeColor;
_zoneMaterial.color = Color.Lerp(_zoneMaterial.color, targetColor, Time.unscaledDeltaTime * 12f);
}
else
{
if (_zoneSphere.activeSelf)
{
_zoneMaterial.color = Color.clear;
_zoneSphere.SetActive(false);
}
}
}
private void HandleInput()
{
if (Keyboard.current == null) return;
if (Keyboard.current.eKey.wasPressedThisFrame && AimedEnemy != null && IsTargetInPossessZone)
{
StartCoroutine(PossessRoutine(AimedEnemy));
}
else if (Keyboard.current.qKey.wasPressedThisFrame && !IsInSpiritForm)
{
EnterSpiritForm(true);
}
}
private IEnumerator PossessRoutine(EnemyAI target)
{
_isTransitioning = true;
if (_zoneSphere != null) _zoneSphere.SetActive(false);
BodyProfile newProfile = target.profile != null
? target.profile
: BodyProfile.CreateDefault(target.enemyClass);
float inheritedHealth = Mathf.Max(target.CurrentHealth, newProfile.maxHealth * 0.5f);
Vector3 startPos = transform.position;
Vector3 endPos = target.transform.position;
Quaternion targetRot = target.transform.rotation;
Destroy(target.gameObject);
float elapsed = 0f;
while (elapsed < possessDashDuration)
{
elapsed += Time.unscaledDeltaTime;
float t = Mathf.Clamp01(elapsed / possessDashDuration);
transform.position = Vector3.Lerp(startPos, endPos, t);
yield return null;
}
transform.position = endPos;
Player.Instance.CameraController.SetYaw(targetRot.eulerAngles.y);
ApplyProfile(newProfile, inheritedHealth);
_invulnerableTimer = 0.4f;
_isTransitioning = false;
}
public void EnterSpiritForm(bool ejectImpulse)
{
if (IsDead) return;
ApplyProfile(spiritProfile, spiritProfile.maxHealth);
_invulnerableTimer = 0.5f;
if (ejectImpulse && Player.Instance.Movement != null)
{
Player.Instance.Movement.AddVerticalImpulse(8f);
}
}
public void ApplyProfile(BodyProfile profile, float startHealth)
{
CurrentProfile = profile;
CurrentHealth = Mathf.Clamp(startHealth, 1f, profile.maxHealth);
bool isSpirit = profile.bodyClass == BodyClass.Spirit;
Time.timeScale = isSpirit ? spiritTimeScale : 1f;
Time.fixedDeltaTime = 0.02f * Time.timeScale;
Player.Instance.Movement.ApplyProfile(profile);
Player.Instance.CameraController.ApplyProfile(profile);
Player.Instance.Combat.ApplyProfile(profile);
Player.Instance.Sound.SetFootstepPitch(profile.footstepPitch);
}
public void TakeDamage(float amount)
{
if (IsDead || _invulnerableTimer > 0f || _isTransitioning) return;
CurrentHealth -= amount;
if (CurrentHealth <= 0f)
{
if (!IsInSpiritForm)
{
// Тело уничтожено — выбрасывает обратно в Духа
Debug.Log("Носитель уничтожен! Выброс в форму Духа!");
EnterSpiritForm(true);
}
else
{
// Убили самого Духа — проигрыш!
Die();
}
}
}
private void Die()
{
if (IsDead) return;
IsDead = true;
CurrentHealth = 0f;
if (_zoneSphere != null) _zoneSphere.SetActive(false);
// Останавливаем время и отключаем управление персонажа
Time.timeScale = 0f;
Player.Instance.Controls.Player.Disable();
// Освобождаем курсор мыши для клика по кнопкам панели проигрыша
Cursor.lockState = CursorLockMode.None;
Cursor.visible = true;
Debug.Log("ДУХ РАЗВЕЯН! Игрок погиб.");
OnPlayerDied?.Invoke();
}
private void OnDestroy()
{
if (_zoneSphere != null) Destroy(_zoneSphere);
Time.timeScale = 1f;
Time.fixedDeltaTime = 0.02f;
}
}
}