Jump to content
Unity Insider Forum

2D Game Gegner-Sprite bewegen


Schokokeks15

Recommended Posts

Hallo zusammen,

ich bin neu hier und sage erst einmal Hallo!

Hallo!

So, jetzt bitte nicht hauen, aber ich habe das Forum und Google durchgesehen, aber nichts gefunden was mir hilft. Daher hier eine Frage:

Es geht um ein reines 2D-Plattform-Spiel. Ohne scrollen. Ich habe einen Gegner erstellt, der sich nur entlang der X-Achse bewegen soll. Bis er auf einen Box-Collider stößt. Egal, ob  damit der "Rahmen", der "Boden" oder ein "Tile" gemeint ist. Dann soll er sich drehen und in die entgegengesetzte Richtung zurück laufen, bis er wieder auf ein Hindernis stößt. Mehr soll der gar nicht machen. Nicht schießen, nicht den Spieler suchen oder sonst irgendwas. Stumpf von links nach rechts bis ein Hindernis auftaucht.
Oh, doch - der Gegner soll eine weitere Animation ausführen, wenn er auf ein Hindernis stößt - und zwar soll er sich in die jeweils andere Richtung drehen.

Ich habe zwar ein paar Sachen im Internet gefunden, aber die passen alle nicht. Habe die Version 2018.3.4.

Ich will Euch gar nicht die Zeit stehlen und eine fertige Script-Lösung haben, sondern nur gern wissen, wo ich ein Tutorial zum lernen finde, wie ich das ganze anstelle.
Denn es stellen sich mir folgende Fragen:

1. Was muss in das Script rein?
2. Was muss ich wie  im Animator anpassen?
3. Welche Einstellungen und "Components" werden bei dem Gegner benötigt?
4. Wie kriege ich das hin, dass wenn sich der Gegner an bzw. in der Position einer Tür befindet und diese sich schließen soll, dies nicht mehr geht? Sonst bleibt er ja stecken...^^

Ich weiß, viele für Euch lächerliche Fragen, aber mich treiben diese schon seit Tagen um, da ich wie gesagt noch kein entsprechendes Tutorial im Internet gefunden und seitdem mehr als nur ein Brett vor dem Kopf habe.

Also würde ich mich über einen Stupser in die richtige oder einen Hinweis auf einen Link oder sonstige Hilfe sehr freuen.

Vielen Dank und Euch allen noch einen schönen Abend,

Alex

Link zu diesem Kommentar
Auf anderen Seiten teilen

Am 6.2.2019 um 18:55 schrieb Schokokeks15:

Hallo!

Hallo!

Am 6.2.2019 um 18:55 schrieb Schokokeks15:

1. Was muss in das Script rein?

Das musst du dir selber ausdenken. Ich kann nicht einschätzen, wie das mit dem Coden bei dir soweit läuft, aber generell würde ich sagen, dass es keine Schande ist, erstmal gemütlich etwas auf ein Blatt Papier zu malen. Als Text oder als Diagramm.

  1. Das Objekt bewegt sich
  2. Das Objekt reagiert auf seitliche Kollisionen
  3. Das Objekt ändert seine Bewegungsrichtung

Dann recherschierst du nach und nach, was du für die einzelnen Teile brauchst. Wenn du dann hier (oder sonstwo) fragst "Wie mache DiesesEineBestimmteDing", dann kann dir immer sehr direkt geholfen werden ;)

Am 6.2.2019 um 18:55 schrieb Schokokeks15:

3. Welche Einstellungen und "Components" werden bei dem Gegner benötigt?

Das ist mit "Das Objekt bewegt sich" verwandt. Es gibt verschiedene Arten, Dinge zu bewegen, und je nach Situation hat jede ihre Daseinsberechtigung. Ich habe jetzt keine schöne Auflistung zur Hand... aber das kommt so oder so mit der Erfahung, was es da so gibt.

