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

282 lines
9.9 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 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;
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 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;
_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);
_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 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)
{
SetState(baseRotation, faceUp, rested, damageLayout);
_targetPosition = position;
_targetScale = Vector3.one;
_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(-_config.smoothMoveSpeed * deltaTime);
var rotate = 1f - Mathf.Exp(-_config.smoothRotateSpeed * 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);
}
}
}