Questions about how to separate data from visual representation.

Started by
9 comments, last by MartinMM 16 years, 6 months ago
Hi, my name is Martin and i'm new to the forums. Im working on a small 2d rpg and try to polish my c++ skills. As the titel says, I need help on how to separate the data from its visual representation. We can use my NPC class as an example. As it is now the npc class inherits from the general object class, which contains info about positioning and some other stuff. The NPC has a pointer to a WalkingSprite object that contains all the frames for the sprite. It also has an update function that checks what it should do, that sends data to the WalkingSprite on where it should be drawn next and what direction it should be facing. All npcs are contained in a vector. For each game loop, i call the update function of the NPC and then the NPC draw function (which only passes it forward to the WalkingSprites draw, like m_WalkingSprite->Draw()) This works perfectly fine, but could it be done better? I have tried to understand the MVC pattern and how it could be applied to a game like this, but i haven't found any good tutorials. Any help on how to use the MVC pattern in a 2d sprite based game? (i'm using SDL by the way)
Advertisement
I am not exactly sure of what the MVC states, but one idea, in haste, is to have a set of classes solely dedicated to rendering. ie CSpriteRenderer::CSpriteRenderer( CWalkingSprite& ) that takes instances of NPC ( or any sprite that needs rendering ). This way, you create an interface to the sprite data without actually having any inner-class dependencies... And you could derive that from a seperate base class that is tailored to rendering...
From http://en.wikipedia.org/wiki/Model-view-controller :

Model-view-controller (MVC) is an architectural pattern used in software engineering. In complex computer applications that present a large amount of data to the user, a developer often wishes to separate data (model) and user interface (view) concerns, so that changes to the user interface will not affect data handling, and that the data can be reorganized without changing the user interface. The model-view-controller solves this problem by decoupling data access and business logic from data presentation and user interaction, by introducing an intermediate component: the controller.

Now how does that apply to your NPC example ?

Your NPC is represented by a Sprite and is controlled by a script. In MVC, the data is always static: I mean all methods or behaviors acting on or using the data are externalized in the View or Controller part.

One way to define the implementation would be:

generic NPC class (Model part)
class NPC_Model:
- world data: position, inventory, health, mana, ...
- technical data: current animation frame, controller script ID, ...
(as you can see no methods here)

Sprite Rendering class (View part)
class RenderSprite:
- reference to the Sprite resource manager
- method Render(reference to NPC_Model): reads in the NPC_MODEL all the necessary data for a correct display of the sprite (world data for position and technical data for current frame ID and sprite resource ID)

Generic Controller class (controller part)
class ControlSprite:
- reference to the controller manager(keyboard, script or any other control device)
- method Execute(reference to NPC_Model): updates the NPC_Model data through control instructions(for example keyboard control or script control on world data position).

When you need to create a specific NPC, you just join in a class a model class and all the necessary View and Controller classes:

Specific_NPC
- constructor(NPC_Model, Render class reference, Controller class reference)
- method Render: call Render_class Render method with NPC_Model as argument
- method Control: call Controller_class Execute method with NPC_Model as argument.

Now you can do any kind of NPC: if you need a flying NPC, just create a new controller class specialized in flying and you are all set. This means you do not need anymore to derive a new specialized class to cater for any way to control or render your NPC. Even better, your player object can also be represented by a Specific_NPC class with a specialized Render class and a Keyboard_Controller class.

The MVC architecture has however an inherent challenge: how to let the View and Controller classes dialog between them without specifically coupling them ?
That is the meat of this architecture. In other words, when your scripted control changes the state of your NPC from walking to flying, how do you know how to change the NPC representation to show it flying ?

1) either you add up more varaibles to your NPC_Model class: this is a quick hack but will lead for large sets of behaviours and renderings to unmaintainable NPC data.

2) or implement a messenger system to let components dialog without cluttering the NPC_Model class: this is much better. If your script sends a message that the NPC state is changed to flying, then the Render class when receiving the message will either act on it (play an intermediate animation for example then set the NPC flying) and update the NPC technical data, or ignore the message (because there are no response built in).

I hope I helped you gain a little more insight on the MVC architecture pattern.

Ghostly yours,
Red.
Ghostly yours,Red.
Thanks alot!

I think i understand the MVC pattern a bit more now, and i also see the benefits more clearly. I'll try to implement it, i'll probably run into problems (i don't have a control manager, but will look into it). For now i only have one question. Should the NPC_Model class be completly public?, Well i guess so because it shouldn't contain any methods. So well, i think i answered that myself :)

Thanks again!
Well, it didn't take me long to realise that i didn't understand as much as i thought i did :)

If i have that layout, how should i use it? Should i like before during every gameloop call the Specific_NPC::Control() and Render() from a list of specific_NPCs? I guess so, but i'm not completely sure.

