The object starts to freeze when approaching the end point

I’m trying to make a game (top view) where there is transport. Cars drive on some roads on the map. The map is a picture (PNG) and physical bodies made using the JSON file uploader. On the map there are points (interconnected one after another) to which the cars should aim. These points are also stored in the JSON file, which I constantly process during the program.
I create several cars-objects in an array, which I process in the update() function. For each object, a check is made: whether the object has reached its point, after which the next point is taken from the JSON file.
I noticed that if the distance between two points was made much more than the average value between all points, then when approaching the end point the object starts moving incorrectly (jerks in place, just stops forever, or even starts moving in the opposite direction backwards at great speed).
Who can tell what is the reason? Are there any solutions?
The program code is already very much, but if you need it, I can write here.

Have you heard about way points?

Ammmm… No)
Probably missed when I read guides on the site…
Can you tell or give a link to explore?

Its a GPS concept: https://en.wikipedia.org/wiki/Waypoint

But, this concept applies to games where objects need to follow paths. Many racing games use this concept. Even non racing games like Monument Valley can use this concept. One of their developers did a talk on how they use waypoints.

slackmoehrle,
I read and watched a short video about waypoints. I have done the same thing with one difference: the points are not stored in the internal memory, but in the JSON file: the indices of the points, their coordinates (x, y) and the index of the next point are given. This file is processed each time an object approaches the minimum distance to the target point.

In other words, I did it myself, just did not know how this method is called in the world)

I also forgot to say that the problem persists if we split the long distance between two points into short sections programmatically.
It only helps to add a point to the JSON file itself manually.

I guess I need more info. It’s hard to digest exactly since we are talk about about distance between points but also you are reading from JSON, etc. Can you describe your issue in more detail? Perhaps post a picture or code.

JSON file with waypoints has the structure as shown in the picture.

All fields except “name” (aka index), “point” and “next” exist for other checks and calculations.

As I said, there is already a lot of software code, so I’ll give excerpts:
Level1.cpp
#include “Level1.h”
USING_NS_CC;

Scene* Level1::createScene()
{
        // added Main Layer
        // added GUI Layer
	return scene;
}
bool Level1::init()
{
	// added Background (Map) sprite
        // added Edge Body
        // Loading map from JSON file
// WayPoints Parser:
	TrajectoryParser::getInstance()->parseJsonFile("traectory.json");

        // Player Setup
        // added Collision handler

	this->scheduleUpdate();
	return true;
}

bool Level1::onContactBegin(cocos2d::PhysicsContact &contact)
{
         // collision handling
}

void Level1::update(float dt)
{
	vPlayer->updateMovementPlayerVehicle(dt, wheelRotationPercentage, speedPercentage, isCrashed);

	cocos2d::Vec2 vehiclePlayerPosition = vPlayer->getPositionPlayerVehicle();

	if (numAIVehicles < MAX_NUM_OF_GENERATED_AIVEHICLES)
	{
		std::string pointNameForGenerate = TrajectoryParser::getInstance()->getPointNameForGenerateAIVehicle(vehiclePlayerPosition, visibleSize, allVehiclesPositionsOnMapList);
		
		if (pointNameForGenerate != "VehicleDoesNotBeGenerated")
		{
			auto vehicleAI = new std::shared_ptr<VehicleAI>(new VehicleAI(this, 0x000003, 9, pointNameForGenerate));
			vehicleAIList.push_back(vehicleAI);
			VehicleAI *vehai = vehicleAI->get();
			allVehiclesPositionsOnMapList.push_back(vehai->getPositionAIVehicle());
			numAIVehicles++;
		}
	}
	if (vehicleAIList.size() != 0)
	{
		int iterator = 0;
		while (vehicleAIList.size() != iterator)
		{
			VehicleAI *vai = vehicleAIList[iterator]->get();
			cocos2d::Vec2 vehicleAIPosition = vai->getPositionAIVehicle();
			if (vehiclePlayerPosition.distance(vehicleAIPosition) > MAX_DISTANCE_FOR_DELETE_GENERATED_AIVEHICLES || vai->isCancelled())
			{
				delete vai;
				vehicleAIList.erase(vehicleAIList.begin() + iterator);
				allVehiclesPositionsOnMapList.erase(allVehiclesPositionsOnMapList.begin() + iterator);
				numAIVehicles--;
				iterator--;
			}
			else
			{
				vai->vehicleAIMovement(dt, vehiclePlayerPosition, vehicleAIList, iterator);
				vehicleAIPosition = vai->getPositionAIVehicle();
				allVehiclesPositionsOnMapList.at(iterator) = vehicleAIPosition;
			}
			iterator++;
		}
	}
}

