views:

356

answers:

7

I'll be as direct as I can concerning this problem, because there must be something I'm totally missing coming from a structured programming background.

Say I have a Player class. This Player class does things like changing its position in a game world. I call this method warp() which takes a Position class instance as a parameter to modify the internal position of the Player. This makes total sense to me in OO terms because I'm asking the player "to do" something.

The issue comes when I need to do other things in addition to just modifying the players position. For example, say I need to send that warp event to other players in an online game. Should that code also be within Player's warp() method? If not, then I would imagine declaring some kind of secondary method within say the Server class like warpPlayer(player, position). Doing this seems to reduce everything a player does to itself as a series of getters and setters, or am I just wrong here? Is this something that's totally normal? I've read countless times that a class that exposes everything as a series of getters/setters indicates a pretty poor abstraction (being used as a data structure instead of a class).

The same problem comes when you need to persist data, saving it to a file. Since "saving" a player to a file is at a different level of abstraction than the Player class, does it make sense to have a save() method within the player class? If not, declaring it externally like savePlayer(player) means that the savePlayer method would need a way to get every piece of data it needs out of the Player class, which ends up exposing the entire private implementation of the class.

Because OOP is the design methodology most used today (I assume?), there's got to be something I'm missing concerning these issues. I've discussed it with my peers who also do light development, and they too have also had these exact same issues with OOP. Maybe it's just that structured programming background that keeps us from understanding the full benefits of OOP as something more than providing methods to set and get private data so that it's changed and retrieved from one place.

Thanks in advance, and hopefully I don't sound too much like an idiot. For those who really need to know the languages involved with this design, it's Java on the server side and ActionScript 3 on the client side.

A: 

I would probably consider having a Game object that keeps track of the player object. So you can do something like game.WarpPlayerTo(WarpLocations.Forest); If there are multiple players, maybe pass a player object or guid with it. I feel you can still keep it OO, and a game object would solve most of your issues I think.

Dested
It would be OO still, but it still seems to me at least that the player class just ends up being a huge series of getters and setters. Imagine attacking a player. I would have to have a method like player.getVitals().setHP(int) or player.getVitals().damageHP(int) and still need to have another secondary method that actually calculates damage based on an attacker to the victim. This ends up making the Player still a series of getters/setters. Or possibly do this: player.attack(attackerPlayer) which internally all calculations are done. In this case, no way to send update over network though.
suinswofi
A: 

The problems you are describing don't belong just to game design, but to software architecture in general. The common approach is to have a Dependency Injection (DI) and Inversion of Control (IoC) mechanisms. In short what you are trying to achieve is to be able to access a local Service of sorts from your objects, in order for example to propagate some event (e.g warp), log, etc.

Inversion of control means in short that instead of creating your objects directly, you tell some service to create them for you, that service in turn uses dependency injection to inform the objects about the services that they depend on.

