using Unity.Collections; using Unity.Entities; using Unity.Mathematics; using Unity.Rendering; using Unity.Transforms; using UnityEngine; using UnityEngine.Rendering; namespace HappyForest.Prototypen { // Einziges MonoBehaviour im DOTS-Setup. Aufgabe: Input lesen (Space/M/C/K), // die Entity-World initialisieren und Pillen-Entities spawnen/löschen. // Bewegung + Kollision laufen komplett in ISystem/Burst (siehe PilleMovementSystem // und PilleCollisionSystem) - hier passiert keine Simulationslogik mehr. public class PilleDotsBootstrap : MonoBehaviour { [Header("Rendering (Pflichtfelder)")] [SerializeField] private Mesh pilleMesh; [SerializeField] private Material pilleMaterial; [Header("Einstellungen")] [SerializeField] private float speed = 5f; [SerializeField] private float pilleRadius = 0.5f; [SerializeField] private float spawnHeight = 1f; [SerializeField] private string wallTag = "Wall"; [SerializeField] private float actionDelay = 0.05f; private EntityManager entityManager; private Entity pilleTemplateEntity; private EntityQuery pilleQuery; private EntityQuery configQuery; private int pilleCount = 0; private float lastActionTime = 0f; private bool collisionEnabledCache = false; private Texture2D backgroundTexture; void Start() { if (pilleMesh == null || pilleMaterial == null) { Debug.LogError("❌ PilleDotsBootstrap: Mesh oder Material im Inspector zuweisen!"); enabled = false; return; } entityManager = World.DefaultGameObjectInjectionWorld.EntityManager; SetupSimulationConfig(); SetupPilleTemplate(); pilleQuery = entityManager.CreateEntityQuery(typeof(PilleComponent), typeof(LocalTransform)); configQuery = entityManager.CreateEntityQuery(typeof(PilleSimulationConfig)); backgroundTexture = MakeTex(new Color(0f, 0f, 0f, 0.65f)); Debug.Log("✅ PilleDotsBootstrap bereit! Space=Spawn M=Delete C=Spawn100 K=Kollision Toggle"); } void SetupSimulationConfig() { var wallBounds = ComputeWallBounds(); float margin = pilleRadius + 0.1f; var configEntity = entityManager.CreateEntity(typeof(PilleSimulationConfig)); entityManager.SetComponentData(configEntity, new PilleSimulationConfig { BoundsMin = new float3(wallBounds.min.x + margin, spawnHeight, wallBounds.min.z + margin), BoundsMax = new float3(wallBounds.max.x - margin, spawnHeight, wallBounds.max.z - margin), PlayHeight = spawnHeight, PilleRadius = pilleRadius, PilleCollisionEnabled = false }); } Bounds ComputeWallBounds() { var walls = GameObject.FindGameObjectsWithTag(wallTag); if (walls.Length == 0) { Debug.LogWarning($"⚠️ Keine GameObjects mit Tag '{wallTag}' gefunden. Nutze Standard-Grenzen (50x100)."); return new Bounds(Vector3.zero, new Vector3(50, 10, 100)); } Bounds combined = default; bool first = true; foreach (var wall in walls) { var rend = wall.GetComponent(); if (rend == null) continue; if (first) { combined = rend.bounds; first = false; } else combined.Encapsulate(rend.bounds); } Debug.Log($"✅ Feld-Grenzen aus '{wallTag}'-Objekten berechnet: Min={combined.min} Max={combined.max}"); return combined; } void SetupPilleTemplate() { pilleTemplateEntity = entityManager.CreateEntity( typeof(PilleComponent), typeof(LocalTransform), typeof(LocalToWorld)); var desc = new RenderMeshDescription(ShadowCastingMode.On, receiveShadows: true); var renderMeshArray = new RenderMeshArray(new[] { pilleMaterial }, new[] { pilleMesh }); RenderMeshUtility.AddComponents( pilleTemplateEntity, entityManager, desc, renderMeshArray, MaterialMeshInfo.FromRenderMeshArrayIndices(0, 0)); entityManager.SetComponentData(pilleTemplateEntity, LocalTransform.FromPosition(new float3(0, spawnHeight, 0))); entityManager.SetComponentData(pilleTemplateEntity, new PilleComponent { Velocity = new float3(1, 0, 0), Speed = speed }); // WICHTIG: Prefab-Tag, damit dieses Template NICHT von den Queries/Systemen // (Bewegung, Kollision, Zählung) erfasst wird. Instantiate() entfernt den Tag // automatisch bei jeder Kopie. entityManager.AddComponent(pilleTemplateEntity); } void Update() { if (Input.GetKeyDown(KeyCode.K)) { ToggleCollision(); } if (Time.time - lastActionTime < actionDelay) return; if (Input.GetKey(KeyCode.Space)) { SpawnPille(); lastActionTime = Time.time; } else if (Input.GetKey(KeyCode.M)) { DeletePille(); lastActionTime = Time.time; } else if (Input.GetKey(KeyCode.C)) { for (int i = 0; i < 100; i++) SpawnPille(); lastActionTime = Time.time; } } void SpawnPille() { float3 spawnPos = new float3(0, spawnHeight, 0); int existing = pilleQuery.CalculateEntityCount(); if (existing > 0) { var transforms = pilleQuery.ToComponentDataArray(Allocator.Temp); int idx = UnityEngine.Random.Range(0, transforms.Length); spawnPos = transforms[idx].Position; transforms.Dispose(); } Entity newPille = entityManager.Instantiate(pilleTemplateEntity); float angle = UnityEngine.Random.Range(0f, math.PI * 2f); var velocity = new float3(math.cos(angle), 0, math.sin(angle)); entityManager.SetComponentData(newPille, LocalTransform.FromPosition(spawnPos)); entityManager.SetComponentData(newPille, new PilleComponent { Velocity = velocity, Speed = speed }); pilleCount++; } void DeletePille() { if (pilleQuery.CalculateEntityCount() == 0) return; var entities = pilleQuery.ToEntityArray(Allocator.Temp); entityManager.DestroyEntity(entities[0]); entities.Dispose(); pilleCount--; } void ToggleCollision() { if (configQuery.CalculateEntityCount() == 0) return; var configEntity = configQuery.GetSingletonEntity(); var cfg = entityManager.GetComponentData(configEntity); cfg.PilleCollisionEnabled = !cfg.PilleCollisionEnabled; entityManager.SetComponentData(configEntity, cfg); collisionEnabledCache = cfg.PilleCollisionEnabled; } Texture2D MakeTex(Color color) { var tex = new Texture2D(1, 1); tex.SetPixel(0, 0, color); tex.Apply(); return tex; } void OnGUI() { GUI.DrawTexture(new Rect(5, 5, 430, 90), backgroundTexture); GUI.color = Color.white; GUI.skin.label.fontSize = 18; GUI.Label(new Rect(15, 10, 410, 25), $"Pillen: {pilleCount} | FPS: {(int)(1f / Time.deltaTime)}"); GUI.Label(new Rect(15, 35, 410, 25), $"Pille-Kollision (K): {(collisionEnabledCache ? "AN" : "AUS")}"); GUI.Label(new Rect(15, 60, 410, 25), "Space=Spawn M=Delete C=Spawn100 K=Kollision Toggle"); } void OnDestroy() { if (backgroundTexture != null) Destroy(backgroundTexture); } } }