I would like to use a Finite State Machine to handle Entity states in my game. Specifically, for the purpose of this post, I'm going to refer to the Player entity.
My Player is going to have states such as idling, running, jumping, falling, etc... and needs some way to manage these states and the transitions between them. In an OOP environment, the simplest solution was to make each state its own class, and have a method called handleInput take in input and determine if a state change should occur. For example, in the IdleState if either move_right or move_left occurred, the state would change to a new RunningState. This is easy and makes sense because the behavior of a state should be encapsulated in the state.
However, everything changes when you use a FSM in an entity component system. States are no longer objects (because that would go against the flexibility of a component system), but are instead different arrangements of components. The JumpState might have components like JumpComponent, AirbornMovementComponent, etc... whereas the AttackState might have components to represent an attack like SwingComponent, DamageComponent, SwordComponent, etc... The idea is by rearranging the components, new states can be created. The systems job is to simply handle these components separately, because the systems don't care about the state, they only care about the individual components. The actual FSM sits in a FSMComponent held by the entity.
This makes a lot of sense, except for when it comes to handling state transitions. Right now I have an InputSystem that looks for Entities that have an InputComponent and a FSMComponent and attempts to update the state of the FSM based on the current input. However, this doesn't work so well.
The best way (in my opinion) for the FSM to handle input is to have each state determine how it wants to handle input and how to transition to a new state based on that input. This goes back to the OOP way of implementing a FSM, going against the design of an ECS where components are simply data bags and systems do all the logic. In an ECS, the idea would be to have a system handle state transitions, but that gets complicated because every FSM might have different conditions to transition between states.
You can't simply state in the InputSystem "if the input is to move right, then set the state to running". That would be specific to the player, but may not hold true for ALL entities. If one day I decide to make an enemy controllable, the inputs that work for a Player wouldn't be the same inputs for an Enemy.
My question: How can I let my FSM be generic enough and flexible enough in an ECS to allow for various implementations of state transitions without having to do explicit if/else checks in the systems themselves?
Am I approaching this completely the wrong way? If so, what's a better solution for implementing a FSM in an entity component system?
Just riffing off of #fallaciousreasoning's post (and its subsequent comments).
Ash actually has two FSMs that operate at different levels.
First, there is the FSM operating at the Entity level that manages (entity) state transitions by changing the composition of components on an entity as you transition from one state to another.
And secondly there is the FSM operating at the Engine level that manages (engine) state transitions by changing the composition of Systems executed by the engine.
Combined they make for a pretty robust FSM.
The trick is to determine what type of transition you need to work with; one where 'data' composition drives transitions, or one where 'logic' composition drives transitions.
So armed with this new found knowledge lets reason about how this would work.
In more naive approaches to changing component composition, we'd use a number of 'tag' components, along with some lengthy switch statements (or if/else checks) to handle these changes in entity state, ultimately resulting in bloated Systems that do way more than they should. Ash's Entity FSM refines this and avoids those lengthy switch statements (or if/else clauses) by mapping a given component configuration to an identifier and providing a manager that can be used to trigger state transitions. This manager instance can be passed around as a property within a component OR it can be composed/injected as a member of a system.
Alternatively, taking the Engine FSM approach, we break out each bit of state specific logic into their own Systems, and swap them in and out based on the given (engine) state. This method is not without its drawbacks since the absence of a system will affect all entities associated with it. However, its not uncommon to have Systems dedicated to a single entity instance (for example, a player character) so this can prove useful in the right context. Come to think of it affecting entities globally via System swaps may be desirable in some cases as well.
( Note: If the scope of your logic modification needs to be more narrow, you can restrict it to the System without involving the Engine FMS. This can be achieved by implementing the State pattern within a System where the system can change its behavior by delegating to different sub-systems based on state.)
I've seen some ECS frameworks label System compositions as 'Phases', but they fundamentally act as FSMs where the ECS engine processes entities using different sets of systems associated with a given phase.
In closing, data composition is just one half of the equation; how you compose your bits (or blocks) of logic is just as important when trying to implement a FSM within an ECS.
The ash framework has quite an interesting approach, the author writes about it here
He takes a similar approach to the one you were suggesting, arguing that an entity's state is dependent on the components that make it up (which in turn, determine what systems process the entity).
To make managing the state transitions easier, he introduces a FiniteStateMachine class, which tracks what components are required for an entity to be in various states.
For example, an enemy AI might be in a patrol state if it has a Transform and Patrol component, so he would register that information with the FiniteStateMachine
var fsm = new FiniteStateMachine(enemyEntity);
fsm.CreateState("patrol")
.WithComponent(new Transform())
.WithComponent(new Patrol());
When the Finite State machine is told to change the state of the entity, it removes and adds components to get to the desired state. Any system that needs to change the state of the entity just needs a reference to the finite state machine (something like fsm.ChangeState("patrol")).
He's also released the source code for it, which you can find here (it also explains it better than I ever could) and there's a practical example in the basic asteroids game here. The code is in ActionScript (I think) but you should be able to decipher it without too much difficulty.
Related
Encapsulation lends itself to hierarchical "silos" or "trees" of objects, with a given application's major functionalities decomposed into core trunks, each further decomposed into sub-functionalities instantiated as sub-branch member objects of their respective branches.
As an example, suppose I'm programming a GUI in QT. To separate GUI elements from business logic, for every widget I have a class for the GUI elements, a class for the business logic associated with the GUI elements, and what I'll call a controller which serves as a container for both and exists primarily to pass signals/slots in between. I understand this pattern to be called dependency injection.
Let's suppose our application has 2 windows A and B.
Window A contains 2 independent widgets that have separate business functions i and ii, so we might have the structure A::i and A::ii, where :: is "contains an instance of" and not "extends".
Then i and ii both contain gui and business logic units, so we have A::i::business and A::i::gui.
Now, suppose A::i::business and A::ii::business want to pass information between each other, or engage in bidirectional communication with the model-view for my database, identified as MV. Generally speaking, what are my options for doing this?
What I've thought of so far:
1) Pass signals up and down the tree, i.e., the Verilog solution. This seems the most strictly object oriented, but most tedious.
2) Have a flatter architecture to ease the implementation of solution 1). This hurts encapsulation.
3) Make A::i::business and A::ii::business public all the way down, and have either the other object or a third-party shared class access A::i::business or A::ii:business directly. This hurts encapsulation.
4) Have a relatively unencapsulated object, like the database MV or some other form of "shared storage", exist in a public form near the top level of the program with few/ no super-containers. This seems most appealing, as the relatively encapsulated objects can stay encapsulated and communicate through unidirectional reading/ writing to something that's unencapsulated. However, if I want other objects to perform actions based on changes shared storage, some way of notifying the dependent objects while keeping them private is necessary. This might be called the "multi-threading inspired" or "multi-processing inspired" model of communication.
And any other suggestions you all may have.
I've read this post here, but at my level of understanding, the solutions in the accepted answer such as controller, listener and pub-sub, refer to general design patterns that don't seem to commit to a solution for the concrete problem of how to route signals and make decisions about public/ private accessibility of member classes. It may be the case that these solutions have associated conventions for where the communication objects go and how they access the different variables in either silo that I'm not familiar with.
Most generally speaking, I seem to be running into a general problem of communication across container-trees in well-encapsulated programming.
For future reference, is there a general term for this problem to aide future searching? Is my architectural approach of having the object-container structure directly reflecting the tree-decomposition of application functionality lending itself to too hierarchical a design, and would a different pattern of object containment and cross-branch communication be more optimal?
First of all: This is not an question of opinion (I'm afraid someone will flag this question), I'm interested in the technological background or the decisions for this.
That being said: Redux's store needs to be serializable. It is not allowed or frowned upon to use model classes and write their instances in the store. This is highly annoying to me. The applications logic ends up in actions or reducers where models would be a nice thing to have.
I am wondering why. What is the technological decision behind this? Why not write class instances to the store?
1) Class in instances are mutable, which leads to the same problems redux has tried to address around predictability of state by championing immutability. It also means you have to do more manual shouldComponentUpdate checks for changes if pairing with React (as references to mutated instances will be the same even if their internal state has changed)
2) Immutability makes it possible to move back and forth between states (eg during time travel debugging), something that cannot be done when mutations have occurred within instances
3) Serialisation means its very easy to persist and rehydrate the store (to/from JSON) for more advanced uses such as server side rendering and offline use
If you are interested I just wrote a more in depth answer to a similar question with an example of how it is possible to use the best of both worlds https://stackoverflow.com/a/47472724/7385246
I've noticed that my dependency injected, observer-pattern-heavy code (using Guava's EventBus) is often significantly more difficult to debug than code I've written in the past without these features. Particularly when trying to determine when and why observer code is being called.
Martin Oderski and friends wrote a lengthy paper with an especially alluring title, "Deprecating the Observer Pattern" and I have not yet made the time to read it.
I'd like to know what is so wrong with the observer pattern and so much better about the (proposed or other) alternatives to lead such bright people to write this paper.
As a start, I did find one (entertaining) critique of the paper here.
Quoting directly from the paper:
To illustrate the precise problems of the observer pattern,
we start with a simple and ubiquitous example: mouse dragging.
The following example traces the movements of the
mouse during a drag operation in a Path object and displays
it on the screen. To keep things simple, we use Scala closures
as observers.
var path: Path = null
val moveObserver = { (event: MouseEvent) =>
path.lineTo(event.position)
draw(path)
}
control.addMouseDownObserver { event =>
path = new Path(event.position)
control.addMouseMoveObserver(moveObserver)
}
control.addMouseUpObserver { event =>
control.removeMouseMoveObserver(moveObserver)
path.close()
draw(path)
}
The above example, and as we will argue the observer
pattern as defined in [25] in general, violates an impressive
line-up of important software engineering principles:
Side-effects Observers promote side-effects. Since observers
are stateless, we often need several of them to simulate
a state machine as in the drag example. We have to save
the state where it is accessible to all involved observers
such as in the variable path above.
Encapsulation As the state variable path escapes the scope
of the observers, the observer pattern breaks encapsulation.
Composability Multiple observers form a loose collection
of objects that deal with a single concern (or multiple,
see next point). Since multiple observers are installed at
different points at different times, we can’t, for instance,
easily dispose them altogether.
Separation of concerns The above observers not only trace
the mouse path but also call a drawing command, or
more generally, include two different concerns in the
same code location. It is often preferable to separate the
concerns of constructing the path and displaying it, e.g.,
as in the model-view-controller (MVC) [30] pattern.
Scalablity We could achieve a separation of concerns in our
example by creating a class for paths that itself publishes
events when the path changes. Unfortunately, there is no
guarantee for data consistency in the observer pattern.
Let us suppose we would create another event publishing
object that depends on changes in our original path, e.g.,
a rectangle that represents the bounds of our path. Also
consider an observer listening to changes in both the
path and its bounds in order to draw a framed path. This
observer would manually need to determine whether the
bounds are already updated and, if not, defer the drawing
operation. Otherwise the user could observe a frame on
the screen that has the wrong size (a glitch).
Uniformity Different methods to install different observers
decrease code uniformity.
Abstraction There is a low level of abstraction in the example.
It relies on a heavyweight interface of a control
class that provides more than just specific methods to install
mouse event observers. Therefore, we cannot abstract
over the precise event sources. For instance, we
could let the user abort a drag operation by hitting the escape
key or use a different pointer device such as a touch
screen or graphics tablet.
Resource management An observer’s life-time needs to be
managed by clients. Because of performance reasons,
we want to observe mouse move events only during a
drag operation. Therefore, we need to explicitly install
and uninstall the mouse move observer and we need to
remember the point of installation (control above).
Semantic distance Ultimately, the example is hard to understand
because the control flow is inverted which results
in too much boilerplate code that increases the semantic
distance between the programmers intention and
the actual code.
[25] E. Gamma, R. Helm, R. Johnson, and J. Vlissides. Design
patterns: elements of reusable object-oriented software.
Addison-Wesley Longman Publishing Co., Inc., Boston, MA,
USA, 1995. ISBN 0-201-63361-2.
I believe the Observer pattern has the standard drawbacks that come with decoupling things. The Subject gets decoupled from Observer but you cannot just look at its source code and find out who observes it. Hardcoded dependencies are usually easier to read and think about but they are harder to modify and reuse. It's a tradeoff.
As to the paper, it does not address the Observer pattern itself but a particular usage of it. In particular: multiple stateless Observer objects per single object being observed. This has the obvious drawback of the separate observers needing to synchronize with each other ("Since observers are stateless, we often need several of them to simulate
a state machine as in the drag example. We have to save
the state where it is accessible to all involved observers
such as in the variable path above.")
The above drawback is specific to this kind of usage, not to the Observer pattern itself. You could as well create a single (stateful!) observer object that implements all the OnThis, OnThat,OnWhatever methods and get rid of the problem of simulating a state machine across many stateless objects.
I will be brief because I'm new to the topic (and didn't read that specific article yet).
Observer Pattern is intuitively wrong: The Object to be observed knows who is observing (Subject<>--Observer).
That is against real-life (in event-based scenarios). If I scream, I have no idea who is listening; if a lightening, hits the floor... lightning doesn't know that there is a floor till it hits!. Only Observers know what they can observe.
When this kind of of things happen then software use to be a mess -because constructed against our way of thinking-.
It is as if and object knew what other objects can call his methods.
IMO a layer such as "Environment" is the one in charge of taking the events and notifying the affected. (OR mixes the event and the generator of that event)
Event-Source (Subject) generates events to the Environment. Environment delivers the event to the Observer. Observer could register to the kind of events that affects him or it is actually defined in the Environment. Both possibilities make sense (but I wanted to be brief).
In my understanding the Observer Pattern puts together environment & subject.
PS. hate to put in paragraphs abstract ideas! :P
For a 2D game I'm making (for Android) I'm using a component-based system where a GameObject holds several GameComponent objects. GameComponents can be things such as input components, rendering components, bullet emitting components, and so on. Currently, GameComponents have a reference to the object that owns them and can modify it, but the GameObject itself just has a list of components and it doesn't care what the components are as long as they can be updated when the object is updated.
Sometimes a component has some information which the GameObject needs to know. For example, for collision detection a GameObject registers itself with the collision detection subsystem to be notified when it collides with another object. The collision detection subsystem needs to know the object's bounding box. I store x and y in the object directly (because it is used by several components), but width and height are only known to the rendering component which holds the object's bitmap. I would like to have a method getBoundingBox or getWidth in the GameObject that gets that information. Or in general, I want to send some information from a component to the object. However, in my current design the GameObject doesn't know what specific components it has in the list.
I can think of several ways to solve this problem:
Instead of having a completely generic list of components, I can let the GameObject have specific field for some of the important components. For example, it can have a member variable called renderingComponent; whenever I need to get the width of the object I just use renderingComponent.getWidth(). This solution still allows for generic list of components but it treats some of them differently, and I'm afraid I'll end up having several exceptional fields as more components need to be queried. Some objects don't even have rendering components.
Have the required information as members of the GameObject but allow the components to update it. So an object has a width and a height which are 0 or -1 by default, but a rendering component can set them to the correct values in its update loop. This feels like a hack and I might end up pushing many things to the GameObject class for convenience even if not all objects need them.
Have components implement an interface that indicates what type of information they can be queried for. For example, a rendering component would implement the HasSize interface which includes methods such as getWidth and getHeight. When the GameObject needs the width, it loops over its components checking if they implement the HasSize interface (using the instanceof keyword in Java, or is in C#). This seems like a more generic solution, one disadvantage is that searching for the component might take some time (but then, most objects have 3 or 4 components only).
This question isn't about a specific problem. It comes up often in my design and I was wondering what's the best way to handle it. Performance is somewhat important since this is a game, but the number of components per object is generally small (the maximum is 8).
The short version
In a component based system for a game, what is the best way to pass information from the components to the object while keeping the design generic?
We get variations on this question three or four times a week on GameDev.net (where the gameobject is typically called an 'entity') and so far there's no consensus on the best approach. Several different approaches have been shown to be workable however so I wouldn't worry about it too much.
However, usually the problems regard communicating between components. Rarely do people worry about getting information from a component to the entity - if an entity knows what information it needs, then presumably it knows exactly what type of component it needs to access and which property or method it needs to call on that component to get the data. if you need to be reactive rather than active, then register callbacks or have an observer pattern set up with the components to let the entity know when something in the component has changed, and read the value at that point.
Completely generic components are largely useless: they need to provide some sort of known interface otherwise there's little point them existing. Otherwise you may as well just have a large associative array of untyped values and be done with it. In Java, Python, C#, and other slightly-higher-level languages than C++ you can use reflection to give you a more generic way of using specific subclasses without having to encode type and interface information into the components themselves.
As for communication:
Some people are making assumptions that an entity will always contain a known set of component types (where each instance is one of several possible subclasses) and therefore can just grab a direct reference to the other component and read/write via its public interface.
Some people are using publish/subscribe, signals/slots, etc., to create arbitrary connections between components. This seems a bit more flexible but ultimately you still need something with knowledge of these implicit dependencies. (And if this is known at compile time, why not just use the previous approach?)
Or, you can put all shared data in the entity itself and use that as a shared communication area (tenuously related to the blackboard system in AI) that each of the components can read and write to. This usually requires some robustness in the face of certain properties not existing when you expected them to. It also doesn't lend itself to parallelism, although I doubt that's a massive concern on a small embedded system...?
Finally, some people have systems where the entity doesn't exist at all. The components live within their subsystems and the only notion of an entity is an ID value in certain components - if a Rendering component (within the Rendering system) and a Player component (within the Players system) have the same ID, then you can assume the former handles the drawing of the latter. But there isn't any single object that aggregates either of those components.
Like others have said, there's no always right answer here. Different games will lend themselves towards different solutions. If you're building a big complex game with lots of different kinds of entities, a more decoupled generic architecture with some kind of abstract messaging between components may be worth the effort for the maintainability you get. For a simpler game with similar entities, it may make the most sense to just push all of that state up into GameObject.
For your specific scenario where you need to store the bounding box somewhere and only the collision component cares about it, I would:
Store it in the collision component itself.
Make the collision detection code work with the components directly.
So, instead of having the collision engine iterate through a collection of GameObjects to resolve the interaction, have it iterate directly through a collection of CollisionComponents. Once a collision has occurred, it will be up to the component to push that up to its parent GameObject.
This gives you a couple of benefits:
Leaves collision-specific state out of GameObject.
Spares you from iterating over GameObjects that don't have collision components. (If you have a lot of non-interactive objects like visual effects and decoration, this can save a decent number of cycles.)
Spares you from burning cycles walking between the object and its component. If you iterate through the objects then do getCollisionComponent() on each one, that pointer-following can cause a cache miss. Doing that for every frame for every object can burn a lot of CPU.
If you're interested I have more on this pattern here, although it looks like you already understand most of what's in that chapter.
Use an "event bus". (note that you probably can't use the code as is but it should give you the basic idea).
Basically, create a central resource where every object can register itself as a listener and say "If X happens, I want to know". When something happens in the game, the responsible object can simply send an event X to the event bus and all interesting parties will notice.
[EDIT] For a more detailed discussion, see message passing (thanks to snk_kid for pointing this out).
One approach is to initialize a container of components. Each component can provide a service and may also require services from other components. Depending on your programming language and environment you have to come up with a method for providing this information.
In its simplest form you have one-to-one connections between components, but you will also need one-to-many connections. E.g. the CollectionDetector will have a list of components implementing IBoundingBox.
During initialization the container will wire up connections between components, and during run-time there will be no additional cost.
This is close to you solution 3), expect the connections between components are wired only once and are not checked at every iteration of the game loop.
The Managed Extensibility Framework for .NET is a nice solution to this problem. I realize that you intend to develop on Android, but you may still get some inspiration from this framework.
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.
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 :)
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.
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.
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.
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.
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.
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.