@@ -0,0 +1,524 @@
#nullable enable
using System ;
using System.Collections.Generic ;
using System.IO ;
using System.Linq ;
using ReplacedPerson.Runtime ;
using ShrinkApp ;
using ShrinkContext.AppAdapter ;
using ShrinkModFramework ;
using ShrinkTutorial ;
using TMPro ;
using UnityEditor ;
using UnityEditor.SceneManagement ;
using UnityEngine ;
using UnityEngine.EventSystems ;
using UnityEngine.SceneManagement ;
using UnityEngine.TextCore.LowLevel ;
using UnityEngine.UI ;
namespace ReplacedPerson.Editor
{
public static class ReplacedPersonSceneBuilder
{
private const string Root = "Assets/Demos/ReplacedPerson" ;
private const string PrefabRoot = Root + "/Prefabs" ;
private const string ResourceRoot = Root + "/Resources" ;
private const string ScenePath = Root + "/ReplacedPerson.unity" ;
private const string DemoPrefabPath = PrefabRoot + "/ReplacedPersonDemo.prefab" ;
private const string BattlePrefabPath = PrefabRoot + "/BattleScreen.prefab" ;
private const string ModalPrefabPath = PrefabRoot + "/ModalOverlay.prefab" ;
private const string FontPath = Root + "/Art/NotoSansSC-Variable.ttf" ;
private const string FontAssetPath = Root + "/Art/NotoSansSC SDF.asset" ;
private static TMP_FontAsset ? _font ;
private static Sprite ? _paper ;
private static Sprite ? _attack ;
private static Sprite ? _dodge ;
private static Sprite ? _counter ;
[MenuItem("ShrinkSDK/Demos/Replaced Person/Rebuild Full Demo")]
public static void Rebuild ( )
{
Directory . CreateDirectory ( PrefabRoot ) ;
Directory . CreateDirectory ( ResourceRoot ) ;
AssetDatabase . Refresh ( ) ;
EnsureSdkAssets ( ) ;
ConfigureSprites ( ) ;
EnsureFont ( ) ;
BuildBattlePrefab ( ) ;
BuildModalPrefab ( ) ;
BuildDemoPrefab ( ) ;
BuildTutorialAssets ( ) ;
BuildScene ( ) ;
AddToBuildSettings ( ) ;
AssetDatabase . SaveAssets ( ) ;
AssetDatabase . Refresh ( ) ;
Debug . Log ( $"[ReplacedPerson] Demo rebuilt: {ScenePath}" ) ;
}
[MenuItem("ShrinkSDK/Demos/Replaced Person/Build Windows Player")]
public static void BuildWindowsPlayer ( )
{
var output = Path . GetFullPath ( Path . Combine ( Application . dataPath , ".." , "Artifacts" , "ReplacedPersonBuild" , "ReplacedPerson.exe" ) ) ;
Directory . CreateDirectory ( Path . GetDirectoryName ( output ) ! ) ;
var report = BuildPipeline . BuildPlayer ( new BuildPlayerOptions
{
scenes = new [ ] { ScenePath } ,
locationPathName = output ,
target = BuildTarget . StandaloneWindows64 ,
options = BuildOptions . Development
} ) ;
if ( report . summary . result ! = UnityEditor . Build . Reporting . BuildResult . Succeeded )
throw new InvalidOperationException ( $"Windows build failed: errors={report.summary.totalErrors}, warnings={report.summary.totalWarnings}" ) ;
Debug . Log ( $"[ReplacedPerson] Windows build complete: {output}, warnings={report.summary.totalWarnings}" ) ;
}
private static void EnsureSdkAssets ( )
{
const string appSettingsPath = "Assets/Resources/ShrinkAppSettings.asset" ;
var appSettings = AssetDatabase . LoadAssetAtPath < ShrinkAppSettings > ( appSettingsPath ) ;
var appScript = appSettings = = null
? null
: new SerializedObject ( appSettings ) . FindProperty ( "m_Script" ) ? . objectReferenceValue ;
if ( appSettings = = null | | appScript = = null )
{
if ( ! AssetDatabase . IsValidFolder ( "Assets/Resources" ) ) AssetDatabase . CreateFolder ( "Assets" , "Resources" ) ;
if ( ! string . IsNullOrEmpty ( AssetDatabase . AssetPathToGUID ( appSettingsPath ) ) ) AssetDatabase . DeleteAsset ( appSettingsPath ) ;
appSettings = ScriptableObject . CreateInstance < ReplacedPersonAppSettings > ( ) ;
appSettings . hostingMode = ShrinkAppHostingMode . ContextLoader ;
appSettings . verboseLogging = false ;
AssetDatabase . CreateAsset ( appSettings , appSettingsPath ) ;
}
const string modSettingsPath = ResourceRoot + "/ShrinkModFrameworkSettings.asset" ;
var modSettings = AssetDatabase . LoadAssetAtPath < ShrinkModFrameworkSettings > ( modSettingsPath ) ;
if ( modSettings = = null )
{
modSettings = ScriptableObject . CreateInstance < ShrinkModFrameworkSettings > ( ) ;
modSettings . verboseLogging = false ;
AssetDatabase . CreateAsset ( modSettings , modSettingsPath ) ;
}
const string profilePath = "Assets/Resources/ShrinkAppComposition.asset" ;
var profile = AssetDatabase . LoadAssetAtPath < ShrinkAppCompositionProfile > ( profilePath ) ;
if ( profile ! = null )
{
var document = profile . ResolveDocument ( ) ;
if ( document . entries . All ( value = > ! string . Equals ( value . id , "demo.replaced-person" , StringComparison . OrdinalIgnoreCase ) ) )
document . entries . Add ( new ShrinkAppCompositionEntry ( "demo.replaced-person" ) ) ;
profile . SetDocument ( document ) ;
EditorUtility . SetDirty ( profile ) ;
}
AssetDatabase . SaveAssets ( ) ;
}
private static void ConfigureSprites ( )
{
var paths = new [ ]
{
Root + "/Art/ForumOriginal/paper.jpg" ,
Root + "/Art/ForumOriginal/card-attack-a.png" ,
Root + "/Art/ForumOriginal/card-dodge-a.png" ,
Root + "/Art/ForumOriginal/card-counter.png" ,
Root + "/Art/ForumOriginal/card-back.png"
} ;
foreach ( var path in paths )
{
if ( AssetImporter . GetAtPath ( path ) is not TextureImporter importer ) continue ;
importer . textureType = TextureImporterType . Sprite ;
importer . spriteImportMode = SpriteImportMode . Single ;
importer . mipmapEnabled = false ;
importer . alphaIsTransparency = true ;
importer . SaveAndReimport ( ) ;
}
_paper = AssetDatabase . LoadAssetAtPath < Sprite > ( paths [ 0 ] ) ;
_attack = AssetDatabase . LoadAssetAtPath < Sprite > ( paths [ 1 ] ) ;
_dodge = AssetDatabase . LoadAssetAtPath < Sprite > ( paths [ 2 ] ) ;
_counter = AssetDatabase . LoadAssetAtPath < Sprite > ( paths [ 3 ] ) ;
}
private static void EnsureFont ( )
{
DeleteAssetIfExists ( FontAssetPath ) ;
var source = AssetDatabase . LoadAssetAtPath < Font > ( FontPath ) ;
if ( source = = null ) throw new InvalidOperationException ( "CJK source font missing: " + FontPath ) ;
_font = TMP_FontAsset . CreateFontAsset ( source , 48 , 9 , GlyphRenderMode . SDFAA , 1024 , 1024 ,
AtlasPopulationMode . Dynamic , true ) ;
_font . name = "NotoSansSC SDF" ;
_font . atlasPopulationMode = AtlasPopulationMode . Dynamic ;
AssetDatabase . CreateAsset ( _font , FontAssetPath ) ;
foreach ( var atlas in _font . atlasTextures )
{
if ( atlas = = null | | AssetDatabase . Contains ( atlas ) ) continue ;
atlas . name = _font . name + " Atlas" ;
AssetDatabase . AddObjectToAsset ( atlas , _font ) ;
}
if ( _font . material ! = null & & ! AssetDatabase . Contains ( _font . material ) )
{
_font . material . name = _font . name + " Material" ;
AssetDatabase . AddObjectToAsset ( _font . material , _font ) ;
}
EditorUtility . SetDirty ( _font ) ;
AssetDatabase . SaveAssets ( ) ;
}
private static void BuildBattlePrefab ( )
{
var screen = Screen ( "BattleScreen" ) ;
try
{
var header = Panel ( screen . transform , "Header" , new Color ( 0.05f , 0.055f , 0.06f , 0.96f ) ,
new Vector2 ( 0 , . 92f ) , Vector2 . one ) ;
Button ( header . transform , "BackButton" , "‹ " , new Vector2 ( 0 , 0 ) , new Vector2 ( . 07f , 1 ) , 32 ) ;
Text ( header . transform , "RoundText" , "回合 1" , 25 , TextAlignmentOptions . Center ,
new Vector2 ( . 08f , 0 ) , new Vector2 ( . 92f , 1 ) ) ;
var enemy = Panel ( screen . transform , "EnemyArea" , new Color ( . 11f , . 12f , . 12f , . 94f ) ,
new Vector2 ( 0 , . 75f ) , new Vector2 ( 1 , . 92f ) ) ;
Text ( enemy . transform , "EnemyStatus" , "贪婪 HP 30/30 COST 10/10" , 24 , TextAlignmentOptions . Left ,
new Vector2 ( . 025f , . 56f ) , new Vector2 ( . 98f , . 95f ) ) ;
Text ( enemy . transform , "EnemyQueue" , "等待敌方锁定" , 22 , TextAlignmentOptions . Left ,
new Vector2 ( . 025f , . 08f ) , new Vector2 ( . 98f , . 54f ) ) ;
Anchor ( enemy . gameObject , "rp.matchup" ) ;
var effects = FullRect ( screen . transform , "EffectArea" , new Vector2 ( . 03f , . 43f ) , new Vector2 ( . 97f , . 74f ) ) ;
var layout = effects . gameObject . AddComponent < HorizontalLayoutGroup > ( ) ;
layout . spacing = 18 ; layout . childAlignment = TextAnchor . MiddleCenter ;
layout . childControlWidth = true ; layout . childControlHeight = true ; layout . childForceExpandWidth = true ;
for ( var i = 1 ; i < = 3 ; i + + )
{
var slot = Panel ( effects , "Slot" + i , i = = 2 ? new Color ( . 29f , . 11f , . 10f , . 94f ) : new Color ( . 11f , . 14f , . 14f , . 94f ) , Vector2 . zero , Vector2 . one ) ;
Text ( slot . transform , "Text" , $"槽 {i}\n等待拼点" , 21 , TextAlignmentOptions . Center , Vector2 . zero , Vector2 . one , new Vector2 ( 20 , 16 ) , new Vector2 ( - 20 , - 16 ) ) ;
}
var log = Panel ( screen . transform , "LogPanel" , new Color ( . 04f , . 045f , . 05f , . 90f ) , new Vector2 ( . 03f , . 30f ) , new Vector2 ( . 97f , . 42f ) ) ;
Text ( log . transform , "LogText" , "战斗记录" , 15 , TextAlignmentOptions . TopLeft , Vector2 . zero , new Vector2 ( . 48f , 1 ) , new Vector2 ( 16 , 10 ) , new Vector2 ( - 12 , - 10 ) ) ;
Text ( log . transform , "CardDetailTitle" , "卡牌效果" , 18 , TextAlignmentOptions . TopLeft ,
new Vector2 ( . 51f , . 60f ) , new Vector2 ( . 98f , . 94f ) ) ;
Text ( log . transform , "CardDetailText" , "悬停、点击或拖动卡牌查看完整效果。" , 15 , TextAlignmentOptions . TopLeft ,
new Vector2 ( . 51f , . 06f ) , new Vector2 ( . 98f , . 62f ) ) ;
var player = Panel ( screen . transform , "PlayerArea" , new Color ( . 08f , . 085f , . 09f , . 97f ) , new Vector2 ( 0 , 0 ) , new Vector2 ( 1 , . 29f ) ) ;
Text ( player . transform , "PlayerStatus" , "被替代之人 HP 30/30 COST 10/10" , 21 , TextAlignmentOptions . Left ,
new Vector2 ( . 02f , . 82f ) , new Vector2 ( . 5f , . 99f ) ) ;
Text ( player . transform , "SelectionText" , "普通牌:未选择\n结束牌:未选择" , 15 , TextAlignmentOptions . TopLeft ,
new Vector2 ( . 51f , . 73f ) , new Vector2 ( . 98f , . 99f ) ) ;
var normalHand = FullRect ( player . transform , "NormalHand" , new Vector2 ( . 02f , . 30f ) , new Vector2 ( . 72f , . 81f ) ) ;
var normalLayout = normalHand . gameObject . AddComponent < HorizontalLayoutGroup > ( ) ;
normalLayout . spacing = 8 ; normalLayout . childControlWidth = false ; normalLayout . childControlHeight = true ;
normalLayout . childForceExpandWidth = false ; normalLayout . padding = new RectOffset ( 0 , 0 , 4 , 4 ) ;
CardTemplate ( normalHand , "CardButtonTemplate" ) ;
Anchor ( normalHand . gameObject , "rp.normal-hand" ) ;
var endHand = FullRect ( player . transform , "EndHand" , new Vector2 ( . 73f , . 30f ) , new Vector2 ( . 98f , . 81f ) ) ;
var endLayout = endHand . gameObject . AddComponent < HorizontalLayoutGroup > ( ) ;
endLayout . spacing = 8 ; endLayout . childControlWidth = false ; endLayout . childControlHeight = true ; endLayout . childForceExpandWidth = false ;
CardTemplate ( endHand , "CardButtonTemplate" ) ;
Anchor ( endHand . gameObject , "rp.end-hand" ) ;
var actions = FullRect ( player . transform , "Actions" , new Vector2 ( . 02f , . 03f ) , new Vector2 ( . 98f , . 28f ) ) ;
var actionLayout = actions . gameObject . AddComponent < HorizontalLayoutGroup > ( ) ;
actionLayout . spacing = 10 ; actionLayout . childControlHeight = true ; actionLayout . childControlWidth = true ; actionLayout . childForceExpandWidth = true ;
Button ( actions , "Reroll1Button" , "骰 1" , Vector2 . zero , Vector2 . one , 17 ) ;
Button ( actions , "Reroll2Button" , "骰 2" , Vector2 . zero , Vector2 . one , 17 ) ;
Button ( actions , "Reroll3Button" , "骰 3" , Vector2 . zero , Vector2 . one , 17 ) ;
Button ( actions , "PassButton" , "跳过" , Vector2 . zero , Vector2 . one , 18 ) ;
Button ( actions , "SubmitButton" , "锁定" , Vector2 . zero , Vector2 . one , 19 , new Color ( . 66f , . 12f , . 10f , 1 ) ) ;
Anchor ( actions . gameObject , "rp.reroll" ) ;
PrefabUtility . SaveAsPrefabAsset ( screen , BattlePrefabPath ) ;
}
finally { UnityEngine . Object . DestroyImmediate ( screen ) ; }
}
private static void BuildModalPrefab ( )
{
var root = Screen ( "ModalOverlay" ) ;
try
{
root . AddComponent < CanvasGroup > ( ) ;
var scrim = Image ( root . transform , "Scrim" , null , new Color ( 0 , 0 , 0 , . 72f ) , Vector2 . zero , Vector2 . one ) ;
scrim . raycastTarget = true ;
var panel = Panel ( root . transform , "Panel" , new Color ( . 11f , . 12f , . 12f , 1 ) , new Vector2 ( . 3f , . 32f ) , new Vector2 ( . 7f , . 68f ) ) ;
Text ( panel . transform , "Title" , "提示" , 30 , TextAlignmentOptions . Center , new Vector2 ( . 08f , . 67f ) , new Vector2 ( . 92f , . 94f ) ) ;
Text ( panel . transform , "Message" , "" , 20 , TextAlignmentOptions . Center , new Vector2 ( . 08f , . 28f ) , new Vector2 ( . 92f , . 68f ) ) ;
Button ( panel . transform , "CloseButton" , "确定" , new Vector2 ( . 32f , . 08f ) , new Vector2 ( . 68f , . 25f ) , 20 ) ;
PrefabUtility . SaveAsPrefabAsset ( root , ModalPrefabPath ) ;
}
finally { UnityEngine . Object . DestroyImmediate ( root ) ; }
}
private static void BuildDemoPrefab ( )
{
var root = new GameObject ( "ReplacedPersonDemo" , typeof ( RectTransform ) , typeof ( ReplacedPersonDemoController ) ) ;
try
{
var canvasObject = new GameObject ( "Canvas" , typeof ( RectTransform ) , typeof ( Canvas ) , typeof ( CanvasScaler ) , typeof ( GraphicRaycaster ) ) ;
canvasObject . transform . SetParent ( root . transform , false ) ;
var canvas = canvasObject . GetComponent < Canvas > ( ) ; canvas . renderMode = RenderMode . ScreenSpaceOverlay ;
var scaler = canvasObject . GetComponent < CanvasScaler > ( ) ;
scaler . uiScaleMode = CanvasScaler . ScaleMode . ScaleWithScreenSize ; scaler . referenceResolution = new Vector2 ( 1920 , 1080 ) ; scaler . matchWidthOrHeight = . 5f ;
Stretch ( canvasObject . GetComponent < RectTransform > ( ) ) ;
var background = Image ( canvasObject . transform , "Background" , _paper , new Color ( . 32f , . 35f , . 36f , 1 ) , Vector2 . zero , Vector2 . one ) ;
background . preserveAspect = false ;
var safe = FullRect ( canvasObject . transform , "SafeAreaRoot" , Vector2 . zero , Vector2 . one ) ;
safe . gameObject . AddComponent < ReplacedSafeArea > ( ) ;
var screens = FullRect ( safe , "Screens" , Vector2 . zero , Vector2 . one ) ;
BuildTitle ( screens ) ;
BuildChapter ( screens ) ;
BuildDeck ( screens ) ;
BuildLan ( screens ) ;
var battlePrefab = AssetDatabase . LoadAssetAtPath < GameObject > ( BattlePrefabPath ) ? ? throw new InvalidOperationException ( "Battle prefab missing." ) ;
var battle = ( GameObject ) PrefabUtility . InstantiatePrefab ( battlePrefab ) ;
battle . transform . SetParent ( screens , false ) ; Stretch ( battle . GetComponent < RectTransform > ( ) ) ;
BuildReward ( screens ) ;
BuildSettings ( screens ) ;
PrefabUtility . SaveAsPrefabAsset ( root , DemoPrefabPath ) ;
}
finally { UnityEngine . Object . DestroyImmediate ( root ) ; }
}
private static void BuildTitle ( Transform parent )
{
var screen = Screen ( "TitleScreen" , parent ) ;
Text ( screen . transform , "Title" , "被替代之人" , 70 , TextAlignmentOptions . Center , new Vector2 ( . 08f , . 70f ) , new Vector2 ( . 92f , . 94f ) ) ;
Text ( screen . transform , "Subtitle" , "第一章 · 贪婪" , 24 , TextAlignmentOptions . Center , new Vector2 ( . 15f , . 62f ) , new Vector2 ( . 85f , . 72f ) ) ;
var art = FullRect ( screen . transform , "ArtTriptych" , new Vector2 ( . 08f , . 20f ) , new Vector2 ( . 58f , . 62f ) ) ;
var layout = art . gameObject . AddComponent < HorizontalLayoutGroup > ( ) ; layout . spacing = 20 ; layout . childControlHeight = true ; layout . childControlWidth = true ; layout . childForceExpandWidth = true ;
Image ( art , "Attack" , _attack , Color . white , Vector2 . zero , Vector2 . one ) . preserveAspect = true ;
Image ( art , "Dodge" , _dodge , Color . white , Vector2 . zero , Vector2 . one ) . preserveAspect = true ;
Image ( art , "Counter" , _counter , Color . white , Vector2 . zero , Vector2 . one ) . preserveAspect = true ;
var actions = FullRect ( screen . transform , "Actions" , new Vector2 ( . 64f , . 20f ) , new Vector2 ( . 91f , . 61f ) ) ;
var vertical = actions . gameObject . AddComponent < VerticalLayoutGroup > ( ) ;
vertical . spacing = 14 ; vertical . childControlWidth = true ; vertical . childControlHeight = true ; vertical . childForceExpandHeight = true ;
Button ( actions , "ContinueButton" , "进入章节" , Vector2 . zero , Vector2 . one , 25 , new Color ( . 67f , . 13f , . 11f , 1 ) ) ;
Button ( actions , "DeckButton" , "牌组" , Vector2 . zero , Vector2 . one , 22 ) ;
Button ( actions , "LanButton" , "局域网" , Vector2 . zero , Vector2 . one , 22 ) ;
Button ( actions , "SettingsButton" , "设置" , Vector2 . zero , Vector2 . one , 22 ) ;
Text ( screen . transform , "Footer" , "DEMO / WINDOWS PC / v1" , 14 , TextAlignmentOptions . BottomLeft , new Vector2 ( . 03f , . 02f ) , new Vector2 ( . 7f , . 09f ) ) ;
}
private static void BuildChapter ( Transform parent )
{
var screen = Screen ( "ChapterScreen" , parent ) ;
Header ( screen . transform , "章节" ) ;
var content = FullRect ( screen . transform , "Content" , new Vector2 ( . 10f , . 17f ) , new Vector2 ( . 90f , . 88f ) ) ;
Text ( content , "ChapterLabel" , "第一章 · 贪婪" , 42 , TextAlignmentOptions . TopLeft , new Vector2 ( 0 , . 77f ) , Vector2 . one ) ;
Text ( content , "StoryText" , "" , 26 , TextAlignmentOptions . TopLeft , Vector2 . zero , new Vector2 ( 1 , . 76f ) ) ;
var footer = FullRect ( screen . transform , "Footer" , new Vector2 ( . 66f , . 05f ) , new Vector2 ( . 90f , . 14f ) ) ;
Button ( footer , "StartBattleButton" , "进入第一战" , Vector2 . zero , Vector2 . one , 24 , new Color ( . 67f , . 13f , . 11f , 1 ) ) ;
}
private static void BuildDeck ( Transform parent )
{
var screen = Screen ( "DeckScreen" , parent ) ; Header ( screen . transform , "牌组编辑" ) ;
var content = FullRect ( screen . transform , "Content" , new Vector2 ( . 04f , . 15f ) , new Vector2 ( . 96f , . 88f ) ) ;
Text ( content , "DeckStatus" , "普通牌 12/12" , 22 , TextAlignmentOptions . Left , new Vector2 ( 0 , . 91f ) , Vector2 . one ) ;
ScrollList ( content , "CurrentDeck" , "当前牌组 · 点击移除" , new Vector2 ( 0 , 0 ) , new Vector2 ( . 48f , . 89f ) ) ;
ScrollList ( content , "Collection" , "收藏 · 点击加入" , new Vector2 ( . 52f , 0 ) , new Vector2 ( 1 , . 89f ) ) ;
var footer = FullRect ( screen . transform , "Footer" , new Vector2 ( . 62f , . 04f ) , new Vector2 ( . 96f , . 12f ) ) ;
var layout = footer . gameObject . AddComponent < HorizontalLayoutGroup > ( ) ; layout . spacing = 12 ; layout . childControlHeight = true ; layout . childControlWidth = true ;
Button ( footer , "ResetButton" , "重置" , Vector2 . zero , Vector2 . one , 20 ) ;
Button ( footer , "SaveButton" , "保存" , Vector2 . zero , Vector2 . one , 20 , new Color ( . 67f , . 13f , . 11f , 1 ) ) ;
}
private static void BuildLan ( Transform parent )
{
var screen = Screen ( "LanScreen" , parent ) ; Header ( screen . transform , "局域网大厅" ) ;
var content = FullRect ( screen . transform , "Content" , new Vector2 ( . 18f , . 16f ) , new Vector2 ( . 82f , . 84f ) ) ;
Text ( content , "StatusText" , "尚未连接" , 24 , TextAlignmentOptions . Center , new Vector2 ( . 05f , . 68f ) , new Vector2 ( . 95f , . 92f ) ) ;
Input ( content , "AddressInput" , "127.0.0.1" , new Vector2 ( . 18f , . 49f ) , new Vector2 ( . 82f , . 62f ) ) ;
Button ( content , "HostButton" , "创建房间" , new Vector2 ( . 18f , . 31f ) , new Vector2 ( . 48f , . 43f ) , 22 ) ;
Button ( content , "JoinButton" , "直接加入" , new Vector2 ( . 52f , . 31f ) , new Vector2 ( . 82f , . 43f ) , 22 , new Color ( . 67f , . 13f , . 11f , 1 ) ) ;
Button ( content , "StartMatchButton" , "进入对局" , new Vector2 ( . 34f , . 16f ) , new Vector2 ( . 66f , . 27f ) , 21 , new Color ( . 67f , . 13f , . 11f , 1 ) ) ;
Text ( content , "DiscoveryText" , "局域网发现会自动广播可用房间" , 17 , TextAlignmentOptions . Center , new Vector2 ( . 1f , . 01f ) , new Vector2 ( . 9f , . 14f ) ) ;
}
private static void BuildReward ( Transform parent )
{
var screen = Screen ( "RewardScreen" , parent ) ;
Text ( screen . transform , "Title" , "结算" , 28 , TextAlignmentOptions . Center , new Vector2 ( . 2f , . 87f ) , new Vector2 ( . 8f , . 96f ) ) ;
var content = FullRect ( screen . transform , "Content" , new Vector2 ( . 18f , . 22f ) , new Vector2 ( . 82f , . 84f ) ) ;
Text ( content , "ResultText" , "胜利" , 52 , TextAlignmentOptions . Center , new Vector2 ( . 1f , . 75f ) , new Vector2 ( . 9f , . 94f ) ) ;
Text ( content , "RewardText" , "" , 21 , TextAlignmentOptions . TopLeft , new Vector2 ( . 05f , . 30f ) , new Vector2 ( . 48f , . 73f ) ) ;
Text ( content , "AppearanceText" , "当前装饰:无" , 20 , TextAlignmentOptions . Left , new Vector2 ( . 52f , . 61f ) , new Vector2 ( . 98f , . 72f ) ) ;
var ornaments = FullRect ( content , "Ornaments" , new Vector2 ( . 52f , . 30f ) , new Vector2 ( . 98f , . 59f ) ) ;
var ornamentLayout = ornaments . gameObject . AddComponent < VerticalLayoutGroup > ( ) ;
ornamentLayout . spacing = 8 ; ornamentLayout . childControlWidth = true ; ornamentLayout . childControlHeight = true ; ornamentLayout . childForceExpandHeight = true ;
Button ( ornaments , "GoldEyeButton" , "金色势利眼" , Vector2 . zero , Vector2 . one , 17 ) ;
Button ( ornaments , "GoldChainButton" , "金链" , Vector2 . zero , Vector2 . one , 17 ) ;
Button ( ornaments , "WornSuitButton" , "褶皱西装" , Vector2 . zero , Vector2 . one , 17 ) ;
var footer = FullRect ( screen . transform , "Footer" , new Vector2 ( . 38f , . 08f ) , new Vector2 ( . 62f , . 17f ) ) ;
Button ( footer , "ContinueButton" , "继续" , Vector2 . zero , Vector2 . one , 23 , new Color ( . 67f , . 13f , . 11f , 1 ) ) ;
}
private static void BuildSettings ( Transform parent )
{
var screen = Screen ( "SettingsScreen" , parent ) ; Header ( screen . transform , "设置" ) ;
var content = FullRect ( screen . transform , "Content" , new Vector2 ( . 24f , . 25f ) , new Vector2 ( . 76f , . 78f ) ) ;
Text ( content , "TransportStatus" , "LAN 传输:TCP" , 28 , TextAlignmentOptions . Center , new Vector2 ( . 1f , . 68f ) , new Vector2 ( . 9f , . 91f ) ) ;
Button ( content , "TcpButton" , "TCP" , new Vector2 ( . 12f , . 42f ) , new Vector2 ( . 48f , . 61f ) , 23 ) ;
Button ( content , "KcpButton" , "KCP" , new Vector2 ( . 52f , . 42f ) , new Vector2 ( . 88f , . 61f ) , 23 ) ;
Text ( content , "Note" , "1920× 1080 基准 · 支持 16:10 与 4:3\n拖牌与点牌共用同一提交队列" , 18 , TextAlignmentOptions . Center , new Vector2 ( . 06f , . 09f ) , new Vector2 ( . 94f , . 34f ) ) ;
}
private static void Header ( Transform screen , string title )
{
var header = Panel ( screen , "Header" , new Color ( . 05f , . 055f , . 06f , . 94f ) , new Vector2 ( 0 , . 90f ) , Vector2 . one ) ;
Button ( header . transform , "BackButton" , "‹ " , new Vector2 ( 0 , 0 ) , new Vector2 ( . 08f , 1 ) , 34 ) ;
Text ( header . transform , "Title" , title , 30 , TextAlignmentOptions . Left , new Vector2 ( . 10f , 0 ) , new Vector2 ( . 9f , 1 ) ) ;
}
private static void ScrollList ( Transform parent , string name , string title , Vector2 min , Vector2 max )
{
var root = Panel ( parent , name , new Color ( . 08f , . 085f , . 09f , . 94f ) , min , max ) ;
Text ( root . transform , "Title" , title , 20 , TextAlignmentOptions . Left , new Vector2 ( . 04f , . 90f ) , new Vector2 ( . 96f , . 99f ) ) ;
var viewport = FullRect ( root . transform , "Viewport" , new Vector2 ( . 03f , . 03f ) , new Vector2 ( . 97f , . 88f ) ) ;
viewport . gameObject . AddComponent < RectMask2D > ( ) ;
var content = FullRect ( viewport , "Content" , new Vector2 ( 0 , 1 ) , Vector2 . one ) ;
content . anchorMin = new Vector2 ( 0 , 1 ) ; content . anchorMax = new Vector2 ( 1 , 1 ) ; content . pivot = new Vector2 ( . 5f , 1 ) ; content . sizeDelta = new Vector2 ( 0 , 0 ) ;
var layout = content . gameObject . AddComponent < VerticalLayoutGroup > ( ) ;
layout . spacing = 6 ; layout . childControlWidth = true ; layout . childControlHeight = true ; layout . childForceExpandWidth = true ; layout . childForceExpandHeight = false ; layout . padding = new RectOffset ( 4 , 4 , 4 , 4 ) ;
var fitter = content . gameObject . AddComponent < ContentSizeFitter > ( ) ; fitter . verticalFit = ContentSizeFitter . FitMode . PreferredSize ;
var row = Button ( content , "RowTemplate" , "卡牌" , Vector2 . zero , Vector2 . one , 17 ) ;
row . gameObject . AddComponent < LayoutElement > ( ) . preferredHeight = 54 ; row . gameObject . SetActive ( false ) ;
var scroll = root . gameObject . AddComponent < ScrollRect > ( ) ; scroll . viewport = viewport ; scroll . content = content ; scroll . horizontal = false ; scroll . movementType = ScrollRect . MovementType . Clamped ;
}
private static void CardTemplate ( Transform parent , string name )
{
var button = Button ( parent , name , "卡牌" , Vector2 . zero , Vector2 . one , 14 , new Color ( . 90f , . 87f , . 78f , . 98f ) ) ;
button . gameObject . AddComponent < ReplacedCardDragHandler > ( ) ;
var layout = button . gameObject . AddComponent < LayoutElement > ( ) ; layout . preferredWidth = 132 ; layout . minWidth = 116 ;
var rect = button . GetComponent < RectTransform > ( ) ; rect . sizeDelta = new Vector2 ( 132 , 132 ) ;
button . gameObject . SetActive ( false ) ;
}
private static GameObject Screen ( string name , Transform ? parent = null )
{
var result = new GameObject ( name , typeof ( RectTransform ) ) ;
if ( parent ! = null ) result . transform . SetParent ( parent , false ) ;
Stretch ( result . GetComponent < RectTransform > ( ) ) ;
return result ;
}
private static RectTransform FullRect ( Transform parent , string name , Vector2 min , Vector2 max )
{
var result = new GameObject ( name , typeof ( RectTransform ) ) . GetComponent < RectTransform > ( ) ;
result . SetParent ( parent , false ) ; result . anchorMin = min ; result . anchorMax = max ; result . offsetMin = Vector2 . zero ; result . offsetMax = Vector2 . zero ;
return result ;
}
private static Image Panel ( Transform parent , string name , Color color , Vector2 min , Vector2 max ) = > Image ( parent , name , null , color , min , max ) ;
private static Image Image ( Transform parent , string name , Sprite ? sprite , Color color , Vector2 min , Vector2 max )
{
var rect = FullRect ( parent , name , min , max ) ;
var image = rect . gameObject . AddComponent < Image > ( ) ; image . sprite = sprite ; image . color = color ; image . raycastTarget = false ;
return image ;
}
private static TMP_Text Text ( Transform parent , string name , string value , float size , TextAlignmentOptions alignment ,
Vector2 min , Vector2 max , Vector2 ? offsetMin = null , Vector2 ? offsetMax = null )
{
var rect = FullRect ( parent , name , min , max ) ; rect . offsetMin = offsetMin ? ? Vector2 . zero ; rect . offsetMax = offsetMax ? ? Vector2 . zero ;
var text = rect . gameObject . AddComponent < TextMeshProUGUI > ( ) ; text . font = _font ; text . text = value ; text . fontSize = size ; text . alignment = alignment ;
text . color = new Color ( . 92f , . 91f , . 86f , 1 ) ; text . enableWordWrapping = true ; text . overflowMode = TextOverflowModes . Ellipsis ; text . raycastTarget = false ;
return text ;
}
private static Button Button ( Transform parent , string name , string label , Vector2 min , Vector2 max , float size , Color ? color = null )
{
var image = Image ( parent , name , null , color ? ? new Color ( . 18f , . 19f , . 19f , . 98f ) , min , max ) ; image . raycastTarget = true ;
var button = image . gameObject . AddComponent < Button > ( ) ; button . targetGraphic = image ;
Text ( image . transform , "Label" , label , size , TextAlignmentOptions . Center , Vector2 . zero , Vector2 . one , new Vector2 ( 8 , 4 ) , new Vector2 ( - 8 , - 4 ) ) ;
return button ;
}
private static TMP_InputField Input ( Transform parent , string name , string placeholder , Vector2 min , Vector2 max )
{
var background = Image ( parent , name , null , new Color ( . 92f , . 90f , . 84f , 1 ) , min , max ) ; background . raycastTarget = true ;
var area = FullRect ( background . transform , "TextArea" , new Vector2 ( . 04f , . 1f ) , new Vector2 ( . 96f , . 9f ) ) ; area . gameObject . AddComponent < RectMask2D > ( ) ;
var text = Text ( area , "Text" , string . Empty , 22 , TextAlignmentOptions . Left , Vector2 . zero , Vector2 . one ) ; text . color = Color . black ;
var hint = Text ( area , "Placeholder" , placeholder , 22 , TextAlignmentOptions . Left , Vector2 . zero , Vector2 . one ) ; hint . color = new Color ( . 2f , . 2f , . 2f , . 45f ) ;
var input = background . gameObject . AddComponent < TMP_InputField > ( ) ; input . textViewport = area ; input . textComponent = ( TMP_Text ) text ; input . placeholder = hint ;
return input ;
}
private static void Anchor ( GameObject target , string id )
{
var anchor = target . AddComponent < ShrinkTutorialAnchor > ( ) ; anchor . SetAnchorId ( id ) ;
}
private static void Stretch ( RectTransform rect )
{
rect . anchorMin = Vector2 . zero ; rect . anchorMax = Vector2 . one ; rect . offsetMin = Vector2 . zero ; rect . offsetMax = Vector2 . zero ;
}
private static void BuildTutorialAssets ( )
{
DeleteAssetIfExists ( ResourceRoot + "/ReplacedPersonFirstMatchTutorial.asset" ) ;
DeleteAssetIfExists ( ResourceRoot + "/ReplacedPersonTutorialDatabase.asset" ) ;
DeleteAssetIfExists ( ResourceRoot + "/ShrinkTutorialSettings.asset" ) ;
DeleteAssetIfExists ( ResourceRoot + "/ReplacedPersonTutorialSettings.asset" ) ;
var tutorial = ScriptableObject . CreateInstance < ShrinkTutorialData > ( ) ;
tutorial . tutorialId = "replaced-person.first-match" ;
tutorial . steps = new List < ShrinkTutorialStep >
{
Step ( "rp.normal-hand" , "rp.card-selected" , "选择一张普通牌。牌面顺序会从左到右继承到结束牌。" , ShrinkTutorialDialogAnchor . Top ) ,
Step ( "rp.end-hand" , "rp.end-selected" , "选择结束牌。它提供三个效果槽,并锁定本次提交。" , ShrinkTutorialDialogAnchor . Top ) ,
Step ( "rp.matchup" , "rp.matchup-submitted" , "后手能看到先手牌序。对齐克制关系后再锁定。" , ShrinkTutorialDialogAnchor . Bottom ) ,
Step ( "rp.reroll" , "rp.reroll-marked" , "标记一个效果槽;初次骰点不高于 2 时会自动重掷一次。" , ShrinkTutorialDialogAnchor . Top )
} ;
AssetDatabase . CreateAsset ( tutorial , ResourceRoot + "/ReplacedPersonFirstMatchTutorial.asset" ) ;
var database = ScriptableObject . CreateInstance < ShrinkTutorialDatabase > ( ) ;
var serialized = new SerializedObject ( database ) ; var tutorials = serialized . FindProperty ( "tutorials" ) ; tutorials . arraySize = 1 ; tutorials . GetArrayElementAtIndex ( 0 ) . objectReferenceValue = tutorial ; serialized . ApplyModifiedPropertiesWithoutUndo ( ) ;
AssetDatabase . CreateAsset ( database , ResourceRoot + "/ReplacedPersonTutorialDatabase.asset" ) ;
var settings = ScriptableObject . CreateInstance < ShrinkTutorialSettings > ( ) ; settings . database = database ; settings . playerPrefsStorageKey = "ReplacedPerson.Tutorial" ; settings . showSkipButton = true ; settings . canvasSortingOrder = 5000 ;
settings . name = "ReplacedPersonTutorialSettings" ;
AssetDatabase . CreateAsset ( settings , ResourceRoot + "/ReplacedPersonTutorialSettings.asset" ) ;
}
private static ShrinkTutorialStep Step ( string anchor , string signal , string text , ShrinkTutorialDialogAnchor dialogAnchor ) = > new ( )
{
targetMode = ShrinkTutorialTargetMode . AnchorId ,
anchorId = anchor ,
completeCondition = ShrinkTutorialCompleteCondition . CustomEvent ,
customEventName = signal ,
fallbackText = text ,
dialogAnchor = dialogAnchor ,
maskShape = ShrinkTutorialMaskShape . Rect ,
maskPadding = 10 ,
waitForTarget = true ,
waitTimeout = 3
} ;
private static void BuildScene ( )
{
var scene = EditorSceneManager . NewScene ( NewSceneSetup . EmptyScene , NewSceneMode . Single ) ;
var prefab = AssetDatabase . LoadAssetAtPath < GameObject > ( DemoPrefabPath ) ? ? throw new InvalidOperationException ( "Demo prefab missing." ) ;
var instance = ( GameObject ) PrefabUtility . InstantiatePrefab ( prefab , scene ) ; instance . name = "ReplacedPersonDemo" ;
var cameraObject = new GameObject ( "Main Camera" , typeof ( Camera ) , typeof ( AudioListener ) ) ;
cameraObject . tag = "MainCamera" ;
cameraObject . transform . position = new Vector3 ( 0 , 0 , - 10 ) ;
var camera = cameraObject . GetComponent < Camera > ( ) ;
camera . clearFlags = CameraClearFlags . SolidColor ;
camera . backgroundColor = new Color ( . 02f , . 025f , . 03f , 1 ) ;
camera . cullingMask = 0 ;
camera . orthographic = true ;
camera . depth = - 100 ;
new GameObject ( "EventSystem" , typeof ( EventSystem ) , typeof ( StandaloneInputModule ) ) ;
EditorSceneManager . SaveScene ( scene , ScenePath ) ;
}
private static void AddToBuildSettings ( )
{
var scenes = EditorBuildSettings . scenes . ToList ( ) ;
var existing = scenes . FindIndex ( value = > value . path = = ScenePath ) ;
if ( existing > = 0 ) scenes [ existing ] = new EditorBuildSettingsScene ( ScenePath , true ) ;
else scenes . Add ( new EditorBuildSettingsScene ( ScenePath , true ) ) ;
EditorBuildSettings . scenes = scenes . ToArray ( ) ;
}
private static void DeleteAssetIfExists ( string path ) { if ( AssetDatabase . LoadMainAssetAtPath ( path ) ! = null ) AssetDatabase . DeleteAsset ( path ) ; }
}
}