mehrere Threads/Tasks in einem synchronisieren -> TaskPoo

Status
Nicht offen für weitere Antworten.

hdi

Top Contributor
Hallo,

sorry wegen Titel -> der Spamschutz spinnt wieder und nimmt mir nix an :roll:

Den Titel wollte ich "mehere Threads/Tasks in einem synchronisieren -> TaskPool" nennen.

ich habe mitbekommen dass man bei Spielen 2 Threads haben sollte:
Main Thread, AWT E.D.

Ich hatte schonmal ein Tetris programmiert, ganz klassisch, und bin damit gut zurecht gekommen.

Jetzt mach ich wieder ein Tetris-artiges Spiel, aber diesmal etwas effektvoller. Damit meine ich zB die Beweungen. Während im normalen Tetris die SpielSteine "von 0 auf 100" ihre Position oder Form ändern (man drückt ne Taste und plötzlich ist der Stein gedreht, der GameLoop bewegt den Stein alle x ms ein Feld nach unten, auch ohne Animation), soll bei mir alles flüssig animiert ablaufen.

Bei der Rotation eines Steines soll man so zB wirklich die Rotation sehen, d.h. ich bewege den Stein pixel per pixel mit sehr geringen Sleep-Times dazwischen, um das darzustellen.

Und hier bekomme ich jetzt ein Problem, das ich nicht mehr so angehen kann wie bei meinem Classic Tetris damals:

Ich habe viele Ereignisse im Spiel, die Unterbrechungen haben müssen, um zeichnerisch einen flüssigen Ablauf darstellen zu können. Die Eregnisse dauern unterschiedlich lange, die sleep-times sind auch verschieden. zB dreht sich ein Stein schneller ( = sleep time gering) als er sich nach unten bewegt ( = sleep time etwas länger).

Trotzdem muss alles gleichzeitig geschehen, und wenn der Stein halt schon fertig gedreht ist, bevor er sich ein Stück nach unten bewegt hat, dann ist das halt so. Ihr wisst, was ich meine.

Und dabei bleibt es ja nicht: Ich hab noch Kettenexplosionen usw.

Gut, ich dachte mir jetzt: Hm, da ich nicht anfangen kann, für jedes Event einen eigenen Thread zu starten (weil schnell alles ruckelt und abstürzt, wenn man mal eben 80+ Threads hat, was zB bei einer Kettenexplosion so sein würde), muss man kucken, dass man die einzelnen Aufgaben, die anstehen, in einen einzigen Thread packt (den Gameloop halt).

Aber irgendwie muss man die Threads ja synchronisieren, irgendwie ist das ziemlich übel :autsch:

Hier mal meine Herangehensweise:
Sagen wir, ich habe eine Klasse, die eine aufgabe beschreibt (und die erstmal abstrakt ist):

Code:
public abstract class Task {

	protected Object myItem;
	protected int sleepDelay;
	protected int loops; // oder duration, oder etwas ähnliches

	public Task(int sleepTime, int loops, Object object) {

		myItem = object;
		this.sleepDelay = sleepTime;
		this.loops = loops;
	}

       // hier noch getter für sleepDelay und loops

	public abstract void toDo();
}
Das "Object" ist dann das, worauf dieses Event passiert, zB ein Spielstein. Die Methode toDo() überschreibt
ein Task dann so, dass darin steht, was passieren soll.

So wäre ein spezifischer Task zB "Task_SteinNachUntenBewegen":

Code:
public class Task_SteinNachUntenBewegen extends Task{

	public Task_SteinNachUntenBewegen(SpielStein stein) {

		super(10, 10 ,stein); 
	}

       @Override
       public void toDo(){
               
                (SpielStein) object . bewegeEinenPixelNachUnten();
        }
}

Dieser Task würde nun heissen: Du hast nen Spielstein, bewege den insgesamt 10 Pixel nach unten
(weil du 10x die Methode, die in toDo() stehst aufrufen sollst). Das ganze dauert 100ms, weil du immer
10 ms warten sollst.

Gut, als letztes eine Klasse die irgendwie ne Liste von Tasks hat. Sagen wir mal, die Liste hat im Moment nur 2 Tasks, und es sind beide SteinNachUntenBewegen-Tasks. Die wurden jetz hinzugefügt und im Game sollen jetz 2 SpielSteine gleichzeitig sich runterbewegen.

So weit, so gut, das is einfach: Im GameLoop rufen wir die Methode fireTasks() auf, wo auch immer diese MEthode steht oder zu welchen Klasse sie gehört. Is ja erstmal egal.
Im Endeffekt würde das so aussehen, mit 2 Task_SteinNachUntenBewegen Tasks in der Liste:

Code:
public void fireTasks(){

              int loops = listeVonTasks.get(0).getLoops(); 
              int sleepTime = listeVonTasks.get(0).getSleepTime();

              while(loops > 0){

                           for(int i = 0; i<listeVonTasks.size(); i++){
                                    listeVonTasks.get(i).toDo();
                           }

                          meinSpielFenster.repaint(); // das kennt er halt

                           try{
                               Thread.sleep(sleepTime);
                           } catch (...) {}

                           loops--;
                }
}

edit:

Jetzt krieg ich wahrscheinlich ein paar virtuelle Fäuste ins Gesicht, aber ich lass erstmal eine konkrete Fragestellung weg, weil ich mir noch darüber klar werden möchte ;) Ich muss nochmal bisschen drüber nachdenken, um dann mein Problem konkret bennen zu können.
Ich bedanke mich hier schon mal bei jedem, der sich das durchgelesen hat -- es war NICHT umsonst ;)
Es wär nett, wenn Interessierte mal wieder vorbeischauen, ich werde dann in den nächsten paar Minunten/Stunden meine genau Frage formulieren. Aber es ist schonmal gut zu wissen, worum es hier geht.

Dank euch ;)
 

Apo

Bekanntes Mitglied
Mach doch einfach einen Thread den du 20 Millisekunden oder so schlafen legst.
Dieser ruft eine think oder logic Methode auf in der du dann von Hand testet was nun passieren muss, denn du weißt ja wieviel Zeit seit dem letzten Aufruf vergangen ist. Du kannst also jedes Objekt testen lassen, ob es nun was machen soll. Also die Sachen die sich drehen sollen, die Explosion usw.
So mache ich es immer. Also 1 Thread, der sich um die Logik kümmert. Und mit 50 Aufrufen in der Sekunde also 50fps bist du für ein Tetrisähnliches Spiel gut bedient sag ich mal =)
 

hdi

Top Contributor
So, sorry nochmal für diesen etwas strangen Thread :p

Also, ich musste mir Blatt + Stift nehmen und an ein paar Beispiele rumtun, bis ich drauf gekommen bin, schon mal folgendes Problem zu lösen:

Der obige Code (das beispiel) funktioniert nur, wenn die Attribute "loops" und "sleepTime" von ALLEN tasks in der liste gleich sind.

Ich habe das jetzt geändert, sodass dies nicht mehr der Fall ist:
(wem das ganze hier eher wie eine Präsentation, als eine Frage vorkommt, der soll mich entschuldigen. Ich komme noch zu der Frage, die ich hab...)

zuerstmal hab ich die Klasse Task etwas erweitert:

Code:
public abstract class Task {

   protected Object myItem;
   protected int sleepDelay;
   protected int loops;
   // NEU:
   protected int remainingWait;
   protected boolean isReady;

   public Task(int sleepTime, int loops, Object object) {

      myItem = object;
      this.sleepDelay = sleepTime;
      this.loops = loops;
      //NEU:
      remainingWait = sleepDelay;
      isReady = true;
   }

       // hier noch getter für sleepDelay und loops

   public abstract void toDo();
}

So als nächstes hab ich den GameLoop erstellt. Hier der Code, am besten erstmal die Beschreibung darunter
lesen und dann durchlesen, das erspart es euch ewig nachzuvollziehen was die ganzen schleifen sollen...

Code:
while(gameRunning) {
		
        (synchronized){
	// task-animationen ausführen:

	for(Task task : listOfTasks) {
		if(task.isReady) {
			task.toDo();
			task.loops --;
		}
	}
	try{
		Thread.sleep( 1 );  
	} catch(...) {}
	
	// fertige tasks löschen:
	for(int i = 0; i<tasks.size(); i++) {
		Task task = tasks.get(i);
		if(task.loops == 0) {
			tasks.remove(i);
			i--;
		}
	}
	
	// noch bestehende tasks updaten:
	for(Task task : listOfTasks) {
		task.remainingWait --;
		task.isReady = false;
	}
	for(Task task : listOfTasks) {
		if(tasks.remainingWait == 0) {
			task.remainingWait = task.sleepTime;
			task.isReady = true;
		}
	}

	// zeichnen:
	spielFenster.repaint();
       }
}

Also das ganze macht jetz lediglich, dass man Tasks mit unterschiedlicher loop-Anzahl und auch verschiedenen
Sleep-Anweisungen korrekt malt! Wenn ein Stein jetz langsamer dreht als er droppt etc, passiert trotzdem alles
in der Zeit, in der es passieren soll.
Ich bin soweit echt sehr damit zufrieden. Eigentlich ist das sogar die ultamitve, letztendliche Lösung.

