Need more BRAIN!

hdi

Top Contributor
Hi, also ich hock hier grad an nem Problem und hab nen Knoten im Kopf. Bevor ich jetzt noch ausflippe zapfe ich mal lieber eure Hirnzellen an :D

Ich krieg's grad nicht hin einen "CPU-Ticks"-abhängigen Ablauf zu steuern. Klingt jetzt etwas dumm, ich meine sowas:

Java:
    @Override
    public int getYShift() {
	// 1000 pixels down per second
	int pixels = ticksPassed;
	return pixels;
    }

    @Override
    public void process(long delta) { // delta in ms
	ticksPassed = delta;
    }

Mein GameLoop ruft die process-Methode und anschliessend die getYShift() Methode immer wieder auf mit der Zeit, die seit dem letzten Cycle vergangen ist, und ich bewege halt das Objekt entsprechend. Also das hab ich hingekriegt ;)

Aber jetzt will ich folgendes: Das Objekt soll sich nur alle 500ms nach unten bewegen, und zwar in zB 250ms um 50 Pixel.
Also: Wenn 250ms vergangen sind, soll er 50px zurückgelegt haben, wenn weitere 500ms vergehen, soll er sich nicht bewegen, erst dann wieder 50px in den nächsten 250ms, usw.
Krieg's grad echt nicht hin :oops: HIRN, wieso lässt du mich so oft im Stich ;-(

Ich danke vielmals für Hilfe!
 

Marco13

Top Contributor
Code:
for (int tick=0; tick<Integer.MAX_VALUE; tick++)
{
    int a = tick % 750; // Wo ist man gerade im Zyklus?
    if (a < 250) // Ist man in den ersten 250 Ticks?
    {
        moveAbout(a/250.0*50); // Bewege dich in 250 Ticks um 50 Pixel
    }
    else
    {
        // Ah, man ist in den "hinteren 500ms" eines 750ms-Zyklus - nix zu tun...
    }
}
!?
 

hdi

Top Contributor
Danke erstmal, das funktioniert jetzt theoretisch. Das Problem ist das Runden. Der Gameloop ist ja für gewöhnlich immer ungefähr gleich schnell bei jedem Durchlauf, in meinem Fall sind das ca 12 ms (inkl. sleep).
Das führt zu
Code:
12 / 250.0 * 50 = 2.4
. Ich bewege ihn wegen Runden nur um 2 pixel. Da der GameLoop alle ~12 ms aufgerufen wird, und meine Bewegung 250ms ist, sind das
Code:
250/12 = ~20
abgefälschte Aufrufe von jeweils etwa 0.4, also
Code:
20*0.4 = ~9
Pixel zu wenig nach der Bewegung. Wenn ich aufrunde, sind es 9 Pixel zu viel...

Also ich muss mir irgendwie die Abweichung von der jeweils letzten Bewegung abspeichern, und dann sicherstellen dass ich nach 250ms genau 50 px hab. Aber ich hoff mal dass ich das alleine hinkrieg ;)

Danke nochmal, schöne Grüße an dein Brain ;)
 

Marco13

Top Contributor
OK, 15 Stunden Schlaf später (das hilft dem Brain, und meine "Vision" ist, dass irgendwann alle Menschen so weit zur Vernunft kommen, dass sie sich auch dementsprechend verhalten) : In Quaxli's Tutorial steht, wie man sowas lösen kann: In diesem Fall zählst du ja nicht die "ticks", und es macht auch keinen Sinn, seine Programmabläufe darauf aufzubauen - wenn du dann im GameLoop eine zusätzliche Berechnung einbaust, haut's das ganze raus, und wenn du im Hintergrund auf einmal eine MP3 laufen läßt, läuft auf einmal dein Programm langsamer... von einem anderen (schnelleren oder langsameren) Rechner mal ganz abgesehen :autsch:

Wirklich zeitabhängige Bewegung kann man GROB nach diesem Muster erreichen
Java:
long previousStepStartNs = System.nanoTime();
long currentStepStartNs = 0;

// Game loop:
while (true)
{
    currentStepStartNs = System.nanoTime();
    long passedTimeNs = currentStepStartNs - previousStepStartNs;
    previousStepStartNs = currentStepStartNs;

    double passedTimeMs = passedTimeNs / 1e9;
    double movementDurationMs = 250.0;
    double movementAlpha = passedTimeMs / movementDurationMs;
    double movement = 50; // Pixel
    double currentMovement = movementAlpha * movement;

    moveAbout(currentMovement);

    // Hier die übrigen Berechnungen des GameLoops.
    // Je nach Dauer dieser Berechnugen wird die Bewegung
    // auf Basis der "passedTimeMs" im nächsten Schritt richtig berechnet:
    // 25 ms Dauer: "passedTimeMs" 25, Bewegung ist 25/250*50 = 5 Pixel
    // 50 ms Dauer: "passedTimeMs" 50, Bewegung ist 50/250*50 = 10 Pixel
    // 250 ms Dauer: "passedTimeMs" 250, Bewegung ist 250/250*50 = 50 Pixel

}

