Jump to content
Unity Insider Forum

Spieler zählen die einen Bereich betreten...


chefselber

Recommended Posts

Hallo Gemeinde,

ich arbeite zur Zeit an einem Projekt. So in der Art "Z". Wenn das noch jemand kennt?! ;)

Ich würde gerne die Bereiche überwachen - hier Brücken - in welche Richtung die Brücke verlassen bzw. betreten wurde.

Ich habe über die Brücke 2 Box Collider gelegt. Die überlappen sich ein wenig.

Anhand von TriggerEnter und TriggerExit überwache ich ob sich der Spieler noch in Box 1 befindet, wenn die 2. Box betreten wird zähle ich hoch.

Und umgekehrt. Funktioniert zu 70%. Oft zählt er doppelt. Die Spieler haben auch einen Box Collider.

Wie löst ihr sowas?

 

 

MfG Chef

Link zu diesem Kommentar
Auf anderen Seiten teilen

Moin Chef!
Du sprichst von diesem alten Z mit den 2 Robotern und der Dose Öl? Ja, das kenne ich noch. War ganz schön schwer. ;)

Ja, ich würde das schon so ähnlich machen wie du. Ich würde aber nicht die Trigger zählen lassen, sondern die Player selbst.  Wenn du die Trigger zählen lässt, müssen die ja erkennen können, wer getriggert wurde.
Machst du das über die Spielfiguren, wissen sie selbst ja in welchem Trigger sie gerade stecken und ob sie zusätzlich den 2. überlappenden Trigger berühren. Das Enter kommt ja nur beim Eindringen in einen Collider.  Du könntest dabei z.B. eine boolsche Variable setzen, wenn sie nicht schon gesetzt ist. Beim Verlassen eines Triggers wird die Variable wieder in den Urzustand zurück gebracht. Kommst du jetzt aber in den 2. Trigger und diese Variable ist schon true, hast du die Brücke überwunden.  
Wenn du jetzt auswerten willst, von welcher Seite du die Brücke überwunden hast, könntest du den Triggern Tags geben, die du beim Berühren abfragst. Der Erste berührte Trigger setzt dann die Richtungsangabe wieder in einer Variable. Auch diese würde beim verlassen des Triggers zurückgesetzt.

So hättest du beim betreten der Brücke 2 Infos. Einmal, dass du eine Brücke betreten hast und zum anderen, dass du z.B. von oben kommst.
Würde der Player umdrehen und die Brücke wieder verlassen, werden diese beiden Informationen wieder resettet.
Geht der Player aber weiter, wird er den 2ten Trigger berühren. Dabei wird erkannt, dass er von der Brücke her kommt und somit von oben kam und jetzt drüber ist.

Ob das Überlappen der richtige Weg ist, weiß ich nicht. Ich hätte eher einen kleinen Trigger am Anfang und einen kleinen Trigger am Ende gesetzt. Ich würde denen wahrscheinlich Tags geben die sowas wie Teil1 und Teil2 heissen würden. Ich würde nur das Eintreten in einen Trigger auswerten.
Egal welche Trigger er zuerst berührt, er würde eine Richtungsinfo bekommen und sich merken, welchen Trigger er eben berührt hat. Wenn er dann irgendwann auf den anderen Trigger trifft, ist klar woher er kam und dass er die Brücke über diesen Weg genommen hat. Jetzt erst würde ich die gespeicherte Info verwerfen.
Der Player merkt sich also immer welchen Trigger er berührt hat. Egal wo er weiterhin hingehen wird.
Kommt er dann zum Partnertriger, gib es ne Auswertung. Kommt er nicht zum Partnertrigger, sndern zu einen anderen Trigger, wird der Andere als Erstinformation gespeichert.
Wenn es jetzt 5 Brücken gäbe, die alle von Nord nach süd führen würden, so müssten alle NordTrigger Teil1 heissen und alle Südtrigger Teil2.
Verstehste?

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Ich würde machen, dass die sich nicht überlappen.

