Jump to content
Unity Insider Forum
ShV

Score und Highscore erstellen

Recommended Posts

Ich habe eine endless runner Game erstellt, wo man über Gegner springen muss oder sie abschießt. Immer wenn man es über einen Gegner geschafft hat, wird er am Ende von einem Collector aufgesammelt. Wenn man einen Gegner abschießt, wird er direkt deaktiviert. Wie kann ich pro deaktiviertem Gegner Punkte verteilen( die auch auf dem Bildschirm angezeigt werden) und diese als Highscore abspeichern sobald der Spieler tot ist?

Share this post


Link to post
Share on other sites

Da gibt's hässliche Wege, einen okay-en Weg und (mindestens?) einen schönen Weg. Für den einen schönen Weg mache ich einfach mal ein bisschen Werbung. In dem Thread gibt's unter anderem ein erklärendes Video.

Der okay-e Weg geht über simple statische Variablen. Um da direkt vorweg zu nehmen, warum der Weg weniger schön ist (aber halt okay): Wenn du bei diesem Weg ein zweites Punktekonto (wie eine zweite Währung) bauen willst, dann musst du direkt neuen Code für die Unterscheidung bauen, im Zweifelsfall direkt eine neue Klasse. Funktionieren tut's aber einwandfrei.

Da gibt's wieder mehrere Varianten... in diesem Fall würde ich eine statische Klasse mit Event nehmen.

using System;

public static class Score
{
  private static int _currentScore = 0;
  public static int currentScore
  {
    get { return _currentScore; }
    set
    {
      _currentScore = value;
      onScoreChange?.Invoke(value);
    }
  }
  
  public static event Action<int> onScoreChange;
}

Da sind jetzt ein paar fortgeschrittene Konzepte drin - weiß nicht, was davon noch Erklärung bräuchte. Sag einfach Bescheid.

Punktzahl ändern kannst du an jeder Stelle deines Codes dann über:

Score.currentScore += 10;

Reagieren tust du auf Änderungen der Punktzahl, indem Objekte von Interesse am Anfang ihres Lebens (oder der Aktivität) eine Reaktion zum Event hinzufügen und die Reaktion am Ende des Lebens/der Aktivität wieder austragen. Sieht bei der Anzeige z.B. so aus:

using UnityEngine;
using UnityEngine.UI;

[RequireComponent(typeof(Text))] // Damit kann diese Komponente nur auf GameObjects gelegt werden, die auch eine Textkomponente haben
public class ScoreDisplay : MonoBehaviour
{
  private Text text;
  
  private void Awake()
  {
    text = GetComponent<Text>();
  }
  
  private void OnEnable()
  {
    Score.onScoreChange += UpdateScore; // Reaktion "UpdateScore" hinzufügen
    UpdateScore(Score.currentScore); // Gleich mal den aktuellen Wert anzeigen, nicht erst bis zur ersten Änderung warten
  }
  
  private void OnDisable()
  {
    Score.onScoreChange -= UpdateScore; // Reaktion wieder austragen
  }
  
  private void UpdateScore(int newScore)
  {
    text.text = newScore + "";
  }
}

Beim Game Over kannst du die Punkte einfach auslesen:

public void GameOver()
{
  var finalScore = Score.currentScore;

Speichern kannst du die z.B. mit PlayerPrefs. Gibt auch andere Möglichkeiten, Dinge zu speichern, und welche davon gerade sinnvoll ist, hängt komplett von deinem Projekt ab. Wenn du außer Highscore oder Levelfortschritt nicht viel speicherst, dann sind PlayerPrefs sehr gut.

Geht dann so:

  PlayerPrefs.SetInt("Highscore", finalScore);

 

  • Thanks 1

Share this post


Link to post
Share on other sites

1. Wofür brauche ich beim ersten Code "using System;"

2. Wofür ist Invoke zuständig 

3. Wenn ich den ersten Code so eingebe, wie er oben steht, kommt bei mir die Fehlermeldung: "Score.currentScore": Instanzmember können nicht in einer statischen Klasse deklariert werden. Was bedeutet das?

 

Share this post


Link to post
Share on other sites
  1. Um die Action-Klasse nutzen zu können. Ein Action-Objekt repräsentiert eine Methode, die aufgerufen werden kann. Durch die Verwendung von "event" kann es mehrere methoden auf einmal repräsentieren. Mit += und -= fügt der andere Code eine Methode zum Event hinzu bzw. nimmt sie wieder weg.
  2. Invoke ruft die repräsentierten Methode(n) auf, die gerade hinzugefügt sind.
  3. Habe meinen Code korrigiert, es fehlen zwei "static"-Keywords.

Wenn noch etwas unklar ist, bitte gerne weiter fragen. Das ist schon fortgeschrittener Code, den man nicht übernehmen sollte, ohne ihn wirklich verstanden zu haben ;)

