Jump to content
Unity Insider Forum

"Risse" in Objekt/ Textur


Kojote

Recommended Posts

Grüße!

Mal eine Frage bezüglich Textur und Effekten. Ich habe mir ein Glasmaterial erstellt und möchte gern, dass wenn eine Kollision stattfindet Risse auf dem Glas entstehen. Des weiteren möchte ich, dass das Glas bei einem bestimmten Zustand zerbricht und die Glasscherben in eine bestimmte Richtung fallen. Kann mir wer verraten, wie man so etwas macht bzw. welche Suchbegriffe ich nutzen kann?

Danke schon mal! :)

Kojote

Link zu diesem Kommentar
Auf anderen Seiten teilen

Du musst einfach nur bei der Kollision die Textur ändern. Also du erstellst einfach 2 Glastexturen. Eine normale und eine von dem zerbrochenen Glas. Dann machst du ein Script mit OnCollisionEnter und weißt dem Renderer dein Materiel mit der Zerbrochen Textur zu. 

Bei dem Zerbrechen ersetzt du dein Glas objekt mit mehreren kleinen Glassplittern und gibts denen ein Rigidbody mit Force.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Kleiner Zusatz zum Zerbrechen: Es gibt einige Möglichkeiten, die kleinen Glassplitter automatisiert zu erzeugen. Das macht man in der Regel vorher, also nicht direkt ingame, und tauscht dann zur Laufzeit die Scheibe mit den bereits kalkulierten Bruchstücken aus. Einige Pakete machen das auch zur Laufzeit, aber du musst für dich entscheiden, ob du das wirklich brauchst. Das Stichwort hier heißt in jedem Fall "Mesh Fracturing".

Link zu diesem Kommentar
Auf anderen Seiten teilen

Hab ich genau so gemacht, Sascha! :)

Hab dieses schöne Tutorial gefunden:

Habe eine Glasplatte genommen und eine zweite, die ich in Teile zerlegte. Beide habe ich in Unity genau übereinander gelegt.

Alle Bruchstücke haben einen Ridgidbody und einen Mesh Collider bekommen. Wie auch im Video ein Glasmaterial und das Script:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Glasbruch : MonoBehaviour {

    public Transform glasBruchObjekt;
    public float magnitudeCol;

    private void OnCollisionEnter(Collision collision) {
        if(collision.relativeVelocity.magnitude > magnitudeCol) {
            Destroy(gameObject);
            Instantiate(glasBruchObjekt, transform.position, transform.rotation);
            glasBruchObjekt.localScale = transform.localScale;
        }
    }
}

Problem, sobald ich es teste kommt das:

Non-convex MeshCollider with non-kinematic Rigidbody is no longer supported in Unity 5.
If you want to use a non-convex mesh either make the Rigidbody kinematic or remove the Rigidbody component. Scene hierarchy path "Bodenplatte Glas Zerschnitten/Objekt001", Mesh asset path "Assets/Modelle/Bodenplatte Glas Zerschnitten.FBX" Mesh name "Objekt001"

Leider versteh ich nicht ganz was er will, was ist ein Non-convec MeshCollider?

Habe nun versucht mit IsKinemativ zu arbeiten, keine Fehlermeldung mehr, aber auch kein Glasbruch, kann mir da einer helfen?


EDIT:

Hab den kleinen Schalter für Convec im Mesh-Collider gefunden! :)

EDIT 2:

Ich habs nun so gemacht: Kleine Einzelteile bekommen einen Mesh Collider, mit Convex, dazu einen Rigidbody mit Use Gravity. Mein Spieler soll durch die Glasscheibe nach unten fallen. Um dies etwas zu beschleunigen, habe ich folgendes in meinem Quellcode:
 

transform.Translate((Vector3.down * 20.0f * Time.deltaTime), Space.Self);

