What I am dealing with: There is a 2D game with a top view. The map in the game is tiled, procedurally generated, the size is infinite. Briefly about the architecture of the game: the whole game is divided into four modules - physics, graphics, logic, event system. These modules are executed in one thread (hereinafter I will call it the main one). Each object in the game contains one component of physics, graphics and logic. These components are stored and processed by the respective systems.

What needs to be done and what problem I have: Since the map is infinite, a small portion of the map of a fixed size is downloaded. When a player approaches the edges of the map, it is necessary to generate new sections of the map, and remove the old sections along with all the objects on them. The generation can take up to several seconds, so I decided to put it into a separate module that will be executed in my own thread. But I had difficulties with how from an architectural point of view to organize the interaction of the generation module with the other modules in the game.

As I try to solve the problem: when a character comes to the border - the flow in which the generation module is processed wakes up. Next, the generation module starts to generate the world by adding objects to special buffers provided by the physics, graphics and logic module. When generation is complete, the generation module sends an event to the event system and falls asleep. The event system, at the time of the update in the main thread, notifies logic, physics and graphics that generation is complete. Those in turn swap the current storage on the buffer. I chose this approach because we fill the buffer NOT CONTACTING TO OBJECTS in the module storage, which facilitates synchronization between threads; we do not need to spend time adding objects from the buffer to the storage, as well as searching for and deleting objects that are outside the map. Based on the above, the following requirements are imposed on the buffer:

  1. The buffer is the same data structure as the storage of the corresponding module.
  2. At the moment when the modules receive an event about the end of generation, they swap storage and buffer - the buffer should contain only relevant information. It is necessary to take into account that in the storages of modules from the main stream will be deleted and added objects during the operation of the generation module.
  3. When a player approaches the edges of the loaded portion of the map, it shifts. It is necessary to determine which objects remained within the map. They should get in the buffer.

Actually the questions that I have:

  1. A quad tree is used to store dynamic physical components. How to determine which components will remain within the loaded portion of the map after the current generation is completed, if in the main thread processing a physical module, components can be constantly added and removed?
  2. To store the various components of logic using regular lists. The question is the same as above.
  3. Static physical components are stored in a one-dimensional array of chunks. They are not located discretely relative to chunks and tiles. They can be both very small and very large. The question is the following: let's say we load a new section of the map and generate a house on it. The house should be located on the border with the previously loaded chunk, which is now being processed by the physics module in another stream. We can add a house, provided that there is not a single static or dynamic physical component in the space occupied by it. At the time of checking the generation of the house, this condition was observed and the house was added to the buffer. But the physics module does not know anything about the generation module and cannot peek into the buffer until generation is completed, we will also remember that dynamic and static physical components are stored separately. Next, let the player stand on the place where the house is located in the buffer (this place is empty in the main vault). And then the generation ended and the physics module swapped the buffer and storage. It turns out - the player was inside the house without going into it. How to solve this problem?
  • The question is very extensive. In short, there are synchronization methods, there is a volatile label / attribute. Razparalelit - everything is possible. The main thing is to synchronize after - otherwise it will turn out porridge. We will have to slow down the process of synchronization so that the situation doesn’t arise “suddenly there wasn’t at home, and then it turned out that he was there” - nick_n_a
  • Even shorter - razpareleli -> synchronized. Otherwise it will not work. - nick_n_a

1 answer 1

In general, this is solved by loading parts of the level at such a distance from the player that the download is faster than the player can get there. Those. Start loading so that when a player (and actions with physics and logic around him) come to the boundaries, everything will be loaded.

Also, you can add "security zones" along the borders of parts of the level to exclude entities from entering them that might be blocked. That is, A - do not set the routes of moving NPCs and houses on the borders of parts of the level so that they do not overlap each other when loaded. And B - if part of the level is still loading, do not let anything get into the vicinity of the border.

On Habré there was an article about how this was done in the World of Tanks, you can search.

  • Thank you, Kromster. And what could you advise on questions 1 and 2? - Bakuard
  • The advice is already contained in the answer - load everything then when nothing dynamic is happening in the loading area. - Kromster