1
\$\begingroup\$

I've been trying to make games for a while now, but even when making a very simple one, I've always run into a problem - Because different subclasses have different functionality, therefore needing different requirements, I can't properly use OOP concepts in my code.

For example, I have a Projectile class in my code, which serves as a superclass for a Bullet class and Missile class. All of my projectiles are stored in a List, and I would like to be able to loop through the List and update all projectiles. However, I would like the Missiles class to follow the player, which means I need to supply the player's position to update it.

I could also store a pointer to the player in the Missile class, but this quickly becomes messy, and since the game is very simple, I wouldn't want to use ECS's. What solutions are available for this?

\$\endgroup\$

2 Answers 2

3
\$\begingroup\$

Fundamentally, if you have implementations of any interface (Projectile in this case) that need data that isn't available via that interface to operate then your solutions are:

  1. Modify the interface such that it can supply the data.
  2. Give the data directly to the subclass implementation.
  3. Accept that perhaps some of the implementations are can longer correctly be considered implementors of that interface, and treat them separately.

The first option tends to scale poorly and lead to very poor OO designs, because over time the interface or base class becomes bloated with various pieces of data that are only actually relevant to a subset of implementations or subclasses.

The second option is generally preferable (you could argue that the third is as well, but that often involves a more significant set of changes). Alexandre's answer provides possible implementations of both, but you yourself provided (what I think is) a better one:

I could also store a pointer to the player in the Missile class,

I'd argue this is better from an OO design perspective (because it doesn't push subtype-specific behavior up into a base type), and also better from a more abstract design perspective (because it doesn't require reliance on global state).

You could generalize this to storing a reference to some kind of HasPosition type of interface, so your missiles can track anything.

You note that

this quickly becomes messy

but I'd challenge that argument, on the basis of the above points. Also on the basis that where I can see some eventual complexity here, since you describe your game as "simple" I'm not convinced it would come up.

I will concede that you've traded a type design problem for a lifetime design problem in going this route, however: now you have to either ensure that the target always outlives the missile (not practical in most game contexts) or you have a dangling reference.

However you can address that by providing the reference via something other than a vanilla, language-level pointer or reference. Using a handle or similar "weak" reference that allows you to determine if the referred-to thing is still alive and valid allows you to deal with the lifetime problem by first checking for validity in the missile's Update function. If the target reference is still valid, track it. Otherwise let the missile continue along whatever its previous trajectory was, or maybe try to acquire a new target.

\$\endgroup\$
2
\$\begingroup\$

You don't necessarily need to supply the position of the player to the Missile.

You can make the position of the player available to everyone, and let your game objects access it.

For instance (pseudo-code):

GameManagerSingleton
  static getInstance()

  getPlayer()


Missile : Projectile
  update():
    player = GameManagerSingleton.getInstance().getPlayer()

    position = bla bla bla player.getPosition()

Yes, a singleton. It will work, but this, too, can get messy and grow out of control.

You could pass the player object to the Update function, without keeping a reference to it (pseudo-code):

Projectile
  update( PlayerRef )

Missile : Projectile
  update( PlayerRef ):
    position = bla bla bla PlayerRef.getPosition()

Of course, your Bullet does not care, but that's ok. Note that this is a slippery slope, though, as new subclasses need new objects, you'll have more and more stuff to pass through the Update function parameters.

You could also go hybrid, and pass your game manager instance to your update function, so that it goes get whatever it needs :)

In my experience, though, I kind of have concluded that inheritance can be a pain to work with - nothing is really generic and reusable. And I would suggest you find a way to plug in a component based architecture into it because it is a small project. That could be a great practice run for your next project.

\$\endgroup\$
2
  • \$\begingroup\$ Thanks so much for this detailed answer! I'm going to wait a while to see if there are any other answers first before accepting this one. I always thought that components were for bigger projects, but it seems to help anywhere! \$\endgroup\$
    – Sarah
    Commented May 11, 2019 at 5:38
  • \$\begingroup\$ Yeah, I agree there can be some boiler plate code, but one can see some advantages in making the rest of the project more simple. You can also avoid writing the boilerplate code by using an already existing library/framework. \$\endgroup\$
    – Vaillancourt
    Commented May 11, 2019 at 10:13

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .