Jump to content
Unity Insider Forum
Reggie()

[Kartenspiel] Grundgedanken eines Newbies

Recommended Posts

Hi. Mein erstes Unity-Projekt soll ein Kartenspiel (Kartenlegespiel) werden. Erst einmal würde ich es in 2D versuchen. Im Zuge meiner theoretischen Überlegungen bin ich bei einer Idee gelandet, zu der ich gerne eine Meinung lesen würde.

Das Kartendeck (z.B. 60 Karten) erstelle ich zu Spielbeginn und lege sie in einem Array ab. Jeder "Ort", an dem sich Spielkarten befinden können (Vor den Spielern, in der Hand der Spieler, Ziehstapel, Ablagestapel) ist ein GameObjekt. Wechselt nun eine Karte den Ort, so würde sie vom "Child" des alten GameObjektes zum "Child" des neuen GameObjektes werden. Die Eigenschaften der jeweiligen GameObjekte steuert dann automatisch, wie die Karten angeordnet/angezeigt werden.

Die Benutzeroberfläche würde ich mit UnityGUI realisieren. Wer am Zug ist, steuert eine Anzeige. Ist der menschliche Spieler nicht am Zug, ignoriere ich jede Eingabe (Maus, tastatur (außer ESC)). 

Die Programmlogic befindet sich dann in den Scrips zu den Karten und wird ausgeführt, wenn diese in eine Aktion "verwickelt" werden (ziehen, verschieben, aktivieren...usw.)

Ist das so ungefähr machbar? Wie gesagt, ich befinde mich in der Lernphase und dies ist nur eine Umsetzungsidee (ich befinde mcih zur Zeit im Büro).

Reggie()

Share this post


Link to post
Share on other sites

Das sollte so machtbar sein  👌🏻

Grade in der Lernphase würde ich die Ideen einfach mal versuchen, wenn das später mal was nicht klappen sollte kannst du immernoch hier im Forum fragen. 

Aber die Grundidee der Umsetzung hört sich gut an. 

Share this post


Link to post
Share on other sites

für mich ist der prodezuale Ansatz immer noch so tief verankert, dass ich instinktiv nach der Möglichkeit durch eine zentrale Programmsteuerung suche. Durch UNITY ist mir zum ersten Mal der Objekt orientierte Ansatz klar geworden :)

Die Ablauf zu Programmstart geschieht dass durch ein leeres GameObject-Script? oder kann ich ein script direkt an eine scene hängen? oder gibt es direkt eine Möglichkeit Start-Code zu Beginn einer scene aufzurufen?

Reggie

Share this post


Link to post
Share on other sites

oder gibt es direkt eine Möglichkeit Start-Code zu Beginn einer scene aufzurufen?

Hallo,
Einfach einen Skript in ein aktives GameObject reinziehen und den Code sehr wichtig in die Start() Funktion des Skriptes reinziehen, dann wird der Programmcode direkt beim Start der Szene aufgerufen :) 

Share this post


Link to post
Share on other sites

da eine scene üblicherweise eine camera hat, kann ich dort ein script anhängen mit einer start() Methode?

Share this post


Link to post
Share on other sites

Kannst du. Aber wenn es nichts mit deiner Kamera zu tun hat, dann ist das schlechter Stil durch den du früher oder später auf die Nase fallen wirst. Beschreibe doch einfach mal kurz, was du überhaupt zum Spielstart machen willst.

Share this post


Link to post
Share on other sites

Es ist ein Karten-Legespiel. D.h. zu Spielstart müssen die Spielkarten "erschaffen", der Kartenstapel gemischt und die Start-Karten an die Spieler verteilt werden. Dann muss der Zuganzeiger eingestellt und angezeigt werden. Dann erst kann die erste Aktion des Spielers verarbeitet werden.

Reggie()

Share this post


Link to post
Share on other sites

so stelle ich mir den Aufbau vor: 

screen.jpg

die Bereiche sollen, sobald Karten dorthin geschoben werden, diese gemäß einem Raster einordnen. Der Spieler macht das über Drag&Drop.

Share this post


Link to post
Share on other sites

Dann bau dir ein GameObject namens "Card Generator" oder irgendwas. Nur keine Scheu, einfach GameObjects für jede Rolle in deinem System zu erstellen.

Share this post


Link to post
Share on other sites

Ich stehe grad vor der Frage, wie ich das "Einordnen der Karten" bei den Bereichen der Spielfläche umsetze.  Spontan denke ich da an 2 Möglichkeiten:

1. Die Bereiche sind so gestaltet, dass die Karten einen festen Abstand zu den Rändern und anderen Karten haben müssen. Das würde dann der BereichsManager regeln, wenn ihm eine Karte zum Einordnen "übergeben" wird

2. Jeder Bereich hat ein Array mit points. Diese points sind die "Einrast-Punkte" für die Kartenobjekte (setzte Karte auf den ersten freien point)

Geht das so oder setzt man das ganz anders um? 

Reggie()

Share this post


Link to post
Share on other sites

Ginge beides. Bei meinem Kartenprojekt gibt man einmalig eine Zahl ein, die dann den Abstand zwischen den Karten darstellt. Mehr Werte eintragen zu müssen, um dasselbe zu erreichen, ist nicht so prickelnd. Wenn du aber unregelmäßige Kartenverteilung haben willst, ist die zweite Variante besser.

Share this post


Link to post
Share on other sites

die Kartenanordnung muss nur 2 Regeln folgen: Unterschiedliche nebeneinander, gleiche Karten überlappend übereinander. d.h ich brauche wohl neben der horizontalen Ebene auch eine vertikale...

Share this post


Link to post
Share on other sites

Wenn die Abstände aber immer gleich sind, dann reicht aber ein einzelner Wert pro Achse; da brauchst du kein Array mit Punkten.

Share this post


Link to post
Share on other sites

ist die Formel zur Berechnung des nächsten freien Platzes denn nicht aufwendiger, als einfach den nächsten freien Punkt aus dem Array auszulesen? Zumal ich die Karten je eh in den Array einordne, denn jede Karte ist ja ein einzigartiges GameObject, dass ja immer irgendwo sein muss, damit alles seine Ordnung hat. Dann kann ich das doch gleich mit nutzen, dass jede neue Karte im Array auch ihren Platz zugeordnet und gespeichert bekommt.

Share this post


Link to post
Share on other sites

Wir reden hier von Prozessoren mit gut und gerne 102.000.000.000 floating point operations pro Sekunde. Das sind bei 60 fps 1.700.000.000 Operationen pro Frame. Mach dir mal wegen ein paar kleinen Rechenaufgaben für deinen Computer keinen Kopf, ist nicht mehr 1992 ;)

Heutzutage ist es viel wichtiger, Fehleranfälligkeit, wie sie z.B. durch das manuelle Eingeben aller Positionen kommt, gering zu halten. Um Performance solltest du dir erst dann einen Kopf machen, wenn sie sich als Problem entpuppt. Als kleiner Spoiler: Das wird sie hier nicht.

Share this post


Link to post
Share on other sites

das ist witzig. ich dachte da nicht an die Rechenleistung meines Computers, sondern an MICH bei der Erstellung der Rechenformel ;)

Share this post


Link to post
Share on other sites

Öh, na dann...

positionDerKarte = ursprungDesPlatzes * Vector3.right * indexDerKarte;

oder anders:

public Vector3 GetCardPosition(int index)
{
  return transform.position * Vector3.right * index;
}

davon ausgehend, dass das Script auf dem Kartenfeld liegt. Mehr ist da nicht dran.

Edited by Sascha
Code korrigiert

Share this post


Link to post
Share on other sites

ich glaube ich verstehe deine Formel nicht. Was ist mit

freePoint = firstPoint + ((cardwidth + padding) * index)

bei deinem 2. code verwirrt mich das void. wird nicht durch return ein Rückgabewert zurückgegeben?

also so?

public Transform GetCardPosition(int index)
{
  return transform.position * Vector3.right * index;
}

 

Share this post


Link to post
Share on other sites

Ich glaube, dass da in Saschas Code ein kleiner Dreher drin ist.. 

 

Zum einen hast du Recht, dass das 'void' an der Stelle keinen Sinn ergibt. Aber der Rückgabewert ist nicht vom Typ 'Transform', sondern 'Vector3'. 

 

vor 22 Minuten schrieb Reggie():

firstPoint + ((cardwidth + padding) * index)

Die Formel sieht richtig aus. Sie gibt dir aber nur den Abstand vom Startpunkt.

Wenn du die Position ausrechnen willst, würde ich es so machen wie Sascha. Nur 'plus' die Startposition, nicht 'mal'. Und die Verschiebung nach Rechts mit dem Abstand zwischen zwei Karten multiplizieren.

Also:

public Vector3 GetCardPosition(int index)
{
 return firstPoint + Vector3.right * (cardwidth + padding) * index;
}

 

Share this post


Link to post
Share on other sites

Jo, hab das nur so runtergetippt, war ein Fehler drin. "cardwidth + padding" kann man natürlich machen, muss man aber auch nicht, wenn man einfach den Abstand zwischen immer demselben Punkt einer Karte meint (z.B. Mittelpunkt zu Mittelpunkt). Wenn's dir damit einfacher fällt, nur zu :)

Share this post


Link to post
Share on other sites

ich meinte auch nicht, dass meins besser wäre- ich hatte nur deinen code nicht verstanden, daher..  aber jetzt glaube ich sogar, dass deiner meinem Wunsch näher kommt. Nur verstehe ich noch nicht, woher kommt der index? wenn ich den ersten freien spot in einem Bereich suche, dann müsste ich dann nicht statt index eher array.length nehmen? dann würde sich auch gleich die Anordnung bei jeder Veränderung hinlegen oder wegnehmen von Karten aus dem Bereich anpassen. oder?

Share this post


Link to post
Share on other sites

Der kommt aus deinem Kontext. Wenn du deine Karte immer hinten anreihen willst, dann wird das zum Beispiel der Anzahl der bereits liegenden Karten entsprechen.

Share this post


Link to post
Share on other sites

Ich stehe vor folgendem Problem:

Ich möchte ca 80 unterschiedliche Objekte erstellen, die aber erst im Laufe des Spiels angezeigt und verwendet werden sollen. Ich sehe da folgende Möglichkeiten:

a) ich erstelle für jedes Objekt ein prefab und rufe dies auf mit instancinate

b) ich erstelle jedes Objekt sofort und verschiebe es außerhalb des Sichtbereichs und bringe es in den sichtbaren Bereich, wenn es gebraucht wird

c) ich erstelle die Objekte per script und constructor und verleihe den Objekten dann eine Transform und einen regidbody, wenn es gebraucht wird

 

zu a) 80 prefabs (ist das nicht sehr unübersichtlich?) Problem: das Belegen der Objekt-Variablen erscheint mir zu aufwendig

zu b) finde ich am besten, aber wirkt irgendwie falsch/unprofessionell

zu c) das gleich Probel wie a) nur andersrum. Ich habe dann Objekte ohne Unity-Gameobjekt-Hülle und muss jedem Objekt Transform und einen regidbody verpassen, weiss aber nicht wie das geht.

Wie würdet ihr das umsetzen?

Share this post


Link to post
Share on other sites

Worin unterscheiden sich denn deine 80 Prefabs? Ich nehme an, dass sie alle dieselben Komponenten haben und sich nur durch die Werte der Komponenten unterscheiden. Auftritt: ScriptableObjects!

ScriptableObjects sind Objekte, die nicht wie MonoBehaviours als Komponenten auf GameObjects gelegt, sondern (unter anderem) als Assets in deinen Assets-Ordner gespeichert werden können. Als Grundlage baust du das hier:

using UnityEngine;

[CreateAssetMenu]
public class CardData : ScriptableObject
{
  // Inhalte hier
}

Wenn das getan ist, kannst du fortan in deiner Project View Rechtsklicken, dann Create, und statt z.B. "C# Script" "CardData" auswählen. Jetzt hast du ein neues CardData-Objekt, das exakt keine Werte oder sonst irgendetwas beinhaltet. Im Vergleich zu einem Prefab fehlen auch die Transform-Komponente und vergleichbares. Ein ScriptableObject enthält nur genau die Daten, die du ihm vorgibst. Das machst du, indem du Felder in die ScriptableObject-Klasse hinzufügst. Statt "Inhalte hier" schreibst du also z.B.:

public string title;
[Multiline]
public string description;
public Sprite image;
public Color backgroundColor;

und kannst dann auf deinem erstellten ScriptableObject diese Werte wie gewohnt einstellen.

Als nächstes sind ScriptableObjects allesamt UnityEngine.Objects. Das bedeutet, dass du sie per Drag and Drop zuweisen kannst, so wie du auch z.B. ein Sprite in einen SpriteRenderer ziehen kannst. Dafür erstellst du einfach ein öffentliches Feld in einer normalen MonoBehaviour-Klasse:

// Nur zum Testen
public class CardTest : MonoBehaviour
{
  public CardData card;
  
  private void Start()
  {
    Debug.Log("Titel der Karte: " + card.title);
    Debug.Log("Beschreibung der Karte: " + card.description);
  }
}

Diese Klasse ist nicht besonders sinnvoll, aber veranschaulicht dass du, sobald du eine Referenz auf ein CardData-Objekt hast, die Felder davon ansprechen und auslesen kannst. Denke daran, das CardData-Objekt aus deinen Assets auf diese Komponente zu ziehen, nachdem du sie auf ein GameObjekt gezogen hast.

Wenn du jetzt einen SpriteRenderer auf deinem Test-GameObject hast, kannst du auch so etwas machen:

var spriteRenderer = GetComponent<SpriteRenderer>();
spriteRenderer.sprite = card.image;

Und dann wird das Sprite, dass du dem CardData-Objekt in deinen Assets zugewiesen hast, vom SpriteRenderer angezeigt.

Jetzt kannst du dir ein Kartendeck zusammenstellen, indem du eine Liste oder ein Array von CardData-Objekten anlegst:

public CardData[] cards;

Dort kannst du jetzt deine 80 Karten reinziehen, nachdem du sie in einem Ordner in den Assets angelegt hast. Und dann kannst du mit allen Karten arbeiten.

Wenn du jetzt noch ein Karten-Prefab anlegst, dem du ein CardData-Objekt in die Hand drückst und es zeigt dieses dann an, dann hast du den größten Teil für ein System, das Karten anzeigt. Mit nur einem Prefab.

Falls du dich jetzt fragst: "Was soll denn das, dann hab ich doch immer noch 80 einzelne Objekte?"

Ja, die hast du. Aber...

  1. Du hast aber nur noch ein Prefab. Wenn du also z.B. das Hintergrundbild aller Karten ändern willst, musst du das eine Prefab abändern, und nicht alle 80.
  2. CardData-Objekte sind wesentlich kleiner als Prefabs, weil sie nur die Daten beinhalten, die du ihnen gibst, und kein Ballast wie die Transform-Komponente.
  3. Als alternative Möglichkeit hast du prinzipiell immer nur eine lange Liste. Sei es eine Textdatei, in die du alle Karten untereinander schreibst, oder eine Liste irgendwie in Unity. Diese Liste bedeutet ungefähr gleich viel Arbeit, erlaubt dir aber nie so schön, die CardData-Objekte so hin und her zu schieben, wie du es gerade haben willst.

Ich jedenfalls habe auch u.a. ein Kartenspiel in der Mache, und da mache ich das genau so ;)

  • Like 1

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×