Problem bei Updaterate/Bewegungsmethode



  • Hio 🙂

    Bin zurzeit dabei für mein Spiel (entsteht in sfml) den Frameratelimiter mit festem Timestep für die Updatemethode im hintergrund zu schreiben.

    Das klappt auch soweit, aber ich hab das problem, dass das Updaten viel zu langsam abläuft. Wird vlt klarer, wenn ich den code zeige:

    setMovementVector(sf::Vector2f((getMovementVector().x * frametime)/1000000.0, (getMovementVector().y * frametime)/1000000.0));
    	setMovementBufferNew(sf::Vector2f(getMovementBufferOld().x + getMovementVector().x, getMovementBufferOld().y + getMovementVector().y));
    	std::cout << mTestClock->restart().asMicroseconds() << std::endl;
    
    	validateMovement();
    	if ((fabs(getMovementBufferOld().x) >= 1.0 || getMovementBufferOld().x == 0) 
    && (fabs(getMovementBufferOld().y) >= 1.0 || getMovementBufferOld().y == 0) && (getMovementBufferOld().x != 0 || getMovementBufferOld().y != 0)){ 
    
    		moveObject();
    		setMovementBufferOld(sf::Vector2f (0.0, 0.0));
    		setMovementBufferNew(sf::Vector2f (0.0, 0.0));
    	}
    

    Und ja, die if abfrage ist so noch schrott, aber den grundlegenden zweck erfüllt sie, sofern ich mich auf eine richtung beschränke.

    Die Frametime die übergeben wird sind 1666.67 mikrosekunden, die sf::clock bestätigt mir, dass die tatsächliche aufrufzeit etwa im rahmen liegt (~2000mikrosekunden, genauer ist leider die sf:sleepmethode nicht, die ich nutze um die updaterate zu erreichen).

    Das Problem ist jetzt: Der charakter braucht etwa 4s um sich 1 pixel zu bewegen, nach den berechnungen in der methode sollte er sich in 1s 5 pixel bewegen, das ganze ist also 20 mal zu langsam.

    (der movementvector ist aktuell 5 in die entsprechende richtung).

    Die rechnung sollte ja eigentlich die 5 * 1666 / 1000000 rechnen. das wäre 0.0083333 (laut debugger kommt das auch raus) . Den wert mal der ~600 Updates die sekunde sollte 5 pixel bewegung pro sekunde ergeben, tut es aber nicht 😕

    Die validateMovement methode sollte eigentlich nichts beeinflussen, dass ist nur meine Kollisionserkennung, die ging auch schon und mitten im raum gibts natürlich keine kollision.

    Trotzdem mal der code:

    void Player::validateMovement(){
    	//ckeck left corners
    	mCurrentTilePosition = convertToTilePosition(sf::Vector2i(getCollisionBox().left, getCollisionBox().top));
    	mFirstNewTilePosition = convertToTilePosition(sf::Vector2i(getCollisionBox().left + getMovementBufferNew().x , getCollisionBox().top)); //top left
    	mSecondNewTilePosition = convertToTilePosition(sf::Vector2i(getCollisionBox().left + getMovementBufferNew().x , getCollisionBox().top + getCollisionBox().height)); //bottom left
    
    	//collision
    	if(mCurrentRoomTiles[mFirstNewTilePosition.x][mFirstNewTilePosition.y].getCollideID() == 1 || 
    
    mCurrentRoomTiles[mSecondNewTilePosition.x][mSecondNewTilePosition.y].getCollideID() == 1){
    
    		setMovementBufferNew(sf::Vector2f(getMovementBufferOld().x, getMovementBufferNew().y));
    	}
    
    	//doors
    	else if(mCurrentRoomTiles[mFirstNewTilePosition.x][mFirstNewTilePosition.y].getTileID() == 2 || 
    
    mCurrentRoomTiles[mSecondNewTilePosition.x][mSecondNewTilePosition.y].getTileID() == 2){
    		if( (mFirstNewTilePosition.x == 0 && mFirstNewTilePosition.y == 4 ) || (mSecondNewTilePosition.x == 0 && mSecondNewTilePosition.y == 4)) mTeleportTo = "Left";
    		else if( (mFirstNewTilePosition.x == 7 && mFirstNewTilePosition.y == 0 )) mTeleportTo = "Up";
    		else if( mSecondNewTilePosition.x == 7 && mSecondNewTilePosition.y == 8) mTeleportTo = "Down";
    	}
    
    	//ckeck top corners
    	mCurrentTilePosition = convertToTilePosition(sf::Vector2i(getCollisionBox().left, getCollisionBox().top));
    	mFirstNewTilePosition = convertToTilePosition(sf::Vector2i(getCollisionBox().left  , getCollisionBox().top + getMovementBufferNew().y)); //left top
    	mSecondNewTilePosition = convertToTilePosition(sf::Vector2i(getCollisionBox().left  + getCollisionBox().width , getCollisionBox().top + getMovementBufferNew().y)); //right top
    
    	//collision
    	if(mCurrentRoomTiles[mFirstNewTilePosition.x][mFirstNewTilePosition.y].getCollideID() == 1 || 
    
    mCurrentRoomTiles[mSecondNewTilePosition.x][mSecondNewTilePosition.y].getCollideID() == 1){
    		setMovementBufferNew(sf::Vector2f(getMovementBufferNew().x, getMovementBufferOld().y));
    	}
    
    	//doors
    	else if(mCurrentRoomTiles[mFirstNewTilePosition.x][mFirstNewTilePosition.y].getTileID() == 2 || 
    
    mCurrentRoomTiles[mSecondNewTilePosition.x][mSecondNewTilePosition.y].getTileID() == 2){
    		if( (mFirstNewTilePosition.x == 7 && mFirstNewTilePosition.y == 0 ) || (mSecondNewTilePosition.x == 7 && mSecondNewTilePosition.y == 0)) mTeleportTo = "Up";
    		else if( (mFirstNewTilePosition.x == 0 && mFirstNewTilePosition.y == 4 )) mTeleportTo = "Left";
    		else if( (mSecondNewTilePosition.x == 14 && mSecondNewTilePosition.y == 4)) mTeleportTo = "Right";
    	}
    
    	//ckeck right corners
    	mCurrentTilePosition = convertToTilePosition(sf::Vector2i(getCollisionBox().left, getCollisionBox().top));
    	mFirstNewTilePosition = convertToTilePosition(sf::Vector2i(getCollisionBox().left + getMovementBufferNew().x + getCollisionBox().width , 
    getCollisionBox().top )); //top right
    	mSecondNewTilePosition = convertToTilePosition(sf::Vector2i(getCollisionBox().left + getMovementBufferNew().x + getCollisionBox().width ,
     getCollisionBox().top + getCollisionBox().height )); //bottom right
    
    	//collision
    	if(mCurrentRoomTiles[mFirstNewTilePosition.x][mFirstNewTilePosition.y].getCollideID() == 1 || 
    
    mCurrentRoomTiles[mSecondNewTilePosition.x][mSecondNewTilePosition.y].getCollideID() == 1){
    		setMovementBufferNew(sf::Vector2f(getMovementBufferOld().x, getMovementBufferNew().y));
    	}
    
    	//doors
    	else if(mCurrentRoomTiles[mFirstNewTilePosition.x][mFirstNewTilePosition.y].getTileID() == 2 || 
    
    mCurrentRoomTiles[mSecondNewTilePosition.x][mSecondNewTilePosition.y].getTileID() == 2){
    		if( (mFirstNewTilePosition.x == 14 && mFirstNewTilePosition.y == 4 ) || (mSecondNewTilePosition.x == 14 && mSecondNewTilePosition.y == 4)) mTeleportTo = "Right";
    		else if( (mFirstNewTilePosition.x == 7 && mFirstNewTilePosition.y == 0 )) mTeleportTo = "Up";
    		else if( (mSecondNewTilePosition.x == 7 && mSecondNewTilePosition.y == 8)) mTeleportTo = "Down";
    	}
    
    	//ckeck bottom corners
    	mCurrentTilePosition = convertToTilePosition(sf::Vector2i(getCollisionBox().left, getCollisionBox().top));
    	mFirstNewTilePosition = convertToTilePosition(sf::Vector2i(getCollisionBox().left , 
    
    getCollisionBox().top + getMovementBufferNew().y + getCollisionBox().height)); //left bottom
    	mSecondNewTilePosition = convertToTilePosition(sf::Vector2i(getCollisionBox().left + getCollisionBox().width , 
    getCollisionBox().top + getMovementBufferNew().y + getCollisionBox().height)); //right bottom
    
    	//collision
    	if(mCurrentRoomTiles[mFirstNewTilePosition.x][mFirstNewTilePosition.y].getCollideID() == 1 || 
    
    mCurrentRoomTiles[mSecondNewTilePosition.x][mSecondNewTilePosition.y].getCollideID() == 1){
    		setMovementBufferNew(sf::Vector2f(getMovementBufferNew().x, getMovementBufferOld().y));
    	}
    
    	//doors
    	else if(mCurrentRoomTiles[mFirstNewTilePosition.x][mFirstNewTilePosition.y].getTileID() == 2 || 
    
    mCurrentRoomTiles[mSecondNewTilePosition.x][mSecondNewTilePosition.y].getTileID() == 2){
    		if( (mFirstNewTilePosition.x == 7 && mFirstNewTilePosition.y == 8 ) || (mSecondNewTilePosition.x == 7 && mSecondNewTilePosition.y == 8)) mTeleportTo = "Down";
    		else if( (mFirstNewTilePosition.x == 0 && mFirstNewTilePosition.y == 4 )) mTeleportTo = "Left";
    		else if( (mSecondNewTilePosition.x == 14 && mSecondNewTilePosition.y == 4)) mTeleportTo = "Right";
    	}
    
    	setMovementBufferOld(getMovementBufferNew());
    }
    

    Die framerategrenze löse ich wie folgt:

    while(mMainWindow->isOpen()){
    
    		HandleEvents();
    		Update();
    
    		if(mCurrentRenderTime >= mRenderT){
    			Render();
    			mCurrentRenderTime = 0.0;
    		}
    
    		mCurrentFrameTime = mMainClock->restart().asMicroseconds();
    		mCurrentRenderTime += mMainRenderClock->restart().asMicroseconds();
    
    		mSleepTime = sf::microseconds(mUpdateT - mCurrentFrameTime);
    		sf::sleep(mSleepTime);
    		mCurrentFrameTime = mMainClock->restart().asMicroseconds();
    	}
    

    Is vlt recht krude, aber bis auf die ungenauigkeit von sf:sleep (die auch auftritt, wenn man die framerategrenze von sfml selbst nutzt, die sorgt aber bei mir für regelmäßiges stocken) klappts^^

    Vlt hat ja irgendwer nen tipp 😕


  • Mod

    600update/s ? was hast du vor? es macht normalerweise garkeinen sinn mehr updates zu machen als gerendert wird. es ist eher andersrum, dass oefter zwischen zwei updates gerendert wird.

    dein problem ist dass du davon ausgehst dass der render aufruf 0ms braucht, aber in wirklichkeit braucht er 16.6ms, weil dein monitor vermutlich 60Hz hat und auch das flip dann nicht oefter als 60mal gemacht werden kann.
    entsprechend addierst du hier:

    mCurrentRenderTime += mMainRenderClock->restart().asMicroseconds();
    

    16.6ms auf und im naechsten durchlauf zeichnest du wieder, dein loop kann also garnicht oefter als 60mal durchlaufen.
    entsprechend bewegt sich bei dir alles 10 mal langsammer.



  • Hab aktuell halt das Problem (wenn ich nicht häufiger update), dass es lange dauert auf änderungen z.B. beim bewegen zu reagieren. Wenn ich an einer Steinkante aktuell diagonal weglaufe beweg ich mich fast 1 tile noch vollkommen gradeaus, das möchte ich durch feinere updates verhindern. Die 600/s waren erstmal nur n wert, kann auch gut sein, dass ich auf 120/s zurückgehe oder so.
    (Die tiles haben 76px kantenlänge, meine bewegung sind 5px/s, sollte also nicht so sein, aber da kommen von HandleEVents wohl zu selten Updates. Wobei das natürlich auch Frameworintern sein kann, und ich da garnichts machen kann)

    der renderaufruf ansich dauert nicht soo lange, ohne framerategrenze komm ich auf ~1500-2000FPS, gemessen mit fraps.

    Außerdem sind das ja zwei unterschiedliche timer, einmal fürs rendern und einmal fürs updaten/handlen der events. Der mCurrentRenderTime dient ja nru der if-abfrage und bekommt die zeit aufadiert, die seit dem letzten mal vergangen ist, also immer ~2000mikrosekunden, (außer es wurde gerendert, dann natürlich länger). Der für den sleep wichtige timer mCurrentUpdateTime bekommt ja nur die zeit zugeteilt, die nach dem letzten sleep vergangen ist.

    Und der Timer in meiner Updatemethode vom player gibt mir ja auch immer ~ diese 2000mikrosekunden aus, die ich haben will, die wird also schon so oft aufgerufen wie sie soll.

    Außerdem hab ich grad mal getestet, selbst wenn ich den ganzen sleep kram weglasse und einfach laufen lass so schnell es geht hab ich das problem(dann wird die Updatemethode etwa alle 500mikroS aufgerufen) 😕 muss also irgendwie an der Updatefunktion selbst liegen 😕


Log in to reply