Sign in to follow this  

When tile&pixel position math goes wrong?..

This topic is 4656 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I've got a bit of a problem with my movement system. Or, at least, it *should* be a little problem, but it seems to have become somewhat insurmountable. What it is currently doing is moving right when you press the right arrow (that's correct), moves right when you press the left arrow (that's not), moves down when you press the down arrow (that's correct), and moves down when you press the up arrow (that's not). The problem is fairly obvious in terms of what's actually going wrong: the program is forcing an addition instead of an subtraction (to go up/left). The problem is, I don't know WHY. >< See, the movement aspect of my program works like this: 1. In sprite manager there is a function used to set a new destination. This calls a function for the specific sprite to take that destination called SetDest, passing an index value to it.
[source="c++"]
// 1. Sprite location is consistantly measured in both *T(x) and *P(x)
// 2. *P(x) is the most accurate as it is in pixels.
// 3. When we add a new destination in to the m_vldestlist we do it as 
//    a difference in tiles from the current location
//    of said sprite.
// 4. We then convert the difference in tiles from the current sprite 
//    to a direct tile location - by either adding or subtracting
//    depending on the value of the number (+, - ).
void CSprite::Set_NewDest(int iMove_Tx, int iMove_Ty)
{
	unsigned int uiDest_X = 0;
	unsigned int uiDest_Y = 0;

	// First of all, does our destination have a different x co-ordinate?
	if( iMove_Tx )
	{
		// We have a movement value for the x co-ordinate and it is positive.
		if( iMove_Tx > 0 )
		{
			// Check to see if the value given is in acceptable range.
			if( (iMove_Tx*32)+m_uiPos_Px < NC_Grab_Map_Width() )
			{
				// Set our destination value.
				uiDest_X = m_iPos_Tx + iMove_Tx;
			}
		}
		// We have a movement value for the x co-ordinate and it is negetive.
		else
		{
			// Check to see if the value given is in acceptable range.
			if( (m_uiPos_Px + (iMove_Tx*32)) >= 0 )
			{
				// Set our destination value.
				uiDest_X = m_iPos_Tx + iMove_Tx;
			}	
		}
	}

	// First of all, does our destination have a different y co-ordinate?
	if( iMove_Ty )
	{
		// We have a movement value for the y co-ordinate and it is positive.
		if( iMove_Ty > 0 )
		{
			// Check to see if the value given is in acceptable range.
			if( (iMove_Ty*32)+m_uiPos_Py  < NC_Grab_Map_Height() )
			{
				// Set our destination value.
				uiDest_Y = m_iPos_Ty + iMove_Ty;
			}
		}
		// We have a movement value for the y co-ordinate and it is negetive.
		else
		{
			// Check to see if the value given is in acceptable range.
			if( (m_uiPos_Py + (iMove_Ty*32)) >= 0 )
			{
				// Set our destination value.
				uiDest_Y = m_iPos_Ty + iMove_Ty;
			}	
		}
	}

	// If one or more values are greater then zero, we're good to go.
	if( uiDest_X || uiDest_Y  )
	{
		// Insert the new destination into the list.
		sLocation sDest = {uiDest_X, uiDest_Y};
		m_vlDestList.push_back(sDest);
	}
	// Return happily.
	return;
}


This done, the destination is now added to a structure of existing locations to be travelled to which are then worked through sequentially. When the sprite arrives at a destination, that destination marker is deleted from the structure. Simple, right? ;) The update process looks like this:
[source="c++"]
/**********************************************************
Function: void CSprite_Manager::Query_SpriteStatus_LocationQueue_Status()
Description: Go through all of the sprites in the sprite list, checking all of their destinations
in their destination queue seeing if they are at the location of any of them.  If so,
we remove the destination from the queue and they move onto the next one.
The first of two Sprite manager's heartbeat functions.
Parameters: 
Returns: 
***********************************************************/
void CSprite_Manager::Query_SpriteStatus_LocationQueue_Status()
{
	// Our erase iterator.
	vector<sLocation>::iterator iter;

	unsigned int uiDestsID = 0;

	// Go through all of the sprites in the sprite list, checking all of their destinations
	// in their destination queue seeing if they are at the location of any of them.  If so,
	// we remove the destination from the queue and they move onto the next one.
	for (unsigned int uiIndex=0; uiIndex < m_vlpSprList.size(); uiIndex++)
	{
		m_vlpSprList[uiIndex]->Update_Animation();

		for (iter = m_vlpSprList[uiIndex]->m_vlDestList.begin(); iter< m_vlpSprList[uiIndex]->m_vlDestList.end(); iter++)
		{
			// If the sprite's location = the destination location, erase the
			// location from the queue and move on to the next.
			if ( m_vlpSprList[uiIndex]->bHas_ReachedDest(uiDestsID) )
			{
				m_vlpSprList[uiIndex]->m_vlDestList.erase(iter);
				Write_Log("Log: Detail: SpriteManager erased location from Sprite Location Queue.");
			}
			else
			{
				// We have found the first instance of a queue item that hasn't been completed.
				// Get the difference between the sprite's location and the destination's location.
				m_vlpSprList[uiIndex]->Move_Sprite(uiDestsID); 
			}

			// Increment our Destination index appropriately.
			uiDestsID++;
		}
	}
	return;
}


Still, pretty straight forward, the function description says it all, really. Last, we have our actual movement function (which I suspect is where the problem is).
[source="c++"]
/**********************************************************
Function: void CSprite::Move_Sprite()
Description: Get the difference between our location and the destination.
			 if the movement distance is less then that distance, we can get
			 closer to it still without looping infinitely.
Parameters: 
Returns: None.
***********************************************************/
void CSprite::Move_Sprite(int iDestIndex)
{
	// If this index value has a movement value for either x or y...
	if( m_vlDestList[iDestIndex].iMove_Tx || m_vlDestList[iDestIndex].iMove_Ty )
	{
		// Holds the pixel value for our destination. This value is never negetive.
		guiDest_Px = m_vlDestList[iDestIndex].iMove_Tx * 32;
		guiDest_Py = m_vlDestList[iDestIndex].iMove_Ty * 32;

		// Allows us to maintain a contant rate of movement regardless of Frame Rate.
		dwTime = timeGetTime() - dwFrameTime;
		fFrameRatio = (dwTime / gkuiFRAME_RATE);

		// Calculate the distance we should move the player based on the moverate of the players sprite
		// divided by our framerate (to ensure a steady Rate of Movement).
		giSpr_MoveDist = m_iSpr_MoveRate/(fFrameRatio);

		if( guiDest_Py )
		{
			// If we still have iSpr_MoveDist or more pixels to go, great!
			// otherwise, we've reached our destination. 

			// We're headed North.
			//
			if( guiDest_Py < m_uiPos_Py )
			{
				// Find the absolute distance between location and destination.
				guiDiff_y = m_uiPos_Py - guiDest_Py;
				if( guiDiff_y >= giSpr_MoveDist )
					m_uiPos_Py -= giSpr_MoveDist;
			}

			// We're headed South.
			else if( guiDest_Py > m_uiPos_Py )
			{
				// Find the absolute distance between location and destination.
				guiDiff_y = guiDest_Py - m_uiPos_Py;
				if( guiDiff_y >= giSpr_MoveDist ) 
					m_uiPos_Py += giSpr_MoveDist;
			}
		}

		if( guiDest_Px )
		{
			//
			// We're headed West.
			if( guiDest_Px < m_uiPos_Px )
			{
				// Find the absolute distance between location and destination.
				guiDiff_x = m_uiPos_Px - guiDest_Px;
				if( guiDiff_x >= giSpr_MoveDist )
					m_uiPos_Px -= giSpr_MoveDist;
			}

			// We're headed East.
			else if( guiDest_Px > m_uiPos_Px )
			{
				// Find the absolute distance between location and destination.
				guiDiff_x = guiDest_Px - m_uiPos_Px;
				if( guiDiff_x >= giSpr_MoveDist ) 
					m_uiPos_Px += giSpr_MoveDist;
			}
		}

		// Get the current tile location of the Sprite.
		m_iPos_Tx = (m_uiPos_Px / 32);
		m_iPos_Ty = (m_uiPos_Py / 32);
	}
	// Return nothing, qed. ;)
	return;
}


The gui* stands for global unsigned int - yeah, I know, it's a GLOBAL. Evil, warding off with the cross and all that. But in this case, it saves me having to recreate more then four variables for every iteration that the function runs (and it runs as often as your update_game/update_frame). So, not so evil, me thinks. ;) Explaination of the class members: m_iPos_T(x/y) - stores the lower case letter co-ordinate name for the current position of the sprite's position in (T)iles. m_iPos_P(x/y) - stores the lower case letter co-ordinate name for the current position of the sprite's position in (P)ixels. I use 32 pixels to the tile. (I know I have too many literals present in the code atm, I'll get rid of that once it's all working properly :) ). This code typically follows a simple execution like so:
[source="c++"]
if(KeyDown(cKeys,DIK_RIGHT))
{

	m_pSprMan->Clear_Sprite_Personal_Queue(1);
	m_pSprMan->Queue_Destination(1, 1, 0 );
	m_pSprMan->Set_Sprite_Facing(1, RIGHT);
}


Which does the following: first, clear the sprites movement queue. second, queue a new destination in tiles (the 1, 1) indicates an offset from the current location - which is automatically converted using setdest and the 0 indicates the ID of the sprite. Last it sets the sprite to face in the new direction. Then on every iteration of the game loop it checks to see if there are any queue items outstanding. If so, it checks to see if any of the queue items can be removed (is the sprite standing on that location? if so, remove queue item, else, movesprite(index)). Then it runs move sprite, which should move the sprite a variable distance towards the destination based on the user's frame rate (to try and ensure smooth animation). Wow, thats a lot of text. The point of this post is two-fold. First off, I need to get those freaking movement keys working. Second, I want to know if you guys have any suggestions on how to improve this (efficiency, redundant overhead), I've got a bit of an efficiency addiction going here :) ]: The updated code is now up :[ I know you guys don't have to help, any help I do receive is greatly appreciated (and earns you a spot in the game credits). -HeavyBlade [Edited by - HeavyBlade on March 10, 2005 7:09:43 PM]

Share this post


Link to post
Share on other sites
Thanks :)
Are you referring to where the destination is set in SetDest(x,y)?

I tried changing that, and now, while my character no longer moves in the opposite direction (yea), he doesn't move at all (when the left and up keys are pressed)...
how annoyingly bizarre XD.

Share this post


Link to post
Share on other sites
Quote:
Original post by HeavyBlade
Thanks :)
Are you referring to where the destination is set in SetDest(x,y)?


No. I'm referring to where you calculate uiDest_x and uiDest_y in Set_NewDest().

Share this post


Link to post
Share on other sites
Ok, I fixed that, but the error above remains... (at least he's not moving in the wrong direction now?..) he just doesn't move at all to the left or upwards, which would suggest something getting cancelled out, but I'm really not sure what or where -_-.

Share this post


Link to post
Share on other sites
Well, I don't see anything obviously wrong with the movement calculations. The only thing dodgy is the fFrameRatio calculation. It looks like you are doing integer division to arrive at the fFrameRatio value. In that case, you probably won't get what you expect. However, if that were the whole problem, I would expect your right/down movement to be broken, as well.

The only thing I can suggest at this point is to put a breakpoint in Move_Sprite, right after the dwTime calculation. Then step through it and see what values you are getting. That might help you pinpoint where it is going wrong.

Share this post


Link to post
Share on other sites
Bit of an update:

It *will* let me move left and up now (seriously seeing that almost made me fall off my chair XD), BUT, only if I'm more then two or three tiles away from the left and top borders (of the map)... that's really weird.

I tried removing the conditional in set_newdest for the negetive direction, but that didn't help. Any ideas?

Share this post


Link to post
Share on other sites
Allow me to explain the integer float division thing..

What I'm *trying* to do is get a representative value of the framerate the user's computer is rendering at. That way I can make smoother animations - and also a smooth change in distance!, but to be honest, I'm not sure I'm going about it in any way near correct.

Any ideas on that?

Share this post


Link to post
Share on other sites
The method of frame rate calculation seems to be ok, you just need to use floating point throughout. The conversion to integer should be delayed as long as possible to limit precision loss. As it is, you have int3 = int1 / int2 where int2 is larger than int1. Since all of the variables involved are integers, the result is going to be zero.

I would recommend using floating point for your x/y positions. Otherwise, any movements of less than a full pixel will get truncated to zero and not accumulate across frames and could make the sprite not move at all.

Share this post


Link to post
Share on other sites

This topic is 4656 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this