VehiceAI.cpp
#include “VehicleAI.h”
USING_NS_CC;

VehicleAI::VehicleAI(cocos2d::Layer *layerVAI, int bitMaskVAI, int numLayerVAI, std::string pointNameForGenerate)
{
	nextPointVAI = TrajectoryParser::getInstance()->nextPointCoordinates(pointNameForGenerate);
	pointName = pointNameForGenerate;
	float startRotation = TrajectoryParser::getInstance()->getStartRotationAIVehicle(pointNameForGenerate);
        // added AI Vehicle Sprite
}
void VehicleAI::vehicleAIMovement(float dt, cocos2d::Vec2 vehiclePlayerPosition, std::vector<std::shared_ptr<VehicleAI>*> vehicleAIList, int iterator)
{
	if (nextPointVAI.distance(vehicleAISprite->getPosition()) < MIN_DISTANCE_FOR_UPDATE)
	{
		roadType = TrajectoryParser::getInstance()->getRoadType(pointName);
		// get data about new point:
		pointName = TrajectoryParser::getInstance()->nextPointName(pointName);
		nextRoadType = TrajectoryParser::getInstance()->getRoadType(pointName);
		prevPointVAI = nextPointVAI;
		if (pointName != "stop")
		{
			nextPointVAI = TrajectoryParser::getInstance()->nextPointCoordinates(pointName);
		}
	}
        // further calculations of the new position and rotation
}

I can’t show how an object starts to frizz :frowning:
How it works in simple words:
in each iteration of the UPDATE() function, the array (vector) with the AI ​​vehicles is processed completely (from 0 to the last element). For each element of the vector, the function of calculating new coordinates and rotation for the object is called. This function is defined in the class VehicleAI.
How the function works:
Is the current vehicle position from the target point smaller than the specified minimum distance?
yes → 1) take the next point from the JSON file by the index specified in the “next” field; 2) calculate the new coordinates for the object
no → just calculate new coordinates for the object

This is how the calculations for each element in the vector go, after which a new iteration of the UPDATE() function occurs.

If you still do not have enough information, I will send more.

No ideas? :frowning:
I have suspicions that this bug is reproduced somewhere at the engine level, but I am not a C ++ guru to say exactly what the problem is. Maybe in my code, but at short distances my code works well. All problems begin with a strong distance to the coordinates of the next point from the previous point.
If you understand what the problem is, then tell me how to get around it, please. :roll_eyes:

It’s probably time for you to step over this code line by line with the debugger and see what you notice. Also, if you can profile it that would help. It’s hard with problems like this for us to know your code well enough to see a 1off problem.

If it is getting slow moving towards the last point, do you have a lot retained? What else is going on? What is your frame rate?

It looks like you don’t parse the whole JSON up front and store what you need in a vector or other data structure for faster access. If you parse the JSON when each point is needed this could add to slowness.

I guess you should post some screenshots of your actual game or maybe a short video demonstrating the problem. Visual might help here.

https://youtu.be/VFJW1k1nv_w
The picture below (test map) shows the approximate location of the points that I recorded in the file.
Yes, I read it every time for a new point (without saving everything into an array). The point is recorded in the class variable and stored there until the condition that the object approaches it and it is time to take a new point.


I decided not to overwrite all the points in the array, because in the future there will be a great many such points on the big map. And it is better not to take extra memory.
If my reasoning is wrong, then please correct me :slight_smile:

So you are saying it is best to parse a JSON file for each and every point? Have you timed this operation? If you have 1,000 points that is 1,000 parses versus creating a vector, pre-allocating 1,000 spots and sucking it all in.

What specifically should we look for in the video. I watched it and besides some blips the slowness you mention isn’t obvious to me.

Let’s tag in some other folks here so we can get the wisdom of the crowd. @R101 @dimon4eg @mars3142

Just to get an understanding of your thought process here, is there a specific reason that you want to constantly parse the JSON data during the execution of the program? It seems like a really inefficient way to handle the data, not to mention that you’re also possible over-complicating the implementation because of it.

Loading the entire JSON data into memory, processing it, and storing the relevant data in a more suitable data structure that is more of a natural fit for how you use that data, would have many benefits that you may or may not be aware of. The speed of processing will improve significantly, and the code to use that data should become much simpler. At least then you can focus on what you’re trying to achieve, and not have to worry about the JSON parsing layer and whatever negative impact it may be having on the performance of your app.

