Jump to content
  • Advertisement
Sign in to follow this  
stitchs_login

Classes, Engine and Ownership.

This topic is 2165 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

Hello all,

A while back I posted about how I am trying to use Model-View-Controller in developing a Tic-Tac-Toe game. I have a Grid class that stores a Grid and returns data like the Width/Height, a Grid Square, and sets a Grid square. I also have an Engine class which includes the Grid, and uses the data returned by the Grid to draw it, among other things. Thereby separating the Logic from the Draw.

I was wondering, when it came to the game end, either Win or Draw, and the game is chosen to be reset, which out of the Grid class and the Engine should have control over resetting the board.

Currently my engine has a block of code that looks like this:
[source lang="cpp"]else if(a_GameState == 2){
// RESET THE BOARD. SAVE for PHASE 2. REQUIRES DOUBLE BUFFERING or a Clear_to_col.
// Loop through each Row of the board.
for(unsigned int j = 0; j < a_Grid->f_GetHeight(); j++){
// In turn looping through each character of said row.
for(unsigned int i = 0; i < a_Grid->f_GetWidth(); i++){
// Set each square back to its' blank state.
a_Grid->f_SetGridSquare(i, j, 'H');
}
}
// The game is set back to In-Play. THE PLAYER STATES NEED TO BE RESET HERE.
a_GameState = 0;
}[/source]
Is this something that the Engine should have control over, or should the Engine just say "Grid, reset yourself":
[source lang="cpp"]if(a_GameState == 2){
Grid->reset();
a_GameState = 0; // back in play.
}[/source]
I'm just unable to define clear ownership, not just with this. Every time I think of a reason why a class should do something, I think of a counter-reason as to why the place where the class is included should use the data provided, and execute for it.

Thanks in advance,

Stitchs.

Share this post


Link to post
Share on other sites
Advertisement
I would say this definitely belongs in the Grid class. The Engine shouldn't have to worry about what the default state of the Grid looks like - it should be content to know the Grid has been reset, never worrying about the specifics of how.

Not only will it make the code easier to read, it will make it easier to make changes later. Say you want to implement a hexagonal grid, or a 3D grid, or whatever; if the resetting code resides in the Engine, you will have to change it; if it's handled by the Grid class, you can simply replace it with a new Grid class. As long as it implements the reset() method, the Engine won't know the difference - and shouldn't have to.

(In practice it will likely not be that simple, of course - but the principle holds.)

Share this post


Link to post
Share on other sites

Is this something that the Engine should have control over, or should the Engine just say "Grid, reset yourself":

The latter is what you want, the engine class should not have to be aware of the grid's inner workings.

I'm just unable to define clear ownership, not just with this. Every time I think of a reason why a class should do something, I think of a counter-reason as to why the place where the class is included should use the data provided, and execute for it.

For me this seems to come naturally, probably because of my years of experience. I'd be interested in knowing what your thought patterns are, could you give us some examples?

Share this post


Link to post
Share on other sites
Okay, this helps put my focus into a more clear direction. But I have so many other questions.