In deinem Fall soll dein Objekt nicht durch Wände gehen können und sogar darauf reagieren, wenn es etwas berührt. Es gibt auch da mehrere Varianten, aber eine recht einfache wäre eine Rigidbody-Komponente, zusammen mit mindestens einem Collider, wobei in deinem Fall natürlich Collider2D sinnvoll sind, da dein Gameplay ja 2D ist. Entsprechend würdest du auch einen Rigidbody2D benutzen anstatt einen dreidimensionalen.

Ein Rigidbody(2D) macht, dass das Objekt Kräften ausgesetzt wird. Das bedeutet sowohl Gravitation (die man ausstellen kann), als auch Kräfte die du über dein Script steuern kannst, aber vor allem auch quasi "Gegenkräfte" von Collidern, die im Weg sind. Ein Rigidbody(2D) hat eine Geschwindigkeit, die sich ändert oder komplett kontrollieren lässt, und in jedem Physik-Update bewegt er sich entsprechend dieser Geschwindigkeit. Trifft er dabei auf ein Hindernis, entsteht eine Kollision (auf die man reagieren kann), die verhindert, dass die zum Rigidbody(2D) gehörenden Collider(2D) nicht mit anderen Collider(2D) überlappen.

Wenn du dich für diesen Weg entscheidest, sind wir wieder bei Frage 1, und die Antworten sehen so aus:

  1. Es geht anders, aber du kannst in FixedUpdate die Geschwindigkeit des Rigidbody2D setzen.
  2. Du baust eine Methode Namens OnCollisionEnter2D ein, ...
  3. die die Richtung ändert, die für 1. benutzt wird.

Würde etwa so aussehen:

new private Rigidbody2D rigidbody2D;
public float speed = 10f;
private bool movingRight = true;

private void Awake()
{
  rigidbody2D = GetComponent<Rigidbody2D>();
}

private void FixedUpdate()
{
  var horizontalMovement = speed * (movingRight ? 1 : -1);
  rigidbody2D.velocity = new Vector2(horizontalMovement, 0);
}

private void OnCollisionEnter2D()
{
  movingRight = !movingRight;
}

Jetzt reagiert das Ding auf wirklich sämtliche Kollisionen, auch auf den Boden. Es könnte helfen, das Objekt so zu platzieren, dass es den Boden nicht berührt und beim Rigidbody2D einzustellen, dass es sich nicht vertikal bewegen kann (Contraints > Häkchen bei Freeze Position, Y).

Am 6.2.2019 um 18:55 schrieb Schokokeks15:

Wie kriege ich das hin, dass wenn sich der Gegner an bzw. in der Position einer Tür befindet und diese sich schließen soll, dies nicht mehr geht? Sonst bleibt er ja stecken...^^

DAS ist ein Thema für sich wert. Ist gar nicht mal trivial.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Hallo Sascha,

vielen Dank für Deine Antwort.
Diese hilft mir auf jeden Fall weiter. Auch Deine Erklärungen zum RigidBody sind einleuchtend und sehr gut erklärt.

In diesem Zusammenhang werde ich Dein Script gleich mal ausprobieren und ansonsten weiter lernen, lernen, lernen...

Das die letzte Frage nicht sooo trivial ist...naja, da wische ich mir schon mal die Schweißperlen von der Stirn... 😉

Nochmals herzlichen Dank und einen schönen Abend,

Alex

Link zu diesem Kommentar
Auf anderen Seiten teilen

Wegen der Tür. Du könntest links und rechts von der Tür unsichtbare Trigger Collider setzen und beim triggern davon die gleiche Funktion auslösen wie bei OnCollisionEnter2D. Also mit OnTriggerEnter2D.

Alternativ kannst du einen Trigger Collider um die Tür herum machen und die darin eingetretenen unerwünschten Objekte registrieren und beim öffnen und schließen der Tür diese aus dem Trigger Bereich raus schieben oder raus spawnen.

Link zu diesem Kommentar
Auf anderen Seiten teilen