Das Problem, was da auftreten kann ist, dass ein Spieler den einen Trigger noch nicht verlassen hat, den anderen aber schon berührt. Dreht er um, löst er OnTriggerEnter nicht noch einmal aus, weil er den Trigger ja nie verlassen hat. Baue es also so, dass der Spieler auf keinen Fall zwei Trigger gleichzeitig berühren kann.

Edit: Maaaaaaan :P

Link zu diesem Kommentar
Auf anderen Seiten teilen

Vielen vielen Dank für die Antworten,

Bisher habe ich sie soweit auseinander gelegt, dass der Spieler nicht in Trig1 und zugleich in Trig2 stehen kann. Ich versteh einfach nicht warum er da hin und wieder mehrfach zählt.

@Malzbie ja ich habe bool variablen die sagen ob sich der Player im Trig1 befindet, oder ob er verlassen ist...

Gibts noch andere Lösungen als Trigger???

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Warum es bei dir mehrfach gezäht wird, kann ich nicht sagen. Wo und wie wertest du das denn aus?
Wie ich oben schon geschrieben habe, musst du nicht auswerten, ob er einen Trigger verlassen hat. Einfach nur merken in welchem Trigger er zuletzt war und dann beim Eintritt in einen anderen Trigger, überprüfen, ob das passt oder ob nicht. Wenn ja, dann ist er drüber, wenn nein, ist der neu berührte Trigger sein Anfang.
Das funktioniert aber nur, wenn die Spielfigur für sich selbst auswertet. Sie kann natürlich seine Info an ein anderes Script senden.
Denk dir einfach es wäre das echte Leben. Der Typ weiß selbst wo er her kommt, weil er es erlebt hat. Der Brücke ist total egal was da gerade auf ihr ist. Sie ist einfach eine Brücke mit 2 Enden, die der Player erkennt.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Danke für den Tipp. Ich werde das mal probieren und die Player selber zählen lassen, ja ich schicke die Info´s an ein Auswertescript.... Der Gegner soll ja wissen wieviel Player sich in seinem Sektor befinden und reagieren!!

Ich sende auch dann mal die Codeschnippsel...

Edit. Es handelt sich um BoxCollider, hatte schon die Vermutung dass der Meshcollider vom Player Probleme macht und Doppelbelegungen!!!

Link zu diesem Kommentar
Auf anderen Seiten teilen

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

public class EnemyAI : MonoBehaviour {

    public bool enemymain_betreten;
    public bool grenzstein5_1_betreten;
    public bool enemymain_austritt;
    public bool grenzstein5_1_austritt;

    private bool grenzstein5_1_drinnen;
    private bool enemymain_drinnen;

    // Use this for initialization
    void Start () {
        
    }
    
    // Update is called once per frame
    void Update () {
        
        if (enemymain_betreten)
        {
            print ("EnemyMainbetreten!");
            enemymain_drinnen = true;
           

}

        if (grenzstein5_1_betreten)
        {
            print("Grenzstein5 betreten");
            grenzstein5_1_drinnen =true;
        }

        if (grenzstein5_1_austritt && enemymain_betreten)
        {
            grenzstein5_1_drinnen = false;
            enemymain_drinnen = true;
            print("Uebertritt in EnemyMain");
            grenzstein5_1_austritt = false;


        }
        if (enemymain_austritt && grenzstein5_1_betreten)
        {
            enemymain_drinnen = false;
            grenzstein5_1_drinnen = true;
            print("Uebertritt in Grenzstein5");
            enemymain_austritt = false;


        }
    }
}

 

Brücke.png

Link zu diesem Kommentar
Auf anderen Seiten teilen

hier für die beiden "Boxen"

/////////////////////

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

public class Uebertritt : MonoBehaviour {


    EnemyAI verwalter;

    // Use this for initialization
    void Start () {

        verwalter = GetComponentInParent<EnemyAI>();

    }
    
    // Update is called once per frame
    void Update () {
        
    }


    private void OnTriggerEnter(Collider col)
    {
        if (col.tag == "Player")
        {
            if (this.name.ToString() == "Grenzstein5")
            {
                verwalter.grenzstein5_1_betreten = true;
                verwalter.enemymain_betreten = false;
            }
            

            if (this.name.ToString() == "Enemy_Main")
             {
                verwalter.enemymain_betreten = true;
                verwalter.grenzstein5_1_betreten = false;
            }


        }


    }

