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;
}
}
}
}