@Lightstorm: vielen Dank für Deinen Tip. Der erste Hinweis mit den Triggern ist sicherlich der Bessere. Denn es soll verhindert werden, dass wenn sich eine Tür schließt, oder plötzlich eine Pflanze vor dem Gegners wächst wird, dass sich der Gegner in dem Objekt "festfrisst". Es soll also nicht möglich sein, ein Objekt in den Weg des Gegners zu packen, wenn dieser den "Tile" betreten hat...

@Sascha: Dein Script funktioniert wunderbar. Muss jetzt nur noch erreichen, dass sich der Gegner in die andere Richtung dreht, nachdem er auf den Collider getroffen ist. Aber das kriege ich hin...

Nochmals Danke!!!

Alex

Link zu diesem Kommentar
Auf anderen Seiten teilen

Was vielleicht auch hilfreich sein kann ist dir zu überlegen ob du für alle solche Aufgabenstellungen eigene Scripte erstellen willst. Vielleicht willst du z.B. ein anderen Gegner machen der sich ähnlich aber etwas anders verhalten soll. Dann könntest du deinen ganzen Script kopieren und eine leicht abgeänderte Version erstellen. Und wenn ein anderer Gegner sich ganz deutlich anders verhalten soll musst du vielleicht ein ganz neuen Script erstellen.

Um so was zu vermeiden könntest du überlegen ob du nicht möglichst universell verwendbare Scripte erstellen kannst. Objekte simple hin und her bewegen macht man recht häufig. Du könntest überlegen wie du ein Script erstellen kannst das Objekte bewegen kann, die Parameter und Variationen der Bewegungen kannst du dann über den Inspector einstellen.

Das was du versuchst könnte ich mit drei universell einsetzbaren Komponenten umsetzen:

comp.thumb.jpg.0d9d84f3957825929558b920b800e3e7.jpg

Bei einer Kollision löst "Collider Trigger 2D" ein Unity Event aus, mit diesem Event kann ich direkt auf "Auto Movement 2D" zugreifen und Werte wie Translation Direction verändern. "Entity Movement 2D" kümmert sich um die Bewegung der Objekte und wird von "Auto Movement 2D" angesteuert. In einem anderen Fall wird Entity Movement über einen Player Script angesteuert, es wird häufig verwendet. Ich kann über den Parameter Movement Mode einstellen mit welcher Methode das GameObject bewegt werden soll. Mit transform.Translate, AdForce, AdForce Impuls oder über rigidbody2D.velocity. Sachen wie Speed Curve sind optional.

Hast du einmal solche Komponenten erstellt kannst du sie in verschiedenen Kombinationen in GameObjects nutzen. Damit werden die Fälle wo man neuen Code schreiben muss deutlich reduziert. Ich versuche jedes mal wenn neuer Code nötig wird, wieder eine möglichst universell verwendbare Komponente zu erstellen. Diese Komponenten entwickeln sich weiter. Nach und nach findet man heraus welche weiteren einstellbaren Parameter sinnvoll sind, dann passe ich die Komponenten an, irgendwann ist ein Optimum erreicht wo keine Änderungen mehr nötig sind.

Mit einer Reihe von spezialisierten und einstellbaren Komponenten kann man erstaunlich viele Variationen an Objekt Verhalten erstellen. Erst wenn für eine Aufgabenstellung sehr viele solcher Komponenten nötig werden, wird es im Editor unübersichtlich und vom Code her vermutlich ineffizient. Dann kann und sollte man neue Scripte und Komponenten erstellen.

 

Für Charakter Bewegungen nutze ich aber andere Komponenten. Ich habe einen CharakterController Script. Über das lassen sich grundlegende Eigenschaften einstellen. Dann gibt es einen PlayerCharacterMovement Script. Damit wird CharakterController angesteuert, über den Player Input. Wenn ich Gegner Charaktere machen will die sich selbst bewegen nutze ich statt PlayerCharacterMovement ein AICharacterMovement Script. Auch dieser AI Script steuert CharakterController an, nur halt nicht über Player Input sondern mit Steuermustern die andere Komponenten senden. Ich habe beispielsweise eine Timer Komponente die in einstellbaren Zeitabständen das Steuermuster für AICharakterMovement verändert. Auch kann die Komponente "Collider Triger 2D" eingesetzt werden, Gegner Charaktere bzw. die Steuereingaben können also auch über Trigger verändert werden und nicht nur über Timer.

