2
\$\begingroup\$

Let's say you're making a tile-based roll-playing game, and you're obsessed with OOP.

You have an abstract base class Tile which will be inherited from by many derived classes, like Grass Sand and Water

The base class declares virtual methods for querying properties of derived classes, such as isCollidable() and isFlammable()

class Tile{
public:
 virtual constexpr bool isCollidable() = 0;
 virtual constexpr bool isFlammable() = 0;
};

class Grass:
public Tile{
public:
 constexpr bool isCollidable(){
  return false;
 }
 constexpr bool isFlammable(){
  return true;
 }
};

class Sand:
public Tile{
public:
 constexpr bool isCollidable(){
  return false;
 }
 constexpr bool isFlammable(){
  return false;
 }
};

class Water:
public Tile{
public:
 constexpr bool isCollidable(){
  return true;
 }
 constexpr bool isFlammable(){
  return false;
 }
};

When you want a new type of Tile you just create a new class, inherit from it, and implement it's virtual methods. Adversely, when you want a new property, you've to add is methods to all the classes.

My question is, what is the cost of using polymorphism in this way? Is this an inappropriate use of polymorphism? If so, What are some alternatives to this while maintaining nice, pretty OOP?

\$\endgroup\$
  • 1
    \$\begingroup\$ For extended discussion on this topic you may want to consider Game Development Chat, as it's a better format for it. \$\endgroup\$ – Josh Dec 11 '15 at 22:15
  • \$\begingroup\$ What is the cost literally? Every virtual keyword oversimplified means "create a lookup table for me"... plus, ofcourse, mess in your code in given example, but this aspect was already covered in answers. \$\endgroup\$ – wondra Dec 11 '15 at 22:49
5
\$\begingroup\$

No.

Grass, Sand and Water aren't different TYPES of tiles, they are different tiles.

i.e.

class Tile
{
private:
  Sprite sprite;
  bool collidable;
  bool flamable;
  bool walkable;
public:

 Tile(Sprite s, bool col, bool flam, bool walk)
 {
   this.sprite = s;
   this.collidable = col;
   this.flamable = flam;
   this.walkable = walk;
 }

 bool isCollidable()
 {
   return this.collidable;
 }


 bool isFlamable()
 {
   return this.flamable;
 }


 bool isWalkable()
 {
   return this.walkable;
 }
}

grass = Tile(Sprite(...),false,true,true);
sand = Tile(Sprite(...),false,true,true);
water = Tile(Sprite(...),false,false,false);

In general you only want to derive a class if the type adds some functionality. Since all the tiles you describe have the same attributes, they are instances not derived objects. Like a tile that can be walkable sometimes, but not others would be a reason to derive a new class.

I added sprite to represent the things I didn't add to the class, but a tile having a sprite isn't what I'd really do. Intead, Tile would have:

  • Static image for tiles the sprite sheet.
  • Static constant length and width of a tile.
  • tile_x and tile_y for where on the sprite sheet to find it
  • x and y of where this tile is placed.

Having a "sprite" was just a way in the above example to make all that easier.

\$\endgroup\$
  • \$\begingroup\$ Good point about Grass, Sand, and Water only being instances of Tile. I'd probably want methods like createGrass() to confine what kinds of tiles can be constructed. Is that what the Factory Pattern does? \$\endgroup\$ – Willy Goat Dec 11 '15 at 21:39
  • \$\begingroup\$ For factory pattern, you'd make the constructore, and use a static member to return new instances. Typically you'd keep track of them in a static container (say vector) of pointers to the created objects. The destructor would have to remove the pointer. You make these createX functions all part of the Tile class. That said, what do think you'll gain by using the factory pattern here? \$\endgroup\$ – J. A. Streich Dec 11 '15 at 21:44
  • \$\begingroup\$ I'm always curious about better ways to implement game-objects. I think in this case, static createX() methods and a static container would be nice, because what's a tile without a tile-map, and when it comes time to draw tiles, you want to have defined tile-kinds. \$\endgroup\$ – Willy Goat Dec 11 '15 at 21:56
  • \$\begingroup\$ Can you re-word first paragraphs of your answer? Without reading the rest of the answer they are a bit confusing (i.e. "no" to what? They are types as for all one read before in question, you probably meant they shouldnt be types because...) \$\endgroup\$ – wondra Dec 11 '15 at 22:58
  • 1
    \$\begingroup\$ I'm not sure whar you actually expext from grass, sand or water, but they could possibly be perfectly modeled by a single integer. Information about properties of any tile id could stored in a separate data structure. Don't write any static factory functions, store a two dimensional array of tile ids in your level file. \$\endgroup\$ – cubuspl42 Dec 12 '15 at 11:12
5
\$\begingroup\$

obsessed with OOP

What you've described isn't OOP. It's an abuse of inheritance, which is frequently attributed to OOP, but isn't an inherent part of the paradigm. :)

My question is, what is the cost of using polymorphism in this way?

Virtual function calls are slower than regular calls, and much slower than directly accessing data members, but are not going to be the bottleneck in your game; I'd be surprised if you could even find any of your Tile virtual methods in a profiler.

If the design appropriately calls for virtual methods, don't shy away from them for performance reasons. That said, I don't think your design calls for them.

Is this an inappropriate use of polymorphism?

It is a misuse of polymorphism and a misuse/misunderstanding of OOP.

If so, What are some alternatives to this while maintaining nice, pretty OOP?

J. A. Streich's answer already covered a far better approach: data-driven design.

To expand upon his answer, I would say that you should only use a class when you are modeling something different from the computer's perspective. The computer does not care in the least about grass vs dirt vs stone. All it cares about are the operations it can perform with a tile. Since the operations that can be performed on a Tile are universal, there should be a single class.

Polymorphism comes into play when the implementation of the operations can vary. Querying a boolean as to whether a tile is flammable is not a different implementation: it's just the querying of a bool for all tile types. Tiles are thus a bad fit for polymorphism.

Good design - even good OOP design! - has a lot fewer uses for polymorphism than you might think. Most of them revolve around high-level abstract concepts; e.g. you might have an abstract FileSerializer type with polymorphic implementations XmlFileSerializer and BinaryFileSerializer. These are cases where the computer must do some low-level operation wildly different ("write XML strings" vs "write raw bytes") for the same high-level operation (e.g. "save player state").

Your types (polymorphic or otherwise) should not be modeling real-world nouns and taxonomies.

A class for an abstract ActorEntity is fine, but a class for ToughGoblin is absolutely wrong, because the computations for enemies are identical between a tough goblin, a weak goblin, or an ogre; the only differences are the inputs to the calculation (how much damage they deal, which image to draw, etc.).

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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