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

255 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.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); // Враг в зоне (можно жать E)
[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 BodyProfile CurrentProfile { get; private set; }
public bool IsInSpiritForm => CurrentProfile != null && CurrentProfile.bodyClass == BodyClass.Spirit;
public float CurrentHealth { get; private set; }
public float MaxHealth => CurrentProfile != null ? CurrentProfile.maxHealth : 25f;
// Враг, на которого наведён прицел (на любом расстоянии до maxAimLookDistance)
public EnemyAI AimedEnemy { get; private set; }
// Находится ли этот враг внутри сферы вселения (possessRange)
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 float _currentZoneAlpha;
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 (_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);
// Диаметр сферы = 2 * радиус (possessRange)
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;
// Клавиша E — Вселиться, если враг под прицелом И находится внутри сферы!
if (Keyboard.current.eKey.wasPressedThisFrame && AimedEnemy != null && IsTargetInPossessZone)
{
StartCoroutine(PossessRoutine(AimedEnemy));
}
// Клавиша Q — Покинуть тело
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)
{
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 (_invulnerableTimer > 0f || _isTransitioning) return;
CurrentHealth -= amount;
if (CurrentHealth <= 0f)
{
if (!IsInSpiritForm)
{
Debug.Log("Носитель уничтожен! Выброс в форму Духа!");
EnterSpiritForm(true);
}
else
{
CurrentHealth = 0f;
Debug.Log("ДУХ УНИЧТОЖЕН! ИГРА ОКОНЧЕНА.");
EnterSpiritForm(false);
}
}
}
private void OnDestroy()
{
if (_zoneSphere != null) Destroy(_zoneSphere);
Time.timeScale = 1f;
Time.fixedDeltaTime = 0.02f;
}
}
}