10

I hope these ramblings will make my question clear — I'd totally understand if they wont, though, so let me know if that's the case, and I'll try making myself clearer.

Meet BoxPong, a very simple game I made to get acquainted with object-oriented game development. Drag the box to control the ball and collect yellow things.
Making BoxPong helped me formulate, among other things, a fundamental question: how can I have objects that interact with each other without having to "belong" to each other? In other words, is there a way for objects to not be hierarchical, but instead coexist? (I'll go into further detail below.)

I suspect the problem of objects coexisting is a common one, so I hope there's an established way to solve it. I don't want to reinvent the square wheel, so I guess the ideal answer I'm looking for is "here's a design pattern that's commonly used to solve your kind of problem."

Especially in simple games like BoxPong, it's clear that there are, or should be, a handful of objects coexisting on the same level. There's a box, there's a ball, there's a collectible. All I can express in object-oriented languages, though — or so it seems — are strict HAS-A relationships. That's done through member variables. I can't just start off the ball and let it do its thing, I need it to permanently belong to another object. I've set it up so that the main game object has a box, and the box in turn has a ball, and has a score counter. Each object also has an update() method, which calculates position, direction etc., and I go a similar way there: I call the main game object's update method, which calls the update methods of all its children, and they in turn call the update methods of all their children. This is the only way I can see to make an object-oriented game, but I feel it's not the ideal way. After all, I wouldn't exactly think of the ball as belonging to the box, but rather as being on the same level and interacting with it. I suppose that can be achieved by turning all game objects into member variables of the main game object, but I don't see that solving anything. I mean... leaving aside the obvious clutter, how would there be a way for the ball and the box to know each other, that is, to interact?

There's also the issue of objects needing to pass information between each other. I have quite a bit of experience writing code for the SNES, where you have access to practically the entire RAM all the time. Say you're making a custom enemy for Super Mario World, and you want it to remove all of Mario's coins, then just store zero to address $0DBF, no problem. There's no limitations saying enemies can't access the player's status. I guess I've been spoiled by this freedom, because with C++ and the like I often find myself wondering how to make a value accessible to some other objects (or even global).
Using the example of BoxPong, what if I wanted the ball to bounce off the edges of the screen? width and height are properties of the Game class, and I would need the ball to have access to them. I could pass these kinds of values on (either through constructors or the methods where they're needed), but that just screams bad practice to me.

I guess my main problem is that I need objects to know each other, but the only way I can see to do that is strict hierarchy, which is ugly and impractical.

I've heard of "friend classes" on C++ and kinda know how they work, but if they're the end-all solution, then how come I don't see friend keywords poured all over every single C++ project, and how come the concept doesn't exist in every OOP language? (The same goes for function pointers, which I've just recently learned of.)

Thanks in advance for answers of any kind — and again, if there's a part that doesn't make sense to you, do let me know.

2
  • 2
    Much of the game industry has moved towards the Entity-Component-System architecture, and its variations. It is a different mindset from traditional OO approaches but it works well and does make sense once the concept sinks in. Unity uses it. Actually, Unity just uses the Entity-Component part but is based off of ECS.
    – Dunk
    Commented May 26, 2015 at 19:21
  • The problem of allowing classes to collaborate with one another without knowledge of each other is solved by the Mediator design pattern. Have you looked at it? Commented May 26, 2015 at 23:19

4 Answers 4

14

In general, it turns out very badly if objects of the same level know about each other. Once objects know about each other they are tied, or coupled to each other. This makes them hard to change, hard to test, hard to maintain.

It works out much better if there is some object "above" that knows about the two and can set the interactions between them. The object that knows about the two peers can tie them together via dependency injection or via events or via message passing (or any other of decoupling mechanisms). Yes, that leads to a bit of an artificial hierarchy, but it's far better than the spaghetti mess that you get when things just interact willy-nilly. That is only more important in C++, since you need something to own the lifetime of the objects too.

So in short, you can do it by just having objects side by side everywhere tied together by ad hoc access, but it's a bad idea. The hierarchy provides order and clear ownership. The main thing to remember is that objects in code are not necessarily objects in real life (or even the game). If the objects in the game don't make a good hierarchy, a different abstraction may be better.

3

Using the example of BoxPong, what if I wanted the ball to bounce off the edges of the screen? width and height are properties of the Game class, and I would need the ball to have access to them.

No!

I think the main issue you are having, is you are taking "Object Oriented Programming" a little too literally. In OOP, an object does not represent a "thing" but an "idea" that means that a "Ball", "Game", "Physics", "Math", "Date", etc. All are valid objects. There is also no requirement for objects to "know" about anything. For example, Date.Now().getTommorrow() Would ask the computer what day today is, apply arcane date rules to figure out tomorrow's date, and return that to the caller. The Date object does not know about anything else, it only needs to request information as needed from the system. Also, Math.SquareRoot(number) doesn't need to know anything besides the logic of how to calculate a square root.

So in your example I quoted, the "Ball" should not know anything about the "Box". Boxes and Balls are completely different ideas, and have no right to talk to each other. But a Physics engine does know what a Box and Ball is (or at least, ThreeDShape), and it knows where they are, and what should be happening to them. So if the ball shrinks because it is cold, the Physics Engine would tell that ball instance that it is smaller now.

It's kinda like building a car. A computer chip knows nothing about a car engine, but a car can use a computer chip to control an engine. The simple idea of using small, simple things together to create a slightly bigger, more complex thing, that is itself reusable as a component of other more complex parts.

And in your Mario example, what if you are in a challenge room where touching an enemy doesn't drain Marios coins, but just ejects him from that room? It is outside the idea space of Mario or the enemy that Mario should lose coins when touching an enemy (in fact, if Mario has an invulnerability star, He kills the enemy instead). So whatever object (domain/idea) that is responsible for what happens when mario touches an enemy is the only one that needs to know about either, and should do whatever it to either of them (to the extent that that object allows external driven changes).

Also, with your statements about objects calling children Update(), that is extremely bug prone as what if Update is called multiple times per frame from different parents? (even if you catch this, that is wasted CPU time that can slow your game) Everyone should only touch what they need to, when they need to. If you are using Update(), you should be using some form of subscription pattern to make sure all Update are called once per frame (if this is not handled for you like in Unity)

Learning how to define your domain ideas into clear, isolated, well defined, and easy to use blocks will be the biggest factor in how well you can utilize OOP.

1

Meet BoxPong, a very simple game I made to get acquainted with object-oriented game development.

Making BoxPong helped me formulate, among other things, a fundamental question: how can I have objects that interact with each other without having to "belong" to each other?

I have quite a bit of experience writing code for the SNES, where you have access to practically the entire RAM all the time. Say you're making a custom enemy for Super Mario World, and you want it to remove all of Mario's coins, then just store zero to address $0DBF, no problem.

You appear to be missing the point of object-oriented programming.

Object Oriented Programming is about managing dependencies by selectively inverting certain key dependencies in your architecture so that you can prevent rigidity, fragility, and non-reusability.

What is dependency? Dependency is reliance on something else. When you store zero to address $0DBF, you are relying on the fact that that address is where Mario's coins are located and that the coins are represented as an integer. Your Custom Enemy code is reliant on the code implementing Mario and his coins. If you make a change to where Mario stores his coins in memory, you must manually update all the code referencing the memory location.

Object oriented code is all about making your code depend upon abstractions and not on details. So instead of

class Mario
{
    public:
        int coins;
}

you would write

class Mario
{
    public:
        void LoseCoins();

    private:
        int coins;
}

Now, if you want to change how Mario stores his coins from an int to a long or a double or store it on the network or store it in a database or kick off some other long process, you make the change in one place: the Mario class, and all your other code keeps working with no changes.

Therefore, when you ask

how can I have objects that interact with each other without having to "belong" to each other?

you are really asking:

how can I have code that directly depends on each other without any abstractions?

which is not object oriented programming.

I suggest you begin by reading everything here: http://objectmentor.com/omSolutions/oops_what.html and then search youtube for everything by Robert Martin and watch all of it.

My answers derive from him, and some of it is directly quoted from him.

7
  • Thanks for the answer (and the page you linked to; looks interesting). I actually know about abstraction and reusability, but I guess I didn't put that across very well in my answer. However, from the example code you provided I can better illustrate my point now! You're basically saying that the enemy object should not do mario.coins = 0;, but mario.loseCoins();, which is fine and true - but my point is, how can the enemy have access to the mario object anyway? mario being a member variable of enemy doesn't seem right to me.
    – vvye
    Commented May 26, 2015 at 21:02
  • Well the simple answer is to pass Mario in as an argument to a function in Enemy. You might have a function like marioNearby() or attackMario() which would take a Mario as an argument. So then whenever the logic behind when an Enemy and a Mario should interact is triggerd, you would call enemy.marioNearby(mario) which would call mario.loseCoins(); Later down the road you may decide that there is a class of enemies who cause mario to lose only one coin or even gain coins. You now have one place to make that change which doesn't cause side effect changes to other code. Commented May 26, 2015 at 21:46
  • By passing Mario to an enemy, you have just coupled them. Mario and Enemy should have no knowledge that the other is even a thing. This is why we create higher order objects to mange how to couple the simple objects together.
    – Tezra
    Commented Jan 25, 2019 at 20:36
  • @Tezra But then, aren’t these higher order objects not reusable at all ? It feels like these objects act as functions, they only exist to be the procedure they exhibit. Commented Jan 25, 2019 at 23:12
  • @SteveChamaillard Every program will have at least a bit of specific logic that makes no sense in any other program, but the idea is to keep this logic isolated to a few high order classes. If you have a mario, enemy, and level class, you can reuse mario and enemy in other games. If you tie enemy and mario directly to each other, than any game that needs one has to pull in the other as well.
    – Tezra
    Commented Jan 26, 2019 at 15:08
0

You can enable loose coupling by applying the mediator pattern. Implementing it requires you to have a reference to a mediator component which knows all the receiving components.

It realizes the "game master, please let this and that happen" kind of thought.

A generalized pattern is the publish-subscribe pattern. It's appropriate if the mediator should not contain much logic. Otherwise use a hand crafted mediator which knows where to route all the calls and perhaps even modify them.

There are synchronous and asynchronous variants usually named event bus, message bus or message queue. Look them up to determine wether they are appropriate in your particular case.

Not the answer you're looking for? Browse other questions tagged or ask your own question.