Pin hand to camera view and move camera closer
CI / test (push) Successful in 2m13s
Details
CI / test (push) Successful in 2m13s
Details
This commit is contained in:
parent
c2489b5452
commit
f2a01bae16
|
|
@ -53,11 +53,49 @@ namespace Vanguard.Editor
|
||||||
var board = UnityEngine.Object.FindAnyObjectByType<Vanguard.Board3D.GameBoard3D>();
|
var board = UnityEngine.Object.FindAnyObjectByType<Vanguard.Board3D.GameBoard3D>();
|
||||||
var cards = UnityEngine.Object.FindObjectsByType<Vanguard.Board3D.Card3D>(
|
var cards = UnityEngine.Object.FindObjectsByType<Vanguard.Board3D.Card3D>(
|
||||||
FindObjectsInactive.Exclude);
|
FindObjectsInactive.Exclude);
|
||||||
var success = board != null && cards.Length >= 10;
|
var camera = Camera.main;
|
||||||
|
var handVisible = 0;
|
||||||
|
var handCards = 0;
|
||||||
|
var fieldVisible = false;
|
||||||
|
if (camera != null)
|
||||||
|
{
|
||||||
|
foreach (var card in cards)
|
||||||
|
{
|
||||||
|
var viewport = camera.WorldToViewportPoint(card.transform.position);
|
||||||
|
if (card.Ref.Zone == Vanguard.Board3D.BoardZone.Hand)
|
||||||
|
{
|
||||||
|
handCards++;
|
||||||
|
if (viewport.z > 0f
|
||||||
|
&& viewport.x > 0.02f
|
||||||
|
&& viewport.x < 0.98f
|
||||||
|
&& viewport.y > 0.01f
|
||||||
|
&& viewport.y < 0.55f)
|
||||||
|
{
|
||||||
|
handVisible++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (card.Ref.Zone == Vanguard.Board3D.BoardZone.Vanguard
|
||||||
|
&& card.Ref.Player == 0
|
||||||
|
&& viewport.z > 0f
|
||||||
|
&& viewport.y > 0.05f
|
||||||
|
&& viewport.y < 0.75f)
|
||||||
|
{
|
||||||
|
fieldVisible = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var success = board != null
|
||||||
|
&& cards.Length >= 10
|
||||||
|
&& handCards > 0
|
||||||
|
&& handVisible == handCards
|
||||||
|
&& fieldVisible;
|
||||||
Debug.Log(
|
Debug.Log(
|
||||||
"PLAYMAT_SMOKE success=" + success
|
"PLAYMAT_SMOKE success=" + success
|
||||||
+ " board=" + (board != null)
|
+ " board=" + (board != null)
|
||||||
+ " cards=" + cards.Length
|
+ " cards=" + cards.Length
|
||||||
|
+ " hand=" + handVisible + "/" + handCards
|
||||||
|
+ " fieldVisible=" + fieldVisible
|
||||||
+ " error=" + VanguardApp.LastInitializationError);
|
+ " error=" + VanguardApp.LastInitializationError);
|
||||||
|
|
||||||
EditorApplication.Exit(success ? 0 : 1);
|
EditorApplication.Exit(success ? 0 : 1);
|
||||||
|
|
|
||||||
|
|
@ -54,12 +54,12 @@ namespace Vanguard.App
|
||||||
camera.fieldOfView = theme == SceneTheme.Battle ? 50f : 52f;
|
camera.fieldOfView = theme == SceneTheme.Battle ? 50f : 52f;
|
||||||
camera.transform.position = theme switch
|
camera.transform.position = theme switch
|
||||||
{
|
{
|
||||||
SceneTheme.Battle => new Vector3(0f, 8.7f, -12.4f),
|
SceneTheme.Battle => new Vector3(0f, 6.9f, -9.8f),
|
||||||
SceneTheme.Login => new Vector3(0f, 2.4f, -9.5f),
|
SceneTheme.Login => new Vector3(0f, 2.4f, -9.5f),
|
||||||
_ => new Vector3(0f, 3.2f, -10f),
|
_ => new Vector3(0f, 3.2f, -10f),
|
||||||
};
|
};
|
||||||
camera.transform.rotation = theme == SceneTheme.Battle
|
camera.transform.rotation = theme == SceneTheme.Battle
|
||||||
? Quaternion.Euler(34.4f, 0f, 0f)
|
? Quaternion.Euler(35.2f, 0f, 0f)
|
||||||
: Quaternion.Euler(8f, 0f, 0f);
|
: Quaternion.Euler(8f, 0f, 0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,13 +36,19 @@ MonoBehaviour:
|
||||||
handSpacing: 0.42
|
handSpacing: 0.42
|
||||||
handMaxWidth: 7.4
|
handMaxWidth: 7.4
|
||||||
handArc: 0.035
|
handArc: 0.035
|
||||||
|
handViewportY: 0.16
|
||||||
|
handViewDistance: 3.6
|
||||||
|
handViewMaxWidth: 2
|
||||||
|
handViewScale: 0.9
|
||||||
|
handViewRoll: 3.2
|
||||||
|
handViewArc: 0.02
|
||||||
guardianRowGap: 0.55
|
guardianRowGap: 0.55
|
||||||
guardianSpacing: 0.44
|
guardianSpacing: 0.44
|
||||||
guardianMaxWidth: 3.6
|
guardianMaxWidth: 3.6
|
||||||
damageX: 3.5
|
damageX: 3.5
|
||||||
pileX: 3.55
|
pileX: 3.55
|
||||||
cameraPosition: {x: 0, y: 8.7, z: -12.4}
|
cameraPosition: {x: 0, y: 6.9, z: -9.8}
|
||||||
cameraTarget: {x: 0, y: 0, z: 0.3}
|
cameraTarget: {x: 0, y: 0, z: -0.15}
|
||||||
cameraFov: 50
|
cameraFov: 50
|
||||||
orbitYawLimit: 32
|
orbitYawLimit: 32
|
||||||
orbitPitchMin: 20
|
orbitPitchMin: 20
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ namespace Vanguard.Board3D
|
||||||
public float smoothRotateSpeed = 14f;
|
public float smoothRotateSpeed = 14f;
|
||||||
public float handHoverLift = 0.08f;
|
public float handHoverLift = 0.08f;
|
||||||
|
|
||||||
[Header("Рука")]
|
[Header("Рука на столе (fallback без камеры)")]
|
||||||
public float handCenterZ = -5.3f;
|
public float handCenterZ = -5.3f;
|
||||||
public float handHeight = 1.12f;
|
public float handHeight = 1.12f;
|
||||||
[Range(0f, 85f)] public float handTilt = 58f;
|
[Range(0f, 85f)] public float handTilt = 58f;
|
||||||
|
|
@ -49,6 +49,16 @@ namespace Vanguard.Board3D
|
||||||
public float handMaxWidth = 7.4f;
|
public float handMaxWidth = 7.4f;
|
||||||
public float handArc = 0.035f;
|
public float handArc = 0.035f;
|
||||||
|
|
||||||
|
[Header("Рука в поле зрения")]
|
||||||
|
[Tooltip("Доля высоты экрана для центра карт: 0 — низ, 0.5 — середина.")]
|
||||||
|
[Range(0.05f, 0.45f)] public float handViewportY = 0.16f;
|
||||||
|
[Tooltip("Расстояние от камеры до веера руки, метры.")]
|
||||||
|
public float handViewDistance = 3.6f;
|
||||||
|
public float handViewMaxWidth = 2.0f;
|
||||||
|
public float handViewScale = 0.9f;
|
||||||
|
public float handViewRoll = 3.2f;
|
||||||
|
public float handViewArc = 0.02f;
|
||||||
|
|
||||||
[Header("Круг защиты")]
|
[Header("Круг защиты")]
|
||||||
public float guardianRowGap = 0.55f;
|
public float guardianRowGap = 0.55f;
|
||||||
public float guardianSpacing = 0.44f;
|
public float guardianSpacing = 0.44f;
|
||||||
|
|
@ -61,8 +71,8 @@ namespace Vanguard.Board3D
|
||||||
public float pileX = 3.55f;
|
public float pileX = 3.55f;
|
||||||
|
|
||||||
[Header("Камера")]
|
[Header("Камера")]
|
||||||
public Vector3 cameraPosition = new Vector3(0f, 8.7f, -12.4f);
|
public Vector3 cameraPosition = new Vector3(0f, 6.9f, -9.8f);
|
||||||
public Vector3 cameraTarget = new Vector3(0f, 0f, 0.3f);
|
public Vector3 cameraTarget = new Vector3(0f, 0f, -0.15f);
|
||||||
public float cameraFov = 50f;
|
public float cameraFov = 50f;
|
||||||
public float orbitYawLimit = 32f;
|
public float orbitYawLimit = 32f;
|
||||||
public float orbitPitchMin = 20f;
|
public float orbitPitchMin = 20f;
|
||||||
|
|
@ -131,6 +141,12 @@ namespace Vanguard.Board3D
|
||||||
snapRadius = Mathf.Clamp(snapRadius, 0.2f, 1.5f);
|
snapRadius = Mathf.Clamp(snapRadius, 0.2f, 1.5f);
|
||||||
handMaxWidth = Mathf.Max(1f, handMaxWidth);
|
handMaxWidth = Mathf.Max(1f, handMaxWidth);
|
||||||
guardianMaxWidth = Mathf.Max(1f, guardianMaxWidth);
|
guardianMaxWidth = Mathf.Max(1f, guardianMaxWidth);
|
||||||
|
handViewportY = Mathf.Clamp(handViewportY, 0.05f, 0.45f);
|
||||||
|
handViewDistance = Mathf.Max(1f, handViewDistance);
|
||||||
|
handViewMaxWidth = Mathf.Max(0.5f, handViewMaxWidth);
|
||||||
|
handViewScale = Mathf.Clamp(handViewScale, 0.3f, 1.5f);
|
||||||
|
handViewRoll = Mathf.Clamp(handViewRoll, 0f, 20f);
|
||||||
|
handViewArc = Mathf.Max(0f, handViewArc);
|
||||||
orbitPitchMax = Mathf.Clamp(orbitPitchMax, orbitPitchMin + 1f, 85f);
|
orbitPitchMax = Mathf.Clamp(orbitPitchMax, orbitPitchMin + 1f, 85f);
|
||||||
zoomMax = Mathf.Max(zoomMin + 0.5f, zoomMax);
|
zoomMax = Mathf.Max(zoomMin + 0.5f, zoomMax);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -149,11 +149,12 @@ namespace Vanguard.Board3D
|
||||||
bool faceUp,
|
bool faceUp,
|
||||||
bool rested,
|
bool rested,
|
||||||
bool immediate,
|
bool immediate,
|
||||||
bool damageLayout = false)
|
bool damageLayout = false,
|
||||||
|
float scale = 1f)
|
||||||
{
|
{
|
||||||
SetState(baseRotation, faceUp, rested, damageLayout);
|
SetState(baseRotation, faceUp, rested, damageLayout);
|
||||||
_targetPosition = position;
|
_targetPosition = position;
|
||||||
_targetScale = Vector3.one;
|
_targetScale = Vector3.one * Mathf.Max(0.05f, scale);
|
||||||
_immediate = immediate;
|
_immediate = immediate;
|
||||||
|
|
||||||
if (_dragging)
|
if (_dragging)
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ namespace Vanguard.Board3D
|
||||||
private readonly BoardZoneView[] _damageZones = new BoardZoneView[2];
|
private readonly BoardZoneView[] _damageZones = new BoardZoneView[2];
|
||||||
private readonly HashSet<BoardSlotKey> _activeHighlights = new HashSet<BoardSlotKey>();
|
private readonly HashSet<BoardSlotKey> _activeHighlights = new HashSet<BoardSlotKey>();
|
||||||
private readonly HashSet<BoardSlotKey> _validDropSlots = new HashSet<BoardSlotKey>();
|
private readonly HashSet<BoardSlotKey> _validDropSlots = new HashSet<BoardSlotKey>();
|
||||||
|
private readonly List<HandCardBinding> _handBindings = new List<HandCardBinding>();
|
||||||
private bool _guardianDropAllowed;
|
private bool _guardianDropAllowed;
|
||||||
|
|
||||||
private PlaymatLayoutConfig _config;
|
private PlaymatLayoutConfig _config;
|
||||||
|
|
@ -150,6 +151,7 @@ namespace Vanguard.Board3D
|
||||||
{
|
{
|
||||||
CancelPointer();
|
CancelPointer();
|
||||||
_hasRendered = false;
|
_hasRendered = false;
|
||||||
|
_handBindings.Clear();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -176,7 +178,10 @@ namespace Vanguard.Board3D
|
||||||
SetSelfPlayer(view.You);
|
SetSelfPlayer(view.You);
|
||||||
_view = view;
|
_view = view;
|
||||||
state ??= new BoardRenderState();
|
state ??= new BoardRenderState();
|
||||||
|
if (_camera == null)
|
||||||
|
_camera = Camera.main;
|
||||||
|
|
||||||
|
_handBindings.Clear();
|
||||||
var specs = new List<CardSpec>(40);
|
var specs = new List<CardSpec>(40);
|
||||||
BuildSpecs(view, state, specs);
|
BuildSpecs(view, state, specs);
|
||||||
|
|
||||||
|
|
@ -203,8 +208,17 @@ namespace Vanguard.Board3D
|
||||||
spec.FaceUp,
|
spec.FaceUp,
|
||||||
spec.Rested,
|
spec.Rested,
|
||||||
immediateAll || changed,
|
immediateAll || changed,
|
||||||
spec.DamageLayout);
|
spec.DamageLayout,
|
||||||
|
spec.Scale);
|
||||||
card.SetHighlight(spec.Highlight);
|
card.SetHighlight(spec.Highlight);
|
||||||
|
|
||||||
|
if (spec.IsHand)
|
||||||
|
{
|
||||||
|
_handBindings.Add(new HandCardBinding(
|
||||||
|
card,
|
||||||
|
spec.HandIndex,
|
||||||
|
spec.HandCount));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_cards.Count > 0)
|
if (_cards.Count > 0)
|
||||||
|
|
@ -228,6 +242,7 @@ namespace Vanguard.Board3D
|
||||||
public void Clear()
|
public void Clear()
|
||||||
{
|
{
|
||||||
ClearCards();
|
ClearCards();
|
||||||
|
_handBindings.Clear();
|
||||||
_view = null;
|
_view = null;
|
||||||
_hasRendered = false;
|
_hasRendered = false;
|
||||||
}
|
}
|
||||||
|
|
@ -597,7 +612,7 @@ namespace Vanguard.Board3D
|
||||||
for (var i = 0; i < count; i++)
|
for (var i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
var card = self.Hand[i];
|
var card = self.Hand[i];
|
||||||
var pose = _layout.GetHandPose(i, count);
|
GetHandViewPose(i, count, out var position, out var rotation, out var scale);
|
||||||
Color? highlight = null;
|
Color? highlight = null;
|
||||||
if (state.SelectedHandInstanceIds.Contains(card.InstanceId))
|
if (state.SelectedHandInstanceIds.Contains(card.InstanceId))
|
||||||
highlight = _config.selectedColor;
|
highlight = _config.selectedColor;
|
||||||
|
|
@ -609,15 +624,99 @@ namespace Vanguard.Board3D
|
||||||
Key = "H:" + card.InstanceId,
|
Key = "H:" + card.InstanceId,
|
||||||
Ref = BoardCardRef.Hand(self.Index, i, card),
|
Ref = BoardCardRef.Hand(self.Index, i, card),
|
||||||
Art = ResolveArt(card.PrintingId),
|
Art = ResolveArt(card.PrintingId),
|
||||||
Position = pose.Position,
|
Position = position,
|
||||||
BaseRotation = Quaternion.Euler(_config.handTilt, pose.Yaw, 0f),
|
BaseRotation = rotation,
|
||||||
FaceUp = true,
|
FaceUp = true,
|
||||||
Rested = false,
|
Rested = false,
|
||||||
|
Scale = scale,
|
||||||
|
IsHand = true,
|
||||||
|
HandIndex = i,
|
||||||
|
HandCount = count,
|
||||||
Highlight = highlight,
|
Highlight = highlight,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Рука висит в поле зрения камеры снизу по центру и всегда смотрит
|
||||||
|
/// на игрока. При отсутствии камеры используется старый настольный
|
||||||
|
/// вариант — для EditMode-проверок.
|
||||||
|
/// </summary>
|
||||||
|
private void GetHandViewPose(
|
||||||
|
int index,
|
||||||
|
int count,
|
||||||
|
out Vector3 position,
|
||||||
|
out Quaternion rotation,
|
||||||
|
out float scale)
|
||||||
|
{
|
||||||
|
if (_camera == null)
|
||||||
|
_camera = Camera.main;
|
||||||
|
|
||||||
|
if (_camera == null)
|
||||||
|
{
|
||||||
|
var fallback = _layout.GetHandPose(index, count);
|
||||||
|
position = fallback.Position;
|
||||||
|
rotation = Quaternion.Euler(_config.handTilt, fallback.Yaw, 0f);
|
||||||
|
scale = 1f;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var safeCount = Mathf.Max(1, count);
|
||||||
|
var step = _config.handSpacing;
|
||||||
|
if (safeCount > 1)
|
||||||
|
step = Mathf.Min(step, _config.handViewMaxWidth / (safeCount - 1));
|
||||||
|
|
||||||
|
var offset = index - (safeCount - 1) * 0.5f;
|
||||||
|
var halfHeight = Mathf.Tan(
|
||||||
|
_camera.fieldOfView * 0.5f * Mathf.Deg2Rad) * _config.handViewDistance;
|
||||||
|
var x = offset * step;
|
||||||
|
var y = (_config.handViewportY - 0.5f) * 2f * halfHeight
|
||||||
|
+ (1f - Mathf.Abs(offset) / safeCount) * _config.handViewArc;
|
||||||
|
var z = _config.handViewDistance
|
||||||
|
+ Mathf.Abs(offset) * _config.handViewArc;
|
||||||
|
|
||||||
|
position = _camera.transform.TransformPoint(new Vector3(x, y, z));
|
||||||
|
var roll = -offset * _config.handViewRoll;
|
||||||
|
rotation = _camera.transform.rotation
|
||||||
|
* Quaternion.Euler(0f, 0f, roll)
|
||||||
|
* Quaternion.Euler(-90f, 0f, 0f);
|
||||||
|
scale = _config.handViewScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateHandView()
|
||||||
|
{
|
||||||
|
if (_handBindings.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_camera == null)
|
||||||
|
{
|
||||||
|
_camera = Camera.main;
|
||||||
|
if (_camera == null)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var binding in _handBindings)
|
||||||
|
{
|
||||||
|
if (binding.Card == null || binding.Card.IsDragging)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
GetHandViewPose(
|
||||||
|
binding.Index,
|
||||||
|
binding.Count,
|
||||||
|
out var position,
|
||||||
|
out var rotation,
|
||||||
|
out var scale);
|
||||||
|
binding.Card.SetPose(
|
||||||
|
position,
|
||||||
|
rotation,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
scale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void UpdatePiles(MatchView view)
|
private void UpdatePiles(MatchView view)
|
||||||
{
|
{
|
||||||
var self = view.Players.FirstOrDefault(player => player.Index == view.You);
|
var self = view.Players.FirstOrDefault(player => player.Index == view.You);
|
||||||
|
|
@ -717,6 +816,8 @@ namespace Vanguard.Board3D
|
||||||
|
|
||||||
private void Update()
|
private void Update()
|
||||||
{
|
{
|
||||||
|
UpdateHandView();
|
||||||
|
|
||||||
var delta = Time.unscaledDeltaTime;
|
var delta = Time.unscaledDeltaTime;
|
||||||
foreach (var card in _cards.Values)
|
foreach (var card in _cards.Values)
|
||||||
{
|
{
|
||||||
|
|
@ -909,6 +1010,9 @@ namespace Vanguard.Board3D
|
||||||
var targetValue = target.Value;
|
var targetValue = target.Value;
|
||||||
var targetRef = targetValue.ToRef();
|
var targetRef = targetValue.ToRef();
|
||||||
|
|
||||||
|
if (source.IsHand)
|
||||||
|
_handBindings.RemoveAll(binding => binding.Card == card);
|
||||||
|
|
||||||
// Атака — это клик по цели, карта остаётся на своём круге.
|
// Атака — это клик по цели, карта остаётся на своём круге.
|
||||||
if ((source.IsField && targetValue.Player != source.Player)
|
if ((source.IsField && targetValue.Player != source.Player)
|
||||||
|| (source.IsHand && targetValue.Player != source.Player
|
|| (source.IsHand && targetValue.Player != source.Player
|
||||||
|
|
@ -1120,6 +1224,20 @@ namespace Vanguard.Board3D
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed class HandCardBinding
|
||||||
|
{
|
||||||
|
public HandCardBinding(Card3D card, int index, int count)
|
||||||
|
{
|
||||||
|
Card = card;
|
||||||
|
Index = index;
|
||||||
|
Count = count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Card3D Card { get; }
|
||||||
|
public int Index { get; }
|
||||||
|
public int Count { get; }
|
||||||
|
}
|
||||||
|
|
||||||
private sealed class CardSpec
|
private sealed class CardSpec
|
||||||
{
|
{
|
||||||
public string Key;
|
public string Key;
|
||||||
|
|
@ -1130,6 +1248,10 @@ namespace Vanguard.Board3D
|
||||||
public bool FaceUp;
|
public bool FaceUp;
|
||||||
public bool Rested;
|
public bool Rested;
|
||||||
public bool DamageLayout;
|
public bool DamageLayout;
|
||||||
|
public float Scale = 1f;
|
||||||
|
public bool IsHand;
|
||||||
|
public int HandIndex;
|
||||||
|
public int HandCount;
|
||||||
public Color? Highlight;
|
public Color? Highlight;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,53 @@ namespace Vanguard.Tests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Hand_Is_Anchored_To_Camera_Bottom_Center_And_Faces_Player()
|
||||||
|
{
|
||||||
|
var config = PlaymatLayoutConfig.CreateDefault();
|
||||||
|
var cameraGo = new GameObject("Main Camera", typeof(Camera))
|
||||||
|
{
|
||||||
|
tag = "MainCamera",
|
||||||
|
};
|
||||||
|
var camera = cameraGo.GetComponent<Camera>();
|
||||||
|
camera.fieldOfView = config.cameraFov;
|
||||||
|
camera.transform.position = config.cameraPosition;
|
||||||
|
camera.transform.rotation = Quaternion.LookRotation(
|
||||||
|
config.cameraTarget - config.cameraPosition,
|
||||||
|
Vector3.up);
|
||||||
|
|
||||||
|
var board = GameBoard3D.Create(null, config);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
board.SetVisible(true);
|
||||||
|
board.Render(BuildView(you: 0), new BoardRenderState());
|
||||||
|
|
||||||
|
var hand = ActiveCards()
|
||||||
|
.Where(card => card.Ref.Zone == BoardZone.Hand)
|
||||||
|
.ToList();
|
||||||
|
Assert.AreEqual(2, hand.Count);
|
||||||
|
|
||||||
|
foreach (var card in hand)
|
||||||
|
{
|
||||||
|
var viewport = camera.WorldToViewportPoint(card.transform.position);
|
||||||
|
Assert.Greater(viewport.z, 0f, "Hand card must be in front of the camera");
|
||||||
|
Assert.That(viewport.x, Is.InRange(0.15f, 0.85f));
|
||||||
|
Assert.That(viewport.y, Is.InRange(0.01f, 0.55f));
|
||||||
|
|
||||||
|
var toCamera = (camera.transform.position - card.transform.position).normalized;
|
||||||
|
Assert.Greater(
|
||||||
|
Vector3.Dot(card.transform.up, toCamera),
|
||||||
|
0.8f,
|
||||||
|
"Hand card must face the camera");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Object.DestroyImmediate(board.gameObject);
|
||||||
|
Object.DestroyImmediate(cameraGo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Pool_Reuses_Cards_Between_Snapshots()
|
public void Pool_Reuses_Cards_Between_Snapshots()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -854,15 +854,19 @@ Battle; Boot и общие сервисы остаются персистент
|
||||||
- `GameBoard3D` строит стол, круги, зоны, стопки и веер руки; raycast-клики,
|
- `GameBoard3D` строит стол, круги, зоны, стопки и веер руки; raycast-клики,
|
||||||
drag&drop на плоскость стола с подъёмом, подсветкой допустимых кругов и
|
drag&drop на плоскость стола с подъёмом, подсветкой допустимых кругов и
|
||||||
индикатором точки броска. Ошибка интента возвращает карту на место.
|
индикатором точки броска. Ошибка интента возвращает карту на место.
|
||||||
- Камера: `BoardCameraRig` — ПКМ орбита (±32°), СКМ панорама, колесо зум
|
- Камера приближена: позиция `(0, 6.9, -9.8)`, цель `(0, 0, -0.15)`, FOV 50.
|
||||||
|
Рука не лежит на столе, а закреплена в экране снизу по центру
|
||||||
|
(`handViewportY`, `handViewDistance`) и всегда развёрнута лицом к игроку;
|
||||||
|
при орбите камеры веер следует за полем зрения, драг из руки уходит на стол.
|
||||||
|
- `BoardCameraRig` — ПКМ орбита (±32°), СКМ панорама, колесо зум
|
||||||
(9.5–16); ограничения в конфиге.
|
(9.5–16); ограничения в конфиге.
|
||||||
- Душа: клик по стопке или кнопка «Душа: n» в верхней панели открывает
|
- Душа: клик по стопке или кнопка «Душа: n» в верхней панели открывает
|
||||||
оверлей со списком карт.
|
оверлей со списком карт.
|
||||||
- Песочница: `Assets/Vanguard/Scenes/GameBoard3D.unity` +
|
- Песочница: `Assets/Vanguard/Scenes/GameBoard3D.unity` +
|
||||||
`PlaymatSandbox`; собирается `Vanguard → Rebuild Playmat Sandbox`,
|
`PlaymatSandbox`; собирается `Vanguard → Rebuild Playmat Sandbox`,
|
||||||
проверяется `SceneSmoke.RunPlaymat`.
|
проверяется `SceneSmoke.RunPlaymat`.
|
||||||
- Проверки: EditMode **17/17** (геометрия, зеркальность, правила, рендер
|
- Проверки: EditMode **18/18** (геометрия, зеркальность, правила, рендер
|
||||||
3D-карт, пул), `SCENE_SMOKE success=True`,
|
3D-карт, пул, рука в поле зрения), `SCENE_SMOKE success=True`,
|
||||||
`PLAYMAT_SMOKE success=True cards=20`.
|
`PLAYMAT_SMOKE success=True cards=20 hand=5/5 fieldVisible=True`.
|
||||||
- Ограничения: retire/ghost-анимации старого 2D-поля пока не перенесены;
|
- Ограничения: retire/ghost-анимации старого 2D-поля пока не перенесены;
|
||||||
модели карт/стола — процедурные примитивы.
|
модели карт/стола — процедурные примитивы.
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,9 @@
|
||||||
остаются uGUI. Карты — тонкие 3D-объекты с артом, рубашкой, торцом и
|
остаются uGUI. Карты — тонкие 3D-объекты с артом, рубашкой, торцом и
|
||||||
коллайдером. Rest (атака/поддержка) — поворот по часовой стрелке вокруг Y,
|
коллайдером. Rest (атака/поддержка) — поворот по часовой стрелке вокруг Y,
|
||||||
урон лежит на боку против часовой; закрытость — переворот вокруг X.
|
урон лежит на боку против часовой; закрытость — переворот вокруг X.
|
||||||
Драг-н-дроп на плоскость стола с подсветкой целей и плавным снапом.
|
Рука закреплена в поле зрения камеры снизу по центру и всегда смотрит
|
||||||
|
на игрока, а не лежит на столе. Драг-н-дроп на плоскость стола с
|
||||||
|
подсветкой целей и плавным снапом; камера приближена.
|
||||||
- Поле: у каждого игрока два ряда `R V R` / `R R R` (V + 5 RC), у
|
- Поле: у каждого игрока два ряда `R V R` / `R R R` (V + 5 RC), у
|
||||||
противника зеркально; между полями общий круг защиты; душа — стопка под
|
противника зеркально; между полями общий круг защиты; душа — стопка под
|
||||||
V; колода, дроп и урон — 3D-стопки/карты. Локальный игрок всегда снизу,
|
V; колода, дроп и урон — 3D-стопки/карты. Локальный игрок всегда снизу,
|
||||||
|
|
@ -131,10 +133,11 @@ curl http://127.0.0.1:18080/health
|
||||||
-executeMethod Vanguard.Editor.SceneSmoke.RunPlaymat
|
-executeMethod Vanguard.Editor.SceneSmoke.RunPlaymat
|
||||||
```
|
```
|
||||||
|
|
||||||
Клиентские проверки: **17/17** EditMode (сборка всех экранов, импорт арта,
|
Клиентские проверки: **18/18** EditMode (сборка всех экранов, импорт арта,
|
||||||
перевод статических/динамических строк, live-переключение языка, рендер
|
перевод статических/динамических строк, live-переключение языка, рендер
|
||||||
3D-доски с уроном и отдыхающей картой, геометрия поля и правила
|
3D-доски с уроном и отдыхающей картой, геометрия поля, правила
|
||||||
перемещения), `SCENE_SMOKE success=True`, `PLAYMAT_SMOKE success=True cards=20`.
|
перемещения, рука в поле зрения камеры), `SCENE_SMOKE success=True`,
|
||||||
|
`PLAYMAT_SMOKE success=True cards=20 hand=5/5 fieldVisible=True`.
|
||||||
|
|
||||||
`SceneSmoke` заменяет PlayMode-тест загрузки сцен: Single-загрузка сцены
|
`SceneSmoke` заменяет PlayMode-тест загрузки сцен: Single-загрузка сцены
|
||||||
ломает стандартный PlayMode test runner. `SceneBuilder` пересобирает сцены:
|
ломает стандартный PlayMode test runner. `SceneBuilder` пересобирает сцены:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue