Dev-Diary: Serious Games Praxisprojekt Sommersemester 2024
Projektinformationen
Projektzeitraum: 28.05.2024 – 02.07.2024
Abgabedatum: 02.07.2024, 23:59 Uhr
Gruppenmitglieder: Marvi Karroci, Patrick Seemann, Samuel Stein, Malte Vu, Asya Topkaya
Spiel-Link: https://eatery0349.itch.io/cooking-showdown
## Woche 1: Basics (28. Mai – 03. Juni)
### Unity-Setup:
Wir haben Unity Hub und den Unity Editor installiert und uns anschließend für ein 2D-Spiel entschieden
#### Narrative Design:
Wir haben uns folgende Story passend ausgedacht:
#### Hintergrundgeschichte:
Eine einst blühende Stadt ist ins Chaos gestürzt, als ein böser Alchemist die Macht an sich riss und eine tödliche Krankheit verbreitete. Diese Krankheit befällt die Bewohner auf unterschiedliche Weise, und der Alchemist plant, die gesamte Stadt zu vernichten. Als letzte Hoffnung schlüpft der Spieler in die Rolle eines talentierten Kochs, der die Kunst des heilenden Kochens beherrscht. Mit gesunden Gerichten, die speziell auf die individuellen Bedürfnisse der Kranken abgestimmt sind, muss der Spieler die Bewohner heilen und schließlich den bösen Alchemist besiegen.
#### Spielmechanik:
Der Spieler beginnt mit einem Basisset an Zutatenkarten, die verschiedene gesunde und heilende Zutaten repräsentieren. Jede Runde zieht der Spieler zufällige Zutatenkarten aus seinem Deck und kombiniert diese, um Gerichte zu kochen, die auf die spezifischen Krankheiten der Stadtbewohner abgestimmt sind. Durch erfolgreiche Heilungen sammelt der Spieler neue Zutaten und Rezepte, die ihm im weiteren Spielverlauf helfen.
### Erste Szene:



### Zutatenkarte:
Folgende Zutaten stehen zur Verfügung und können zum Kochen verwendet werden:
Beans, Beef, Blue Cheese, Butter, Carrots, Eggs, Garlic, Honey, Olive Oil, Onions, Oyster, Parsnip, Salt, SHrimp, Turnips, Vinegar, White Cheese
### Diät-Auswahl:
Wir haben uns für die Diäten Keto, Vegetarisch und Vegan entschieden.
Die drei Gerichte und die dazu passende Zutaten sind folgende:
Natürlich, hier sind die Rezepte auf Deutsch übersetzt:
Keto-Gerichte:
1. "Cheese-Omlette"
- Zutaten: Eggs, Blue Cheese, Butter, Salt, Onions
2. "Roasted Beef"
- Zutaten: Beef, Olive Oil, Salt, Garlic, Onions
3. "Shrimp Skewer"
- Zutaten: Shrimp, Olive Oil, Garlic, Salt, Onion
Vegetarische Gerichte:
1. "Blue Cheese Salad"
- Zutaten: Blue Cheese, Carrots, Olive Oil, Honey, Vinegar
2. "Cheese and Bean Casserole"
- Zutaten: Beans, White Cheese, Eggs, Salt, Onions
3. "Roasted Root Vegetables"
- Zutaten: Carrots, Parsnips, Salt, Olive Oil, Garlic
Vegane Gerichte:
1. "Marinated Mushrooms"
- Zutaten: Oyster (mushrooms), Olive Oil, Garlic, Vinegar, Salt
2. "Refried Beans"**
- Zutaten: Beans, Olive Oil, Garlic, Salt, Onions
3. "Root Vegetable Curry"
- Zutaten: Carrots, Parsnips, Turnips, Olive Oil, Salt
## Woche 2: Kartenmechanik (04. Juni – 10. Juni)
### Handkarten-Platzierung:
Für die Platzierung der fünf Handkarten haben wir die folgende Anordnung gewählt.
Mit dem GameObject CardSlot wurde ein Deck aus den fünf Karten erstellt. Außerdem gibt es einen Button, der die Anzahl der verbleibenden Karten anzeigt. Die genauen Funktionen zum Auffüllen, Mischen und weiteren Details sind in der Deck-Implementierung zu finden.

### Drag&Drop-Mechanik
Das DraggableItem-Skript ermöglicht es einem Spielobjekt, durch Drag & Drop verschoben zu werden. Es reagiert auf verschiedene Mausereignisse (Beginn, Ziehen, Ende des Ziehens, Klick) und verarbeitet Kollisionen mit verschiedenen Zielobjekten, die durch Tags spezifiziert sind (TrashBin, CookingPot, Enemy). Bei einer Kollision führt es entsprechende Aktionen aus, wie das Hinzufügen zum Zielobjekt oder das Deaktivieren des eigenen Spielobjekts.

#### OnBeginDrag() Methode
- Wird aufgerufen, wenn das Ziehen des Objekts beginnt.
- parentAfterDrag speichert den ursprünglichen Eltern-Transform des Objekts.
- Das Objekt wird auf die Wurzel des Hierarchiebaums gesetzt und als letztes Geschwisterobjekt angezeigt.
- canvasGroup.blocksRaycasts wird auf false gesetzt, um Raycasts zu blockieren und das Objekt für Kollisionen transparent zu machen.
#### OnDrag() Methode
- Wird jedes Mal aufgerufen, wenn das Objekt während des Ziehens bewegt wird.
- Setzt die Position des Objekts auf die aktuelle Mausposition.
#### OnEndDrag() Methode
- Wird aufgerufen, wenn das Ziehen des Objekts endet.
- Ruft CheckCollision() auf, um festzustellen, ob eine Kollision mit spezifischen Zielobjekten vorliegt.
- Je nach Ergebnis der Kollision wird das Objekt entweder an seinen ursprünglichen Eltern-Transform zurückgesetzt oder dort belassen.
- Setzt canvasGroup.blocksRaycasts wieder auf true, um Raycasts wieder zuzulassen.
#### CheckCollision() Methode

- Verwendet Raycasting, um festzustellen, ob das Objekt mit bestimmten Zielobjekten (Tags wie "TrashBin", "CookingPot", "Enemy") kollidiert ist.
- Führt entsprechende Aktionen aus, wenn eine Kollision festgestellt wird, z.B. hinzufügen zum Müllbehälter (TrashBin), zum Kochtopf (CookingPot) oder Einfrieren des Feindes (Enemy).
### DECK-Implementierung
Das DeckManager-Skript verwaltet ein Kartendeck durch eine Queue von Kartenobjekten. Es ermöglicht das Initialisieren des Decks mit ausgewählten Karten, das Mischen des Decks, das Befüllen von Kartenplätzen und das Reagieren auf Ereignisse wie das Entfernen von Karten. Die Methoden des Skripts stellen sicher, dass das Kartendeck korrekt verwaltet wird und die UI entsprechend aktualisiert wird, um den Spielern die aktuelle Anzahl von Karten im Deck anzuzeigen.

InitializeDeck-Methode: Diese Methode füllt die deck-Queue mit den ausgewählten Karten aus dem SelectedCardsManager. Sie wird beim Start des Skripts aufgerufen.
#### ShuffleDeck() Methode

- Diese Methode mischt die Karten im deck. Sie erstellt zunächst eine Kopie der aktuellen Kartenliste, mischt diese und füllt dann die deck-Queue mit den gemischten Karten.
#### FillCardSlots() Methode

- Diese Methode füllt die leeren Kartenplätze (cardSlots) mit Karten aus der deck-Queue. Sie instanziiert für jeden leeren Slot ein neues cardPrefab, konfiguriert es mit den Karteninformationen (SetupCard(card)) und entfernt die Karte aus der deck-Queue.
#### CheckAndRefillSlots() Methode

- Diese Methode überprüft alle Kartenplätze und füllt leere Plätze mit neuen Karten aus der deck-Queue nach. Sie wird durch das Event OnItemRemoved aufgerufen, was darauf hinweist, dass eine Karte aus dem Deck entfernt wurde.
#### AddCardToDeck() Methode

- Fügt eine Karte zur deck-Queue hinzu, mischt das Deck neu, füllt leere Kartenplätze nach und aktualisiert den Deckzähler.
#### RemoveCardFromDeck() Methode

- Entfernt eine bestimmte Karte aus der deck-Queue, wenn sie enthalten ist. Aktualisiert dann den Deckzähler und zerstört das zugehörige GameObject der Karte im Deck.
#### UpdateDeckCounter() Methode

- Aktualisiert den Text im deckCounterText (TMP_Text), um die aktuelle Anzahl der verbleibenden Karten im Deck anzuzeigen.
Für den zweiten Teil dieser Aufgabe soll eine neue, zufällige Karte aus dem Deck gezogen werden, sobald eine Karte den Mülleimer berührt. Zusätzlich soll die berührende Karte mit einer 50%igen Wahrscheinlichkeit komplett aus dem Deck entfernt werden. Dies wird mithilfe eines Skripts namens TrashBin realisiert.

#### AddItem(GameObject item):
- Fügt das übergebene item zur itemsInTrash-Liste hinzu.
- Setzt das item als Child des TrashBin.
- Setzt die Position des item auf die Position des TrashBin.
- Mit einer Wahrscheinlichkeit von 50% wird die Karte dem Deck wieder hinzugefügt.
- Falls die Karte dem Deck wieder hinzugefügt wird, wird eine visuelle Rückmeldung angezeigt.
- Löst das ItemRemoved-Ereignis aus.
#### ShowFeedback(string message):

