You are looking for an alternative to dependency injection. I'll offer you a perspective shift: The game world is external. You are just sending and receiving messages. It is not a great idea to have a dependency on something external that you can't control.
Wait. We are making games. And a game is a piece of software, it isn't a portal to another world. Or is it? (cue Jake Chudnow - Moon Men (instrumental), extended version to make sure it lasts while you read).
Hey Gamedev, Thereot here. And how do you handle things external to your game? For example, game input.
You need to access your
input from your update code. And we want that code well delimited. Remember, don't mix concerns or responsibilities.
However, we have this problem of passing things around. We could have singletons, or do dependency injection. I have here a different solution: The
input service will send messages.
That is, the game objects will have a sort of "inbox". A notification mechanism. For example, the service calls a method on them, or sets a property.
Since not every game object needs input, the service could have list of game objects that should receive the messages (call them observers, listeners, subscribers,
This approach can be applied for any services that deals with an external (or a let-us-pretend-external) input device. For example, we can use to handle actual messages from the
network, or to handle collisions in our
But how would you handle output?
There are some services that that do output (
audio), and you need to tell them what to do. For example, you tell to the
physics service to apply a force or a velocity.
However, we have this problem of passing things around, again. Well, I have another box for you: an "outbox". The game objects could publish messages for the services to pick up, where they describe what they want the service to do.
Those services already have a list of game objects that need to interact with them. So they could go over the list, and pick messages from the "outbox" of each game object.
We have a name for a message that describe what to do. We call it a Command.
I need to access terrain data to do a lot of actions, based on the object's current position. How do I store and access this data?
There are a lot of spatially aware logic that makes sense to handle as external to the core logic of the game. For example, you may need to find nearby game objects, or find a path to navigate the world. There are also collisions, and so on. And you would find a good idea to have dedicated data structures to handle that (e.g. grids, quad-trees, etc).
Perhaps for you, terrain is physics. For example, in a 3D game we could have a raycast downwards to check if the player avatar is on the ground, and what kind of ground. Or perhaps for you terrain is its own thing. For example, in a 2D game, you probably have a tile-map (or perhaps a texture) that you can query with some coordinates to get terrain information. Or it is a voxel game, and you just query the voxel.
When there is a collision (including the raycast contacting a different object), or when a game object moves to another tile, you can send a message.
What is the best way to solve this problem?
I don't know about best. But, the command pattern is a good counterpart for dependency injection.
Let us say you have a class that has a dependency. Your class calls a method on the dependency, and gets a return value. Ok, we are going to flip that around. Instead of calling a method we publish a command. Some time later, the "dependency" takes the published command. When it has a result, it calls a method in our class (or perhaps the command object includes a callback), which is the opposite of what we had before. Somehow we had your class call the other, now the other calls yours. The return value became a parameter. We left synchronous and arrived at asynchronous. And we left coupled and arrived at decoupled.
In fact, there could be one object taking the commands, or there could be multiple, or none.
This idea is a way to bring back the concept of passing messages that we had at the origins of object oriented programming.
Let us be honest, this is not free. You would be instantiating messages. If they are reference types, you would be making garbage, unless you pool them. As per dependencies, we have moved from A depends on B, to A and B depend on M, where M is the message type.
Of course, that is not the only way. I'll mention that you can use an actor model. An actor has in and out channels on which they do push and pull operations to communicate with other parts of the software (cough queues cough). In this case if you want something to happen in a different part of your system, push a message to the appropriate channel, on the other side an object can pull it some time later. "Queues decouple spacetime." -- Kain0_0 (source).
Make the above thread-safe, and parallelism becomes easier.
Since, Entity-Component-System has popularity in game development, I'll also mention that if you are doing an ECS sending a message could mean attaching a component to the entity for some another system to read it.
By the way, if you want, you can use an actual external device for communication. For example, one part writes to a file, and the other reads from the file. Hopefully a memory mapped file. Or it could be a network connection (and then you can start thinking about how to do a distributed system).
Remember that you can think about your architecture in terms of internal and external things, and passing messages between them. Then worry about the mechanism for that communication. Plus, often you can flip those around.