    private void OnTriggerExit(Collider col)
    {
        if (col.tag.ToString() == "Player")
        {
            if (this.name.ToString() == "Grenzstein5")
            {
                verwalter.grenzstein5_1_austritt = true;
                verwalter.grenzstein5_1_betreten = false;

 

            }
            if (this.name.ToString() == "Enemy_Main")
            {
                verwalter.enemymain_austritt = true;
                verwalter.enemymain_betreten = false;

 

            }

 


        }


    }


}

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Da könnte man jetzt rumknobeln, aber ich glaube, es ist tatsächlich einfacher, einmal die grundlegende Struktur zu überdenken.

Du arbeitest mit String-Vergleichen, einem Objekt das als "verwalter" referenziert wird und mit Variablen, die Zahlen beinhalten. Alles Anzeichen für Code, bei dem Probleme eigentlich garantiert sind. Außerdem benutzt du nicht einfach nur OnTriggerEnter, sondern auch OnTriggerExit. Es wird also nicht nur das Überqueren eines Brückenendes, sondern auch das Verlassen eines solchen beachtet.

Ich würde das so machen wollen:

  1. Es gibt eine Komponente "Area". Ein Player merkt sich, in welchem Area er ist. Wenn man das braucht, kann sich stattdessen das Area auch merken, welche Spieler alle darin sind. Oder beides.
  2. Es gibt eine Komponente "AreaPortal", die auf einem Trigger liegt, welcher an das Areal angrenzt. Das, was du schon hast.
  3. Es gibt eine Komponente "AreaWalker", die sich darum kümmert, dass das Objekt, auf dem es liegt (ein Player) als jemand wahrgenommen wird, der sich durch Areale bewegt.

Diese Scripts würden etwa so aussehen:

public class Area : MonoBehaviour
{
  // Für die Variante "Player merkt sich Area" ist diese Klasse leer.
}
public class AreaPortal : MonoBehaviour
{
  // public, damit's an dieser Stelle nicht zu verwirrend wird (@malzbie :))
  // Dieses Feld referenziert die Area, die man betritt, wenn man dieses Portal berührt.
  // Es muss ein Area-GameObject in das Feld im Inspektor gezogen werden.
  public Area area;
  
  private void OnTriggerEnter(Collider other)
  {
    var areaWalker = other.GetComponent<AreaWalker>();
    
    if (areaWalker)
    {
      areaWalker.SetArea(area);
    }
  }
  
  // Die ganze folgende Funktion ist nur zur Veranschaulichung im Editor.
  // Sie malt eine Linie vom Portal zum Area, zu dem es gehört.
  private void OnDrawGizmos()
  {
    if (area)
    {
      Gizmos.DrawLine(transform.position, area.transform.position);
    }
  }
}
public class AreaWalker : MonoBehaviour
{
  private Area currentArea;
  
  public void SetArea(Area area)
  {
    currentArea = area;
    Debug.Log("Ich (" + gameObject.name + ") bin jetzt in Area " + area.gameObject.name + ".");
  }
}

Keine String-Vergleiche, keine Manager-/Verwalter-Klasse, keine Variablen mit Index drin. Der Code ist zudem sehr simpel. Wenn trotzdem Fragen aufkommen, gerne fragen.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Du machst dir ein leeres GameObject (oben GameObject > Create Empty) und tust das Area-Script drauf. Dieses GameObject ist nicht sichtbar oder sonst irgendwie interaktiv (auch z.B. keine Kollision), aber es repräsentiert eben jeweils ein Areal. Dieses kannst du dann aus der Hierarchie in den Inspektor ziehen - bei der AreaPortal-Komponente.

Wenn du dann noch im Inspektor des Area-GameObjects ganz oben links ein Icon auswählst...

image.png

hast du im Editor gleich eine richtige grafische Repräsentation dessen, was da los ist.

image.png

Link zu diesem Kommentar
Auf anderen Seiten teilen

vor einer Stunde schrieb chefselber:

Doch wieder bools, Zählerstände weiter leiten.