Das beschleunigt seinen Absturz und passt auch so. Problem ist, dass auf der hälfte des herunter fallens nun mein Charakter die Glassscheiben "überholt", was sichtlich unschön aussieht. Habe nun die Masse der Einzelteile schon auf 300 erhöht, hat Besserung gebracht, aber nur bedingt. Helishcoffe meinte ich solle Force beim Rigidbody einsetzen, Problem es sind 29 Glasscherben die herunter fallen. Muss ich den Befehl nun jeder einzelnen Scherbe geben oder kann man das auch eleganter lösen?

Link zu diesem Kommentar
Auf anderen Seiten teilen

Eine 300 kilo schwere Glasscherbe? :D

Denke daran, dass schwerere Objekte nicht schneller fallen.

JamesMcConnell-1964-Galileo-s.jpg

Transform.Translate ist, besonders mit Space.Self, keine gute Idee. Wenn sich eine Scherbe dreht, fleigt sie plötzlich nach oben? Und selbst mit Space.World hast du eine erhöhte Geschwindigkeit, aber realistisch wäre eine erhöhte Beschleunigung. Nimm lieber Rigidbody.AddForce.

Nicht 29 Mal dasselbe Script auf lauter Objekte desselben Systems zu legen, ist aber eine gute Idee. Du kannst dir die Rigidbodys raussuchen und zentralisiert mit einer Schleife bei jedem davon AddForce aufrufen.

Übrigens änderst du mit deinem Code den Prefab und nicht die Instanz:

Instantiate(glasBruchObjekt, transform.position, transform.rotation);
glasBruchObjekt.localScale = transform.localScale;

Richtig wäre:

var instance = Instantiate(glasBruchObjekt, transform.position, transform.rotation);
instance.localScale = transform.localScale;

Und dann im Anschluss kannst du machen:

shards = instance.GetComponentsInChildren<Rigidbody>();

shards ist ein Array von Rigidbodys, das du außerhalb von Update definieren musst:

private Rigidbody[] shards;

Dann kannst du in FixedUpdate AddForce bei allen aufrufen:

foreach(var shard in shards)
{
  shard.AddForce(Vector3.down * extraGravity);
}

Jetzt ist aber noch eine Sache, die du machen solltest: Irgendwann aufhören. Die realistischste Lösung wäre, bei jedem Rigidbody regelmäßig zu checken, ob er "eingeschlafen" ist. Diesen Zustand hat er, wenn er liegengeblieben ist und sich nciht mehr bewegt. Die Methode, die dir das sagt, heißt Rigidbody.IsSleeping. Wenn der Rigidbody nun schläft, könntest du ihn löschen, damit er nicht mehr weiter Leistung frisst. Du musst dann natürlich auch aufhören zu versuchen, AddForce bei ihm aufzurufen. Sag bescheid, wenn du dabei Hilfe brauchst.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Also bisher siehts so aus:

public class Bodenschaden : MonoBehaviour {

    private int anzahlBegehungen = 1;
    private MeshRenderer meshrend;
    private BoxCollider boxcoll;
    public Transform zerbrochenesGlas;
    private Rigidbody[] shards;


    private void Awake() {
        meshrend = GetComponent<MeshRenderer>();
        boxcoll = GetComponent<BoxCollider>();
    }


    private void FixedUpdate() {
        foreach (var shard in shards) {
            shard.AddForce(Vector3.down * 20);
        }
    }


    private void OnTriggerEnter(Collider charakter) {
        if (anzahlBegehungen == 0) {
            // Es wird das erste mal die Plattform betreten, Risse werden erzeugt 
        } else {
            // Es wird das zweite mal die Plattform betreten, es folgt der Absturz
            StartCoroutine("Glasbruch");
        }
    }


    IEnumerator Glasbruch() {
        yield return new WaitForSeconds(0.5f);
        boxcoll.enabled = false;
        meshrend.enabled = false;
        var instance = Instantiate(zerbrochenesGlas, transform.position, transform.rotation);
        instance.localScale = transform.localScale;
        shards = instance.GetComponentsInChildren<Rigidbody>();
    }
}