Dein "Sonderverhalten" mit den 500ms-Pausen zwischen den 250ms-Bewegungsblöcken könnte man da jetzt irgendwie "händisch" reindengeln, aber es wäre vermutlich besser und eleganter, sich dafür eine Klasse zu machen, die Solche Berechungen schön kapselt - im Sinne von
Java:
class/interface MovementComputer { double computeMovement(double timeSinceStartMs); }
oder so. Wenn sich sonst etwas nicht mit den "750=250+500"-Zyklen bewegen soll, sondern vielleicht mit "600=300+300"-Zyklen kommt man mit diesem Gewurschtel mit den lokalen Variablen komplett durcheinander...


EDIT Codebreite angepasst
 
Zuletzt bearbeitet:

hdi

Top Contributor
Danke nochmal, aber ich hab's inzwischen hingekriegt. kA ob's besser oder schlechter ist als dein Vorschlag, hab mir den grad nich so genau angesehen (bin etwas in Eile)

Ich hab's jetzt so gemacht:

Java:
@Override
    public int getYShift() {
	int pixels = 0;
	while (toProcess > 0) {
	    if (toMove > 0) {
		int using = (int) (toMove >= toProcess ? toProcess : toMove);
		toMove -= using;
		toProcess -= using;
		double accuratePixels = ((double) using / 100 * 50);
		pixels = (int) accuratePixels;
		moveSkipped += accuratePixels - pixels;
		if ((int) moveSkipped == 1) {
		    pixels++;
		    moveSkipped--;
		}
		totalPixels += pixels;
		if (toMove == 0) {
		    toWait = 500;
		    if (totalPixels < 50) {
			pixels += 50 - totalPixels;
		    }
		}
	    }
	    if (toWait > 0) {
		int using = (int) (toWait >= toProcess ? toProcess : toWait);
		toWait -= using;
		toProcess -= using;
		if (toWait == 0) {
		    toMove = 100;
		    totalPixels = 0;
		    moveSkipped = 0;
		}
	    }
	}
	return pixels;
    }

    @Override
    public void process(long delta) {
	toProcess = delta;
    }

aber es wäre vermutlich besser und eleganter, sich dafür eine Klasse zu machen, die Solche Berechungen schön kapselt
Diese Klasse besteht schon aus nix anderem als genau dieser Bewegung, und es ist auch die einzige die solche Pausen in den Bewegungen beinhaltet, d.h. das muss ich jetzt nicht unbedingt nochmal splitten.

...danke nochmal
 

Marco13

Top Contributor
hab mir den grad nich so genau angesehen

Dito :D

Diese Klasse besteht schon aus nix anderem als genau dieser Bewegung, und es ist auch die einzige die solche Pausen in den Bewegungen beinhaltet, d.h. das muss ich jetzt nicht unbedingt nochmal splitten.

OK, dann würden mich die Zahlen (100, 500 usw) im Code aber irrtitieren... Ein Objekt so einer Klasse KÖNNTE man ja vielleicht erstellen wollen mit sowas wie
Code:
MovementComputer mc = new MovementComputer(totalDuration, movementDuration, movementAmount);
// also z.B. 
MovementComputer mc = new MovementComputer(750, 250, 50);
... aber das musst du wissen...
 

hdi

Top Contributor
Ja, ich hab dir jetzt nur die Methoden gezeigt. Die meisten Variablen die da benutzt werden sind ja Member, und die werden auch im Konstruktor übergeben ;)
edit: Wobei du hast ja Recht ich nutz die ja zT gar nich ^^ Naja diese Magic Numbers sind da eig. drinnen ^^
 

hdi

Top Contributor
Uff, Leute jetzt geht's hier erst richtig los :eek: Ich verwende diesen Thread für die nachfolgende Frage, weil das damit indirekt (naja eigentlich eher direkt^^) etwas zu tun hat. Also, die Bewegung läuft jetzt zwar...
Nun bin ich aber grad bei der Kollisionerkennung von zwei Objekten.
Es geht um zwei (gleich große) Kreise, und eine Collision hab ich dann wenn die Entfernung der Mittelpunkte kleiner als der Radius ist. Wenn dies der Fall ist, muss ich die Position des einen Kreises anpassen, damit sie nicht ineinanderstecken.
Nun muss ich aber herausfinden, was für eine Bewegung das war. Mit ner kleinen Veranschaulichung versteht man das sicher besser. Angenommen ich hab den Zustand zweier Kreise wie folgt:

Code:
OO

also nebeneinenander. Der rechte Kreis bewegt sich jetzt genau um den Radius nach links, und somit hab ich eine Überschneidung, in der beide Kreise genau die gleiche Position haben:

Code:
O (dahinter halt noch einer)

Und die Art der Bewegung muss ich jetzt deshalb rekonstruieren, weil ich an dieser Stelle nicht weiss, ob der Zustand vorher so aussah:


oder so:

Code:
OO

Denn ich muss jetzt wissen ob ich den rechten Stein zurück nach rechts schiebe, oder nach oben.
Okay, also hier meine Überlegung:
Java:
boolean xPenetration = newX != gem.getX() && otherOldX == other.getX();
boolean yPenetration = newY != gem.getY() && otherOldY == other.getY();
newX ist die neue gewünschte Position von "gem", gem.getX() speichert noch die alte. Bei "other" ist es andersrum: other.getX() enthält die Position nach der Bewegung und otherOldX seine Position davor. Naja klingt etwas verwirrend, das kommt daher dass ich eine Kollision von A mit B erst dann sicherstellen kann, wenn B selber schon abgearbeitet wurde (kann ja sein dass sich B in diesem Cycle auch in die gleiche Richtung bewegt)

Jetzt erklär ich euch erstmal warum ich nicht einfach folgendes mache:
Java:
boolean yPenetration = newY != gem.getY();
Der Grund dafür ist dass sich beide Kreise nach unten bewegen können. D.h. nur weil ich eine Kollision habe, und sich der eine auf der Y-Achse bewegt hat, heisst das nicht, dass das das Problem sein muss, denn wenn sich der andere um genau den gleichen Wert nach unten bewegt hat, hat meine Kollision nichts mit den y-Werten zu tun.

So... also obige booleans sind an sich okay so. Aaaber jetzt erinnern wir uns an diesen Thread, und die Tatsache mit den Rundungsfehlern: Wenn ich so eine Kollision wie oben dargestellt nur oft genug probiere, passiert irgendwann mal schmarrn, weil irgendwann mal die Differenz auf der y-Achse des rechten Kreises 1.1 sein könnte (die Methode returned 1 Pixel), aber beim anderen Kreis 0.9 (Methode returned 0 Pixel). D.h. die Abfrage
Code:
otherOldY == other.getY();
gibt true, und damit wird eine Kollision auf der y-Achse ermittelt, obwohl sich beide gleichmässig nach unten bewegen. Das wiederum heisst für meinen Algorithmus dass er den rechten Stein nicht nur nach rechts zurückdrückt, sondern auch nach oben, und rauskommen tut dann sowas:


Tja, also ich kann nach 250ms sicherstellen, dass sich der Kreis um genau 50px bewegt hat, aber ich kann nicht bei jedem einzelnen Cycle sicherstellen, dass gleich gerundet wird bei allen Kreisen.

...keine Ahnung ob ich das Problem jetzt gut rüberbringen konnte?!
Falls nein: Nachfragen!
Falls ja: Was sagt ihr? Wie kann ich dieses Dilemma jetzt aus der Welt schaffen????

Würde mich über Hilfe sehr freuen, und denkt dran: Das Christkind würde das sicherlich honorieren... :D
 
Zuletzt bearbeitet:

hdi

Top Contributor
Aahhh Leute (erstmal xD) Entwarnung!! Mir ist grad was aufgefallen, und zwar sagte ich:
Tja, also ich kann nach 250ms sicherstellen, dass sich der Kreis um genau 50px bewegt hat, aber ich kann nicht bei jedem einzelnen Cycle sicherstellen, dass gleich gerundet wird bei allen Kreisen.
Was für ein Bullshit! Natürlich wird immer gleich gerundet, denn jeder Kreis bekommt in seiner Process Methode das selbe Game-Loop-Delta. Ich bin allerdings einem dummen Fehler zum Opfer gefallen. Ich rufe in meinem Kollisions-Algo des öfteren die Methoden getDesiredX()/getDesiredY() auf.
Und diese Methode ist wie folgt implementiert:
Java:
    public int getDesiredY() {
	return y + controller.getYShift();
    }
getYShift() wiederum "frisst" die Ticks auf, mit anderen Worten das ist mehr als nur ein Getter, das Ding beinhaltet Side Effects, und beim zweiten Aufruf ist das nicht das gleiche wie beim ersten. Also wohl ein klassischer Verstoß gegen eine Konvention: Was get heisst, sollte nichts verändern. :oops:
 

hdi

Top Contributor
@unsympath
ja sry Flüchtigkeitsfehler im Posting. Ich meinte natürlich Durchmesser. Thema hat sich erledigt, siehe letzter Post.
 

Ähnliche Java Themen

Neue Themen


Oben