Share this post


Link to post
Share on other sites

Ich habe dieses Gegneroff Script geschrieben:

public class Gegneroff : MonoBehaviour
{
    
    private void OnTriggerEnter2D(Collider2D other)
    {
        if(other.tag == "Collector")
        {
            gameObject.SetActive(false);
            
        }
    }

Könnte ich den Scorezähler in diesem Script mithilfe einer if-Abfrage erstellen? Und wenn ja. Wäre das ein guter Ansatz bzw. wie sollte ich weitermachen:

public class Gegneroff : MonoBehaviour
{
    private static int _currentScore = 0;
    public static int currentscore;
    private void OnTriggerEnter2D(Collider2D other)
    {
        if(other.tag == "Collector")
        {
            gameObject.SetActive(false);
            if (gameObject.SetActive(false))
            {

            }
        }
    }

Share this post


Link to post
Share on other sites

Es sieht irgendwie aus, als hättest du irgendwelche Zeilen dahingepackt, die irgendwie passend klingen. Das ist ein Ansatz, mit dem man nicht sonderlich weit kommt. Du musst die Einzelteile des Codes verstehen und dann bewusst zusammensetzen. Was du jetzt tun kannst: Zum einen kannst du erstmal aufschreiben - als Text oder als Diagramm, was du genau willst, was am Ende passiert. Einfach auf Deutsch: "Objekt betritt Trigger - Es wird geschaut, ob es der Spieler ist - Wenn ja, dann..." und zum anderen darfst du mich (und alle anderen hier) gerne mit beliebig vielen Fragen darüber löchern, wie man die Einzelteile deines Konzepts implementiert, oder auch was die Einzelteile meines Codes da oben machen.

Share this post


Link to post
Share on other sites

Wo liegt der Unterschied zwischen "public static class" und "public class"?Was bewirkt das "static"?

Share this post


Link to post
Share on other sites

Statische Variablen und Methoden sind nicht an Objektinstanzen gebunden. So als Beispiel: Wenn du zwei Lichter hast, kannst du eines rot und das andere blau färben. Jede Instanz der Klasse "Light" hat seinen eigenen Wert für die Farbeigenschaft. "Light.color" ist daher nicht statisch. Wäre diese Eigenschaft statisch, könnte man "Light.color" setzen (also direkt in der Klasse!) und die Farbe wäre dann für alle Licht-Komponenten gleichermaßen gültig. Eine statische Variable gibt es daher nur einmal pro Programm, innerhalb der Klasse, und nicht einmal pro Objekt.

Eine statische Klasse ist eine ganz normale Klasse, aber mit drei Einschränkungen:

  1. Man kann keine Instanzen davon erstellen.
  2. Man kann damit nicht Vererbungsdinge tun.
  3. Alle Member (Variablen, Eigenschaften und Methoden) müssen statisch sein.

So als Beispiel für eine statische Klasse wäre Unitys Mathf-Klasse. Du sagst einfach Mathf.Lerp, was eine statische Methode ist, und fertig. Du musst nicht ein Mathf-Objekt erstellen oder finden, um dann dieses Objekt die Methode ausführen zu lassen. Genau genommen kannst du das nicht einmal.

Im Fall der Score-Klasse gilt genau dasselbe. Du kannst keine Score-Objekte erstellen, sondern nur direkt mit "Score.currentScore" arbeiten. Es gibt also nur eine Punktzahl im gesamten Programm.

Share this post


Link to post
Share on other sites

Was bewirkt das:

 public static int currentScore
  {
    get { return _currentScore; }
    set
    {
      _currentScore = value;
      onScoreChange?.Invoke(value);
    }

Share this post


Link to post
Share on other sites

Das ist eine so genannte "Property". Sie tut so, als wäre sie eine Variable, aber man definiert, was passieren soll, wenn man den Wert ausliest oder setzt.

Der Code ist äquivalent zu diesem:

private int _currentScore;

public int GetCurrentScore()
{
  return _currentScore;
}

public void SetCurrentScore(int value)
{
  _currentScore = value;
  onChangeScore?.Invoke(value);
}

So würde man das z.B. auch in Java machen, weil es da keine Propertys gibt.

Wenn man nun aber eine Property benutzt, dann benutzt man diese wie eine Variable. Statt

Debug.Log(Score.GetCurrentScore());

schreibt man beim Auslesen

Debug.Log(Score.currentScore);

und beim Setzen des Wertes statt

Score.SetCurrentScore(10);

dann

Score.currentScore = 10;

Das wird besonders beim Erhöhen hübsch, weil man da statt

Score.SetCurrentScore(Score.GetCurrentScore() + 5);

einfach schreiben kann

Score.currentScore += 5;

Der ganze Kram ist jedenfalls dafür da, dass beim Setzen des Wertes das Event aufgerufen wird.

Share this post


Link to post
Share on other sites

Das hat mit Unitys Eventsystem nichts zu tun - es wird die Action-Klasse benutzt, die Teil von C# ist.

Share this post


Link to post
Share on other sites

Mein Code dafür sieht jetzt wie folgt aus:

using System; ///Brauche ich, um die Action-Klasse nutzen zu können
    // Ein Action-Objekt repräsentiert eine Methode, die aufgerufen werden kann. Durch die Verwendung von "event" kann es mehrere methoden auf einmal repräsentieren. Mit += und -= fügt der andere Code eine Methode zum Event hinzu bzw. nimmt sie wieder weg.

public static class Zähler //Statische Variablen und Methoden sind nicht an Objektinstanzen gebunden
{
    private static int _currentscore = 0;
    public static int currentscore
    {
        get { return _currentscore; }
        set
        {
            _currentscore = value; //value ist auf deutsch Wert
            onScoreChange?.Invoke(value); 
        }                           
    }

}

public static event Action<int> onScoreChange; (habe diesen Code jetzt verstanden, waas der macht)

dann noch den UI-Code von oben (weiß auch da, was die einzelnen Befehle bedeuten) und mein eigenes Script:


public class Gegneroff : MonoBehaviour
{
    
    private void OnTriggerEnter2D(Collider2D other)
    {
        if(other.tag == "Collector")
        {
            gameObject.SetActive(false);
            Zähler.currentscore += 10; //Mit "Zähler" auf das Script verwiesen und mit "currentscore" auf die Variable im Script
        }
    }

Das UI-Script habe ich dann als Component zu meinem Text hinzugefügt. Aber wenn die Gegner deaktiviert werden passiert trotzdem nichts. Was muss ich machen, damit es Funktioniert?

Share this post


Link to post
Share on other sites

Hast du auch auf deinem Text ein Script, das auf das Event reagiert und die neue Punktzahl anzeigt?

Share this post


Link to post
Share on other sites

Achso ich dachte dafür sei das zweite Script.

1. Wofür ist das zweite Script?

2. Wie würde so ein Script aussehen?

Share this post


Link to post
Share on other sites

Du hast drei Klassen:

  1. Die statische Klasse, die sich die Punktzahl für das gesamte Programm merkt und ein Event hat, das bei einer Änderung ausgelöst wird.
  2. Eine Komponente für die Gegner, die die Punktzahl erhöht, wenn einer stirbt.
  3. Eine Komponente für dein Text-Objekt, das als Reaktion auf das Event den Inhalt des Textes ändert.

1 und 2 hast du - wie 3 aussieht habe ich in meinem ersten Beitrag geschrieben.

Share this post


Link to post
Share on other sites
Am 31.3.2020 um 19:23 schrieb Sascha:

using UnityEngine; using UnityEngine.UI; [RequireComponent(typeof(Text))] // Damit kann diese Komponente nur auf GameObjects gelegt werden, die auch eine Textkomponente haben public class ScoreDisplay : MonoBehaviour { private Text text; private void Awake() { text = GetComponent<Text>(); } private void OnEnable() { Score.onScoreChange += UpdateScore; // Reaktion "UpdateScore" hinzufügen UpdateScore(Score.currentScore); // Gleich mal den aktuellen Wert anzeigen, nicht erst bis zur ersten Änderung warten } private void OnDisable() { Score.onScoreChange -= UpdateScore; // Reaktion wieder austragen } private void UpdateScore(int newScore) { text.text = newScore + ""; } }

Das hier, oder?

Das habe ich nämlich als Komponente meinem Text hinzugefügt, aber es passiert trotzdem nichts

Share this post


Link to post
Share on other sites

Dann gehts ans Debuggen! Pack da Debug.Logs rein oder setze Breakpoints im Debugger, schaue in die Konsole und finde heraus, wo es hakt. Wird die Punktzahl nicht verändert? Wird sie verändert aber das Event nicht ausgelöst? Wird das Event ausgelöst aber das Text-Script reagiert nicht?

Share this post


Link to post
Share on other sites

Das Text-Script reagiert nicht. Was kann ich machen, damit es reagiert?

Edited by ShV

Share this post


Link to post
Share on other sites
Am 31.3.2020 um 19:23 schrieb Sascha:

using UnityEngine; using UnityEngine.UI; [RequireComponent(typeof(Text))] // Damit kann diese Komponente nur auf GameObjects gelegt werden, die auch eine Textkomponente haben public class ScoreDisplay : MonoBehaviour { private Text text; private void Awake() { text = GetComponent<Text>(); } private void OnEnable() { Score.onScoreChange += UpdateScore; // Reaktion "UpdateScore" hinzufügen UpdateScore(Score.currentScore); // Gleich mal den aktuellen Wert anzeigen, nicht erst bis zur ersten Änderung warten } private void OnDisable() { Score.onScoreChange -= UpdateScore; // Reaktion wieder austragen } private void UpdateScore(int newScore) { text.text = newScore + ""; } }

Außer diesem Script reagieren alle Scripts. Ich habe da überall Debug.Logs eingesetzt, aber in diesem Script wird nichts ausgelöst. Was kann ich machen, damit es funktioniert?

Share this post


Link to post
Share on other sites

Wenn nicht einmal bei Awake etwas in der Konsole auftaucht, hast du das Script nicht auf ein aktives GameObject gezogen.

Share this post


Link to post
Share on other sites

Wenn du

  1. keine Fehlermeldungen beim Kompilieren hast,
  2. dein Script auf ein aktives GameObject in einer offenen Szene gezogen hast,
  3. in Awake ein Debug.Log drinhast,
  4. die Komponente nicht deaktiviert hast und
  5. Play Mode anmachst,

dann taucht das auch in der Konsole auf.

Share this post


Link to post
Share on other sites

Ok habe den Fehler hierbei gefunden. Jetzt beginnt der Spieler aber direkt mit 010 Punkten und jedes mal, wenn sich die Punktzahl erhöhen sollte steht in der Konsole die Fehlermeldung "Missing Reference Exception: The object of type 'Text' has been destroyed but you are still trying to acces it. Your script should either check if it is null or you should not destroy the object". Was muss ich jetzt machen? Und wenn ich das Spiel mehrmal starte verändert sich die Punktzahl Plötzlich so: 010, 8010, 1010. Die Punktzahl wird auch nicht direkt im Spiel aktuallisiert, sondern erst, wenn ich wieder auf Play gedrückt habe.

 

Share this post


Link to post
Share on other sites

Ich weiß ja nicht, was du gemacht hast, aber den Code richtig übernommen hast du nicht. Poste doch bitte Mal deinen ganzen (hierfür relevanten) Code, wie er jetzt aussieht.

Hast du geschaut, von welcher Klasse die Fehlermeldung kommt? Wenn sie tatsächlich von meiner Komponente kommt, und du sie tatsächlich 1:1 übernommen hast, dann ist die einzige Möglichkeit, die mir einfällt, um so eine Meldung zu kriegen, die Komponente auf einem GameObject zu platzieren, bevor sie überhaupt den Code enthält. Durch das [RequireComponent] kann die Text-Komponente ansonsten nicht fehlen.

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...

×
×
  • Create New...