Bekomme aber bei deinem Fixed Update einen Fehler:

NullReferenceException: Object reference not set to an instance of an object
Bodenschaden.FixedUpdate () (at Assets/Scripts/Bodenschaden.cs:27)


Kann jetzt leider selber nicht sagen, was er da möchte. Mit "foreach" und "in" steh ich noch etwas auf Kriegsfuß, so richtig versteh ich immer noch nicht, wie dieser Schleifentyp funktioniert. <_<

Zudem hab ich noch nen Skurielen Bug, sobald das Glas herunter fällt, stoppt es einen Moment und wird nach oben geschossen. Aus unersichtlichen Gründen, vermehren sich auch die Glasscherben und mein Rechner hängt sich auf! :huh:

Link zu diesem Kommentar
Auf anderen Seiten teilen

Ein schneller "Fix" wäre, du solltest es aber noch sauberer implementieren. Hintergrund: Du setzt die "shards" ja erst später innerhalb der Coroutine und deshalb kommt es zu der NPE in der FixedUpdate.

 private void FixedUpdate() {
    if (shards!=null) {     
      foreach (var shard in shards) {
        shard.AddForce(Vector3.down * 20);
      }
    }
 }

Zudem brauchst du noch eine Abbruchbedingung, du möchtest ja nicht bis in alle Ewigkeit den Splittern in der FixedUpdate eine Kraft hinzufügen...
Beispielsweise könntest du abbrechen, wenn sie den Boden erreicht haben.

Zu dem seltsamen Bug, prüfe die OnTriggerEnterEnter-Methode, sie darf nur 1x aufgerufen werden.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Mit dem IsSleeping klappts irgendwie nicht, wahrscheinlich weils ein Array ist, dakomme ich gerade nicht weiter. :huh:

So sieht mein Code bisher aus:

public class Bodenschaden : MonoBehaviour {

    private int anzahlBegehungen = 1;
    private MeshRenderer meshrend;
    private BoxCollider boxcoll;
    public GameObject zerbrochenesGlas;
    private Transform zerbrochenesGlasTransform;
    private Rigidbody[] zerbrochenesGlasRigid; 
    private bool neustartSicherung = false;
    private bool physic = false;


    private void Awake() {
        meshrend = GetComponent<MeshRenderer>();
        boxcoll = GetComponent<BoxCollider>();
        zerbrochenesGlasTransform = zerbrochenesGlas.transform;
        zerbrochenesGlasRigid = zerbrochenesGlas.GetComponentsInChildren<Rigidbody>();
    }


    private void FixedUpdate() {
        if (physic == true) {
            foreach (var scherben in zerbrochenesGlasRigid) {
                scherben.AddForce(Vector3.down * 20);
            }
        }
    }


    private void OnTriggerEnter(Collider charakter) {
        if (anzahlBegehungen == 0) {
            // Es wird das erste mal die Plattform betreten, Risse werden erzeugt 
        } else {
            // Es wird das zweite mal die Plattform betreten, es folgt der Absturz
            StartCoroutine("Glasbruch");
        }
    }


    IEnumerator Glasbruch() {
        if(neustartSicherung == false) {
            yield return new WaitForSeconds(0.5f);
            boxcoll.enabled = false;
            meshrend.enabled = false;
            zerbrochenesGlasTransform = Instantiate(zerbrochenesGlasTransform, transform.position, transform.rotation);
            zerbrochenesGlasTransform.localScale = transform.localScale;
            physic = true;
            neustartSicherung = true;
            yield return new WaitForSeconds(5.0f);
            DestroyObject(zerbrochenesGlas);
            physic = false;
        }        
    }
}

Problem ist nun, ich lasse den Glasbruch nur ein mal geschehen und sperre eine nochmalige Auslösung.

Jedoch:

1. Fliegen die Glasscherben in alle Richtungen statt nur nach Unten, wie in der Physic eigentlich vorgegeben.  //  Problem hat sich aus irgend einem Grund selbst gelöst...