Currently my 'Engine' handles all drawing: I use the same data as I did in the above example, in order to draw the grid, as well as player symbols. From my understanding I was doing the right thing, but then the above example demonstrates that my Grid should be responsible for it's reset, so should it be responsible for its ability to draw. But then, I am going against what I would like, to be able to separate the draw from the class:
[source lang="cpp"]for(unsigned int j = 0; j < a_Grid->f_GetHeight(); j++){
for(unsigned int i = 0; i < a_Grid->f_GetWidth(); i++){
// One Line drawing function to represent Vertical lines, determined by X coordinate spacing.
// Parameters: Draw to, X1 calculated at top of screen evenly spaced by SquareSize, Y1 as zero,
// X2 the same as X1, and Y2 is screen height, WHITE. //600
line(screen, i * a_Grid->f_GetGridSquareSize(), 0, i * a_Grid->f_GetGridSquareSize(), 768, makecol(255,255,255));
// One Line drawing function to represent Horizontal lines, determined by Y coordinate spacing.
line(screen, 0, j * a_Grid->f_GetGridSquareSize(), 768, j * a_Grid->f_GetGridSquareSize(), makecol(255,255,255)); //both need to be 'j'
// Check each grid square to determine what should be drawn there.
if(a_Grid->f_GetGridSquare(i, j) == 'O'){
// Using circles to test if drawing is correct
circle(screen,
/*X*/ ((i + 1) * a_Grid->f_GetGridSquareSize()) - (a_Grid->f_GetGridSquareSize()/2), // Plotting the X and Y for the circle require that they be calculated as
/*Y*/ ((j + 1) * a_Grid->f_GetGridSquareSize()) - (a_Grid->f_GetGridSquareSize()/2), // the circle centre. Also, cannot (/2) as this will draw the circles closer to each other.
a_Grid->f_GetGridSquareSize() / 2,
makecol(255,0,0));
}
}
}[/source]
Right now the Engine has A LOT of code in it, I do hope to break it down into smaller classes, as would necessitate, during later Increments. Should I create mirror classes that represent the 'View' of each Model i.e. 'GridView' drawing the 'Grid', 'PlayerView' drawing the 'Player', but then how far do I go, do I place the Logic of resetting the Grid into these classes, and then in the Engine, call ShapeView->Grid->Reset(). But then I ask myself, well this is just merging Draw with Logic, then I should just make one class that handles all.


[quote name='stitchs' timestamp='1342301023' post='4959133']
I'm just unable to define clear ownership, not just with this. Every time I think of a reason why a class should do something, I think of a counter-reason as to why the place where the class is included should use the data provided, and execute for it.

For me this seems to come naturally, probably because of my years of experience. I'd be interested in knowing what your thought patterns are, could you give us some examples?
[/quote]

Okay, with reference to your request, I'll use my University project as an example: I decided to breakdown my project into an inheritance hierarchy, for simplicity I'll use one branch of the tree:

BasicObject
is the parent of: CollidableObject, ModelObject
these two become the parent of: CollidableModelObject
CollidableObject gives birth to CollidableScoringObject
CMO and CSO become parents of CollidableModelScoringObject.

This is where in myself I felt like I was going too far, I didn't in fact go with this design for the final hand-in, but it took a while to finally decide "Hey, if I dilute these classes as I have, then I could end up with a different class for every single attribute: CollidableModelScoringHealthMagicUsingObject (this is just a silly example, but a route I was going down/is still a concern for me). I eventually removed Scoring from the above example, and the CMO tree became my final design.

Are there any good starting point articles that could help to shed some light on this subject.

Once again, thanks so much for taking your time read this, and any help is greatly appreciated.

Stitchs.

Share this post


Link to post
Share on other sites
Lets start with your example and take it from there. The first question that pops into mind is, do CollidableObject and ModelObject really need a shared base class? What similarities do they have? CollidableModelObject inherits from CollidableObject and from ModelObject. I'm assuming you want a base class with functionality from both classes, but the question is, why? Maybe this is how you think you should compose objects out of reusable code. You continue down this road and create larger and larger classes, I don't think you want or need this.

From what I can tell you have a model that want to draw, you want that model to be attached to some object, this object should interact through collision. There are a lot of ways to go about this, but lets at least cover the basics.
- The model class stands on it's own, it doesn't depend on any other classes, it's there to visually represent objects and the likes. No need to inherit this class.
- The object class is a bit different because you'll have more than one kind of object, each with their own behaviour. So why would you want a base class if they're all so different? Well you'll want to store all objects in a single container so that you can go through the list and update their states. What else do objects have in common? Probably all of them have a position. Do all of them have a visual representation? Not necessarily, so we might not want all of them to contain a model. Same goes for collision.
- The collidable class is there to define some interaction, what type of interaction depends on what kind of object it is, so we want this to be some kind of interface. That means that we'll inherit from this class, and define what happens on collision by overriding the class's functions.