Aviad P.
In this case, whether it was a service or not actually instantiating these objects (example: Player), the Player object would still need a reference to the service (assuming it isn't static) so that it can call upon those functions. Doesn't that then tightly couple the Player object to the service object? In that case warp() would be tightly coupled with the service.sendWarpEvent() method for example. Is this even acceptable OO practice?
suinswofi
By having your objects use an interface for a service, and instantiating the actual service object independently you achieve decoupling. Since you can replace the actual service implementation anytime. Of course, the interface needs to remain the same, but coupling is measured against implementations not against interfaces.
Aviad P.
A: 

If you are sharing data between different PCs for multiplayer, then a core function of the program is holding and synchronising that piece of state between the PCs. If you keep these values scattered about in many different classes, it will be difficult to synchronise.

In that case, I would advise that you design the data that needs to be synchronised between all the clients, and store that in a single class (e.g. GameState). This object will handle all the synchronisation between different PCs as well as allowing your local code to request changes to the data. It will then "drive" the game objects (Player, EnemyTank, etc) from its own state. [edit: the reason for this is that keeping this state as small as possible and transferring it efficiently between the clients will be a key part of your design. By keeping it all in one place it makes it much easier to do this, and encourages you to only put the absolute essentials in that class so that your comms don't become bloated with unnecessary data]

If you're not doing multiplayer, and you find that changing the player's position needs to update multiple objects (e.g. you want the camera to know that the player has moved so that it can follow him), then a good approach is to make the player responsible for its own position, but raise events/messages that other objects can subscribe/listen to in order to know when the player's position changes. So you move the player, and the camera gets a callback telling it that the player's position has been updated.

Another approach for this would be that the camera simply reads the player's position every frame in order to updaet itself - but this isn't as loosely coupled and flexible as using events.

Jason Williams
In your example, storing all game related data in a single class GameState, I would still need to define functions within that class to do all the process I would need on my objects like Player. This would still create duplicate functions and have all my classes end up being data structures. From what I can tell in your post, you're defining an API of functions from that one class to operate on all the data, correct?
suinswofi
Yes. You might still store the data in the individual objects, but whenever you want to move the Player, you would ask the GameState to update it, rather than asking the player directly. You can also enforce this usage by making the Player methods private, but friends of the GameState, so it is the only class that can change them.
Jason Williams
A: 

Sometimes the trick to OOP is understanding what is an object, and what is functionality of an object. I think its often pretty easy for us to conceptually latch onto objects like Player, Monster, Item, etc as the "objects" in the system and then we need to create objects like Environment, Transporter, etc to link those objects together and it can get out-of-control depending on how the concepts work together, and what we need to accomplish.

The really good engineers I have worked with in the past have had a way of seeing systems as collections of objects. Sometimes in one system they would be business objects (like item, invoice, etc) and sometimes they would be objects that encapsulated processing logic (DyeInjectionProcessor, PersistanceManager) which cut across several operations and "objects" in the system. In both cases the metaphors worked for that particular system and made the overall process easier to implement, describe, and maintain.

The real power of OOP is in making things easier to express and manage in large complex systems. These are the OOP principles to target, and not worry as much whether it fits a rigid object hierarchy.

I havent worked in game design, so perhaps this advice will not work as well, in the systems I do work on and develop it has been a very beneficial change to think of OOP in terms of simplification and encapsulation rather than 1 real world object to 1 OOP class.

GrayWizardx
I would relate this to "God" classes in Code Complete. This is pretty much the case I make for the latter solutions in my original post, which requires duplicate method definitions. Where basically the Player's warp() function is just one piece of the overall process, and the secondary method in the God class calling on the Player warp method, along with other methods to do things like send the warp update to other players and perhaps output a message to the player saying "you've been warped" or whatever. This still makes Player basically a class of getters and setters. :/
suinswofi
Actually player would just be "gameobject" with no concept of it being a player, a rock, or a monster, its just a game object. There is no real problem with a "bag" class in your design if thats its intended function. These are different than God classes, because they dont themselves relate to a higher order controlling class but rather a lower order plumbing class.
GrayWizardx
Having an inheritance hierarchy like GameObject, GamePlayer, GameNPC and etc still doesn't have anything to do with this issue. I use a hierarchy like this for my 2D graphics engine where OO practices have worked flawlessly. It's when I have "something" that needs to be done that ripples across several areas of abstraction that it becomes a problem.
suinswofi
A: 

I'd like to expand on GrayWizardx's last paragraph to say that not all objects need to have the same level of complexity. It may very well fit your design to have objects that are simple collections of get/set properties. On the other hand, it is important to remember that objects can represent tasks or collections of tasks rather than real-world entities.

For example, a player object might not be responsible for moving the player, but instead representing its position and current state. A PlayerMovement object might contain logic for changing a player's position on screen or within the game world.

Before I start simply repeating what's already been said, I'll point towards the SOLID principles of OOP design (Aviad P. already mentioned two of them). They might provide some high-level guidelines for creating a good object model for a game.

djacobson
Thanks for the comments. I think what you've said pretty much hits on exactly my problem. It seems to me that in some cases it's a juggle between encapsulation and coupling. In my examples above, having a lightly coupled Player object makes it a series of getters and setters breaking encapsulation imo. Or go with a nice encapsulated Player object that references other service objects or static references to do other things that need to be done, making the class tightly coupled with its service object/static referenced global methods.
suinswofi
+3  A: 

I advise you not to fear the fact, that player will be a class of getters and setters. What is object anyway? It's compilation of attributes and behaviours. In fact the more simple your classes are, the more benefits of an OOP you'll get in the development process.

I would breakdown your tasks/features into classes like that:

Player:

  • has hitpoints attribute
  • has position attribute
  • can walkTo(position), firing "walk" events
  • can healUp(hitpoints)
  • can takeDamage(hitpoints), firing "isHurt" event
  • can be checked for still living, like isAlive() method

Fighter extends Player (you should be able to cast Player to Fighter, when it's needed) :

  • has strength and other fighting params to calculate damage
  • can attack() firing "attack" event

World keeps track of all players:

  • listens to "walk" events (and prevents illegal movements)
  • listents to "isHurt" events (and checks if they are still alive)

Battle handles battles between two fighters:

  • constructor with two fighters as parameters (you only want to construct battle between players that are really fighting with each other)
  • listens to "attack" events from both players, calculates damage, and executes takeDamage method of the defending player

PlayerPersister extends AbstractPersister:

  • saves player's state in database
  • restores player's state from database

Of course, you game's breakdown will be much more complicated, but i hope this helps you to start thinking of problems in "more OOP" way :)

Anton N
Yeah, this is similar to what I'm doing right now. My Player class is just a bunch of gets/sets, and then I have another method in the Game class that has warpPlayer(player, newposition). It just doesn't really feel like that should be the best solution. I can't explain it.
suinswofi
No, in my example World class can't move Player at all. Player moves herself with the walk() method (you invoke walk() method not from the World, but from UI). And World can only prevent player from walking to illegal position (it's only for example, you can implement your own logic of how player's walking interacts with the world), by listening to Player's "walk" events.
Anton N
+1  A: 

Don't worry too much about the Player class being a bunch of setters and getters. The Player class is a model class, and model classes tend to be like that. It's important that your model classes are small and clean, because they will be reused all over the program.

I think you should use the warpPlayer(player, position) approach you suggested. It keeps the Player class clean. If you don't want to pass the player into a function, maybe you could have a PlayerController class that contains a Player object and a warp(Position p) method. That way you can add event posting to the controller, and keep it out of the model.

As for saving the player, I'd do it by making Player implement some sort of serialisation interface. The player class is responsible for serializing and unserializing itself, and some other class would be responsible for writing the serialised data to/from a file.

Tom Dalling
so as an example, there might be a method called serialize that returns a key value pair containing the state of the class which could then be restored using another method or constructor taking in the key value pair. My worry was having all this serialization stuff everywhere around the program, but didn't think about just having the serialization part in it and then have the actual output to a file/database being done elsewhere. Thanks for that tip.
suinswofi