2. Es entsteht nicht ein, sonder drei Klone, der Prefab. Es liegen also drei Scherbenhaufen herum. // neustartSicherung ganz an den Anfang gesetzt, jetzt nur noch ein Clone da!

3. Destroy will irgendwie auch nicht.

Edit: So funktioniert nun:

public class Bodenschaden : MonoBehaviour {

    private int anzahlBegehungen = 1;
    private bool neustartSicherung = false;
    private bool physic = false;
    private MeshRenderer meshrend;
    private BoxCollider boxcoll;
    public GameObject zerbrochenesGlasPrefab;
    private GameObject zerbrochenesGlasClone;
    private Rigidbody[] zerbrochenesGlasRigid; 
    


    private void Awake() {
        meshrend = GetComponent<MeshRenderer>();
        boxcoll = GetComponent<BoxCollider>();
        zerbrochenesGlasRigid = zerbrochenesGlasPrefab.GetComponentsInChildren<Rigidbody>();
    }


    private void FixedUpdate() {
        // Sorgt dafür, dass alle Glasteile mit Beschleunigung nach unten fallen
        if (physic == true) {
            foreach (var scherben in zerbrochenesGlasRigid) {
                scherben.AddForce(Vector3.down * 100);
            }
        }
    }


    private void OnTriggerEnter(Collider charakter) {
        if (anzahlBegehungen == 0) {
            // Es wird das erste mal die Plattform betreten, Risse werden erzeugt 
        } else {
            // Es wird das zweite mal die Plattform betreten, es folgt der Absturz
            StartCoroutine("Glasbruch");
        }
    }


    IEnumerator Glasbruch() {
        if(neustartSicherung == false) {
            neustartSicherung = true;
            yield return new WaitForSeconds(0.5f);
            boxcoll.enabled = false;
            meshrend.enabled = false;
            zerbrochenesGlasClone = Instantiate(zerbrochenesGlasPrefab, transform.position, transform.rotation);
            physic = true;            
            yield return new WaitForSeconds(3.5f);
            Object.Destroy(zerbrochenesGlasClone);
            physic = false;
        }        
    }
}

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Doch noch ein Problem und zwar ein ganz skurieles. Ich nutze OnTriggerEnter um eine Variable nach oben zu setzen, wenn die Plattform betreten wird. Normalerweise ist es so gedacht, Charakter betritt das Glas und bekommt einen Riss, tritt er noch einmal darauf, bricht er ein. Dache das könnte ich über OnTriggerEnter bewerkstelligen, da OnTriggerEnter doch eigentlich nur 1 mal aktiviert wird, wenn ein Objekt in das andere eintaucht. Ich hingegen habe aber gerade das Problem,. dass meine Variable nicht um 1 hoch gesetzt wird, sondern ganz schnell auf 37 ist! Warum zählt er denn mehrfach hoch?

public class Bodenschaden : MonoBehaviour {

    public int anzahlBegehungen = 0;
    public bool neustartAngebrochenSicherung = false;
    public bool neustartZerbrochenSicherung = false;
    public bool physic = false;
    public MeshRenderer meshrend;
    public BoxCollider boxcoll;
    public GameObject angebrochenesGlasPrefab;
    public GameObject angebrochenesGlasClone;
    public GameObject zerbrochenesGlasPrefab;
    public GameObject zerbrochenesGlasClone;
    public Rigidbody[] zerbrochenesGlasRigid; 
    


    private void Awake() {
        meshrend = GetComponent<MeshRenderer>();
        boxcoll = GetComponent<BoxCollider>();
        zerbrochenesGlasRigid = zerbrochenesGlasPrefab.GetComponentsInChildren<Rigidbody>();
    }


    private void FixedUpdate() {
        // Physik zur Fallbeschleunigung der Glasscherben    
        if (physic == true) {
            foreach (var scherben in zerbrochenesGlasRigid) {
                scherben.AddForce(Vector3.down * 100);
            }
        }
    }


