ThreeKingdomsSlingshot/Assets/Scripts/Editor/UnitArtImporter.cs

180 lines
7.4 KiB
C#
Raw Permalink Normal View History

using System.IO;
using UnityEditor;
using UnityEngine;
namespace TKS.EditorTools
{
/// <summary>
/// Assets/Resources/Units/&lt;art&gt;/&lt;clip&gt;/f###.png 임포트 설정: 단일 스프라이트 · 피벗 = meta.json pivot(발) · PPU = meta.ppu · 밉맵 없음 · 무압축.
/// meta.json 은 skeletal-anim-pipeline `rig_frames_export.py` 가 쓴다.
/// </summary>
public class UnitArtImporter : AssetPostprocessor
{
public const string Root = "Assets/Resources/Units/";
전투 로직 #1080 — 알까기 충돌(보병) · 관통·아군 직전 정지(기병) · 밀며 함께 전진(창병) · 밀린 아군 공격 기회 · 궁병 제자리 사격(사거리 = 슬링샷)+접근 20 % · 궁수 근접 피해 50 %/피격 +50 % · 원화 1차·2차 반영(전장 배경 + 황건적 5 + 동탁군 4) · 테스트 22/22 - ActionResolver 전면 재작성: InfantryRoutine(접촉점 정지 · Push 접촉 방향 × collisionPushRatio · 적이면 공격+반격 뒤 밀림) · SpearRoutine(앞 유닛과 같은 속도로 함께 전진 · 접촉 간격 유지) · CavalryRoutine(벽만 막음 · StopBeforeUnclearedAllies) · ArcherRoutine(AttackRange = 슬링 · ArcherMoveAllow = 슬링 × archerRangeRatio) · PushedAllyAttack(규칙 ⑤ · ActionOutcome.Pushed/Notes) - Combat: Damage(melee) 궁병 근접 배율(근접 유닛 상대만) · FirstUnitOnPath · TouchingOpponent · FreeTravel ignore - BalanceConfig/Defaults: archerMeleeDealtRatio 0.5 · archerMeleeTakenRatio 1.5 · collisionPushRatio 1 · boardArt battlefield_01 (자산 재시드) - Board: Resources/Board/<boardArt> 원화 배경(격자·진영 띠 생략) · UnitArtImporter Board 규칙 · GameBootstrap - Resources/Units: hgj_infantry·spear·cavalry·archer·boss_zhangjiao · dz_infantry·spear·cavalry·archer (원화 세션 납품 정지 그림 · rig_frames_export static) · Resources/Board/battlefield_01.png - BattleManager Describe 「밀어냄 · 밀린 B → 적 공격」 · BattleHUD 궁병 줄 · 테스트 22종(신규·개정 7 + 원화 확인) · CLAUDE.md · README Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-04 06:56:02 +00:00
/// <summary>전장 배경(원화 세션 납품 · 5:8 불투명 · PPU 100 = 전장 20×32 월드).</summary>
public const string BoardRoot = "Assets/Resources/Board/";
시작 화면 = 챕터 월드맵(스크롤 2160×3840 · 10장) → 스테이지 팝업(임시) → 전투 → 결과 탭 = 맵 복귀·다음 노드 열림 + 아군 뒷모습·황건적·동탁군 포즈 반영 (PD 2026-11-05) - WorldMapScreen(신규): ScrollRect 맵 · 길 따라 깃발 노드(open/clear/lock · 보스) · 길 점 · 현재 위치 핀 · 챕터 띠 ◀▶ · 스테이지 팝업(적 구성·보스 · 출전/닫기) · 임시 진행도(메모리) - 데이터 = Resources/ChapterMap/Data/world.json(원화 세션 chap_install.py 생성 · 맵 크기·실제 길·부품 크기/피벗/번호 자리·임시 스테이지 연결 = 챕터당 10노드 · 전투 = 기존 스테이지 0/1/2) · 지명판 = 끔(생성 그림과 위치 어긋남) - GameBootstrap: StartOnWorldMap(기본 true) · BattleManager: ResultHandler · GoIdle · BattleHUD 결과 문구 · AutoPlayDriver: 월드맵·팝업 캡처 후 1-1 출전 - UnitArtImporter: ChapterMap/Maps(4096 · 압축) · ChapterMap/UI(import.json 피벗·9-slice·PPU 133.33) - 유닛: 아군 5인 앞·뒤 × idle/ready/action(키아트 세션 · md5 30/30) · 황건적 5 = 인박스 20261105_0312_hgj(QA PASS · md5 15/15) · 동탁군 5(여포 포함 · 원화 세션 poses md5 15/15 · PD 지시로 직접 반영) - 테스트 27/27(+ 월드맵 시작 · 팝업→전투→맵 복귀 · 맵/UI 리소스 · 포즈 세트·동작별 스프라이트 교체 · 앞뒤 클립 고정 자료 hgj_infantry → 아군 아트) · console error 0 - 남은 것(QA r2 B 2 · PD 결정): ch04 보스 초상 틀이 위 HUD 줄 안 · ch08 경로가 다리 없는 큰길을 피해 풀밭·강둑으로 우회 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 18:38:47 +00:00
/// <summary>챕터 월드맵(Maps = 2160×3840 불투명) · 거점 UI 부품(UI = 투명 · pivot·9-slice·PPU = UI/import.json).</summary>
public const string ChapterMapRoot = "Assets/Resources/ChapterMap/";
[System.Serializable]
class ChapterUiImport
{
public string file;
public float[] pivot;
public float[] border;
public float ppu;
}
[System.Serializable]
class ChapterUiImportList
{
public ChapterUiImport[] items;
}
void OnPreprocessTexture()
{
string path = assetPath.Replace('\\', '/');
시작 화면 = 챕터 월드맵(스크롤 2160×3840 · 10장) → 스테이지 팝업(임시) → 전투 → 결과 탭 = 맵 복귀·다음 노드 열림 + 아군 뒷모습·황건적·동탁군 포즈 반영 (PD 2026-11-05) - WorldMapScreen(신규): ScrollRect 맵 · 길 따라 깃발 노드(open/clear/lock · 보스) · 길 점 · 현재 위치 핀 · 챕터 띠 ◀▶ · 스테이지 팝업(적 구성·보스 · 출전/닫기) · 임시 진행도(메모리) - 데이터 = Resources/ChapterMap/Data/world.json(원화 세션 chap_install.py 생성 · 맵 크기·실제 길·부품 크기/피벗/번호 자리·임시 스테이지 연결 = 챕터당 10노드 · 전투 = 기존 스테이지 0/1/2) · 지명판 = 끔(생성 그림과 위치 어긋남) - GameBootstrap: StartOnWorldMap(기본 true) · BattleManager: ResultHandler · GoIdle · BattleHUD 결과 문구 · AutoPlayDriver: 월드맵·팝업 캡처 후 1-1 출전 - UnitArtImporter: ChapterMap/Maps(4096 · 압축) · ChapterMap/UI(import.json 피벗·9-slice·PPU 133.33) - 유닛: 아군 5인 앞·뒤 × idle/ready/action(키아트 세션 · md5 30/30) · 황건적 5 = 인박스 20261105_0312_hgj(QA PASS · md5 15/15) · 동탁군 5(여포 포함 · 원화 세션 poses md5 15/15 · PD 지시로 직접 반영) - 테스트 27/27(+ 월드맵 시작 · 팝업→전투→맵 복귀 · 맵/UI 리소스 · 포즈 세트·동작별 스프라이트 교체 · 앞뒤 클립 고정 자료 hgj_infantry → 아군 아트) · console error 0 - 남은 것(QA r2 B 2 · PD 결정): ch04 보스 초상 틀이 위 HUD 줄 안 · ch08 경로가 다리 없는 큰길을 피해 풀밭·강둑으로 우회 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 18:38:47 +00:00
if (path.StartsWith(ChapterMapRoot))
{
PreprocessChapterMap(path);
return;
}
전투 로직 #1080 — 알까기 충돌(보병) · 관통·아군 직전 정지(기병) · 밀며 함께 전진(창병) · 밀린 아군 공격 기회 · 궁병 제자리 사격(사거리 = 슬링샷)+접근 20 % · 궁수 근접 피해 50 %/피격 +50 % · 원화 1차·2차 반영(전장 배경 + 황건적 5 + 동탁군 4) · 테스트 22/22 - ActionResolver 전면 재작성: InfantryRoutine(접촉점 정지 · Push 접촉 방향 × collisionPushRatio · 적이면 공격+반격 뒤 밀림) · SpearRoutine(앞 유닛과 같은 속도로 함께 전진 · 접촉 간격 유지) · CavalryRoutine(벽만 막음 · StopBeforeUnclearedAllies) · ArcherRoutine(AttackRange = 슬링 · ArcherMoveAllow = 슬링 × archerRangeRatio) · PushedAllyAttack(규칙 ⑤ · ActionOutcome.Pushed/Notes) - Combat: Damage(melee) 궁병 근접 배율(근접 유닛 상대만) · FirstUnitOnPath · TouchingOpponent · FreeTravel ignore - BalanceConfig/Defaults: archerMeleeDealtRatio 0.5 · archerMeleeTakenRatio 1.5 · collisionPushRatio 1 · boardArt battlefield_01 (자산 재시드) - Board: Resources/Board/<boardArt> 원화 배경(격자·진영 띠 생략) · UnitArtImporter Board 규칙 · GameBootstrap - Resources/Units: hgj_infantry·spear·cavalry·archer·boss_zhangjiao · dz_infantry·spear·cavalry·archer (원화 세션 납품 정지 그림 · rig_frames_export static) · Resources/Board/battlefield_01.png - BattleManager Describe 「밀어냄 · 밀린 B → 적 공격」 · BattleHUD 궁병 줄 · 테스트 22종(신규·개정 7 + 원화 확인) · CLAUDE.md · README Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-04 06:56:02 +00:00
if (path.StartsWith(BoardRoot))
{
var bi = (TextureImporter)assetImporter;
bi.textureType = TextureImporterType.Sprite;
bi.spriteImportMode = SpriteImportMode.Single;
bi.mipmapEnabled = false;
bi.filterMode = FilterMode.Bilinear;
bi.alphaIsTransparency = false;
bi.textureCompression = TextureImporterCompression.Compressed;
bi.maxTextureSize = 4096;
bi.spritePixelsPerUnit = 100f;
var bs = new TextureImporterSettings();
bi.ReadTextureSettings(bs);
bs.spriteMeshType = SpriteMeshType.FullRect;
bs.spriteAlignment = (int)SpriteAlignment.Center;
bi.SetTextureSettings(bs);
return;
}
if (!path.StartsWith(Root)) return;
var ti = (TextureImporter)assetImporter;
ti.textureType = TextureImporterType.Sprite;
ti.spriteImportMode = SpriteImportMode.Single;
ti.mipmapEnabled = false;
ti.filterMode = FilterMode.Bilinear;
ti.alphaIsTransparency = true;
ti.textureCompression = TextureImporterCompression.Uncompressed;
ti.maxTextureSize = 2048;
var meta = LoadMeta(path);
var s = new TextureImporterSettings();
ti.ReadTextureSettings(s);
s.spriteMeshType = SpriteMeshType.FullRect;
if (meta != null && meta.pivot != null && meta.pivot.Length == 2)
{
s.spriteAlignment = (int)SpriteAlignment.Custom;
s.spritePivot = new Vector2(meta.pivot[0], meta.pivot[1]);
}
if (meta != null && meta.ppu > 0.01f) s.spritePixelsPerUnit = meta.ppu;
ti.SetTextureSettings(s);
}
시작 화면 = 챕터 월드맵(스크롤 2160×3840 · 10장) → 스테이지 팝업(임시) → 전투 → 결과 탭 = 맵 복귀·다음 노드 열림 + 아군 뒷모습·황건적·동탁군 포즈 반영 (PD 2026-11-05) - WorldMapScreen(신규): ScrollRect 맵 · 길 따라 깃발 노드(open/clear/lock · 보스) · 길 점 · 현재 위치 핀 · 챕터 띠 ◀▶ · 스테이지 팝업(적 구성·보스 · 출전/닫기) · 임시 진행도(메모리) - 데이터 = Resources/ChapterMap/Data/world.json(원화 세션 chap_install.py 생성 · 맵 크기·실제 길·부품 크기/피벗/번호 자리·임시 스테이지 연결 = 챕터당 10노드 · 전투 = 기존 스테이지 0/1/2) · 지명판 = 끔(생성 그림과 위치 어긋남) - GameBootstrap: StartOnWorldMap(기본 true) · BattleManager: ResultHandler · GoIdle · BattleHUD 결과 문구 · AutoPlayDriver: 월드맵·팝업 캡처 후 1-1 출전 - UnitArtImporter: ChapterMap/Maps(4096 · 압축) · ChapterMap/UI(import.json 피벗·9-slice·PPU 133.33) - 유닛: 아군 5인 앞·뒤 × idle/ready/action(키아트 세션 · md5 30/30) · 황건적 5 = 인박스 20261105_0312_hgj(QA PASS · md5 15/15) · 동탁군 5(여포 포함 · 원화 세션 poses md5 15/15 · PD 지시로 직접 반영) - 테스트 27/27(+ 월드맵 시작 · 팝업→전투→맵 복귀 · 맵/UI 리소스 · 포즈 세트·동작별 스프라이트 교체 · 앞뒤 클립 고정 자료 hgj_infantry → 아군 아트) · console error 0 - 남은 것(QA r2 B 2 · PD 결정): ch04 보스 초상 틀이 위 HUD 줄 안 · ch08 경로가 다리 없는 큰길을 피해 풀밭·강둑으로 우회 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 18:38:47 +00:00
void PreprocessChapterMap(string path)
{
var ti = (TextureImporter)assetImporter;
ti.textureType = TextureImporterType.Sprite;
ti.spriteImportMode = SpriteImportMode.Single;
ti.mipmapEnabled = false;
ti.filterMode = FilterMode.Bilinear;
var s = new TextureImporterSettings();
if (path.StartsWith(ChapterMapRoot + "Maps/"))
{
ti.alphaIsTransparency = false;
ti.textureCompression = TextureImporterCompression.Compressed;
ti.maxTextureSize = 4096;
ti.spritePixelsPerUnit = 100f;
ti.ReadTextureSettings(s);
s.spriteMeshType = SpriteMeshType.FullRect;
s.spriteAlignment = (int)SpriteAlignment.Center;
ti.SetTextureSettings(s);
return;
}
ti.alphaIsTransparency = true;
ti.textureCompression = TextureImporterCompression.Uncompressed;
ti.maxTextureSize = 1024;
ti.ReadTextureSettings(s);
s.spriteMeshType = SpriteMeshType.FullRect;
var item = FindChapterUi(path);
if (item != null)
{
if (item.pivot != null && item.pivot.Length == 2)
{
s.spriteAlignment = (int)SpriteAlignment.Custom;
s.spritePivot = new Vector2(item.pivot[0], 1f - item.pivot[1]); // import.json = 이미지 좌표(위 → 아래)
}
if (item.border != null && item.border.Length == 4)
s.spriteBorder = new Vector4(item.border[0], item.border[1], item.border[2], item.border[3]);
if (item.ppu > 0.01f) s.spritePixelsPerUnit = item.ppu;
}
ti.SetTextureSettings(s);
}
static ChapterUiImport FindChapterUi(string texPath)
{
string dir = Path.GetDirectoryName(texPath);
if (string.IsNullOrEmpty(dir)) return null;
string json = Path.Combine(dir, "import.json");
if (!File.Exists(json)) return null;
try
{
var list = JsonUtility.FromJson<ChapterUiImportList>(File.ReadAllText(json));
string name = Path.GetFileName(texPath);
if (list != null && list.items != null)
foreach (var it in list.items)
if (it.file == name) return it;
}
catch
{
// 표가 깨졌으면 기본 설정
}
return null;
}
static UnitArtMeta LoadMeta(string texPath)
{
string clipDir = Path.GetDirectoryName(texPath);
string artDir = clipDir != null ? Path.GetDirectoryName(clipDir) : null;
if (string.IsNullOrEmpty(artDir)) return null;
string metaPath = Path.Combine(artDir, "meta.json");
if (!File.Exists(metaPath)) return null;
try
{
return JsonUtility.FromJson<UnitArtMeta>(File.ReadAllText(metaPath));
}
catch
{
return null;
}
}
/// <summary>프레임을 새로 넣거나 meta.json 피벗을 바꾼 뒤 임포트 설정을 다시 적용한다(메뉴 · CLI `-executeMethod TKS.EditorTools.UnitArtImporter.ReimportAll`).</summary>
[MenuItem("TKS/Reimport Unit Art (유닛 아트 재임포트)")]
public static void ReimportAll()
{
시작 화면 = 챕터 월드맵(스크롤 2160×3840 · 10장) → 스테이지 팝업(임시) → 전투 → 결과 탭 = 맵 복귀·다음 노드 열림 + 아군 뒷모습·황건적·동탁군 포즈 반영 (PD 2026-11-05) - WorldMapScreen(신규): ScrollRect 맵 · 길 따라 깃발 노드(open/clear/lock · 보스) · 길 점 · 현재 위치 핀 · 챕터 띠 ◀▶ · 스테이지 팝업(적 구성·보스 · 출전/닫기) · 임시 진행도(메모리) - 데이터 = Resources/ChapterMap/Data/world.json(원화 세션 chap_install.py 생성 · 맵 크기·실제 길·부품 크기/피벗/번호 자리·임시 스테이지 연결 = 챕터당 10노드 · 전투 = 기존 스테이지 0/1/2) · 지명판 = 끔(생성 그림과 위치 어긋남) - GameBootstrap: StartOnWorldMap(기본 true) · BattleManager: ResultHandler · GoIdle · BattleHUD 결과 문구 · AutoPlayDriver: 월드맵·팝업 캡처 후 1-1 출전 - UnitArtImporter: ChapterMap/Maps(4096 · 압축) · ChapterMap/UI(import.json 피벗·9-slice·PPU 133.33) - 유닛: 아군 5인 앞·뒤 × idle/ready/action(키아트 세션 · md5 30/30) · 황건적 5 = 인박스 20261105_0312_hgj(QA PASS · md5 15/15) · 동탁군 5(여포 포함 · 원화 세션 poses md5 15/15 · PD 지시로 직접 반영) - 테스트 27/27(+ 월드맵 시작 · 팝업→전투→맵 복귀 · 맵/UI 리소스 · 포즈 세트·동작별 스프라이트 교체 · 앞뒤 클립 고정 자료 hgj_infantry → 아군 아트) · console error 0 - 남은 것(QA r2 B 2 · PD 결정): ch04 보스 초상 틀이 위 HUD 줄 안 · ch08 경로가 다리 없는 큰길을 피해 풀밭·강둑으로 우회 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 18:38:47 +00:00
foreach (var root in new[] { Root, BoardRoot, ChapterMapRoot })
{
전투 로직 #1080 — 알까기 충돌(보병) · 관통·아군 직전 정지(기병) · 밀며 함께 전진(창병) · 밀린 아군 공격 기회 · 궁병 제자리 사격(사거리 = 슬링샷)+접근 20 % · 궁수 근접 피해 50 %/피격 +50 % · 원화 1차·2차 반영(전장 배경 + 황건적 5 + 동탁군 4) · 테스트 22/22 - ActionResolver 전면 재작성: InfantryRoutine(접촉점 정지 · Push 접촉 방향 × collisionPushRatio · 적이면 공격+반격 뒤 밀림) · SpearRoutine(앞 유닛과 같은 속도로 함께 전진 · 접촉 간격 유지) · CavalryRoutine(벽만 막음 · StopBeforeUnclearedAllies) · ArcherRoutine(AttackRange = 슬링 · ArcherMoveAllow = 슬링 × archerRangeRatio) · PushedAllyAttack(규칙 ⑤ · ActionOutcome.Pushed/Notes) - Combat: Damage(melee) 궁병 근접 배율(근접 유닛 상대만) · FirstUnitOnPath · TouchingOpponent · FreeTravel ignore - BalanceConfig/Defaults: archerMeleeDealtRatio 0.5 · archerMeleeTakenRatio 1.5 · collisionPushRatio 1 · boardArt battlefield_01 (자산 재시드) - Board: Resources/Board/<boardArt> 원화 배경(격자·진영 띠 생략) · UnitArtImporter Board 규칙 · GameBootstrap - Resources/Units: hgj_infantry·spear·cavalry·archer·boss_zhangjiao · dz_infantry·spear·cavalry·archer (원화 세션 납품 정지 그림 · rig_frames_export static) · Resources/Board/battlefield_01.png - BattleManager Describe 「밀어냄 · 밀린 B → 적 공격」 · BattleHUD 궁병 줄 · 테스트 22종(신규·개정 7 + 원화 확인) · CLAUDE.md · README Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-04 06:56:02 +00:00
string dir = root.TrimEnd('/');
if (!AssetDatabase.IsValidFolder(dir))
{
Debug.Log("[TKS] " + dir + " 없음");
continue;
}
AssetDatabase.ImportAsset(dir, ImportAssetOptions.ImportRecursive | ImportAssetOptions.ForceUpdate);
Debug.Log("[TKS] 아트 재임포트 완료 · " + dir);
}
AssetDatabase.Refresh();
}
}
}