Nein, es geht immer anders.

Was genau willst du denn zählen? Wie viele Spieler in jedem Areal jeweils sind? Dann erweiterst du die Area-Klasse:

public class Area : MonoBehaviour
{
  // Das kann man später hübscher machen
  public int walkersInside = 0;
}

und dazu die AreaWalker-Klasse:

public class AreaWalker : MonoBehaviour
{
  private Area currentArea;
  
  public void SetArea(Area area)
  {
    // Wenn ich gerade schon in einem Areal bin...
    if (currentArea)
    {
      // ...dann ziehe ich mich selbst vom Zähler ab
      currentArea.walkersInside--;
    }
    
    currentArea = area;
    Debug.Log("Ich (" + gameObject.name + ") bin jetzt in Area " + area.gameObject.name + ".");
    
    // Falls ich jetzt in einem neuen Areal bin (kann auch nicht der Fall sein)...
    if (currentArea)
    {
      // ...dann füge ich mich selbst zum Zähler hinzu
      currentArea.walkersInside++;
    }
  }
}

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Das habe ich jetzt gelöst. Vielen Dank für die doch sehr saubere Codelösung.

Ich hätte da noch eine Kleinigkeit. ;)

Gehört zwar nicht mehr Thread.

Und zwar kann ich über eine Auswahlbox die ich aufziehe den Spieler oder mehrere selektieren.

Das klappt auch, nur wenn ich dann einen Punkt auf der Karte mit der rechten MT anwähle kann es sein, dass nur mal ein, zwei Spieler dorthin laufen...

