2026-11-09 14:12:52 +00:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.Globalization;
|
|
|
|
|
|
using TKS.OutGame;
|
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
using UnityEngine.UI;
|
|
|
|
|
|
|
|
|
|
|
|
namespace TKS
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 스킬 테스트 씬(<c>Assets/Scenes/SkillTest.unity</c> · 메뉴 TKS/Skill Test/Build Scene · 2026-11-09 PD
|
|
|
|
|
|
/// 「유닛마다 액티브 스킬을 구현하기 쉽도록 목록에서 유닛을 선택 · 선택된 유닛으로 기본 스킬 사용 방식 및 슬링샷 조작이 가능한 공간」).
|
|
|
|
|
|
/// WL #947 스킬 테스트 씬과 같은 역할 — 전투 코드(BattleManager · SlingshotInput · ActionResolver · SkillSystem · BattleHUD)는 그대로 쓰고
|
|
|
|
|
|
/// 월드맵·아웃게임·세이브 없이 「고른 영웅 1 + 더미」 만 세운다.
|
|
|
|
|
|
/// 더미 = 죽지 않음 · 기본 행동 안 함(토글) · 배치 = 표 <c>Resources/Skills/skill_test.csv</c>(영웅 출발 자리 기준 상대 좌표).
|
|
|
|
|
|
/// 왼쪽 위 「유닛」 = 영웅 목록(heroes.csv · 병사 제외) · 「이펙트」 = 팩 이펙트 미리보기(에디터 전용 · <c>Assets/VFX</c> 프리팹 검색 → 고른 영웅에 띄움 · 경로 복사).
|
|
|
|
|
|
/// 스킬 이펙트 값 = <c>Resources/Skills/skill_vfx.csv</c>(SkillVfx) · 「다시 배치」 = 표 다시 읽기 + 처음 자리.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public class SkillTestBootstrap : MonoBehaviour
|
|
|
|
|
|
{
|
|
|
|
|
|
public const string DummyTablePath = "Skills/skill_test";
|
|
|
|
|
|
const string PrefKey = "TKS_SKILLTEST_HERO";
|
|
|
|
|
|
public const string VfxRoot = "Assets/VFX";
|
2026-11-10 04:19:44 +00:00
|
|
|
|
/// <summary>「기운 시험」 스위치가 켜는 이펙트 표 키(skill_vfx.csv test_aura 행).</summary>
|
|
|
|
|
|
public const string AuraTestKey = "test_aura";
|
2026-11-09 14:12:52 +00:00
|
|
|
|
|
|
|
|
|
|
public BalanceConfig Config;
|
|
|
|
|
|
|
|
|
|
|
|
public static SkillTestBootstrap Instance { get; private set; }
|
|
|
|
|
|
public BattleManager Battle { get; private set; }
|
|
|
|
|
|
public BattleHUD Hud { get; private set; }
|
|
|
|
|
|
public SlingshotInput Sling { get; private set; }
|
|
|
|
|
|
public CameraController CameraCtrl { get; private set; }
|
|
|
|
|
|
public HeroDb.Hero Current { get; private set; }
|
|
|
|
|
|
public IReadOnlyList<HeroDb.Hero> Heroes
|
|
|
|
|
|
{
|
|
|
|
|
|
get { return heroes; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public bool NoCooldown = true;
|
|
|
|
|
|
public bool EnemyActs;
|
|
|
|
|
|
public bool HeroInvincible = true;
|
|
|
|
|
|
|
|
|
|
|
|
readonly List<HeroDb.Hero> heroes = new List<HeroDb.Hero>();
|
|
|
|
|
|
readonly List<RectTransform> blockers = new List<RectTransform>();
|
|
|
|
|
|
Camera cam;
|
|
|
|
|
|
Text infoText;
|
2026-11-10 04:19:44 +00:00
|
|
|
|
Text cdLabel, enemyLabel, invLabel, auraLabel;
|
2026-11-09 14:12:52 +00:00
|
|
|
|
RectTransform heroPanel, fxPanel;
|
|
|
|
|
|
|
|
|
|
|
|
void Awake()
|
|
|
|
|
|
{
|
|
|
|
|
|
Instance = this;
|
|
|
|
|
|
if (Config == null) Config = Resources.Load<BalanceConfig>("BalanceConfig");
|
|
|
|
|
|
if (Config == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.LogError("[TKS] BalanceConfig 자산이 없습니다. 메뉴 TKS/Setup Project 를 실행하세요.");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
cam = Camera.main;
|
|
|
|
|
|
if (cam == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
var cgo = new GameObject("Main Camera");
|
|
|
|
|
|
cgo.tag = "MainCamera";
|
|
|
|
|
|
cam = cgo.AddComponent<Camera>();
|
|
|
|
|
|
}
|
|
|
|
|
|
cam.orthographic = true;
|
|
|
|
|
|
cam.clearFlags = CameraClearFlags.SolidColor;
|
|
|
|
|
|
cam.backgroundColor = new Color(0.07f, 0.09f, 0.11f);
|
|
|
|
|
|
cam.transform.position = new Vector3(0f, 0f, -10f);
|
|
|
|
|
|
cam.nearClipPlane = 0.1f;
|
|
|
|
|
|
cam.farClipPlane = 100f;
|
|
|
|
|
|
cam.transparencySortMode = TransparencySortMode.CustomAxis;
|
|
|
|
|
|
cam.transparencySortAxis = Vector3.up;
|
|
|
|
|
|
cam.depthTextureMode |= DepthTextureMode.Depth; // 이펙트 셰이더 소프트 파티클(GameBootstrap 과 같음)
|
|
|
|
|
|
|
|
|
|
|
|
var board = new GameObject("Board").AddComponent<Board>();
|
|
|
|
|
|
board.Build(Config.boardSize, Config.boardArt);
|
|
|
|
|
|
|
|
|
|
|
|
CameraCtrl = cam.GetComponent<CameraController>();
|
|
|
|
|
|
if (CameraCtrl == null) CameraCtrl = cam.gameObject.AddComponent<CameraController>();
|
|
|
|
|
|
CameraCtrl.Init(board, Config.cameraViewHeight, Config.cameraMinViewHeight, Config.cameraMaxViewHeight);
|
|
|
|
|
|
|
|
|
|
|
|
Battle = new GameObject("BattleManager").AddComponent<BattleManager>();
|
|
|
|
|
|
Battle.Configure(Config, board);
|
|
|
|
|
|
Battle.LineupProvider = Lineup;
|
|
|
|
|
|
Battle.EnemyHold = !EnemyActs;
|
|
|
|
|
|
|
|
|
|
|
|
Hud = new GameObject("HUD").AddComponent<BattleHUD>();
|
|
|
|
|
|
Hud.Build(Battle, cam, CameraCtrl);
|
|
|
|
|
|
Battle.Hud = Hud;
|
|
|
|
|
|
|
|
|
|
|
|
Sling = new GameObject("SlingshotInput").AddComponent<SlingshotInput>();
|
|
|
|
|
|
Sling.Init(Battle, cam, Hud, CameraCtrl);
|
|
|
|
|
|
Sling.BlockPress = OverPanel;
|
|
|
|
|
|
Battle.Sling = Sling;
|
|
|
|
|
|
|
|
|
|
|
|
var cc = CameraCtrl;
|
|
|
|
|
|
Battle.UnitActing += u => cc.Follow(u.transform);
|
|
|
|
|
|
Battle.PhaseChanged += OnPhase;
|
|
|
|
|
|
Battle.StageStarted += () => cc.SnapTo(Focus());
|
|
|
|
|
|
|
|
|
|
|
|
var db = OutGameData.Default;
|
|
|
|
|
|
if (db != null && db.Heroes != null)
|
|
|
|
|
|
foreach (var h in db.Heroes.heroes)
|
|
|
|
|
|
if (h != null && !h.isSoldier && !string.IsNullOrEmpty(h.id)) heroes.Add(h);
|
|
|
|
|
|
if (heroes.Count == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.LogError("[TKS] 스킬 테스트: 영웅 표가 비었습니다(메뉴 TKS/Import OutGame Data).");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 진영(촉 · 위 · 오 · 군) 순으로 묶되 진영 안은 표 순서 그대로(안정 정렬)
|
|
|
|
|
|
var order = new List<string>();
|
|
|
|
|
|
foreach (var h in heroes)
|
|
|
|
|
|
if (!order.Contains(h.faction)) order.Add(h.faction);
|
|
|
|
|
|
var sorted = new List<HeroDb.Hero>();
|
|
|
|
|
|
foreach (var f in order)
|
|
|
|
|
|
foreach (var h in heroes)
|
|
|
|
|
|
if (h.faction == f) sorted.Add(h);
|
|
|
|
|
|
heroes.Clear();
|
|
|
|
|
|
heroes.AddRange(sorted);
|
|
|
|
|
|
|
|
|
|
|
|
BuildUi();
|
|
|
|
|
|
string last = PlayerPrefs.GetString(PrefKey, "");
|
|
|
|
|
|
var first = heroes.Find(h => h.id == last) ?? heroes[0];
|
|
|
|
|
|
SelectHero(first);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void OnDestroy()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (Instance == this) Instance = null;
|
2026-11-10 04:19:44 +00:00
|
|
|
|
SkillVfx.TestTravelKey = ""; // 시험 키가 다른 씬(실제 전투)으로 새지 않게
|
2026-11-09 14:12:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------- 전투 구성 ----------
|
|
|
|
|
|
|
|
|
|
|
|
List<LineupEntry> Lineup()
|
|
|
|
|
|
{
|
|
|
|
|
|
var list = new List<LineupEntry>();
|
|
|
|
|
|
var h = Current;
|
|
|
|
|
|
if (h == null) return list;
|
|
|
|
|
|
list.Add(new LineupEntry
|
|
|
|
|
|
{
|
|
|
|
|
|
slot = heroSlot, id = h.id, name = h.nameKo, type = h.type, art = h.art,
|
|
|
|
|
|
hpMult = h.hpMult, atkMult = h.atkMult, defMult = h.defMult,
|
|
|
|
|
|
skillActive = h.skillActive ?? "", skillPassive = h.skillPassive ?? "", activeLv = 1, passiveLv = 1, passiveMult = 1f, general = true,
|
|
|
|
|
|
});
|
|
|
|
|
|
return list;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void SelectHero(HeroDb.Hero h)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (h == null) return;
|
|
|
|
|
|
Current = h;
|
|
|
|
|
|
PlayerPrefs.SetString(PrefKey, h.id);
|
|
|
|
|
|
if (heroPanel != null) heroPanel.gameObject.SetActive(false);
|
|
|
|
|
|
Restart();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>처음 자리로 다시 배치(표 skill_vfx.csv · skill_test.csv 다시 읽음).</summary>
|
|
|
|
|
|
public void Restart()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (Battle == null || Current == null) return;
|
|
|
|
|
|
Battle.EnemyHold = !EnemyActs;
|
|
|
|
|
|
var stage = MakeStage(); // 영웅 자리(heroSlot)도 이 표에서 → Lineup 보다 먼저
|
|
|
|
|
|
var mode = new BattleMode { kind = "skilltest", stage = stage, maxTurns = 0, onExit = Restart };
|
|
|
|
|
|
Battle.StartMode(mode);
|
|
|
|
|
|
ApplyFlags();
|
|
|
|
|
|
RefreshInfo();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>영웅 출발 자리 = BalanceConfig.playerSpawnPoints[heroSlot](표 skill_test.csv 의 e_type = hero 행 n_slot).</summary>
|
|
|
|
|
|
int heroSlot;
|
|
|
|
|
|
|
|
|
|
|
|
Vector2 HeroSpawn
|
|
|
|
|
|
{
|
|
|
|
|
|
get
|
|
|
|
|
|
{
|
|
|
|
|
|
var sp = Config.playerSpawnPoints;
|
|
|
|
|
|
return sp != null && sp.Length > 0 ? sp[Mathf.Clamp(heroSlot, 0, sp.Length - 1)] : Vector2.zero;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
BalanceConfig.StageDef MakeStage()
|
|
|
|
|
|
{
|
|
|
|
|
|
var st = new BalanceConfig.StageDef { title = "스킬 테스트", subtitle = "", aiAccuracy = 1f, aiAggroRange = 999f };
|
|
|
|
|
|
var ta = Resources.Load<TextAsset>(DummyTablePath);
|
|
|
|
|
|
heroSlot = 0;
|
|
|
|
|
|
if (ta == null) return st;
|
|
|
|
|
|
var rows = new List<string[]>();
|
|
|
|
|
|
string[] head = null;
|
|
|
|
|
|
foreach (var raw in ta.text.Replace("\r", "").Split('\n'))
|
|
|
|
|
|
{
|
|
|
|
|
|
var line = raw.TrimStart('');
|
|
|
|
|
|
if (line.Trim().Length == 0 || line.TrimStart().StartsWith("#")) continue;
|
|
|
|
|
|
var c = SkillVfx.SplitCsv(line);
|
|
|
|
|
|
if (head == null) head = c;
|
|
|
|
|
|
else rows.Add(c);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (head == null) return st;
|
|
|
|
|
|
int iType = Array.IndexOf(head, "e_type"), iSlot = Array.IndexOf(head, "n_slot");
|
|
|
|
|
|
foreach (var c in rows) // 영웅 자리 먼저(더미 좌표 = 영웅 자리 기준)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (iType < 0 || iType >= c.Length || c[iType].Trim().ToLowerInvariant() != "hero") continue;
|
|
|
|
|
|
if (iSlot >= 0 && iSlot < c.Length) int.TryParse(c[iSlot].Trim(), out heroSlot);
|
|
|
|
|
|
}
|
|
|
|
|
|
head = null;
|
|
|
|
|
|
foreach (var raw in ta.text.Replace("\r", "").Split('\n'))
|
|
|
|
|
|
{
|
|
|
|
|
|
var line = raw.TrimStart('');
|
|
|
|
|
|
if (line.Trim().Length == 0 || line.TrimStart().StartsWith("#")) continue;
|
|
|
|
|
|
var c = SkillVfx.SplitCsv(line);
|
|
|
|
|
|
if (head == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
head = c;
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
UnitType type = UnitType.Infantry;
|
|
|
|
|
|
float dx = 0f, dy = 0f, hp = 1f;
|
|
|
|
|
|
string name = "";
|
|
|
|
|
|
bool skip = false;
|
|
|
|
|
|
for (int i = 0; i < head.Length && i < c.Length; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
string k = head[i].Trim(), v = c[i].Trim();
|
|
|
|
|
|
if (v.Length == 0) continue;
|
|
|
|
|
|
if (k == "e_type")
|
|
|
|
|
|
{
|
|
|
|
|
|
if (v.ToLowerInvariant() == "hero" || !Enum.TryParse(v, true, out type)) skip = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (k == "f_dx") dx = F(v);
|
|
|
|
|
|
else if (k == "f_dy") dy = F(v);
|
|
|
|
|
|
else if (k == "f_hp") hp = F(v);
|
|
|
|
|
|
else if (k == "s_name") name = v;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (skip) continue;
|
|
|
|
|
|
st.enemies.Add(new BalanceConfig.EnemySpawn
|
|
|
|
|
|
{
|
|
|
|
|
|
name = name.Length > 0 ? name : "더미 " + UnitTypeInfo.Korean(type),
|
|
|
|
|
|
type = type,
|
|
|
|
|
|
position = HeroSpawn + new Vector2(dx, dy),
|
|
|
|
|
|
hpMult = hp > 0f ? hp : 1f,
|
|
|
|
|
|
art = DummyArt(type),
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
return st;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static float F(string v)
|
|
|
|
|
|
{
|
|
|
|
|
|
float f;
|
|
|
|
|
|
return float.TryParse(v, NumberStyles.Float, CultureInfo.InvariantCulture, out f) ? f : 0f;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>더미 그림 = 스테이지 표의 첫 번째 같은 병종 병사 그림(장수 제외 · 없으면 도형).</summary>
|
|
|
|
|
|
string DummyArt(UnitType t)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (Config.stages == null) return "";
|
|
|
|
|
|
foreach (var s in Config.stages)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (s == null || s.enemies == null) continue;
|
|
|
|
|
|
foreach (var e in s.enemies)
|
|
|
|
|
|
if (e != null && e.type == t && !e.isBoss && !e.isGeneral && !string.IsNullOrEmpty(e.art)) return e.art;
|
|
|
|
|
|
}
|
|
|
|
|
|
return "";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ApplyFlags()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (Battle == null) return;
|
|
|
|
|
|
foreach (var u in Battle.Units)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (u == null) continue;
|
|
|
|
|
|
u.Invincible = u.Faction == Faction.Enemy || HeroInvincible;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void OnPhase(BattlePhase p)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (p != BattlePhase.PlayerTurn) return;
|
|
|
|
|
|
ApplyFlags();
|
|
|
|
|
|
foreach (var u in Battle.Units)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (u == null || !u.IsAlive) continue;
|
|
|
|
|
|
if (u.Faction == Faction.Enemy && u.Hp < u.MaxHp)
|
|
|
|
|
|
{
|
|
|
|
|
|
u.Hp = u.MaxHp; // 더미 체력 회복(다음 시험을 같은 조건으로)
|
|
|
|
|
|
if (u.View != null) u.View.Refresh();
|
|
|
|
|
|
}
|
|
|
|
|
|
if (NoCooldown && u.Faction == Faction.Player && Battle.Skills != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
var s = Battle.Skills.Of(u);
|
|
|
|
|
|
if (s != null) s.ReadyTurn = Battle.TurnNumber;
|
|
|
|
|
|
if (u.View != null) u.View.Refresh();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (CameraCtrl != null) CameraCtrl.FocusOn(Focus());
|
|
|
|
|
|
Battle.NotifyChanged();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Vector2 Focus()
|
|
|
|
|
|
{
|
|
|
|
|
|
var ps = Battle.AliveUnits(Faction.Player);
|
|
|
|
|
|
Vector2 p = ps.Count > 0 ? ps[0].Position : HeroSpawn;
|
|
|
|
|
|
return p + new Vector2(0f, Config.cameraViewHeight * 0.2f);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------- UI ----------
|
|
|
|
|
|
|
|
|
|
|
|
static readonly Color PanelColor = new Color(0.08f, 0.09f, 0.12f, 0.92f);
|
|
|
|
|
|
static readonly Color BtnColor = new Color(0.22f, 0.26f, 0.34f, 0.95f);
|
|
|
|
|
|
static readonly Color BtnOnColor = new Color(0.25f, 0.5f, 0.32f, 0.95f);
|
|
|
|
|
|
static readonly Color TextColor = new Color(0.95f, 0.93f, 0.86f);
|
|
|
|
|
|
|
|
|
|
|
|
bool OverPanel(Vector2 screen)
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var rt in blockers)
|
|
|
|
|
|
if (rt != null && rt.gameObject.activeInHierarchy && RectTransformUtility.RectangleContainsScreenPoint(rt, screen, null)) return true;
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void BuildUi()
|
|
|
|
|
|
{
|
|
|
|
|
|
var cgo = new GameObject("SkillTestUI");
|
|
|
|
|
|
var canvas = cgo.AddComponent<Canvas>();
|
|
|
|
|
|
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
|
|
|
|
|
canvas.sortingOrder = 200;
|
|
|
|
|
|
var scaler = cgo.AddComponent<CanvasScaler>();
|
|
|
|
|
|
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
|
|
|
|
|
|
scaler.referenceResolution = new Vector2(1080f, 1920f);
|
|
|
|
|
|
scaler.matchWidthOrHeight = 0.5f;
|
|
|
|
|
|
cgo.AddComponent<GraphicRaycaster>();
|
|
|
|
|
|
var root = cgo.transform;
|
|
|
|
|
|
|
|
|
|
|
|
// 왼쪽 위: 유닛 · 이펙트 열기 + 고른 영웅 정보
|
2026-11-10 04:19:44 +00:00
|
|
|
|
var top = Panel("Top", root, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(16f, -290f), new Vector2(640f, 300f), PanelColor);
|
2026-11-09 14:12:52 +00:00
|
|
|
|
blockers.Add(top);
|
|
|
|
|
|
Btn(top, "▤ 유닛 선택", new Vector2(10f, -10f), new Vector2(300f, 76f), () => Toggle(heroPanel));
|
|
|
|
|
|
Btn(top, "✦ 이펙트 보기", new Vector2(320f, -10f), new Vector2(310f, 76f), () => Toggle(fxPanel));
|
|
|
|
|
|
infoText = Label(top, "", 27, TextAnchor.UpperLeft);
|
|
|
|
|
|
SetRect(infoText.rectTransform, Vector2.zero, Vector2.one, new Vector2(0.5f, 0.5f), Vector2.zero, Vector2.zero);
|
|
|
|
|
|
infoText.rectTransform.offsetMin = new Vector2(14f, 6f);
|
|
|
|
|
|
infoText.rectTransform.offsetMax = new Vector2(-14f, -94f);
|
|
|
|
|
|
|
|
|
|
|
|
// 왼쪽 아래: 시험 토글
|
|
|
|
|
|
var tools = Panel("Tools", root, Vector2.zero, Vector2.zero, Vector2.zero, new Vector2(16f, 40f), new Vector2(560f, 260f), PanelColor);
|
|
|
|
|
|
blockers.Add(tools);
|
|
|
|
|
|
Btn(tools, "다시 배치", new Vector2(10f, -10f), new Vector2(265f, 74f), Restart);
|
|
|
|
|
|
cdLabel = Btn(tools, "", new Vector2(285f, -10f), new Vector2(265f, 74f), () =>
|
|
|
|
|
|
{
|
|
|
|
|
|
NoCooldown = !NoCooldown;
|
|
|
|
|
|
OnPhase(Battle.Phase);
|
|
|
|
|
|
RefreshInfo();
|
|
|
|
|
|
});
|
|
|
|
|
|
enemyLabel = Btn(tools, "", new Vector2(10f, -94f), new Vector2(265f, 74f), () =>
|
|
|
|
|
|
{
|
|
|
|
|
|
EnemyActs = !EnemyActs;
|
|
|
|
|
|
Battle.EnemyHold = !EnemyActs;
|
|
|
|
|
|
RefreshInfo();
|
|
|
|
|
|
});
|
|
|
|
|
|
invLabel = Btn(tools, "", new Vector2(285f, -94f), new Vector2(265f, 74f), () =>
|
|
|
|
|
|
{
|
|
|
|
|
|
HeroInvincible = !HeroInvincible;
|
|
|
|
|
|
ApplyFlags();
|
|
|
|
|
|
RefreshInfo();
|
|
|
|
|
|
});
|
2026-11-10 04:19:44 +00:00
|
|
|
|
Btn(tools, "이펙트 표 다시 읽기", new Vector2(10f, -178f), new Vector2(265f, 70f), () =>
|
2026-11-09 14:12:52 +00:00
|
|
|
|
{
|
|
|
|
|
|
SkillVfx.Reload();
|
2026-11-10 04:19:44 +00:00
|
|
|
|
HitFeel.Reload();
|
2026-11-09 14:12:52 +00:00
|
|
|
|
Battle.LastMessage = "skill_vfx.csv 다시 읽음 · 행 " + SkillVfx.Rows.Count;
|
|
|
|
|
|
Battle.NotifyChanged();
|
|
|
|
|
|
RefreshInfo();
|
|
|
|
|
|
});
|
2026-11-10 04:19:44 +00:00
|
|
|
|
// 몸 뒤덮는 기운 시험(PD 2026-11-10 「첨부 같은 느낌으로 몸을 뒤덮는 연출 · 스킬용 시험」): 켜면 슬링샷 공격 돌진에 표 test_aura 행을 함께 띄움
|
|
|
|
|
|
auraLabel = Btn(tools, "", new Vector2(285f, -178f), new Vector2(265f, 70f), () =>
|
|
|
|
|
|
{
|
|
|
|
|
|
SkillVfx.TestTravelKey = SkillVfx.TestTravelKey.Length > 0 ? "" : AuraTestKey;
|
|
|
|
|
|
RefreshInfo();
|
|
|
|
|
|
});
|
2026-11-09 14:12:52 +00:00
|
|
|
|
|
|
|
|
|
|
heroPanel = BuildHeroList(root);
|
|
|
|
|
|
blockers.Add(heroPanel);
|
|
|
|
|
|
fxPanel = BuildFxBrowser(root);
|
|
|
|
|
|
blockers.Add(fxPanel);
|
|
|
|
|
|
heroPanel.gameObject.SetActive(false);
|
|
|
|
|
|
fxPanel.gameObject.SetActive(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Toggle(RectTransform p)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (p == null) return;
|
|
|
|
|
|
bool on = !p.gameObject.activeSelf;
|
|
|
|
|
|
heroPanel.gameObject.SetActive(false);
|
|
|
|
|
|
fxPanel.gameObject.SetActive(false);
|
|
|
|
|
|
p.gameObject.SetActive(on);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
RectTransform BuildHeroList(Transform root)
|
|
|
|
|
|
{
|
|
|
|
|
|
var p = Panel("HeroList", root, new Vector2(0f, 0f), new Vector2(0.78f, 1f), new Vector2(0f, 1f), new Vector2(16f, -550f), Vector2.zero, PanelColor);
|
|
|
|
|
|
p.offsetMin = new Vector2(16f, 320f);
|
|
|
|
|
|
p.offsetMax = new Vector2(0f, -550f);
|
|
|
|
|
|
var content = Scroll(p);
|
|
|
|
|
|
string lastFaction = null;
|
|
|
|
|
|
foreach (var h in heroes)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (h.faction != lastFaction)
|
|
|
|
|
|
{
|
|
|
|
|
|
lastFaction = h.faction;
|
|
|
|
|
|
var hd = Label(content, "— " + UiKit.FactionKo(h.faction) + " —", 30, TextAnchor.MiddleLeft);
|
|
|
|
|
|
hd.color = new Color(1f, 0.85f, 0.4f);
|
|
|
|
|
|
hd.gameObject.AddComponent<LayoutElement>().preferredHeight = 52f;
|
|
|
|
|
|
}
|
|
|
|
|
|
var hero = h;
|
|
|
|
|
|
var a = SkillCatalog.Get(h.skillActive);
|
|
|
|
|
|
string text = h.nameKo + " · " + UnitTypeInfo.Korean(h.type) + (a != null ? " · " + a.NameKo + "(" + TriggerKo(a.Trigger) + ")" : " · 액티브 없음");
|
|
|
|
|
|
var b = ListItem(content, text, () => SelectHero(hero));
|
|
|
|
|
|
b.gameObject.name = "Hero_" + h.id;
|
|
|
|
|
|
}
|
|
|
|
|
|
return p;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
RectTransform BuildFxBrowser(Transform root)
|
|
|
|
|
|
{
|
|
|
|
|
|
var p = Panel("FxBrowser", root, new Vector2(0f, 0f), new Vector2(0.86f, 1f), new Vector2(0f, 1f), Vector2.zero, Vector2.zero, PanelColor);
|
|
|
|
|
|
p.offsetMin = new Vector2(16f, 320f);
|
|
|
|
|
|
p.offsetMax = new Vector2(0f, -550f);
|
|
|
|
|
|
#if UNITY_EDITOR
|
|
|
|
|
|
var bar = Rect("Bar", p, new Vector2(0f, 1f), new Vector2(1f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -8f), new Vector2(-16f, 170f));
|
|
|
|
|
|
var search = SearchField(bar, "검색(예: HCFX_Hit · Slash · Earth)", new Vector2(8f, -4f), new Vector2(520f, 74f));
|
|
|
|
|
|
var scaleBtn = Btn(bar, "", new Vector2(536f, -4f), new Vector2(160f, 74f), null);
|
|
|
|
|
|
var tiltBtn = Btn(bar, "", new Vector2(704f, -4f), new Vector2(170f, 74f), null);
|
|
|
|
|
|
var faceBtn = Btn(bar, "", new Vector2(8f, -86f), new Vector2(250f, 74f), null);
|
|
|
|
|
|
var copyBtn = Btn(bar, "경로 복사", new Vector2(266f, -86f), new Vector2(200f, 74f), null);
|
|
|
|
|
|
var countText = Label(bar, "", 26, TextAnchor.MiddleLeft);
|
|
|
|
|
|
SetRect(countText.rectTransform, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(476f, -86f), new Vector2(400f, 74f));
|
|
|
|
|
|
var listHost = Rect("ListHost", p, Vector2.zero, Vector2.one, new Vector2(0.5f, 0.5f), Vector2.zero, Vector2.zero);
|
|
|
|
|
|
listHost.offsetMin = new Vector2(0f, 0f);
|
|
|
|
|
|
listHost.offsetMax = new Vector2(0f, -180f);
|
|
|
|
|
|
var content = Scroll(listHost);
|
|
|
|
|
|
|
|
|
|
|
|
var all = new List<string>();
|
|
|
|
|
|
foreach (var g in UnityEditor.AssetDatabase.FindAssets("t:Prefab", new[] { VfxRoot }))
|
|
|
|
|
|
all.Add(UnityEditor.AssetDatabase.GUIDToAssetPath(g));
|
|
|
|
|
|
all.Sort(StringComparer.OrdinalIgnoreCase);
|
|
|
|
|
|
|
|
|
|
|
|
float scale = 1f;
|
|
|
|
|
|
float tilt = 0f;
|
|
|
|
|
|
bool face = false;
|
|
|
|
|
|
string lastPath = null;
|
|
|
|
|
|
Action refreshBar = () =>
|
|
|
|
|
|
{
|
|
|
|
|
|
scaleBtn.text = "크기 ×" + scale.ToString("0.##");
|
|
|
|
|
|
tiltBtn.text = "기울기 " + tilt.ToString("0") + "°";
|
|
|
|
|
|
faceBtn.text = face ? "방향: 더미 쪽" : "방향: 없음";
|
|
|
|
|
|
};
|
|
|
|
|
|
Action<string> play = path =>
|
|
|
|
|
|
{
|
|
|
|
|
|
lastPath = path;
|
|
|
|
|
|
var prefab = UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>(path);
|
|
|
|
|
|
var hero = Battle.AliveUnits(Faction.Player);
|
|
|
|
|
|
if (prefab == null || hero.Count == 0) return;
|
|
|
|
|
|
var u = hero[0];
|
|
|
|
|
|
Vector2 dir = Vector2.up;
|
|
|
|
|
|
var ds = Battle.AliveUnits(Faction.Enemy);
|
|
|
|
|
|
if (ds.Count > 0) dir = (ds[0].Position - u.Position).normalized;
|
|
|
|
|
|
var go = SkillVfx.SpawnPrefab(prefab, u.Position + new Vector2(0f, u.ArtHeight * 0.45f), scale * u.ArtHeight, tilt,
|
|
|
|
|
|
face ? (Vector2?)dir : null, SkillVfx.SortingBase, 1f, null);
|
|
|
|
|
|
Destroy(go, SkillVfx.AutoLife(go));
|
|
|
|
|
|
Battle.LastMessage = "이펙트 " + System.IO.Path.GetFileNameWithoutExtension(path) + " · 크기 ×" + scale.ToString("0.##") + " · 기울기 " + tilt.ToString("0") + "°";
|
|
|
|
|
|
Battle.NotifyChanged();
|
|
|
|
|
|
};
|
|
|
|
|
|
scaleBtn.GetComponentInParent<Button>().onClick.AddListener(() =>
|
|
|
|
|
|
{
|
|
|
|
|
|
scale = scale >= 4f ? 0.25f : scale * 2f;
|
|
|
|
|
|
refreshBar();
|
|
|
|
|
|
if (lastPath != null) play(lastPath);
|
|
|
|
|
|
});
|
|
|
|
|
|
tiltBtn.GetComponentInParent<Button>().onClick.AddListener(() =>
|
|
|
|
|
|
{
|
|
|
|
|
|
tilt = tilt > -1f ? -45f : tilt > -46f ? -90f : 0f;
|
|
|
|
|
|
refreshBar();
|
|
|
|
|
|
if (lastPath != null) play(lastPath);
|
|
|
|
|
|
});
|
|
|
|
|
|
faceBtn.GetComponentInParent<Button>().onClick.AddListener(() =>
|
|
|
|
|
|
{
|
|
|
|
|
|
face = !face;
|
|
|
|
|
|
refreshBar();
|
|
|
|
|
|
if (lastPath != null) play(lastPath);
|
|
|
|
|
|
});
|
|
|
|
|
|
copyBtn.GetComponentInParent<Button>().onClick.AddListener(() =>
|
|
|
|
|
|
{
|
|
|
|
|
|
if (lastPath == null) return;
|
|
|
|
|
|
GUIUtility.systemCopyBuffer = lastPath;
|
|
|
|
|
|
Battle.LastMessage = "복사: " + lastPath;
|
|
|
|
|
|
Battle.NotifyChanged();
|
|
|
|
|
|
});
|
|
|
|
|
|
const int MaxRows = 120;
|
|
|
|
|
|
Action<string> fill = q =>
|
|
|
|
|
|
{
|
|
|
|
|
|
for (int i = content.childCount - 1; i >= 0; i--) Destroy(content.GetChild(i).gameObject);
|
|
|
|
|
|
int n = 0, hit = 0;
|
|
|
|
|
|
string ql = (q ?? "").Trim().ToLowerInvariant();
|
|
|
|
|
|
foreach (var path in all)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (ql.Length > 0 && path.ToLowerInvariant().IndexOf(ql, StringComparison.Ordinal) < 0) continue;
|
|
|
|
|
|
hit++;
|
|
|
|
|
|
if (n >= MaxRows) continue;
|
|
|
|
|
|
n++;
|
|
|
|
|
|
var pth = path;
|
|
|
|
|
|
string shortName = pth.Substring(VfxRoot.Length + 1);
|
|
|
|
|
|
ListItem(content, shortName, () => play(pth)).GetComponentInChildren<Text>().fontSize = 24;
|
|
|
|
|
|
}
|
|
|
|
|
|
countText.text = hit + "개" + (hit > MaxRows ? " (앞 " + MaxRows + ")" : "") + " / 전체 " + all.Count;
|
|
|
|
|
|
};
|
|
|
|
|
|
search.onValueChanged.AddListener(v => fill(v));
|
|
|
|
|
|
refreshBar();
|
|
|
|
|
|
fill("");
|
|
|
|
|
|
#else
|
|
|
|
|
|
var t = Label(p, "이펙트 보기는 에디터에서만(Assets/VFX 검색)", 30, TextAnchor.MiddleCenter);
|
|
|
|
|
|
SetRect(t.rectTransform, Vector2.zero, Vector2.one, new Vector2(0.5f, 0.5f), Vector2.zero, Vector2.zero);
|
|
|
|
|
|
#endif
|
|
|
|
|
|
return p;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static string TriggerKo(SkillTrigger t)
|
|
|
|
|
|
{
|
|
|
|
|
|
return t == SkillTrigger.NextAction ? "다음 행동" : t == SkillTrigger.Instant ? "즉시" : "상시";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void RefreshInfo()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (cdLabel != null) cdLabel.text = "쿨 무시: " + (NoCooldown ? "켬" : "끔");
|
|
|
|
|
|
if (enemyLabel != null) enemyLabel.text = "더미 행동: " + (EnemyActs ? "켬" : "끔");
|
|
|
|
|
|
if (invLabel != null) invLabel.text = "영웅 무적: " + (HeroInvincible ? "켬" : "끔");
|
2026-11-10 04:19:44 +00:00
|
|
|
|
if (auraLabel != null) auraLabel.text = "기운 시험: " + (SkillVfx.TestTravelKey.Length > 0 ? "켬" : "끔");
|
2026-11-09 14:12:52 +00:00
|
|
|
|
if (infoText == null || Current == null) return;
|
|
|
|
|
|
var h = Current;
|
|
|
|
|
|
var a = SkillCatalog.Get(h.skillActive);
|
|
|
|
|
|
var ps = SkillCatalog.Get(h.skillPassive);
|
|
|
|
|
|
string s = h.nameKo + " · " + UnitTypeInfo.Korean(h.type) + " · " + UiKit.FactionKo(h.faction);
|
|
|
|
|
|
s += "\n액티브 " + (a != null ? a.NameKo + "(" + TriggerKo(a.Trigger) + " · 쿨 " + a.Cooldown + ")" : "없음");
|
|
|
|
|
|
s += "\n패시브 " + (ps != null ? ps.NameKo : "없음");
|
2026-11-10 04:19:44 +00:00
|
|
|
|
// 이 영웅에게 걸리는 이펙트 표 행 수(스킬 id 행 + 일반 공격 basic_<병종> 행 · 진영 s_camp 일치)
|
|
|
|
|
|
string camp = string.IsNullOrEmpty(h.faction) ? "other" : h.faction.ToLowerInvariant();
|
|
|
|
|
|
string basic = SkillVfx.BasicKey(h.type);
|
|
|
|
|
|
int c = 0, t = 0, hit = 0, bt = 0, bh = 0;
|
|
|
|
|
|
foreach (var r in SkillVfx.Rows)
|
2026-11-09 14:12:52 +00:00
|
|
|
|
{
|
2026-11-10 04:19:44 +00:00
|
|
|
|
if (r.camp.Length > 0 && r.camp != camp) continue;
|
|
|
|
|
|
if (a != null && r.HasKey(a.Id))
|
2026-11-09 14:12:52 +00:00
|
|
|
|
{
|
|
|
|
|
|
if (r.evt == SkillVfx.EventCast) c++;
|
|
|
|
|
|
else if (r.evt == SkillVfx.EventTravel) t++;
|
|
|
|
|
|
else if (r.evt == SkillVfx.EventHit) hit++;
|
|
|
|
|
|
}
|
2026-11-10 04:19:44 +00:00
|
|
|
|
if (r.HasKey(basic))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (r.evt == SkillVfx.EventTravel) bt++;
|
|
|
|
|
|
else if (r.evt == SkillVfx.EventHit) bh++;
|
|
|
|
|
|
}
|
2026-11-09 14:12:52 +00:00
|
|
|
|
}
|
2026-11-10 04:19:44 +00:00
|
|
|
|
s += "\n이펙트 표: 스킬 발동 " + c + " · 돌진 " + t + " · 명중 " + hit + " / 일반 돌진 " + bt + " · 명중 " + bh;
|
2026-11-09 14:12:52 +00:00
|
|
|
|
infoText.text = s;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------- UI 부품 ----------
|
|
|
|
|
|
|
|
|
|
|
|
static RectTransform Rect(string name, Transform parent, Vector2 aMin, Vector2 aMax, Vector2 pivot, Vector2 pos, Vector2 size)
|
|
|
|
|
|
{
|
|
|
|
|
|
var go = new GameObject(name, typeof(RectTransform));
|
|
|
|
|
|
go.transform.SetParent(parent, false);
|
|
|
|
|
|
var rt = (RectTransform)go.transform;
|
|
|
|
|
|
SetRect(rt, aMin, aMax, pivot, pos, size);
|
|
|
|
|
|
return rt;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void SetRect(RectTransform rt, Vector2 aMin, Vector2 aMax, Vector2 pivot, Vector2 pos, Vector2 size)
|
|
|
|
|
|
{
|
|
|
|
|
|
rt.anchorMin = aMin;
|
|
|
|
|
|
rt.anchorMax = aMax;
|
|
|
|
|
|
rt.pivot = pivot;
|
|
|
|
|
|
rt.anchoredPosition = pos;
|
|
|
|
|
|
rt.sizeDelta = size;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static RectTransform Panel(string name, Transform parent, Vector2 aMin, Vector2 aMax, Vector2 pivot, Vector2 pos, Vector2 size, Color c)
|
|
|
|
|
|
{
|
|
|
|
|
|
var rt = Rect(name, parent, aMin, aMax, pivot, pos, size);
|
|
|
|
|
|
var img = rt.gameObject.AddComponent<Image>();
|
|
|
|
|
|
img.color = c;
|
|
|
|
|
|
return rt;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static Text Label(Transform parent, string text, int size, TextAnchor anchor)
|
|
|
|
|
|
{
|
|
|
|
|
|
var go = new GameObject("Text", typeof(RectTransform));
|
|
|
|
|
|
go.transform.SetParent(parent, false);
|
|
|
|
|
|
var t = go.AddComponent<Text>();
|
|
|
|
|
|
t.font = TextUtil.Font;
|
|
|
|
|
|
t.fontSize = size;
|
|
|
|
|
|
t.alignment = anchor;
|
|
|
|
|
|
t.color = TextColor;
|
|
|
|
|
|
t.text = text;
|
|
|
|
|
|
t.raycastTarget = false;
|
|
|
|
|
|
t.horizontalOverflow = HorizontalWrapMode.Wrap;
|
|
|
|
|
|
t.verticalOverflow = VerticalWrapMode.Overflow;
|
|
|
|
|
|
return t;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>왼쪽 위 기준 버튼 · 반환 = 글자(토글 문구 갱신용).</summary>
|
|
|
|
|
|
static Text Btn(Transform parent, string text, Vector2 pos, Vector2 size, Action onClick)
|
|
|
|
|
|
{
|
|
|
|
|
|
var rt = Panel("Btn", parent, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(0f, 1f), pos, size, BtnColor);
|
|
|
|
|
|
var b = rt.gameObject.AddComponent<Button>();
|
|
|
|
|
|
if (onClick != null) b.onClick.AddListener(() => onClick());
|
|
|
|
|
|
var t = Label(rt, text, 30, TextAnchor.MiddleCenter);
|
|
|
|
|
|
SetRect(t.rectTransform, Vector2.zero, Vector2.one, new Vector2(0.5f, 0.5f), Vector2.zero, Vector2.zero);
|
|
|
|
|
|
return t;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static Button ListItem(Transform content, string text, Action onClick)
|
|
|
|
|
|
{
|
|
|
|
|
|
var go = new GameObject("Item", typeof(RectTransform));
|
|
|
|
|
|
go.transform.SetParent(content, false);
|
|
|
|
|
|
var img = go.AddComponent<Image>();
|
|
|
|
|
|
img.color = BtnColor;
|
|
|
|
|
|
var b = go.AddComponent<Button>();
|
|
|
|
|
|
b.onClick.AddListener(() => onClick());
|
|
|
|
|
|
go.AddComponent<LayoutElement>().preferredHeight = 76f;
|
|
|
|
|
|
var t = Label(go.transform, text, 30, TextAnchor.MiddleLeft);
|
|
|
|
|
|
SetRect(t.rectTransform, Vector2.zero, Vector2.one, new Vector2(0.5f, 0.5f), Vector2.zero, Vector2.zero);
|
|
|
|
|
|
t.rectTransform.offsetMin = new Vector2(16f, 0f);
|
|
|
|
|
|
t.rectTransform.offsetMax = new Vector2(-10f, 0f);
|
|
|
|
|
|
t.horizontalOverflow = HorizontalWrapMode.Overflow;
|
|
|
|
|
|
return b;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static Transform Scroll(RectTransform host)
|
|
|
|
|
|
{
|
|
|
|
|
|
var view = Rect("Viewport", host, Vector2.zero, Vector2.one, new Vector2(0.5f, 0.5f), Vector2.zero, Vector2.zero);
|
|
|
|
|
|
view.offsetMin = new Vector2(8f, 8f);
|
|
|
|
|
|
view.offsetMax = new Vector2(-8f, -8f);
|
|
|
|
|
|
view.gameObject.AddComponent<Image>().color = new Color(0f, 0f, 0f, 0.01f);
|
|
|
|
|
|
view.gameObject.AddComponent<RectMask2D>();
|
|
|
|
|
|
var content = Rect("Content", view, new Vector2(0f, 1f), new Vector2(1f, 1f), new Vector2(0.5f, 1f), Vector2.zero, Vector2.zero);
|
|
|
|
|
|
var lay = content.gameObject.AddComponent<VerticalLayoutGroup>();
|
|
|
|
|
|
lay.spacing = 6f;
|
|
|
|
|
|
lay.childControlHeight = true;
|
|
|
|
|
|
lay.childControlWidth = true;
|
|
|
|
|
|
lay.childForceExpandHeight = false;
|
|
|
|
|
|
lay.childForceExpandWidth = true;
|
|
|
|
|
|
content.gameObject.AddComponent<ContentSizeFitter>().verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
|
|
|
|
|
var sr = host.gameObject.AddComponent<ScrollRect>();
|
|
|
|
|
|
sr.viewport = view;
|
|
|
|
|
|
sr.content = content;
|
|
|
|
|
|
sr.horizontal = false;
|
|
|
|
|
|
sr.movementType = ScrollRect.MovementType.Clamped;
|
|
|
|
|
|
sr.scrollSensitivity = 60f;
|
|
|
|
|
|
return content;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#if UNITY_EDITOR
|
|
|
|
|
|
static InputField SearchField(Transform parent, string placeholder, Vector2 pos, Vector2 size)
|
|
|
|
|
|
{
|
|
|
|
|
|
var rt = Panel("Search", parent, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(0f, 1f), pos, size, new Color(1f, 1f, 1f, 0.92f));
|
|
|
|
|
|
var f = rt.gameObject.AddComponent<InputField>();
|
|
|
|
|
|
var t = Label(rt, "", 30, TextAnchor.MiddleLeft);
|
|
|
|
|
|
t.color = Color.black;
|
|
|
|
|
|
t.supportRichText = false;
|
|
|
|
|
|
SetRect(t.rectTransform, Vector2.zero, Vector2.one, new Vector2(0.5f, 0.5f), Vector2.zero, Vector2.zero);
|
|
|
|
|
|
t.rectTransform.offsetMin = new Vector2(14f, 0f);
|
|
|
|
|
|
t.rectTransform.offsetMax = new Vector2(-10f, 0f);
|
|
|
|
|
|
var ph = Label(rt, placeholder, 26, TextAnchor.MiddleLeft);
|
|
|
|
|
|
ph.color = new Color(0f, 0f, 0f, 0.4f);
|
|
|
|
|
|
SetRect(ph.rectTransform, Vector2.zero, Vector2.one, new Vector2(0.5f, 0.5f), Vector2.zero, Vector2.zero);
|
|
|
|
|
|
ph.rectTransform.offsetMin = new Vector2(14f, 0f);
|
|
|
|
|
|
f.textComponent = t;
|
|
|
|
|
|
f.placeholder = ph;
|
|
|
|
|
|
return f;
|
|
|
|
|
|
}
|
|
|
|
|
|
#endif
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|