This is a classic example of premature optimization. Worry about it if and when you see it as being a problem. Also, I can’t imagine way-point data requiring an unreasonable amount of memory.

I’m not 100% certain, but there may be something wrong with the while loop.

Consider doing something like this if you want to erase in the middle of a loop:

auto it = vehicleAIList.begin();
for ( ; it != vehicleAIList.end(); ) {
  if (condition) {
    it = vehicleAIList.erase(it);
  } else {
    ++it;
  }
}

It would mean re-thinking how you reference the VehicleAI, especially since you’re storing it in two different vectors. On that note, why are you storing the same pointer to VehicleAI in 2 different vectors? Also, why is your vector this:

std::vector<std::shared_ptr<VehicleAI>*>

Which is a vector of a pointer to a shared_ptr, I don’t quite understand the reasoning for that.

Shouldn’t it be this:

std::vector<std::shared_ptr<VehicleAI>>

You could also store weak_ptr in the 2nd vector, and check if it’s valid before using it, and if it’s not, then remove it from the list.

For instance:

std::vector<std::weak_ptr<VehicleAI>> allVehiclesPositionsOnMapList;

Example of how to use it:

for (auto&& it = allVehiclesPositionsOnMapList.begin(); it != allVehiclesPositionsOnMapList.end(); /* do nothing here */)
{
    // Check if the pointer is valid
    if (auto vehicle = (*it).lock())
    {
        // do what you want with vehicle
        ++it;
    }
    else
    {
        // it's no longer a valid pointer, so remove it from the vector
        it = allVehiclesPositionsOnMapList.erase(it);
    }
}

I’m not sure about the correctness of my decision, I did not think about this issue at the time of creation. But if do not take into account the processing time for taking a new point, then, logically, the problem is not this, because the coordinates of the target point are stored in a variable. The variable is overwritten.
The twitching is visible on the video, although all objects should move smoothly and with equal speed.

R101,
Thanks for the tip.)

In this case, I will rewrite to an array.
I hope the problem lies only in this.

Sorry, I forgot to say. allVehiclesPositionsOnMapList is used only to store the coordinates of all objects in my world. And I process it in a function that determines where a new object can be generated so that they do not overlap each other.
In my opinion, this is not the best way, and I have long wanted to remove this vector from the program, and use only vehicleAIList, from where you can get coordinates. Just forgot.

if do not specify *, then cannot take a managed object with the get () function…

I just noticed why. This is not correct:

auto vehicleAI = new std::shared_ptr<VehicleAI>(new VehicleAI(this, 0x000003, 9, pointNameForGenerate));

What you’re doing is creating a pointer to a shared_ptr, which I assume was not your intention (and it’s not how it is typically used).

It should be

auto vehicleAI = std::make_shared<VehicleAI>(this, 0x3, 9, pointNameForGenerate);

or

auto vehicleAI = std::shared_ptr<VehicleAI>(new VehicleAI(this, 0x3, 9, pointNameForGenerate));

Also, consider writing 0x000003 as 0x3 instead, to save yourself typing and improve readability. :wink:

1 Like

Yes, I just learn smart pointers and did not know how to use them correctly, but what I code was the only option that worked)
Then maybe give me a couple more tips on pointers?

  1. Сan I still take a managed class object with the get() function?
  2. I used to delete an object in the destructor of the VehicleAI class:

VehicleAI::~VehicleAI()
{
vehicleAISprite->removeFromParentAndCleanup(true);
}

then in Level1::update():


delete vai;
vehicleAIList.erase(vehicleAIList.begin() + iterator);

Will it still work?

I don’t understand what you mean by managed class object.

You shouldn’t need to use .get() unless you want the raw pointer being held by the smart pointer.

For instance:

auto obj = std::make_shared<MyClass>(param1, param2);

obj->doSomething(); 
is the same as 
obj->get()->doSomething();

Smart pointers will get released (data is freed) automatically if they go out of scope. So, all you really have to do is remove it from the std::vector, and as long as nothing else is holding a reference to that smart pointer, it will be freed.

VehicleAI::~VehicleAI()
{
    vehicleAISprite->removeFromParentAndCleanup(true);
}

There is nothing wrong with this code, and nothing really changes. Once the smart pointer has no remaining references, it will still call the destructor. If you’re unsure about something, add CCLOG(…) console output calls in places like your destructor to see the order of events in your code. There is no point guessing if things are or are not working.