    private void OnTriggerEnter() {
        Debug.Log("Test");
        anzahlBegehungen += 1;
        if (anzahlBegehungen <= 1) {
            StartCoroutine("GlasAnbrechen"); // Es wird das erste mal die Plattform betreten, Risse werden erzeugt 
        } else if (anzahlBegehungen > 1) {
            StartCoroutine("GlasZerbrechen"); // Es wird das zweite mal die Plattform betreten, es folgt der Absturz
        }
    }


    IEnumerator GlasAnbrechen() {
        if (neustartAngebrochenSicherung == false) {
            neustartAngebrochenSicherung = true;
            yield return new WaitForSeconds(0.5f); // Kurze Wartezeit bis der Chrakter weiter auf die Glasfläche gelaufen ist
            angebrochenesGlasClone = Instantiate(angebrochenesGlasPrefab, transform.position, transform.rotation); // Es wird eine beschädigte Glasplatte erzeugt
            meshrend.enabled = false; // Die Sichtbarkeit der unbeschädigten Glasplatte wird deaktiviert
            boxcoll.enabled = false; // Es wird der Collider der unbeschädigten Glasplatte wird deaktiviert
            neustartAngebrochenSicherung = true;
        }
        
    }

    IEnumerator GlasZerbrechen() {
        if(neustartZerbrochenSicherung == false) {
            neustartZerbrochenSicherung = true;
            yield return new WaitForSeconds(0.5f); // Kurze Wartezeit bis der Chrakter weiter auf die Glasfläche gelaufen ist
            zerbrochenesGlasClone = Instantiate(zerbrochenesGlasPrefab, transform.position, transform.rotation); // Erzeugung der zerstörten Glasplatte, die in Stücken herunter fällt
            Object.Destroy(angebrochenesGlasClone); // Zerstörung der beschädigten Glasplatte             
            physic = true; // Aktivierung der Physik zur Fallbeschleunigung der Glasscherben            
            yield return new WaitForSeconds(3.5f); // Wartezeit zum Fallen der Scherben
            physic = false; // Deaktivierung der Physik
            Object.Destroy(zerbrochenesGlasClone); // Zerstörung der Glassplitter 
        }       
    }
}

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Vermutlich betritt "etwas" den Trigger mehrfach. Ergänze mal den Code um die Methode OnTriggerExit() und setze da mal eine Debugausgabe hinein. Kann auch für OnTriggerEnter() nicht schaden.

Zudem kannst du dir auch einmal ausschauen, wer da eigentlich deinen Trigger betritt:

  void OnTriggerEnter(Collider other) {
        Debug.Log("OnTriggerEnter: " + other.gameObject.name + "("+ Time.time +")");
  }

  void OnTriggerExit(Collider other) {
        Debug.Log("OnTriggerExit: " + other.gameObject.name + "("+ Time.time +")");
  }

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Ohne zu testen, fällt mir gerade die Lösung ein. Ich lasse doch an der selben Stelle der Bodenplatte den Clon der zerbrochenen Platte erstellen, jedes einzelne Kind Objekt tritt damit in den Collider ein. :rolleyes: Also muss ich die nur aus der Enter Abfrage ausklammern.

Gut, ich hatte teilweise Recht, dass wäre die Antwort:

OnTriggerEnter: Charakter_4(2.6)
UnityEngine.Debug:Log(Object)
Bodenschaden:OnTriggerEnter(Collider) (at Assets/Scripts/Bodenschaden.cs:47)

OnTriggerEnter: Charakter_4(3.3)
UnityEngine.Debug:Log(Object)
Bodenschaden:OnTriggerEnter(Collider) (at Assets/Scripts/Bodenschaden.cs:47)

OnTriggerEnter: Charakter_1(3.78)
UnityEngine.Debug:Log(Object)
Bodenschaden:OnTriggerEnter(Collider) (at Assets/Scripts/Bodenschaden.cs:47)

