ArrayList in Runnable verwenden

  • Guten Abend allerseits.


    In meinem Towerdefense versuche ich gerade die Türme auf die Einheiten schießen zu lassen. um dies zu bewerkstelligen habe ich eine eigene Klasse Turm, welche als membervariable eine Liste hat (ArrayList), in der alle Einheiten sind, die sich gerade auf dem Spielfeld bewegen.
    Nun wollte ich der Klasse Turm auch noch eine Instanz der Klasse Runnable geben, die jede sekunde aufgerufen wird, um einen Schuss auf eine Einheit abzugeben. Damit ich prüfen kann, ob die Einheiten sich überhaupt in Reichweite befinden, wollte ich nun in der Runnable-Methode alle Elemente der Liste, in der die Einheiten stehen, durchgehen und prüfen, ob diese Einheiten sich in der Reichweite befinden.
    Wenn ich allerdings die Liste der Einheiten in der Runnable-Methode verwenden möchte, wird mir ein Fehler("EinheitenListe cannot be resolved or is not a field") angezeigt. Was mache ich falsch, bzw. was muss ich machen damit ich die Liste zur Überprüfung in der Runnable-Methode verwenden kann?


    MfG XoR

  • Moin!
    Zunächst mal kenne ich mich mit der Spieleentwicklung so gar nicht aus, wenn also irgendwelche Konzepte da anders sind als im Normalfall bitte ich diese Fehlinformation zu entschuldigen.


    Ich weiß nicht, ob Listen im Speziellen und Collections im Allgemeinen überhaupt threadfähig sind.
    Da sie ja veränderbar sind gehe ich einmal davon aus das nicht.


    Warum möchtest du das in eine Runnable des Turmes packen?
    Für mich klingt es gerade sinnvoller, dem Turm eine Methode 'attackEnemy(Object theEnemy)' zu verpasse, in der RunLoop des Spiels das Array durchzuhangeln und dann die entsprechende Methode aufzurufen.


    Da dein Spiel in einer Endlosschleife laufen dürfte und du das System eh davon abhalten musst, dein Spiel als 'abgeschmiert' anzusehen, kannst du ja mit der Auslagerung der Aktionen warten bis die Performance wirklich nicht mehr stimmt.


    Andererseits fällt mir spontan ein:

    Java
    final List<Enemies> theEnemies = EinheitenListe;
    Runnable attackEnemy = new Runnable()
    {
      @Override
      public void run()
      {
        attackEnemiesInList(theEnemies);
      }
    };


    Die final definiert deine Liste ja als unveränderlich und damit sollte das klappen.

    Je mehr Käse, desto mehr Löcher.
    Je mehr Löcher, desto weniger Käse.
    Daraus folgt: je mehr Käse, desto weniger Käse.


    »Dies ist ein Forum. Schreibt Eure Fragen in das Forum, nicht per PN!«

  • Erstmal danke für deine Antwort :)


    Ich dachte mir, dass es komfortabel wäre, wenn ich das in eine Run-Methode des Turmes packe, denn wenn ich es über die Gameloop aufrufe, habe ich keinen turm-spezifischen Interval mit dem der Turm schießt(z.B.: alle 0,75sek oder 1sek).


    Wenn sich einer noch fix die Arbeit auf sich nehmen möchte, mir zu erklären, warum denn die Variable die ich in der Run-Methode benutze final sein muss(Sinn erschließt sich mir noch nicht ganz)? :D


    MfG XoR

  • Also klappt das mit dem final? Sehr schön. :)


    Nun, final ist ein Schlüsselwort, das besagt: dies hier ändert sich nicht mehr.
    Wenn du etwas als final definierst und anschließend ändern willst, dann meckert jemand rum, dass das so nicht ginge.

    Java
    final int eins = 1;
    public void changeEinsToZwei()
    {
      eins = 2; // Cannot assign a value to final variable 'eins'
    }


    Threads greifen ja auf Objekte zu, so von außerhalb. Und Objekte lassen sich in Java ja generell verändern.
    Nicht nur, dass du aus eins 2 machen kannst, du kannst aus der ArrayList auch eine eigene List machen. Oder du kannst der ArrayList Objekte hinzufügen. Oder welche herauslöschen.


    Nun nehmen wir mal ein kreatives Beispiel:


    Sekunde 127 deines Spiels, deine Runnable prüft gerade, ob in der Liste der Viecher deines fünften Turms ein Viech dabei ist, dass er erreichen könnte.
    Dein Runnable ist an Position 6 der Liste, zwei kommen noch - als just in dem Moment drei der 8 Gegner von den Türmen 1, 2 und 4 zerfetzt werden.


    JETZT ist deine Liste plötzlich nur noch 5 Gegner groß, deine Turm-Runnable will aber gerade das sechste Viech aus dieser Liste holen - deine Anwendung stürzt gnadenlos ab.
    Andere Änderungen wären weniger gravierend: die Liste wird zu lang und die letzten drei Viecher, die gerade dazu kamen, werden nicht mitgezählt. Ist nur blöd, wenn der Turm direkt am Einstiegsbereich der Kreaturen steht.
    Oder zwei Kreaturen wurden zerfetzt, zwei kamen hinzu und du arbeitest einfach mit falschen Werten, indem du das Projektil in die Pampa feuerst.


    Das final sorgt dafür, dass dies nicht passieren kann. Die Liste hat beim Eintritt in die Runnable genau DEN Zustand, den sie zu dem Zeitpunkt hat.


    Problem: dein Turm bekommt nun überhaupt nicht mehr mit, dass die Viecher 1,2 und 4 zerfetzt wurden, die 0 es gerade so zum Ende geschafft hat und von hinten 18,19 und 20 angerückt kommen. Er ist immer noch auf dem Stand vom Eintritt in den Thread - und der muss nun wirklich nicht aktuell sein.


    Wenn du den ganzen Kram über die GameLoop abwickelst, dann kannst du sehr wohl einen turmspezifischen Run haben: das Ding wird 'rechtzeitig' ausgeführt und schaut einfach bei jedem Turm, ob er schon dran wäre. So kann Turm 1 dann die Liste mit 1,2 und 4 abarbeiten, während bei Turm 5 1,2 und 4 gar nicht mehr vorkommen, dafür aber 18,19 und 20 hinzu gekommen sind.

    Je mehr Käse, desto mehr Löcher.
    Je mehr Löcher, desto weniger Käse.
    Daraus folgt: je mehr Käse, desto weniger Käse.


    »Dies ist ein Forum. Schreibt Eure Fragen in das Forum, nicht per PN!«

  • WoW! Super Erklärung, könnten sich n paar ne Scheibe davon abschneiden :P
    Dennoch finde ich es eigentlich übertrieben, dass man diese Variablen zwingend als final deklarieren muss... man könnte ja immerhin mit try - catch arbeiten um das Problem zu umgehen.


    Nochmals danke für die super Erklärung!
    MfG XoR

  • try catch ist nicht dafür gedacht das fehler abgefangen werden die vermiedern werden können, sondern das fehler abgefangen werden die nicht vermieden werden können.


    ich versteh auch nicht warum du keine klasse spielfeld hast wo ne liste mit spielern ist und ne liste mit türmen und du sekündlich beide durchläufst wo du abgleichst welche spieler in der schussnähe welches turmes sind.

    Java
    for( Tower tower : towerList) {
         for(Player player : playerList) {
              if(player.umgebung(tower) {
                   tower.attack(player);
              }
         }
    }


    so in der art würde ich mir das vorstellen

  • Das mache ich doch...
    Das Problem hierbei ist, dass ich eine bestimmte Funktion jede Sekunde(Schussinterval des Turmes) aufrufen muss.
    Ich kenne es so, dass man Funktuionen, die in bestimmten zeitlichen Intervalen aufgerufen werden sollen(was hier der Fall ist), mithilfe von Runnables und Handler zeitverzögert aufruft.
    Ich will mich natürlich nicht auf diesen Lösungsweg versteifen, bin selber noch ein Anfänger, was Android angeht, deshalb bin ich für jeden anderen Lösungsansatz offen :). Also immer her damit! :D


    MfG XoR

  • WoW! Super Erklärung, könnten sich n paar ne Scheibe davon abschneiden :P


    Danke. :)


    Dennoch finde ich es eigentlich übertrieben, dass man diese Variablen zwingend als final deklarieren muss... man könnte ja immerhin mit try - catch arbeiten um das Problem zu umgehen.


    Welches Problem?
    Das Ändern von Listen ist ein Feature. Wenn die Liste also geändert wird, ist dies doch kein Problem sondern die ganz normale Benutzungsweise.
    Davon abgesehen würde dir schlimmstenfalls permanent eine ListDidChangeException um die Ohren fliegen - damit hättest du dann auch nichts gewonnen.
    Und wie würdest du darauf reagieren wollen? Einfach von vorn anfangen? Im schlimmsten Fall feuert dein Turm dann schlicht niemals.


    Ich persönlich bin übrigens überhaupt kein Freund dieser unsäglichen Threading-Geschichte.
    Android besteht beispielsweise darauf, dass Netzwerkaktivität im Hintergrundthread ausgeführt wird. Warum?
    Wenn _ich_ zeitintensive Netzwerkaktivität habe, dann, weil sie zum Gebrauch des Programms essentiell notwendig ist. Sprich: bevor das nicht durch ist, kann die Anwendung nicht bedient werden.
    Doch dank Androids Vorgaben muss ich im Hintergrund die aktualisierten Daten laden während ich meinen UI-Thread blockieren muss, damit der User nichts kaputtspielt.


    Nicht sehr klug gelöst, wenn man mich fragt. ;)


    Threads sollen Arbeiten übernehmen, die das UI auf Grund ihrer hohen Ausführungszeit blockieren würden.
    Mein UI soll blockieren, also brauche ich den Thread nicht.


    Ein Spiele-UI ist von vorn herein blockiert, zumindest aus Sicht des Betriebssystems. Ob es jetzt künstlich blockiert ist oder manuell blockiert wurde macht da doch keinen Unterschied.


    Im Prinzip läuft auch so ein TowerDefense eher rundenbasiert ab:
    1 Jedes Viech bewegt sich (entsprechend seiner Eigenschaften)
    2 Jeder Turm prüft ob er schießen kann (entsprechend seiner Geschwindigkeit)
    2.1 Turm schießt (entsprechend seiner Eigenschaften)
    2.2 getroffenes Viech prüft, ob es tot ist (entsprechend seiner Gesundheit und des Projektils)
    2.2.1 Beute wird dem Spieler zugeschrieben (entsprechend der Beuteeigenschaftes des Viechs und ggf. Boni des Turms)
    Fahre fort bei Schritt 1.0


    Das heißt, bevor deine Türme schießen können sollten sich die Viecher schon bewegt haben.
    (Meinetwegen auch anders herum: du schießt erst und sie bewegen sich dann.)
    Dann passiert nichts weiter, die Runde endet erst, wenn alle Türme prüften ob sie schießen konnten.
    Das kannst du in die Runnable des Turmes auslagern, solltest du aber nicht tun.


    Genau genommen baust du dir gerade gewaltige Code Smells ein.
    Du verletzt nicht nur das Paradigma der Kapselung, sondern verdrahtest auch Codeteile miteinander, die nichts miteinander zu tun haben.


    Beispiel: dein fünfter Turm KANN gemäß seiner Eigenschaften nur ein einziges Viech angreifen. Wieso sollte er von der Existenz anderer Viecher wissen?
    Wenn du dein Spielfeld mit Quadraten aufteilst (Gamefield Array), dann kennst du ja schon den Umkreis des Bereiches, den dein Turm überblicken kann.
    Warum gibst du ihm dann nicht einfach eine neue Liste mit allen Viechern in diesem Umkreis mit? Wenn er nur die Eigenschaft 'attackiere schwächstes Wesen' hat, dann gib ihm einfach an Stelle ALLER nur das Viech in seinem Umkreis mit der geringsten Energie mit.


    Du kannst schon bei der Erstellung der Listen so viel optimieren, dass du das Auslagern in einen anderen Thread überhaupt nicht benötigst.
    Schneller wird es dadurch eh nicht, da der Mainthread auf die Ergebnisse wartet. (a.k.a. wenn du geschossen hast, muss der UI-Thread das Zeichnen und Bewegen des Projektils übernehmen.)


    Auf Threading würde ich wirlkich nur dann zurückgreifen, wenn es Performanceengpässe gibt.

    Je mehr Käse, desto mehr Löcher.
    Je mehr Löcher, desto weniger Käse.
    Daraus folgt: je mehr Käse, desto weniger Käse.


    »Dies ist ein Forum. Schreibt Eure Fragen in das Forum, nicht per PN!«

  • Zitat

    sweise darauf, dass Netzwerkaktivität im Hintergrundthread ausgeführt wird. Warum?
    Wenn _ich_ zeitintensive Netzwerkaktivität habe, dann, weil sie zum Gebrauch des Programms essentiell notwendig ist. Sprich: bevor das nicht durch ist, kann die Anwendung nicht bedient werden.
    Doch dank Androids Vorgaben muss ich im Hintergrund die aktualisierten Daten laden während ich meinen UI-Thread blockieren muss, damit der User nichts kaputtspielt.


    nicht jede netzwerk aktivität muss den Mainthread blockieren, was wenn du eine nachricht im hintergrund laden willst aber der user die gui weiter benutzen soll.
    ich sitz gerade an einer app für eine firma und wenn ich da den mainthread blockieren lassen würde weil ich sachen übers netzwerk lade, damn der user könnte nichts mehr benutzen ^^


    es gibt unendlich viele use cases wo viel netzwerk traffic verbraucht wird aber die gui benutzbar bleiben muss und der netzwerk traffic somit nicht im mainthread abgearbeitet werden darf

  • Bei meinen Apps soll im Allgemeinfall das UI blockieren, wenn Daten geladen werden.
    Zum einen, damit der User weiß, dass da gerade Traffic auf seinem Gerät verursacht wird. Zum Anderen um auszuschließen, dass der User irgendwelche Dinge tun kann, die ein Fehlverhalten der App auslösen könnten.


    Ich finde es eine Frechheit, dass ich gezwungen werde, die Netzwerkaktivität in einem anderen Thread auszuführen wenn ich das überhaupt nicht will. ;)

    Je mehr Käse, desto mehr Löcher.
    Je mehr Löcher, desto weniger Käse.
    Daraus folgt: je mehr Käse, desto weniger Käse.


    »Dies ist ein Forum. Schreibt Eure Fragen in das Forum, nicht per PN!«

  • naja die anzeige das traffic entsteh bzw wievielt kann man auch anders lösen.
    und was soll der user tun das die app abschmiert nur weil im hintergrund daten geladen werden?


    wenn iein gui element daten braucht die noch nicht vorhanden sind, wartet das gui element und es wird nen listener geschrieben der das gui element setzt sobald diese vorhanden sind.


    aber ich muss dich recht geben, der ZWANG ist etwas übertrieben, als default hätte es auch gereicht und optional hätten user das dann im ui thread machen können

  • naja die anzeige das traffic entsteh bzw wievielt kann man auch anders lösen.
    und was soll der user tun das die app abschmiert nur weil im hintergrund daten geladen werden?


    wenn iein gui element daten braucht die noch nicht vorhanden sind, wartet das gui element und es wird nen listener geschrieben der das gui element setzt sobald diese vorhanden sind.


    Eben. Und jetzt gehen wir einmal davon aus, dass die Daten initial geladen werden. Mein Element wartet also auf die Daten und ich habe beispielsweise 15 leere Listenfelder, die signalisieren, dass Daten geladen werden. Zu dieser Zeit kann auch niemand diese 15 leeren Listenfelder auswählen, da noch keine Inhalte da sind. Das UI ist zu diesem Zeitpunkt schlicht unbrauchbar. Idealerweise (ich gehe gern vom WCS aus) werden auch noch die untersten 10 Elemente zuerst geladen und bereit gestellt, so dass dem User sehr lange die sichtbaren 5 Listenfelder leer angezeigt werden. Der User starrte also gut 5 Sekunden auf nicht auswählbare leere Listenelemente und konnte das UI nicht benutzen.


    Meiner Meinung nach ist der Unterschied zu diesem Szenario und 'der User starrt 6 Sekunden auf einen Downloadhinweis und kann das UI nicht bedienen' offensichtlich: im letzten Fall weiß der User, dass er während das Updates die App nicht vernünftig bedienen kann. Im ersten Fall hingegen lässt sich seine App sporadisch für einige Sekunden einfach nicht bedienen, obwohl es so aussieht als ginge dies.


    Ich bekomme auch jedes Mal bei der Twitter App wieder die Krise, weil ich nicht weiß, ob sie mal wieder nen Fehler hat weil sie mir einen Gesprächsverlauf nicht anzeigt oder ob sie einfach nur noch mit Runterladen beschäftigt ist.

    Je mehr Käse, desto mehr Löcher.
    Je mehr Löcher, desto weniger Käse.
    Daraus folgt: je mehr Käse, desto weniger Käse.


    »Dies ist ein Forum. Schreibt Eure Fragen in das Forum, nicht per PN!«

  • du beschreibst aber nur einen use case, was ist wenn es eben nur 3 leere listenfelder sind und eben andere elemnte die bedient werden sollen.


    beispiel, du hast einen film auf dem phone, der film soll abgespielt werden, infos dazu werden im hintergrund von imdb geladen.
    würden die infos im mainthread laufen könntest das video anschauen vergessen.


    aber wir sind ja schon auf den nenner gekommen das der zwang etwas zuviel des guten ist

Jetzt mitmachen!

Sie haben noch kein Benutzerkonto auf unserer Seite? Registrieren Sie sich kostenlos und nehmen Sie an unserer Community teil!