C# Monogame - Schnelle Bewegung von verketteten Objekten erzeugt Lücken oder überlappt

Hydrano

Lieutenant
Registriert
März 2008
Beiträge
937
Guten Abend,

ich habe gestern angefangen mit dem Monogame Framework für C# ein 2D Roulette zu programmieren.
Das Roulette Rad ist bei mir einfach eine Aneinanderreihung von 3 Texturen (Schwarz, Rot und Grün).
Diese sollen nun wenn ich die Leertaste drücke sich schnell nach rechts bewegen und dann langsam zum stehen kommen. Soweit kein Problem, sollte man jedenfalls denken :D

Dieser "Scrollvorgang", so nenne ich das laufen des Rads, ist ganz simple implementiert und sollte von meinem Verständnis her auch funktionieren, leider tut es das nicht so ganz.
Während es läuft verschieben sich die Texturen nach und nach. Insbesondere wenn das Rad gestartet wird und wenn sich die Scroll-Geschwindigkeit dann ändert (z.B. langsamer wird). Die Texturen fangen an sich zu überlappen oder es entstehen Lücken.

Es folgen die relevanten Code Schnipsel, die Klasse Sprite ist eine typische Sprite Klasse mit Textur, Position, Rotation etc.
Davon erstelle ich ein Array über so viele Fragmente wie ich benötige (15 in dem Fall)

Code:
// Relevante variablen
Sprite[] fieldList = new Sprite[15]; // Stellt das Rad dar. 
Rectangle scrollArea; // Der Bereich indem das Rad scrollt, da ich nicht warten will bis die Objekte aus dem Bildschirm laufen
float scrollSpeed = 0.0f; // Die Geschwindigkeit mit der das Rad sich bewegt

// Hier setze ich die Startposition der Objekte im Rad (Diese Methode wird nur 1x aufgerufen)
private void AdditionalInit()
{
    for (int i = 0; i < fieldList.Length; i++)
    {
        fieldList[i].Position = new Vector2(i * fieldList[i].Texture.Width + 140, Statics.GAME_WINDOW_HEIGHT / 2 - fieldList[i].Texture.Height / 2); 
    }
    // Festlegen des maximalen scroll Bereichs
    scrollArea = new Rectangle((int)fieldList[0].Position.X, (int)fieldList[0].Position.Y, fieldList[0].Texture.Height * fieldList.Length + 30, fieldList[0].Texture.Height);
}

// Diese Methode wird in Update() aufgerufen und ich denke hier liegt auch der Fehler
private void ScrollRoulette()
{
    for (int i = 0; i < fieldList.Length; i++)
    {
        fieldList[i].position.X += scrollSpeed; // scrollSpeed kann ich über Tasten einstellen, bzw. mit Leertaste einen Durchlauf mit abbremsen simulieren
        if (fieldList[i].Position.X >= scrollArea.Width + fieldList[i].Texture.Width)
        {
            // Sobald das Objekt die Breite der Scrollarea überschreitet wird es an den Anfang davon gesetzt. (Rechts raus und links wieder rein)
            fieldList[i].position.X = scrollArea.X - fieldList[i].Texture.Width;
        }
    }
}

// Zur Vollständigkeit noch die Draw Methode, aber daran kann man nichts falsch machen.
public override void Draw(GameTime gameTime)
{
    Statics.SPRITEBATCH.Begin(SpriteSortMode.BackToFront, BlendState.AlphaBlend);
    for (int i = 0; i < fieldList.Length; i++)
    {
         fieldList[i].Draw(Statics.SPRITEBATCH);
    }
    Statics.SPRITEBATCH.End();
}

Und um das ganze Problem zu visualisieren habe ich ein kleines Video davon erstellt welches das Problem zeigt.
Wenn ich das ganze mit einem Scrollspeed von 5 starte, dann scheint es relativ normal zu bleiben. Sobald ich das aber ändere oder schneller mache geht das Chaos los. Als ich zum zweiten mal in das Roulette ging habe ich eine Simulation laufen lassen wie es endgültig auch sein sollte. Also mit kurzer Zeit Scrollspeed um die 25f und dann langsam abbremsen. Und dabei entstehen eben die wildesten Lücken.
https://puu.sh/vx58V/5790aeb1e9.mp4 (Das Video ist knapp 15 MB groß. Manche Browser puffern es erst vollständig, nicht wundern falls nicht direkt was zu sehen ist. Wollte dafür jetzt nicht YouTube missbrauchen)

Ich hoffe irgendjemand hat dafür eine Lösung parat :freak:
 
Das Problem ist float. Öffne mal deine Browserkonsole und gib ein "0.1 + 0.2", dann siehst du was ich meine.

Das umgehst du, indem du nur ein float nutzt und dann für jedes Feld ein offset nimmst "x = scrollValue + fieldIndex*fieldWidth".
 
Interessanter Ansatz. Aber ich verstehe jetzt nicht so ganz wo ich das jetzt nutzen kann bzw. was du mit dem offset meinst, das sollte dann ein Integer sein?
 
Wenn du sie immer an der gleichen Stelle rendern wollen würdest, könnte man das so machen:

x = field.width * i;
DrawAt(x);

Nun willst du, dass es rotiert, also fügst du ein offset hinzu, den du bei jedem Update inkrementierst:

x = field.width * i + offset;
DrawAt(x);

Der offset kann hierbei sowohl int als als auch float sein und ist der Wert, um den sich das Rad drehen soll.

Edit:
Eventuell ist auch Zeile 26 das Problem.

Du solltest es nicht direkt an den Anfang setzen, sondern bereits wieder so weit, wie es drüber war:

Code:
fieldList[i].position.X = scrollArea.X - fieldList[i].Texture.Width + (fieldList[i].Position.X - (scrollArea.Width + scrollArea.X));


Die Sache mit Float ist zwar prinzipiell auch ein Fehler, doch wird die Abweichung dadurch wohl nicht ins Gewicht fallen.
Bei 10.000.000 mal += 0.1 beträgt die Abweichung vom Soll 0.0000000001610246%.
 
Zuletzt bearbeitet:
Bagbag schrieb:
Edit:
Eventuell ist auch Zeile 26 das Problem.

Du solltest es nicht direkt an den Anfang setzen, sondern bereits wieder so weit, wie es drüber war:

Code:
fieldList[i].position.X = scrollArea.X - fieldList[i].Texture.Width + (fieldList[i].Position.X - (scrollArea.Width + scrollArea.X));


Die Sache mit Float ist zwar prinzipiell auch ein Fehler, doch wird die Abweichung dadurch wohl nicht ins Gewicht fallen.

Das ist es! Vielen dank, ich hab mir da seit mehreren Stunden den Kopf drüber zerbrochen :freak:
Der Ansatz mit dem offset und meiner alten Zeile 26 funktionierte auch, also die Felder blieben beisammen, aber die Position wurde über die Abfrage dann nicht mehr zurückgesetzt, warum auch immer.
Jetzt funktioniert es jedenfalls fast. Es entsteht lediglich ein Leerfeld, dass sollte ich aber in den Griff bekommen.

Edit: Das Rad wird sich ja höchstens so 50 mal drehen, wenn überhaupt. Da spielt die Abweichung dann wohl tatsächlich keine Rolle. Ich behalte mir das aber für andere Projekte mal im Hinterkopf. Fieses float.

Code:
private void ScrollRoulette()
{
    for (int i = 0; i < fieldList.Length; i++)
    {
        fieldList[i].position.X += scrollSpeed;
        if (fieldList[i].Position.X >= scrollArea.Width + (fieldList[i].Texture.Width * 2))
        {
             fieldList[i].position.X = scrollArea.X + (fieldList[i].Position.X - (scrollArea.Width + scrollArea.X) );
         }                
     }
}
Das ist jetzt die endgültige Methode. Sie tut nun was sie soll :)
 
Zuletzt bearbeitet: (Funktionsfähige Methode ergänzt)
Ist "scrollArea.Width + (fieldList.Texture.Width * 2)" nicht theopraktisch auch falsch? Da fehlt ja der X-Wert vom scrollArea.
 
Haha da hast du sogar recht :D
"Theopraktisch" fake ich das ganze derzeit mehr oder weniger durch die Hintergrundtextur, die halt nur dort durchsichtig ist wo das Rad sichtbar sein soll. Dadurch sieht man halt auch das Grüne Feld rausgehen aber erst ein schwarzes wieder reinkommen weil die scrollArea derzeit viel größer ist als sie sein sollte. Aber das war mir auch erstmal nicht so wichtig, jetzt kann ich quasi erst richtig anfangen... Wegen dieser einen Zeile da soviel Zeit verschwendet. Darf man keinem erzählen. :evillol:
 
Gewöhn dich daran, wenn du weiterhin programmieren willst. Die zeitaufwendigsten Fehler sind immer die einfachsten, doofsten und unnötigsten.
 
Ja ich programmiere seit 7 Jahren, das ist das traurige. Sind halt meist eher Webanwendungen oder kleine Windows Applikationen.
Zuletzt hatte ich mich vor 4 Jahren mit XNA beschäftigt, macht Spaß sich wieder einzuarbeiten.

Und manchmal muss einfach ein außenstehender drauf schauen, der den Fehler dann schnell findet. Die typische Blockade im Kopf.
 
Zurück
Oben