But still, in my current design npc:s walk randomly inside a rectangle. When i call the update function it uses a random number to move the NPC. In your suggested design, this should go into the controller i guess and be some kind of RandomMovementController class or something. But i like the idea of a control manager, but i have problems seeing how to implement that.

updates the NPC_Model data through control instructions(for example keyboard control or script control on world data position).

What should those control instructions be like? should it be a script message sent as a string like "moveto left" and parsed by the controller class? (or maybe "move randomly")

And in the case of the keyboard control?

The way i control the player object now is to during each game loop i poll for a SDL_event, check if its keydown, check if its a movement key and sets a variable accordingly (if it's left then xm=-1, down ym=1 etc.). I then use those variables to change the player position by using Player.SetPosition(x,y). Kindof :) Maybe this is the wrong approach.

Maybe i should post a new message about these problems, but it took me one year to start posting topics (or even get an account) and now i cant get myself to post two topics in two days :)
Hi,

Let's be more detailed.

Your game has 3 parts:
- Initiation: this is where you create all your specific_NPC classes.
- GameLoop: this is where you process the following steps (pseudocode)
while (not GameEnded){   //process AI   for each NPC      process specific_NPC control method   //display   for each NPC      if specific_NPC position is within screen world pos         process specific_NPC render method}

- End game: this is where you remove all specific_NPC classes

This shows that you check on each frame all your specific_NPC classes and call the methods Render and Control.

About the control manager:
The aim of such a manager is to store all the different kinds of controllers.
Your control manager may have the different controllers (non exhaustive list):
- keyboard controller
- script based controller
- random movement controller

Let's look at an example of a controller class(pseudocode):
class RandomMoveInRect_Controller: public ControlSprite{   public:   method Execute(reference to NPC_Model){      get from NPC_Model the current position and the current destination      if destination is reached         {         get from NPC_Model technical data on wander area (bounds of area)         compute a random destination within area         set in NPC_Model world data the new destination         }      set NPC_MODEL world data orientation towards the new destination      change NPC_MODEL world data current position towards new destination      };}


Controlling using a keyboard is not much different:
class Keyboard_Controller: public ControlSprite{   protected:   char array which holds all the key states    //key states   //0: not pressed   //1: first keypress   //2: subsequent keypress   public:   method UpdateKeyboard(key ID, bool IsDown){      if (IsDown = true) AND (array[key ID] < 2)          {          array[key ID] += 1          }      if (IsDown = false) AND (array[key ID] > 0)          {          array[key ID] = 0          }      }   method Execute(reference to NPC_Model){      get from NPC_Model the current position      if (array[Key UP] > 0)         {         change NPC_MODEL world data current position towards up         }      if (array[Key DOWN] > 0)         {         change NPC_MODEL world data current position towards down         }      if (array[Key LEFT] > 0)         {         change NPC_MODEL world data current position towards left         }      if (array[Key RIGHT] > 0)         {         change NPC_MODEL world data current position towards right         }      }}

When you poll for an sDL event in your game loop, instead of sending it to a player class, you send it to the keyboard controller (through the method Update keyboard). And that's all.

Hope that clarifies a bit.
Ghostly yours,
Red.
Ghostly yours,Red.
Thanks!! You are being really helpful :)

I think i get it now, the script based controller would get the script data from/via the NPC_Model. And update the NPC_model accordingly.

But one thing still puzzles me. The control manager. Or i understand the purpose of it to contain the different controllers. But why do the controllers themselves need a reference to the manager? Maybe i misinterpret...
The aim of such a manager is to store all the different kinds of controllers.
and
-reference to the controller manager(keyboard, script or any other control device)

I think i can solve my problem with all the help from you now, but i'm a bit curious to see if i really have understanded everything correctly :)

Some problems i see:

Where should you check if you could move to the new place? Because if i would have walking and flying npc:s they would differ in where they could be (a flying one could fly over a wall). Say we are talking about the player object here. The Keyboard_Controller would need to be different for those two (but not much). Should i send the events to all the controllers using the keyboard, or should i use the Keyboard_Controller for both and check some other place if the new location is okay?

I think i might be misunderstanding here. The Keyboard_Controller you wrote about, is that the one that should control the specific_NPC ? Or is it another controller that uses(by reference) the Keyboard_Controller to control the specific_NPC. Not certain i make much sense here :)

Thanks again :)


Hi,

The controller manager stores all the different kinds of controllers. I made an error when writing that controllers hold a reference to the controller manager. I was thinking about a higher level of complexity. Imagine you have many specific_NPCs which share the same script:
- either you duplicate the script data in each NPC_Model: the controller then holds no reference to any kind of manager.
- or you store the script in a script manager: the NPC_Model then only stores a script ID and the Script controller holds a reference to the script manager. When the script controller is executed, it first retrieves the script ID from the NPC_Model, then queries the script manager to get the script data.

