using UnityEngine; using Vanguard.Battle; namespace Vanguard.Board3D { /// /// Стрелка атаки на столе: линия от атакующего к цели и наконечник. /// Точки обновляются каждый кадр, поэтому стрелка едет за картами. /// public sealed class AttackArrowView { private const float ShaftWidth = 0.075f; private const float HeadWidth = 0.17f; private const float HeadLength = 0.38f; private GameObject _root; private LineRenderer _shaft; private LineRenderer _head; private Material _material; private AnimationConfig _config; private Vector3 _from; private Vector3 _to; private bool _visible; private float _alpha; private float _widthScale = 1f; public bool IsVisible => _visible || _alpha > 0.001f; public void Build(Transform parent, AnimationConfig config) { _config = config; _root = new GameObject("Attack Arrow"); _root.transform.SetParent(parent, false); var shader = Shader.Find("Sprites/Default"); if (shader == null) shader = Shader.Find("Unlit/Color"); _material = shader != null ? new Material(shader) : null; _shaft = CreateLine("Shaft", ShaftWidth); _head = CreateLine("Head", ShaftWidth * 0.7f); _head.positionCount = 3; _root.SetActive(false); } public void SetConfig(AnimationConfig config) { _config = config; } public void Show(Vector3 from, Vector3 to) { SetPoints(from, to); _visible = true; if (_root != null) _root.SetActive(true); } public void Hide() { _visible = false; } public void HideImmediate() { _visible = false; _alpha = 0f; if (_root != null) _root.SetActive(false); } public void SetPoints(Vector3 from, Vector3 to) { _from = from; _to = to; UpdateGeometry(); } public void Tick(float deltaTime) { if (_root == null) return; var target = _visible ? 1f : 0f; var duration = _visible ? (_config != null ? _config.arrowShowDuration : 0.25f) : (_config != null ? _config.arrowHideDuration : 0.2f); _alpha = Mathf.MoveTowards( _alpha, target, deltaTime / Mathf.Max(0.01f, duration)); if (!_visible && _alpha <= 0.001f) { _root.SetActive(false); return; } _root.SetActive(true); _widthScale = 0.55f + (_alpha * 0.45f); UpdateGeometry(); ApplyColor(); } public void Dispose() { if (_root != null) { if (Application.isPlaying) Object.Destroy(_root); else Object.DestroyImmediate(_root); _root = null; } if (_material != null) { if (Application.isPlaying) Object.Destroy(_material); else Object.DestroyImmediate(_material); _material = null; } } private LineRenderer CreateLine(string name, float width) { var go = new GameObject(name); go.transform.SetParent(_root.transform, false); var line = go.AddComponent(); line.useWorldSpace = false; line.positionCount = 2; line.widthMultiplier = width; line.numCapVertices = 4; line.numCornerVertices = 4; line.alignment = LineAlignment.View; line.textureMode = LineTextureMode.Stretch; line.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; line.receiveShadows = false; if (_material != null) line.material = _material; return line; } private void UpdateGeometry() { if (_shaft == null || _head == null) return; var direction = _to - _from; if (direction.sqrMagnitude < 0.0001f) direction = Vector3.forward; direction.Normalize(); _shaft.SetPosition(0, _from); _shaft.SetPosition(1, _to); _shaft.widthMultiplier = ShaftWidth * _widthScale; var tip = _to; var back = _to - (direction * HeadLength); var side = Vector3.Cross(Vector3.up, direction).normalized; if (side.sqrMagnitude < 0.0001f) side = Vector3.right; var half = side * (HeadWidth * _widthScale); _head.SetPosition(0, back + half); _head.SetPosition(1, tip); _head.SetPosition(2, back - half); _head.widthMultiplier = ShaftWidth * 0.7f * _widthScale; } private void ApplyColor() { var color = new Color(1f, 0.72f, 0.26f, _alpha * 0.92f); foreach (var line in new[] { _shaft, _head }) { if (line == null) continue; line.startColor = color; line.endColor = color; } } } }