Jump to content
Unity Insider Forum

Frage zu Destroy


markusk
 Share

Recommended Posts

Hallo,

ich programmier gerade einen 2D Shooter und zwar hab ich da ein Raumschiff daß ich in alle Richtungen bewegen bzw. mit der linken Strg-Taste feuern lassen kann.

Für die Geschosse hab ich ein Prefab gemacht und jedesmal wenn Strg gedrückt wird erzeuge ich eine Instanz desselbigen und merke es mir gleichzeitig in einer List<GameObject>.

Dannach geh ich diese Liste immer durch und erhöhe die x-Position des aktuellen Geschosses um einen bestimmten Wert.

Dadurch bewegen sie sich nach rechts und wenn sie über den rechten Rand hinaus sind will ich sie erstens aus der erwähnten List<GameObject> löschen und zweitens soll es auch aus Unity-Sicht mit Destroy destroyed werden.

Im Moment mach ich es so daß ich mir die Objekte die über den Rand raus sind in einer separaten Liste merke und die dann durchlaufe bzw. die Objekte lösche.

Und zwar hab ich im Spieler-Skript folgendes programmiert, sollte das so passen aus eurer Sicht?

if (Input.GetButtonDown("Fire1")) {
            float xSpieler = transform.position.x;
            float ySpieler = transform.position.y;

            float xGeschossStart = xSpieler + 1.5f;
            float yGeschossStart = ySpieler;

            GameObject geschosss = Instantiate(prefabGeschossGruen, new Vector3(xGeschossStart, yGeschossStart, 0), Quaternion.identity);

            geschosse.Add(geschosss);
        }

        foreach(GameObject geschoss in geschosse) {
            if (geschoss != null) {
                float x = geschoss.transform.position.x;

                x += 1.7f * eingabeFaktor * Time.deltaTime;

                if (x > 9.05f) {
                    geschosseToDestroy.Add(geschoss);
                } else {
                    geschoss.transform.position = new Vector3(x, geschoss.transform.position.y, 0);
                }
            }
        }

        for (int i = geschosseToDestroy.Count-1; i >= 0; i--) {
            Destroy(geschosseToDestroy[i]);
        }

        geschosseToDestroy.Clear();

lg, Markus

Link to comment
Share on other sites

Moin!

Es gibt keinen Grund, eine "geschosseToDestroy"-Liste anzulegen. Anstatt die Geschosse da einzutragen, kannst du sie auch direkt zerstören.

Was ich aber eigentlich noch wichtiger finde: Warum hast du ein zentrales Script, das deine Geschosse verwaltet? Warum verwaltet sich nicht jedes Geschoss einfach selber? Einfach die Bewegung und die Zerstörung von einem Script machen lassen, das du auf dein Prefab packst. Je weniger deine Komponenten andere Objekte verwalten, desto besser.

Link to comment
Share on other sites

Warum nicht Objekt Pooling?

Damit kannst du einstellen, wieviel Geschosse du verwalten willst und die werden in einer Liste registriert. Und anstatt diese dann zu löschen, werden sie wieder nach gebrauch, in der Liste Freigegeben. So hast du immer z.b. 50 Geschosse reserviert.

Schau mal hier 

https://learn.unity.com/tutorial/introduction-to-object-pooling#5ff8d015edbc2a002063971d

Sascha hatte auch glaub mal dazu was geschrieben, postet er bestimmt auch gleich :D

Link to comment
Share on other sites

Nö, hatte ich nicht vor :D

Ich meine... Pooling schadet hier höchstwahrscheinlich nicht, aber es ist erstmal auch nicht unbedingt nötig. Performance soll man halt dann in Angriff nehmen, wenn sie zu einem Problem zu werden droht. Ich fand die Geschichte mit den autonomen Objekten erstmal wichtiger.

Link to comment
Share on other sites

Na ich dachte, nachdem ich Pooling erwähne, kommst du nochmal :D

Ja schlecht ist es nicht, jedes Objekt eigenes Script zu geben, was sich selbst zerstört.

Würd gern wissen, was Permance Mäßig besser ist. Alles zu Anfang erstellt und reserviert oder nach und nach.

Hat sicher beides seine vor und Nachteile.

 

Link to comment
Share on other sites

Hallo,

also ich hab das alles mal so umgebaut daß im Spieler-Skript nur mehr auf den Tastendruck für's Feuern abgefragt wird und wenn die Taste gedrückt wird erzeuge ich ein Geschoß aus dem Prefab und platziere es vor dem Raumschiff. Die Bewegung und das Zerstören übernimmt das Geschoß selbst.