Mit einer handvoll Komponenten kann man sehr komplexe Verhaltensmuster erzeugen. Der Nachteil ist dass die Entwicklung der einzelnen Komponenten am Anfang etwas dauert. Aber man spart später enorm viel Zeit und kann die Komponenten in anderen Projekten wieder verwenden.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Hallo Lightstorm,

vielen Dank für Deine sehr ausführliche Antwort.

Das macht alles absolut Sinn, was Du schreibst. Leider bin ich kein Programmierer, daher wird es nicht einfach. Aber ich verstehe was Du meinst und es ist ja noch kein Meister vom Himmel gefallen... 😉

Ich denke, ich werde das so machen: wenn zwei Scripte funktionieren, kann ich ja versuchen, die miteinander zu verbinden und die Einstellungen über den Editor zu machen. Erst mal was ganz Einfaches und dann halt mehr und mehr...^^

Nochmals herzlichen Dank und noch einen schönen Tag,

Alex

Link zu diesem Kommentar
Auf anderen Seiten teilen

Hallo @Schokokeks15

Ich bin auch noch am lernen und bin kein erfahrener Programmierer. Ich hatte schon früher einige Anläufe mit der der Programmierung. Der Unterschied dieses mal ist dass ich versuche systematischer vorzugehen. Früher oder später musst du dir überlegen wie deine Code Architektur in Unity aussehen soll. Schau dir diesbezüglich auch mal das an:

https://gitlab.com/FlaShG/Unity-Architecture

Es ist vielleicht am Anfang mühseliger sich gedanklich damit zu beschäftigen, aber es lohnt sich wirklich.

Link zu diesem Kommentar
Auf anderen Seiten teilen

@Lightstorm: Wow, das sind viele hilfreiche Infos. Da muss ich jetzt erstmal vieles im Kleinen testen und verstehen.

Vielen Dank dafür.

Momentan stehe ich vor einem grundlegenden Verständnisproblem (bitte nicht lachen): Aber warum packst Du für die Character-Steuerung nicht alles in ein Script?
Wo die PlayerInputs drin stehen, die entsprechenden Animationen und Sprite-Movements ausgeführt werden, geprüft wird, ob der Character "onGround" ist usw.?

So, ich lese dann mal in Deinem Link weiter... 😉

Nochmals herzlichen Dank,

-Alex

Link zu diesem Kommentar
Auf anderen Seiten teilen

vor 3 Stunden schrieb Schokokeks15:

Momentan stehe ich vor einem grundlegenden Verständnisproblem (bitte nicht lachen): Aber warum packst Du für die Character-Steuerung nicht alles in ein Script?
 Wo die PlayerInputs drin stehen, die entsprechenden Animationen und Sprite-Movements ausgeführt werden, geprüft wird, ob der Character "onGround" ist usw.?

Modularisierung. Man unterteilt ein Programm in sinnvolle Einzelteile. Ein Vorteil ist dass ein Programm so übersichtlicher wird und ein anderer Vorteil ist dass du Module bzw. Scripte miteinander unterschiedlich kombinieren kannst. Du vermeidest es gleichen oder ähnlichen Code immer wieder neu schreiben zu müssen.

Ein konkretes Beispiel ist folgendes: Ich habe im Prinzip zwei Scripte um einen Charakter zu steuern. CharakterController.cs und PlayerCharacterMovement.cs. Ich könnte beides vereinen und einen einzigen Script schreiben. Kann man machen. Aber die Trennung macht den Code flexibler.

