Refine board camera, hand, piles and card backs
CI / test (push) Successful in 31m46s
Details
CI / test (push) Successful in 31m46s
Details
This commit is contained in:
parent
b4fa5d132f
commit
e3243e58b6
|
|
@ -51,15 +51,15 @@ namespace Vanguard.App
|
|||
|
||||
camera.clearFlags = CameraClearFlags.SolidColor;
|
||||
camera.backgroundColor = palette.Background;
|
||||
camera.fieldOfView = theme == SceneTheme.Battle ? 50f : 52f;
|
||||
camera.fieldOfView = theme == SceneTheme.Battle ? 46f : 52f;
|
||||
camera.transform.position = theme switch
|
||||
{
|
||||
SceneTheme.Battle => new Vector3(1.4f, 9f, -5.2f),
|
||||
SceneTheme.Battle => new Vector3(1.4f, 5.8f, -8f),
|
||||
SceneTheme.Login => new Vector3(0f, 2.4f, -9.5f),
|
||||
_ => new Vector3(0f, 3.2f, -10f),
|
||||
};
|
||||
camera.transform.rotation = theme == SceneTheme.Battle
|
||||
? Quaternion.Euler(60.5f, 0f, 0f)
|
||||
? Quaternion.Euler(35.9f, 0f, 0f)
|
||||
: Quaternion.Euler(8f, 0f, 0f);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,10 +37,10 @@ MonoBehaviour:
|
|||
handMaxWidth: 7.4
|
||||
handArc: 0.035
|
||||
handViewportX: 0.44
|
||||
handViewportY: 0.18
|
||||
handViewDistance: 3.8
|
||||
handViewMaxWidth: 1.8
|
||||
handViewScale: 0.62
|
||||
handViewportY: 0.1
|
||||
handViewDistance: 4
|
||||
handViewMaxWidth: 1.4
|
||||
handViewScale: 0.5
|
||||
handViewRoll: 3.2
|
||||
handViewArc: 0.02
|
||||
guardianRowGap: 0.55
|
||||
|
|
@ -48,9 +48,9 @@ MonoBehaviour:
|
|||
guardianMaxWidth: 3.6
|
||||
damageX: 3
|
||||
pileX: 2.95
|
||||
cameraPosition: {x: 1.4, y: 9, z: -5.2}
|
||||
cameraTarget: {x: 1.4, y: 0, z: -0.1}
|
||||
cameraFov: 50
|
||||
cameraPosition: {x: 1.4, y: 5.8, z: -8}
|
||||
cameraTarget: {x: 1.4, y: 0, z: 0}
|
||||
cameraFov: 46
|
||||
tableColor: {r: 0.045, g: 0.04, b: 0.055, a: 1}
|
||||
matSelfColor: {r: 0.055, g: 0.095, b: 0.16, a: 1}
|
||||
matOpponentColor: {r: 0.13, g: 0.065, b: 0.075, a: 1}
|
||||
|
|
|
|||
|
|
@ -53,11 +53,11 @@ namespace Vanguard.Board3D
|
|||
[Tooltip("Доля ширины экрана для центра карт: 0 — левый край, 0.5 — середина.")]
|
||||
[Range(0.05f, 0.95f)] public float handViewportX = 0.44f;
|
||||
[Tooltip("Доля высоты экрана для центра карт: 0 — низ, 0.5 — середина.")]
|
||||
[Range(0.05f, 0.45f)] public float handViewportY = 0.18f;
|
||||
[Range(0.05f, 0.45f)] public float handViewportY = 0.10f;
|
||||
[Tooltip("Расстояние от камеры до веера руки, метры.")]
|
||||
public float handViewDistance = 3.8f;
|
||||
public float handViewMaxWidth = 1.8f;
|
||||
public float handViewScale = 0.62f;
|
||||
public float handViewDistance = 4.0f;
|
||||
public float handViewMaxWidth = 1.4f;
|
||||
public float handViewScale = 0.5f;
|
||||
public float handViewRoll = 3.2f;
|
||||
public float handViewArc = 0.02f;
|
||||
|
||||
|
|
@ -74,9 +74,9 @@ namespace Vanguard.Board3D
|
|||
|
||||
[Header("Камера")]
|
||||
[Tooltip("Фиксированная камера над полем. Не двигается игроком.")]
|
||||
public Vector3 cameraPosition = new Vector3(1.4f, 9.0f, -5.2f);
|
||||
public Vector3 cameraTarget = new Vector3(1.4f, 0f, -0.1f);
|
||||
public float cameraFov = 50f;
|
||||
public Vector3 cameraPosition = new Vector3(1.4f, 5.8f, -8.0f);
|
||||
public Vector3 cameraTarget = new Vector3(1.4f, 0f, 0f);
|
||||
public float cameraFov = 46f;
|
||||
|
||||
[Header("Палитра")]
|
||||
public Color tableColor = new Color(0.045f, 0.04f, 0.055f);
|
||||
|
|
|
|||
|
|
@ -78,14 +78,14 @@ namespace Vanguard.Board3D
|
|||
public Vector3 DeckPosition(int localSeat)
|
||||
{
|
||||
var x = (localSeat == 0 ? 1f : -1f) * _config.pileX;
|
||||
var z = ZSign(localSeat) * (_config.backRowZ + 0.15f);
|
||||
var z = ZSign(localSeat) * (_config.frontRowZ + 0.05f);
|
||||
return new Vector3(x, _config.pileLift, z);
|
||||
}
|
||||
|
||||
public Vector3 DropPosition(int localSeat)
|
||||
{
|
||||
var x = (localSeat == 0 ? 1f : -1f) * _config.pileX;
|
||||
var z = ZSign(localSeat) * (_config.frontRowZ + 0.05f);
|
||||
var z = ZSign(localSeat) * (_config.backRowZ + 0.15f);
|
||||
return new Vector3(x, _config.pileLift, z);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -45,20 +45,12 @@ namespace Vanguard.Board3D
|
|||
new Vector3(halfW, 0f, halfH),
|
||||
new Vector3(-halfW, 0f, halfH),
|
||||
},
|
||||
uv = front
|
||||
? new[]
|
||||
uv = new[]
|
||||
{
|
||||
new Vector2(0f, 0f),
|
||||
new Vector2(1f, 0f),
|
||||
new Vector2(1f, 1f),
|
||||
new Vector2(0f, 1f),
|
||||
}
|
||||
: new[]
|
||||
{
|
||||
new Vector2(0f, 1f),
|
||||
new Vector2(1f, 1f),
|
||||
new Vector2(1f, 0f),
|
||||
new Vector2(0f, 0f),
|
||||
},
|
||||
normals = new[] { normal, normal, normal, normal },
|
||||
triangles = new[]
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ namespace Vanguard.Board3D
|
|||
public event Action<BoardCardRef> SlotClicked;
|
||||
public event Action<BoardCardRef, BoardCardRef> CardDropped;
|
||||
public event Action<BoardCardRef> PileClicked;
|
||||
public event Action<BoardCardRef> PileRightClicked;
|
||||
|
||||
public static GameBoard3D Create(Transform parent, PlaymatLayoutConfig config = null)
|
||||
{
|
||||
|
|
@ -276,7 +277,9 @@ namespace Vanguard.Board3D
|
|||
new Vector3(width - 0.3f, 0.012f, 0.055f),
|
||||
divider);
|
||||
|
||||
var railMaterial = BoardMaterials.CreateLit(_config.lineColor, 0.9f);
|
||||
var railMaterial = BoardMaterials.CreateUnlit(
|
||||
null,
|
||||
new Color(0.40f, 0.29f, 0.11f));
|
||||
CreateBox(
|
||||
"Rail Left",
|
||||
new Vector3(-(_config.boardHalfWidth + 1.02f), 0.04f, 0f),
|
||||
|
|
@ -809,9 +812,27 @@ namespace Vanguard.Board3D
|
|||
card.Tick(delta);
|
||||
}
|
||||
|
||||
HandleRightClick();
|
||||
HandlePointer();
|
||||
}
|
||||
|
||||
private void HandleRightClick()
|
||||
{
|
||||
var mouse = Mouse.current;
|
||||
if (mouse == null
|
||||
|| !mouse.rightButton.wasPressedThisFrame
|
||||
|| IsPointerOverUi())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var ray = GetRay(mouse.position.ReadValue());
|
||||
if (!TryRaycast(ray, out _, out _, out var pile) || pile == null)
|
||||
return;
|
||||
|
||||
PileRightClicked?.Invoke(pile.Ref);
|
||||
}
|
||||
|
||||
private void HandlePointer()
|
||||
{
|
||||
if (!_hasRendered && _view == null)
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ namespace Vanguard.Board3D
|
|||
|
||||
_top.transform.localPosition = new Vector3(
|
||||
0f,
|
||||
(layers - 1) * thickness + 0.0015f,
|
||||
((layers - 0.5f) * thickness) + 0.0015f,
|
||||
0f);
|
||||
|
||||
if (topTexture != null)
|
||||
|
|
|
|||
|
|
@ -40,8 +40,6 @@ namespace Vanguard.UI
|
|||
private readonly Button _concede;
|
||||
private readonly Text _concedeLabel;
|
||||
private readonly Button _reconnect;
|
||||
private Button _soulCounter;
|
||||
private Text _soulCounterLabel;
|
||||
private readonly RectTransform _result;
|
||||
private RectTransform _logContent;
|
||||
private Text _resultText;
|
||||
|
|
@ -113,12 +111,6 @@ namespace Vanguard.UI
|
|||
UiKit.MakeLiteral(_topInfo);
|
||||
UiKit.SetPreferredHeight(_topInfo, 38f);
|
||||
|
||||
_soulCounter = UiKit.CreateButton(
|
||||
top, "SoulCounter", "Душа: 0", OnSoulCounterClicked, 16);
|
||||
_soulCounterLabel = _soulCounter.GetComponentInChildren<Text>();
|
||||
var soulElement = UiKit.SetPreferredHeight(_soulCounter, 44f, 0f);
|
||||
soulElement.preferredWidth = 132f;
|
||||
|
||||
_clock = UiKit.CreateText(
|
||||
top, "Clock", "—", 22, Color.white, TextAnchor.MiddleRight);
|
||||
UiKit.SetPreferredHeight(_clock, 44f, 0f);
|
||||
|
|
@ -198,6 +190,7 @@ namespace Vanguard.UI
|
|||
_board3D.SlotClicked += OnBoard3DSlotClicked;
|
||||
_board3D.CardDropped += OnBoard3DCardDropped;
|
||||
_board3D.PileClicked += OnBoard3DPileClicked;
|
||||
_board3D.PileRightClicked += OnBoard3DPileRightClicked;
|
||||
_board3D.SetVisible(false);
|
||||
|
||||
_root.gameObject.SetActive(false);
|
||||
|
|
@ -235,8 +228,6 @@ namespace Vanguard.UI
|
|||
if (_soulOverlay != null)
|
||||
_soulOverlay.gameObject.SetActive(false);
|
||||
_topInfo.text = (info.DeckName ?? string.Empty) + " · " + info.MatchId;
|
||||
_soulCounterLabel.text = "Душа: 0";
|
||||
_soulCounter.interactable = false;
|
||||
if (_selfStatsText != null)
|
||||
_selfStatsText.text = string.Empty;
|
||||
if (_opponentStatsText != null)
|
||||
|
|
@ -779,7 +770,6 @@ namespace Vanguard.UI
|
|||
|
||||
_board3D?.SetSelfPlayer(_view.You);
|
||||
_board3D?.Render(_view, BuildBoardRenderState());
|
||||
UpdateSoulCounter();
|
||||
UpdateStatsOverlay();
|
||||
|
||||
RebuildIntents(_view);
|
||||
|
|
@ -917,7 +907,7 @@ namespace Vanguard.UI
|
|||
break;
|
||||
|
||||
case BoardZone.Soul:
|
||||
ShowSoul(card.Player);
|
||||
SetStatus("Душа открывается правой кнопкой мыши.", Muted);
|
||||
break;
|
||||
|
||||
case BoardZone.Guardian:
|
||||
|
|
@ -998,29 +988,29 @@ namespace Vanguard.UI
|
|||
OnIntentClicked(intent);
|
||||
}
|
||||
|
||||
private void OnSoulCounterClicked()
|
||||
private void OnBoard3DPileRightClicked(BoardCardRef pile)
|
||||
{
|
||||
if (_view != null)
|
||||
ShowSoul(_view.You);
|
||||
}
|
||||
|
||||
private void UpdateSoulCounter()
|
||||
{
|
||||
if (_soulCounterLabel == null)
|
||||
if (_view == null)
|
||||
return;
|
||||
|
||||
var self = _view?.Players.FirstOrDefault(player => player.Index == _view.You);
|
||||
_soulCounterLabel.text = "Душа: " + (self?.Soul?.Count ?? 0);
|
||||
_soulCounter.interactable = _view != null && !_view.IsFinished;
|
||||
if (pile.Zone == BoardZone.Soul)
|
||||
ShowSoul(pile.Player);
|
||||
}
|
||||
|
||||
private void OnBoard3DPileClicked(BoardCardRef pile)
|
||||
{ if (_view == null)
|
||||
{
|
||||
if (_view == null)
|
||||
return;
|
||||
|
||||
if (pile.Zone == BoardZone.Soul)
|
||||
{
|
||||
ShowSoul(pile.Player);
|
||||
SetStatus("Душа открывается правой кнопкой мыши.", Muted);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pile.Zone == BoardZone.Drop)
|
||||
{
|
||||
ShowDrop(pile.Player);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1029,14 +1019,8 @@ namespace Vanguard.UI
|
|||
return;
|
||||
|
||||
if (pile.Zone == BoardZone.Deck)
|
||||
{
|
||||
SetStatus("В колоде " + player.DeckCount + " карт.", Muted);
|
||||
}
|
||||
else if (pile.Zone == BoardZone.Drop)
|
||||
{
|
||||
SetStatus("В дропе " + (player.Drop?.Count ?? 0) + " карт.", Muted);
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildStatsOverlays()
|
||||
{
|
||||
|
|
@ -1157,26 +1141,43 @@ namespace Vanguard.UI
|
|||
private void ShowSoul(int playerIndex)
|
||||
{
|
||||
var player = _view?.Players.FirstOrDefault(item => item.Index == playerIndex);
|
||||
if (player == null || _soulOverlay == null)
|
||||
if (player == null)
|
||||
return;
|
||||
|
||||
_soulTitle.text = "Душа · " + player.Name + " · " + (player.Soul?.Count ?? 0);
|
||||
ShowCardList("Душа", player.Name, player.Soul);
|
||||
}
|
||||
|
||||
private void ShowDrop(int playerIndex)
|
||||
{
|
||||
var player = _view?.Players.FirstOrDefault(item => item.Index == playerIndex);
|
||||
if (player == null)
|
||||
return;
|
||||
|
||||
ShowCardList("Дроп", player.Name, player.Drop);
|
||||
}
|
||||
|
||||
private void ShowCardList(string title, string playerName, List<CardView> cards)
|
||||
{
|
||||
if (_soulOverlay == null)
|
||||
return;
|
||||
|
||||
_soulTitle.text = title + " · " + playerName + " · " + (cards?.Count ?? 0);
|
||||
ClearChildren(_soulContent);
|
||||
|
||||
if (player.Soul == null || player.Soul.Count == 0)
|
||||
if (cards == null || cards.Count == 0)
|
||||
{
|
||||
var empty = UiKit.CreateText(
|
||||
_soulContent,
|
||||
"SoulEmpty",
|
||||
"В душе нет карт.",
|
||||
"CardListEmpty",
|
||||
"Нет карт.",
|
||||
17,
|
||||
Muted);
|
||||
UiKit.SetPreferredHeight(empty, 80f);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = player.Soul.Count - 1; i >= 0; i--)
|
||||
AddSoulRow(player.Soul[i]);
|
||||
for (var i = cards.Count - 1; i >= 0; i--)
|
||||
AddSoulRow(cards[i]);
|
||||
}
|
||||
|
||||
_soulOverlay.SetAsLastSibling();
|
||||
|
|
|
|||
|
|
@ -80,6 +80,21 @@ namespace Vanguard.Tests
|
|||
Assert.IsTrue(hand.All(position => position.z < -4f));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Deck_Is_Closer_Than_Drop_After_Swap()
|
||||
{
|
||||
var config = PlaymatLayoutConfig.CreateDefault();
|
||||
var layout = new PlaymatLayout(config);
|
||||
|
||||
var deck = layout.DeckPosition(0);
|
||||
var drop = layout.DropPosition(0);
|
||||
Assert.Greater(deck.z, drop.z);
|
||||
|
||||
var opponentDeck = layout.DeckPosition(1);
|
||||
var opponentDrop = layout.DropPosition(1);
|
||||
Assert.Less(opponentDeck.z, opponentDrop.z);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Drop_Target_Finds_Own_And_Opponent_Circles()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -858,21 +858,26 @@ Battle; Boot и общие сервисы остаются персистент
|
|||
- `GameBoard3D` строит стол, круги, зоны, стопки и веер руки; raycast-клики,
|
||||
drag&drop на плоскость стола с подъёмом, подсветкой допустимых кругов и
|
||||
индикатором точки броска. Ошибка интента возвращает карту на место.
|
||||
- Камера фиксирована над полем: позиция `(1.4, 9, -5.2)`, цель
|
||||
`(1.4, 0, -0.1)`, FOV 50; игрок её не двигает. Рука не лежит на столе,
|
||||
- Камера фиксирована над полем: позиция `(1.4, 5.8, -8)`, цель
|
||||
`(1.4, 0, 0)`, FOV 46; игрок её не двигает. Рука не лежит на столе,
|
||||
а закреплена в экране снизу по центру (`handViewportX/Y`,
|
||||
`handViewDistance`) и всегда развёрнута лицом к игроку; драг из руки
|
||||
уходит на стол.
|
||||
`handViewDistance`) и всегда развёрнута лицом к игроку; уменьшена, чтобы
|
||||
не перекрывать задний ряд RC. Драг из руки уходит на стол.
|
||||
- Статистика карт вынесена в отдельные UI-панели: своя — снизу слева,
|
||||
противника — сверху справа (`HAND/DECK/DROP/DMG/SOUL`); 3D-подписи
|
||||
противника — сверху справа (`Рука/Колода/Дроп/Урон/Душа`); 3D-подписи
|
||||
стопок и зон больше не дублируют числа.
|
||||
- Колода и дроп поменяны местами: колода ближе к игроку. Верх дропа
|
||||
показывает последнюю положенную карту, ЛКМ открывает список всего дропа.
|
||||
Душа открывается только ПКМ по стопке под V; кнопка души из топ-бара
|
||||
убрана.
|
||||
- Душа: клик по стопке или кнопка «Душа: n» в верхней панели открывает
|
||||
оверлей со списком карт.
|
||||
- Песочница: `Assets/Vanguard/Scenes/GameBoard3D.unity` +
|
||||
`PlaymatSandbox`; собирается `Vanguard → Rebuild Playmat Sandbox`,
|
||||
проверяется `SceneSmoke.RunPlaymat`.
|
||||
- Проверки: EditMode **18/18** (геометрия, зеркальность, правила, рендер
|
||||
3D-карт, пул, рука в поле зрения), `SCENE_SMOKE success=True`,
|
||||
- Проверки: EditMode **19/19** (геометрия, зеркальность, правила, рендер
|
||||
3D-карт, пул, рука в поле зрения, обмен колоды/дропа),
|
||||
`SCENE_SMOKE success=True`,
|
||||
`PLAYMAT_SMOKE success=True cards=20 hand=5/5 fieldVisible=True`.
|
||||
- Ограничения: retire/ghost-анимации старого 2D-поля пока не перенесены;
|
||||
модели карт/стола — процедурные примитивы.
|
||||
|
|
|
|||
|
|
@ -67,17 +67,19 @@
|
|||
урон лежит на боку против часовой; закрытость — переворот вокруг X.
|
||||
Рубашка — стандартная VF Vanguard (`Resources/CardArt/vanguard_back.png`),
|
||||
общая для 2D и 3D. Рука закреплена в поле зрения камеры снизу по центру
|
||||
и всегда смотрит на игрока, а не лежит на столе. Камера фиксирована над
|
||||
полем под небольшим углом и не двигается игроком. Драг-н-дроп на
|
||||
плоскость стола с подсветкой целей и плавным снапом.
|
||||
и всегда смотрит на игрока, а не лежит на столе; уменьшена, чтобы не
|
||||
закрывать задний ряд RC. Камера фиксирована над полем под небольшим
|
||||
углом и не двигается игроком. Драг-н-дроп на плоскость стола с
|
||||
подсветкой целей и плавным снапом.
|
||||
- Поле: у каждого игрока два ряда `R V R` / `R R R` (V + 5 RC), у
|
||||
противника зеркально; между полями общий круг защиты; душа — стопка под
|
||||
V; колода, дроп и урон — 3D-стопки/карты. Локальный игрок всегда снизу,
|
||||
даже если он за вторым местом. Карты в кругах V/R принудительно
|
||||
показываются лицом вверх.
|
||||
- Статистика карт — отдельное UI-наложение: своя панель снизу слева,
|
||||
противника — сверху справа. Кнопка «Душа: n» в верхней панели и клик по
|
||||
стопке души открывают список карт души.
|
||||
противника — сверху справа. Душа открывается только ПКМ по стопке под V;
|
||||
дроп показывает верхнюю карту, по ЛКМ — список всех карт дропа; колода и
|
||||
дроп поменяны местами (колода ближе к игроку).
|
||||
- Зона урона: карты лежат открытыми и на боку; после Counter Blast
|
||||
выбранная карта переворачивается рубашкой. Heal выбирает карту урона;
|
||||
Counter Blast — открытую карту до активации способности.
|
||||
|
|
@ -137,10 +139,11 @@ curl http://127.0.0.1:18080/health
|
|||
-executeMethod Vanguard.Editor.SceneSmoke.RunPlaymat
|
||||
```
|
||||
|
||||
Клиентские проверки: **18/18** EditMode (сборка всех экранов, импорт арта,
|
||||
Клиентские проверки: **19/19** EditMode (сборка всех экранов, импорт арта,
|
||||
перевод статических/динамических строк, live-переключение языка, рендер
|
||||
3D-доски с уроном и отдыхающей картой, геометрия поля, правила
|
||||
перемещения, рука в поле зрения камеры), `SCENE_SMOKE success=True`,
|
||||
перемещения, рука в поле зрения камеры, обмен колоды/дропа),
|
||||
`SCENE_SMOKE success=True`,
|
||||
`PLAYMAT_SMOKE success=True cards=20 hand=5/5 fieldVisible=True`.
|
||||
|
||||
`SceneSmoke` заменяет PlayMode-тест загрузки сцен: Single-загрузка сцены
|
||||
|
|
|
|||
Loading…
Reference in New Issue