Konkret sieht das bei mir dann so aus daß mit der linken Strg-Taste ein rotes und mit der rechten Strg-Taste ein grünes Geschoß erzeugt wird. Hab in meine Szene eine einfache weiße senkrechte Wand eingebaut und wenn ein Geschoß auf die Wand trifft wird ein Partikeleffekt in der Farbe des Geschosses erzeugt der die Exlosion anzeigen soll und das Geschoß zerstört sich selbst. Scheint gut zu funktionieren, man sieht das auch ganz deutlich in der Hierarchy-View wie die Instanzen erzeugt und dann wieder zerstört werden.

lg, Markus

public class Spieler : MonoBehaviour
{
    private readonly float eingabeFaktor = 10.0f;
    public GameObject geschossRotPrefab;
    public GameObject geschossGruenPrefab;

    void Start()
    {
        
    }


    void Update() {
        float xEingabe = Input.GetAxis("Horizontal");
        float yEingabe = Input.GetAxis("Vertical");

        if (xEingabe != 0) {
            float xNeu = transform.position.x + xEingabe * eingabeFaktor * Time.deltaTime;

            if (xNeu > 3.0f) {
                xNeu = 3.0f;
            } else if (xNeu < -7.7f) {
                xNeu = -7.7f;
            }

            transform.position = new Vector3(xNeu, transform.position.y, 0);
        }

        if (yEingabe != 0) {
            float yNeu = transform.position.y + yEingabe * eingabeFaktor * Time.deltaTime;

            if (yNeu > 4.5f) {
                yNeu = 4.5f;
            } else if (yNeu < -4.5f) {
                yNeu = -4.5f;
            }

            transform.position = new Vector3(transform.position.x, yNeu, 0);
        }

        if (Input.GetKeyDown(KeyCode.LeftControl)) {
            Instantiate(geschossRotPrefab, new Vector3(transform.position.x + 1.7f, transform.position.y, 0), Quaternion.identity);
        }

        if (Input.GetKeyDown(KeyCode.RightControl)) {
            Instantiate(geschossGruenPrefab, new Vector3(transform.position.x + 1.7f, transform.position.y, 0), Quaternion.identity);
        }
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GeschossScript : MonoBehaviour
{
    private static GameObject explosionRotPrefab;
    private static GameObject explosionGruenPrefab;

    void Start()
    {
        Debug.Log("neues geschoss");

        if (explosionRotPrefab == null) {
            explosionRotPrefab = Resources.Load<GameObject>("prefabs/ExplosionRot");
        }

        if (explosionGruenPrefab == null) {
            explosionGruenPrefab = Resources.Load<GameObject>("prefabs/ExplosionGruen");
        }
    }

    void Update()
    {
        float xNeu = transform.position.x + 0.5f * 20.0f * Time.deltaTime;

        if (xNeu > 9) {
            Destroy(this.gameObject);
        } else {
            transform.position = new Vector3(xNeu, transform.position.y, 0);
        }
    }

    public void OnTriggerEnter2D(Collider2D collision) {
        GameObject gameObject = collision.gameObject;

        if (gameObject.CompareTag("IstWand")) {
            if (this.gameObject.CompareTag("IstGeschossRot")) {
                Debug.Log("rotes geschoss hat wand getroffen");
                
                Instantiate(explosionRotPrefab, this.gameObject.transform.position, Quaternion.identity);
            } else if (this.gameObject.CompareTag("IstGeschossGruen")) {
                Debug.Log("grünes geschoss hat wand getroffen");
               
                Instantiate(explosionGruenPrefab, this.gameObject.transform.position, Quaternion.identity);
            }

            Destroy(this.gameObject);
        }
    }
}

 

Link to comment
Share on other sites

Ja, an sich funktioniert das.
Du machst da aber ein paar Dinge, die doppelt gemoppelt sind.
Denn:

Du hast 2 Prefabs für die Geschosse und du hast 2 Prefabs für die Explosionen. Gut.
Der Player hat 2 Slots für die 2 unterschiedlichen Geschosse. Gut.

Deine Geschosse haben 2 static Variablen für die Explosionen. Putzig.
Deine Geschosse laden sich beide Explosionen rein, obwohl ja nur eine genutzt wird. Putzig.
Wenn dein Geschoss die Wand trift, wird entschieden welche Explosion geziegt werden soll. Putzig.

Ich würde es anders machen und du solltest es auch.

Und zwar:
Dein Player hat für die beiden Geschosse ja public Variablen angelegt. Dort kannst du einfach die Prefabs im Inspector rein legen und musst sie nicht extra nachladen.
Gut, zur Sicherheit, falls du vergessen hast die Variablen zu bestücken und somit die Variable =null ist, kann man natürlich dieses Nachladen machen.
Aber eigentlich braucht man das nicht.

Dein Geschoss hat für die Explosionen "statc" Variablen genutzt. Da sehe ich absolut keinen Sinn drin. Static macht man nur etwas, wenn es global gültig und einzigartig sein soll!
Auch in diesem Script lädst du prefabs nach, was du nicht müsstest, wenn du die Variable public machen würdest und sie einfach im Inspector bestücken würdest.
Vor allem stört mich, dass du beide Explosionen rein holst, obwohl doch klar ist, dass das rote Geschoss nur rot explodiert und das blaue nur blau!

Ich würde diesem Script nur eine Explosionsvariable geben, die entweder public ist oder private und das Attribut [SerializeField] davor stehen hat. Dieses Attribut gibt dir die Möglichkeit eine private Variable trotzdem im Inspector zu bestücken.

[SerializeFild] private GameObject Geschoss;

Aber wenn du einfach nur public nutzen willst, ist es auch voll ok.

So, weil ein rotes Geschoss ja nur rot explodiert, legst du also in den Variablenslot einfach das rote Explosionsobjekt rein und  übernimmst das im Prefab. Das Gleiche machst du mit dem blauen Explosionsprefab.
Jetzt muss dein Geschoss überhaupt nicht mehr überprüfen, ob es blau oder rot ist. Es ist ja alles schon festgelegt.

Und jetzt noch etwas wichtiges zum gameObject mit kleinem g :

Unity erzeugt für dich automatisch eine Verknüpfung zu deinem GameObject und nennt es gameObject mit kleinem g!
Das hier passiert also im Hintergrund für dich:

private GameObject gameObject;

gameObject= GetComponent<GameObject>();

Da dieses gameObject das Object selbst ist, brauchst du kein this mehr, denn es ist klar definiert. Du kannst es natürlich weiterhin nutzen, wenn es dir bei der lesbarkeit hilft.

Du solltest aber aus diesem Grund auch gameObject nicht mit einem anderen GameObject verknüpfen, so wie du es bei deiner Abfrage mit der Wand gemacht hast. Egal ob diese Variable nur innnerhalb eine Methode/Funktion genutzt wird, oder nicht.
Das kann zu unschönen Dingen führen. Nutze lieber einen anderen Namen, der eben nicht schon von Unity genutzt wird.

Du musst auch beim Triggern keine neue Variable bilden, denn das passiert da oben ja schon. Der Collider2D des getriggerten Objektes wird der angegebenen Variable übergeben. Bei dir heisst sie collision (was auch unschön ist weil du ja triggerst und nicht kollidierst) und Untiy schlägt dir other vor.

private void OnTriggerEnter2D(Collider2D other){
  if (other.CompareTag("istWand"){
    // explodiere
  }
}

Alle Komponenten eines Objektes erben dessen Tag. Deswegen musst du nicht extra das GameObject verknüpfen.

So.
Das war mein Senf dazu. :)
Mir ist klar, dass du dich erst noch so richtig in Unity einarbeiten musst und dir ersteinmal die Funktion wichtig ist. Deswegen nimm das einfach so als Anmerkung.
 

Link to comment
Share on other sites

Hallo,

danke für die vielen Verbesserungsvorschläge :) Hätte jedoch einige Fragen dazu.

Fangen wir mit dem Player an:

Du hast geschrieben daß ich die beiden Geschoss-Prefabs im Editor direkt in die zwei public Slots ziehen kann und sie nicht extra nachladen brauche.

Genau so hab ich's gemacht, ich hab die beiden Geschoss-Prefabs in die zwei public Slots vom Skript Spieler.cs gezogen.

Und je nachdem ob die rechte oder linke Strg-Taste gedrückt wird instanziere ich mit Instantiate ein grünes oder ein rotes Geschoss.

Also das Spieler-Skript sollte eigentlich soweit passen oder?

Deine Anmerkungen zum Geschoss-Skript kann ich verstehen und ich hab es folgendermaßen geändert:

public class GeschossScript : MonoBehaviour
{
    public GameObject explosionPrefab;

