-1

i'm trying to design a game program, but when thinking about which objects to introduce there seems to be far too many.

For example for main object "Player" there are a lot of peripheral classes that should exist for e.g:

  • PlayerRenderer - renders the player
  • PlayerMovementHandler - moves the player
  • So on and so forth...

How do I "filter" which classes should actually exist? Is there a general rule?

Thanks

8
  • 1
    What's giving you the feeling that PlayerRenderer and PlayerMovementHandler shouldn't exist?
    – Alexander
    Jun 14, 2021 at 14:48
  • 1
    @Alexander The problem is that there would be an infinite number of classes "responsible" for things with this design. Why stop at PlayerRenderer and PlayerMovementHandler? you could have (i know its silly but in principle) thousands of classes. This is called an plato.stanford.edu/entries/infinite-regress Jun 14, 2021 at 20:07
  • 1
    I've been there ("if everything can be boiled down to being called a responsibility, and each class should only have one responsibility, then you get can infinite regress of classes"), but the counter argument is "you stop the regress by merging things were it makes sense". The a player isn't single-faceted. There's a player as understood by a company's billing department, a different understanding of a player as seen by the cheat-detection team, yet another understanding of a player as seen by the higher score system, and so on
    – Alexander
    Jun 14, 2021 at 20:37
  • There's no utility to be gained from merging a PlayerRenderer (which presumably is responsible for some graphics stuff) and PlayerMovementHandler (which might be responsible for detecting triggers in the game world that should cause certain events/dialogue/etc.). And there's lots of utility to be gained from splitting them, and thus, they should be split.
    – Alexander
    Jun 14, 2021 at 20:38
  • I would offer a possibly controversial question of why a player should even be an object in the first place as opposed to just data, and not just data for a player, but any renderable entity in the world which an EverythingRenderer object can understand and render. I don't think my thoughts are controversial in gamedev these days. I've seen the industry move more and more towards data-oriented design and looking at game engines as data transformers rather than a series of abstract objects.
    – user379844
    Nov 11, 2021 at 16:35

3 Answers 3

5

My recommended methodology to find the "right number of classes" is the YAGNI principle, as explained in this former answer from 2019:

  • Make a decision about the requirements you want to code in your next milestone. Don't put more requirements on the list than you expect to implement in a cycle of a few days, two weeks at maximum.

  • Implement as many functions, classes and structure as needed for this milestone (but not more).

  • Whenever the code starts to become convoluted, hard to test, or hard to extend, refactor to smaller functions or classes, but only to the point where the issue is solved. Note this refactoring may sometimes take place at the beginning of a milestone, to make it easier to implement new requirements.

  • Don't generalize anything "just in case". Generalize when there is a real scenario in your code where this generalization helps to reuse something, or where it helps to keep the code DRY.

That way, your own restricted development speed will work as a "filter" how many classes exist, and all classes created will have a clear justification why they are required.

Said that, there is nothing inherently wrong in sometimes envisioning a potential class design structure in some kind of UML diagram before implementing the classes in code, to get a better idea about your intended layering or architecture. I would just recommend against taking such a vision unreflected as a "blueprint". Don't create empty hulls for those classes in code, detached from the actual requirements of your next milestone.

4

Let's say you had just one class: Player, responsible for handling everything related to the player, such as knowing how to be displayed on screen, knowing how to respond to user input, checking collision with other game objects, and so forth...

Furthermore, let's say you took it to the extrem and had only one class Game, responsible for handling everything related to the game: handling events, displaying data on screen, keeping game state...

If you were to code this class, it would probably be huge, hard to maintain and to understand.

One of the main ideas of OOP is to isolate diferent responsibilities into different clases.

So now, your question

How do I "filter" which classes should actually exist? Is there a general rule?

should probably be:

How do I know which responsibilities should or should not be grouped in a single class?

And, for me, the answer to that is it depends:

  • On how related those responsibilites are.
  • On how complex the responsibilities are.
  • On whether some of the logic could be reused by some other different and unrelated classes.

So, to answer your question, if you just want to quickly write a few lines of code and learn a few things on game design, you may not want to separate responsibilities too much (most likely because the responsibilities will be kind of simple). Then, as you start adding logic to it, you will probably need to start separating responsibilities into classes, and start abstracting common logic.

3

Start from the simple, complicate things when you must.

Very simply put, avoid any level of complication/abstraction unless it actually solves more problems than it creates.

In order to cut down on fluff, but also ensure that you don't cut corners either, it helps to err on the side of simplicity, and elaborate as you see fit.

For example, first develop a Player class that handles all of the player logic: movement, rendering, stats, ... As you go, re-evaluate that decision.

  • Maybe you now have an enemy class and want to reuse the movement logic
  • Maybe you now have a level which also needs to be rendered
  • Maybe all the player logic is too much to keep organised in a single class

Whatever the reason, identify the missing abstraction, then implement it. Respectively:

  • Create a generalized MovementHandler which can service any Player or Enemy.
  • Create a more generalized rendering logic, which is then implemented by all the things that need to be rendered.
  • Start subdividing your Player class into (composition!) subclasses such as PlayerMovement, PlayerStats, ...

This way, you don't overshoot from your first version, and you only build the things that you need.

This is a repeating pattern in development:

  • Don't prematurely optimize. Just make something that works, and deal with performance issues if and when (!) they occur in the future.
  • Don't abstract things that do not need abstraction yet - unless you are adamant that the abstract will be needed down the line (e.g. because the requirements are already known, just not implemented yet)
  • Don't build things that are not part of the requirements.
    • Caveat: unless that thing helps you get to your requirements quicker (i.e. something that assists the developer without necessarily making it into the final product). A great example here is debug logging or automated testing.
  • By waiting for multiple implementations to warrant a shared abstraction, you ensure that you have good examples of precisely what these multiple implementations should and shouldn't (!) share. If you build your abstraction based on a single example, you are liable to make the wrong abstraction because it's unclear which parts will be reused and which will not.

Don't try to get it right the first time.

You won't. Instead, focus on clean coding practices, because what they do is enable you to minimize the effort/impact of having to make a change further down the line.

Having to make a change is practically inevitable, and actually a good thing (you didn't overbuild or violate YAGNI prematurely), and clean coding makes sure that your life doesn't become a living hell when you need to reshuffle the implementation in an established codebase.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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