275 lines
9.6 KiB
C#
275 lines
9.6 KiB
C#
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)
|
||
{
|
||
_baseRotation = baseRotation;
|
||
_faceUp = faceUp;
|
||
_rested = rested;
|
||
|
||
var rest = Quaternion.Euler(0f, rested ? 90f : 0f, 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)
|
||
{
|
||
SetState(baseRotation, faceUp, rested);
|
||
_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);
|
||
}
|
||
}
|
||
}
|