    void Start()
    {
        Debug.Log("neues geschoss");
    }

    void Update()
    {
        float xNeu = transform.position.x + 0.5f * 20.0f * Time.deltaTime;

        if (xNeu > 9) {
            Destroy(this.gameObject);
        } else {
            transform.position = new Vector3(xNeu, transform.position.y, 0);
        }
    }

    public void OnTriggerEnter2D(Collider2D trigger) {
        GameObject triggerObject = trigger.gameObject;

        if (triggerObject.CompareTag("IstWand")) {
            Instantiate(explosionPrefab, gameObject.transform.position, Quaternion.identity);
            
            Destroy(gameObject);
        }
    }
}

In den Geschoss-Prefabs hab ich über den public slot das jeweilige Explosions-Prefab reingezogen.

Sieht schon viel besser aus finde ich und funktionieren tut's auch :)

lg, Markus

 

 

Link to comment
Share on other sites

Ja genau. Wenn du diese Geschosse im Inspector hinzugefügt hast, brauchst du sie nicht mehr nachladen, denn sie sind ja schon mit den Variablen verknüpft.

Und bei den Geschossen selber, wo du ja jetzt nur eine Explosion einlädtst, musst du dir immer vor Augen halten, dass ein Prefab soetwas wie eine Baupause ist.
Also eine Bauanleitung mit gewissen festgelegten Eigenschaften, die von dir aber mit unterschiedlichen Dingen bestückt werden kann und soll.

Du baust ja aus unterschiedlichen DIngen etwas zusammen. Du nimmst ein oder mehrere geometrische(s) Objekt(e), gibst ihm ein Material, bestimmst die Größen, hängst ein Script an, gibst ihm physikalische Eigenschaften und vielleicht sogar noch einen Sound. Dieses machst du zu einem Prefab also ein vorgefertigtes Produkt und kannst es sofort im Spiel verwenden. Auch die Dinge, die innerhalb einer Komponente variabel sind, wie z.B. die Explosion, die im Script hinzugefügt wurde, oder aber ein ganz bestimmter Sound, der der Audiosource hinzugefügt wurde, sind Bestandteile des Prefabs.
Ein Prefab erstellt man ja gerade weil man nicht alles im Code zusammenbauen will. Natürlich kann man per Code ein Objekt dynamisch zusammenbauen und sich die Komponenten nehmen, die man so haben will. Aber sinnvoll ist so etwas nur in ganz wenigen Fällen. Mir würde da ein konfigurierbarer Charakter einfallen, dem du unterschiedliche Outfits geben könntest.

Beim Triggern machst du aber immer noch unnötige Dinge:

vor 13 Stunden schrieb markusk:
GameObject triggerObject = trigger.gameObject;

Wie ich oben schon geschrieben habe, musst du nicht das GameObject des getriggerten Colliders holen um den Tag zu überprüfen.
Alle Komponenten, die an einem Gameobject hängen, erben den Tag des GameObjects (damit meine ich aber nicht irgendwelche Unterobjekte, die ja selber wieder GameObjects sind und nur mitgeschleift werden, wie ein eigenständigens Rad an einem Auto) .

Wenn du hier in der Scripting API mal den Bereich Inherit Members anschaust (Inherit = erben), dann siehst du all die Dinge die vom GameObject oder dem Object an sich weitervererbt werden.
https://docs.unity3d.com/ScriptReference/Collider2D.html

Du musst also keine weitere Variable bilden den Tag des GameObjects zu erfahren. Der Collider hat den Tag selber!

Also kannst du gleich  sowas schreiben:
 

public void OnTriggerEnter2D(Collider2D trigger) {
  if (trigger.CompareTag("IstWand")) {
    
  }
}

 

Link to comment
Share on other sites

Hallo,

nun will ich gegnerische Raumschiffe erzeugen welche sich von rechts nach links auf mein eigenes zubewegen.

Ok, für die Gegner hab ich mir wieder ein Prefab gemacht weil ich ja beliebig viele erzeugen will.

Für den Anfang wollte ich mal folgendes machen:

Wenn man auf G drückt soll ein Gegner auf 2, 2, 0 erzeugt werden.

Nur mal um einen sichtbar zu machen.

public class Spieler : MonoBehaviour
{
    private readonly float eingabeFaktor = 10.0f;
    public GameObject geschossRotPrefab;
    public GameObject geschossGruenPrefab;
    public GameObject gegnerPrefab;

    private void Start() {
       
    }

    void Update() {
        // hier war der bisherige code

        
        if (Input.GetKeyDown(KeyCode.G)) {
            Instantiate(gegnerPrefab, new Vector3(2, 2, 0), Quaternion.identity);
            
        }
    }
}

Ich hab hier einen public Slot für das Gegner-Prefab ergänzt und im Editor das Prefab auf diesen Slot gezogen.

Wenn ich das Spiel nun starte und auf G drücke dann krieg ich die Fehlermeldung:

UnassignedReferenceException: The variable gegnerPrefab of Spieler has not been assigned.
You probably need to assign the gegnerPrefab variable of the Spieler script in the inspector.

Was mach ich hier falsch? Denn ich hab ja eigentlich genau das gemacht - im Editor das Prefab assigned.

lg, Markus

Link to comment
Share on other sites

Is den der Player evtl. auch ein Prefab, welches neu instanziert wird?
Dann könnte es sein, dass das Prefab selbst noch keinen gegner im Slot hat, sondern nur der Player, der vor in der Szene drin war.
 

Du musst einfach mal nach dem Start des Spiels auf pause gehen und dann deinen player anschauen. Ist da noch das Prefab im Slot?

Link to comment
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...
 Share

×
×
  • Create New...