VanguardReMaster/client/Assets/Vanguard/Runtime/Board3D/View/Card3D.cs

340 lines
12 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;
using UnityEngine.Rendering;
namespace Vanguard.Board3D
{
/// <summary>
/// Карта на столе: тонкая коробка с отдельными материалами лица, рубашки
/// и торца. Rest — поворот вокруг Y, закрытость — переворот вокруг X.
/// </summary>
public sealed class Card3D : MonoBehaviour
{
private PlaymatLayoutConfig _config;
private MeshRenderer _front;
private MeshRenderer _back;
private MeshRenderer _edge;
private MeshRenderer _highlight;
private BoxCollider _collider;
private MaterialPropertyBlock _block;
private TextMesh _stats;
private bool _faceUp = true;
private bool _rested;
private Quaternion _baseRotation = Quaternion.identity;
private Quaternion _targetRotation = Quaternion.identity;
private Vector3 _targetPosition;
private Vector3 _targetScale = Vector3.one;
private bool _immediate;
private bool _dragging;
private Vector3 _dragOriginPosition;
private Quaternion _dragOriginRotation;
private float _moveSpeed = 16f;
private float _rotateSpeed = 14f;
public BoardCardRef Ref { get; private set; }
public bool IsFaceUp => _faceUp;
public bool IsRested => _rested;
public bool IsDragging => _dragging;
public Vector3 TargetPosition => _targetPosition;
public float TargetYaw => _targetRotation.eulerAngles.y;
public Quaternion TargetRotation => _targetRotation;
public bool IsAnimating =>
!_dragging
&& (Vector3.SqrMagnitude(transform.position - _targetPosition) > 0.0000004f
|| Quaternion.Angle(transform.rotation, _targetRotation) > 0.1f
|| Vector3.SqrMagnitude(transform.localScale - _targetScale) > 0.0000004f);
public string StatsText => _stats != null && _stats.gameObject.activeSelf
? _stats.text
: null;
public static Card3D Create(Transform parent, PlaymatLayoutConfig config)
{
var go = new GameObject("Card3D");
go.transform.SetParent(parent, false);
var card = go.AddComponent<Card3D>();
card.Build(config != null ? config : PlaymatLayoutConfig.Default);
return card;
}
private void Build(PlaymatLayoutConfig config)
{
_config = config;
_moveSpeed = config.smoothMoveSpeed;
_rotateSpeed = config.smoothRotateSpeed;
_block = new MaterialPropertyBlock();
var width = _config.cardWidth;
var height = _config.cardHeight;
var thickness = _config.cardThickness;
var edgeGo = GameObject.CreatePrimitive(PrimitiveType.Cube);
edgeGo.name = "Edge";
RemoveCollider(edgeGo);
edgeGo.transform.SetParent(transform, false);
edgeGo.transform.localScale = new Vector3(width, thickness, height);
_edge = edgeGo.GetComponent<MeshRenderer>();
_edge.sharedMaterial = BoardMaterials.Edge;
BoardMaterials.SetShadows(_edge, true);
var frontGo = new GameObject("Front");
frontGo.transform.SetParent(transform, false);
frontGo.transform.localPosition = new Vector3(0f, (thickness * 0.5f) + 0.0008f, 0f);
var frontFilter = frontGo.AddComponent<MeshFilter>();
frontFilter.sharedMesh = BoardMeshLibrary.CardFace(width, height, front: true);
_front = frontGo.AddComponent<MeshRenderer>();
_front.sharedMaterial = BoardMaterials.White;
BoardMaterials.SetShadows(_front, false);
var backGo = new GameObject("Back");
backGo.transform.SetParent(transform, false);
backGo.transform.localPosition = new Vector3(0f, -(thickness * 0.5f) - 0.0008f, 0f);
var backFilter = backGo.AddComponent<MeshFilter>();
backFilter.sharedMesh = BoardMeshLibrary.CardFace(width, height, front: false);
_back = backGo.AddComponent<MeshRenderer>();
_back.sharedMaterial = BoardMaterials.Back;
BoardMaterials.SetShadows(_back, false);
var highlightGo = new GameObject("Highlight");
highlightGo.transform.SetParent(transform, false);
highlightGo.transform.localPosition = new Vector3(0f, -(thickness * 0.5f) - 0.0015f, 0f);
var filter = highlightGo.AddComponent<MeshFilter>();
filter.sharedMesh = BoardMeshLibrary.Frame(
width * 1.16f,
height * 1.16f,
Mathf.Max(0.025f, width * 0.035f));
_highlight = highlightGo.AddComponent<MeshRenderer>();
_highlight.sharedMaterial = BoardMaterials.Highlight;
BoardMaterials.SetShadows(_highlight, false);
_highlight.gameObject.SetActive(false);
_stats = BoardText.Create(
transform,
string.Empty,
0.028f,
Color.white,
Vector3.zero);
_stats.gameObject.SetActive(false);
_collider = gameObject.AddComponent<BoxCollider>();
_collider.size = new Vector3(
width * 1.02f,
Mathf.Max(thickness + 0.02f, 0.03f),
height * 1.02f);
}
public void Bind(BoardCardRef reference)
{
Ref = reference;
name = "Card3D " + reference;
}
public void SetArt(Texture2D texture, Color accent)
{
if (_front == null)
return;
_front.GetPropertyBlock(_block);
_block.Clear();
BoardMaterials.SetTexture(
_block,
_front.sharedMaterial,
texture != null ? texture : BoardTextures.White);
BoardMaterials.SetColor(
_block,
_front.sharedMaterial,
texture != null ? Color.white : accent);
_front.SetPropertyBlock(_block);
}
public void SetAnimationSpeeds(float moveSpeed, float rotateSpeed)
{
_moveSpeed = Mathf.Max(1f, moveSpeed);
_rotateSpeed = Mathf.Max(1f, rotateSpeed);
}
public void SetStats(string text, bool modified) {
if (_stats == null)
return;
if (string.IsNullOrEmpty(text))
{
_stats.gameObject.SetActive(false);
return;
}
_stats.text = text;
_stats.color = modified
? new Color(1f, 0.82f, 0.28f)
: new Color(0.92f, 0.95f, 1f, 0.92f);
_stats.gameObject.SetActive(true);
}
private void LateUpdate()
{
if (_stats == null || !_stats.gameObject.activeSelf)
return;
var position = transform.position;
_stats.transform.position = new Vector3(
position.x,
position.y + 0.015f,
position.z - (_config.cardHeight * 0.62f));
_stats.transform.rotation = Quaternion.Euler(90f, 0f, 0f);
}
public void SetState(
Quaternion baseRotation,
bool faceUp,
bool rested,
bool damageLayout = false)
{
_baseRotation = baseRotation;
_faceUp = faceUp;
_rested = rested;
// Атака/Rest — по часовой стрелке (+90°), урон лежит против (-90°).
var restYaw = rested ? (damageLayout ? -90f : 90f) : 0f;
var rest = Quaternion.Euler(0f, restYaw, 0f);
var flip = Quaternion.Euler(faceUp ? 0f : 180f, 0f, 0f);
_targetRotation = rest * baseRotation * flip;
}
public void SetPose(
Vector3 position,
Quaternion baseRotation,
bool faceUp,
bool rested,
bool immediate,
bool damageLayout = false,
float scale = 1f)
{
SetState(baseRotation, faceUp, rested, damageLayout);
_targetPosition = position;
_targetScale = Vector3.one * Mathf.Max(0.05f, scale);
_immediate = immediate;
if (_dragging)
return;
if (immediate)
Snap();
}
public void SetVisible(bool visible)
{
gameObject.SetActive(visible);
}
public void SetHighlight(Color? color)
{
if (_highlight == null)
return;
var active = color.HasValue;
_highlight.gameObject.SetActive(active);
if (!active)
return;
_highlight.GetPropertyBlock(_block);
_block.Clear();
BoardMaterials.SetColor(_block, _highlight.sharedMaterial, color.Value);
_highlight.SetPropertyBlock(_block);
}
public void BeginDrag()
{
if (_dragging)
return;
_dragging = true;
_dragOriginPosition = transform.position;
_dragOriginRotation = transform.rotation;
_targetScale = Vector3.one * 1.05f;
if (_collider != null)
_collider.enabled = false;
SetHighlight(null);
}
public void DragTo(Vector3 worldPosition)
{
if (!_dragging)
return;
var yaw = _targetRotation.eulerAngles.y;
transform.position = new Vector3(
worldPosition.x,
_config.dragLift + (_config.cardThickness * 0.5f),
worldPosition.z);
transform.rotation = Quaternion.Euler(0f, yaw, 0f);
transform.localScale = Vector3.one * 1.05f;
}
public void DropAt(Vector3 position, Quaternion rotation)
{
_dragging = false;
if (_collider != null)
_collider.enabled = true;
_targetPosition = position;
_targetRotation = rotation;
_targetScale = Vector3.one;
_immediate = false;
}
public void ReturnToOrigin()
{
_dragging = false;
if (_collider != null)
_collider.enabled = true;
_targetPosition = _dragOriginPosition;
_targetRotation = _dragOriginRotation;
_targetScale = Vector3.one;
_immediate = false;
}
public void Snap()
{
transform.position = _targetPosition;
transform.rotation = _targetRotation;
transform.localScale = _targetScale;
_immediate = false;
}
public void Tick(float deltaTime)
{
if (_dragging || deltaTime <= 0f)
return;
if (_immediate)
{
Snap();
return;
}
var move = 1f - Mathf.Exp(-_moveSpeed * deltaTime);
var rotate = 1f - Mathf.Exp(-_rotateSpeed * deltaTime);
transform.position = Vector3.Lerp(transform.position, _targetPosition, move);
transform.rotation = Quaternion.Slerp(transform.rotation, _targetRotation, rotate);
transform.localScale = Vector3.Lerp(transform.localScale, _targetScale, move);
if (Vector3.SqrMagnitude(transform.position - _targetPosition) < 0.0000004f
&& Quaternion.Angle(transform.rotation, _targetRotation) < 0.1f)
{
Snap();
}
}
private static void RemoveCollider(GameObject target)
{
var collider = target.GetComponent<Collider>();
if (collider == null)
return;
if (Application.isPlaying)
Destroy(collider);
else
DestroyImmediate(collider);
}
}
}