Angepasste Garage, verbessertes Icon Asset system, range der türme vorschau beim ziehen

This commit is contained in:
PBM2H25BEB committed 2026-11-05 07:52:47 +01:00
1 parent 5486444dde
commit 5af8655c97
59 files changed
+12079 -457

No files matched your search

@@ -38,7 +38,7 @@ public static class HandbookInspectorValidation
var menu=UnityEngine.Object.FindFirstObjectByType<BuildMenuController>();int[] expected={6,6,3,4};for(int i=0;i<4;i++){menu.SelectCategory(i);if(menu.GetComponentsInChildren<TowerCardView>().Length!=expected[i])throw new Exception("Category count incorrect: "+i);}
var p=root.GetComponent<TestPlacementController>();var catalog=AssetDatabase.LoadAssetAtPath<HandbookTowerCatalog>("Assets/HandbookData/TestTurmkatalog.asset");var gun=catalog.towers.First(t=>t.id=="ammo_0");var energy=catalog.towers.First(t=>t.id=="energy_0");
if(!p.Backend.ExecuteBuild(new BuildRequest(gun,new Vector3(3.5f,0,0))).Allowed||!p.Backend.ExecuteBuild(new BuildRequest(energy,new Vector3(0,3.5f,0))).Allowed)throw new Exception("Base placement failed");
var obj=p.Backend.BuiltObjects[0].instance;var renderer=obj.GetComponent<SpriteRenderer>();if(!renderer || !renderer.sprite || (gun.baseSprite && renderer.sprite!=gun.baseSprite) || renderer.bounds.size.x>0.8f)throw new Exception("Base sprite or placeholder scaling incorrect");
var obj=p.Backend.BuiltObjects[0].instance;var renderer=obj.GetComponent<SpriteRenderer>();if(!renderer || !renderer.sprite || renderer.bounds.size.x>0.8f)throw new Exception("Base sprite or placeholder scaling incorrect");
int before=p.Backend.Energy;if(!p.Backend.ExecuteUpgrade(p.Backend.BuiltObjects[0].instanceId).Allowed||p.Backend.Energy>=before)throw new Exception("Upgrade failed");
c.Open(2);var upgrades=UnityEngine.Object.FindFirstObjectByType<HandbookUpgradesController>();if(upgrades.VisibleUpgradeCount!=2)throw new Exception("Upgrade listing failed");
c.Open(3);var index=UnityEngine.Object.FindFirstObjectByType<HandbookIndexController>();if(index.VisibleEntryCount!=24)throw new Exception("All entries not displayed");int[] counts={24,12,3,4,5};for(int i=0;i<5;i++){index.SelectFilter(i);if(index.VisibleEntryCount!=counts[i])throw new Exception("Index filter count incorrect");}
@@ -0,0 +1,23 @@
using System;
using System.Linq;
using UnityEngine;
using UnityEditor;
using UnityEditor.SceneManagement;
using DepthsOfGreed.HandbookUI;
public static class PreviewLayersValidation
{
static void Check(bool ok,string message){if(!ok)throw new Exception(message);}
public static void Validate(){GameObject root=null,source=null;Texture2D texture=null;Sprite a=null,b=null;HandbookTowerEntry t=null;try{
EditorSceneManager.OpenScene("Assets/Scenes/Tim_Test_UI.unity");
root=new GameObject("TEMP_LayersTest");var preview=root.AddComponent<LayeredTowerPreview>();texture=new Texture2D(32,32);a=Sprite.Create(texture,new Rect(0,0,32,32),new Vector2(.25f,.75f),32);b=Sprite.Create(texture,new Rect(0,0,32,32),new Vector2(.8f,.1f),32);
t=ScriptableObject.CreateInstance<HandbookTowerEntry>();t.id="layer-test";t.previewLayers=new[]{new PreviewSpriteLayer{sprite=a},new PreviewSpriteLayer{sprite=b,rotateWithAim=false}};preview.Show(t,a);
var layers=root.GetComponentsInChildren<SpriteRenderer>();Check(layers.Length==2 && layers[0].sortingOrder>layers[1].sortingOrder,"Front-to-back order");Check(layers[0].transform.position==layers[1].transform.position,"Different sprite pivots share mouse anchor");Check(layers[0].transform.localScale==layers[1].transform.localScale,"Shared scale preserves proportions");
t.baseShape=BaseShape.Rectangle;t.baseSize=new Vector2(3,1);t.previewLayers=new PreviewSpriteLayer[2];preview.Show(t,a);Check(Mathf.Abs(layers[0].transform.localScale.x/layers[0].transform.localScale.y-3)<.01f,"Rectangular fallback retains base proportions");t.baseShape=BaseShape.Circle;
var gameplay=root.AddComponent<PrototypeHandbookGameplay>();gameplay.Initialize(root.transform,Vector3.zero,3,4,null);source=new GameObject("ActualGameplayPrefab",typeof(SpriteRenderer));source.transform.localScale=Vector3.one*1.2f;t.gameplayPrefab=source;t.baseRadius=.2f;
Check(gameplay.ExecuteBuild(new BuildRequest(t,new Vector3(3.5f,0,0),30)).Allowed,"Prefab placement");Check(gameplay.BuiltObjects[0].instance.transform.localScale==source.transform.localScale,"Assigned prefab instantiated without rescaling");
Check(Shader.Find("Handbook/ArenaRange"),"Arena clipping shader available");
UnityEngine.Object.DestroyImmediate(root);UnityEngine.Object.DestroyImmediate(source);UnityEngine.Object.DestroyImmediate(t);UnityEngine.Object.DestroyImmediate(a);UnityEngine.Object.DestroyImmediate(b);UnityEngine.Object.DestroyImmediate(texture);Debug.Log("PREVIEW_LAYERS_VALIDATION_OK");EditorApplication.Exit(0);
}catch(Exception e){Debug.LogException(e);EditorApplication.Exit(1);}}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 1efe7a0c358869143800296d28875a94
@@ -31,7 +31,7 @@ public static class SupplyBasementValidation
{
c=UnityEngine.Object.FindFirstObjectByType<SupplyBasementController>();
Assert(c && c.connectionButtons.Length==8 && c.priorityButtons.Length==3,"Authored references");
Assert(!c.basementView.activeSelf && c.handbookRoot.activeSelf,"Starts on tower floor");
Assert((c.roomView ? !c.roomView.IsVisible && c.basementView.activeSelf : !c.basementView.activeSelf) && c.handbookRoot.activeSelf,"Starts on tower floor; room remains active");
count=Count(c);bookScale=c.handbook.transform.Find("OpenBook").localScale;
var catalog=AssetDatabase.LoadAssetAtPath<HandbookTowerCatalog>("Assets/HandbookData/TestTurmkatalog.asset");
var result=c.placement.Backend.ExecuteBuild(new BuildRequest(catalog.towers.First(t=>t.id=="ammo_0"),new Vector3(3.5f,0,0)));
@@ -73,17 +73,19 @@ public static class SupplyBasementValidation
catch(Exception e){EditorApplication.update-=run;Debug.LogException(e);EditorApplication.Exit(1);}
};EditorApplication.update+=run;
}
static int Count(SupplyBasementController c)=>c.GetComponentInParent<Canvas>().GetComponentsInChildren<RectTransform>(true).Count(t=>!t.name.StartsWith("TMP"));
static int Count(SupplyBasementController c)=>c.GetComponentInParent<Canvas>().GetComponentsInChildren<RectTransform>(true).Concat(c.basementView.GetComponentsInChildren<RectTransform>(true)).Distinct().Count(t=>!t.name.StartsWith("TMP"));
static void Assert(bool condition,string message){if(!condition)throw new Exception(message);}
static void Capture(SupplyBasementController c)
{
var camera=Camera.main;var canvas=c.GetComponentInParent<Canvas>();
var camera=c.roomView?c.roomView.roomCamera:Camera.main;var canvas=c.GetComponentInParent<Canvas>();
var rt=new RenderTexture(1920,1080,24);rt.Create();camera.targetTexture=rt;camera.aspect=1920f/1080;
canvas.renderMode=RenderMode.ScreenSpaceCamera;canvas.worldCamera=camera;canvas.planeDistance=1;canvas.sortingOrder=200;
Canvas.ForceUpdateCanvases();RenderPipeline.SubmitRenderRequest(camera,new UniversalRenderPipeline.SingleCameraRequest{destination=rt});
var previous=RenderTexture.active;RenderTexture.active=rt;var tex=new Texture2D(1920,1080,TextureFormat.RGB24,false);tex.ReadPixels(new Rect(0,0,1920,1080),0,0);tex.Apply();RenderTexture.active=previous;
var folder=Path.Combine(Path.GetDirectoryName(Application.dataPath),"Temp","HandbookValidation");
var folder=Path.Combine(Path.GetDirectoryName(Application.dataPath),"Logs","HandbookValidation");
Directory.CreateDirectory(folder);File.WriteAllBytes(Path.Combine(folder,"basement-preview.png"),tex.EncodeToPNG());
UnityEngine.Object.DestroyImmediate(tex);camera.targetTexture=null;rt.Release();UnityEngine.Object.DestroyImmediate(rt);canvas.renderMode=RenderMode.ScreenSpaceOverlay;
}
}
@@ -0,0 +1,112 @@
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
using UnityEngine.EventSystems;
using DepthsOfGreed.HandbookUI;
[InitializeOnLoad]
public static class UINotesValidation
{
static UINotesValidation(){if(SessionState.GetBool("NotesValidation",false))EditorApplication.playModeStateChanged+=Ready;}
public static void Validate(){EditorSceneManager.OpenScene("Assets/Scenes/Tim_Test_UI.unity");SessionState.SetBool("NotesValidation",true);EditorApplication.playModeStateChanged+=Ready;EditorApplication.EnterPlaymode();}
static void Check(bool condition,string message){if(!condition)throw new Exception(message);}
static void Ready(PlayModeStateChange state)
{
if(state!=PlayModeStateChange.EnteredPlayMode)return;SessionState.SetBool("NotesValidation",false);EditorApplication.playModeStateChanged-=Ready;
int stage=0;double due=EditorApplication.timeSinceStartup+1;SupplyBasementController c=null;HandbookStatisticsController stats=null;SupplyContractTestBackend supply=null;UIOverviewTestBackend battle=null;BasementBattleOverview monitor=null;HandbookTowerCatalog catalog=null;float truckX=0;Camera main=null;
EditorApplication.CallbackFunction run=null;run=()=>
{
if(EditorApplication.timeSinceStartup<due)return;
try
{
if(stage==0)
{
c=UnityEngine.Object.FindFirstObjectByType<SupplyBasementController>();main=Camera.main;catalog=AssetDatabase.LoadAssetAtPath<HandbookTowerCatalog>("Assets/HandbookData/TestTurmkatalog.asset");
Check(c.roomView && c.roomView.roomCanvas.renderMode==RenderMode.WorldSpace && c.roomView.roomCanvas.transform.position.x>30,"Room exists outside game area");
Check(c.basementView.activeInHierarchy && !c.roomView.IsVisible && main.enabled,"Room active while hidden, main camera active");
var combat=new CombatDefinition{range=5,minimumRange=1.5f,rotation=RotationMode.Free,attackShape=AttackShape.Cone,coneAngle=60,forwardAngleOffset=90};
Check(AttackRangeGeometry.CoverageAngle(combat)==360,"Rotating cone covers full circle");
Check(!AttackRangeGeometry.CanReach(combat,Vector2.zero,Vector2.up,0) && AttackRangeGeometry.CanReach(combat,Vector2.zero,Vector2.up*3,0),"Minimum range respected");
combat.coverage=AttackCoverage.Sector;Check(AttackRangeGeometry.CanReach(combat,Vector2.zero,Vector2.up*3,0) && !AttackRangeGeometry.CanReach(combat,Vector2.zero,Vector2.right*3,0),"Cone angle and orientation");
combat.coverage=AttackCoverage.Circle;combat.unlimitedRange=true;Check(AttackRangeGeometry.CanReach(combat,Vector2.zero,Vector2.right*1000,0),"Unlimited range has no false maximum");
truckX=c.truck.anchoredPosition.x;
typeof(SupplyBasementController).GetField("truckPauseUntil",System.Reflection.BindingFlags.Instance|System.Reflection.BindingFlags.NonPublic).SetValue(c,0f);
stage=1;due=EditorApplication.timeSinceStartup+.8;
}
else if(stage==1)
{
Check(Mathf.Abs(c.truck.anchoredPosition.x-truckX)>.1f,"Truck preview continues outside basement");
var howitzer=catalog.towers.First(t=>t.id=="ammo_4");Check(howitzer.combat.minimumRange>0,"Howitzer minimum seeded");
var range=UnityEngine.Object.FindFirstObjectByType<AttackRangePreview>(FindObjectsInactive.Include);
c.handbook.Open(0);Check(c.placement.Begin(howitzer,main.WorldToScreenPoint(new Vector3(3.5f,0,0))),"Begin range preview");
Check(range.gameObject.activeInHierarchy && range.minimumEdge.gameObject.activeSelf && range.outerEdge.loop,"Annular placement preview");
stage=2;due=EditorApplication.timeSinceStartup+.7;
}
else if(stage==2)
{
c.placement.Move(main.WorldToScreenPoint(new Vector3(3.5f,0,0)));
Capture(main,c.handbook.GetComponentInParent<Canvas>(),c.GetComponentInParent<Canvas>(),"range-preview.png");c.placement.Cancel();
supply=new GameObject("TEST_ONLY_Supply").AddComponent<SupplyContractTestBackend>();
supply.state.channels=new[]{new SupplyChannelState{kind=SupplyKind.Physical,storage=180,storageCapacity=300,buffer=60,bufferCapacity=100,consumedPerSecond=2}};
supply.state.towers=new[]{new TowerSupplyState{type=catalog.towers.First(t=>t.id=="ammo_0"),instanceId=811,magazine=12,magazineCapacity=30,consumedPerSecond=2}};
c.BindSupplyBackend(supply);c.handbook.Open(1);stats=UnityEngine.Object.FindFirstObjectByType<HandbookStatisticsController>();stats.Refresh();
Check(stats.channelRates[0].text.Contains("120 / min") && stats.channelTimes[0].text.Contains("00:30"),"Measured rates and reserve estimate");
Check(stats.towerNames[0].text.Contains("811") && stats.towerMagazines[0].text=="12 / 30","Per-instance magazine and rate");
stats.SetUnit(false);stats.Refresh();Check(stats.channelRates[0].text.Contains("2 / s"),"Rate unit conversion");
Check(HandbookStatisticsController.FormatRate(null,true)=="—" && HandbookStatisticsController.Estimate(60,0)=="kein Verbrauch","Unknown differs from zero");
stats.SetUnit(true);stats.Refresh();stage=3;due=EditorApplication.timeSinceStartup+.4;
}
else if(stage==3)
{
Capture(main,c.handbook.GetComponentInParent<Canvas>(),c.GetComponentInParent<Canvas>(),"statistics-preview.png");
battle=new GameObject("TEST_ONLY_CombatOverview").AddComponent<UIOverviewTestBackend>();
battle.state.floorName="Ebene 2";battle.state.gates=new[]{new GateOverviewState{direction=GateDirection.North,health=100,maxHealth=100},new GateOverviewState{direction=GateDirection.East,health=80,maxHealth=100,isOpen=true},new GateOverviewState{direction=GateDirection.South,health=100,maxHealth=100},new GateOverviewState{direction=GateDirection.West,health=100,maxHealth=100}};
monitor=UnityEngine.Object.FindFirstObjectByType<BasementBattleOverview>();monitor.BindCombatBackend(battle);monitor.SendMessage("Update");
Check(monitor.gateLabels[0].text.Contains("100") && monitor.gateLabels[1].text.Contains("offen"),"Gate state and open gate separate");
battle.state.gates[0].health=20;battle.state.gates[0].underAttack=true;battle.state.gates[0].hitSequence++;
c.UseLift();stage=4;due=EditorApplication.timeSinceStartup+1.7;
}
else if(stage==4)
{
Check(c.IsInBasement && c.roomView.IsVisible && c.basementView.activeInHierarchy && c.roomView.roomCamera.enabled && main.enabled,"Only view changes, both rooms stay active");
Check(monitor.gateLabels[0].text.Contains("20") && monitor.gateLabels[0].text.Contains("!"),"Gate damage appears in basement");
Check(monitor.gateIcons[0].color.r>monitor.gateIcons[0].color.g,"Critical gate warning color");
var pointer=new PointerEventData(EventSystem.current){position=RectTransformUtility.WorldToScreenPoint(c.roomView.roomCamera,c.connectionButtons[0].transform.position)};
var hits=new List<RaycastResult>();EventSystem.current.RaycastAll(pointer,hits);
Check(hits.Any(hit=>hit.gameObject==c.connectionButtons[0].gameObject),"World-space room buttons receive UI pointer hits");
c.roomView.roomCamera.aspect=2.5f;c.roomView.SendMessage("LateUpdate");
Check(((RectTransform)c.roomView.roomCanvas.transform).rect.width>=2699,"Room expands to wide screen");
Capture(c.roomView.roomCamera,null,c.GetComponentInParent<Canvas>(),"basement-monitor-preview.png");
c.UseLift();stage=5;due=EditorApplication.timeSinceStartup+1.7;
}
else
{
Check(!c.IsInBasement && !c.roomView.roomCamera.enabled && c.basementView.activeInHierarchy && main.enabled,"Return keeps room and main simulation active");
Debug.Log("UI_NOTES_VALIDATION_OK: separate active room, background preview, attack/minimum/sector ranges, statistics, unit conversion and gate alerts.");EditorApplication.update-=run;EditorApplication.Exit(0);
}
}
catch(Exception e){EditorApplication.update-=run;Debug.LogException(e);EditorApplication.Exit(1);}
};EditorApplication.update+=run;
}
static void Capture(Camera camera,Canvas book,Canvas lift,string filename)
{
if(SystemInfo.graphicsDeviceType==GraphicsDeviceType.Null)return;
var target=new RenderTexture(1920,1080,24);target.Create();camera.targetTexture=target;camera.aspect=1920f/1080;
var room=camera.GetComponentInParent<BasementRoomView>();if(room)room.SendMessage("LateUpdate");
int oldMask=camera.cullingMask;camera.cullingMask|=1;
if(book){book.renderMode=RenderMode.ScreenSpaceCamera;book.worldCamera=camera;book.planeDistance=1;book.sortingOrder=100;}
lift.renderMode=RenderMode.ScreenSpaceCamera;lift.worldCamera=camera;lift.planeDistance=.5f;lift.sortingOrder=200;
Canvas.ForceUpdateCanvases();RenderPipeline.SubmitRenderRequest(camera,new UniversalRenderPipeline.SingleCameraRequest{destination=target});
var previous=RenderTexture.active;RenderTexture.active=target;var texture=new Texture2D(1920,1080,TextureFormat.RGB24,false);texture.ReadPixels(new Rect(0,0,1920,1080),0,0);texture.Apply();RenderTexture.active=previous;
var folder=Path.Combine(Path.GetDirectoryName(Application.dataPath),"Logs","HandbookValidation");Directory.CreateDirectory(folder);File.WriteAllBytes(Path.Combine(folder,filename),texture.EncodeToPNG());
UnityEngine.Object.DestroyImmediate(texture);camera.targetTexture=null;camera.cullingMask=oldMask;target.Release();UnityEngine.Object.DestroyImmediate(target);if(book)book.renderMode=RenderMode.ScreenSpaceOverlay;lift.renderMode=RenderMode.ScreenSpaceOverlay;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 1f370da3130a64348937147e95cf6d8f
@@ -0,0 +1,44 @@
using System;
using System.Linq;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using DepthsOfGreed.HandbookUI;
public static class WallBuildAreaValidation
{
static void Check(bool condition,string message){if(!condition)throw new Exception(message);}
public static void Validate()
{
GameObject test=null;HandbookTowerEntry tower=null;
try
{
EditorSceneManager.OpenScene("Assets/Scenes/Tim_Test_UI.unity");
var area=UnityEngine.Object.FindFirstObjectByType<WallBuildArea>();
Check(area && area.TryGetRadii(out _,out _,out _),"Scene has configured build boundaries");
Check(new SerializedObject(UnityEngine.Object.FindFirstObjectByType<TestPlacementController>()).FindProperty("buildArea").objectReferenceValue==area,"Inspector reference wired");
test=new GameObject("TEMP_WallAreaValidation");var settings=test.AddComponent<WallBuildArea>();
settings.innerBoundarySprite=area.innerBoundarySprite;settings.outerBoundarySprite=area.outerBoundarySprite;settings.boundarySource=WallBoundarySource.SpriteRenderers;
string oldInner=settings.innerBoundarySprite.name,oldOuter=settings.outerBoundarySprite.name;
try
{
settings.innerBoundarySprite.name="ArbitraryInnerObject";settings.outerBoundarySprite.name="ArbitraryOuterObject";
Check(settings.TryGetRadii(out float a,out float b,out _) && b>a,"Sprite assignment works after renaming");
}
finally{settings.innerBoundarySprite.name=oldInner;settings.outerBoundarySprite.name=oldOuter;}
settings.boundarySource=WallBoundarySource.CustomRadii;settings.innerBoundarySprite=null;settings.outerBoundarySprite=null;settings.innerRadius=1;settings.outerRadius=2;
settings.transform.localScale=Vector3.one*5;
Check(settings.TryGetRadii(out float inner,out float outer,out _) && inner==1 && outer==2,"Custom world radii ignore missing sprites and transform scale");
var prototype=test.AddComponent<PrototypeHandbookGameplay>();prototype.Initialize(test.transform,Vector3.zero,inner,outer,null);
var catalog=AssetDatabase.LoadAssetAtPath<HandbookTowerCatalog>("Assets/HandbookData/TestTurmkatalog.asset");tower=UnityEngine.Object.Instantiate(catalog.towers.First(t=>t.id=="ammo_0"));tower.baseShape=BaseShape.Circle;tower.baseRadius=.2f;
Check(prototype.CheckBuild(new BuildRequest(tower,new Vector3(1.5f,0,0))).Allowed,"Custom ring accepts fitting base");
Check(!prototype.CheckBuild(new BuildRequest(tower,new Vector3(1.1f,0,0))).Allowed,"Base must clear inner edge");
Check(!prototype.CheckBuild(new BuildRequest(tower,new Vector3(1.9f,0,0))).Allowed,"Base must clear outer edge");
settings.innerRadius=2;Check(!settings.TryGetRadii(out _,out _,out _),"Invalid custom ring rejected");
settings.boundarySource=WallBoundarySource.SpriteRenderers;Check(!settings.TryGetRadii(out _,out _,out _),"Missing sprite references rejected");
UnityEngine.Object.DestroyImmediate(tower);UnityEngine.Object.DestroyImmediate(test);
Debug.Log("WALL_AREA_VALIDATION_OK: inspector binding, renamed sprites, custom radii, invalid values, placement and edge clearance.");EditorApplication.Exit(0);
}
catch(Exception e){if(tower)UnityEngine.Object.DestroyImmediate(tower);if(test)UnityEngine.Object.DestroyImmediate(test);Debug.LogException(e);EditorApplication.Exit(1);}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 8f68c3932a0690f4ca3434bf439c096d