Aber nur wenn...

1) ich das mit dem synchronized so machen kann? Ich meine ich will nicht dass mitten in einer for-schleife durhc die TaskListe ein neuer reingeaddet wird...

2) sleep(1) ok ist :/ Ich könnte es noch auf 5 erhöhen, das is maximum. wenn ich es weiter erhöhe, ist nicht mehr garantiert dass Tasks genau dann anfangen und weitermachen (!! das is noch viel wichtiger für flüssigen und gleichmässigen Spielverlauf !!)...

Ist es okay? bitte sagt ja :bahnhof:
 

L-ectron-X

Gesperrter Benutzer
Ich habe den Titel angepasst, sorry für die Probleme mit den Threadtiteln.
Der Fehler kann vorerst nicht behoben werden, er hat sich versteckt und mag nicht heraus kommen. ;)
 

hdi

Top Contributor
Danke, aber ich will dass jemand "ja" sagt ;)

Falls ihr euch fragt, warum ich nich mehr als 5 ms schlafen kann: meines erachtens nach kann ich das nicht, weil es animationen gibt, die nur 5ms schlafen. Und wenn ich den gameloop nun länger hinlege, ja ich meine dann verzögern sich gewisse dinge, und das auch noch laufzeitabhängig -- das spiel würde manchmal ne explosion so und so schnell zeichen/anfangen, dann so und so schnell etc....
 

Marco13

Top Contributor
Hab's nur überflogen... das mit dem "remainingWait" und dass da in dieser Form runtergezählt wird sieht auf den ersten Blick ein bißchen kompliziert aus - und es ist nur bedingt ... "realzeitfähig": Wenn einer der Tasks für sein "toDo" 5 Sekunden braucht, steht 5 Sekunden lang alles still. Ein "sleep(1)" ist auch nicht unbedingt sinnvoll, weil kein Betriebssystem es schaffen wird, einen Thread genau eine Millisekunde schlafen zu legen.... Grundsätzlich könnte man (in grober Anlehung an Quaxli's Spieleturotial) ja sowas machen wie
Code:
void startGame()
{
    long prevTime = System.currentTimeMillis();
    while (gameRunning)
    {
        long currentTime = System.currentTimeMillis();
        long passedTime = currentTime - prevTime;
   
        advanceGameStaeAbout(passedTime);

        ... Thread.sleep(someTime);

        prevTime = currentTime;
    }
}

In der magischen "advanceGameStaeAbout"-Methode wird der aktuelle Zustand des Spiels eben so viel "weitegeschoben", wie er auf Basis der übergebenen Zeit weitergehen muss. Wenn ein durchlauf der Hauptschleife z.B. 13 Millisekunden gedauert hat, dann wird dort die "passedTime" von 13 Millisekunden übergeben. Ein sich schnell drehender Stein deht sich in dieser Zeit z.B. um 45°, ein langsamer nur um 20° - aber wie weit er sich dreht, entscheidet nicht er selbst, sondern ist vorgegeben durch die übergebene Zeit. Selbst wenn in der "advanceGameStaeAbout"-Methode irgendwas gemacht wird, was 5 Sekunden gedauert hat, dann wird beim nächsten Aufruf ein Zustand hergesetellt, der dem entspricht, der eben nach 5 Sekunden eintreten muss....
 

Quaxli

Top Contributor
Kann ich nur zustimmen :)
Du könntest auch mal nach "Killer Game Programming" googeln, da gibt's einige Kapitel zum herunter laden, die sich mit "fortgeschrittenen GameLoops" beschäftigen und darauf abzielen größer 80 FPS zu kommen, z. B. durch aktives Rendern etc..
Damit hast Du dann bei Deinem GameLoop Frequenzen, die klein genug sind um jede Animation darzustellen.
 

EgonOlsen

Bekanntes Mitglied
Dieses ganze Task-Konstrukt wirkt mir etwas zu "gut gemeint". Wenn ich das richtig verstehe, dann haben deine Spielobjekte so gesehen gar keinen echten Zustand, oder? D.h. wenn ich Stein X zum Beispiel drehe, dann weiß Stein X gar nicht, dass er sich dreht, sondern in der Taskliste hängt lediglich was drin, was sagt: "Drehe Stein X für y ms nach z" oder so ähnlich. Das lässt in mir die Frage aufkommen, wer die Tasks denn einfügt? Der Spieler durch Tastendrücke? Irgendeine überordnete Instanz? Wer auch immer...woher soll diese Instanz wissen, dass der einzufügende Task gültig ist, d.h. wenn Stein X z.B. gerade explodiert, dann soll er sich vermutlich nicht drehen oder fallen oder sonstwas. Wenn der Stein selber keinen Zustand hält, weiß das aber nur der Task...und der lungert irgendwo in der Liste rum. Weiß der Stein es aber doch (oder irgendwer anders, quasi eine Art Steincontroller), dann macht der Task meiner Ansicht wiederum keinen Sinn. Dann können Stein und/oder Steincontroller auch gleich die Operationen ausführen, die sonst im Task gekapselt sind.
Also kurz: Ich halte dieses Taskkonzept für nicht sehr sinnvoll. Ich denke, man kann damit durchaus ein Tetris bauen, aber ich würde es nicht so machen.
 

hdi

Top Contributor
@egon:

natürlich hast du recht: Statt dem ganzen Task Zeug könnte ich auch dem Stein selber so Attribute geben wie "loop", "sleepTime" etc, und die Methoden da drin selber implementieren. die Klasse Task kapselt das ja nur, wie du schon sagtest. Das ist auch richtig. aber trotzdem brauch ich ja irgendwo ne Liste von Dingen, die abgearbeitet werden müssen. Ob das nun Tasks sind oder SpielSteine selbst, macht da keinen Unterschied.
Und eine Sache find ich besser an Tasks: Nicht alle Ereignisse passieren zB auf SpielSteinen, es gibt Ereignisse, die gar nicht an einem Objekt hängen. Mit dem Task-Konzept kann ich sowas trotzdem reinschieben, dann enthält halt die "toDo()" Methode nur Seiteneffekte oder so.

zB: TaskManager.addToList (new HighScoreUpload_Task() / DrawSomeEffectsOnScreen_Task() ) etc..

Also ich kuck mal in wiefern ich damit zu Recht komm und ob es funzen wird... im Moment hab ich Swing probleme, er bleibt mir hängen usw, auch ein EventQueue/SwingUtilities.invokeLater hilft nicht, aber ich hab das ganze vorher schnell hingeklatscht, ich mach das jetz nochmal sauber dann müsste es schon gehen...

Danke soweit für eure Hilfe, ich meld mich zu über 90% nochma ;)
 

EgonOlsen

Bekanntes Mitglied
hdi hat gesagt.:
@egon:
Mit dem Task-Konzept kann ich sowas trotzdem reinschieben, dann enthält halt die "toDo()" Methode nur Seiteneffekte oder so.
zB: TaskManager.addToList (new HighScoreUpload_Task() / DrawSomeEffectsOnScreen_Task() ) etc..
Schon. Aber vom Gefühl her sage ich weiterhin, dass das alles nur solange gut funktioniert wie die Tasks weitgehend eigenständig sind, nicht vom Status anderer Tasks abhängen und nicht mittendrin auf was Unvorhergesehenes reagieren müssen. Was macht dein 10-Pixel-nach-unten Task denn z.B. wenn es nach 5 Pixeln eine Kollision gibt, die den Spielstein zerstört und dem Spieler 35 Punkte abzieht? Muss er dann zwei neue Tasks erzeugen ("Bumm machen" und "Spieler Punkte geben"), die in die Liste hängen und sich selber entfernen? Ich habe irgendwie den Eindruck, du versuchst damit eine Art "singlethreaded Threadmodel" zu basteln und dein TaskManager ist quasi ein primitiver Scheduler.
 

hdi

Top Contributor
also ich hab noch eine äussere struktur, die die logischen dinge checkt. so werden tasks nur gesartet, wenn sie gültig sind. Wenn also ein Stein nicht mehr fallen kann, weil das Spielfeld zu ende ist, wird kein solcher Task mehr erzeugt.

Also es ist jetz so, dass ichs soweit eig. hab. Beim Test krieg ich jetz leider folgenden Fehler:

Exception in thread "Thread-5" java.util.ConcurrentModificationException
at java.util.AbstractList$Itr.checkForComodification(Unknown Source)
at java.util.AbstractList$Itr.next(Unknown Source)
at controller.GameLoop.executeCycle(GameLoop.java:44)
at controller.GameLoop.run(GameLoop.java:23)

In Zeile 44 von executeCycle() in meinem Gameloop wird auf die Liste aller Tasks zugefriffen. Da steht konkret:

Code:
for (Task task : listOfAllTasks)

und da gibt er mir eben diesen Itr.next (Unknown Source) fehler... Also irdendwie sieht das ja nach Race Condition aus, vorallem weil der Fehler immer irgendwann passiert, d.h. ein paar Sekunden funzt alles bestens und er rasselt mir lauter Steine runter (ich pump eben so Tasks in die Liste, diemir mehrere Steine nach unten bewegen, also besser gesagt: für jeden Stein den Task halt)... Aber plötzlich hängt sich dann alles auf mit dem o.g. Fehler.

