Sign in to follow this  

Using a middle layer beetween logic and input?

This topic is 3293 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I'm currently redesigning the code of my rpg project. My plan is to have 5 major classes: Main, Logic, World, Input and Draw. Main is the game application containing the main loop. World contains all the data representing tha game world (the map, characters, items, etc.) and logic contains the code which does something with that data. Input checks for user input and Draw does the drawing. Here's the diagram (arrows represent flow of data and communication:) Class diagram My first question is, should I use the middle layer I've proposed or not. If I do not use it, Main would have to know about Logic and Draw (for setting up the game and for calling the drawing function) and Input would need to know about Logic (to call its functions when the corresponding user input happens.) When using the middle layer, Main and Input would only need to know about the middle layer class. So perhaps using it would be better object-oriented design. But it does add some more complexity too. When using the middle layer, Input would not call Logic's functions directly. Instead, it would send an event message ("user pressed key x") to the middle layer. After that, I have two options. Either 1. the middle layer knows about Logic and its functions, and calls them accordingly when receiving input events. Or, 2. the middle layer does not know about Logic, but Logic knows about the middle layer: during initialization, Logic registers its functions in the middle layer (by assining its functions to the middle layer's function pointers), so that when the middle layer receives the message "user pressed key x", it calls a function through a function pointer Keypress supplying x as an argument. Option 2 would perhaps be good because using it I could change Logic to something else without having to change anything in the middle layer (as the latter knows nothing about the former.) Which option do you think would be better? I could take this "event-driven" approach using the middle layer even further and implement drawing to screen by making Main send a message called "draw" to the middle layer, which then calls a drawing function in Draw through a function pointer. Main could also give "CPU time" to Logic and Input (for updating the game world and getting input from the keyboard) by sending "DoLogic" and "GetInput" messages to the middle layer, which then calls the functions getInput() in Input and updateGame() in Logic. Another possibility for implementing the communication, which does not use function pointers, is to have a "message buffer" in the middle layer, which Logic, Draw and Main would then iterate through and check if there's something for them there. (The messages could be implemented as integers or strings for example.) However, I think this approach would be slower than using function pointers. I'd like to know what more experienced object-oriented programmers think about the design. Should I use the middle layer or not? How should I implement the communication beetween input and logic? Do you think that using function pointers as I described is a good idea? I know that I'm probably overdoing object-oriented design somewhat (as my game isn't really that big yet, and I don't know if I'm ever going to reuse parts of the engine.) But I thought it would be an interesting exercise to make the design as good as possible. Any advice you could give me would be helpful. (And sorry about my bit confusing and wordy explanation :)

Share this post


Link to post
Share on other sites
Quote:
I know that I'm probably overdoing object-oriented design somewhat (as my game isn't really that big yet, and I don't know if I'm ever going to reuse parts of the engine.)


Quote:
But I thought it would be an interesting exercise to make the design as good as possible.


Those two statements are incompatible.

Quote:
which does something with that data
What data? Does what? Why is it doing that? Where? How? Who?

Define very precisely, down to last data type what your data is. Then define every single action on this data. And out will pop perfect design.

Quote:
(the map, characters, items, etc.)
What kind of map, how many, what kind of characters, how many, ditto for items, what happens to them, why does it happen, where do they go and why and why not?

Everything else you've listed are various implementation techniques. Any of them can be valid, all of them, or none.

But not knowing what you're trying to acomplish makes it impossible for anyone to provide even the slightest hint of how to do something.

Your requirements aren't even clear what it is. It could be travel planner, which stores maps, characters you'll be visiting and items you'll take with you, or it could be card game, or FPS or MMORPG or game wiki, which lists all maps, characters and items in some game.

Define at least the most basic of goals first.

Share this post


Link to post
Share on other sites
Quote:

But not knowing what you're trying to acomplish makes it impossible for anyone to provide even the slightest hint of how to do something.

Your requirements aren't even clear what it is. It could be travel planner, which stores maps, characters you'll be visiting and items you'll take with you, or it could be card game, or FPS or MMORPG or game wiki, which lists all maps, characters and items in some game.

Define at least the most basic of goals first.


Like I said in my post, it is an RPG. A traditional single-player, 2D RPG. I'm using C++ and Allegro. I already have a working game; at the moment I'm just redesigning the code (like I said in my post.) So the functionality is already pretty well defined.

The questions I asked concern object-oriented design (or perhaps, "game engine design.") For that purpose, I think I've already given enough information. If you need more information in order to help, please ask specific questions.

[Edited by - formalproof on December 3, 2008 12:24:19 PM]

Share this post


Link to post
Share on other sites
Quote:
Original post by Antheus
Quote:
I know that I'm probably overdoing object-oriented design somewhat (as my game isn't really that big yet, and I don't know if I'm ever going to reuse parts of the engine.)