private void FixedUpdate()
    {

        // Scrollen oder neue Position mit rechtem Mausklick
        Position();

        // Angriff???
        if ((Angriff_start_bew || Angriff_start) && !angriff_flucht)
            Angriff();

        if (!click.userdragging)                          // click.cs -- Auswahlbox ; Keine Box aufgezogen dann Einzelselektion
        {
            if (Input.GetMouseButton(0))
            {
                switch (cameraRaycaster.layerHit)
                {
                    case Layer.Begehbar:
                        //    Wenn Spieler ausgewählt und klick ins freie Feld ---> abwahl
                        if (Selected)
                        {
                            Selected = false;
                            SelectRect.SetActive(false);
                        }
                        break;

                    case Layer.Spieler:

                        // Einzelselektion

                        if (cameraRaycaster.hit.transform.gameObject.name.ToString() == this.gameObject.name)
                        {
                            if (!Selected)                 // Wenn nicht ausgewählt dann Auswahlrahmen aktivieren
                            {
                                if (SelectRect == null)
                                    SelectRect = Instantiate(Rect, transform.position, Quaternion.Euler(105, 0, 0));
                                else
                                    SelectRect.SetActive(true);

                                Selected = true;

                                break;
                            }
                            if (Selected && Angriff_start)
                            {
                                SelectRect.SetActive(false);
                                Selected = false;

                                break;
                            }
                        }
                        break;
                    default:
                        return;
                }
            }

            if (!Angriff_start_bew && !Angriff_start)
            {

                navmeshagent.destination = currentClickTarget;


                if (navmeshagent.remainingDistance > 4f && navmeshagent.remainingDistance > navmeshagent.stoppingDistance)
                {
                    animator.SetBool("running", true);
                    navmeshagent.isStopped = false;

                }

                else
                {
                    navmeshagent.isStopped = true;
                    //m_Character.Move(Vector3.zero, false, false);
                    animator.SetBool("running", false);
                    // navmeshagent.destination = Vector3.zero;

                }

            }



        }
        // Auswahlbox      USER is DRAGGING
        else
        {
            //Selected = false;

            Vector3 screenPos = cam.WorldToScreenPoint(this.transform.position);

            // Box von links oben aufziehen
            if (screenPos.x > click.auswahlbox.x && screenPos.x < (click.auswahlbox.x + click.auswahlbox.width)
            && (Screen.height - screenPos.y) > click.auswahlbox.y && (Screen.height - screenPos.y) < (click.auswahlbox.y + click.auswahlbox.height)
            //  // Box von rechts oben aufziehen
            || screenPos.x < click.auswahlbox.x && screenPos.x > (click.auswahlbox.x + click.auswahlbox.width)
            && (Screen.height - screenPos.y) > click.auswahlbox.y && (Screen.height - screenPos.y) > (click.auswahlbox.y - click.auswahlbox.height)
            // Box von links unten
            || screenPos.x > click.auswahlbox.x && screenPos.x < (click.auswahlbox.x + click.auswahlbox.width)
            && (Screen.height - screenPos.y) < click.auswahlbox.y && (Screen.height - screenPos.y) > (click.auswahlbox.y + click.auswahlbox.height)
            // Box von rechts unten 
            || screenPos.x < click.auswahlbox.x && screenPos.x > (click.auswahlbox.x + click.auswahlbox.width)
            && (Screen.height - screenPos.y) < click.auswahlbox.y && (Screen.height - screenPos.y) > (click.auswahlbox.y + click.auswahlbox.height)
            )
            {



                if (!Selected && Input.GetMouseButtonUp(0))
                {
                    // Warten();
                    if (SelectRect == null)
                        SelectRect = Instantiate(Rect, transform.position, Quaternion.Euler(105, 0, 0));
                    else
                        SelectRect.SetActive(true);
                    Selected = true;

                }
            }
        }

 

 

 

 

und hier die klasse der Auswahlbox...

 

 

 

 

 

 

ublic class click : MonoBehaviour
{
     
    Camera cam;
    private Vector3 currentMousePosition;
    private Vector3 startMousePosition;
    public GUIStyle skin;
    private Vector2 DragStartPoint;
    public static bool userdragging = false;

  /*  public delegate void dragger();
    dragger dragit;
    */


    public static Rect auswahlbox;
    // Use this for initialization

    void Start()
    {
        cam = GetComponent<Camera>();
       
    }
    /// <summary>
    /// Prüfung ob Wert bei erstem Druck der Maustaste im Vergleich mit aktueller Mausposition sich geändert hat
    /// </summary>
    /// <param name="startPoint"></param>
    /// <param name="actualPoint"></param>
    /// <returns></returns>
    private bool Userisdragging(Vector2 startPoint, Vector2 actualPoint)
    {
        float x = Mathf.Abs(actualPoint.x - startPoint.x);
        float y = Mathf.Abs(actualPoint.y - startPoint.y);
        if (x > 5f || y > 5f)
        {

            return true;
        }
        else return false;
    }

    // Update is called once per frame
    void Update()
    {
       

        // Maustaste am Anfang gedrückt? 
        if (Input.GetMouseButtonDown(0))
        {
            DragStartPoint = Input.mousePosition;
            startMousePosition.x = Input.mousePosition.x;
            startMousePosition.y = Screen.height - Input.mousePosition.y;
        }

        // Maustaste aktuell gedrückt?? 
        if (Input.GetMouseButton(0))
        {
            userdragging = Userisdragging(DragStartPoint, Input.mousePosition);
        }
        else
        {
            userdragging = false;        // Dragging stoppen
        }

        // Maustaste losgelassen?? Dragging Stop
        if (Input.GetMouseButtonUp(0))
        {
            DragStartPoint = Input.mousePosition;
            userdragging = false;
        }

      

    }

   private void OnGUI()
    {
        if (userdragging)
        {
            float boxwitdh = Input.mousePosition.x - DragStartPoint.x;
            float boxheigth = DragStartPoint.y - Input.mousePosition.y;
            auswahlbox = new Rect(DragStartPoint.x, Screen.height - DragStartPoint.y, boxwitdh, boxheigth);
            GUI.Box(new Rect(auswahlbox), "", skin);

        }

    }
   
  

}

hier noch die Position -Methode!

 

 private void Position()
    {

        if (Selected)
            SelectRect.transform.position = Quadpos(transform.position);

    
        if (Messung())   // Wenn Player selektiert und unter 0.17 gedrückt wurde
        { 
                switch (cameraRaycaster.layerHit)
                {
                    case Layer.Feind:
                        Angriff_start_bew = true;
                    
                        Feindname = cameraRaycaster.hit.transform.name.ToString();
                        TempGegner = richtigerGegner(Feindname);
                    
                        break;
                    case Layer.Begehbar:
                        if (Angriff_start || Angriff_start_bew)
                            angriff_flucht = true;
                        Angriff_start_bew = false;
                        Angriff_start = false;
                        TempGegner = null;
                        animator.SetBool("Schuss", false);
                        currentClickTarget = cameraRaycaster.hit.point;
                        break;
                    case Layer.Spieler:
                        return;

                }

            }
            else
                currentClickTarget = currentClickTarget_old;
            messung = 0;
            // return;
        }

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Uff. Das ist ne Menge Code. Ist vielleicht etwas ernüchternd, aber ich würde vorschlagen, das nochmal durchstrukturiert neu zu schreiben, vielleicht mit den neuen Erkenntnissen von weiter oben.

Da gibt's ein paar Dinge, die ich noch dazu geben könnte:

Namen - Benenne deine Variablen und Methoden aussagekräftiger. Ich sehe sachen wie TempGegner, Angriff_start_bew und Position() und ganz ehrlich; ich hab keinen Schimmer was die Dinger bedeuten und müsste mich durch den ganzen Code durchlesen, um zu verstehen was die Einheit macht. Gute Namen für Variablen und Methoden sind extrem wichtig für guten Code.

Kleinere Einheiten, Kapselung, weniger Code-Duplizierung - Diese if-Abfrage unter dem Kommentar

// Box von links oben aufziehen

ist ziemlich harter Stoff. Selbst, wenn man weiß, was da abgeht, ist das Fehlerpotential in Code wie diesem extrem hoch.

Statt einer Menge Anfragen genau dort, wo sie gebraucht werden, kann man idR eine Funktion bauen, in die man einen Punkt und ein Rect reinsteckt und die dann ansagt, ob der Punkt im Rect steckt. Dann kann man die Funktion an dieser Stelle aufrufen und das ganze wird einfacher lesbar. In diesem Fall gibt es dies Funktion sogar schon: Rect.Contains.

Generell kannst du aber Methoden bauen, die durch ihren Namen beschreiben, was da passiert. Zum Beispiel geht sowas (komischer Code, nur zur Veranschaulichung):

private void Update()
{
  if (Input.GetButtonDown("Fire"))
  {
    cooldown = 0;
    var bullet = Instantiate(bulletPrefab, transform.position, transform.rotation);
    bullet.velocity = transform.forward * bulletVelocity;
  }
}

Der ist jetzt noch recht kurz und dadurch noch einigermaßen lesbar. Aber sobald das ein zwei Zeilen mehr werden, muss man schon den gesamten Block in der if-Abfrage lesen, um zu verstehen, was genau da passiert, wenn man denn "Fire" drückt. Deshalb wäre das hier besser:

private void Update()
{
  if (Input.GetButtonDown("Fire"))
  {
    FireBullet();
  }
}

private void FireBullet()
{
  cooldown = 0;
  var bullet = Instantiate(bulletPrefab, transform.position, transform.rotation);
  bullet.velocity = transform.forward * bulletVelocity;
}

Es passiert genau dasselbe, aber anstatt in Update erstmal einen Fließtext lesen zu müssen, um zu schauen was passiert, sieht man: Wenn man "Fire" drückt, wird eine Kugel abgefeuert. Alleine, weil die Methode so heißt. Wenn es einen dann interessiert, was genau es bedeutet, wenn eine Kugel abgefeuert wird, dann kann man sich die Methode ansehen.

Solche Sachen sind Verbesserungen der Code-Qualität. Diese zeichnet sich nicht dadurch aus, ob der Code funktioniert oder nicht, sondern dadurch, dass man ihn gut lesen und warten kann, dass er robust gegen Änderungen ist und so weiter. Wenn dein Code von guter Qualität ist, können sich Probleme und Fehler viel weniger gut darin verstecken, und falls es doch mal ein Problem gibt, können andere den Code viel besser analysieren - nicht zuletzt, weil du weniger Code posten kannst, da du mit einer guten Struktur dein Problem direkt eingrenzen kannst.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Archiviert

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

×
×
  • Neu erstellen...