Stellen Sie sich vor, Sie spielen ein packendes Videospiel oder arbeiten in einer komplexen Anwendung, und plötzlich benötigen Sie schnellen Zugriff auf eine Reihe von Aktionen. Anstatt durch verschachtelte Dropdown-Menüs zu navigieren, erscheint direkt unter Ihrem Mauszeiger ein elegantes, kreisförmiges Menü, das Ihnen auf einen Blick alle Optionen präsentiert. Willkommen in der Welt der Pie Menus – oder wie wir sie im Deutschen liebevoll nennen: Kuchenmenüs. Diese intuitiven und schnellen Benutzeroberflächen (UIs) sind nicht nur in Spielen wie World of Warcraft oder Photoshop beliebt, sondern finden auch immer mehr Anwendung in professionellen Tools.
In diesem umfassenden Leitfaden erfahren Sie, wie Sie ein dynamisches Kuchenmenü in Unity erstellen. Wir tauchen tief in die Materie ein, von der grundlegenden UI-Struktur bis hin zu komplexen Skripten, die Ihr Menü intelligent auf Benutzereingaben reagieren lassen. Egal, ob Sie ein erfahrener Unity-Entwickler oder ein neugieriger Anfänger sind, dieser Artikel wird Ihnen die Werkzeuge an die Hand geben, um Ihre Benutzeroberfläche auf das nächste Level zu heben. Machen Sie sich bereit, die Art und Weise, wie Benutzer mit Ihrer Anwendung interagieren, zu revolutionieren!
Warum ist Unity die perfekte Plattform, um ein solches interaktives Menü zu realisieren? Unitys mächtiges UI-System, das auf dem Canvas-Konzept basiert, bietet eine flexible und leistungsstarke Grundlage. Gepaart mit der Möglichkeit, eigene C#-Skripte zu schreiben, können Sie nahezu jede gewünschte Interaktion umsetzen. Von der visuellen Gestaltung der einzelnen „Kuchenstücke” bis zur dynamischen Anpassung der Menüpunkte basierend auf dem Spielzustand oder Benutzeraktionen – Unity macht es möglich. Die integrierte Entwicklungsumgebung (IDE) und das intuitive Drag-and-Drop-System beschleunigen den Entwicklungsprozess erheblich.
Bevor wir ins Detail gehen, hier eine kurze Auffrischung der Konzepte, die wir benötigen werden:
* **Unity UI:** Verständnis von Canvas, RectTransform, Image, Text und Button-Komponenten.
* **C# Programmierung:** Grundlagen der Objektorientierung, Listen, Ereignisse.
* **Vektormathematik:** Insbesondere das Verständnis von Vektoren, Winkeln und der Funktion `Mathf.Atan2` zur Berechnung von Winkeln aus Koordinaten.
* **Unity Event System:** Wie man auf Mausklicks, Hover-Effekte und andere Interaktionen reagiert.
Lassen Sie uns nun Schritt für Schritt Ihr eigenes dynamisches Pie Menu aufbauen.
I. Struktur und Design des Pie Menus (UI-Setup)
Der erste Schritt ist die visuelle Gestaltung und die Hierarchie Ihrer UI-Elemente.
1. **Das Canvas erstellen:**
Jede UI in Unity benötigt einen Canvas. Gehen Sie in der Hierarchie auf `Rechtsklick -> UI -> Canvas`. Stellen Sie sicher, dass der Render Mode auf `Screen Space – Overlay` eingestellt ist, wenn Ihr Menü immer im Vordergrund erscheinen soll. Für 3D-Anwendungen oder spezifische Kamera-Setups können Sie auch `Screen Space – Camera` oder `World Space` wählen. Für ein einfaches, bildschirmfüllendes Overlay ist `Screen Space – Overlay` ideal.
2. **Das leere Game Object als Pie Menu Root:**
Erstellen Sie innerhalb des Canvas ein leeres Game Object (`Rechtsklick auf Canvas -> Create Empty`). Nennen Sie es beispielsweise „PieMenuRoot”. Dieses Objekt wird der zentrale Ankerpunkt für Ihr Menü sein. Setzen Sie sein RectTransform auf die Mitte des Bildschirms (`Pos X/Y/Z` auf 0, Anker auf `Middle Center`). Dies wird das Elternobjekt sein, das wir aktivieren oder deaktivieren, um das gesamte Menü ein- oder auszublenden.
3. **Das Pie Slice Prefab:**
Jedes einzelne „Kuchenstück” – also jeder Menüpunkt – wird eine Instanz eines Prefabs sein. Dies erleichtert die dynamische Erstellung und Verwaltung.
* Erstellen Sie ein neues leeres Game Object unter Ihrem `PieMenuRoot` (nennen Sie es „PieSlicePrefab”).
* Fügen Sie eine `Image`-Komponente hinzu. Für eine einfache visuelle Darstellung können Sie eine einfarbige quadratische Sprite verwenden und die Farbe später anpassen. Wenn Sie ein spezielles „Pie Slice”-Sprite haben, können Sie es hier zuweisen. Stellen Sie den `Image Type` auf `Filled` mit `Radial 360` ein, wenn Sie einen kreisförmigen Füllungseffekt wünschen, der sich dynamisch anpassen lässt. Alternativ verwenden Sie ein einfaches Quadrat und drehen es.
* Fügen Sie eine `Text (TMP)`-Komponente als Kind des „PieSlicePrefab” hinzu (wenn Sie TextMeshPro verwenden; andernfalls `UI/Text`). Positionieren Sie den Text so, dass er am äußeren Rand des Sektors gut lesbar ist.
* Optional können Sie auch ein `Image`-Komponente für ein Icon hinzufügen.
* Fügen Sie dem „PieSlicePrefab” ein leeres Skript namens `PieMenuItem` hinzu. Dieses Skript wird später die Logik für die Interaktion mit diesem spezifischen Sektor enthalten.
* Ziehen Sie das „PieSlicePrefab” aus der Hierarchie in den Project-Tab, um ein Prefab zu erstellen. Löschen Sie es danach aus der Hierarchie.
II. Die Scripting-Logik (C#)
Jetzt kommt der Kern des dynamischen Pie Menus – die C#-Skripte, die alles zum Leben erwecken.
1. **Das `PieMenuController` Skript:**
Erstellen Sie ein neues C#-Skript namens `PieMenuController` und fügen Sie es Ihrem `PieMenuRoot` Game Object hinzu. Dieses Skript wird die Hauptlogik für das gesamte Menü verwalten.
„`csharp
using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
using System; // Für Action
using TMPro; // Für TextMeshPro
public class PieMenuController : MonoBehaviour
{
[Header(„UI Elemente”)]
public GameObject pieSlicePrefab; // Das Prefab für einen Menüsektor
public Transform sliceParent; // Das Elternelement, wo Sektoren instanziiert werden
public Image centerIcon; // Optional: Ein Icon in der Mitte des Menüs
[Header(„Menü Einstellungen”)]
public float radius = 150f; // Radius, auf dem die Sektoren platziert werden
public float slicePadding = 5f; // Abstand zwischen den Sektoren
public float startAngle = 90f; // Startwinkel für den ersten Sektor (0=rechts, 90=oben)
public float hoverScale = 1.1f; // Skalierung beim Hover
public Color hoverColor = Color.yellow; // Farbe beim Hover
private List
private List
private int currentSelectedIndex = -1;
private Camera mainCamera;
// Eine innere Klasse oder Struct, um Daten für jeden Menüpunkt zu speichern
[Serializable]
public class PieMenuItemData
{
public string name;
public Sprite icon;
public Action onClick; // Verwenden Sie Action für flexiblere Callbacks
public PieMenuItemData(string name, Sprite icon, Action onClick)
{
this.name = name;
this.icon = icon;
this.onClick = onClick;
}
}
void Awake()
{
mainCamera = Camera.main;
if (mainCamera == null)
{
Debug.LogError(„No Main Camera found! Please tag your camera as ‘MainCamera’.”);
}
// Menü zu Beginn ausblenden
gameObject.SetActive(false);
}
void Update()
{
if (gameObject.activeSelf && currentSlices.Count > 0)
{
UpdateSelection();
// Beispiel: Menü schliessen, wenn linke Maustaste losgelassen wird
if (Input.GetMouseButtonUp(0))
{
ExecuteSelectedItem();
HideMenu();
}
// Beispiel: Menü schliessen, wenn Escape gedrückt wird
if (Input.GetKeyDown(KeyCode.Escape))
{
HideMenu();
}
}
}
public void ShowMenu(Vector2 screenPosition, List
{
// Positionieren Sie das Menü an der Mausposition
RectTransform rectTransform = GetComponent
rectTransform.position = screenPosition;
currentItemData = items;
GenerateMenu(items);
gameObject.SetActive(true);
currentSelectedIndex = -1; // Zurücksetzen der Auswahl
UpdateSelection(); // Sicherstellen, dass die erste Auswahl korrekt ist
}
public void HideMenu()
{
gameObject.SetActive(false);
ClearMenu();
}
private void ClearMenu()
{
foreach (GameObject slice in currentSlices)
{
Destroy(slice);
}
currentSlices.Clear();
}
private void GenerateMenu(List
{
ClearMenu(); // Vorherige Sektoren entfernen
if (items == null || items.Count == 0)
{
Debug.LogWarning(„No items provided for Pie Menu.”);
return;
}
float totalAngle = 360f;
float anglePerSlice = totalAngle / items.Count;
for (int i = 0; i < items.Count; i++)
{
GameObject slice = Instantiate(pieSlicePrefab, sliceParent);
currentSlices.Add(slice);
RectTransform sliceRect = slice.GetComponent
Image sliceImage = slice.GetComponent
TMP_Text sliceText = slice.GetComponentInChildren
// Winkel für diesen Sektor berechnen
float currentSliceAngle = startAngle – (i * anglePerSlice) – (anglePerSlice / 2f); // Zentriert
// Position berechnen (polar zu kartesisch)
Vector2 position = new Vector2(
radius * Mathf.Cos(currentSliceAngle * Mathf.Deg2Rad),
radius * Mathf.Sin(currentSliceAngle * Mathf.Deg2Rad)
);
sliceRect.anchoredPosition = position;
// Rotation des Sektors, um zum Zentrum zu zeigen (oder radial ausgerichtet zu sein)
sliceRect.localRotation = Quaternion.Euler(0, 0, currentSliceAngle – 90); // Text horizontal halten
// Daten zuweisen
if (sliceText != null) sliceText.text = items[i].name;
// Optional: Icon setzen
// Wenn Sie ein Image für das Icon haben, hier zuweisen:
// Image iconImage = slice.transform.Find(„Icon”).GetComponent
// if (iconImage != null && items[i].icon != null) iconImage.sprite = items[i].icon;
// Hier kann man auch das PieMenuItem-Skript auf dem Sektor initialisieren, falls vorhanden
PieMenuItem menuItemComponent = slice.GetComponent
if (menuItemComponent != null)
{
menuItemComponent.Initialize(items[i], i, this); // ‘this’ ist der Controller
}
}
}
private void UpdateSelection()
{
if (currentSlices.Count == 0 || mainCamera == null) return;
Vector2 mousePosition = Input.mousePosition;
Vector2 menuCenter = transform.position; // Position des PieMenuRoot
Vector2 direction = mousePosition – menuCenter;
if (direction.magnitude < radius / 3) // Optional: Eine "Todeszone" in der Mitte
{
SetSelection(-1); // Nichts ausgewählt
return;
}
// Winkel berechnen (Arctan2 gibt den Winkel in Radiant von -PI bis PI zurück)
float angle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
// Winkel von 0-360 konvertieren und an unser Koordinatensystem anpassen
angle = (angle + 360) % 360; // Macht den Winkel positiv
angle = (startAngle - angle + 360 + 180) % 360; // Dreht das Koordinatensystem und richtet es aus
float anglePerSlice = 360f / currentSlices.Count;
int newSelectedIndex = Mathf.FloorToInt(angle / anglePerSlice);
// Sicherstellen, dass der Index gültig ist
if (newSelectedIndex >= 0 && newSelectedIndex < currentSlices.Count)
{
SetSelection(newSelectedIndex);
}
else
{
SetSelection(-1); // Kein Sektor ausgewählt
}
}
private void SetSelection(int index)
{
if (currentSelectedIndex == index) return;
// Alten Sektor zurücksetzen
if (currentSelectedIndex != -1 && currentSelectedIndex < currentSlices.Count)
{
ResetSliceVisuals(currentSlices[currentSelectedIndex]);
}
currentSelectedIndex = index;
// Neuen Sektor hervorheben
if (currentSelectedIndex != -1 && currentSelectedIndex < currentSlices.Count)
{
HighlightSliceVisuals(currentSlices[currentSelectedIndex]);
}
}
private void HighlightSliceVisuals(GameObject slice)
{
Image img = slice.GetComponent
if (img != null) img.color = hoverColor;
slice.transform.localScale = Vector3.one * hoverScale;
}
private void ResetSliceVisuals(GameObject slice)
{
Image img = slice.GetComponent
if (img != null) img.color = Color.white; // Oder die Originalfarbe
slice.transform.localScale = Vector3.one;
}
private void ExecuteSelectedItem()
{
if (currentSelectedIndex != -1 && currentSelectedIndex < currentItemData.Count)
{
currentItemData[currentSelectedIndex].onClick?.Invoke();
Debug.Log($"Action for: {currentItemData[currentSelectedIndex].name} executed!");
}
}
// Beispiel, wie man das Menü von aussen aufrufen könnte
public void OpenTestMenu()
{
List
{
new PieMenuItemData(„Angriff”, null, () => Debug.Log(„Angriff ausgewählt!”)),
new PieMenuItemData(„Verteidigung”, null, () => Debug.Log(„Verteidigung ausgewählt!”)),
new PieMenuItemData(„Heilung”, null, () => Debug.Log(„Heilung ausgewählt!”)),
new PieMenuItemData(„Flucht”, null, () => Debug.Log(„Flucht ausgewählt!”)),
new PieMenuItemData(„Inventar”, null, () => Debug.Log(„Inventar ausgewählt!”)),
new PieMenuItemData(„Magie”, null, () => Debug.Log(„Magie ausgewählt!”))
};
ShowMenu(Input.mousePosition, testItems);
}
}
„`
2. **Das `PieMenuItem` Skript (optional, aber empfohlen):**
Fügen Sie dieses Skript zu Ihrem `PieSlicePrefab` hinzu. Es ist für die Behandlung individueller Sektor-Interaktionen und die Kommunikation mit dem Controller zuständig.
„`csharp
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
public class PieMenuItem : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler, IPointerClickHandler
{
private PieMenuController.PieMenuItemData itemData;
private int itemIndex;
private PieMenuController controller;
public void Initialize(PieMenuController.PieMenuItemData data, int index, PieMenuController parentController)
{
itemData = data;
itemIndex = index;
controller = parentController;
}
public void OnPointerEnter(PointerEventData eventData)
{
// Die Auswahl wird im Controller durch Mausposition aktualisiert, nicht durch PointerEnter
// Dies ist nützlich, wenn Sie spezifische Aktionen beim Betreten eines Sektors wünschen
}
public void OnPointerExit(PointerEventData eventData)
{
// Auch hier: Auswahl im Controller gehandhabt
}
public void OnPointerClick(PointerEventData eventData)
{
// Wenn Sie möchten, dass ein Klick den Sektor direkt ausführt, statt loslassen der Maustaste
// controller?.ExecuteItemByIndex(itemIndex); // Eine solche Methode müsste im Controller existieren
// controller?.HideMenu();
}
}
„`
*Hinweis:* Im obigen `PieMenuController` wird die Auswahl und Ausführung primär über die Mausposition und das Loslassen der Maustaste gesteuert, was für Pie Menus oft intuitiver ist. Das `PieMenuItem` Skript ist dennoch nützlich, um spezifische Daten mit dem Sektor zu verknüpfen und erweiterte Interaktionen zu implementieren.
III. Interaktion und Dynamik
Das Pie Menu ist nur dann nützlich, wenn es auf Benutzereingaben reagiert und seine Inhalte anpassen kann.
1. **Menü aufrufen:**
In unserem Beispiel rufen wir `ShowMenu()` auf, wenn die linke Maustaste gedrückt wird und das Menü noch nicht aktiv ist. Dies könnte in einem anderen Skript geschehen, z.B. einem `PlayerInputController`.
„`csharp
// Beispiel-Code in einem anderen Skript (z.B. PlayerInputManager.cs)
using UnityEngine;
using System.Collections.Generic;
public class PlayerInputManager : MonoBehaviour
{
public PieMenuController pieMenu; // Referenz im Inspector zuweisen
void Update()
{
if (Input.GetMouseButtonDown(0) && !pieMenu.gameObject.activeSelf)
{
// Beispiel-Daten (diese könnten auch dynamisch generiert werden)
List
{
new PieMenuController.PieMenuItemData(„Angriff”, null, () => Debug.Log(„Angriff!⚔️”)),
new PieMenuController.PieMenuItemData(„Verteidigung”, null, () => Debug.Log(„Verteidigung!🛡️”)),
new PieMenuController.PieMenuItemData(„Heilung”, null, () => Debug.Log(„Heilung!❤️”)),
new PieMenuController.PieMenuItemData(„Inventar”, null, () => Debug.Log(„Inventar!🎒”)),
new PieMenuController.PieMenuItemData(„Karte”, null, () => Debug.Log(„Karte!🗺️”))
};
pieMenu.ShowMenu(Input.mousePosition, menuItems);
}
}
}
„`
Weisen Sie im Inspector des `PieMenuRoot` (wo der `PieMenuController` ist) das `pieSlicePrefab` und das `sliceParent` (den `PieMenuRoot` selbst, oder ein Unterobjekt) zu. Testen Sie das `OpenTestMenu()` aus dem Inspector im Play-Modus.
2. **Selektion und Aktivierung:**
Wie in der `UpdateSelection()`-Methode des `PieMenuController` gezeigt, berechnen wir kontinuierlich den Mauswinkel relativ zum Menüzentrum. Der Sektor, in den der Winkel fällt, wird hervorgehoben (`HighlightSliceVisuals`). Wenn der Benutzer die Maustaste loslässt (`Input.GetMouseButtonUp(0)`), wird die Aktion des aktuell ausgewählten Sektors ausgeführt (`ExecuteSelectedItem()`).
3. **Dynamische Inhalte:**
Der Schlüssel zu einem dynamischen Menü liegt in der `PieMenuItemData`-Klasse und der `GenerateMenu`-Methode. Sie können die Liste der `PieMenuItemData`-Objekte zur Laufzeit erstellen, basierend auf:
* **Kontext:** Welches Objekt wurde angeklickt? Welchen Zustand hat der Spieler?
* **Inventar:** Zeigen Sie nur verfügbare Gegenstände an.
* **Fähigkeiten:** Zeigen Sie nur erlernte oder aktive Fähigkeiten.
* **Einstellungen:** Menüpunkte für Konfigurationsoptionen.
Indem Sie die `ShowMenu`-Methode mit unterschiedlichen `List
IV. Optimierung und Best Practices
Ein funktionierendes Pie Menu ist gut, ein performantes und benutzerfreundliches ist besser!
* **Performance:**
* **UI Batching:** Unity versucht, UI-Elemente zu batchen, um die Zeichenaufrufe zu reduzieren. Achten Sie darauf, dass Ihre UI-Elemente nicht unnötig überlappen und dass Materialwechsel minimiert werden.
* **Canvas Rebuilds:** Das Instanziieren und Zerstören von UI-Elementen kann zu teuren Canvas Rebuilds führen. Für extrem häufige Änderungen sollten Sie ein **Object Pooling**-System in Betracht ziehen, bei dem Sie Sektoren deaktivieren und wiederverwenden, anstatt sie ständig neu zu erstellen und zu zerstören.
* **Update-Methoden:** Vermeiden Sie komplexe Berechnungen in der `Update`-Methode, es sei denn, sie sind unbedingt notwendig (wie die Mauswinkelberechnung).
* **Usability und UX:**
* **Visuelles Feedback:** Das Hervorheben des ausgewählten Sektors (Farbe, Skalierung) ist unerlässlich. Überlegen Sie sich zusätzliche Effekte wie kurze Animationen beim Öffnen/Schließen oder beim Auswählen.
* **Akustisches Feedback:** Ein subtiler Sound beim Wechseln der Auswahl oder beim Ausführen einer Aktion kann das Benutzererlebnis verbessern.
* **Haptisches Feedback:** Für mobile Geräte oder Controller kann haptisches Feedback die Interaktion greifbarer machen.
* **Anzahl der Sektoren:** Begrenzen Sie die Anzahl der Sektoren (oft 6-8 ist optimal), um die kognitive Belastung zu reduzieren und schnelle Auswahl zu ermöglichen. Für mehr Optionen können Untermenüs eine Lösung sein.
* **Design:** Stellen Sie sicher, dass Icons klar erkennbar und Texte lesbar sind, auch bei kleineren Größen. Kontrast ist hier entscheidend.
* **Zugänglichkeit:**
* **Tastatur- / Gamepad-Support:** Implementieren Sie die Navigation auch über Tastaturpfeile oder Gamepad-Sticks, um eine breitere Benutzerbasis zu erreichen. Dies erfordert zusätzliche Logik zur Steuerung der `currentSelectedIndex`.
* **Anpassbarkeit:** Ermöglichen Sie es Benutzern, die Größe des Menüs oder die Farben anzupassen.
V. Weiterführende Ideen
Das hier gezeigte Pie Menu ist ein solides Fundament. Hier sind einige Ideen, wie Sie es erweitern können:
* **Untermenüs (Nested Pie Menus):** Wenn ein Menüpunkt selbst ein weiteres Pie Menu öffnen soll, können Sie die Logik rekursiv anwenden oder ein System für die Verwaltung mehrerer Menüs implementieren.
* **Gamepad-Unterstützung:** Erweitern Sie die `UpdateSelection`-Logik, um die Eingaben von Gamepad-Sticks zu verarbeiten, anstatt nur die Maus.
* **3D Pie Menus:** Statt eines 2D-UI-Canvas könnten Sie 3D-Modelle verwenden, die sich in einem Kreis um ein Objekt anordnen. Dies ist besonders reizvoll für VR/AR-Anwendungen.
* **Drag-and-Drop-Funktionalität:** Erlauben Sie Benutzern, Gegenstände aus dem Inventar in einen Menüsektor zu ziehen, um eine Aktion auszuführen.
* **Konfigurierbarkeit:** Speichern Sie Menüpunkte in externen Dateien (JSON, XML), um sie ohne Codeänderungen anpassen zu können.
* **Animationen:** Feine Animationen beim Öffnen, Schließen oder beim Hervorheben der Sektoren verbessern das Gefühl der Flüssigkeit und Professionalität. Nutzen Sie Unitys Animation-System oder DOTween/LeanTween.
Sie haben nun das Wissen und die Werkzeuge, um ein leistungsstarkes und intuitives Pie Menu in Unity zu erstellen. Von der grundlegenden UI-Struktur über die komplexe C#-Logik bis hin zu fortgeschrittenen Optimierungen – Sie sind bestens gerüstet, um Ihre Benutzeroberflächen zu transformieren. Ein dynamisches Kuchenmenü spart nicht nur wertvolle Millisekunden bei der Interaktion, sondern verbessert auch das gesamte Benutzererlebnis erheblich, indem es die Auswahlmöglichkeiten direkt unter den Fingerspitzen des Benutzers platziert.
Experimentieren Sie mit verschiedenen Designs, Animationen und Interaktionsmöglichkeiten. Die Grenzen setzt nur Ihre Kreativität. Viel Erfolg beim Entwickeln Ihrer nächsten großartigen Unity-Anwendung!