Quote:
But I thought it would be an interesting exercise to make the design as good as possible.


Those two statements are incompatible.


I don't think they are. Making use of good OO design practices might be overkill if I keep the game small and don't reuse the engine or parts of it. But the opposite might happen in the future. And BTW, it wouldn't hurt to use a bit friendlier tone in your posts.

Share this post


Link to post
Share on other sites
Anyway, a better and more detailed explanation probably won't hurt:

Main -- the main class which represents the game application. The entry point is a function called startMainLoop(). Main "hands over the CPU" to Input, Logic and Draw (by calling their functions directly or using the middle layer I described), which get the user input from Allegro, update the game world and do the rendering, respectively.

World -- this class represents the game world's current state. It has the tilemap, the character list, and the item list among others. (Character, Item, Tile, Sprite, etc. are separate classes.)

Draw -- draws the currently visible part of the map to the screen. The Draw class has a pointer to the World class in order to access the sprites in the currently visible part of the map. However, World knows nothing about Draw, so this relationship is one-way.

Logic -- has functions like updateCharacters() (iterates over the character list in World, doing AI processing), moveCharacter(), attack(), getItem(), etc, etc. I will probably split this class into subclasses such as CharacterHandler (does character processing) and ItemHandler (item processing) in the future. An example function would be Logic::attackCharacter(shared_ptr<Character> attacker, shared_ptr<Character> defender) which does the combat processing when "attacker" hits the "defender."

The communication beetween Input and Logic I covered in my original post. Interesting is also the probable need to have communication beetween Logic and Main: for example, Main needs to tell Logic to initialize itself, and Main has to know if the player is still alive (to know when to display the death screen or exit the game.) Main will not communicate with World directly in order to isolate the two from each other.

Another question which I have is where to put the GUI code, as the GUI has to reflect changes in the game state (the player presses esc to access the menu, the player character dies, etc.) Should I put it into Draw or Main, and how will the GUI code receive information about the changes in the game state and the world?

