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 Texture2D _dashTexture; private AnimationConfig _config; private Vector3 _from; private Vector3 _to; private bool _visible; private float _alpha; private float _widthScale = 1f; private float _grow = 1f; private float _growDuration = 0.35f; private float _fadeDuration = 0.2f; private float _thickness = ShaftWidth; private float _arcHeight = 0.1f; private Color _color = new Color(1f, 0.16f, 0.16f); 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; if (_material != null) { _dashTexture = CreateDashTexture(); _material.mainTexture = _dashTexture; _material.mainTextureScale = new Vector2(2.5f, 1f); } _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 SetStyle( Color color, float thickness, float growDuration, float fadeDuration, float arcHeight) { _color = color; _thickness = Mathf.Max(0.01f, thickness); _growDuration = Mathf.Max(0.01f, growDuration); _fadeDuration = Mathf.Max(0.01f, fadeDuration); _arcHeight = Mathf.Max(0f, arcHeight); ApplyColor(); } public void Show(Vector3 from, Vector3 to) { SetPoints(from, to); if (!_visible) { _grow = 0f; _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; if (_visible && _grow < 1f) _grow = Mathf.MoveTowards( _grow, 1f, deltaTime / _growDuration); var target = _visible ? 1f : 0f; var duration = _visible ? (_config != null ? _config.arrowShowDuration : 0.25f) : _fadeDuration; _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); if (_material != null && _config != null) { var offset = _material.mainTextureOffset; offset.x += _config.dashScrollSpeed * deltaTime; _material.mainTextureOffset = offset; } UpdateGeometry(); ApplyColor(); } public void Dispose() { if (_root != null) { if (Application.isPlaying) Object.Destroy(_root); else Object.DestroyImmediate(_root); _root = null; } if (_dashTexture != null) { if (Application.isPlaying) Object.Destroy(_dashTexture); else Object.DestroyImmediate(_dashTexture); _dashTexture = null; } if (_material != null) { if (Application.isPlaying) Object.Destroy(_material); else Object.DestroyImmediate(_material); _material = null; } } private static Texture2D CreateDashTexture() { const int width = 16; const int height = 4; var texture = new Texture2D(width, height, TextureFormat.RGBA32, false) { wrapMode = TextureWrapMode.Repeat, filterMode = FilterMode.Bilinear, name = "ArrowDash", }; for (var x = 0; x < width; x++) { var on = x < 9; var color = on ? new Color(1f, 1f, 1f, 1f) : new Color(1f, 1f, 1f, 0.18f); for (var y = 0; y < height; y++) texture.SetPixel(x, y, color); } texture.Apply(false, true); return texture; } 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.Tile; line.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; line.receiveShadows = false; if (_material != null) line.sharedMaterial = _material; return line; } private void UpdateGeometry() { if (_shaft == null || _head == null) return; var end = Vector3.Lerp(_from, _to, _grow); var direction = end - _from; if (direction.sqrMagnitude < 0.0001f) direction = _to - _from; if (direction.sqrMagnitude < 0.0001f) direction = Vector3.forward; direction.Normalize(); var mid = Vector3.Lerp(_from, end, 0.5f) + (Vector3.up * _arcHeight * Mathf.Sin(_grow * Mathf.PI)); _shaft.positionCount = 3; _shaft.SetPosition(0, _from); _shaft.SetPosition(1, mid); _shaft.SetPosition(2, end); _shaft.widthMultiplier = _thickness * _widthScale; var tip = end; var back = tip - (direction * 0.32f); var side = Vector3.Cross(Vector3.up, direction).normalized; if (side.sqrMagnitude < 0.0001f) side = Vector3.right; var half = side * (0.16f * _widthScale); _head.SetPosition(0, back + half); _head.SetPosition(1, tip); _head.SetPosition(2, back - half); _head.widthMultiplier = _thickness * 0.7f * _widthScale; } private void ApplyColor() { var color = new Color( _color.r, _color.g, _color.b, _alpha * 0.92f); foreach (var line in new[] { _shaft, _head }) { if (line == null) continue; line.startColor = color; line.endColor = color; } } } }