Files
AIGame/Tools/redesign_secondary_ui.py
T

276 lines
19 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Author the task journal, seven-day rewards and mailbox via JSON -> Prefab."""
import json
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
OUT = ROOT / 'Doc/UIPrefabCreater'
INK = '#29465FFF'
MUTED = '#647B8BFF'
class Page:
def __init__(self, key, title, subtitle, icon, height):
self.name = 'UI_Prototype' + key
self.data = dict(schemaVersion=1, prefabName=self.name,
prefabPath=f'Assets/Bundles/UI/Prefab/{self.name}.prefab',
description='Normal 层营地冒险手册:安全区、可滚动内容、奖励优先;由 JSON 生成,沿用服务端奖励规则。',
canvas={'canvasScaler': {'referenceResolution': [1080, 1920], 'matchWidthOrHeight': 0}},
assets={'sprites': {}}, nodes=[], bindings=[], events=[])
self.node(self.name, None, self.stretch(), [dict(type='Canvas', sortingOrder=100),
dict(type='CanvasScaler', referenceResolution=[1080, 1920], matchWidthOrHeight=0), dict(type='GraphicRaycaster')])
self.data['assets']['sprites']['journal_paper_v1'] = 'Assets/Bundles/UI/Bg/journal_paper_v1.png'
self.node('Background', self.name, self.stretch(), [
dict(type='Image', sprite='journal_paper_v1', imageType='Simple', color='#FFFFFFFF', raycastTarget=True),
dict(type='MonoBehaviour', typeName='AOT.UI.BootArtworkCover')])
self.node('SafeArea', self.name, self.stretch(), [dict(type='MonoBehaviour', typeName='AOT.UI.UiSafeArea')])
self.node('Header', 'SafeArea', dict(anchorMin=[0, 1], anchorMax=[1, 1], pivot=[.5, 1], anchoredPosition=[0, 0], sizeDelta=[0, 164]))
self.button('Back', 'Header', 36, 26, 104, 104, '', icon='btn_back')
self.text('Title', 'Header', 166, 22, 690, 68, title, 50)
self.text('Subtitle', 'Header', 170, 94, 690, 42, subtitle, 26, MUTED)
self.icon('HeaderEmblem', 'Header', 910, 24, 110, 110, icon)
self.node('Scroll_Page', 'SafeArea', self.stretch([0, 22], [0, -170]),
[dict(type='ScrollRect', horizontal=False, vertical=True, viewport='Viewport_Page', content='Content_Page')])
self.node('Viewport_Page', 'Scroll_Page', self.stretch(),
[dict(type='Image', color='#FFFFFF00', raycastTarget=True), dict(type='RectMask2D')])
self.node('Content_Page', 'Viewport_Page', dict(anchorMin=[0, 1], anchorMax=[1, 1], pivot=[.5, 1], anchoredPosition=[0, 0], sizeDelta=[0, height]))
@staticmethod
def stretch(lo=None, hi=None):
return dict(anchorMin=[0, 0], anchorMax=[1, 1], offsetMin=lo or [0, 0], offsetMax=hi or [0, 0])
@staticmethod
def rect(x, y, w, h):
return dict(anchorMin=[0, 1], anchorMax=[0, 1], pivot=[0, 1], anchoredPosition=[x, -y], sizeDelta=[w, h])
def node(self, name, parent, rect, components=None, active=True):
self.data['nodes'].append(dict(name=name, parent=parent, active=active, rect=rect, components=components or []))
return name
def sprite(self, name):
folder = 'Journal' if name.startswith('journal_') else 'Home' if name.startswith('home_') else 'Prototype' if name.startswith('proto_') else 'Common'
self.data['assets']['sprites'][name] = f'Assets/Bundles/UI/Texture/{folder}/{name}.png'
return name
def image(self, name, parent, x, y, w, h, sprite, color='#FFFFFFFF', simple=False, raycast=False):
return self.node(name, parent, self.rect(x, y, w, h), [dict(type='Image', sprite=self.sprite(sprite),
imageType='Simple' if simple else 'Sliced', color=color, preserveAspect=simple, raycastTarget=raycast)])
def icon(self, name, parent, x, y, w, h, sprite):
return self.image(name, parent, x, y, w, h, sprite, simple=True)
def card(self, name, y, h, sprite='journal_card', color='#FFFFFFFF', x=40, w=1000, parent='Content_Page'):
return self.image(name, parent, x, y, w, h, sprite, color)
def text(self, name, parent, x, y, w, h, text, size=32, color=INK, align='Left'):
node = 'Txt_' + name
self.node(node, parent, self.rect(x, y, w, h), [dict(type='TextMeshProUGUI', text=text,
fontSize=size, color=color, alignment={'MidlineLeft':'Left','MidlineRight':'Right','TopLeft':'Left'}.get(align,align), raycastTarget=False)])
if parent in ['TaskSummary','MailWallet']:
self.data['nodes'][-1]['components'].append(dict(type='Shadow',effectColor='#10203360',effectDistance=[0,-1]))
self.data['bindings'].append(node)
return node
def button(self, name, parent, x, y, w, h, text, primary=False, icon=None, active=True):
sprite = icon or ('journal_primary_normal' if primary else 'journal_secondary_normal')
node = self.image('Btn_' + name, parent, x, y, w, h, sprite, simple=icon is not None, raycast=True)
obj = self.data['nodes'][-1]
obj['active'] = active
obj['components'].append(dict(type='Button', transition='ColorTint' if icon else 'SpriteSwap',
spriteHighlighted=self.sprite(sprite), spritePressed=self.sprite(sprite if icon else sprite.replace('normal', 'pressed')),
spriteDisabled=self.sprite('journal_disabled'), onClick='Prototype.' + name))
if not icon:
label = self.text(name, node, 12, 8, w-24, h-16, text, 32, '#5D431FFF' if primary else INK, 'Center')
self.data['bindings'].append(node)
self.data['events'].append(dict(name='Prototype.' + name, node=node))
return node
def progress(self, name, parent, x, y, w, h=22):
self.image(name + 'Track', parent, x, y, w, h, 'journal_progress_bg')
self.image(name + 'Fill', parent, x, y, w, h, 'journal_progress_fill')
def save(self):
(OUT / (self.name + '.json')).write_text(json.dumps(self.data, ensure_ascii=False, indent=2) + '\n', encoding='utf-8')
def tasks():
p = Page('Tasks', '冒险手册', '每天一小步,营地更热闹', 'home_ico_tasks', 1718)
p.card('TaskSummary', 12, 250, 'journal_hero')
p.text('TodayCaption', 'TaskSummary', 36, 28, 580, 44, '今日旅程', 30, '#F4CE88FF')
p.text('TaskSummary', 'TaskSummary', 36, 82, 660, 70, '0 / 3 份奖励已领取', 46, '#FFFFFFFF')
p.text('TaskReset', 'TaskSummary', 38, 170, 660, 42, '每日 00:00 更新', 26, '#C2D5DFFF')
p.icon('TodayReward', 'TaskSummary', 778, 32, 176, 176, 'home_ico_chest')
for i in range(3):
p.image('DailyMilestone' + str(i), 'TaskSummary', 38+i*216, 222, 198, 12, 'journal_progress_fill', '#ACC5D6FF')
p.text('DailyHeading', 'Content_Page', 52, 290, 500, 54, '每日委托', 36)
p.text('DailyHint', 'Content_Page', 600, 296, 422, 42, '完成即可领取奖励', 26, MUTED, 'MidlineRight')
icons = ['home_ico_pinball', 'home_ico_battle', 'home_ico_pets']
titles = ['完成3次弹珠发射', '完成1场冒险', '与伙伴互动1次']
for i in range(3):
card = 'TaskCard' + str(i+1)
p.card(card, 360+i*236, 216)
p.icon('DailyIcon'+str(i), card, 24, 40, 112, 112, icons[i])
p.text('DailyName'+str(i), card, 158, 24, 560, 50, titles[i], 36)
p.text('DailyInfo'+str(i), card, 160, 80, 540, 40, '完成一次结算即可计入', 26, MUTED)
p.progress('DailyProgress'+str(i), card, 160, 145, 350, 20)
p.text('DailyCount'+str(i), card, 532, 128, 170, 50, '0 / 3', 30, MUTED, 'MidlineRight')
p.text('DailyReward'+str(i), card, 734, 26, 236, 54, '弹珠 ×5', 34, '#99641FFF', 'Center')
p.button('ClaimDaily'+str(i), card, 738, 102, 224, 92, '领取奖励', True, active=False)
p.button('GoDaily'+str(i), card, 738, 102, 224, 92, '去完成')
p.text('DailyDone'+str(i), card, 738, 118, 224, 60, '已领取', 32, '#698479FF', 'Center')
p.data['nodes'][-1]['active'] = False
p.text('GrowthHeading', 'Content_Page', 52, 1090, 800, 54, '成长足迹', 36)
p.card('TaskCard0', 1162, 200, color='#E5EDF2FF')
p.icon('GoalIcon', 'TaskCard0', 24, 40, 112, 112, 'home_ico_home')
p.text('GoalName', 'TaskCard0', 158, 22, 550, 50, '营地初守', 36)
p.text('GoalInfo', 'TaskCard0', 160, 82, 540, 74, '首次通关 1-1\n成长奖励永久保留', 26, MUTED)
p.text('GoalReward', 'TaskCard0', 734, 22, 236, 54, '弹珠 ×5', 34, '#99641FFF', 'Center')
p.button('ClaimGoal', 'TaskCard0', 738, 98, 224, 88, '领取奖励', True, active=False)
p.button('GoGoal', 'TaskCard0', 738, 98, 224, 88, '去冒险')
p.text('GoalDone', 'TaskCard0', 738, 114, 224, 60, '已领取', 32, '#698479FF', 'Center')
p.data['nodes'][-1]['active'] = False
p.card('SigninEntry', 1396, 156, 'journal_gold_card')
p.icon('SigninEntryIcon', 'SigninEntry', 24, 20, 116, 116, 'home_ico_calendar')
p.text('SigninEntryTitle', 'SigninEntry', 164, 28, 520, 52, '七日同行礼', 36)
p.text('SigninEntryHint', 'SigninEntry', 166, 90, 520, 40, '累计登录,每一天都有收获', 26, MUTED)
p.button('ToSignin', 'SigninEntry', 738, 34, 224, 88, '查看奖励')
p.text('TaskRules', 'Content_Page', 64, 1590, 952, 84, '每日奖励请在重置前领取\n成长奖励仅可领取一次', 25, MUTED, 'Center')
p.save()
def signin():
p = Page('Signin', '七日同行礼', '每一次回到营地,都值得期待', 'home_ico_calendar', 1700)
p.card('SigninSummary', 12, 258, 'journal_gold_card')
p.text('SigninCaption', 'SigninSummary', 36, 28, 650, 44, '把小小的收获,攒成大大的惊喜', 30)
p.text('SigninSummary', 'SigninSummary', 36, 82, 690, 70, '已相伴 1 天', 48)
p.text('SigninHint', 'SigninSummary', 38, 176, 650, 42, '漏登不重置 · 已解锁奖励可补领', 26, MUTED)
p.icon('SigninHeroGift', 'SigninSummary', 786, 42, 176, 176, 'home_ico_gift')
p.text('SigninSection', 'Content_Page', 52, 302, 800, 54, '你的七日礼物', 36)
for i in range(7):
special = i == 6
card = 'SigninCard' + str(i)
x, y, w, h = (40, 1138, 1000, 300) if special else (40+(i%3)*340, 382+(i//3)*366, 320, 342)
p.card(card, y, h, 'journal_gold_card' if special else 'journal_card', x=x, w=w)
p.text('SigninDay'+str(i), card, 28, 18, w-56, 48, '第 7 天 · 同行大礼' if special else f'第 {i+1} 天', 34, INK, 'MidlineLeft' if special else 'Center')
p.icon('SigninIcon'+str(i), card, 44 if special else 104, 90 if special else 72, 170 if special else 112, 170 if special else 112, 'home_ico_chest' if special else 'home_ico_pinball')
p.text('SigninReward'+str(i), card, 260 if special else 18, 96 if special else 194, 420 if special else 284, 50, '弹珠 ×'+str([10,5,5,10,5,10,20][i]), 40 if special else 32, '#99641FFF', 'MidlineLeft' if special else 'Center')
if special: p.text('FinalGiftHint', card, 260, 174, 420, 48, '累计登录七天即可解锁', 26, MUTED)
p.button('Signin'+str(i), card, 718 if special else 28, 174 if special else 252, 246 if special else 264, 88 if special else 78, '领取', True)
# Locked/claimed days are labels, never disabled fake actions.
p.text('SigninState'+str(i), card, 718 if special else 28, 174 if special else 270, 246 if special else 264, 88 if special else 60, '等待解锁', 28, MUTED, 'Center')
p.button('ToTasks', 'Content_Page', 260, 1484, 560, 96, '去完成今日委托')
p.text('SigninRules', 'Content_Page', 64, 1604, 952, 60, '每日首次登录计入天数 · 每份奖励仅可领取一次', 25, MUTED, 'Center')
p.save()
def mail():
p = Page('Mail', '营地信箱', '来自小镇的问候与补给', 'home_ico_mail', 1840)
p.card('MailWallet', 12, 172, 'journal_hero')
p.icon('MailHero', 'MailWallet', 24, 24, 120, 120, 'home_ico_mail')
p.text('MailWallet', 'MailWallet', 170, 26, 780, 62, '营地来信', 42, '#FFFFFFFF')
p.text('MailSummaryHint', 'MailWallet', 172, 100, 780, 42, '点击来信,查看消息与随信礼物', 26, '#C2D5DFFF')
p.text('InboxHeading', 'Content_Page', 52, 210, 500, 54, '收到的来信', 36)
p.node('MailStack', 'Content_Page', p.rect(40, 284, 1000, 0), [
dict(type='VerticalLayoutGroup', spacing=24, childControlWidth=True, childControlHeight=True,
childForceExpandWidth=True, childForceExpandHeight=False),
dict(type='ContentSizeFitter', verticalFit='PreferredSize')])
for i in range(5):
b=p.button('Mail'+str(i), 'Content_Page', 40, 284+i*126, 1000, 114, '')
n=next(n for n in p.data['nodes'] if n['name']==b)
n['components'][0]['sprite']=p.sprite('journal_card')
n['components'][1].update(spriteHighlighted=p.sprite('journal_card'),spritePressed=p.sprite('journal_secondary_pressed'),spriteDisabled=p.sprite('journal_card'))
# The generic label is intentionally replaced by title and status.
p.data['nodes']=[n for n in p.data['nodes'] if n['name']!='Txt_Mail'+str(i)]
p.data['bindings'].remove('Txt_Mail'+str(i))
p.icon('MailIcon'+str(i), b, 20, 17, 80, 80, 'home_ico_mail')
p.text('MailTitle'+str(i), b, 128, 12, 800, 48, '营地来信', 34)
p.text('MailStatus'+str(i), b, 130, 66, 800, 36, '未读 · 附件待领取', 25, MUTED)
p.image('MailUnread'+str(i), b, 950, 28, 16, 16, 'journal_progress_fill')
p.text('MailEmpty', 'Content_Page', 100, 440, 880, 180, '信箱暂时空空的\n下一份问候,正在路上', 36, MUTED, 'Center')
p.data['nodes'][-1]['active']=False
p.node('MailPager', 'MailStack', p.rect(0,0,1000,76), [dict(type='LayoutElement', preferredHeight=76)])
p.button('MailPrev', 'Content_Page', 48, 930, 184, 76, '上一页')
p.text('MailPage', 'Content_Page', 260, 938, 560, 60, '1 / 1 页', 28, MUTED, 'Center')
p.button('MailNext', 'Content_Page', 848, 930, 184, 76, '下一页')
p.card('MailDetail', 1040, 502)
p.text('MailDetailTitle', 'MailDetail', 32, 26, 936, 60, '请选择一封邮件', 38)
p.text('MailDetailStatus', 'MailDetail', 34, 96, 932, 42, '查看来自营地的消息', 26, MUTED)
p.node('MailBodyScroll', 'MailDetail', p.rect(34, 156, 932, 164), [dict(type='ScrollRect',horizontal=False,vertical=True,viewport='MailBodyViewport',content='Txt_MailBody')])
p.node('MailBodyViewport', 'MailBodyScroll', p.stretch(), [dict(type='Image',color='#FFFFFF00',raycastTarget=True),dict(type='RectMask2D')])
p.text('MailBody','MailBodyViewport',0,0,920,164,'营地来信和奖励补给会显示在这里。',30,INK,'TopLeft')
p.data['nodes'][-1]['components'].append(dict(type='ContentSizeFitter',verticalFit='PreferredSize'))
p.text('MailAttachment','MailDetail',34,338,932,58,'',30,'#99641FFF')
p.button('ClaimMail','MailDetail',590,414,374,70,'领取附件',True)
p.button('ClaimAllMail','Content_Page',240,1580,600,104,'领取全部附件',True)
p.text('MailRules','Content_Page',64,1720,952,80,'附件领取后直接到账\n一键最多领取 20 封,请在到期前领取',25,MUTED,'Center')
stack_heights = {'Btn_Mail'+str(i):114 for i in range(5)}
stack_heights.update(Txt_MailEmpty=240,MailDetail=502,Btn_ClaimAllMail=104,Txt_MailRules=80)
for n in p.data['nodes']:
if n['name'] in stack_heights:
n['parent']='MailStack'
n['components'].append(dict(type='LayoutElement',preferredHeight=stack_heights[n['name']]))
if n['name'] in ['Btn_MailPrev','Txt_MailPage','Btn_MailNext']:
n['parent']='MailPager'
n['rect']['anchoredPosition'][0]-=40
n['rect']['anchoredPosition'][1]+=930
p.save()
def challenges():
p = Page('Challenges', '属性试炼', '元素相生相克 · 携同调伙伴挑战强敌', 'home_ico_battle', 1830)
# 1. Hero Card
p.card('ChallengeSummary', 12, 210, 'journal_hero')
p.text('HeroCaption', 'ChallengeSummary', 36, 26, 620, 44, '元素试炼 · 专属徽章与伙伴碎片', 30, '#F4CE88FF')
p.text('Summary', 'ChallengeSummary', 36, 78, 680, 64, '通过 1-1 后开放。只上阵对应元素,首次通过获得徽章和碎片。', 32, '#FFFFFFFF')
p.text('HeroHint', 'ChallengeSummary', 38, 150, 680, 40, '试炼不消耗弹珠 · 首胜获10碎片与专属徽章', 26, '#C2D5DFFF')
p.icon('ChallengeHero', 'ChallengeSummary', 778, 24, 160, 160, 'home_ico_battle')
# 2. Section Heading
p.text('TrialHeading', 'Content_Page', 52, 244, 500, 54, '五行试炼', 36)
p.text('TrialSub', 'Content_Page', 560, 250, 460, 42, '点击切换试炼项目', 26, MUTED, 'MidlineRight')
# 3. 5 Challenge Buttons
pet_icons = ['proto_pet_1', 'proto_pet_2', 'proto_pet_0', 'proto_pet_3', 'proto_pet_4']
default_titles = ['火 · 灼草之试', '草 · 碎岩之试', '岩 · 避雷之试', '电 · 破浪之试', '水 · 灭火之试']
for i in range(5):
b = p.button('Challenge' + str(i), 'Content_Page', 40, 308 + i * 140, 1000, 124, '')
n = next(n for n in p.data['nodes'] if n['name'] == b)
n['components'][0]['sprite'] = p.sprite('journal_card')
n['components'][1].update(
spriteHighlighted=p.sprite('journal_card'),
spritePressed=p.sprite('journal_secondary_pressed'),
spriteDisabled=p.sprite('journal_disabled'))
p.data['nodes'] = [n for n in p.data['nodes'] if n['name'] != 'Txt_Challenge' + str(i)]
p.data['bindings'].remove('Txt_Challenge' + str(i))
p.icon('ChallengeIcon' + str(i), b, 18, 14, 96, 96, pet_icons[i])
p.text('Challenge' + str(i), b, 134, 16, 640, 48, default_titles[i], 34, INK, 'Left')
p.text('ChallengeState' + str(i), b, 136, 68, 640, 40, '可挑战', 26, MUTED, 'Left')
p.text('ChallengeTag' + str(i), b, 780, 40, 180, 44, '选择 >', 26, '#8FA6B5FF', 'Center')
# 4. Detail Panel
p.text('DetailSectionHeading', 'Content_Page', 52, 1028, 500, 50, '试炼详情与阵容', 36)
p.text('DetailSectionHint', 'Content_Page', 560, 1034, 460, 42, '同调元素自动上阵', 26, MUTED, 'MidlineRight')
p.card('DetailPanel', 1088, 450, 'journal_gold_card')
p.icon('DetailEmblem', 'DetailPanel', 28, 22, 64, 64, 'home_ico_trophy')
p.text('DetailTitle', 'DetailPanel', 106, 28, 400, 44, '试炼情报', 34, INK)
p.text('DetailTag', 'DetailPanel', 720, 30, 240, 36, '不消耗弹珠', 26, '#99641FFF', 'MidlineRight')
p.text('Detail', 'DetailPanel', 34, 90, 932, 220, '选择试炼查看详细情报…', 28, INK, 'TopLeft')
p.button('Start', 'DetailPanel', 32, 324, 936, 96, '开始试炼', primary=True)
# 5. Quick Actions
p.text('QuickHeading', 'Content_Page', 52, 1558, 500, 48, '冒险整备', 36)
p.text('QuickHint', 'Content_Page', 560, 1564, 460, 40, '缺少对应元素伙伴?', 26, MUTED, 'MidlineRight')
p.button('Capture', 'Content_Page', 40, 1618, 485, 92, '结识伙伴')
p.button('Fish', 'Content_Page', 555, 1618, 485, 92, '小镇钓鱼')
# 6. Rules
p.text('Rules', 'Content_Page', 64, 1730, 952, 76, '属性试炼不消耗弹珠 · 首胜即发专属徽章与10枚伙伴碎片\n再次挑战仅在刷新个人最短用时纪录时计入排行榜', 25, MUTED, 'Center')
p.save()
if __name__ == '__main__':
tasks()
signin()
mail()
challenges()
print('Authored Tasks / Signin / Mail / Challenges JSON. Build and verify with Unity Pipeline.')