An example: the player presses a key to access a menu. This doesn't concern the actual game world and its logic, so perhaps I should just send a message through the middle layer to Draw to tell it to draw the menu, ignoring World and Logic. However, if accessing the menu pauses the game, this will affect the game's "state", so Main would need to know about this (so that it won't call doLogic() while the game is paused.) It seems and the "game state" and GUI must always be linked, so I'll probably need to have some kind of co-operation beetween Main and the GUI code (whichever class the latter is located in.) Any suggestions / advice on how to solve this problem?

So my main questions are:
1. Should I use the middle layer or not?
2. How should I implement the communication beetween Input and Logic? Should I use the function pointer approach I described in my first point, or the message passing approach?
3. In which class should I put my GUI code in? How to make the GUI reflect changes in the game state?

As for requirements, lets assume that I want to make a large game and reuse as much of the engine in my later games as possible. And that I want to use good practices which more experienced programmers would also use in this situation.

Any advice you could give me would be very helpful. How do you solve the problems I mentioned in your code/designs?

Share this post


Link to post
Share on other sites
The rule I try to follow is to keep things simple to begin with - when they get beyond a certain level of complexity, then I refactor and maybe divide responsibilities. So what I would advise is to merge 'Main' and the '(possible middle layer)' to begin with, and this class you need to coordinate the running of the game. If it gets complicated, you can always abstract out another layer.

Basically if you start out with a design that's more complex than it needs to be, it can be hard to work out what to simplify. But if your design is simple, it quickly becomes apparent what you need to factor into a new class. I'd steer clear of doing too much on paper other than listing the requirements for the project. Start coding this stuff, and re-design as needed.

Share this post


Link to post
Share on other sites
Quote:
Original post by Argus2
The rule I try to follow is to keep things simple to begin with - when they get beyond a certain level of complexity, then I refactor and maybe divide responsibilities. So what I would advise is to merge 'Main' and the '(possible middle layer)' to begin with, and this class you need to coordinate the running of the game. If it gets complicated, you can always abstract out another layer.

Basically if you start out with a design that's more complex than it needs to be, it can be hard to work out what to simplify. But if your design is simple, it quickly becomes apparent what you need to factor into a new class. I'd steer clear of doing too much on paper other than listing the requirements for the project. Start coding this stuff, and re-design as needed.


Sounds like a good idea. But I'd still like to get more advice on this. Can someone at least point to me some resources which would help with the questions I'm asking?

Share this post


Link to post
Share on other sites
There isn't strictly a need for extra layer, especially not one that cuts across the entire design.

It seems you're trying to solve a problem you haven't adequately addressed before - different states.

While the core may be about world map and actions inside it, there are other example, such as various menu screens that user needs to interact with.

Going back to basics:
- readInput();
- updateLogic();
- render();

This is the basic approach that is partially reflected in your design:
[Input] -> [Logic, lots happens here] -> [Screen]

Of course, the problem occurs since logic may vary depending on what you're doing right now, whether user is interacting with world, whether the world no longer exists due to death, options screen, main menu, inventory, and so on....

Input will not change. It receives something from all input devices.
Screen won't change either. It's your rendering device.

Interpretation of input however will change. So will the structures that are rendered on the screen.

This approach is often solved via states. Each State is a self-contained game loop. Trivial design can be:
class State {
State(Input * i, Screen * s);
virtual void readInput() = 0; // would-be middle layer
virtual State * updateLogic(float time) = 0; // Logic
virtual void render() = 0; // Draw
// World is defined by implementation

Input * input;
Screen * screen;
};


"Main" then maintains a list of such states, along with currently active one. It runs a trivial game loop:
while (true) {
State * s = getCurrentState();
s->readInput();
State * next = s->updateLogic(now());
if (s == NULL) {
s->render();
} else {
setCurrentState(next);
}
};


This approach would allow you to, with (hopefully) minimal changes, make your design more re-usable. It solves the problem of dependencies (Input->State->Screen), since they all go into single direction. It makes your game loop oblivious of actual state implementation (the death screen problem), and allows you to re-use existing implementations.

For example, as first step, you'd implement WorldState:
class WorldState : public State {
State(Input * i, Screen * s);
virtual void readInput() = 0;
virtual State * updateLogic(float time) {
if (player.isDead()) {
return new EndGameState(getInput(), getScreen());
}
if (openOptions) {
return &options;
}
if (openInventory) {
return &inventory
}

// no state transition
// manipulate world and logic as needed
return NULL;
}
virtual void render() {
// this function does what Draw used to do before
// or perhaps even screen
// it knows about World and Logic, doesn't care about input
// and can do all state-specific rendering
}

// Old classes, now owned by this State
World world;
Logic logic;
Draw draw;

// new States, also owned by this one
InventoryState inventory;
OptionsState options;
};


If you need to extend this, all you need to do now is implement custom states. Only currently active state will be getting updated, each state is perfectly self-sufficient and isolated from Main, while Main is still capable of throttling CPU on per-frame basis.

In addition, each state is responsible for managing its own resources.

Important note: The pointers to State returned above are not ideal, they are just used for simplicity.

One solution to this is to pre-allocate all states in main, and pass pointers to each individual state as needed. The other is to use some form of smart pointers.

I also don't touch multiple states (inventory or menu could be open while game keeps on running), since that one complicates things (what happens if player dies while using an item in inventory).

But it does try to stay as close to your existing design, while addressing some conceptual issues. Whether you choose to pass input via messages or some other way is just an implementation detail.


Share this post


Link to post
Share on other sites
Your post cleared up a lot of things. Handling game states was definitely the biggest problem in my design, although I think I failed to formulate that in my original post. What you said solves the GUI problem I mentioned very nicely. I'll try to implement your suggestions and then come back if I have more questions. Thank you so much!

Share this post


Link to post
Share on other sites

This topic is 3293 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this