Казармы: найм войск карточками в ряд, сильные и слабые стороны из правил боя, место под иконки
This commit is contained in:
@@ -4,7 +4,8 @@ using UnityEngine.UI;
|
||||
using TMPro;
|
||||
|
||||
// ВОЙСКА: армия, очередь обучения (идёт и когда игра закрыта), найм воинов за ресурсы,
|
||||
// ускорение за кристаллы. Новые типы войск открываются уровнем Ратуши.
|
||||
// ускорение за кристаллы. Найм -- карточки войск в ряд (листаются вбок): иконка, сила, цена,
|
||||
// против кого силён и слаб (из правил боя). Новые типы войск открывают уровни Ратуши и Казарм.
|
||||
public class TroopsPanel : BasePanel
|
||||
{
|
||||
readonly List<Image> queueBars = new List<Image>();
|
||||
@@ -101,37 +102,133 @@ public class TroopsPanel : BasePanel
|
||||
}
|
||||
Tick();
|
||||
|
||||
// --- Типы войск ---
|
||||
for (int i = 0; i < GameData.Troops.Count; i++)
|
||||
// --- Типы войск: карточки в ряд, листаются вбок ---
|
||||
var troopsCard = UIKit.Card(list, UIKit.Theme.CardDark);
|
||||
ProfilePanel.Header(troopsCard, "Найм войск");
|
||||
UIKit.Text(troopsCard, "Листайте карточки вбок →", 22, UIKit.Theme.TextDim);
|
||||
var strip = HorizontalStrip(troopsCard, CardHeight);
|
||||
for (int i = 0; i < GameData.Troops.Count; i++) TroopCard(strip, i);
|
||||
}
|
||||
|
||||
// ---------------- Карточка войска ----------------
|
||||
// Место под иконку: пока квадрат цвета войска с первой буквой. Картинку поставим в TroopIcon, когда будет арт.
|
||||
// "Силён / слаб против" берётся из правил боя (контры в модуле Боя и BattleConfig), а не пишется руками:
|
||||
// поменяют контры -- поменяется и текст.
|
||||
|
||||
const float CardWidth = 340f;
|
||||
const float CardHeight = 640f;
|
||||
|
||||
void TroopCard(Transform strip, int i)
|
||||
{
|
||||
int idx = i;
|
||||
var def = GameData.Troops[i];
|
||||
bool unlocked = GP.TroopUnlocked(i);
|
||||
|
||||
var card = UIKit.Card(strip, unlocked ? UIKit.Theme.Card : UIKit.Theme.CardDark, 16, 8);
|
||||
UIKit.Size(card, CardWidth, -1);
|
||||
|
||||
var iconRow = UIKit.Row(card, 130, 0);
|
||||
var lpad = UIKit.Make("Pad", iconRow); UIKit.Size(lpad, -1, -1, 1);
|
||||
TroopIcon(iconRow, i, unlocked);
|
||||
var rpad = UIKit.Make("Pad", iconRow); UIKit.Size(rpad, -1, -1, 1);
|
||||
|
||||
UIKit.Text(card, def.name, 32, UIKit.Theme.Text, TextAlignmentOptions.Center).fontStyle = FontStyles.Bold;
|
||||
UIKit.Text(card, "в армии: " + GP.TroopCount(i).ToString("N0"), 22, UIKit.Theme.TextDim, TextAlignmentOptions.Center);
|
||||
UIKit.Text(card, "Сила " + def.power + " • " + def.secondsPerUnit + " сек/воин", 22, UIKit.Theme.Text, TextAlignmentOptions.Center);
|
||||
UIKit.Text(card, GameData.RewardsText(def.cost), 22, UIKit.Theme.TextDim, TextAlignmentOptions.Center);
|
||||
|
||||
string strong, weak;
|
||||
CounterText(i, out strong, out weak);
|
||||
if (strong.Length > 0) UIKit.Text(card, "<color=#9be59b>Силён против:</color> " + strong, 22, UIKit.Theme.Text);
|
||||
if (weak.Length > 0) UIKit.Text(card, "<color=#ff8a80>Слаб против:</color> " + weak, 22, UIKit.Theme.Text);
|
||||
UIKit.Text(card, def.description, 20, UIKit.Theme.TextDim);
|
||||
|
||||
var fill = UIKit.Make("Fill", card); UIKit.Size(fill, -1, -1).flexibleHeight = 1;
|
||||
|
||||
if (!unlocked)
|
||||
{
|
||||
int idx = i;
|
||||
var def = GameData.Troops[i];
|
||||
bool unlocked = GP.TroopUnlocked(i);
|
||||
|
||||
var card = UIKit.Card(list, unlocked ? UIKit.Theme.Card : UIKit.Theme.CardDark);
|
||||
var top = UIKit.Row(card, 120, 16);
|
||||
UIKit.Icon(top, unlocked ? def.color : UIKit.Theme.Gray, def.name.Substring(0, 1), 110);
|
||||
var info = UIKit.Card(top, new Color(0, 0, 0, 0), 0, 2);
|
||||
UIKit.Size(info, -1, -1, 1);
|
||||
UIKit.Text(info, def.name + " <color=#c8c8c8><size=24>в армии: " + GP.TroopCount(i).ToString("N0") + "</size></color>", 32).fontStyle = FontStyles.Bold;
|
||||
UIKit.Text(info, def.description, 22, UIKit.Theme.TextDim);
|
||||
UIKit.Text(info, "Сила " + def.power + " • " + def.secondsPerUnit + " сек/воин • " + GameData.RewardsText(def.cost), 22, UIKit.Theme.TextDim);
|
||||
|
||||
if (!unlocked)
|
||||
{
|
||||
UIKit.Text(card, "Откроется: " + GP.TroopLockText(i), 26, UIKit.Theme.Gold, TextAlignmentOptions.Center);
|
||||
continue;
|
||||
}
|
||||
|
||||
var btns = UIKit.Row(card, 80, 10);
|
||||
foreach (int amount in Amounts)
|
||||
{
|
||||
int a = amount;
|
||||
bool can = Barracks.Count > 0 && GP.CanAfford(def.cost, a) && GP.Training.Count < Barracks.QueueSize && GP.Recruitable >= a;
|
||||
var b = UIKit.Button(btns, "+" + a, can ? UIKit.Theme.Green : UIKit.Theme.Gray, () => GP.Train(idx, a), -1, 80, 30);
|
||||
UIKit.Size(b, -1, 80, 1);
|
||||
}
|
||||
UIKit.Text(card, "Откроется:\n" + GP.TroopLockText(i), 24, UIKit.Theme.Gold, TextAlignmentOptions.Center);
|
||||
return;
|
||||
}
|
||||
|
||||
var btns = UIKit.Row(card, 70, 8);
|
||||
foreach (int amount in Amounts)
|
||||
{
|
||||
int a = amount;
|
||||
bool can = Barracks.Count > 0 && GP.CanAfford(def.cost, a) && GP.Training.Count < Barracks.QueueSize && GP.Recruitable >= a;
|
||||
var b = UIKit.Button(btns, "+" + a, can ? UIKit.Theme.Green : UIKit.Theme.Gray, () => GP.Train(idx, a), -1, 70, 26);
|
||||
UIKit.Size(b, -1, 70, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Иконка войска. Сейчас -- заглушка (цветной квадрат с буквой); сюда же потом подставим спрайт.
|
||||
static void TroopIcon(Transform parent, int i, bool unlocked)
|
||||
{
|
||||
var def = GameData.Troops[i];
|
||||
UIKit.Icon(parent, unlocked ? def.color : UIKit.Theme.Gray, def.name.Substring(0, 1), 120);
|
||||
}
|
||||
|
||||
static readonly string[] TroopNamesGenitive = { "пехоты", "лучников", "кавалерии", "осадных" };
|
||||
|
||||
// Контры из правил боя: против кого этот тип сильнее и кто сильнее против него
|
||||
static void CounterText(int type, out string strong, out string weak)
|
||||
{
|
||||
var s = new List<string>();
|
||||
var w = new List<string>();
|
||||
var battle = GameServices.TryGet(out IBattleSystem b) ? b as BattleSystem : null;
|
||||
if (battle != null)
|
||||
for (int j = 0; j < TroopType.Count && j < GameData.Troops.Count; j++)
|
||||
{
|
||||
if (j == type) continue;
|
||||
int mine = battle.Counter(type, j), theirs = battle.Counter(j, type);
|
||||
if (mine > 0) s.Add(Genitive(j) + " (+" + mine + "% силы)");
|
||||
if (theirs > 0) w.Add(Genitive(j));
|
||||
}
|
||||
if (type == TroopType.Siege)
|
||||
{
|
||||
var cfg = BattleConfig.Load();
|
||||
if (cfg.siegeVsWallsPercent > 0) s.Add("стен при штурме (+" + cfg.siegeVsWallsPercent + "%)");
|
||||
if (cfg.siegeInFieldPercent < 0) w.Add("всех в поле и в обороне (" + cfg.siegeInFieldPercent + "%)");
|
||||
}
|
||||
strong = string.Join(", ", s.ToArray());
|
||||
weak = string.Join(", ", w.ToArray());
|
||||
}
|
||||
|
||||
static string Genitive(int type)
|
||||
{
|
||||
return type >= 0 && type < TroopNamesGenitive.Length ? TroopNamesGenitive[type] : GameData.Troops[type].name;
|
||||
}
|
||||
|
||||
// Горизонтальная лента с прокруткой вбок внутри общего вертикального списка.
|
||||
// Вертикальный свайп по ленте прокручивает весь список (NestedScrollRect).
|
||||
static RectTransform HorizontalStrip(Transform parent, float height)
|
||||
{
|
||||
var root = UIKit.Make("TroopStrip", parent);
|
||||
UIKit.Size(root, -1, height);
|
||||
root.gameObject.AddComponent<Image>().color = new Color(0, 0, 0, 0);
|
||||
var scroll = root.gameObject.AddComponent<NestedScrollRect>();
|
||||
scroll.horizontal = true;
|
||||
scroll.vertical = false;
|
||||
scroll.movementType = ScrollRect.MovementType.Elastic;
|
||||
scroll.scrollSensitivity = 40f;
|
||||
|
||||
var viewport = UIKit.Stretch(UIKit.Make("Viewport", root));
|
||||
viewport.gameObject.AddComponent<RectMask2D>();
|
||||
|
||||
var content = UIKit.Make("Content", viewport);
|
||||
content.anchorMin = new Vector2(0, 0); content.anchorMax = new Vector2(0, 1);
|
||||
content.pivot = new Vector2(0, 0.5f);
|
||||
content.offsetMin = Vector2.zero; content.offsetMax = Vector2.zero;
|
||||
var h = content.gameObject.AddComponent<HorizontalLayoutGroup>();
|
||||
h.spacing = 14;
|
||||
h.padding = new RectOffset(4, 4, 4, 4);
|
||||
h.childControlWidth = true; h.childControlHeight = true;
|
||||
h.childForceExpandWidth = false; h.childForceExpandHeight = true;
|
||||
content.gameObject.AddComponent<ContentSizeFitter>().horizontalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||
|
||||
scroll.viewport = viewport;
|
||||
scroll.content = content;
|
||||
return content;
|
||||
}
|
||||
|
||||
// Окно перестраивается само: Армия публикует TroopsChangedEvent, GameProgress сообщает об изменении
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
// Прокрутка внутри прокрутки (например, лента карточек вбок внутри вертикального списка).
|
||||
// Свайп в сторону своей оси листает эту ленту, свайп поперёк и колесо мыши -- родительский список.
|
||||
// Без этого палец на ленте "застревает": общий список не прокручивается.
|
||||
public class NestedScrollRect : ScrollRect
|
||||
{
|
||||
ScrollRect parentScroll;
|
||||
bool routeToParent;
|
||||
|
||||
protected override void Start()
|
||||
{
|
||||
base.Start();
|
||||
if (transform.parent != null) parentScroll = transform.parent.GetComponentInParent<ScrollRect>();
|
||||
}
|
||||
|
||||
bool CrossAxis(PointerEventData e)
|
||||
{
|
||||
float dx = Mathf.Abs(e.delta.x), dy = Mathf.Abs(e.delta.y);
|
||||
return horizontal && !vertical ? dy > dx : vertical && !horizontal ? dx > dy : false;
|
||||
}
|
||||
|
||||
public override void OnInitializePotentialDrag(PointerEventData e)
|
||||
{
|
||||
if (parentScroll != null) parentScroll.OnInitializePotentialDrag(e);
|
||||
base.OnInitializePotentialDrag(e);
|
||||
}
|
||||
|
||||
public override void OnBeginDrag(PointerEventData e)
|
||||
{
|
||||
routeToParent = parentScroll != null && CrossAxis(e);
|
||||
if (routeToParent) parentScroll.OnBeginDrag(e);
|
||||
else base.OnBeginDrag(e);
|
||||
}
|
||||
|
||||
public override void OnDrag(PointerEventData e)
|
||||
{
|
||||
if (routeToParent) parentScroll.OnDrag(e);
|
||||
else base.OnDrag(e);
|
||||
}
|
||||
|
||||
public override void OnEndDrag(PointerEventData e)
|
||||
{
|
||||
if (routeToParent) parentScroll.OnEndDrag(e);
|
||||
else base.OnEndDrag(e);
|
||||
routeToParent = false;
|
||||
}
|
||||
|
||||
public override void OnScroll(PointerEventData e)
|
||||
{
|
||||
if (parentScroll != null) parentScroll.OnScroll(e);
|
||||
else base.OnScroll(e);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 59a54e4603712204d81f167a1c02873f
|
||||
Reference in New Issue
Block a user