OnTriggerEnter: Charakter_1(4.28)
UnityEngine.Debug:Log(Object)
Bodenschaden:OnTriggerEnter(Collider) (at Assets/Scripts/Bodenschaden.cs:47)

Es treten hintereinander zwei Charaktere auf die Platte, nämlich Nr. 1 und Nr 4, die werden jeweils aber doppelt angerechnet.

EDIT:

Also das Problem liegt eindeitig an dieser Methode:

private void OnTriggerEnter(Collider other) {
        if(other.gameObject.tag == "Charakter_1" || other.gameObject.tag == "Charakter_2" || other.gameObject.tag == "Charakter_3" || other.gameObject.tag == "Charakter_4" || other.gameObject.tag == "Charakter_5" || other.gameObject.tag == "Charakter_6") {
            anzahlBegehungen += 1;
            Debug.Log("OnTriggerEnter: " + other.gameObject.name + "(" + Time.time + ")");
            if (anzahlBegehungen <= 1) {
                //StartCoroutine("GlasAnbrechen"); // Es wird das erste mal die Plattform betreten, Risse werden erzeugt 
            } else if (anzahlBegehungen > 1) {
                //StartCoroutine("GlasZerbrechen"); // Es wird das zweite mal die Plattform betreten, es folgt der Absturz
            }
        }    
    }

Jeder Charakter, der die Platte betritt wird je 3 mal gezählt! :huh:

Hab nun auch einmal aus Test einen Kubus mit Collider und Rigidbody ausgestattet und auf die Platte fallen lassen, auch hier, eine Berührung, wird 2 mal registriert.

Des weiteren auch mal ein neues Script gemacht in dem nur dies enthalten ist:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Zähler : MonoBehaviour {

    public int anzahlBegehungen = 0;

    private void OnTriggerEnter(Collider other) {
        anzahlBegehungen += 1;
    }
}

Dies habe ich aber nicht nur bei der Glasplatte eingebaut, sondern auch bei anderen Bodenplatten. Ergebnis, auch hier wird einmal in den Collider eintauchen 3 mal gezählt!

Link zu diesem Kommentar
Auf anderen Seiten teilen

Die Werte von Time.time sind jedesmal unterschiedlich so dass davon auszugehen ist dass die Charaktere zwischenzeitlich den Trigger wieder verlassen.

Wie genau ist der Trigger denn aufgebaut? Leicht größer oder gleich groß wie der normale Collider von zB der Bodenplatte/dem Glas? Wenn ja wäre dies eine Erklärung, die Physikengine löst Kollisionen auf, dadurch kann es passieren dass allein durch die Korrektur damit 2 Objekte nicht ineinander stecken bleiben eine Trennung auftritt.

Auch spielt es eine Rolle ob Objekte voneinander abprallen, selbst minimal könnte dies eine Trennung emittieren.

 

Du könntest entweder die Trigger größer machen oder ein delay einbauen der verhindert dass zwei gleiche Objekte kurz folgend mit dem Glas kollidieren können.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Du bringst mich auf eine Idee!

Hab das Problem gefunden, ich nutze bei meinen Charakteren 3 Collider, da der Charakter ziemlich unförmig ist.

Collider 1 ist am Kopf, eine Sphere und ist Trigger.

Collider 2 ist am Körper, eine Box und ist ein Trigger.

Collider 3 ist ebenfalls am Körper, eine Box und ist kein Trigger.

Collider 2 habe ich aus Kollisionsgründen eingebaut, da ich auch hier einen Trigger benötigte, dieser ist neu. Da er auch den kompletten Körper des Charakters bedeckt, reagiert die Bodenplatte mit OnTriggerEnter auch auf beide Collider...

Link zu diesem Kommentar
Auf anderen Seiten teilen

Archiviert

Dieses Thema ist jetzt archiviert und für weitere Antworten gesperrt.

×
×
  • Neu erstellen...