영웅 목록 신규 유닛 크기 = 기존 20종 기준에 맞춤 + 대기 그림 보는 방향 오른쪽 통일(#1108 · PD 2026-11-10)
PD: 「강유, 서황과 같이 새로 추가된 유닛의 크기가 이전에 제작된 유닛과 달리 큰 것 같아 · 이전에 유닛 크기를 조정한 기준에 맞게 크기나 위치도 조정」 · 「유닛의 기본 보는 방향을 오른쪽으로 통일」 - 원인: 크기 표가 눈 간격 하나 기준 → 새 그림은 투구·관이 커서 머리 덩어리가 커짐(카드 위 머리 폭 기존 202~283 · 신규 180~349) - art_size.csv: lock 1 = PD 확인 기존 20종 크기 고정(기준점) · 신규 = 네 지표(눈 간격 · 얼굴 폭 · 머리 실루엣 폭 · 정수리 높이) 가운데 값 × adjust(영웅 목록 카드 시뮬레이션 눈 확인 · 78종) · Tools/unit_art_size.py 측정 추가 - 대기 그림 23종 좌우 반전(왼쪽 보던 유비·관우·제갈량·주유·곽가·장합·동탁·축융 등 · 눈 가운데 ↔ 머리 가운데 치우침 판정 + 눈 확인 · 피벗 = 프레임 가운데라 발 위치 그대로) · 출발·돌진은 원래 오른쪽 - 테스트(고친 부분만): UnitArt_SizeTable(크기 표 키 = 표준 키 · 기마 > 1.2 · lock 행) 1/1 · Lobby_Tabs_Back_ComingSoon 1/1 · error 0 · 캡처 hero_list Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
@ -1,128 +1,128 @@
|
||||||
art,mounted,eye_px,body_px,size_px,note
|
art,mounted,eye_px,face_px,head_px,crown_px,body_px,size_px,lock,adjust,note
|
||||||
cc_archer,0,125.1,744,736.3,
|
cc_archer,0,125.1,250,444.0,249.0,744,736.3,1,,
|
||||||
cc_boss_bianxi,0,172.1,835,1013.0,
|
cc_boss_bianxi,0,172.1,471,556.0,266.0,835,1010.0,,1.05,
|
||||||
cc_boss_caiyang,1,91.1,847,536.2,
|
cc_boss_caiyang,1,91.1,184,352.0,198.0,847,556.4,,,
|
||||||
cc_boss_caocao,0,135.0,808,794.6,
|
cc_boss_caocao,0,135.0,288,446.0,260.0,808,794.6,1,,
|
||||||
cc_boss_caohong,0,155.4,843,914.7,
|
cc_boss_caohong,0,155.4,335,546.0,304.0,843,1013.1,,1.12,
|
||||||
cc_boss_caoren,0,148.1,829,871.7,
|
cc_boss_caoren,0,148.1,303,519.0,282.0,829,984.4,,1.15,
|
||||||
cc_boss_chezhou,0,133.1,804,783.4,
|
cc_boss_chezhou,0,133.1,304,424.0,249.0,804,772.1,,,
|
||||||
cc_boss_chunyudao,0,125.6,829,739.3,
|
cc_boss_chunyudao,0,125.6,264,497.0,256.0,829,874.9,,1.15,
|
||||||
cc_boss_gaolan,0,119.4,772,702.8,
|
cc_boss_gaolan,0,119.4,251,462.0,229.0,772,806.5,,1.15,
|
||||||
cc_boss_hanfu,0,131.8,815,775.8,
|
cc_boss_hanfu,0,131.8,316,525.0,246.0,815,808.5,,,
|
||||||
cc_boss_kongxiu,0,159.0,875,935.8,
|
cc_boss_kongxiu,0,159.0,326,725.0,317.0,875,1066.5,,1.12,
|
||||||
cc_boss_lidian,0,124.7,814,734.0,
|
cc_boss_lidian,0,124.7,260,451.0,258.0,814,846.8,,1.15,
|
||||||
cc_boss_liudai,0,118.3,774,696.3,
|
cc_boss_liudai,0,118.3,258,487.0,227.0,774,778.4,,1.12,
|
||||||
cc_boss_lvkuang,1,92.2,903,542.7,
|
cc_boss_lvkuang,1,92.2,193,372.0,201.0,903,576.0,,,
|
||||||
cc_boss_lvxiang,1,89.7,863,528.0,
|
cc_boss_lvxiang,1,89.7,185,340.0,202.0,863,542.5,,,
|
||||||
cc_boss_maojie,0,139.3,804,819.9,
|
cc_boss_maojie,0,139.3,330,448.0,292.0,804,849.2,,,
|
||||||
cc_boss_mengtan,1,95.6,853,562.7,
|
cc_boss_mengtan,1,95.6,185,363.0,204.0,853,578.7,,,
|
||||||
cc_boss_qinqi,0,122.5,814,721.0,
|
cc_boss_qinqi,0,122.5,254,449.0,246.0,814,837.5,,1.15,
|
||||||
cc_boss_wangzhi,0,135.3,811,796.4,
|
cc_boss_wangzhi,0,135.3,322,553.5,255.0,811,826.7,,,
|
||||||
cc_boss_wangzhong,0,125.2,783,736.9,
|
cc_boss_wangzhong,0,125.2,269,450.0,247.0,783,847.6,,1.15,
|
||||||
cc_boss_xiahoudun,1,103.4,867,608.6,눈 수동 실측 2026-11-10: 왼눈 x 408.0 · 오른눈 = 수염 가운데 459.7 대칭 511.4(안대)
|
cc_boss_xiahoudun,1,103.4,0,0,0,867,608.6,,,눈 수동 실측 2026-11-10: 왼눈 x 408.0 · 오른눈 = 수염 가운데 459.7 대칭 511.4(안대)
|
||||||
cc_boss_xiahouen,1,87.5,840,515.0,
|
cc_boss_xiahouen,1,87.5,172,341.0,195.0,840,536.8,,,
|
||||||
cc_boss_xiahoujie,1,94.2,845,554.4,
|
cc_boss_xiahoujie,1,94.2,182,369.0,217.0,845,579.5,,,
|
||||||
cc_boss_xuchu,0,154.9,854,911.7,
|
cc_boss_xuchu,0,154.9,353,487.0,189.0,854,1008.6,,1.18,
|
||||||
cc_boss_xuhuang,0,151.7,805,892.9,
|
cc_boss_xuhuang,0,151.7,319,763.0,321.0,805,1011.8,,1.08,
|
||||||
cc_boss_yanming,0,112.0,786,659.2,
|
cc_boss_yanming,0,112.0,239,425.0,233.0,786,799.7,,1.18,
|
||||||
cc_boss_yujin,0,138.4,844,814.6,
|
cc_boss_yujin,0,138.4,303,627.0,384.0,844,1059.0,,1.15,
|
||||||
cc_boss_zhanghe,0,112.3,853,661.0,
|
cc_boss_zhanghe,0,112.3,228,477.0,235.0,853,793.0,,1.15,
|
||||||
cc_cavalry,1,91.9,849,540.9,
|
cc_cavalry,1,91.9,186,346.0,193.0,849,540.9,1,,
|
||||||
cc_infantry,0,125.3,765,737.5,
|
cc_infantry,0,125.3,252,444.0,253.0,765,737.5,1,,
|
||||||
cc_spear,0,124.1,785,730.4,
|
cc_spear,0,124.1,255,481.0,243.0,785,730.4,1,,
|
||||||
chunyuqiong,1,96.0,917,565.0,
|
chunyuqiong,1,96.0,190,411.0,191.0,917,620.3,,1.08,
|
||||||
dianwei,0,167.4,843,985.3,
|
dianwei,0,167.4,413,551.0,269.0,843,1132.7,,1.2,
|
||||||
diaochan,0,158.2,911,931.1,
|
diaochan,0,158.2,320,569.5,320.0,911,1006.6,,1.08,
|
||||||
dongxi,0,116.2,757,683.9,
|
dongxi,0,116.2,245,437.5,220.0,757,759.5,,1.12,
|
||||||
dongzhuo,0,120.9,931,711.6,
|
dongzhuo,0,120.9,448,517.0,442.0,931,1070.7,,1.05,
|
||||||
dz_archer,0,123.3,745,725.7,
|
dz_archer,0,123.3,257,448.5,251.0,745,725.7,1,,
|
||||||
dz_boss_caoxing,0,140.7,804,828.1,
|
dz_boss_caoxing,0,140.7,310,585.0,274.0,804,957.6,,1.15,
|
||||||
dz_boss_chengong,0,157.8,892,928.8,
|
dz_boss_chengong,0,157.8,376,532.0,290.0,892,980.1,,1.08,
|
||||||
dz_boss_gaoshun,0,132.2,798,778.1,
|
dz_boss_gaoshun,0,132.2,275,473.0,252.0,798,888.3,,1.15,
|
||||||
dz_boss_huaxiong,0,116.7,774,686.9,
|
dz_boss_huaxiong,0,116.7,263,444.0,230.0,774,827.7,,1.18,
|
||||||
dz_boss_huzhen,1,87.5,836,515.0,
|
dz_boss_huzhen,1,87.5,183,344.0,184.0,836,538.6,,,
|
||||||
dz_boss_lvbu,1,75.8,819,446.1,
|
dz_boss_lvbu,1,75.8,173,387.5,123.0,819,446.1,1,,
|
||||||
dz_boss_zhangliao,1,95.7,876,563.3,
|
dz_boss_zhangliao,1,95.7,203,430.0,197.0,876,582.6,,,
|
||||||
dz_cavalry,1,96.2,873,566.2,
|
dz_cavalry,1,96.2,197,364.0,207.0,873,566.2,1,,
|
||||||
dz_infantry,0,139.1,844,818.7,
|
dz_infantry,0,139.1,304,507.0,259.0,844,818.7,1,,
|
||||||
dz_spear,0,117.2,818,689.8,
|
dz_spear,0,117.2,247,500.0,237.0,818,689.8,1,,
|
||||||
guanping,0,132.9,770,782.2,
|
guanping,0,132.9,306,540.0,239.0,770,878.2,,1.1,
|
||||||
guanyu,0,122.7,725,722.2,
|
guanyu,0,122.7,287,568.5,214.0,725,722.2,1,,
|
||||||
guojia,0,147.2,854,866.4,
|
guojia,0,147.2,366,528.0,267.0,854,969.6,,1.12,
|
||||||
hanxuan,0,113.5,793,668.0,
|
hanxuan,0,113.5,262,477.0,234.0,793,791.0,,1.12,
|
||||||
hgj_archer,0,154.7,833,910.5,
|
hgj_archer,0,154.7,376,573.0,298.0,833,910.5,1,,
|
||||||
hgj_boss_chengyuanzhi,0,137.2,740,807.5,
|
hgj_boss_chengyuanzhi,0,137.2,395,478.5,233.0,740,875.3,,1.1,
|
||||||
hgj_boss_dengmao,0,146.1,803,859.9,
|
hgj_boss_dengmao,0,146.1,346,515.0,250.0,803,894.4,,1.05,
|
||||||
hgj_boss_guanhai,0,144.2,826,848.7,
|
hgj_boss_guanhai,0,144.2,366,822.0,244.0,826,1075.6,,1.18,
|
||||||
hgj_boss_peiyuanshao,0,134.3,736,790.5,
|
hgj_boss_peiyuanshao,0,134.3,304,531.0,273.0,736,903.9,,1.1,
|
||||||
hgj_boss_zhangbao,0,155.7,804,916.4,
|
hgj_boss_zhangbao,0,155.7,371,548.0,258.0,804,907.1,,,
|
||||||
hgj_boss_zhangjiao,0,147.8,854,869.9,
|
hgj_boss_zhangjiao,0,147.8,341,524.0,341.0,854,869.9,1,,
|
||||||
hgj_boss_zhangliang,0,160.1,882,942.3,
|
hgj_boss_zhangliang,0,160.1,370,649.0,269.0,882,1108.2,,1.15,
|
||||||
hgj_cavalry,1,107.4,879,632.1,
|
hgj_cavalry,1,107.4,242,439.0,214.0,879,632.1,1,,
|
||||||
hgj_infantry,0,161.5,850,950.6,
|
hgj_infantry,0,161.5,387,660.0,292.0,850,950.6,1,,
|
||||||
hgj_spear,0,168.5,862,991.8,
|
hgj_spear,0,168.5,377,584.5,293.0,862,991.8,1,,
|
||||||
huangzhong,0,148.8,797,875.8,
|
huangzhong,0,148.8,394,503.0,230.0,797,875.8,1,,
|
||||||
jiangwei,0,114.2,804,672.2,
|
jiangwei,0,114.2,245,481.0,230.0,804,790.6,,1.15,
|
||||||
jianyong,0,164.8,786,970.0,
|
jianyong,0,164.8,367,515.0,257.0,786,906.8,,,
|
||||||
jiaxu,0,146.4,843,861.7,
|
jiaxu,0,146.4,334,468.0,269.0,843,968.1,,1.15,
|
||||||
jinxuan,0,128.2,772,754.6,
|
jinxuan,0,128.2,289,536.0,257.0,772,932.8,,1.2,
|
||||||
jz_boss_caimao,0,131.1,805,771.6,
|
jz_boss_caimao,0,131.1,278,511.0,248.0,805,902.4,,1.18,
|
||||||
jz_boss_caixun,0,143.2,833,842.9,
|
jz_boss_caixun,0,143.2,320,543.0,296.0,833,1027.4,,1.18,
|
||||||
jz_boss_caizhong,0,139.4,872,820.5,
|
jz_boss_caizhong,0,139.4,324,509.0,276.0,872,1006.3,,1.2,
|
||||||
jz_boss_chensun,0,111.4,749,655.7,
|
jz_boss_chensun,0,111.4,235,394.0,219.0,749,767.7,,1.18,
|
||||||
jz_boss_wenpin,0,117.1,822,689.2,
|
jz_boss_wenpin,0,117.1,260,429.5,243.0,822,851.4,,1.22,
|
||||||
jz_boss_zhangwu,1,84.7,862,498.5,
|
jz_boss_zhangwu,1,84.7,177,332.0,188.0,862,521.2,,,
|
||||||
jz_boss_zhangyun,0,139.5,871,821.1,
|
jz_boss_zhangyun,0,139.5,298,515.0,277.0,871,998.8,,1.2,
|
||||||
lijue,1,93.4,901,549.7,
|
lijue,1,93.4,182,384.0,185.0,901,557.5,,,
|
||||||
lingtong,0,139.3,853,819.9,
|
lingtong,0,139.3,414,585.0,285.0,853,1006.1,,1.1,
|
||||||
liubei,0,142.2,752,837.0,
|
liubei,0,142.2,373,478.0,243.0,752,837.0,1,,
|
||||||
liufeng,1,95.8,851,563.9,
|
liufeng,1,95.8,192,364.0,202.0,851,580.1,,,
|
||||||
lusu,0,158.0,777,930.0,
|
lusu,0,158.0,418,485.0,244.0,777,905.3,,1.05,
|
||||||
luxun,0,171.7,906,1010.6,
|
luxun,0,171.7,379,519.5,304.0,906,968.9,,,
|
||||||
lvmeng,0,147.3,824,867.0,
|
lvmeng,0,147.3,309,501.0,262.0,824,944.9,,1.15,
|
||||||
machao,1,85.6,845,503.8,
|
machao,1,85.6,166,396.0,174.0,845,517.8,,,
|
||||||
menghuo,0,147.7,860,869.3,
|
menghuo,0,147.7,332,735.0,363.0,860,1076.2,,1.08,
|
||||||
mizhu,0,153.5,800,903.5,
|
mizhu,0,153.5,423,589.0,252.0,800,980.9,,1.05,
|
||||||
pangtong,0,163.8,800,964.1,
|
pangtong,0,163.8,405,546.0,277.0,800,929.3,,,
|
||||||
shu_archer,0,148.5,803,874.0,
|
shu_archer,0,148.5,324,554.0,304.0,803,1024.4,,1.15,
|
||||||
shu_cavalry,1,95.2,848,560.3,
|
shu_cavalry,1,95.2,187,361.0,197.0,848,575.9,,,
|
||||||
shu_infantry,0,140.1,881,824.6,
|
shu_infantry,0,140.1,309,524.0,287.0,881,993.0,,1.18,
|
||||||
shu_spear,0,116.3,807,684.5,
|
shu_spear,0,116.3,263,519.0,243.0,807,851.2,,1.18,
|
||||||
shu_strategist,0,134.0,789,788.7,
|
shu_strategist,0,134.0,328,460.0,251.0,789,816.7,,1.05,
|
||||||
sunce,1,96.1,907,565.6,
|
sunce,1,96.1,192,345.0,195.0,907,565.4,,,
|
||||||
sunqian,0,151.7,838,892.9,
|
sunqian,0,151.7,400,460.0,266.0,838,852.8,,,
|
||||||
sunquan,0,149.4,770,879.3,
|
sunquan,0,149.4,339,535.0,220.0,770,921.8,,1.05,
|
||||||
sunshangxiang,0,132.8,676,781.6,
|
sunshangxiang,0,132.8,281,550.0,236.0,676,764.8,,,
|
||||||
taishici,0,146.9,777,864.6,
|
taishici,0,146.9,403,632.0,268.0,777,997.5,,1.05,
|
||||||
wanglang,0,137.5,809,809.3,
|
wanglang,0,137.5,324,434.0,291.0,809,877.7,,1.05,
|
||||||
wei_boss_simayi,0,148.8,846,875.8,
|
wei_boss_simayi,0,148.8,318,439.0,258.0,846,882.8,,1.08,
|
||||||
weiyan,0,127.7,764,751.6,
|
weiyan,0,127.7,257,452.0,246.0,764,857.9,,1.15,
|
||||||
wu_chengpu,0,112.9,717,664.5,
|
wu_chengpu,0,112.9,227,427.0,205.0,717,722.9,,1.12,
|
||||||
wu_ganning,0,146.9,887,864.6,
|
wu_ganning,0,146.9,454,775.0,326.0,887,1102.3,,,
|
||||||
wu_huanggai,0,146.5,762,862.3,
|
wu_huanggai,0,146.5,417,627.0,215.0,762,944.7,,,
|
||||||
wu_sunjian,0,171.2,876,1007.7,
|
wu_sunjian,0,171.2,411,537.0,269.0,876,1056.9,,1.12,
|
||||||
wu_zhouyu,0,136.3,823,802.2,
|
wu_zhouyu,0,136.3,328,449.5,244.0,823,812.6,,1.05,
|
||||||
xiahoumao,1,92.3,840,543.3,
|
xiahoumao,1,92.3,188,361.0,213.0,840,567.3,,,
|
||||||
xiahouyuan,0,125.6,791,739.3,
|
xiahouyuan,0,125.6,265,447.0,252.0,791,868.2,,1.18,
|
||||||
xiaoqiao,0,158.2,859,931.1,
|
xiaoqiao,0,158.2,343,577.0,292.0,859,995.8,,1.08,
|
||||||
xunyu,0,142.0,853,835.8,
|
xunyu,0,142.0,356,441.0,257.0,853,851.1,,1.05,
|
||||||
xushu,0,164.5,898,968.2,
|
xushu,0,164.5,454,551.0,286.0,898,982.2,,1.05,
|
||||||
yanbaihu,0,174.8,923,1028.8,
|
yanbaihu,0,174.8,388,669.0,330.0,923,1185.5,,1.15,
|
||||||
ys_boss_quyi,0,127.7,793,751.6,
|
ys_boss_quyi,0,127.7,256,471.0,255.0,793,875.8,,1.15,
|
||||||
ys_boss_wenchou,1,88.3,913,519.7,
|
ys_boss_wenchou,1,88.3,182,392.0,181.0,913,579.3,,1.08,
|
||||||
ys_boss_yanliang,0,120.4,755,708.7,
|
ys_boss_yanliang,0,120.4,249,458.0,215.0,755,788.6,,1.15,
|
||||||
ys_gongsunzan,1,85.7,825,504.4,
|
ys_gongsunzan,1,85.7,184,327.0,181.0,825,520.0,,,
|
||||||
ysu_boss_jiling,0,111.2,765,654.5,
|
ysu_boss_jiling,0,111.2,232,401.0,208.0,765,761.2,,1.18,
|
||||||
ysu_boss_xunzheng,0,116.2,755,683.9,
|
ysu_boss_xunzheng,0,116.2,234,439.0,215.0,755,750.9,,1.12,
|
||||||
yuanshao,0,131.8,822,775.8,
|
yuanshao,0,131.8,275,520.0,250.0,822,846.8,,1.1,
|
||||||
yuanshu,0,154.9,879,911.7,
|
yuanshu,0,154.9,384,515.0,208.0,879,965.5,,1.1,
|
||||||
zhangfei,1,106.3,917,625.7,눈 수동 실측 2026-11-10: 눈+눈썹 붙은 어두운 덩어리 중심 x 403.0 · 509.3(v5 재디자인 · 자동 = 말 콧구멍 오검출)
|
zhangfei,1,106.3,0,0,0,917,625.7,1,,눈 수동 실측 2026-11-10: 눈+눈썹 붙은 어두운 덩어리 중심 x 403.0 · 509.3(v5 재디자인 · 자동 = 말 콧구멍 오검출)
|
||||||
zhanglu,0,158.9,926,935.3,
|
zhanglu,0,158.9,371,490.0,284.0,926,991.7,,1.1,
|
||||||
zhangren,0,128.5,786,756.3,
|
zhangren,0,128.5,271,464.5,258.0,786,872.4,,1.15,
|
||||||
zhaofan,0,134.6,798,792.2,
|
zhaofan,0,134.6,373,441.0,264.0,798,839.4,,1.05,
|
||||||
zhaoyun,1,100.4,926,590.9,
|
zhaoyun,1,100.4,199,393.0,192.0,926,590.9,1,,
|
||||||
zhoucang,0,173.3,856,1020.0,
|
zhoucang,0,173.3,453,598.0,276.0,856,1149.8,,1.15,
|
||||||
zhoutai,0,162.2,885,954.7,
|
zhoutai,0,162.2,429,599.5,273.0,885,1045.9,,1.08,
|
||||||
zhugejin,0,160.2,870,942.9,
|
zhugejin,0,160.2,372,501.0,270.0,870,954.7,,1.08,
|
||||||
zhugeliang,0,140.2,886,825.2,
|
zhugeliang,0,140.2,336,469.0,264.0,886,962.8,,1.18,
|
||||||
zhurong,0,118.3,773,696.3,눈 수동 실측 2026-11-10: 눈 덩어리 중심 x 524.4 · 642.7(짙은 피부 = 피부 판정 밖)
|
zhurong,0,118.3,0,0,0,773,821.6,,1.18,눈 수동 실측 2026-11-10: 눈 덩어리 중심 x 524.4 · 642.7(짙은 피부 = 피부 판정 밖)
|
||||||
|
|
|
||||||
|
|
Before Width: | Height: | Size: 484 KiB After Width: | Height: | Size: 483 KiB |
|
Before Width: | Height: | Size: 462 KiB After Width: | Height: | Size: 461 KiB |
|
Before Width: | Height: | Size: 417 KiB After Width: | Height: | Size: 416 KiB |
|
Before Width: | Height: | Size: 529 KiB After Width: | Height: | Size: 528 KiB |
|
Before Width: | Height: | Size: 426 KiB After Width: | Height: | Size: 423 KiB |
|
Before Width: | Height: | Size: 488 KiB After Width: | Height: | Size: 488 KiB |
|
Before Width: | Height: | Size: 384 KiB After Width: | Height: | Size: 384 KiB |
|
Before Width: | Height: | Size: 379 KiB After Width: | Height: | Size: 379 KiB |
|
Before Width: | Height: | Size: 306 KiB After Width: | Height: | Size: 308 KiB |
|
Before Width: | Height: | Size: 530 KiB After Width: | Height: | Size: 531 KiB |
|
Before Width: | Height: | Size: 362 KiB After Width: | Height: | Size: 362 KiB |
|
Before Width: | Height: | Size: 572 KiB After Width: | Height: | Size: 571 KiB |
|
Before Width: | Height: | Size: 435 KiB After Width: | Height: | Size: 432 KiB |
|
Before Width: | Height: | Size: 505 KiB After Width: | Height: | Size: 503 KiB |
|
Before Width: | Height: | Size: 593 KiB After Width: | Height: | Size: 592 KiB |
|
Before Width: | Height: | Size: 343 KiB After Width: | Height: | Size: 344 KiB |
|
Before Width: | Height: | Size: 373 KiB After Width: | Height: | Size: 372 KiB |
|
Before Width: | Height: | Size: 464 KiB After Width: | Height: | Size: 464 KiB |
|
Before Width: | Height: | Size: 356 KiB After Width: | Height: | Size: 354 KiB |
|
Before Width: | Height: | Size: 456 KiB After Width: | Height: | Size: 454 KiB |
|
Before Width: | Height: | Size: 424 KiB After Width: | Height: | Size: 424 KiB |
|
Before Width: | Height: | Size: 384 KiB After Width: | Height: | Size: 384 KiB |
|
Before Width: | Height: | Size: 360 KiB After Width: | Height: | Size: 360 KiB |
|
|
@ -978,26 +978,29 @@ namespace TKS.Tests
|
||||||
[UnityTest]
|
[UnityTest]
|
||||||
public IEnumerator UnitArt_SizeTable_SameHeadSize_MountedRiderLikeFoot()
|
public IEnumerator UnitArt_SizeTable_SameHeadSize_MountedRiderLikeFoot()
|
||||||
{
|
{
|
||||||
// PD 2026-11-09 「캐릭터 크기 동일 · 말 탄 유닛도 보병·창병과 비슷한 비율」 — 크기 표(art_size.csv · 눈 간격 기준)로 배율을 정하면
|
// PD 2026-11-09 「캐릭터 크기 동일 · 말 탄 유닛도 보병·창병과 비슷한 비율」 + 2026-11-10 「새 유닛이 이전 유닛보다 크다 · 이전 기준에 맞게」 —
|
||||||
// 게임 안 눈 간격(월드)이 모든 유닛에서 같다 · 기마 유닛은 표준 키보다 커진다(탄 사람 = 보병 크기)
|
// 크기 표(art_size.csv · lock 1 = PD 확인 기준 20종 고정 · 나머지 = 눈·얼굴·머리 폭·정수리 가운데 값 × adjust · Tools/unit_art_size.py)의
|
||||||
|
// size_px 가 모든 유닛에서 표준 키(월드)가 되도록 배율을 정한다 · 기마 유닛은 표준 키보다 커진다(탄 사람 = 보병 크기)
|
||||||
var ta = Resources.Load<TextAsset>(UnitArt.Root + UnitArt.SizeTable);
|
var ta = Resources.Load<TextAsset>(UnitArt.Root + UnitArt.SizeTable);
|
||||||
Assert.IsNotNull(ta, "크기 표 Resources/Units/art_size.csv");
|
Assert.IsNotNull(ta, "크기 표 Resources/Units/art_size.csv");
|
||||||
var lines = ta.text.TrimStart('').Split('\n');
|
var lines = ta.text.TrimStart('').Split('\n');
|
||||||
var head = new List<string>(lines[0].Trim().Split(','));
|
var head = new List<string>(lines[0].Trim().Split(','));
|
||||||
int ia = head.IndexOf("art"), im = head.IndexOf("mounted"), ie = head.IndexOf("eye_px");
|
int ia = head.IndexOf("art"), im = head.IndexOf("mounted"), isz = head.IndexOf("size_px"), il = head.IndexOf("lock");
|
||||||
var eyes = new List<float>();
|
Assert.GreaterOrEqual(il, 0, "lock 열(PD 확인 기준 행)");
|
||||||
int mounted = 0;
|
int rows = 0, mounted = 0, locked = 0;
|
||||||
var go = new GameObject("SizeProbe");
|
var go = new GameObject("SizeProbe");
|
||||||
var fa = go.AddComponent<FrameAnimator>();
|
var fa = go.AddComponent<FrameAnimator>();
|
||||||
for (int i = 1; i < lines.Length; i++)
|
for (int i = 1; i < lines.Length; i++)
|
||||||
{
|
{
|
||||||
var c = lines[i].Trim().Split(',');
|
var c = lines[i].Trim().Split(',');
|
||||||
if (c.Length <= ie) continue;
|
if (c.Length <= Mathf.Max(isz, il)) continue;
|
||||||
var a = UnitArt.Load(c[ia]);
|
var a = UnitArt.Load(c[ia]);
|
||||||
Assert.IsNotNull(a, "표의 아트 " + c[ia]);
|
Assert.IsNotNull(a, "표의 아트 " + c[ia]);
|
||||||
Assert.Greater(a.SizePx, 0f, c[ia] + " size_px");
|
Assert.AreEqual(float.Parse(c[isz], System.Globalization.CultureInfo.InvariantCulture), a.SizePx, 0.01f, c[ia] + " size_px 읽음");
|
||||||
fa.Setup(a, cfg.unitArtHeight, 0);
|
fa.Setup(a, cfg.unitArtHeight, 0);
|
||||||
eyes.Add(float.Parse(c[ie], System.Globalization.CultureInfo.InvariantCulture) / a.Meta.ppu * go.transform.localScale.x);
|
Assert.AreEqual(cfg.unitArtHeight, a.SizePx / a.Meta.ppu * go.transform.localScale.x, 1e-3f, c[ia] + " 크기 표 키(월드) = 표준 키");
|
||||||
|
rows++;
|
||||||
|
if (c[il] == "1") locked++;
|
||||||
if (c[im] == "1")
|
if (c[im] == "1")
|
||||||
{
|
{
|
||||||
mounted++;
|
mounted++;
|
||||||
|
|
@ -1005,10 +1008,9 @@ namespace TKS.Tests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Object.Destroy(go);
|
Object.Destroy(go);
|
||||||
Assert.GreaterOrEqual(eyes.Count, 15, "표 행 수");
|
Assert.GreaterOrEqual(rows, 15, "표 행 수");
|
||||||
Assert.GreaterOrEqual(mounted, 5, "기마 행 수");
|
Assert.GreaterOrEqual(mounted, 5, "기마 행 수");
|
||||||
eyes.Sort();
|
Assert.GreaterOrEqual(locked, 10, "PD 확인 기준 행(lock) 수");
|
||||||
Assert.Less(eyes[eyes.Count - 1] / eyes[0], 1.01f, "모든 유닛 눈 간격(월드) 같음");
|
|
||||||
|
|
||||||
// 배치된 기마 영웅 = HP 바 기준(ArtHeight)이 실제 머리 끝
|
// 배치된 기마 영웅 = HP 바 기준(ArtHeight)이 실제 머리 끝
|
||||||
yield return Fresh();
|
yield return Fresh();
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,17 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""유닛 아트 크기 표 생성(PD 2026-11-09 「영웅 목록 캐릭터 크기 동일 · 말 탄 유닛도 보병·창병과 비슷한 비율」).
|
"""유닛 아트 크기 표 생성(PD 2026-11-09 「영웅 목록 캐릭터 크기 동일 · 말 탄 유닛도 보병·창병과 비슷한 비율」).
|
||||||
|
|
||||||
기준 = 눈 간격(머리 크기) — 투구·수염·말과 상관없이 같은 화풍이면 머리 크기에 비례한다.
|
기준(2026-11-10 개정 · PD 「새 유닛(강유·서황 등)이 이전 유닛보다 크다 · 이전에 유닛 크기를 조정한 기준에 맞게」)
|
||||||
eye_px = 대기 첫 프레임(front_idle/f000.png)의 두 눈 중심 사이 가로 px(얼굴 피부 덩어리 안 어두운 구멍 2개)
|
eye_px = 대기 첫 프레임(front_idle/f000.png)의 두 눈 중심 사이 가로 px(얼굴 피부 덩어리 안 어두운 구멍 2개)
|
||||||
ref = 말을 타지 않은 유닛(mounted=0)의 eye_px / body_px 중앙값
|
face_px = 같은 눈 높이 행에서 얼굴 피부 덩어리(구멍 메움) 폭
|
||||||
size_px = eye_px / ref = 「머리 크기가 표준인 유닛의 키」로 환산한 세로 px
|
head_px = 머리 실루엣 폭(투구·머리카락 포함 · 가는 무기는 열림 연산으로 뗌 · 눈~이마 행 중앙값)
|
||||||
게임(FrameAnimator)·영웅 목록은 bodyHeightPx 대신 size_px 로 배율을 정한다 → 모든 유닛의 머리 크기가 같아지고,
|
crown_px = 정수리(가는 깃 제외)~눈 높이
|
||||||
말 탄 유닛은 탄 사람이 보병과 같은 크기가 되도록 커진다.
|
lock 1 = PD 가 크기를 확인한 행(2026-11-09 기존 20종 · 눈 간격 기준) → size_px 고정 = 기준점
|
||||||
|
나머지 size_px = 가운데 값(eye_px × a, face_px × b, head_px × c, crown_px × d) × adjust
|
||||||
|
a~d = 기준점(걷는 유닛) size_px ÷ 각 지표 중앙값 · adjust = 영웅 목록 카드 시뮬레이션(기준 6종과 나란히) 눈 확인 보정(1.1 = 10 % 작게 · 사람이 적음)
|
||||||
|
(11-09 판 = eye_px 하나 → 새 그림은 투구·관이 커서 머리 덩어리가 커짐 → 네 지표 가운데 값 + 눈 확인 보정)
|
||||||
|
게임(FrameAnimator)·영웅 목록은 bodyHeightPx 대신 size_px 로 배율을 정한다 → 머리 덩어리 크기가 고르게 맞고,
|
||||||
|
말 탄 유닛은 탄 사람이 보병과 같은 크기가 되도록 커진다(게임 상한 = BalanceConfig.mountedArtMaxTop).
|
||||||
|
|
||||||
표 = Assets/Resources/Units/art_size.csv(utf-8-sig · SOT) · mounted = 게임 표(영웅·무장·병사)의 병종이 cavalry 면 1(art_types ·
|
표 = Assets/Resources/Units/art_size.csv(utf-8-sig · SOT) · mounted = 게임 표(영웅·무장·병사)의 병종이 cavalry 면 1(art_types ·
|
||||||
표에 없는 아트만 기존 값 · 그것도 없으면 이름에 cavalry 면 1).
|
표에 없는 아트만 기존 값 · 그것도 없으면 이름에 cavalry 면 1).
|
||||||
|
|
@ -25,7 +30,61 @@ from PIL import Image, ImageDraw, ImageFont
|
||||||
from scipy import ndimage
|
from scipy import ndimage
|
||||||
|
|
||||||
TABLE = 'Assets/Resources/Units/art_size.csv'
|
TABLE = 'Assets/Resources/Units/art_size.csv'
|
||||||
FIELDS = ['art', 'mounted', 'eye_px', 'body_px', 'size_px', 'note']
|
FIELDS = ['art', 'mounted', 'eye_px', 'face_px', 'head_px', 'crown_px', 'body_px', 'size_px', 'lock', 'adjust', 'note']
|
||||||
|
|
||||||
|
|
||||||
|
def head_width(img, eyes):
|
||||||
|
"""(머리 실루엣 폭, 정수리~눈 높이) px — 창대 같은 가는 무기는 열림 연산(반지름 0.22 × 눈 간격)으로 떼어 내고,
|
||||||
|
폭 = 눈 높이 ~ 그 위 0.6 × 눈 간격 행들에서 눈 가운데를 품은 불투명 구간 폭의 중앙값 · 못 재면 (0, 0)."""
|
||||||
|
if not eyes:
|
||||||
|
return 0, 0
|
||||||
|
(_, lx, ly), (_, rx, ry) = eyes
|
||||||
|
E = rx - lx
|
||||||
|
y0, cx = int((ly + ry) / 2), int((lx + rx) / 2)
|
||||||
|
al = np.asarray(img.convert('RGBA'))[..., 3] > 128
|
||||||
|
r = max(2, int(E * 0.22))
|
||||||
|
yy, xx = np.ogrid[-r:r + 1, -r:r + 1]
|
||||||
|
al = ndimage.binary_opening(al, structure=(xx * xx + yy * yy) <= r * r)
|
||||||
|
def run(y):
|
||||||
|
row = al[y]
|
||||||
|
if not row[cx]:
|
||||||
|
return 0
|
||||||
|
l = q = cx
|
||||||
|
while l > 0 and row[l - 1]:
|
||||||
|
l -= 1
|
||||||
|
while q < len(row) - 1 and row[q + 1]:
|
||||||
|
q += 1
|
||||||
|
return q - l + 1
|
||||||
|
ws = [w for w in (run(y) for y in range(max(0, int(y0 - 0.6 * E)), y0 + 1, 2)) if w]
|
||||||
|
top = y0
|
||||||
|
for y in range(y0, -1, -1): # 정수리 = 눈에서 위로, 눈 가운데를 품은 구간 폭 ≥ 0.5 × 눈 간격 인 마지막 행(가는 깃·창끝 제외)
|
||||||
|
if run(y) < 0.5 * E:
|
||||||
|
break
|
||||||
|
top = y
|
||||||
|
return (round(float(np.median(ws)), 1) if ws else 0), float(y0 - top)
|
||||||
|
|
||||||
|
|
||||||
|
def face_width(img, eyes):
|
||||||
|
"""눈 높이 행에서 두 눈을 품은 얼굴 피부 덩어리(구멍 메움) 폭 px · 못 재면 0."""
|
||||||
|
if not eyes:
|
||||||
|
return 0
|
||||||
|
a = np.asarray(img.convert('RGBA')).astype(np.float32) / 255.0
|
||||||
|
(_, lx, ly), (_, rx, ry) = eyes
|
||||||
|
y, cx = int(round((ly + ry) / 2)), int(round((lx + rx) / 2))
|
||||||
|
sk = skin_mask(a, False)[0] | skin_mask(a, True)[0]
|
||||||
|
lab, n = ndimage.label(sk)
|
||||||
|
k = lab[y, cx]
|
||||||
|
for dx in range(1, int(rx - lx) + 1):
|
||||||
|
if k:
|
||||||
|
break
|
||||||
|
for xx in (cx - dx, cx + dx):
|
||||||
|
if 0 <= xx < lab.shape[1] and lab[y, xx]:
|
||||||
|
k = lab[y, xx]
|
||||||
|
break
|
||||||
|
if not k:
|
||||||
|
return 0
|
||||||
|
xs = np.where(ndimage.binary_fill_holes(lab == k)[y])[0]
|
||||||
|
return int(xs.max() - xs.min() + 1) if len(xs) else 0
|
||||||
|
|
||||||
|
|
||||||
def skin_mask(a, red):
|
def skin_mask(a, red):
|
||||||
|
|
@ -135,26 +194,49 @@ def main():
|
||||||
eye = round(eyes[1][1] - eyes[0][1], 1) if eyes else 0
|
eye = round(eyes[1][1] - eyes[0][1], 1) if eyes else 0
|
||||||
if not eyes and prev.get('note', '').startswith('눈 수동') and float(prev.get('eye_px') or 0) > 0:
|
if not eyes and prev.get('note', '').startswith('눈 수동') and float(prev.get('eye_px') or 0) > 0:
|
||||||
eye = float(prev['eye_px']) # 자동으로 못 찾는 그림(안대·눈썹과 붙은 눈·짙은 피부) = 표에 적은 수동 실측 값 유지
|
eye = float(prev['eye_px']) # 자동으로 못 찾는 그림(안대·눈썹과 붙은 눈·짙은 피부) = 표에 적은 수동 실측 값 유지
|
||||||
rows.append({'art': art, 'mounted': mounted, 'eye_px': eye, 'body_px': body, 'size_px': '', 'note': prev.get('note', '')})
|
lock = prev.get('lock', '')
|
||||||
|
hw, cr = head_width(img, eyes)
|
||||||
|
rows.append({'art': art, 'mounted': mounted, 'eye_px': eye, 'face_px': face_width(img, eyes), 'head_px': hw, 'crown_px': cr,
|
||||||
|
'body_px': body, 'size_px': float(prev['size_px']) if lock == '1' and prev.get('size_px') else '', 'lock': lock,
|
||||||
|
'adjust': prev.get('adjust', ''), 'note': prev.get('note', '')})
|
||||||
shots.append((art, meta, img, eyes))
|
shots.append((art, meta, img, eyes))
|
||||||
|
|
||||||
foot = sorted(r['eye_px'] / r['body_px'] for r in rows if r['mounted'] == '0' and r['eye_px'] and r['body_px'])
|
def median(v):
|
||||||
if not foot:
|
v = sorted(v)
|
||||||
sys.exit('말을 타지 않은 유닛의 눈 간격을 하나도 못 찾음')
|
m = len(v) // 2
|
||||||
mid = len(foot) // 2
|
return v[m] if len(v) % 2 else (v[m - 1] + v[m]) / 2
|
||||||
ref = foot[mid] if len(foot) % 2 else (foot[mid - 1] + foot[mid]) / 2
|
|
||||||
|
# 기준 = lock 1 행(PD 가 확인한 크기 · 고정) 중 걷는 유닛: size_px ÷ 눈 간격·얼굴 폭·머리 폭 중앙값 → 나머지 = 세 추정 평균
|
||||||
|
# (PD 2026-11-10 「새 유닛(강유·서황)이 이전 유닛보다 크다 · 이전에 유닛 크기를 조정한 기준에 맞게」 — 새 그림은 투구가 커서 눈 간격 하나로는 머리 덩어리가 커짐)
|
||||||
|
anchor = [r for r in rows if r['lock'] == '1' and r['mounted'] == '0' and r['size_px']]
|
||||||
|
if not anchor:
|
||||||
|
sys.exit('기준 행(lock = 1 · 걷는 유닛) 없음')
|
||||||
|
ratio = {}
|
||||||
|
for key in ('eye_px', 'face_px', 'head_px', 'crown_px'):
|
||||||
|
v = [r['size_px'] / r[key] for r in anchor if r[key]]
|
||||||
|
ratio[key] = median(v) if v else 0
|
||||||
for r in rows:
|
for r in rows:
|
||||||
r['size_px'] = round(r['eye_px'] / ref, 1) if r['eye_px'] else ''
|
if r['lock'] == '1' and r['size_px']:
|
||||||
|
continue
|
||||||
|
# 네 추정(눈 간격 · 얼굴 폭 · 머리 폭 · 정수리 높이)의 가운데 값 — 하나가 투구·수염·무기에 속아도 버팀 · × adjust(영웅 목록 카드 눈 확인 보정 · 1.1 = 10 % 작게)
|
||||||
|
est = sorted(r[k] * ratio[k] for k in ratio if r[k] and ratio[k])
|
||||||
|
if not est:
|
||||||
|
r['size_px'] = ''
|
||||||
|
continue
|
||||||
|
m = len(est) // 2
|
||||||
|
base = est[m] if len(est) % 2 else (est[m - 1] + est[m]) / 2
|
||||||
|
r['size_px'] = round(base * (float(r['adjust']) if r['adjust'] else 1.0), 1)
|
||||||
|
|
||||||
with open(table, 'w', encoding='utf-8-sig', newline='') as f:
|
with open(table, 'w', encoding='utf-8-sig', newline='') as f:
|
||||||
w = csv.DictWriter(f, fieldnames=FIELDS, lineterminator='\n')
|
w = csv.DictWriter(f, fieldnames=FIELDS, lineterminator='\n')
|
||||||
w.writeheader()
|
w.writeheader()
|
||||||
w.writerows(rows)
|
w.writerows(rows)
|
||||||
print('ref(눈 간격 / 키 · 말 안 탄 %d종 중앙값) = %.4f' % (len(foot), ref))
|
print('기준 %d종(lock · 걷는 유닛) · 비율 size/눈 %.3f · size/얼굴 %.3f · size/머리 %.3f · size/정수리 %.3f' % (
|
||||||
|
len(anchor), ratio['eye_px'], ratio['face_px'], ratio['head_px'], ratio['crown_px']))
|
||||||
for r in rows:
|
for r in rows:
|
||||||
s = (r['size_px'] / r['body_px']) if r['size_px'] else 0
|
s = (r['size_px'] / r['body_px']) if r['size_px'] else 0
|
||||||
print('%-22s mounted %s eye %6s body %5s size %7s 배율 x%.2f%s' % (
|
print('%-22s mounted %s lock %1s eye %6s face %4s head %6s body %5s size %7s 배율 x%.2f%s' % (
|
||||||
r['art'], r['mounted'], r['eye_px'], r['body_px'], r['size_px'], 1 / s if s else 0, '' if r['eye_px'] else ' ← 눈 못 찾음(bodyHeightPx 대체)'))
|
r['art'], r['mounted'], r['lock'], r['eye_px'], r['face_px'], r['head_px'], r['body_px'], r['size_px'], 1 / s if s else 0, '' if r['eye_px'] else ' ← 눈 못 찾음'))
|
||||||
|
|
||||||
if sheet_path: # 확인 시트: 표 배율로 그린 대기 그림 + 눈 표시(머리 크기가 같아 보여야 함)
|
if sheet_path: # 확인 시트: 표 배율로 그린 대기 그림 + 눈 표시(머리 크기가 같아 보여야 함)
|
||||||
unit = 160.0 # 표준 키 = 시트 160 px
|
unit = 160.0 # 표준 키 = 시트 160 px
|
||||||
|
|
|
||||||