Das kommt mal nach 10 Sek, mal schon nach einer.

Ich hab jetzt versucht alles, was innerhalb der executeCycle() Methode liegt, auf die Liste der Tasks zu synchronisieren:

Code:
synchronized(listOfAllTasks){
   for (Task task : listOfAllTasks)
    ....
}

hilft leider nix.. Auch die Methode selbst synchronized zu machen, hilft nix.. Könnt ihr euch vorstellen wo da das Problem sein könnte,oder braucht ihr mehr Code oder Infos?

Also diese Methode executeCycles() wird eben innerhalb meines GameLoops aufgerufen, genauer alle 5ms. Und die geht die Liste von Tasks durch und führt die "toDo()" Methode aller Tasks aus, die grad dran sind und nich ne Schlafpause eingelegt haben...

Verstehe nicht, wieso es da Fehler gibt, vorallem wenn ichs auch noch synchronisier...

Achso, bevor er sich aufhängt kommt oft erstmal zig mal (was weiss ich wie oft in der Sekunde) folgender Fehler in die Console geschneit : (der aber das Programm noch nicht beeinflusst wie es scheint)

Exception in thread "AWT-EventQueue-0" java.util.ConcurrentModificationException
at java.util.AbstractList$Itr.checkForComodification(Unknown Source)
at java.util.AbstractList$Itr.next(Unknown Source)
at _DEMOS.DEMO_TaskManager$1.paintComponent(DEMO_TaskManager.java:51)
at javax.swing.JComponent.paint(Unknown Source)
at javax.swing.JComponent.paintToOffscreen(Unknown Source)
at javax.swing.BufferStrategyPaintManager.paint(Unknown Source)
at javax.swing.RepaintManager.paint(Unknown Source)
at javax.swing.JComponent._paintImmediately(Unknown Source)
at javax.swing.JComponent.paintImmediately(Unknown Source)
at javax.swing.RepaintManager.paintDirtyRegions(Unknown Source)
at javax.swing.RepaintManager.paintDirtyRegions(Unknown Source)
at javax.swing.RepaintManager.seqPaintDirtyRegions(Unknown Source)
at javax.swing.SystemEventQueueUtilities$ComponentWorkRequest.run(Unknown Source)
at java.awt.event.InvocationEvent.dispatch(Unknown Source)
at java.awt.EventQueue.dispatchEvent(Unknown Source)
at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.awt.EventDispatchThread.run(Unknown Source)

das is so ziemlich das gleiche, wieder in der zeile wo ein array durchlaufen wird. da is es aber nich die liste von tasks, sondern die liste von steinen. allerdings greifen die tasks ja auf die steine zu...ka

den kompletten GameLoop, der ein Thread ist, starte ich aus der Main Methode indem ich ihn als Runnable auf die EventQueue setze, per invokeLater().
 

EgonOlsen

Bekanntes Mitglied
Eine ConcurrentModificationException bekommst auch dann, wenn du innerhalb der Schleife die Liste modifizierst. Das hat nicht immer was mit Threads zu tun. Welcher andere Thread greift eigentlich zur Laufzeit auf die Liste zu, so dass du da immer ein synchronized einbaust? Der EDT? Ansonsten hast du doch nur einen Hauptthread, wenn ich das richtig verstanden habe.
 

hdi

Top Contributor
naja das sync hab ich gemacht weil ich kucken wollte ob ich damit das problem löse.

es gibt meinen game loop der auf die liste der tasks zugreift, und damit auf bestimmte spielsteine, weil die tasks damit was machen. und die paintcomponent methode meines fensters, also der AWT thread, der alle spielsteine zeichnet.

ich denke das problem ist, nachdem du das über die exception gesagt hast, dass ein spielstein vom gameloop geändert wird, während er gezecihnet wird.

aber dann müsste doch ein sync helfen, oder benutz ich das falsch? woran kann es denn noch liegen?

hier die zwei methoden, die die fehler machen nochmal genau:

Code:
private void executeCycle() {
		synchronized(tasks) {
		for (Task task : tasks) {
			if (task.wantsToProcess()) {
				task.processCycle();      // modifiziert einen spielstein
				task.cycleProcessed();
			}
		}
		}
	}

Code:
public void paintComponent(Graphics g) {
				super.paintComponent(g);
				synchronized (tiles) {
					for (Tile t : tiles) {    // liste der spielsteine
						t.paint(g);
					}
				}
			}
 

hdi

Top Contributor
ok hab die fehler jetz weggekriegt. liegt wohl daran dass tasks zu jeder zeit geaddet werden können,
ich hab die schleifen jetz immer so gemacht:

Code:
int listLen = list.size();
for(int i = 0; i<listLen; i++){
...
}

und dann gibts keine probs mehr.

bekomme jetz andere fehler, nullpointer usw aber das liegt wohl daran dass hier was logisches nicht stimmt. irgendwie wird meine liste von tasks auch immer längern, schnell über 1000, obwohl ich sie eigentlich bei jedem gameloop durchgang aufräume, aber das checkt er wohl noch net :p
 

Fu3L

Top Contributor
hdi hat gesagt.:
es gibt meinen game loop der auf die liste der tasks zugreift, und damit auf bestimmte spielsteine, weil die tasks damit was machen. und die paintcomponent methode meines fensters, also der AWT thread, der alle spielsteine zeichnet.

So weit ich das überflogen hab, kann das nicht sein, da du in deinem GameLoop erst die Logic ausführst und danach repaint() aufrufst, d.h. die Methode paintComponent() ist erst nach der Logik dran und die Logic wird erst wieder ausgeführt, wenn das Zeichnen beendet ist. Und der "AWT-Thread" den du wahrscheinlich meinst, nennt man Event-handling-thread und der kümmert sich nciht ums Zeichnen, sondern nur um Events, die in einem Fenster auftreten...

Die NullPointerException könnte unter Umständen auftreten, weil du in einem Task vllt einen anderen löscht?

Ich muss meinen "Vorrednern" schon recht geben, dass es weit aus praktischer und übersichtlicher sein könnte, alle Aktionen in die Objekte selbst zu verlagern...
 

hdi

Top Contributor
hm, also ich finds eig. so sehr übersichtlich. ich kann mir halt grad nich vorstellen wie ich das problem lösen soll wenn ich's alles in die objekte selbst packe -- irgendeine art des managements brauch ich sowieso, damit das spiel eben flüssig läuft und events sofort eintreten. das problem ist eben die unterschiedliche dauer und unterbrechungen verschiedener dinge. efinach so im gameloop alle steine durchlaufen und ihr evetn auslösen, wenn sie eins haben, funzt nicht. ich krieg das mit diesem tasksystem soweit gut hin und es klappt ja auch wunderbar, bis jetzt.

ich hab eben grad nur paar fehler, wo ich jetz nich sagen würde "ohhh das wundert mich aber" weil ich die logics, also alle schleifendurchgänge und was darin passiert, mal so auf test gecodet hab und es da halt einfach noch fehler gibt.

Ich hoffe, ich krieg's hin, und wenn sich irgendwann rausstellt, dass das system doch zu kompliziert ist, komm ich zurück xD das wird sich dann zeigen, wenn ich anfangen muss zu checken ob gewisse tasks überhaupt gestartet werden dürfen. ob sich zB ein stein anfangen darf zu drehen oder nicht, weil er sich in einen anderen reindrehen würde.
das is eh nochmal ne andre geschichte...

naja bis bald ;)
 

EgonOlsen

Bekanntes Mitglied
Fu3L hat gesagt.:
So weit ich das überflogen hab, kann das nicht sein, da du in deinem GameLoop erst die Logic ausführst und danach repaint() aufrufst, d.h. die Methode paintComponent() ist erst nach der Logik dran und die Logic wird erst wieder ausgeführt, wenn das Zeichnen beendet ist. Und der "AWT-Thread" den du wahrscheinlich meinst, nennt man Event-handling-thread und der kümmert sich nciht ums Zeichnen, sondern nur um Events, die in einem Fenster auftreten...
Das ist beides leider falsch. repaint() zeichnet gar nichts neu. Es signalisiert nur, dass die Komponente gerne neu gezeichnet werden möchte und kehrt danach sofort zurück. Du kannst 10mal repaint() aufrufen, dabei kommt aber unter Umständen nur ein tatsächlicher Zeichenvorgang raus. Und genau das tut dann der AWT Event Dispatch Thread, d.h. er ist eben nicht nur für die Events zuständig, sondern auch für das Zeichnen.
 

EgonOlsen