Now regarding collision detection (aka check if you can move to the new place).
A simple solution is to hold a reference to the world data in the controller class (you could pass world data by reference to the Execute method like the NPC_Model data). Thus you can store flying collision detection within the flying controller method, or store walking collision detection within the walking controller method.
But what if you have a walking soldier chasing after a walking ghost ? Should you duplicate your walking controller into the normal and ghost versions ?
Not necessarily. Enters the object component architecture (you may have read about it on the forums). Like the controller objects, and the render object, you create a collision detection object. Still you do not want to couple the collision detection component with the controller component. To that effect you either use messenging or callback system. Let me show you a simple callback system (messenging systems are more difficult).

Let's evolve our design a little bit:
struct {   - Source   - Destination   - Event   - Data} CALLBACK_MSGclass CollisionDetection:   contructor(reference to World data)   method Check(reference to Specific_NPC class, reference to CALLBACK_MSG){      Compare CALLBACK_MSG.Data with World data      Create a CALLBACK_MSG using:         Source = generic collision detection ID         Dest = previous CALLBACK_MSG.Source         Data = true if new position is valid, else false      }class Specific_NPC:   constructor(NPC_Model,                Render class reference,                Controller class reference,               Collision class reference)   Render: calls Render class Render method and pass this pointer   Control: calls Controller class Execute method ans pass this pointer   bool Callback(reference to CALLBACK_MSG){      switch (CALLBACK_MSG.Destination){         case CollisionDetection: call collision class Check method, return true         case Controller: call Control sprite ProcessMsg method, return true            default: break;         }      return false;      };class ControlSprite:  protected:  variable CanMove  public:  method ProcessMsg(reference to Specific_NPC, reference to CALLBACK_MSG)     {     Set CanMove = CALLBACK_MSG.Data     }  method Execute(reference to Specific_NPC){     1 - compute new position after getting     2 - create a CALLBACK_MSG using:         Source = this controller ID         Dest = generic collision detection ID         Data = new position     3 - call the Specific_NPC Callback method with the Msg     4 - after this message call, the CanMove variable has been updated     5 - if CanMove is true, update NPC_Model     };


What happens here:
1 - the controller sends a message with the new position to the collision detection component held by the specific_NPC
2 - the collision detection component checks data and sends back a message telling wether the NPC can get there or not
3 - the controller resumes its Execute method and acts on the CanMove variable

Through that simple callback mechanism, you have two components able to dialog without being coupled: this means with a walking and flying controller and a normal and ghost collision detection you get all combinations of movement with simple base components. Moreover, adding a new controller or a new collision detection component let you focus on the component itself and not its interactions.

Now about the keyboard controller question. What I am driving at is simply to show how to store all characters with a single structure:
The Player is just a specific_NPC with a keyboard controller and the palace guard is a specific_NPC with a script controller (and the menacing Red Dragon is another specific_NPC with a flying controller).

Hope that helps.
Ghostly yours,
Red.
Ghostly yours,Red.
Wow, thanks :)

I think i understand all now, except for the keyboard controller thingy :)
I understand this:
- The Player is just a specific_NPC with a keyboard controller and the palace guard is a specific_NPC with a script controller (and the menacing Red Dragon is another specific_NPC with a flying controller).


But say that the player walks, then get some kind of powerup or something and starts to fly. You still controll the player from the keyboard, but the controller should now be a flying controller, and if it's a flying controller it's not the Keyboard_Controller anymore. I would not want to create Keyboard_Flying_Controller and Keyboard_Walking_Controller (and then Script_Flying_Controller and Script_Walking_Controller), and i don't think i'll have to. But i'm not really sure how you would implement it. I think it's something i misunderstand somewhere :)

/Martin
Hi,

I guess you are now on the right track to do your own design experiments.
The only answers to your questions stem from your own knowledge of your game needs and constraints. Here are two cases:

1- the player starts the game walking, then in the middle of the game is flying to lastly teleport itself at the end of the game (I remember on game like that when you were a vampire - 3 chapters, each chapter with its specific mean of transportation). This calls for separate keyboard components (walk, fly, teleport)

2- the player is always walking (or running) but may get temporary powerups which give him/her the ability to fly for a limited time (like in some platform based game). From there you have many options like stacking keyboard components (thus having separate components for walking and flying), or having a single keyboard component with a radical different physic environment (i.e. flying is just when gravity is cancelled for the player).

Again, this is where your knwoledge of the playability of your game comes in. Do not fear experimenting different design solutions: this is how you will gain experience and insight on design challenges.

Ghostly yours,
Red.
Ghostly yours,Red.

This topic is closed to new replies.

Advertisement