Ich wollte eine automatische KI Steuerung für Gegner Charaktere und ich dachte mir, eigentlich brauchen die Gegner Charakter die selben Eigenschaften wie mein Player Charakter. Sie müssen sich bewegen, springen, Wände und Böden erfassen, Animationen abspielen. Warum soll ich den Code von CharakterController.cs neu schreiben oder kopieren wenn ich doch nur den PlayerCharacterMovement.cs Script einfach durch ein AICharakterMovement.cs ersetzen muss? Die Modularisierung macht es einfacher.

Um Gegner Charaktere automatisch zu steuern musste ich nur sehr wenig Code schreiben, denn ich konnte CharakterController.cs unverändert nutzen, das ging nur weil es diese Aufteilung gibt Du könntest den Code von CharakterController.cs auch einfach kopieren aber mit so einer Vorgehensweise hast du irgendwann sehr viele Scripte mit gleichem Code. Das ist unschön und wenn du später etwas daran ändern musst, musst du es in allen Scripten ändern wo du es genutzt hast.

 

Die Vorteile einer sauberen Vorgehensweise merkt man übrigens erst so richtig im Laufe eines Projektes. Am Anfang ist die Verlockung groß einfach schnell eine Lösung zu schaffen, weil es ja kein großer Aufwand ist Teile eines Scriptes einfach zu kopieren oder neu zu schreiben. Aber das wiederholt sich dann hunderte mal im Laufe der Entwicklung und irgendwann verwandeln sich die schnellen unschönen Lösungen zu einem unübersichtlichen und fehlerhaften Chaos. Das kann so schlimm werden dass man so nicht mehr weiter entwickeln kann ohne vieles umzubauen. Daher lohnt es sich bereits am Anfang eine disziplinierte Vorgehensweise anzugewöhnen.

Wenn du weitere Fragen hast oder noch etwas unklar ist frag ruhig.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Ah, verstehe. Alles klar. Danke!

Du hast es angeboten, also komme ich mal drauf zurück.

Ich habe ein wenig "gespielt" und einige Zusammenhänge gelernt. Aber jetzt hänge ich seit einiger Zeit an folgendem Problem fest.

Und zwar soll ein Ball solange in eine Richtung rollen, bis er gegen einen Collider stößt. Dann soll er in die entgegengesetzte Richtung zurück rollen. Das klappt schon mal.

Wenn nun ein Loch im Boden ist, soll er durchfallen. Das geht auch. Aber ich jetzt zwei Probleme:

Erstens rollt bzw. bewegt sich der Ball weiter in die jeweilige Rollrichtung, wenn er fällt. Er soll aber wie ein Stück Blei einfach nach unten fallen und die Rollanimation soll stoppen.

Zweitens soll der Ball nicht, wenn er auf den Boden aufkommt (was für ihn auch ein Collider ist) sofort dir Richtung ändern.

Für den Groundcheck mittelt Raycast habe ich mich übrigens entschieden, weil so die Sprites nicht mehr an den einzelnen Bodentiles nicht mehr hängen bleiben...^^

Ich habe es mit folgende Code versucht. Wo ist hier mein Denkfehler?

using UnityEngine;

public class SphereMovement : MonoBehaviour
{
    new private Rigidbody2D rigidbody2D;
    public float speed = 1f;
    private bool movingRight = true;
    private bool m_FacingRight = true;
    public LayerMask groundLayer;

    bool IsGrounded()
    {
        Vector2 position = transform.position;
        Vector2 direction = Vector2.down;
        float distance = 1.0f;
        RaycastHit2D hit = Physics2D.Raycast(position, direction, distance, groundLayer);
        if (hit.collider != null)
        {
            return true;
        }
        return false;
    }
    bool IsBlocked()
    {
        Vector2 position = transform.position;
        Vector2 direction = Vector2.right;
        float distance = 1.0f;
        RaycastHit2D hit = Physics2D.Raycast(position, direction, distance, groundLayer);
        if (hit.collider != null)
        {
            return true;
        }
        return false;
    }
    private void Awake()
    {
        rigidbody2D = GetComponent<Rigidbody2D>();
    }