- Zeigt eine visuelle Rückmeldung an, wenn eine Karte dem Deck wieder hinzugefügt wird.
- Bewegt den Feedback-Text nach rechts und lässt ihn allmählich ausblenden.
### Kartendesign
Für das Kartendesign haben wir Prefabs verwendet, um ein konsistentes Layout zu erzeugen. Jede Karte enthält eine Überschrift, die die Zutat beschreibt, sowie ein passendes Bild aus den Assets von [diesem Unity Asset Store Paket](https://assetstore.unity.com/packages/2d/gui/icons/67-cooking-ingredients-prepared-dishes-food-icons-pack-246901). Eine kurze Beschreibung ergänzt die Karte.
Ein Beispiel von dem Aufbau einer Karte:


## Woche 3: Kampf- und Progressmechanik (11. Juni – 17. Juni)
### Gegner hinzufügen:
Wir haben einen Gegner hinzugefügt, der den Score mit Flammen trifft und Minuscores macht.
Das Enemy-Skript steuert das Verhalten eines Gegner-Objekts im Spiel. Es ermöglicht dem Gegner, sich zu einfrieren und währenddessen inaktiv zu sein, bevor er wieder angreift. Während des Angriffs feuert der Gegner einen Feuerball ab. Die UI-Elemente freezeTimerText und attackTimerText zeigen dabei die verbleibende Dauer des Einfrierens und des Angriffs an. Das Skript verwendet Coroutines, um die zeitgesteuerten Aktionen (Einfrieren, Angriff) zu implementieren, und arbeitet mit UI- und Physikkomponenten von Unity, um das Verhalten des Gegners zu steuern.

- Variablen und Initialisierung: Hier werden Variablen für die verschiedenen Zustände des Gegners initialisiert, wie die Sprites für den gefrorenen Zustand (freezeSprite) und den Angriffs-Zustand (attackSprite), sowie Timer für die Dauer des Einfrierens (freezeDuration), des Angriffsintervalls (attackInterval) und der Angriffs-Dauer (attackDuration).
- UI-Elemente: Image und TMP_Text sind Referenzen auf die UI-Elemente, die die verbleibende Dauer des Einfrierens und des Angriffs anzeigen.
- TimerSystem: TimerSystem ist eine Referenz auf ein anderes Skript in der Szene, das zur Verwaltung von Zeit- und Timer-Funktionen verwendet wird.
#### Freeze() Methode

- Wird aufgerufen, um den Gegner einzufrieren. Die verbleibende Dauer des Einfrierens wird erhöht, und wenn der Gegner nicht bereits gefroren ist, wird die FreezeCoroutine gestartet.
#### FreezeCoroutine() Methode

- Diese Coroutine ändert das Sprite des Gegners in das Einfrier-Sprite (freezeSprite), setzt den Zustand auf isFrozen = true und aktualisiert die UI-Texte, um die verbleibende Einfrierdauer anzuzeigen. Die Coroutine läuft, bis die Einfrierzeit abgelaufen ist, woraufhin der Gegner wieder normal wird und der Angriffstimer zurückgesetzt wird.
#### AttackCoroutine() Methode

- Diese Coroutine steuert den Angriffszyklus des Gegners. Sie läuft dauerhaft und wartet auf das Ende des Angriffsintervalls (attackInterval). Wenn dieses abgelaufen ist, wird der Gegner in den Angriffsmodus versetzt (attackSprite), ein Feuerball wird erzeugt (SpawnFireball()), und der Angriffs-Countdown wird angezeigt. Nach Ablauf der Angriffszeit wird der Angriff beendet und der Zustand zurückgesetzt.
#### SpawnFireball() Methode und RemoveFireballAfterTime() Methode

- SpawnFireball-Methode: Diese Methode erzeugt einen Feuerball, der vom Gegner abgeschossen wird. Die Position wird relativ zum Gegnerobjekt festgelegt (fireballSpawnOffset), und der Feuerball erhält eine Geschwindigkeit (fireballSpeed). Ein Timer wird gestartet, um den Feuerball nach einer bestimmten Zeit wieder zu entfernen (RemoveFireballAfterTime).
- RemoveFireballAfterTime-Methode: Diese Coroutine wartet für eine bestimmte Zeit (delay) und zerstört dann den Feuerball (fireball).
### Rezepte/Gerichte:

Die Methode überprüft, ob der CookingPot und das aktuelle Rezept gesetzt sind. Wenn die Zutaten im Topf mit denen im Rezept übereinstimmen, wird das Erfolgsbild angezeigt und der Punktestand erhöht. Andernfalls wird das Fehlschlagsbild angezeigt und der Punktestand verringert.
### Kochen-Button:

Der Button ermöglicht es, den Kochvorgang auszulösen, der das Ergebnis anzeigt und den Punktestand entsprechend aktualisiert.
### Effektivität von Zutaten und Gerichten:
Wenn man die Kochen-Taste drückt und die Zutaten stimmen, erhält man Pluspunkte. Wenn die Zutaten nicht stimmen, erhält man Minuspunkte.
### Siegbedingungen für den Gegner:
Das Spiel gilt als verloren, wenn man am Ende eine negative Punktzahl hat. Hat man eine positive Punktzahl, gilt das Spiel als gewonnen und das Deck wird um 5 Karten erweitert, um das neue Gericht zu kochen.

#### Gesamtfunktionalität:
- Wenn das Spiel endet, wird die endgültige Punktzahl angezeigt.
Spiel erneut starten: Wenn der playAgainButton geklickt wird, überprüft die Methode OnPlayAgainButtonClicked die endgültige Punktzahl:
- Wenn die Punktzahl positiv ist, wird die maximale Anzahl von Karten erhöht.
- Wenn die Punktzahl nicht positiv ist, wird die maximale Anzahl von Karten auf den Standardwert zurückgesetzt.
- Die Punktzahl wird in beiden Fällen auf 0 zurückgesetzt.
Nachdem die Einstellungen aktualisiert wurden, wird die Szene DietScene geladen, um das Spiel neu zu starten.
## Woche 4: Transformation (18. Juni – 24. Juni)
### Punktesystem:
Dieser Code definiert eine Klasse ScoreManager, die das Punktesystem in einem Spiel verwaltet. Sie aktualisiert die Punkteanzeige und reagiert auf Ereignisse wie Punktesteigerungen und -reduzierungen. Außerdem wird überprüft, ob das Spiel vorbei ist, und bei Bedarf zur GameOver-Szene gewechselt.

- ScoreManager lädt den Punktestand beim Start, aktualisiert ihn bei Änderungen und zeigt den aktuellen Punktestand an.
- Methoden zum Erhöhen und Reduzieren der Punktzahl sind implementiert.
- Bei Erreichen des Spielendes wird die GameOver-Szene geladen und der Punktestand gespeichert.
### Transformationsskills:

Der Code sorgt dafür, dass eine Karte, die doppelt angeklickt wird, durch eine zufällige Karte derselben Kategorie ersetzt wird, wobei sichergestellt wird, dass die neue Karte nicht dieselbe wie die aktuelle Karte ist.
### Doppelklick-Mechanik:

Dieser Code implementiert die Erkennung eines Doppelklicks auf eine Karte. Bei einem Doppelklick innerhalb von 1 Sekunde wird die Methode TransformCard() aufgerufen, die die Karte transformiert.
### Rezeptanpassung

CheckAndSetupRecipe() Methode stellt sicher, dass das erste Gericht basierend auf der Diät des Spielers ausgewählt wird und die anderen Gerichte zufällig sind weil currentRecipeIndex > 1 ist:
## Woche 5: Polishing (25. Juni – 02. Juli)
### Polishing:
Für das Polishing haben wir uns entschieden, das Spiel mit musikalischer Untermalung zu versehen. Soundeffekte beim Schießen des Feuerballs und am Ende des Spiels verbessern das Spielerlebnis erheblich. Eine konstante Hintergrundmusik begleitet die Spieler von Anfang bis Ende des Spiels.
Hierfür haben wir eine Klasse BackgroundMusic erstellt, die die AudioSource beim Start abspielt.

### Deckerweiterung:
Gewinnt der Spieler ein Spiel, also ist der Endscore größer als Null. So wird der Kartdeck des Spielers um fünf zufällige Karten bei einem Neustart des Spieles erweitert.
Dabei benutzen wir eine if-Abfrage, die je nach endScore die maximale Kartenanzahl um fünf erhöht.

### Eigene Erweiterung:
#### Soundeffekte bei Feuerball und Spielende
- Verbesserte Spielatmosphäre: Soundeffekte geben dem Spieler klares Feedback über Aktionen und verstärken die Emotionen, besonders beim Verlust des Spiels.
#### Anzeigedauer bis zum Angriff
- Strategische Planung: Der Spieler kann seine Strategie planen, z.B. wie oft er den Gegner einfrieren will.
- Erhöhte Spannung: Der Countdown bis zum Angriff erhöht die Spannung, da der Spieler darauf reagieren muss.


#### Angreifer einfrieren
- Interaktive Spielmechanik: Das Einfrieren des Angreifers fügt eine zusätzliche Ebene der Kontrolle und Strategie hinzu.

#### Diät generieren
- Bewusstsein für Ernährungsweisen: Das Generieren einer Diät fördert das Bewusstsein für verschiedene Ernährungsweisen.
#### Tutorial zu Spielbeginn
- Verbesserte Spielerfahrung: Klare Anweisungen zu Beginn des Spiels sorgen für eine bessere Spielerfahrung, da die Spieler wissen, was zu tun ist.

### Zusammenfassung und Fazit
Unser Projekt hat uns gezeigt, wie facettenreich die Unity-Entwicklung sein kann. Die Variationen in denen man ein Spiel konzipieren kann sind grenzenlos. Auch die Lernkurve für Einsteiger ist erstmal schwer zu begreifen, da sowohl das visuelle als auch die Spielelogik mit verschiedenen Szenen verflechtet ist und das gemeinsame Zusammenarbeiten der verschiedenen Komponenten sind kein einfaches Unterfangen. Gut hat vor allem die Implementierung der Spielemechanik funktioniert. Durch Verwenden von simplen Softwarepatterns wurde die Einbettung der Skripte wesentlich komfortabler. Auch der visuelle Anteil lässt sich sehen und motivierte beim Programmieren immens. Probleme gab es anfänglich mit dem Verständnis der UNITY Engine, aber mit der gemeinsamen Kommunikation im Team haben wir die Hindernisse überwunden und haben ein für uns ein solides Produkt herausgebracht. Mithilfe des Projektes haben wir vor allem den bildungsspezifischen Teil eines solchen Spieles näher verstanden und denken dass unser Spiel vielen Menschen bei der Erlernung von Rezepten hilfreich sein kann. Wir sind uns sicher, dass andere Länder von den kulinarischen Rezepten im Spiel auch viel Wissen entnehmen können.