Lets say we want to create a cloud class now, we simply inherit from object and define what happens on every update(change the position in some way). We store a model class inside the class because we want it to have a visual representation (there are other ways of doing this, but this is probably the easiest to do and explain).
That was easy enough, looks pretty clean as well. What about an interactive object like a lever? This time we inherit from both object and collidable. We override collidable's onCollision() function and define what happens when a collision occurs(open a door for example). Again, we store a model class inside this class, no need to inherit it.
From these three classes we can create almost any game object, but what if we want to reuse some functionalities? Don't need inheritance for that, we can just create a class and store it inside other classes.

Seems easy enough, but who's in charge of updating/drawing all the objects? Well we could do this in our main loop or if necessary create a separate class for that. What about collisions? Well we could register all our collidable objects to some collision class that calls the onCollision() function when a collision occurs.

Might not have given the best examples, but I hope this gives you some insight in how to approach your designs differently. Edited by Mussi

Share this post


Link to post
Share on other sites
It does indeed, thank you very much. So you are saying that I should create some sort of class that handles models, or in the case of my project; primitives/sprites and include these as part of Player/Grid.

Although, when I went back and looked through my Player and Grid classes, I realised that I did not want them to have any knowledge of Allegro, which is why I made methods that returns data such as a GridSquare, or the Width/Height of the Grid. I also came to the conclusion that, if there is a need to link the Model classes, such as being able to place a "Player Symbol" in a "GridSquare" then this should be handled by the Engine. But if there are class specific actions, such a Grid reset, then this should be handled by the class only.

During the time between my last response and this one, I came across this article: http://obviam.net/in...building-games/
which aided in adding to my confusion of course =D

It kind of went along with my original thought processes, that my Grid and Player should only contain data and render be handled separately, by some higher interface/engine. What I did notice was that it surrendered any modifications of State that these classes might undergo, instead giving Control of these to a separate Controller class. Incoming arbitrary example: Player has X and Y coordinates, these are only set in Player through Mutator functions e.g. X = xIn, with the Controller modifying these coordinates allowing movement:
[source lang="cpp"]void PlayerController::moveLeft(int xmove){ x += xmove;}[/source]
When applying this to my original question "should Grid handle reset" I think that maybe GameEngine should define this transition between the states GridFull to GridEmpty, obviously implementing an additional class to act as controller between the Grid/Engine.

In summary, is this the right way, or are there that many interpretations of how a situation like this can be handled that it should really come down to what suits the developer (aka Me) and the needs of the project?

Thanks in advance,

Stitchs

Share this post


Link to post
Share on other sites
It depends on the complexity of your project, the time frame you have to finish it and your personal preference to name a few things. With that being said, I don't think you should ever place game logic into something like a grid class. Drawing or input handling could be in or outside the class depending on the project, but I think separating them is neater(using controllers and such).

For a simple Tic-Tac-Toe game though, I wouldn't use controllers since you know the boundaries of your game pretty well and they're fairly small. Adding controllers would add unnecessary complexity, unless you add them for educational purposes.

To come back to your original question, your Grid should have a reset method that resets the grid and your GameEngine should handle when that method gets called.

Share this post


Link to post
Share on other sites
I am also a friend of the MCV pattern. But I think it is not such a good idea to make classes named Engine. Just make a class for drawing the grid and one for controlling it. The Controlling class should somehow geht messages from the input system or an AI and change the model accordingly. You could also wrap the graphics library you use in a class with an interface that gives you what you need for the game.

Concerning the class hierarchies discussed earlier. I would try to avoid inheritance as much as possible. I use interfaces to communicate that a class has a certain functionality and aggregation/composition to reuse existing code.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!