    private void FixedUpdate()
    {
        if (!IsGrounded())
        {
        var horizontalMovement = speed * (movingRight ? 1 : -1);
        rigidbody2D.velocity = new Vector2(horizontalMovement, 0);
        }

        if (!IsBlocked())
            movingRight = !movingRight;
            m_FacingRight = !m_FacingRight;


        Vector3 theScale = transform.localScale;
        theScale.x *= -1;
        transform.localScale = theScale;
    }
}        

 

Vielen Dank und einen schönen und erholsamen Abend,

Alex

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Zitat

if (!IsGrounded())
        {
        var horizontalMovement = speed * (movingRight ? 1 : -1);
        rigidbody2D.velocity = new Vector2(horizontalMovement, 0);
        }

Du willst den Ball nur bewegen wenn es keine Bodenhaftung hat bzw. IsGround false ist? Das machst du damit nämlich mit !IsGrounded()

Zitat

  if (!IsBlocked())
            movingRight = !movingRight;

Du prüfst mit IsBlocked() ob rechts vom Ball ein Hindernis ist und schaltest dann die Richtung um. Mit !IsBlocked schaltest du um wenn kein Hindernis da ist? Wenn der Ball aber nicht auf Anhieb sich vom Hindernis entfernen kann trifft der Raycast wieder auf das Hindernis und ändert wieder die Richtung. Das passiert ganz schnell und der Ball kommt gar nicht weg.

Also ich habe dein Script bei mir ausprobiert und mit dem Code bewegt sich der Ball gar nicht. Erst wenn du IsGrounded nicht mehr negierst, also IsGrounded statt !IsGrounded fängt der Ball an sich zu bewegen, bleibt aber beim nächsten Hindernis kleben und ändert nicht die Richtung.

 

Versuch doch das mit Trigger Collidern zu lösen. Wie es Sascha schon mit OnCollisionEnter2D zeigte oder mit OnTriggerEnter2D. Damit würde der Ball sich bei einem Hindernis nicht ständig rechts und links bewegen. OnCollisionEnter2D wird nur einmal ausgelöst wenn du auf ein Hindernis stößt.

Wenn du den Ball stoppen willst musst du die horizontalMovement Variable auf 0 setzen. Das könntest du mit einem Trigger am Loch machen.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Hallo Lightstorm,

das verstehe ich jetzt nicht. Habe es versucht so zu lösen:

using UnityEngine;

public class SphereMovement : MonoBehaviour
{
    new private Rigidbody2D rigidbody2D;
    public float speed = 1f;
    public float speedstop = 0f;
    private bool movingRight = true;
    private bool m_FacingRight = true;
    public LayerMask groundLayer;

    bool IsGrounded()
    {
        Vector2 position = transform.position;
        Vector2 direction = Vector2.down;
        float distance = 1.0f;
        RaycastHit2D hit = Physics2D.Raycast(position, direction, distance, groundLayer);
        if (hit.collider != null)
        {
            return true;
        }
        return false;
    }

    private void Awake()
    {
        rigidbody2D = GetComponent<Rigidbody2D>();
    }

    private void FixedUpdate()
    {
        if (!IsGrounded())
        {
            var horizontalMovement = speed * (movingRight ? 1 : -1);
            rigidbody2D.velocity = new Vector2(horizontalMovement, 0);
        }
        else
        {
            var horizontalMovement = speedstop * (movingRight ? 1 : -1);
            rigidbody2D.velocity = new Vector2(horizontalMovement, 0);
    }
    }
    private void OnCollisionEnter2D()
        {
            movingRight = !movingRight;
        m_FacingRight = !m_FacingRight;
        Vector3 theScale = transform.localScale;
        theScale.x *= -1;
        transform.localScale = theScale;
    }
}

 

Leider ohne Erfolg. Stehe total auf dem Schlauch...

Übrigens, wenn ich ab morgen nicht mehr nerven sollte, ich bin für eine Woche beruflich unterwegs...^^

Besten Dank und Dir und Euch ein schönes Wochenende,

Alex

Link zu diesem Kommentar
Auf anderen Seiten teilen

Archiviert

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

×
×
  • Neu erstellen...