Perekyp/Assets/Scripts/Car/Repair/WheelBolt.cs

259 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 UnityEngine;
[RequireComponent(typeof(SphereCollider))]
public class WheelBolt : MonoBehaviour, IInteractable
{
[Header("Настройки вылета и скорости")]
[SerializeField] private float pullOutDistance = 0.05f;
[SerializeField] private float workDuration = 0.5f;
private CarPart parentWheel;
private MeshRenderer meshRenderer;
private SphereCollider boltCollider;
private Material originalMaterial;
private Material ghostMaterial;
private PlayerToolManager toolManager;
private Vector3 tightenedLocalPos;
private Quaternion tightenedLocalRot;
private Vector3 baseLocalScale;
private Vector3 localMeshCenter;
private Vector3 boltCenterInParent;
private Vector3 parentOutwardDir = Vector3.right;
private float tightness = 1f;
private bool isTightened = true;
private bool isHoveredThisFrame = false;
// Глобальная блокировка до отпускания кнопки, чтобы болт НИКОГДА не крутился туда-сюда по кругу
private static bool waitForKeyRelease = false;
public bool IsTightened => isTightened;
public bool IsAvailableForAim => parentWheel == null || parentWheel.State == PartState.Installed;
// ВАЖНО: Координата для прицела берется от неподвижного гнезда на ступице,
// чтобы во время выдвижения болта он не выскакивал из-под прицела!
public Vector3 WorldBoltCenter => transform.parent != null
? transform.parent.TransformPoint(boltCenterInParent)
: transform.TransformPoint(localMeshCenter);
private void Awake()
{
parentWheel = GetComponentInParent<CarPart>();
meshRenderer = GetComponent<MeshRenderer>();
boltCollider = GetComponent<SphereCollider>();
toolManager = FindAnyObjectByType<PlayerToolManager>();
tightenedLocalPos = transform.localPosition;
tightenedLocalRot = transform.localRotation;
baseLocalScale = transform.localScale;
MeshFilter mf = GetComponent<MeshFilter>();
if (mf != null && mf.sharedMesh != null)
localMeshCenter = mf.sharedMesh.bounds.center;
boltCollider.isTrigger = true;
boltCollider.center = localMeshCenter;
boltCollider.radius = 0.055f;
boltCenterInParent = tightenedLocalPos + tightenedLocalRot * Vector3.Scale(localMeshCenter, baseLocalScale);
if (meshRenderer != null)
originalMaterial = meshRenderer.sharedMaterial;
if (parentWheel != null && parentWheel.Data != null)
ghostMaterial = parentWheel.Data.ghostMaterial;
// Если в CarPartData забыли назначить Ghost Material — создаем запасной полупрозрачный материал
if (ghostMaterial == null)
{
Shader sh = Shader.Find("Universal Render Pipeline/Unlit") ?? Shader.Find("Sprites/Default");
if (sh != null)
{
ghostMaterial = new Material(sh);
ghostMaterial.color = new Color(0.2f, 0.95f, 0.35f, 0.5f);
}
}
RecalculateOutwardDirection();
}
public void RecalculateOutwardDirection()
{
Vector3 worldBoltPos = WorldBoltCenter;
VehicleInteraction car = GetComponentInParent<VehicleInteraction>();
Transform carRoot = car != null ? car.transform : transform.root;
Vector3 fromCarToBolt = worldBoltPos - carRoot.position;
float sideSign = Mathf.Sign(Vector3.Dot(fromCarToBolt, carRoot.right));
if (sideSign == 0f) sideSign = 1f;
Vector3 worldOutDir = carRoot.right * sideSign;
parentOutwardDir = transform.parent != null
? transform.parent.InverseTransformDirection(worldOutDir).normalized
: Vector3.right * sideSign;
UpdateBoltTransform(1f);
}
public void SetTightenedImmediate(bool state)
{
isTightened = state;
tightness = state ? 1f : 0f;
UpdateBoltTransform(1f);
}
private void Update()
{
// Снимаем блокировку сразу, как только игрок отпустил ЛКМ и клавишу E
if (waitForKeyRelease && !Input.GetMouseButton(0) && !Input.GetKey(KeyCode.E))
{
waitForKeyRelease = false;
}
}
private void LateUpdate()
{
if (parentWheel == null)
parentWheel = GetComponentInParent<CarPart>();
// Пока колесо в руках или лежит на земле — болты полностью скрыты
if (parentWheel != null && parentWheel.State != PartState.Installed)
{
if (meshRenderer != null && meshRenderer.enabled) meshRenderer.enabled = false;
if (boltCollider != null && boltCollider.enabled) boltCollider.enabled = false;
isHoveredThisFrame = false;
return;
}
if (boltCollider != null && !boltCollider.enabled)
boltCollider.enabled = true;
if (toolManager == null)
toolManager = FindAnyObjectByType<PlayerToolManager>();
bool hasWrench = toolManager != null && toolManager.currentTool == ToolType.Wrench;
bool isAnimating = tightness > 0.01f && tightness < 0.99f;
// Если игрок отпустил кнопку на полпути или увел взгляд — возвращаем болт в крайнее положение
if (!isHoveredThisFrame || (!Input.GetMouseButton(0) && !Input.GetKey(KeyCode.E)))
{
tightness = isTightened ? 1f : 0f;
isAnimating = false;
}
// Болт под прицелом заметно увеличивается (1.35x), чтобы игрок четко видел выбранный болт
float scaleMult = (isHoveredThisFrame && hasWrench) ? 1.35f : 1.0f;
UpdateBoltTransform(scaleMult);
if (meshRenderer != null)
{
if (isTightened || isAnimating)
{
// Закрученный болт всегда виден; под прицелом с ключом — подсвечивается
meshRenderer.enabled = true;
if (isHoveredThisFrame && hasWrench && ghostMaterial != null)
meshRenderer.sharedMaterial = ghostMaterial;
else if (originalMaterial != null)
meshRenderer.sharedMaterial = originalMaterial;
}
else
{
// КЛЮЧЕВОЕ ИСПРАВЛЕНИЕ: когда болты выкручены и игрок взял КЛЮЧ (hasWrench),
// ВСЕ 4 места под болты сразу подсвечиваются силуэтом, чтобы игрок видел, куда наводиться!
meshRenderer.enabled = hasWrench;
if (hasWrench && ghostMaterial != null)
meshRenderer.sharedMaterial = ghostMaterial;
}
}
isHoveredThisFrame = false;
}
public string GetPrompt()
{
isHoveredThisFrame = true;
if (isTightened && parentWheel != null && !CarJack.IsWheelLifted(parentWheel.transform))
{
return "Колесо под нагрузкой! Сначала поднимите эту сторону домкратом";
}
bool hasWrench = toolManager != null && toolManager.currentTool == ToolType.Wrench;
if (!hasWrench)
{
return isTightened
? "Колесный болт (Возьмите баллонный ключ [Tab])"
: "Место под болт (Возьмите баллонный ключ [Tab])";
}
bool isHoldingKey = Input.GetMouseButton(0) || Input.GetKey(KeyCode.E);
if (isHoldingKey && !waitForKeyRelease)
{
ProcessWrench(Time.deltaTime);
}
int percent = Mathf.RoundToInt((isTightened ? (1f - tightness) : tightness) * 100f);
if (isTightened)
{
if (waitForKeyRelease) return "Болт закручен! Отпустите кнопку";
return percent > 0 ? $"Откручиваем болт... {percent}%" : "[Удерж. ЛКМ / E] Открутить болт";
}
else
{
if (waitForKeyRelease) return "Болт выкручен! Отпустите кнопку";
return percent > 0 ? $"Закручиваем болт... {percent}%" : "[Удерж. ЛКМ / E] Закрутить болт";
}
}
private void ProcessWrench(float dt)
{
float step = dt / workDuration;
if (isTightened)
{
tightness -= step;
if (tightness <= 0f)
{
tightness = 0f;
isTightened = false;
waitForKeyRelease = true; // Блокируем до отпускания кнопки!
}
}
else
{
tightness += step;
if (tightness >= 1f)
{
tightness = 1f;
isTightened = true;
waitForKeyRelease = true; // Блокируем до отпускания кнопки!
}
}
}
private void UpdateBoltTransform(float scaleMultiplier = 1f)
{
// Выкрученный неанимируемый силуэт стоит ровно в гнезде ступицы (outFactor = 0),
// чтобы силуэты отверстий не висели в воздухе в 5 см от диска
bool isAnimating = tightness > 0.001f && tightness < 0.999f;
float outFactor = isAnimating ? (1f - Mathf.Clamp01(tightness)) : 0f;
Vector3 currentScale = baseLocalScale * scaleMultiplier;
Quaternion spinRotation = Quaternion.AngleAxis(outFactor * 720f, parentOutwardDir);
Quaternion newLocalRot = spinRotation * tightenedLocalRot;
Vector3 rotatedCenterOffset = newLocalRot * Vector3.Scale(localMeshCenter, currentScale);
Vector3 pullOutOffset = parentOutwardDir * (pullOutDistance * outFactor);
transform.localScale = currentScale;
transform.localRotation = newLocalRot;
transform.localPosition = boltCenterInParent - rotatedCenterOffset + pullOutOffset;
}
public void Interact(GameObject interactor) { }
}