Fix card faces, lock camera, add stats overlay and standard sleeve back
CI / test (push) Successful in 2m43s
Details
CI / test (push) Successful in 2m43s
Details
This commit is contained in:
parent
f2a01bae16
commit
b4fa5d132f
Binary file not shown.
|
After Width: | Height: | Size: 63 KiB |
|
|
@ -0,0 +1,132 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: fcf11b652a4a6f744917c23591d4b479
|
||||||
|
TextureImporter:
|
||||||
|
internalIDToNameTable: []
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 13
|
||||||
|
mipmaps:
|
||||||
|
mipMapMode: 0
|
||||||
|
enableMipMap: 1
|
||||||
|
sRGBTexture: 1
|
||||||
|
linearTexture: 0
|
||||||
|
fadeOut: 0
|
||||||
|
borderMipMap: 0
|
||||||
|
mipMapsPreserveCoverage: 0
|
||||||
|
alphaTestReferenceValue: 0.5
|
||||||
|
mipMapFadeDistanceStart: 1
|
||||||
|
mipMapFadeDistanceEnd: 3
|
||||||
|
bumpmap:
|
||||||
|
convertToNormalMap: 0
|
||||||
|
externalNormalMap: 0
|
||||||
|
heightScale: 0.25
|
||||||
|
normalMapFilter: 0
|
||||||
|
flipGreenChannel: 0
|
||||||
|
isReadable: 0
|
||||||
|
streamingMipmaps: 0
|
||||||
|
streamingMipmapsPriority: 0
|
||||||
|
vTOnly: 0
|
||||||
|
ignoreMipmapLimit: 0
|
||||||
|
grayScaleToAlpha: 0
|
||||||
|
generateCubemap: 6
|
||||||
|
cubemapConvolution: 0
|
||||||
|
seamlessCubemap: 0
|
||||||
|
textureFormat: 1
|
||||||
|
maxTextureSize: 2048
|
||||||
|
textureSettings:
|
||||||
|
serializedVersion: 2
|
||||||
|
filterMode: 1
|
||||||
|
aniso: 1
|
||||||
|
mipBias: 0
|
||||||
|
wrapU: 0
|
||||||
|
wrapV: 0
|
||||||
|
wrapW: 0
|
||||||
|
nPOTScale: 1
|
||||||
|
lightmap: 0
|
||||||
|
compressionQuality: 50
|
||||||
|
spriteMode: 0
|
||||||
|
spriteExtrude: 1
|
||||||
|
spriteMeshType: 1
|
||||||
|
alignment: 0
|
||||||
|
spritePivot: {x: 0.5, y: 0.5}
|
||||||
|
spritePixelsToUnits: 100
|
||||||
|
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||||
|
spriteGenerateFallbackPhysicsShape: 1
|
||||||
|
alphaUsage: 1
|
||||||
|
alphaIsTransparency: 0
|
||||||
|
spriteTessellationMethod: 0
|
||||||
|
spriteTessellationDetail: -1
|
||||||
|
spriteGeometrySubdivision: -1
|
||||||
|
textureType: 0
|
||||||
|
textureShape: 1
|
||||||
|
singleChannelComponent: 0
|
||||||
|
flipbookRows: 1
|
||||||
|
flipbookColumns: 1
|
||||||
|
maxTextureSizeSet: 0
|
||||||
|
compressionQualitySet: 0
|
||||||
|
textureFormatSet: 0
|
||||||
|
ignorePngGamma: 0
|
||||||
|
applyGammaDecoding: 0
|
||||||
|
swizzle: 50462976
|
||||||
|
cookieLightType: 0
|
||||||
|
platformSettings:
|
||||||
|
- serializedVersion: 4
|
||||||
|
buildTarget: DefaultTexturePlatform
|
||||||
|
maxTextureSize: 2048
|
||||||
|
resizeAlgorithm: 0
|
||||||
|
textureFormat: -1
|
||||||
|
textureCompression: 1
|
||||||
|
compressionQuality: 50
|
||||||
|
crunchedCompression: 0
|
||||||
|
allowsAlphaSplitting: 0
|
||||||
|
overridden: 0
|
||||||
|
ignorePlatformSupport: 0
|
||||||
|
androidETC2FallbackOverride: 0
|
||||||
|
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||||
|
- serializedVersion: 4
|
||||||
|
buildTarget: Standalone
|
||||||
|
maxTextureSize: 2048
|
||||||
|
resizeAlgorithm: 0
|
||||||
|
textureFormat: -1
|
||||||
|
textureCompression: 1
|
||||||
|
compressionQuality: 50
|
||||||
|
crunchedCompression: 0
|
||||||
|
allowsAlphaSplitting: 0
|
||||||
|
overridden: 0
|
||||||
|
ignorePlatformSupport: 0
|
||||||
|
androidETC2FallbackOverride: 0
|
||||||
|
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||||
|
- serializedVersion: 4
|
||||||
|
buildTarget: WebGL
|
||||||
|
maxTextureSize: 2048
|
||||||
|
resizeAlgorithm: 0
|
||||||
|
textureFormat: -1
|
||||||
|
textureCompression: 1
|
||||||
|
compressionQuality: 50
|
||||||
|
crunchedCompression: 0
|
||||||
|
allowsAlphaSplitting: 0
|
||||||
|
overridden: 0
|
||||||
|
ignorePlatformSupport: 0
|
||||||
|
androidETC2FallbackOverride: 0
|
||||||
|
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||||
|
spriteSheet:
|
||||||
|
serializedVersion: 2
|
||||||
|
sprites: []
|
||||||
|
outline: []
|
||||||
|
customData:
|
||||||
|
physicsShape: []
|
||||||
|
bones: []
|
||||||
|
spriteID:
|
||||||
|
internalID: 0
|
||||||
|
vertices: []
|
||||||
|
indices:
|
||||||
|
edges: []
|
||||||
|
weights: []
|
||||||
|
secondaryTextures: []
|
||||||
|
spriteCustomMetadata:
|
||||||
|
entries: []
|
||||||
|
nameFileIdTable: {}
|
||||||
|
mipmapLimitGroupName:
|
||||||
|
pSDRemoveMatte: 0
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -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, 6.9f, -9.8f),
|
SceneTheme.Battle => new Vector3(1.4f, 9f, -5.2f),
|
||||||
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(35.2f, 0f, 0f)
|
? Quaternion.Euler(60.5f, 0f, 0f)
|
||||||
: Quaternion.Euler(8f, 0f, 0f);
|
: Quaternion.Euler(8f, 0f, 0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,27 +36,21 @@ MonoBehaviour:
|
||||||
handSpacing: 0.42
|
handSpacing: 0.42
|
||||||
handMaxWidth: 7.4
|
handMaxWidth: 7.4
|
||||||
handArc: 0.035
|
handArc: 0.035
|
||||||
handViewportY: 0.16
|
handViewportX: 0.44
|
||||||
handViewDistance: 3.6
|
handViewportY: 0.18
|
||||||
handViewMaxWidth: 2
|
handViewDistance: 3.8
|
||||||
handViewScale: 0.9
|
handViewMaxWidth: 1.8
|
||||||
|
handViewScale: 0.62
|
||||||
handViewRoll: 3.2
|
handViewRoll: 3.2
|
||||||
handViewArc: 0.02
|
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
|
||||||
pileX: 3.55
|
pileX: 2.95
|
||||||
cameraPosition: {x: 0, y: 6.9, z: -9.8}
|
cameraPosition: {x: 1.4, y: 9, z: -5.2}
|
||||||
cameraTarget: {x: 0, y: 0, z: -0.15}
|
cameraTarget: {x: 1.4, y: 0, z: -0.1}
|
||||||
cameraFov: 50
|
cameraFov: 50
|
||||||
orbitYawLimit: 32
|
|
||||||
orbitPitchMin: 20
|
|
||||||
orbitPitchMax: 62
|
|
||||||
zoomMin: 9.5
|
|
||||||
zoomMax: 16
|
|
||||||
zoomSpeed: 0.006
|
|
||||||
panSpeed: 0.0035
|
|
||||||
tableColor: {r: 0.045, g: 0.04, b: 0.055, a: 1}
|
tableColor: {r: 0.045, g: 0.04, b: 0.055, a: 1}
|
||||||
matSelfColor: {r: 0.055, g: 0.095, b: 0.16, 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}
|
matOpponentColor: {r: 0.13, g: 0.065, b: 0.075, a: 1}
|
||||||
|
|
|
||||||
|
|
@ -50,12 +50,14 @@ namespace Vanguard.Board3D
|
||||||
public float handArc = 0.035f;
|
public float handArc = 0.035f;
|
||||||
|
|
||||||
[Header("Рука в поле зрения")]
|
[Header("Рука в поле зрения")]
|
||||||
|
[Tooltip("Доля ширины экрана для центра карт: 0 — левый край, 0.5 — середина.")]
|
||||||
|
[Range(0.05f, 0.95f)] public float handViewportX = 0.44f;
|
||||||
[Tooltip("Доля высоты экрана для центра карт: 0 — низ, 0.5 — середина.")]
|
[Tooltip("Доля высоты экрана для центра карт: 0 — низ, 0.5 — середина.")]
|
||||||
[Range(0.05f, 0.45f)] public float handViewportY = 0.16f;
|
[Range(0.05f, 0.45f)] public float handViewportY = 0.18f;
|
||||||
[Tooltip("Расстояние от камеры до веера руки, метры.")]
|
[Tooltip("Расстояние от камеры до веера руки, метры.")]
|
||||||
public float handViewDistance = 3.6f;
|
public float handViewDistance = 3.8f;
|
||||||
public float handViewMaxWidth = 2.0f;
|
public float handViewMaxWidth = 1.8f;
|
||||||
public float handViewScale = 0.9f;
|
public float handViewScale = 0.62f;
|
||||||
public float handViewRoll = 3.2f;
|
public float handViewRoll = 3.2f;
|
||||||
public float handViewArc = 0.02f;
|
public float handViewArc = 0.02f;
|
||||||
|
|
||||||
|
|
@ -66,21 +68,15 @@ namespace Vanguard.Board3D
|
||||||
|
|
||||||
[Header("Стопки")]
|
[Header("Стопки")]
|
||||||
[Tooltip("Расстояние от центра стола до зоны урона.")]
|
[Tooltip("Расстояние от центра стола до зоны урона.")]
|
||||||
public float damageX = 3.5f;
|
public float damageX = 3.0f;
|
||||||
[Tooltip("Расстояние от центра стола до колоды и дропа.")]
|
[Tooltip("Расстояние от центра стола до колоды и дропа.")]
|
||||||
public float pileX = 3.55f;
|
public float pileX = 2.95f;
|
||||||
|
|
||||||
[Header("Камера")]
|
[Header("Камера")]
|
||||||
public Vector3 cameraPosition = new Vector3(0f, 6.9f, -9.8f);
|
[Tooltip("Фиксированная камера над полем. Не двигается игроком.")]
|
||||||
public Vector3 cameraTarget = new Vector3(0f, 0f, -0.15f);
|
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 float cameraFov = 50f;
|
||||||
public float orbitYawLimit = 32f;
|
|
||||||
public float orbitPitchMin = 20f;
|
|
||||||
public float orbitPitchMax = 62f;
|
|
||||||
public float zoomMin = 9.5f;
|
|
||||||
public float zoomMax = 16f;
|
|
||||||
public float zoomSpeed = 0.006f;
|
|
||||||
public float panSpeed = 0.0035f;
|
|
||||||
|
|
||||||
[Header("Палитра")]
|
[Header("Палитра")]
|
||||||
public Color tableColor = new Color(0.045f, 0.04f, 0.055f);
|
public Color tableColor = new Color(0.045f, 0.04f, 0.055f);
|
||||||
|
|
@ -142,13 +138,12 @@ namespace Vanguard.Board3D
|
||||||
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);
|
handViewportY = Mathf.Clamp(handViewportY, 0.05f, 0.45f);
|
||||||
|
handViewportX = Mathf.Clamp(handViewportX, 0.05f, 0.95f);
|
||||||
handViewDistance = Mathf.Max(1f, handViewDistance);
|
handViewDistance = Mathf.Max(1f, handViewDistance);
|
||||||
handViewMaxWidth = Mathf.Max(0.5f, handViewMaxWidth);
|
handViewMaxWidth = Mathf.Max(0.5f, handViewMaxWidth);
|
||||||
handViewScale = Mathf.Clamp(handViewScale, 0.3f, 1.5f);
|
handViewScale = Mathf.Clamp(handViewScale, 0.3f, 1.5f);
|
||||||
handViewRoll = Mathf.Clamp(handViewRoll, 0f, 20f);
|
handViewRoll = Mathf.Clamp(handViewRoll, 0f, 20f);
|
||||||
handViewArc = Mathf.Max(0f, handViewArc);
|
handViewArc = Mathf.Max(0f, handViewArc);
|
||||||
orbitPitchMax = Mathf.Clamp(orbitPitchMax, orbitPitchMin + 1f, 85f);
|
|
||||||
zoomMax = Mathf.Max(zoomMin + 0.5f, zoomMax);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,10 @@ namespace Vanguard.Board3D
|
||||||
|
|
||||||
private static BoardArt SandboxArt(string printingId)
|
private static BoardArt SandboxArt(string printingId)
|
||||||
{
|
{
|
||||||
|
var texture = LoadSandboxTexture(printingId);
|
||||||
|
if (texture != null)
|
||||||
|
return new BoardArt(texture, Color.white, printingId);
|
||||||
|
|
||||||
var clan = printingId != null && printingId.Contains("002")
|
var clan = printingId != null && printingId.Contains("002")
|
||||||
? "Kagero"
|
? "Kagero"
|
||||||
: "RoyalPaladin";
|
: "RoyalPaladin";
|
||||||
|
|
@ -67,6 +71,36 @@ namespace Vanguard.Board3D
|
||||||
printingId);
|
printingId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Texture2D LoadSandboxTexture(string printingId)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(printingId))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var slash = printingId.IndexOf('/');
|
||||||
|
if (slash <= 0 || slash >= printingId.Length - 1)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var set = printingId.Substring(0, slash).ToLowerInvariant();
|
||||||
|
var tail = printingId.Substring(slash + 1);
|
||||||
|
var number = string.Empty;
|
||||||
|
for (var i = 0; i < tail.Length; i++)
|
||||||
|
{
|
||||||
|
var c = tail[i];
|
||||||
|
if (char.IsDigit(c) || (i == 0 && (c == 'S' || c == 's')))
|
||||||
|
{
|
||||||
|
number += char.ToLowerInvariant(c);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (number.Length == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return Resources.Load<Texture2D>("CardArt/" + set + "_" + number);
|
||||||
|
}
|
||||||
|
|
||||||
private static BoardRenderState BuildState()
|
private static BoardRenderState BuildState()
|
||||||
{
|
{
|
||||||
var state = new BoardRenderState();
|
var state = new BoardRenderState();
|
||||||
|
|
|
||||||
|
|
@ -1,132 +0,0 @@
|
||||||
using UnityEngine;
|
|
||||||
using UnityEngine.EventSystems;
|
|
||||||
using UnityEngine.InputSystem;
|
|
||||||
|
|
||||||
namespace Vanguard.Board3D
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Ограниченная орбита камеры: ПКМ — поворот, СКМ — панорама,
|
|
||||||
/// колесо — зум. Левая кнопка остаётся картам и UI.
|
|
||||||
/// </summary>
|
|
||||||
public sealed class BoardCameraRig : MonoBehaviour
|
|
||||||
{
|
|
||||||
private PlaymatLayoutConfig _config;
|
|
||||||
private Vector3 _target;
|
|
||||||
private float _distance = 15f;
|
|
||||||
private float _yaw;
|
|
||||||
private float _pitch = 35f;
|
|
||||||
private bool _orbiting;
|
|
||||||
private bool _panning;
|
|
||||||
private Vector2 _lastPointer;
|
|
||||||
|
|
||||||
public void Configure(PlaymatLayoutConfig config)
|
|
||||||
{
|
|
||||||
_config = config != null ? config : PlaymatLayoutConfig.Default;
|
|
||||||
_target = _config.cameraTarget;
|
|
||||||
var offset = _config.cameraPosition - _target;
|
|
||||||
_distance = Mathf.Clamp(
|
|
||||||
offset.magnitude,
|
|
||||||
_config.zoomMin,
|
|
||||||
_config.zoomMax);
|
|
||||||
_pitch = Mathf.Clamp(
|
|
||||||
Mathf.Asin(Mathf.Clamp(offset.y / Mathf.Max(0.001f, _distance), -1f, 1f))
|
|
||||||
* Mathf.Rad2Deg,
|
|
||||||
_config.orbitPitchMin,
|
|
||||||
_config.orbitPitchMax);
|
|
||||||
_yaw = Mathf.Atan2(-offset.x, -offset.z) * Mathf.Rad2Deg;
|
|
||||||
_yaw = Mathf.Clamp(_yaw, -_config.orbitYawLimit, _config.orbitYawLimit);
|
|
||||||
Apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Update()
|
|
||||||
{
|
|
||||||
if (_config == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var mouse = Mouse.current;
|
|
||||||
if (mouse == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var pointer = mouse.position.ReadValue();
|
|
||||||
var overUi = IsPointerOverUi();
|
|
||||||
|
|
||||||
if (mouse.rightButton.wasPressedThisFrame && !overUi)
|
|
||||||
{
|
|
||||||
_orbiting = true;
|
|
||||||
_lastPointer = pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mouse.rightButton.wasReleasedThisFrame)
|
|
||||||
_orbiting = false;
|
|
||||||
|
|
||||||
if (_orbiting && mouse.rightButton.isPressed)
|
|
||||||
{
|
|
||||||
var delta = pointer - _lastPointer;
|
|
||||||
_lastPointer = pointer;
|
|
||||||
_yaw = Mathf.Clamp(
|
|
||||||
_yaw + (delta.x * 0.18f),
|
|
||||||
-_config.orbitYawLimit,
|
|
||||||
_config.orbitYawLimit);
|
|
||||||
_pitch = Mathf.Clamp(
|
|
||||||
_pitch - (delta.y * 0.16f),
|
|
||||||
_config.orbitPitchMin,
|
|
||||||
_config.orbitPitchMax);
|
|
||||||
Apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mouse.middleButton.wasPressedThisFrame && !overUi)
|
|
||||||
{
|
|
||||||
_panning = true;
|
|
||||||
_lastPointer = pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mouse.middleButton.wasReleasedThisFrame)
|
|
||||||
_panning = false;
|
|
||||||
|
|
||||||
if (_panning && mouse.middleButton.isPressed)
|
|
||||||
{
|
|
||||||
var delta = pointer - _lastPointer;
|
|
||||||
_lastPointer = pointer;
|
|
||||||
var rotation = Quaternion.Euler(_pitch, _yaw, 0f);
|
|
||||||
var right = rotation * Vector3.right;
|
|
||||||
var forward = rotation * Vector3.forward;
|
|
||||||
right.y = 0f;
|
|
||||||
forward.y = 0f;
|
|
||||||
right.Normalize();
|
|
||||||
forward.Normalize();
|
|
||||||
|
|
||||||
var scale = _config.panSpeed * _distance;
|
|
||||||
_target -= (right * (delta.x * scale)) + (forward * (delta.y * scale));
|
|
||||||
_target.x = Mathf.Clamp(_target.x, -2.5f, 2.5f);
|
|
||||||
_target.z = Mathf.Clamp(_target.z, -2f, 2f);
|
|
||||||
Apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
var scroll = mouse.scroll.ReadValue().y;
|
|
||||||
if (!overUi && Mathf.Abs(scroll) > 0.01f)
|
|
||||||
{
|
|
||||||
_distance = Mathf.Clamp(
|
|
||||||
_distance - (scroll * _config.zoomSpeed),
|
|
||||||
_config.zoomMin,
|
|
||||||
_config.zoomMax);
|
|
||||||
Apply();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Apply()
|
|
||||||
{
|
|
||||||
var rotation = Quaternion.Euler(_pitch, _yaw, 0f);
|
|
||||||
transform.rotation = rotation;
|
|
||||||
transform.position = _target - (rotation * Vector3.forward * _distance);
|
|
||||||
var camera = GetComponent<Camera>();
|
|
||||||
if (camera != null)
|
|
||||||
camera.fieldOfView = _config.cameraFov;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsPointerOverUi()
|
|
||||||
{
|
|
||||||
var eventSystem = EventSystem.current;
|
|
||||||
return eventSystem != null && eventSystem.IsPointerOverGameObject();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 30bd2e29295c0b241bb1f29e9dc73ca8
|
|
||||||
|
|
@ -70,7 +70,7 @@ namespace Vanguard.Board3D
|
||||||
var text = BoardText.Create(
|
var text = BoardText.Create(
|
||||||
go.transform,
|
go.transform,
|
||||||
label,
|
label,
|
||||||
0.14f,
|
0.055f,
|
||||||
new Color(1f, 1f, 1f, 0.34f),
|
new Color(1f, 1f, 1f, 0.34f),
|
||||||
new Vector3(0f, 0.001f, 0f));
|
new Vector3(0f, 0.001f, 0f));
|
||||||
text.name = "Slot Label";
|
text.name = "Slot Label";
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,10 @@ namespace Vanguard.Board3D
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
if (_back != null)
|
||||||
|
return _back;
|
||||||
|
|
||||||
|
_back = Resources.Load<Texture2D>("CardArt/vanguard_back");
|
||||||
if (_back == null)
|
if (_back == null)
|
||||||
_back = BuildBack();
|
_back = BuildBack();
|
||||||
return _back;
|
return _back;
|
||||||
|
|
|
||||||
|
|
@ -43,8 +43,6 @@ namespace Vanguard.Board3D
|
||||||
private Transform _pilesRoot;
|
private Transform _pilesRoot;
|
||||||
private Transform _zonesRoot;
|
private Transform _zonesRoot;
|
||||||
private Camera _camera;
|
private Camera _camera;
|
||||||
private BoardCameraRig _cameraRig;
|
|
||||||
private bool _ownsRig;
|
|
||||||
|
|
||||||
private BoardZoneView _guardianZone;
|
private BoardZoneView _guardianZone;
|
||||||
private BoardZoneView _dropIndicator;
|
private BoardZoneView _dropIndicator;
|
||||||
|
|
@ -159,8 +157,6 @@ namespace Vanguard.Board3D
|
||||||
}
|
}
|
||||||
|
|
||||||
gameObject.SetActive(visible);
|
gameObject.SetActive(visible);
|
||||||
if (_cameraRig != null)
|
|
||||||
_cameraRig.enabled = visible;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Render(MatchView view, BoardRenderState state)
|
public void Render(MatchView view, BoardRenderState state)
|
||||||
|
|
@ -271,7 +267,9 @@ namespace Vanguard.Board3D
|
||||||
new Vector3(matWidth, 0.008f, matDepth),
|
new Vector3(matWidth, 0.008f, matDepth),
|
||||||
_config.matOpponentColor);
|
_config.matOpponentColor);
|
||||||
|
|
||||||
var divider = BoardMaterials.CreateLit(_config.lineColor, 1.2f);
|
var divider = BoardMaterials.CreateUnlit(
|
||||||
|
null,
|
||||||
|
new Color(0.34f, 0.24f, 0.10f));
|
||||||
CreateBox(
|
CreateBox(
|
||||||
"Center Line",
|
"Center Line",
|
||||||
new Vector3(0f, 0.011f, 0f),
|
new Vector3(0f, 0.011f, 0f),
|
||||||
|
|
@ -339,8 +337,8 @@ namespace Vanguard.Board3D
|
||||||
guardianDepth,
|
guardianDepth,
|
||||||
Mathf.Max(0.022f, _config.cardWidth * 0.04f),
|
Mathf.Max(0.022f, _config.cardWidth * 0.04f),
|
||||||
_config.guardianColor,
|
_config.guardianColor,
|
||||||
"GUARDIAN",
|
null,
|
||||||
0.11f,
|
0f,
|
||||||
-(guardianDepth * 0.5f) - 0.2f,
|
-(guardianDepth * 0.5f) - 0.2f,
|
||||||
new Color(0.72f, 0.92f, 1f, 0.75f),
|
new Color(0.72f, 0.92f, 1f, 0.75f),
|
||||||
_config.highlightColor);
|
_config.highlightColor);
|
||||||
|
|
@ -360,8 +358,8 @@ namespace Vanguard.Board3D
|
||||||
2.25f,
|
2.25f,
|
||||||
Mathf.Max(0.018f, _config.cardWidth * 0.03f),
|
Mathf.Max(0.018f, _config.cardWidth * 0.03f),
|
||||||
_config.damageColor,
|
_config.damageColor,
|
||||||
"DAMAGE",
|
null,
|
||||||
0.1f,
|
0f,
|
||||||
-zSign * 1.32f,
|
-zSign * 1.32f,
|
||||||
new Color(1f, 0.78f, 0.76f, 0.75f),
|
new Color(1f, 0.78f, 0.76f, 0.75f),
|
||||||
_config.highlightColor);
|
_config.highlightColor);
|
||||||
|
|
@ -433,17 +431,11 @@ namespace Vanguard.Board3D
|
||||||
if (_camera == null)
|
if (_camera == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (_cameraRig == null)
|
_camera.transform.position = _config.cameraPosition;
|
||||||
{
|
var direction = _config.cameraTarget - _config.cameraPosition;
|
||||||
_cameraRig = _camera.GetComponent<BoardCameraRig>();
|
if (direction.sqrMagnitude > 0.0001f)
|
||||||
if (_cameraRig == null)
|
_camera.transform.rotation = Quaternion.LookRotation(direction, Vector3.up);
|
||||||
{
|
_camera.fieldOfView = _config.cameraFov;
|
||||||
_cameraRig = _camera.gameObject.AddComponent<BoardCameraRig>();
|
|
||||||
_ownsRig = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_cameraRig.Configure(_config);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BuildSpecs(
|
private void BuildSpecs(
|
||||||
|
|
@ -496,7 +488,8 @@ namespace Vanguard.Board3D
|
||||||
Art = ResolveArt(card.PrintingId),
|
Art = ResolveArt(card.PrintingId),
|
||||||
Position = _layout.CirclePosition(localSeat, slot),
|
Position = _layout.CirclePosition(localSeat, slot),
|
||||||
BaseRotation = Quaternion.Euler(0f, _layout.BaseYaw(localSeat), 0f),
|
BaseRotation = Quaternion.Euler(0f, _layout.BaseYaw(localSeat), 0f),
|
||||||
FaceUp = card.FaceUp,
|
// Карты в кругах V/R всегда открыты, независимо от снимка.
|
||||||
|
FaceUp = true,
|
||||||
Rested = card.Rested,
|
Rested = card.Rested,
|
||||||
Highlight = state.Highlights.Contains(key)
|
Highlight = state.Highlights.Contains(key)
|
||||||
? _config.highlightColor
|
? _config.highlightColor
|
||||||
|
|
@ -669,7 +662,9 @@ namespace Vanguard.Board3D
|
||||||
var offset = index - (safeCount - 1) * 0.5f;
|
var offset = index - (safeCount - 1) * 0.5f;
|
||||||
var halfHeight = Mathf.Tan(
|
var halfHeight = Mathf.Tan(
|
||||||
_camera.fieldOfView * 0.5f * Mathf.Deg2Rad) * _config.handViewDistance;
|
_camera.fieldOfView * 0.5f * Mathf.Deg2Rad) * _config.handViewDistance;
|
||||||
var x = offset * step;
|
var halfWidth = halfHeight * Mathf.Max(0.1f, _camera.aspect);
|
||||||
|
var x = ((_config.handViewportX - 0.5f) * 2f * halfWidth)
|
||||||
|
+ (offset * step);
|
||||||
var y = (_config.handViewportY - 0.5f) * 2f * halfHeight
|
var y = (_config.handViewportY - 0.5f) * 2f * halfHeight
|
||||||
+ (1f - Mathf.Abs(offset) / safeCount) * _config.handViewArc;
|
+ (1f - Mathf.Abs(offset) / safeCount) * _config.handViewArc;
|
||||||
var z = _config.handViewDistance
|
var z = _config.handViewDistance
|
||||||
|
|
@ -725,22 +720,11 @@ namespace Vanguard.Board3D
|
||||||
UpdatePileSide(self, _deckSelf, _dropSelf, _soulSelf);
|
UpdatePileSide(self, _deckSelf, _dropSelf, _soulSelf);
|
||||||
UpdatePileSide(opponent, _deckOpponent, _dropOpponent, _soulOpponent);
|
UpdatePileSide(opponent, _deckOpponent, _dropOpponent, _soulOpponent);
|
||||||
|
|
||||||
var damageSelf = self != null
|
_damageZones[0]?.SetLabel(string.Empty);
|
||||||
? self.FaceDownDamage + (self.FaceUpDamage?.Count ?? 0)
|
_damageZones[1]?.SetLabel(string.Empty);
|
||||||
: 0;
|
|
||||||
var damageOpponent = opponent != null
|
|
||||||
? opponent.FaceDownDamage + (opponent.FaceUpDamage?.Count ?? 0)
|
|
||||||
: 0;
|
|
||||||
_damageZones[0]?.SetLabel("DAMAGE " + damageSelf);
|
|
||||||
_damageZones[1]?.SetLabel("DAMAGE " + damageOpponent);
|
|
||||||
|
|
||||||
if (_guardianZone != null)
|
if (_guardianZone != null)
|
||||||
{
|
_guardianZone.SetLabel(string.Empty);
|
||||||
var shield = view.Battle != null ? view.Battle.GuardShield : 0;
|
|
||||||
var checks = view.Battle != null ? view.Battle.ChecksRemaining : 0;
|
|
||||||
_guardianZone.SetLabel(
|
|
||||||
"GUARDIAN SHIELD " + shield + " CHECK " + checks);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdatePileSide(
|
private void UpdatePileSide(
|
||||||
|
|
@ -1214,14 +1198,6 @@ namespace Vanguard.Board3D
|
||||||
{
|
{
|
||||||
if (_instance == this)
|
if (_instance == this)
|
||||||
_instance = null;
|
_instance = null;
|
||||||
|
|
||||||
if (_cameraRig != null && _ownsRig)
|
|
||||||
{
|
|
||||||
if (Application.isPlaying)
|
|
||||||
Destroy(_cameraRig);
|
|
||||||
else
|
|
||||||
DestroyImmediate(_cameraRig);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class HandCardBinding
|
private sealed class HandCardBinding
|
||||||
|
|
|
||||||
|
|
@ -73,12 +73,15 @@ namespace Vanguard.Board3D
|
||||||
_top.sharedMaterial = BoardMaterials.White;
|
_top.sharedMaterial = BoardMaterials.White;
|
||||||
BoardMaterials.SetShadows(_top, false);
|
BoardMaterials.SetShadows(_top, false);
|
||||||
|
|
||||||
_label = BoardText.Create(
|
if (Zone != BoardZone.Soul)
|
||||||
transform,
|
{
|
||||||
title,
|
_label = BoardText.Create(
|
||||||
0.115f,
|
transform,
|
||||||
new Color(0.86f, 0.90f, 1f),
|
title,
|
||||||
new Vector3(0f, 0.02f, -(height * 0.78f)));
|
0.05f,
|
||||||
|
new Color(0.86f, 0.90f, 1f),
|
||||||
|
new Vector3(0f, 0.02f, -(height * 0.78f)));
|
||||||
|
}
|
||||||
|
|
||||||
var collider = gameObject.AddComponent<BoxCollider>();
|
var collider = gameObject.AddComponent<BoxCollider>();
|
||||||
collider.size = new Vector3(width * 1.06f, 0.16f, height * 1.06f);
|
collider.size = new Vector3(width * 1.06f, 0.16f, height * 1.06f);
|
||||||
|
|
@ -128,7 +131,7 @@ namespace Vanguard.Board3D
|
||||||
_top.SetPropertyBlock(_block);
|
_top.SetPropertyBlock(_block);
|
||||||
}
|
}
|
||||||
|
|
||||||
BoardText.SetText(_label, title + " " + safe);
|
BoardText.SetText(_label, title);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RemoveCollider(GameObject target)
|
private static void RemoveCollider(GameObject target)
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,8 @@ namespace Vanguard.UI
|
||||||
private RectTransform _soulOverlay;
|
private RectTransform _soulOverlay;
|
||||||
private RectTransform _soulContent;
|
private RectTransform _soulContent;
|
||||||
private Text _soulTitle;
|
private Text _soulTitle;
|
||||||
|
private Text _selfStatsText;
|
||||||
|
private Text _opponentStatsText;
|
||||||
|
|
||||||
public BattleView(Transform parent, MetaService meta, Action onExit)
|
public BattleView(Transform parent, MetaService meta, Action onExit)
|
||||||
{
|
{
|
||||||
|
|
@ -129,7 +131,7 @@ namespace Vanguard.UI
|
||||||
right.anchorMin = new Vector2(1f, 0f);
|
right.anchorMin = new Vector2(1f, 0f);
|
||||||
right.anchorMax = new Vector2(1f, 1f);
|
right.anchorMax = new Vector2(1f, 1f);
|
||||||
right.pivot = new Vector2(1f, 0.5f);
|
right.pivot = new Vector2(1f, 0.5f);
|
||||||
right.offsetMin = new Vector2(-528f, 28f);
|
right.offsetMin = new Vector2(-456f, 28f);
|
||||||
right.offsetMax = new Vector2(-36f, -108f);
|
right.offsetMax = new Vector2(-36f, -108f);
|
||||||
UiKit.AddVerticalLayout(right, 14, 10);
|
UiKit.AddVerticalLayout(right, 14, 10);
|
||||||
|
|
||||||
|
|
@ -188,6 +190,7 @@ namespace Vanguard.UI
|
||||||
_feedback.gameObject.SetActive(false);
|
_feedback.gameObject.SetActive(false);
|
||||||
|
|
||||||
BuildSoulOverlay();
|
BuildSoulOverlay();
|
||||||
|
BuildStatsOverlays();
|
||||||
|
|
||||||
_board3D = GameBoard3D.Create(null, PlaymatLayoutConfig.Default);
|
_board3D = GameBoard3D.Create(null, PlaymatLayoutConfig.Default);
|
||||||
_board3D.ArtProvider = ResolveBoardArt;
|
_board3D.ArtProvider = ResolveBoardArt;
|
||||||
|
|
@ -234,6 +237,10 @@ namespace Vanguard.UI
|
||||||
_topInfo.text = (info.DeckName ?? string.Empty) + " · " + info.MatchId;
|
_topInfo.text = (info.DeckName ?? string.Empty) + " · " + info.MatchId;
|
||||||
_soulCounterLabel.text = "Душа: 0";
|
_soulCounterLabel.text = "Душа: 0";
|
||||||
_soulCounter.interactable = false;
|
_soulCounter.interactable = false;
|
||||||
|
if (_selfStatsText != null)
|
||||||
|
_selfStatsText.text = string.Empty;
|
||||||
|
if (_opponentStatsText != null)
|
||||||
|
_opponentStatsText.text = string.Empty;
|
||||||
SetStatus("Подключаемся к матчу…", Muted);
|
SetStatus("Подключаемся к матчу…", Muted);
|
||||||
_root.gameObject.SetActive(true);
|
_root.gameObject.SetActive(true);
|
||||||
_ = LoadCatalogAsync();
|
_ = LoadCatalogAsync();
|
||||||
|
|
@ -773,6 +780,7 @@ namespace Vanguard.UI
|
||||||
_board3D?.SetSelfPlayer(_view.You);
|
_board3D?.SetSelfPlayer(_view.You);
|
||||||
_board3D?.Render(_view, BuildBoardRenderState());
|
_board3D?.Render(_view, BuildBoardRenderState());
|
||||||
UpdateSoulCounter();
|
UpdateSoulCounter();
|
||||||
|
UpdateStatsOverlay();
|
||||||
|
|
||||||
RebuildIntents(_view);
|
RebuildIntents(_view);
|
||||||
UpdateClock();
|
UpdateClock();
|
||||||
|
|
@ -848,9 +856,16 @@ namespace Vanguard.UI
|
||||||
_catalog.TryGetValue(printingId, out var meta);
|
_catalog.TryGetValue(printingId, out var meta);
|
||||||
var clan = meta != null ? meta.Clan : "None";
|
var clan = meta != null ? meta.Clan : "None";
|
||||||
var nameId = meta != null ? meta.NameId : printingId;
|
var nameId = meta != null ? meta.NameId : printingId;
|
||||||
var sprite = CardImageService.Resolve(
|
var sprite = CardImageService.Resolve(meta != null ? meta.ImageKey : null, null);
|
||||||
meta != null ? meta.ImageKey : null,
|
if (sprite == null)
|
||||||
CardArt.ForCard(nameId ?? printingId ?? "card", clan));
|
{
|
||||||
|
sprite = CardImageService.Resolve(
|
||||||
|
CardImageService.ImageKeyForPrinting(printingId),
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sprite == null)
|
||||||
|
sprite = CardArt.ForCard(nameId ?? printingId ?? "card", clan);
|
||||||
return new BoardArt(
|
return new BoardArt(
|
||||||
sprite != null ? sprite.texture : null,
|
sprite != null ? sprite.texture : null,
|
||||||
CardArt.ClanColor(clan),
|
CardArt.ClanColor(clan),
|
||||||
|
|
@ -1023,6 +1038,86 @@ namespace Vanguard.UI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void BuildStatsOverlays()
|
||||||
|
{
|
||||||
|
_selfStatsText = CreateStatsPanel(
|
||||||
|
"SelfStats",
|
||||||
|
new Vector2(0f, 0f),
|
||||||
|
new Vector2(0f, 0f),
|
||||||
|
new Vector2(30f, 24f),
|
||||||
|
TextAnchor.MiddleLeft);
|
||||||
|
_opponentStatsText = CreateStatsPanel(
|
||||||
|
"OpponentStats",
|
||||||
|
new Vector2(1f, 1f),
|
||||||
|
new Vector2(1f, 1f),
|
||||||
|
new Vector2(-488f, -108f),
|
||||||
|
TextAnchor.MiddleRight);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Text CreateStatsPanel(
|
||||||
|
string name,
|
||||||
|
Vector2 anchor,
|
||||||
|
Vector2 pivot,
|
||||||
|
Vector2 position,
|
||||||
|
TextAnchor alignment)
|
||||||
|
{
|
||||||
|
var panel = UiKit.CreatePanel(
|
||||||
|
_root,
|
||||||
|
name,
|
||||||
|
new Color(0.02f, 0.035f, 0.055f, 0.72f));
|
||||||
|
panel.anchorMin = anchor;
|
||||||
|
panel.anchorMax = anchor;
|
||||||
|
panel.pivot = pivot;
|
||||||
|
panel.anchoredPosition = position;
|
||||||
|
panel.sizeDelta = new Vector2(360f, 58f);
|
||||||
|
|
||||||
|
var image = panel.GetComponent<Image>();
|
||||||
|
if (image != null)
|
||||||
|
image.raycastTarget = false;
|
||||||
|
|
||||||
|
var text = UiKit.CreateText(
|
||||||
|
panel,
|
||||||
|
"Stats",
|
||||||
|
string.Empty,
|
||||||
|
14,
|
||||||
|
new Color(0.86f, 0.91f, 1f),
|
||||||
|
alignment);
|
||||||
|
UiKit.Stretch(text.rectTransform);
|
||||||
|
text.rectTransform.offsetMin = new Vector2(12f, 5f);
|
||||||
|
text.rectTransform.offsetMax = new Vector2(-12f, -5f);
|
||||||
|
text.horizontalOverflow = HorizontalWrapMode.Wrap;
|
||||||
|
text.verticalOverflow = VerticalWrapMode.Overflow;
|
||||||
|
UiKit.MakeLiteral(text);
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateStatsOverlay()
|
||||||
|
{
|
||||||
|
if (_view == null || _selfStatsText == null || _opponentStatsText == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var self = _view.Players.FirstOrDefault(player => player.Index == _view.You);
|
||||||
|
var opponent = _view.Players.FirstOrDefault(player => player.Index != _view.You);
|
||||||
|
_selfStatsText.text = FormatStats(self, selfSide: true);
|
||||||
|
_opponentStatsText.text = FormatStats(opponent, selfSide: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatStats(PlayerView player, bool selfSide)
|
||||||
|
{
|
||||||
|
var title = Loc.Translate(selfSide ? "ВЫ" : "ПРОТИВНИК");
|
||||||
|
if (player == null)
|
||||||
|
return title;
|
||||||
|
|
||||||
|
var damage = player.FaceDownDamage + (player.FaceUpDamage?.Count ?? 0);
|
||||||
|
var hand = Mathf.Max(player.HandCount, player.Hand?.Count ?? 0);
|
||||||
|
return title + " · " + player.Name + "\n"
|
||||||
|
+ Loc.Translate("Рука") + " " + hand
|
||||||
|
+ " " + Loc.Translate("Колода") + " " + player.DeckCount
|
||||||
|
+ " " + Loc.Translate("ДРОП") + " " + (player.Drop?.Count ?? 0)
|
||||||
|
+ " " + Loc.Translate("УРОН") + " " + damage
|
||||||
|
+ " " + Loc.Translate("Душа") + " " + (player.Soul?.Count ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
private void BuildSoulOverlay()
|
private void BuildSoulOverlay()
|
||||||
{
|
{
|
||||||
_soulOverlay = UiKit.CreatePanel(_root, "SoulOverlay", new Color(0f, 0f, 0f, 0.74f));
|
_soulOverlay = UiKit.CreatePanel(_root, "SoulOverlay", new Color(0f, 0f, 0f, 0.74f));
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,17 @@ namespace Vanguard.UI
|
||||||
if (_back != null)
|
if (_back != null)
|
||||||
return _back;
|
return _back;
|
||||||
|
|
||||||
|
var texture = Resources.Load<Texture2D>("CardArt/vanguard_back");
|
||||||
|
if (texture != null)
|
||||||
|
{
|
||||||
|
_back = Sprite.Create(
|
||||||
|
texture,
|
||||||
|
new Rect(0f, 0f, texture.width, texture.height),
|
||||||
|
new Vector2(0.5f, 0.5f),
|
||||||
|
100f);
|
||||||
|
return _back;
|
||||||
|
}
|
||||||
|
|
||||||
_back = BackTexture();
|
_back = BackTexture();
|
||||||
return _back;
|
return _back;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,5 +51,36 @@ namespace Vanguard.UI
|
||||||
Cache[imageKey] = sprite;
|
Cache[imageKey] = sprite;
|
||||||
return sprite;
|
return sprite;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "BT01/042EN" → "bt01_042". Нужно, когда каталог ещё не загрузился,
|
||||||
|
/// а арт уже лежит в Resources/CardArt.
|
||||||
|
/// </summary>
|
||||||
|
public static string ImageKeyForPrinting(string printingId)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(printingId))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var slash = printingId.IndexOf('/');
|
||||||
|
if (slash <= 0 || slash >= printingId.Length - 1)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var set = printingId.Substring(0, slash).ToLowerInvariant();
|
||||||
|
var tail = printingId.Substring(slash + 1);
|
||||||
|
var number = string.Empty;
|
||||||
|
for (var i = 0; i < tail.Length; i++)
|
||||||
|
{
|
||||||
|
var c = tail[i];
|
||||||
|
if (char.IsDigit(c) || (i == 0 && (c == 'S' || c == 's')))
|
||||||
|
{
|
||||||
|
number += char.ToLowerInvariant(c);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return number.Length > 0 ? set + "_" + number : null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,8 @@ namespace Vanguard.UI
|
||||||
["нет"] = "no",
|
["нет"] = "no",
|
||||||
["Вы: "] = "You: ",
|
["Вы: "] = "You: ",
|
||||||
["ДРОП"] = "DROP",
|
["ДРОП"] = "DROP",
|
||||||
|
["Душа"] = "Soul",
|
||||||
|
["ДУША"] = "SOUL",
|
||||||
["Кадр"] = "Frame",
|
["Кадр"] = "Frame",
|
||||||
["Клан"] = "Clan",
|
["Клан"] = "Clan",
|
||||||
["Пыль"] = "Dust",
|
["Пыль"] = "Dust",
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,9 @@ namespace Vanguard.Tests
|
||||||
Assert.IsNotNull(
|
Assert.IsNotNull(
|
||||||
Resources.Load<Texture2D>("CardArt/td01_005"),
|
Resources.Load<Texture2D>("CardArt/td01_005"),
|
||||||
"TD01 card art is not imported");
|
"TD01 card art is not imported");
|
||||||
|
var standardBack = Resources.Load<Texture2D>("CardArt/vanguard_back");
|
||||||
|
Assert.IsNotNull(standardBack, "Standard Vanguard card back is not imported");
|
||||||
|
Assert.AreEqual(standardBack, CardArt.Back.texture);
|
||||||
|
|
||||||
var artGo = new GameObject("ArtProbe", typeof(RectTransform), typeof(Image));
|
var artGo = new GameObject("ArtProbe", typeof(RectTransform), typeof(Image));
|
||||||
var artImage = artGo.GetComponent<Image>();
|
var artImage = artGo.GetComponent<Image>();
|
||||||
|
|
@ -234,6 +237,20 @@ namespace Vanguard.Tests
|
||||||
card.Ref.Zone == BoardZone.Guardian
|
card.Ref.Zone == BoardZone.Guardian
|
||||||
&& card.Ref.InstanceId == 7));
|
&& card.Ref.InstanceId == 7));
|
||||||
|
|
||||||
|
var selfStats = Resources.FindObjectsOfTypeAll<Text>()
|
||||||
|
.FirstOrDefault(text => text.name == "Stats"
|
||||||
|
&& text.transform.parent != null
|
||||||
|
&& text.transform.parent.name == "SelfStats");
|
||||||
|
var opponentStats = Resources.FindObjectsOfTypeAll<Text>()
|
||||||
|
.FirstOrDefault(text => text.name == "Stats"
|
||||||
|
&& text.transform.parent != null
|
||||||
|
&& text.transform.parent.name == "OpponentStats");
|
||||||
|
Assert.IsNotNull(selfStats);
|
||||||
|
Assert.IsNotNull(opponentStats);
|
||||||
|
StringAssert.Contains("Рука 1", selfStats.text);
|
||||||
|
StringAssert.Contains("Душа 1", selfStats.text);
|
||||||
|
StringAssert.Contains("Рука 0", opponentStats.text);
|
||||||
|
|
||||||
Cleanup();
|
Cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -840,7 +840,8 @@ Battle; Boot и общие сервисы остаются персистент
|
||||||
- Раскладка: локальный игрок всегда внизу; передний ряд `RC–V–RC`, задний
|
- Раскладка: локальный игрок всегда внизу; передний ряд `RC–V–RC`, задний
|
||||||
`RC–RC–RC`; у противника зеркально. Между половинами — общий круг защиты
|
`RC–RC–RC`; у противника зеркально. Между половинами — общий круг защиты
|
||||||
(карты обоих игроков и текущий Guard). Душа — стопка под V, колода и дроп
|
(карты обоих игроков и текущий Guard). Душа — стопка под V, колода и дроп
|
||||||
сбоку, урон — две колонки по три карты на боку.
|
сбоку, урон — две колонки по три карты на боку. Карты в V/R всегда
|
||||||
|
рисуются лицом вверх, даже если снимок пришёл с `FaceUp=false`.
|
||||||
- `PlaymatLayoutConfig` (ScriptableObject): размеры карт, позиции рядов,
|
- `PlaymatLayoutConfig` (ScriptableObject): размеры карт, позиции рядов,
|
||||||
высоты, рука, камера, палитра. Ассет:
|
высоты, рука, камера, палитра. Ассет:
|
||||||
`Runtime/Board3D/Config/PlaymatLayoutConfig.asset`.
|
`Runtime/Board3D/Config/PlaymatLayoutConfig.asset`.
|
||||||
|
|
@ -850,16 +851,21 @@ Battle; Boot и общие сервисы остаются персистент
|
||||||
- `Card3D` — тонкая коробка: отдельные лицо/рубашка/торец, коллайдер, арт из
|
- `Card3D` — тонкая коробка: отдельные лицо/рубашка/торец, коллайдер, арт из
|
||||||
`CardImageService`, переворот вокруг X, Rest вокруг Y по часовой стрелке,
|
`CardImageService`, переворот вокруг X, Rest вокруг Y по часовой стрелке,
|
||||||
урон — против часовой, плавный снап. Карты пулятся (`GameBoard3D`),
|
урон — против часовой, плавный снап. Карты пулятся (`GameBoard3D`),
|
||||||
материалы общие, instancing включён.
|
материалы общие, instancing включён. Рубашка — стандартная VF Vanguard
|
||||||
|
`Resources/CardArt/vanguard_back.png` (источник: Card Sleeve Gallery,
|
||||||
|
`Sleeve_06.png`), общая с 2D-UI; при отсутствии файла остаётся
|
||||||
|
процедурный fallback.
|
||||||
- `GameBoard3D` строит стол, круги, зоны, стопки и веер руки; raycast-клики,
|
- `GameBoard3D` строит стол, круги, зоны, стопки и веер руки; raycast-клики,
|
||||||
drag&drop на плоскость стола с подъёмом, подсветкой допустимых кругов и
|
drag&drop на плоскость стола с подъёмом, подсветкой допустимых кругов и
|
||||||
индикатором точки броска. Ошибка интента возвращает карту на место.
|
индикатором точки броска. Ошибка интента возвращает карту на место.
|
||||||
- Камера приближена: позиция `(0, 6.9, -9.8)`, цель `(0, 0, -0.15)`, FOV 50.
|
- Камера фиксирована над полем: позиция `(1.4, 9, -5.2)`, цель
|
||||||
Рука не лежит на столе, а закреплена в экране снизу по центру
|
`(1.4, 0, -0.1)`, FOV 50; игрок её не двигает. Рука не лежит на столе,
|
||||||
(`handViewportY`, `handViewDistance`) и всегда развёрнута лицом к игроку;
|
а закреплена в экране снизу по центру (`handViewportX/Y`,
|
||||||
при орбите камеры веер следует за полем зрения, драг из руки уходит на стол.
|
`handViewDistance`) и всегда развёрнута лицом к игроку; драг из руки
|
||||||
- `BoardCameraRig` — ПКМ орбита (±32°), СКМ панорама, колесо зум
|
уходит на стол.
|
||||||
(9.5–16); ограничения в конфиге.
|
- Статистика карт вынесена в отдельные UI-панели: своя — снизу слева,
|
||||||
|
противника — сверху справа (`HAND/DECK/DROP/DMG/SOUL`); 3D-подписи
|
||||||
|
стопок и зон больше не дублируют числа.
|
||||||
- Душа: клик по стопке или кнопка «Душа: n» в верхней панели открывает
|
- Душа: клик по стопке или кнопка «Душа: n» в верхней панели открывает
|
||||||
оверлей со списком карт.
|
оверлей со списком карт.
|
||||||
- Песочница: `Assets/Vanguard/Scenes/GameBoard3D.unity` +
|
- Песочница: `Assets/Vanguard/Scenes/GameBoard3D.unity` +
|
||||||
|
|
|
||||||
|
|
@ -65,15 +65,19 @@
|
||||||
остаются uGUI. Карты — тонкие 3D-объекты с артом, рубашкой, торцом и
|
остаются uGUI. Карты — тонкие 3D-объекты с артом, рубашкой, торцом и
|
||||||
коллайдером. Rest (атака/поддержка) — поворот по часовой стрелке вокруг Y,
|
коллайдером. Rest (атака/поддержка) — поворот по часовой стрелке вокруг Y,
|
||||||
урон лежит на боку против часовой; закрытость — переворот вокруг X.
|
урон лежит на боку против часовой; закрытость — переворот вокруг X.
|
||||||
Рука закреплена в поле зрения камеры снизу по центру и всегда смотрит
|
Рубашка — стандартная VF Vanguard (`Resources/CardArt/vanguard_back.png`),
|
||||||
на игрока, а не лежит на столе. Драг-н-дроп на плоскость стола с
|
общая для 2D и 3D. Рука закреплена в поле зрения камеры снизу по центру
|
||||||
подсветкой целей и плавным снапом; камера приближена.
|
и всегда смотрит на игрока, а не лежит на столе. Камера фиксирована над
|
||||||
|
полем под небольшим углом и не двигается игроком. Драг-н-дроп на
|
||||||
|
плоскость стола с подсветкой целей и плавным снапом.
|
||||||
- Поле: у каждого игрока два ряда `R V R` / `R R R` (V + 5 RC), у
|
- Поле: у каждого игрока два ряда `R V R` / `R R R` (V + 5 RC), у
|
||||||
противника зеркально; между полями общий круг защиты; душа — стопка под
|
противника зеркально; между полями общий круг защиты; душа — стопка под
|
||||||
V; колода, дроп и урон — 3D-стопки/карты. Локальный игрок всегда снизу,
|
V; колода, дроп и урон — 3D-стопки/карты. Локальный игрок всегда снизу,
|
||||||
даже если он за вторым местом. ПКМ — орбита, СКМ — панорама, колесо —
|
даже если он за вторым местом. Карты в кругах V/R принудительно
|
||||||
зум. Кнопка «Душа: n» в верхней панели и клик по стопке души открывают
|
показываются лицом вверх.
|
||||||
список карт души.
|
- Статистика карт — отдельное UI-наложение: своя панель снизу слева,
|
||||||
|
противника — сверху справа. Кнопка «Душа: n» в верхней панели и клик по
|
||||||
|
стопке души открывают список карт души.
|
||||||
- Зона урона: карты лежат открытыми и на боку; после Counter Blast
|
- Зона урона: карты лежат открытыми и на боку; после Counter Blast
|
||||||
выбранная карта переворачивается рубашкой. Heal выбирает карту урона;
|
выбранная карта переворачивается рубашкой. Heal выбирает карту урона;
|
||||||
Counter Blast — открытую карту до активации способности.
|
Counter Blast — открытую карту до активации способности.
|
||||||
|
|
@ -95,7 +99,7 @@
|
||||||
открывается из Lobby кнопкой «Коллекция» (сцена `DeckBuilder`).
|
открывается из Lobby кнопкой «Коллекция» (сцена `DeckBuilder`).
|
||||||
- Не готово: 3D-модели карт/стола (пока процедурные примитивы и арты),
|
- Не готово: 3D-модели карт/стола (пока процедурные примитивы и арты),
|
||||||
звук, настройки (язык уже есть), DOTween, drag&drop и live-валидация
|
звук, настройки (язык уже есть), DOTween, drag&drop и live-валидация
|
||||||
декбилдера. Камера боя — ограниченная орбита; анимации retire/ghost для
|
декбилдера. Камера боя фиксирована; анимации retire/ghost для
|
||||||
3D-карт ещё не перенесены.
|
3D-карт ещё не перенесены.
|
||||||
|
|
||||||
## Запуск
|
## Запуск
|
||||||
|
|
@ -170,8 +174,8 @@ client/Assets/Vanguard/Runtime/Board3D/
|
||||||
Logic/BoardPlacementRules.cs Ride/Call/Move/Guard
|
Logic/BoardPlacementRules.cs Ride/Call/Move/Guard
|
||||||
View/GameBoard3D.cs стол, круги, пул карт, drag&drop
|
View/GameBoard3D.cs стол, круги, пул карт, drag&drop
|
||||||
View/Card3D.cs карта: лицо/рубашка/торец, Rest, флип
|
View/Card3D.cs карта: лицо/рубашка/торец, Rest, флип
|
||||||
View/BoardCameraRig.cs орбита/панорама/зум
|
|
||||||
PlaymatSandbox.cs демо без сервера
|
PlaymatSandbox.cs демо без сервера
|
||||||
|
client/Assets/Vanguard/Resources/CardArt/vanguard_back.png рубашка VF
|
||||||
client/Assets/Vanguard/Scenes/GameBoard3D.unity песочница поля
|
client/Assets/Vanguard/Scenes/GameBoard3D.unity песочница поля
|
||||||
client/Assets/Vanguard/Runtime/Battle/GameConnection.cs
|
client/Assets/Vanguard/Runtime/Battle/GameConnection.cs
|
||||||
client/Assets/Vanguard/Runtime/Api/ApiClient.cs + ApiContracts.cs
|
client/Assets/Vanguard/Runtime/Api/ApiClient.cs + ApiContracts.cs
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue