Changing Sections in Unity

In this Dev-Log I will explain to you briefly, how my section management for moving entities works. Further we look into a beheaviour of Floats in C# which lead to a strange Bug which took a while to fix.

Section Management

As explained in Devlog Jan 5, 2020 "Loading Sections" my gameworld is devided in what I call Sections. As I'm moving the player arround, I change the state of Sections offscreen to "not loaded". As each Section holds all the entities which are currently located physically in its area, I know exactly which entities I have to update (because they are close to the player) And which I can ignore because the player isn't seeing them anyway at the moment.


For entities like planets, this is very simple. As you load the gameworld (or actually the section in the first time of your session) I instantiate the planet from a Prefab and set the section arround its position as its parent. It gets complicated if you have a actually moving entity, for example a spaceship or a whale, these are naturally allowed to move arround and will change between sections at some point. The bellow showed code manages the changing of sections.

Part of the class "EntityManager"
private void UpdateSection()
{
	if(state != SectionState.active)
	{
		state = SectionState.active;
		SetAllBehaviourEnabled(true);
	}
	
	Section newSection = worldManager.GetSectionDirect(rb2d.position);

	if (actualSection != null)
	{
		if (actualSection != newSection)
		{
			//Debug i.e. "(0, 0) at (15.0, 10.0, 0.0) to (0, 1) at (15.0, 30.0, 0.0)  --> if changed section"
			Debug.Log(
				actualSection.GetPositionInSectionGrid() + " at " + actualSection.transform.position + 
				" to " + newSection.GetPositionInSectionGrid() + " at " + newSection.transform.position);
			
			newSection.AddDynamicToSection(this);
			actualSection.RemoveEntity(this);
			actualSection = newSection;
		}
	}
	else
	{
		Debug.LogError("actual Section can not be null!");
	}          
}

The function "WorldManager.GetSectionDirect(Vector3 position)" returns the section which is arround the instert Position Vector3 (holds 3 floats x, y, z). See the code bellow.

Part of the class "WorldManager"
public Section GetSectionDirect(Vector3 positionInUnits)
{
	Vector2Int vectTemp = GetSectionIndicatorDirect(positionInUnits);
	return sections[vectTemp.x, vectTemp.y];

}

public Vector2Int GetSectionIndicatorDirect(Vector3 positionInUnits)
{
	Vector2Int vectTemp = new Vector2Int();
	if (positionInUnits.x >= 0 && positionInUnits.x <= GetWorldWidthInUnits() && positionInUnits.y >= 0 && positionInUnits.y <= GetWorldHeightInUnits())
	{
		vectTemp.x = (int) (positionInUnits.x / sectionWidthInUnits) - ((positionInUnits.x % sectionWidthInUnits) / sectionWidthInUnits);
		vectTemp.y = (int) (positionInUnits.y / sectionHeightInUnits) - ((positionInUnits.y % sectionHeightInUnits) / sectionHeightInUnits);

		return vectTemp;
	}
	else
	{
		Debug.LogError("Outside WorldGrid, outside sectionsrange!");
		vectTemp.x = -1;
		vectTemp.y = -1;
		return vectTemp;
	}
}

The main function GetSectionIndicatorDirect(Vector3 positionInUnits) does the all the hard work of finding out to which section a position in the world actually belongs. With the line after "vectTemp.x = ..." I do basically a position.x / sectionwidth (which results for 2.345 * sectionWidth in 2.345) and substract all numbers behind the comma (2.345 * sectionWidth % sectionWidth = 0.345, so 2.345 - 0.345 = 2?) In the end I convert to int with "(int)". This looks as if it would work... but it doesn't! Figured already out why not? It has to do with how the conversion into int works.


So to show you where the bug was located I wrote a short C# program. All it does is actually list some float numbers and convert them to int, then print both.
The code
private void PrintSomeFloats()
{
	for(int i = 0; i < 100; i+=5)
	{
		DebugFloat(1.0f + i * 0.01f);
	}
}

private void DebugFloat(float value)
{
	Debug.Log(" (int) " + value + "=" + (int)value);
}
And the result: (please note that because of how floats work and Unity Debug.Log(...) the floats get displayed rounded, more about that later...)
 (int) 1=1
 (int) 1.05=1
 (int) 1.1=1
 (int) 1.15=1
 
  [...]
 
 (int) 1.85=1
 (int) 1.9=1
 (int) 1.95=1
	  

But as we look at this, it would still work right, because if we convert 1 with (int) 1 we get actually 1 right?


Nope Here is why:


The way which float numbers work is special, there is no 2 (.00000000 --> unitl forever) the closest to numbers in floats are something like 1.999999f and 2.000000001f. So what we calculate in the GetSectionIndicatorDirect(Vector3 positionInUnits) function is either a value 1.9999999999 or 2.0000001 if we would round that, it dosn't make a different, it is still 2 but if we convert it to int by (int), we get 1 once (for the 1.99999999) and some milliseconds later 2 (for the 2.00000001). As soon as my whale exited the 0/0 - Section this resulated in it jumping arround fast between 0/0 and Section 0/1 or 1/1 because of rounding erros... I had made my life more complicated than nescessairy. Look at the final code which is implemented now


2 lines of short, but working code
vectTemp.x = (int) (positionInUnits.x / sectionWidthInUnits);
vectTemp.y =  (int) (positionInUnits.y / sectionHeightInUnits);
	  

Look here at Wikipedia if you want to now more about how floats work.

Back to Dev-Log