Bekanntes Mitglied
hdi hat gesagt.:
hm, also ich finds eig. so sehr übersichtlich. ich kann mir halt grad nich vorstellen wie ich das problem lösen soll wenn ich's alles in die objekte selbst packe -- irgendeine art des managements brauch ich sowieso, damit das spiel eben flüssig läuft und events sofort eintreten. das problem ist eben die unterschiedliche dauer und unterbrechungen verschiedener dinge. efinach so im gameloop alle steine durchlaufen und ihr evetn auslösen, wenn sie eins haben, funzt nicht.
Ja, eine Art von Controller (oder auch mehrere) ist schon sinnvoll. Aber ich verstehe nicht, wo du das Problem siehst....andere haben es doch auch schon vorgeschlagen: Du misst quasi die verstrichene Zeit (oder zählt "Ticks"...das benutze ich immer) und lässt den Spielzustand entsprechend voran schreiten. Also z.B. spielSteinController.process(ticksPassed); und in process() werden dann die Steine durchgeeiert und entsprechend den verstrichenen Ticks und ihrem eigenen Status bewegt oder sonstwas. Warum soll das nicht gehen? Mache das nie anders und es geht hervorragend.
 

hdi

Top Contributor
ich hab ja nie gesagt, dass das nicht geht. aber wo is der unterschied zu meinem konzept, das versteh ich nicht ganz.
mein game loop is ja nix andres als diese process() methode bei dir. der einzige unterschied ist denke ich, dass meine process() methode sozusagen selbst auch der controller ist. Und diese ganzen Tasks sind ja nun wirklich eig. das gleiche als wenn es direkt in der Klasse SpielStein stehen würde, nur eben dass ich es übersichtlicher find, weil ich so die Klasse SpielStein klein halte, und ganz schnell neue Funktionatlitäten für die Steine einbauen kann, indem ich einfach nen neuen Task dafür schreibe.

Aber wenn es doch nen grossen Unterschied gibt, der in dem, was du meinst, besser ist als bei mir, dann wärs cool wenn du mir das noch etwas genauer schilderst, wie du es machen würdest.

Danke!
 

EgonOlsen

Bekanntes Mitglied
hdi hat gesagt.:
mein game loop is ja nix andres als diese process() methode bei dir. der einzige unterschied ist denke ich, dass meine process() methode sozusagen selbst auch der controller ist. Und diese ganzen Tasks sind ja nun wirklich eig. das gleiche als wenn es direkt in der Klasse SpielStein stehen würde, nur eben dass ich es übersichtlicher find, weil ich so die Klasse SpielStein klein halte, und ganz schnell neue Funktionatlitäten für die Steine einbauen kann, indem ich einfach nen neuen Task dafür schreibe.
Naja, vielleicht verstehe ich dich auch falsch. Aber so, wie ich es eben verstehe, halte ich für unglücklich, dass zu viel Logik und Zustand in den Tasks steckt. Sagen wir z.B., ein Stein kann rot leuchten. Wenn ich dich richtig verstanden habe, dann ergibt das einen Task, der quasi "Stein leuchtet für 100 Iterationen" sagt. Wenn es jetzt eine Regel gibt, die besagt: "Es darf nur ein Stein zu einer Zeit leuchten", dann müsstest du bei jedem neuen Leuchtstein deine Tasks befragen, ob einer dabei ist, der einen Stein leuchten lässt. Das halte ich für unsinnig. Wenn ein Objekt eine Eigenschaft besitzt, dann sollte man dies dem Objekt auch ansehen und die Informationen nicht in einem Metaobjekt "Task" verstecken.
In meiner Denke gibt es ein Art Bean "Stein", welches den Zustand eines Steins enthält, also Position, Farbe, evtl. Animation usw. Dazu gibt es einen SteinController, der Zugriff auf alle Steine hat und damit Dinge anstellen kann. Rufe ich darauf process() auf, ruft der Controller auf all seinen Steinen process() auf. Zu guter letzt gibt einen SteinView, der den Stein dann letztendlich auf Basis seines Zustandes zeichnet. Wenn du zum Beispiel Multiplayer übers Netz realisieren willst, musst du nur das Stein-Bean transferieren, weil es alles nötige enthält. In deiner Task-Denke ist das unmöglich, weil man den Zustand des Steins aus seinen laufenden Tasks bestimmen müsste...oder ich habe das falsch verstanden...kann auch sein... :wink:
 

hdi

Top Contributor
Hm, is schon was wahres dran, was du sagst.

Aber dann hab ich das Problem, weswegne ich diesen Thread erstellt hatte: Ich weiss nicht, wie ich mit eurer Art und Weise ein flüssiges Spiel zusammenbekommen soll. Ihr sagt ja: leg doch einfach nen Thread 20 ms schlafen und wenn er aufwacht zählst du die ticks die vergangen sind, auf dieser Basis updatest du die Steine. Oder?

Also vllt reden wir aneinander vorbei. Nochmal konkretes Beispiel:

Ein Stein kann sich drehen, und damit meine ich nicht nur, dass er irgendwie seine Teile umbauen kann sodass er dann gedreht aussieht, nein er kann sich wirklich animiert drehen.

Sagen wir doch auch mal, ja er kann auch rot leuchten. Und wieder ist damit gemeint: er kann aufleuchten und abdimmen, d.h. das Leuchten passiert als ein "Flash", nicht einfach Farbe Weiss->Rot und wieder Rot-> Weiss, sondern mit schönen Farbübergängen in einer Animation.

Da kann es dann noch lauter so Dinge geben.

Und mein Problem war jetzt, und deshalb hab ich dieses ganze Task-Zeug gemacht, dass das ja flüssig laufen soll, d.h. ja nix anderes als dass es sehr viele repaints() pro Sekunde gibt.

Ich kann keinen Thread 10-20 ms schlafen legen, und dann das Model anpassen jenachdem wieviele Ticks vergangen sind. Weil wenn ich ihn 20ms pennen leg, kann er nur alle 20ms painten. Und dann sieht der Flash-Effekt nich toll aus, und der Stein dreht sich ruckartig.

Das hat vllt. gar nix mit diesem Thema Tasks oder Controller zu tun, dann hab ich mich nur falsch ausgedrückt. Ich weiss halt nur: Als ich mal ein klassisches Tetris gemachthab, war das alles kein Ding. Aber in dem neuen jetzt passieren viel mehr Dinge, nein, es können viel mehr Dinge passieren.. Das macht den Aufbau des GameLoops ziemlich schwer. Und sie passieren SCHNELL, weil sie gut animiert sein sollen. Und als allerwichtigstes: Sie passieren alle GLEICHZEITIG.

Also, wie soll ich zig Dinge, die schnell animiert sind, gleichzeitig zeichnen? Mir ist eben nur eingefallen dass mein GameLoop quasi nie idlet, höchstens 5 ms (das wär dann halt die Minimalzeit für eine Animation, sprich in weniger als 5ms passiert im Model nix). Und dann eben diese Liste mit Tasks, wo die Steine und der Player, und auch der Gameloop, einfach Tasks reindrücken und sie werden quasi fortlaufend bearbeitet.

Ich kann mir bei so einem Spiel doch keine grösseren Sleep-Times erlauben, dann ruckelt doch alles :?:

Ach.. man das schwerste hier im Forum ist es, sich richtig auszudrücken. Als Anfänger weiss ich ja gar nicht, wo mein Problem wirklich liegt, sozusagen..
IWenn ich mir das alte Tetris ansehe, das eigentlich wie das Original war und nur ein einziger Effekt, und zwar dass Linien kurz aufgeblinkt haben bevor sie verschwunden sind, stelle ich fest: Mit dem, was ich für das neue im Kopf hab, komm ich mit meinem alten Code nicht weit. Und ich weiss nicht recht, wie ich so viele, schnelle, Animationen und logische Berechnungen in einen GameLoop packen soll
 

EgonOlsen

Bekanntes Mitglied
Also ich würde keinen fixen Sleep von 20ms annehmen, weil die Zeit der eigentlichen Berechnungen ja variieren kann, aber letztendlich sind 50fps für die Spiellogik völlig ausreichend. Damit bekommst du alles gewünschte flüssig hin. Wie im Thread bereits erwähnt, würde ich dazu auf Active Rendering wechseln (also weg vom Swing-eigenen repaint-Mechanismus und das Zeichnen explizit im Gameloop selber erledigen).
Du kannst ja mal folgendes versuchen, wenn du Zweifel an den 50fps hast: Guck dir mal mein letztes Werk an: http://jpct.de/robombs.game/
Das verwendet zufällig genau dieses Intervall für die Spiellogik, d.h. es gibt dort keine Animation und keine sonstige Aktion, die höher getaktet ist, als diese 50fps (die Bildrate liegt bei 75fps, aber die ist abgekoppelt...sie hat mit der Spiellogik nichts zu tun). Wenn du da irgendein Ruckeln in der Animation oder im Ablauf findest, dann lass es mich wissen (außer deine GraKa ist zu langsam...dann kauf dir eine neue...).
 

hdi

Top Contributor
ja okay.. also ich hab halt noch das problem, dass mein spiel ja ohne user eingaben etwas machen soll, ich mein es is tetris, d.h. es kommen steine und die werden nach unten gerückt. theoretisch kann man da einfach zukucken bis gameover ist.
und da ja ein stein regelmässig ein stück nach unten gesetzt werden soll, muss ich den gameloop auch immer gleich lang schlafen legen, weil der gameloop ist ja der, der das macht...

zum active rendering:
hab bei google jetz nur paar 1-seiten tutorials mti bisschen code gefunden, aber nicht wirklich ne genaue erklärung wie man das machen muss wenn viele dinge zu zeichnen hat.
ich hab jetz mitbekommen ich schalt das swing-painting aus im frame, aber dann was in meine eigene paint methode reinkommt hab ich noch nich so ganz überrissen..
 

EgonOlsen

Bekanntes Mitglied
hdi hat gesagt.:
und da ja ein stein regelmässig ein stück nach unten gesetzt werden soll, muss ich den gameloop auch immer gleich lang schlafen legen, weil der gameloop ist ja der, der das macht...
Nein, das musst du eigentlich nicht. Du legst nur fest, wie weit sich ein Stein pro Zeiteinheit bewegen soll. Ob diese Einheit nun ms oder Ticks sind, ist letztendlich egal. Der Steinbewegen-Methode übergibst du dann den Zeitwert, der seit dem letzten Aufruf vergangen ist und bewegst den Stein entsprechend. Damit funktioniert das auch bei langsamen Rechnern. Dann fängt die Animation halt an zu ruckeln, aber das Spiel an sich behält seine Geschwindigkeit bei.
 

hdi

Top Contributor
also entweder ich versteh das alles nicht ganz, oder es ist einfach nur komplizierter ???:L
Statt:

Code:
sleep(20);
doStuff();
repaint();

müsste ich ja dann machen:

Code:
Thread.yield() 
doStuffInRelationTo(vergangeneTicks);
if(vergangeneTicks == 20){
    repaint()
}

denn es ist die eine Sache, das ich die vergangene Zeit nicht per hand festlege sondern entsprechend das Model verändere, aber es ist die andere Sache, dass gleichmässig gepainted werden soll!

Konkret:

Ein Stein soll pro Sekunde 10 pixel nach unten fahren, wenn jetz 100ms geschlafen wurde, bewege ich ihn halt 1 pixel nach unten, wenn 200 geschlafen wurde, dann 2. Aber ich kanns ja nicht bringen, dass ich mal nach 100ms painte, dann nach 200, weil das Spiel ja total ungelichmässig verlaufen würde. Nicht im Model, aber in der Ansicht, das is ja kacke für ein Game..

Ich glaube aber eher, ich versteh euch nur nich richtig :bahnhof:
 

EgonOlsen

Bekanntes Mitglied
hdi hat gesagt.:
Ein Stein soll pro Sekunde 10 pixel nach unten fahren, wenn jetz 100ms geschlafen wurde, bewege ich ihn halt 1 pixel nach unten, wenn 200 geschlafen wurde, dann 2. Aber ich kanns ja nicht bringen, dass ich mal nach 100ms painte, dann nach 200, weil das Spiel ja total ungelichmässig verlaufen würde. Nicht im Model, aber in der Ansicht, das is ja kacke für ein Game..
Das verstehe ich nun wieder nicht!? Abgesehen davon, dass (wie oben erwähnt) ein repaint() auf einer Swing-Komponente kein sofortiges Zeichnen auslöst und ich das für Spiele deswegen für ungeeignet halte, sehe ich jetzt das Problem nicht. Wenn deine Spielschleife eben 200ms braucht, weil der Rechner ein Pentium 100Mhz ist, dann ist das halt so und die Grafik ruckelt. Ist doch besser, als wenn das ganze Spiel wie Sirup, weil zu langsam, läuft.
Also ich würde es etwa so machen:


Code:
if (ticks>0) {
   doLogicStuff(ticks);
   doPainting();
} else {
   sleep(whatever-logicTime-paintTime);
}

Wobei doPainting halt selber zeichnen würde und ich den Swing-repaint-Kram aushebeln würde. Aber dazu gibt es im Forum hier auch andere Meinungen, die sicher auch ihre Berechtigung haben.
 

hdi

Top Contributor
ok also wegen repaint(): generell stimmt es schon, es is nur die bitte zu malen. aber solange du das nicht 100 mal in 1 ms aufrufst, paintest es auch tatsächlich immer. zumindest habe ich noch nie feststellen können, dass ein repaint() nicht neu gemalt hat.
trotzdem hab ich mir deinen tipp mit active rendering zu herzen genommen, und werde das so machen. ich hab mir ne demo gezogen wo active rendering benutzt wird und ich hatte 800fps, wobei non-stop etliche rechtecke gemalt wurden. das scheint also zu taugen xD

aber leider gottes verstehe ich einfach nicht, was du mir sagen willst mit diesen ticks und dem repaint.
du sagst:

Code:
if (ticks>0) {
   doLogicStuff(ticks);
   doPainting();
} else {
   sleep(whatever-logicTime-paintTime);
}

Erstmal frag ich mich, wo ich ticks erhöhe, und wie? ich meine was ist ein "tick". ist das eine ms? also dann:

Code:
while(gameRunning) {
	long start = System.currentTimeMillis();
	if(ticks > 0) {
		doLogicStuff(ticks);
		doPainting();
                ticks = 0;
	}
	else {
		sleep(whatever-logicTime-paintTime);
	}
	long end = System.currentTimeMillis();
	ticks = end-start;
}

Als zweites verstehe ich die sleep-Anweisung nicht. Kann ich da nicht einfach Thread.yield() nehmen?
Ich meine so wie es aussieht is das immer irgendeine variable Zeit, und du nutzt nur die logicTime und paintTime
um irgendnen zufälligen Wert zu würfeln. Oder seh ich das falsch?

So, und nun noch immer mein grosses Verständnisproblem: Ich will automatisch einen Stein jede sekunde
ein Stück nach unten bewegen! Das heisst nicht, dass es eine Sek dauert bis ein Stein runtergerutscht ist, und er kontinuierlich rutscht! Ich könnt mir den Mund fusselig reden, ich kapier einfach nich wie das zu realisiern sein soll. Der GameLoop setzt jede Sekunde den Stein eins runter. GENAU jede Sekunde, IMMER nach EXAKT 1000ms.
Das kann ich nach meinem Verständnis mit obigen Code NICHT machen, und damit kann ich mir nicht vorstellen, wie
das Spiel gleichmässig laufen soll. Ich kanns nicht, ich checks einfach nicht :( Sorry...ich komm mir langsam richtig dumm vor. Das gibt's doch nicht, ich kapier diese Logik dahinter nicht, wie zum Teufel soll ich einen Stein kontrolliert und gleichmässig droppen? Wie?

Ich verstehe dieses Tick-Konzept so: Es funktioniert nur, wenn kontinuierlich und ohne Pausen gerechnet und gezeichnet werden soll. Dann ändert er das Model entsprechend den vergangenen Ticks und zeichnet neu. Ok.
Aber wenn ich REGELMÄSSIG, KONTROLLIERTE Pausen haben will, was dann?

Ich wär echt dankbar für KONKRETEN code, der genau das realisiert. Ich kanns NUR so:

Code:
while(true){
   sleep(1000)
   stein.drop()
   repaint()
}

Wie soll das gehen mit dem Tick-Konzept?

Tausend Dank
 

EgonOlsen

Bekanntes Mitglied
Ok, also ich habe in 15min. mal was zusammengehackt. Vielleicht wird damit klarer, was ich meine. Das ist wie gesagt gehackt...da mag manches blödsinnig drin sein und eigentlich müsste man beim Sleep auch das "Überschlafen" noch kompensieren. Hatte ich jetzt aber keine Lust zu.
Wenn du einen Stein immer nur alle x ticks (also z.b. 50 für eine Sekunde) nach unten bewegen willst, dann ergänze in Stone einfach einen Zähler und zieh die aktuellen Ticks davon ab. Und wenn der Wert dann <=0 ist, bewege den Stein und setzen den Zähler zurück.

Also:

Code:
import javax.swing.*;
import java.util.*;
import java.util.List;
import java.awt.*;
import java.awt.image.*;

public class GameLoop extends JFrame {

   protected static final long serialVersionUID=0L;
   
   private final static int SPEED=20;
   private final static int TILE_SIZE=32;
   private List<Stone> stones=new ArrayList<Stone>();
   
   public static void main(String[] args) throws Exception {
      new GameLoop().doIt();
   }
   
   private void doIt() throws Exception {
      this.setSize(800,600);
      this.setVisible(true);
       this.createBufferStrategy(2);
       BufferStrategy strategy=this.getBufferStrategy();

      Ticker ticker=new Ticker(SPEED);
      while(this.isVisible()) {
         
         long start=System.nanoTime()/1000000L;
         int ticks=ticker.getTicks();
         if (ticks>0) {
            Graphics g=strategy.getDrawGraphics();
            g.clearRect(0, 0, this.getWidth(), this.getHeight());
            for (Iterator<Stone> itty=stones.iterator(); itty.hasNext();) {
               Stone s=itty.next();
               if (s.y>this.getHeight()) {
                  itty.remove();
               } else {
                  s.y+=s.s*ticks;
                  g.setColor(s.col);
                  g.fillRect(s.x, s.y, TILE_SIZE, TILE_SIZE);
               }
            }
            if (Math.random()<0.4) {
               Stone s=new Stone();
               stones.add(s);
               s.x=(int) (Math.random()*this.getWidth());
               s.s=(int) (Math.random()*4)+1;
               s.col=new Color((int)(Math.random()*255), (int)(Math.random()*255), (int)(Math.random()*255));
            }
            strategy.show();
         }
         long end=System.nanoTime()/1000000L;
         
         if (end-start>0 && end-start<SPEED) {
            Thread.sleep(SPEED-(end-start));
         }
      }
      System.exit(0);
   }
   
   private static class Stone {
      int x=0;
      int y=0;
      int s=0;
      Color col=null;
   }
   
   private static class Ticker {

      private int rate;
       private long s2;

       public Ticker(int tickrateMS) {
           rate = tickrateMS;
           s2 = System.nanoTime()/1000000L;
       }

       public int getTicks() {
           long i = System.nanoTime()/1000000L;
           if (i - s2 > rate) {
               int ticks = (int) ((i - s2) / (long) rate);
               s2 += (long)rate * ticks;
               return ticks;
           }
           return 0;
       }
   }
}
 

Quaxli

Top Contributor
@EgonOlson:
Schönes Beispiel. Nachdem es weiter oben aber auch Probleme damit gab, wie der Zustand der einzelnen Objekte in diese ausgelagert wird, habe ich mir mal erlaubt, eine kleine Modifikation einzubauen und die Bewegung in die Klasse Stone auszulagern, damit das was Ihr bisher so schön erklärt habt, auch eingesetzt wird ;)

Code:
import javax.swing.*;
import java.util.*;
import java.util.List;
import java.awt.*;
import java.awt.image.*;

public class GameLoop extends JFrame {

   protected static final long serialVersionUID=0L;
   
   private final static int SPEED=20;
   private final static int TILE_SIZE=32;
   private List<Stone> stones = new ArrayList<Stone>();
   private List<Stone> waste  = new ArrayList<Stone>();
   int ticks = 0;
   
   public static void main(String[] args) throws Exception {
      new GameLoop().doIt();
   }
   
   private void doIt() throws Exception {
      this.setSize(800,600);
      this.setVisible(true);
       this.createBufferStrategy(2);
       BufferStrategy strategy=this.getBufferStrategy();

      Ticker ticker=new Ticker(SPEED);
      while(this.isVisible()) {
         
         long start=System.nanoTime()/1000000L;
         ticks=ticker.getTicks();
         if (ticks>0) {
            Graphics g=strategy.getDrawGraphics();
            g.clearRect(0, 0, this.getWidth(), this.getHeight());

            for (Iterator<Stone> itty=stones.iterator(); itty.hasNext();) {
               Stone s=itty.next();
               
               //Methoden-Aufruf für Stone
               s.checkStatus();
               s.move();
               
               //"Rausgefallene" merken
               if(s.remove){
              	 waste.add(s);
               }
               
               g.setColor(s.col);
               g.fillRect(s.x, s.y, TILE_SIZE, TILE_SIZE);
            }
            
            //zu löschende Steine rausnehmen.
            stones.removeAll(waste);
            waste.clear();
            
            
            if (Math.random()<0.4) {
               Stone s=new Stone(this);
               stones.add(s);
               s.x=(int) (Math.random()*this.getWidth());
               s.s=(int) (Math.random()*4)+1;
               s.col=new Color((int)(Math.random()*255), (int)(Math.random()*255), (int)(Math.random()*255));
            }
            strategy.show();
         }
         long end=System.nanoTime()/1000000L;
         
         if (end-start>0 && end-start<SPEED) {
            Thread.sleep(SPEED-(end-start));
         }
      }
      System.exit(0);
   }
   
   private static class Stone {
      int x=0;
      int y=0;
      int s=0;
      Color col=null;
      boolean remove = false;
      GameLoop papa;
      
      public Stone(GameLoop gl){
      	papa = gl;
      }
      
      public void checkStatus(){
        if (y > papa.getHeight()) {
         remove = true;
        } 
      }
      
      public void move(){
        y+=s*papa.ticks;
      }
   }
   
   private static class Ticker {

      private int rate;
       private long s2;

       public Ticker(int tickrateMS) {
           rate = tickrateMS;
           s2 = System.nanoTime()/1000000L;
       }

       public int getTicks() {
           long i = System.nanoTime()/1000000L;
           if (i - s2 > rate) {
               int ticks = (int) ((i - s2) / (long) rate);
               s2 += (long)rate * ticks;
               return ticks;
           }
           return 0;
       }
   }
}
 

EgonOlsen

Bekanntes Mitglied
Quaxli hat gesagt.:
@EgonOlson:
Schönes Beispiel. Nachdem es weiter oben aber auch Probleme damit gab, wie der Zustand der einzelnen Objekte in diese ausgelagert wird, habe ich mir mal erlaubt, eine kleine Modifikation einzubauen und die Bewegung in die Klasse Stone auszulagern, damit das was Ihr bisher so schön erklärt habt, auch eingesetzt wird ;)
Super, danke. Ist mir auch negativ aufgefallen, aber ich hatte gestern Abend einfach keinen Bock mehr dazu... :wink:
 

hdi

Top Contributor
Ich hab mir jetz mal "euren" Code angesehen. Erstmal danke für die Mühe. Ich verstehe soweit, wieso das passiert, was passiert. Aber ich hab schon einige Fragen...

Starten wir mal ganz locker ;)

Was ist dieses System.nanoTime()/1000000L ? Genauer gesagt, wieso wird da durch diese komische Zahl
geteilt, das ist ja glaub ich ne Darstellung für long-Werte, oder? Ich hab die noch nie verstanden.. Was isn "L", und wieso
wird genau durch diese Zahl geteilt?

Okay, nun zu den wichtigen Dingen dem Design. Das verwirrt mich grad ganz krass...

Egon, du meintest:

Wenn du einen Stein immer nur alle x ticks (also z.b. 50 für eine Sekunde) nach unten bewegen willst, dann ergänze in Stone einfach einen Zähler und zieh die aktuellen Ticks davon ab. Und wenn der Wert dann <=0 ist, bewege den Stein und setzen den Zähler zurück.

Das finde ich seltsam, und ich find in diesem Zusammenahng auch folgendes seltsam:
Ihr seid euch wohl einig, dass die Methode move() in der Klasse Stein selbst stehen sollte, das hat
Quaxli ja extra noch geändert, und du sagtest, du wolltest das auch schon machen.

Aber, wenn ich zB im echten Leben eine Cola-Flasche sehe, und ich denk mir "hey die will ich jetz vom Tisch nehmen und auf den Boden legen", dann hat doch die Cola-Flasche keine Methode dafür. Die Cola-Flasche hat nur die Funktionalität, dass sie sich im Raum verschieben lässt. Aber wie genau, und wie lange das dauert, das is doch alles Sache von mir, da muss ICH doch die passenden Methoden haben...

Ein Steine sollte doch nur diese Methode anbieten:

Code:
public void move(int xDistance, int yDistance){
     this.x += xDistance;
     this.y += yDistance;
}

Aber doch keine Game-Logik, der Controller soll den Stein 20 Pixel und 50 Ticks runtersetzen zB, und der Stein
selber sollte davon keine Ahnung haben, er lässt sich halt bewegen.
Es ist doch Aufgabe des Controllers, so eine Funktion zu haben wie:

Code:
public void moveDownStein(Stein stein){
     int y = getDistanceRelativeTo(ticks);
     while(y > 0){
             stein.move(0,1);
     }
}

Ich kann mir auch nicht vorstellen, dass man nem Stein im Konstruktor seinen "Papa"-GameLoop geben soll.
Sorry, ich meine ihr seid die Profis, aber das kann ich grad nich akzeptieren wenn ich daran denk, was ich bisher
gelernt hab. Ich bitte da um Aufklärung ???:L

Ich bekomme den GameLoop einfach nicht hin, ihr habt mir jetz n Beispiel gegeben, aber da passiert ja auch die ganze Zeit das gleiche, der GameLoop mach timmer das selbe, bei jedem Durchlauf. Aber bei meinem Spiel...ich meine man drückt ne Taste, und das Ding soll sich drehen.. Wie und wo bau ich das in den GameLoop ein? Ich weiss doch nicht, wann ein User was drückt. Und es passieren ja soviele Dinge gleichzeitig..

Ich hab mir jetz gedacht, jeder Stein hat nen "SteinController", und der wiederum hat ne Liste von Aufgaben, wie auch immer realisiert, und der bearbeitet den Stein dementsprechen, zB drehen oder runterschieben usw.

Im KeyListener steht dann sowas wie:

Code:
dataModel.getCurrentStein().getController().addTask("bitte drehen").

Und der GameLoop, der tut dann zB jede Sekunde mal sowas machen wie:

Code:
dataModel.getCurrentStein().getController().addTask("bitte Stein runterbewegen lassen");
und läuft bei jedem Schleifendurchlauf alle Steine durch, und ruft auf jeden einzelnen sowas auf wie:
Code:
stein.getController().processRelativeTo(ticks)

undin dieser processRelativeTo(ticks) Methode wiederum läuft der Controller alle Tasks durch und macht
bei ihnen processRelativeTo(ticks), damit sich ein Stein zB gleichzeitig drehen und runterbewegen kann.

Und... kA... was haltet ihr davon?
Naja, eig. das gleiche wie ich es ja weiter oben mit diesen Tasks schon hatte. Und wieder würdet ihr jetz
wohl sagen, dass ihr nicht versteht wieso ich das nicht in die Klasse Stein selber reintu. Aber siehe oben, ich hab in der Uni über Datenkapselung gelernt, und das war genau das Gegenteil von dem, was ihr mir hier erzählt :(



Also da ich grad nich wirklich weiss, was ich nun weiter sagen/fragen soll. Auf jeden Fall danke für eure Demo, ich hab jetz denke ich zumindest verstanden was das mit den Ticks ist. Denke ich, so einigermassen. Es ist logisch dass man Dinge tut in Abhängigkeit von Zeit, aber meiner Meinung nach ist das mit Ticks nix anderes als sleep(fester Wert), nur umgebaut. Ich meine ihr wandelt ne fixe Zahl "SPEED" in "ticks" um, ich kann doch gleich in Relation zu ms das Model weiterschreiten lassen, wieso den Umweg über dieses ganze ticks-Zeug. Das verstehe ich leider nocht immer nicht, sorry :oops:

Ich werde jetzt versuchen, ganz, ganz konkret ein UML Diagramm zu erstellen, um zu sehen wie ich das irgendwie machen kann mit dem Gameloop und den hundert Dingen, die im Spiel gleichzeitig passieren.

So far ... :bahnhof:
 

EgonOlsen

Bekanntes Mitglied
hdi hat gesagt.:
Was ist dieses System.nanoTime()/1000000L ? Genauer gesagt, wieso wird da durch diese komische Zahl
geteilt, das ist ja glaub ich ne Darstellung für long-Werte, oder? Ich hab die noch nie verstanden.. Was isn "L", und wieso
wird genau durch diese Zahl geteilt?
Weil du die Zeit sonst in Nanosekunden hast. Durch die Division sind es Millisekunden. Das L markiert einen Longwert. Bei großen Zahlen muss man das machen, sonst kapiert der Compiler das nicht. Bei kleinen könnte man es weglassen, ich setze es aus Gewohnheit immer.

hdi hat gesagt.:
Das finde ich seltsam, und ich find in diesem Zusammenahng auch folgendes seltsam:
Ihr seid euch wohl einig, dass die Methode move() in der Klasse Stein selbst stehen sollte, das hat
Quaxli ja extra noch geändert, und du sagtest, du wolltest das auch schon machen.

Aber, wenn ich zB im echten Leben eine Cola-Flasche sehe, und ich denk mir "hey die will ich jetz vom Tisch nehmen und auf den Boden legen", dann hat doch die Cola-Flasche keine Methode dafür. Die Cola-Flasche hat nur die Funktionalität, dass sie sich im Raum verschieben lässt. Aber wie genau, und wie lange das dauert, das is doch alles Sache von mir, da muss ICH doch die passenden Methoden haben...

Ein Steine sollte doch nur diese Methode anbieten:

...

Aber doch keine Game-Logik, der Controller soll den Stein 20 Pixel und 50 Ticks runtersetzen zB, und der Stein
selber sollte davon keine Ahnung haben, er lässt sich halt bewegen.
Es ist doch Aufgabe des Controllers, so eine Funktion zu haben wie:

...
Ja, einverstanden. Das ist ja auch nur ein einfaches Beispiel und kommt ohne expliziten Controller aus. In meinen vorherigen Posts hatte ich ja auch immer von einem SteinController gesprochen.

hdi hat gesagt.:
Ich kann mir auch nicht vorstellen, dass man nem Stein im Konstruktor seinen "Papa"-GameLoop geben soll.
Sorry, ich meine ihr seid die Profis, aber das kann ich grad nich akzeptieren wenn ich daran denk, was ich bisher
gelernt hab. Ich bitte da um Aufklärung ???:L
Es ist ein Beispiel. Ich finde den Papa da drin auch nicht so toll, aber für das Beispiel ist es völlig ok IMHO.

hdi hat gesagt.:
Ich bekomme den GameLoop einfach nicht hin, ihr habt mir jetz n Beispiel gegeben, aber da passiert ja auch die ganze Zeit das gleiche, der GameLoop mach timmer das selbe, bei jedem Durchlauf. Aber bei meinem Spiel...ich meine man drückt ne Taste, und das Ding soll sich drehen.. Wie und wo bau ich das in den GameLoop ein? Ich weiss doch nicht, wann ein User was drückt. Und es passieren ja soviele Dinge gleichzeitig..

Ich hab mir jetz gedacht, jeder Stein hat nen "SteinController", und der wiederum hat ne Liste von Aufgaben, wie auch immer realisiert, und der bearbeitet den Stein dementsprechen, zB drehen oder runterschieben usw.

Im KeyListener steht dann sowas wie:
....
undin dieser processRelativeTo(ticks) Methode wiederum läuft der Controller alle Tasks durch und macht
bei ihnen processRelativeTo(ticks), damit sich ein Stein zB gleichzeitig drehen und runterbewegen kann.

Und... kA... was haltet ihr davon?
Naja, eig. das gleiche wie ich es ja weiter oben mit diesen Tasks schon hatte. Und wieder würdet ihr jetz
wohl sagen, dass ihr nicht versteht wieso ich das nicht in die Klasse Stein selber reintu. Aber siehe oben, ich hab in der Uni über Datenkapselung gelernt, und das war genau das Gegenteil von dem, was ihr mir hier erzählt :(
Nein, das ist nicht das Gegenteil. Was ich versucht habe zu vermitteln ist, dass ich es für besser halte, dem Stein einen Zustand zu geben. Das wolltest du in deinen ersten Ansätzen meiner Meinung nach nicht tun. Das hätte irgendwie in den Tasks gesteckt. Um in Analogie zur Colaflasche von oben zu bleiben, so muss die Flasche keine "Pack mich auf den Boden"-Methode haben, aber sie sollte Auskunft geben können, wo sie steht, wie voll sie ist und ob sie gerade vom Tisch fällt. Ich habe nicht gemeint, dass der Stein sein eigener Controller sein soll. Wobei ich das für einfache Anwendungen durchaus ok finde. Man muss die Designerei auch nicht übertreiben und man kann hinterher immer noch umbauen. Für ein Beispiel ist es auf alle Fälle ok.
Was deinen Ansatz mit dem Controller angeht, so tauchen da wieder diese Tasks auf, die ich persönlich für Overengineered halte, aber gehen würde das wohl. Ich würde, wie auch schon gesagt, die Steine mit den Attributen "fällt", "dreht sich", usw. behaften und in einem Controller für alle Steine diese durchgehen und entsprechend ihren Attributen zeichnen.

hdi hat gesagt.:
Also da ich grad nich wirklich weiss, was ich nun weiter sagen/fragen soll. Auf jeden Fall danke für eure Demo, ich hab jetz denke ich zumindest verstanden was das mit den Ticks ist. Denke ich, so einigermassen. Es ist logisch dass man Dinge tut in Abhängigkeit von Zeit, aber meiner Meinung nach ist das mit Ticks nix anderes als sleep(fester Wert), nur umgebaut. Ich meine ihr wandelt ne fixe Zahl "SPEED" in "ticks" um, ich kann doch gleich in Relation zu ms das Model weiterschreiten lassen, wieso den Umweg über dieses ganze ticks-Zeug. Das verstehe ich leider nocht immer nicht, sorry
Nein, das ist nicht identisch mit einem Sleep. Du kannst natürlich die Ticks durch echte Millisekunden ersetzen. Die Ticks sind nur eine Vergröberung, die ich gerne benutze, weil sie ausreichend genau ist und es sich andererseits besser damit rechnen lässt.
Der Unterschied zu Sleep ist, dass das mit den Ticks auch dann noch klappt, wenn die Spielschleife nur 10mal/Sekunde durchläuft, z.B. weil der Rechner so langsam ist. Die Grafik ruckelt dann, aber die Logik bleibt konsistent. Ein fester Sleepwert leistet das nicht.
 

hdi

Top Contributor
Also meine Idee mit dem SteinController hab ich ja gehabt weil du eben sagtest dass das mit Tasks nicht gut ist, wenn der Stein das selber nicht weiss, was mit ihm grad passiert.

und deshalb geb ich jedem stein nen controller. und das auch nur, weil ich nicht nur attribute "fällt" etc. bei nem stein haben möchte, sondern ich würd auch gleich gerne die methoden, in denen das wirklich was passiert (der stein wird fallen gelassen zb) da rein stecken.

das hält halt den gameloop übersichtlicher.

aber ich denke wir reden schon von der gleichen sache, diese tasks hatten halt zuviel eigen-logik, und der controller wird das nicht mehr haben, weil er ja zum stein fest dazugehört, quasi nur in ne eigene klasse gekapselt.

zu ticks vs. sleep: hatte ich mir vorher auch erst so gedacht, hatte aber nen denkfehler und dachte es wird dann auch nicht gleichmässig gerechnet, weil er nich so oft durchläuft. aber die berechnung baut ja auf den ticks auf. von daher hab ichs jetz endlich komplett gecheckt mit den ticks, danke ;)

aber: wieso benutzt du nicht System.currentTimeMillis() ? Ist das ne Angewohnheit, oder hab ich jetz was falsch verstanden und es gibt doch nen Unterschied? Ich meine wenn du durch long teilst, wird ja gerundet oder nicht, daher kann nanoTime() auch nicht genauer sein als currentTimeMillis() :?:

Gut, nochmal danke dass du/ihr mir das mit den Ticks erklärt habt, und so wie es scheint hast du auch nix gegen mein Vorhaben mit dem SteinController usw.
 

Quaxli

Top Contributor
hdi hat gesagt.:
...

Aber, wenn ich zB im echten Leben eine Cola-Flasche sehe, und ich denk mir "hey die will ich jetz vom Tisch nehmen und auf den Boden legen", dann hat doch die Cola-Flasche keine Methode dafür. Die Cola-Flasche hat nur die Funktionalität, dass sie sich im Raum verschieben lässt. Aber wie genau, und wie lange das dauert, das is doch alles Sache von mir, da muss ICH doch die passenden Methoden haben...

Wandeln wir dieses Beispiel mal ab (es ist sehr abstrakt aber vielleicht hilft's ja):

Wenn Du die Flasche Cola aus dem Fenster schmeißt (Tetris :) ), hast Du keinen Einfluß mehr darauf, was passiert. Und trotzdem passiert Folgendes:

- die Flasche wird nach unten bewegt (von der Schwerkraft = der GameLoop) und zwar um x Meter pro Zeiteinheit. Je nachdem wie fein Du messen kannst, ist der Wert größer oder kleiner. Nehmen wir weiterhin an, es ist Nacht und auch noch stockfinster, aber Du hast eine Stroboskop-Lampe, durch welche die Flasche pro Meßzyklus beleuchtet wird, dann ist bei einer sehr feinen Auflösung die Bewegung gut erkennbar, während bei grober Auflösung nur Momentaufnahmen, also eine ruckelnde Bewegung, sichtbar wäre.

- wenn die Flasche unten ankommt, geht Sie vermutlich kaputt. Wann das sein wird, kannst Du messen oder vorberechnen, aber nicht mehr beeinflussen. Das merkt die Flasche sozusagen von selbst (wenn Höhe == 0, dann destroy = true) ;)

Insofern nichts anderes, als was EgonOlsen in seinem Beispiel programmiert hat. :) Der Stein wird um x Pixel bewegt und wenn er nicht mehr sichtbar ist raus genommen. Und das "von ganz allein" (im abstrakten Sinn).

Jetzt nimm mehrere Flaschen und lade Dir ein paar Freunde ein und dann werft Ihr die Flaschen gleichzeitig aus dem Fenster, wobei jeder mit einer anderen Kraft wirft, so daß die Flaschen unterschiedlich schnell fallen. Es passiert das gleiche wie oben. Alle Flaschen werden vom gleichen Thread (der Schwerkraft bewegt) - es gibt keine unterschiedlichen Realitäten für die einzelnen Flaschen. Und wenn Sie unten ankommen, gehen Sie kaputt. Zwar zu unterschiedlichen Zeiten, aber niemand prüft, wann das sein wird - die Flasche merkt es wieder "von allein".

Und das ist genau das, was das Programm von EgonOlson macht. Die Klasse Stone sind die Flaschen und der GameLoop ist die Schwerkraft.

Ich hoffe ich bin jetzt nicht zu sehr abgehoben, aber Dein Beispiel mit der ColaFlasche war so nicht korrekt :)
 

EgonOlsen

Bekanntes Mitglied
hdi hat gesagt.:
aber: wieso benutzt du nicht System.currentTimeMillis() ? Ist das ne Angewohnheit, oder hab ich jetz was falsch verstanden und es gibt doch nen Unterschied? Ich meine wenn du durch long teilst, wird ja gerundet oder nicht, daher kann nanoTime() auch nicht genauer sein als currentTimeMillis() :?:
currentTimeMillis() ist recht ungenau, weil die Granularität zu hoch ist. Auf Singlecores unter Windows liegt sie bei etwa 10ms, auf Dualcores bei 15ms. Unter Linux sieht es wieder anders aus (dort ist sie niedriger, also besser IIRC) usw. Als Fallback ist es ok, aber erste Wahl ist es nicht. nanoTime() ist wesentlich genauer, hat aber unter Umständen Probleme mit CPUs, die zur Laufzeit des Programms die Taktfrequenz dynamisch ändern (wobei ich noch nie Probleme damit gehabt habe, aber Gerüchte sprechen von solchen). Es gibt auch in LWJGL (lwjgl.org) einen Timer, der ziemlich genau ist, aber der hat wieder andere Probleme (läuft durch eine VM-Macke auf manchen Systemen mit doppelter Geschwindigkeit). Ich benutze immer nanoTime(), sofern Kompatibilität zu 1.4 nicht erforderlich ist.
 

hdi

Top Contributor
Ok dann nehm ich auch nanoTime.

Das Cola-Flaschen-Gerede wird langsam etwas bizarr ;) Ich denke wir sollten das jetz einfach so stehen lassen, es war ja nur ein Beispiel. Man kann sich drüber streiten ob die Cola-Flasche eine Methode destroy() hat, oder der Betonboden eine Methode destroy(Colaflasche). Oder die Klasse MotherNature die Methode getMatrix().simulateRealityCollision(ColaFlasche, Betonboden). Usw...
Aber wir sind im Endeffekt Programmierer und keine Philosophen.

Du könntest mir aber statt dessen meine Gedanken in diesem Thread bestätigen:
http://www.java-forum.org/de/viewtopic.php?p=466334#466334

Das hat so nämlich noch keiner getan, nicht dass ich jetz ewig rumtu und am schluss bringt das alles gar nichts.

"Solange Cola-Flaschen auf Betonböden destroyen, ist alles in Ordnung" - hdi&Quaxli, 2008
 
Status
Nicht offen für weitere Antworten.
Ähnliche Java Themen
  Titel Forum Antworten Datum
B Mehrere Klassen auf JFrame Spiele- und Multimedia-Programmierung 3
N Mehrere Rechtecke in großes Quadrat einzeichnen Spiele- und Multimedia-Programmierung 5
J mehrere Listener für einen Button / Label Spiele- und Multimedia-Programmierung 1
M Applets - mehrere Tasten gleichzeitig abfragen Spiele- und Multimedia-Programmierung 3
E [LWJGL] Karusell, mehrere Objekte drehen sich um einen Mittelpunkt Spiele- und Multimedia-Programmierung 31
staxx6 (Slick) Steuerung - mehrere Belegungen Spiele- und Multimedia-Programmierung 12
B GameGrid Probleme mit mehrere Actors Spiele- und Multimedia-Programmierung 5
R Mehrere Shader in einem Program Spiele- und Multimedia-Programmierung 8
B GameGrid Probleme mit mehrere Actors Spiele- und Multimedia-Programmierung 2
S Ein Thread für alle Animationen oder mehrere? Spiele- und Multimedia-Programmierung 2
S Mehrere Steine gleichzeitig setzen und bewegen Spiele- und Multimedia-Programmierung 4
V Java3D: mehrere ViewingPlaforms in Sonnensystem Spiele- und Multimedia-Programmierung 8
A Mehrere geometrische Objekte in JPanel zeichnen Spiele- und Multimedia-Programmierung 4
S Java3D - mehrere Instanzen eines Geometrieobjektes erzeugen Spiele- und Multimedia-Programmierung 3
V Ein oder mehrere GIF-Grafiken zusammenfügen Spiele- und Multimedia-Programmierung 3
M mehrere PNG in eine Datei Spiele- und Multimedia-Programmierung 3
L Wie kann ich mehrere Texturen auf ein Object File mappen Spiele- und Multimedia-Programmierung 4
D mehrere Graphiken erstellen und ihre Position definieren Spiele- und Multimedia-Programmierung 4
S ständig neue Threads Spiele- und Multimedia-Programmierung 4
E Zwei Threads miteinander laufen lassen Spiele- und Multimedia-Programmierung 6
Z Objekte animieren mit Threads Spiele- und Multimedia-Programmierung 4
M Minecraft Minecraft, MySQL und Threads Spiele- und Multimedia-Programmierung 12
J Problem mit Threads Spiele- und Multimedia-Programmierung 8
F Java zwei gleiche Threads mit unterschiedlichen Parametern Spiele- und Multimedia-Programmierung 2
L Synchronisierung von Threads die Antwort? Spiele- und Multimedia-Programmierung 8
J Frage zu Threads Spiele- und Multimedia-Programmierung 5
I Problem mit Threads Spiele- und Multimedia-Programmierung 25
L Threads: Denkzeit-Timer Spiele- und Multimedia-Programmierung 3
Tapsi Thread wartet auf Threads Spiele- und Multimedia-Programmierung 7
0 Problem mit zeitsynchroner Hauptschleife/Threads Spiele- und Multimedia-Programmierung 28
D threads in j3d? Spiele- und Multimedia-Programmierung 6

Ähnliche Java Themen

Neue Themen


Oben