best way to do this.

Started by
11 comments, last by wyrzy 19 years, 7 months ago
I think I have figured out a good way to make the rooms for my rpg. however, it seems like it is unnescesarily complex, and I think there might be an easier way to do it. here is how im doing it: I make a multidemensional array, and make a grid out of it, like this.

char city[25][25];
      {1111111111}
      {1111111111}

I know that isnt the whole grid, but you get the idea. in my program, each part of the gride will have a number(like instead of everything being a 1 like in my example, it will be 1,2,3,4, etc.) and I will use if statments to check what number the player is at. so if, for example, the player is at position number on, it will excectue a certain function. I would like a different way of doing things with less if statements. however, I still would like to use multidemensional arrays, not something else like std::map or whatever. Thanks!
______________________________My website: Quest Networks
Advertisement
If you design each room as an object that runs by itself, then you can put all the rooms in an array instead and just keep track of which one the player is in. If the player can only move in certain directions (N, S, E, W, say) then when the player wants to move, you already know which room he is going to.

For example, say your map is [25][25], and the player starts at [12][12]. When the game starts, run that room. Then when the player moves, you just need to alter the numbers, so he's in [11][12], or [12][13], or wherever.

But I am a beginner too, that's just my thinking. Good luck!
[sub]Now I'm radioactive! That can't be good![/sub]
maybe a switch statement
I havent gotten to objects yet. and a switch statement> how would you do it with a switch statement? I think that probably he only easier way to do this would be to make the rooms a completly different way, as in not usiong arrays. if there is a different way, though, or if the way I am doing it is the only way, lpease tell me.
______________________________My website: Quest Networks
There are great tutorials Here
basically, do away with the array and make structures out of rooms read from a file and keep track that way.
I read the tutorials on gametutorials. it was a tad confusing for me, as im still learning. guess ill just stick with the way im doin it...
______________________________My website: Quest Networks
An introduction to doing it with objects.

First, we have to get things to the point where we have a concept of where the user is within the grid, so that we can check what is contained there and do something:

void handleRoom(int playerX, int playerY) {  int roomType = city[playerX][playerY];  if (roomType == BUILDING) {    doInsideBuildingStuff();  } else if (roomType == STREET) {    talkToMerchants();  } else if (roomType == PARK) {    pickFlowers();  } else { // some other room type    lookNervous();  }}


I assume you already have something that looks vaguely like that.

Step 1. We need to move to a higher-level idea: instead of comparing the room type against several possibilities one at a time, we want to write something that basically says "the room type determines what we are going to do". We can do this with a switch statement, which is already a significant improvement because we avoid repeating "roomType ==" all over the place. Repeating things is not good.

void handleRoom(int playerX, int playerY) {  int roomType = city[playerX][playerY];  switch (roomType) {    case BUILDING:      doInsideBuildingStuff();      break;    case STREET:      talkToMerchants();      break;    case PARK:      pickFlowers();      break;    default: // some other room type      lookNervous();  }}


Compare and contrast this to the if-else if-else form so that you can see how a switch statement works. Notice the 'break;' added to each "case" of the switch; this is necessary because otherwise the code will "fall through" and *also* execute whatever is in the *next* case (in the order you write them). This behaviour dates back to the old days of C, and yet is preserved in Java and C#. Some people find it useful for certain optimizations. But most of the time it is a bad idea to leave out the breaks.

As you might guess, this is not ideal. First of all you have to remember those breaks. As well, we've highlighted the non-OO ness of the code. Chances are we will eventually have several other places in the code where we'll have to check the roomType in this way, and then we're still repeating ourselves. And we're also still relying on an enumerated type (at best) for our conceptual representation of a "room". Wouldn't it be nice to have a bit of structure so that we could add extra data that's part of the room abstraction? It'd be a pretty boring world if all the PARKs looked the same. Surely you'd at least like to count the number of flowers there. But at the same time, having a flowerCount for the STREET would be pretty silly.

We are going to fix this up now by using objects and polymorphism. I assume you have seen C-style structs before; they're just a collection of 'primitive' variables (ints, chars, pointers) which represent all the data for a particular "thing".

Polymorphism is like an implicit switch statement; it lets us treat different but related objects the same way, without knowing what kind of object we have. At run-time, the code will figure out what kind of object it is, and call the code specific to that object. This is done by adding a pointer to the structure, which is used for some code lookup. You don't really have to know anything about the pointer value, but in C++ you do have to ask for it to be included, and be (a bit) careful not to overwrite it .

First, let's see what some structs representing our rooms might look like:

// By convention, class and struct names are usually // CapitalizedLikeThis. In C++ anyway. In C, there are no// classes, and structs are more likely to be // named_like_this_struct.// Actually, in C++ structs and classes are really the same// thing, but whether you choose to call something a "class"// or a "struct" provides a bit of documentation info.// Generally you should prefer to use a "class", and write it// corresponding to expectations of a "class". But I am going// to show "struct"s first because it is useful for teaching. struct Building {  // notice, no roomType. It would always be BUILDING anyway.  int height;  int numberOfWindows;  int address;  string name; // maybe "First National Bank" or something.}struct Street {  int lanes;  string name; // "Pine St."}struct Park {  vector<Flower> flowers; // objects can include other objects.  string name; // "Jakpandora Memorial Park" ;) ooh I'm so morbid.}


OK, so far so good. But now we have some problems:
- We can't put these all into an array, because they're different types. What type would we use for the array declaration?
- The different room types do have *some* things in common; in this example, they just have a "name" in common, but you might think of other things.
- We'd like to associate actions with the current Room, so that we don't have to look it up all the time.
- Any old piece of code can grab any of the data from within any of our objects, and we might mess something up. If we can get the compiler to add some control to the way we access things, it will be harder to make a mistake.

Well. We already have been thinking about the concept of a "Room", which has a type - it could be either a Street, a Building or a Park. It would be nice not to have to worry about that type, except when we need to.

The way we handle this to make a "base class" Room, and "derive" the other classes from it. We're going to switch to calling them classes now. This has the effect of making the contents default to "private" rather than "public", meaning that only the "methods" of the same class can see or change the values.

// We could write these as structs as well, but the convention// I was alluding to earlier is that "structs" are expected to// keep the default of public data, and have relatively few// (and uninteresting) methods of their own.class Room {  string name; // all rooms have a name.  public:  // The following things after "public:" are accessible from  // outside. Good OO style dictates that ou should normally put   // most, if not all of your methods here, and avoid putting   // data members here.  virtual void pickFlowers() {    // The keyword 'virtual' is important, it makes it possible    // to make full use of polymorphism. This is that "you need    // to ask for it" part I was talking about earlier.    cout << "There are no flowers here." << endl;    // We will "override" this in the Park class, since you    // *can* pick flowers there. (Although you might get    // caught!)  }  // You can also define the methods outside of the class  // declaration as shown...  void sing(); // not virtual; singing works the same way  // no matter where you are. (But maybe you want underwater  // rooms or something?)  virtual void smashWindow();  virtual void talkToPeople() = 0; // more on this later.}void Room::sing() {  // The Room:: tells the compiler that we are defining a Room  // method, rather than a free function called "sing()".  cout << "o/~ 99 bottles of beer on the wall... o/~" << endl;}virtual void Room::smashWindow() {  cout << "There are no windows here." << endl;  // will be overridden in Building.}// Notice I missed defining talkToPeople; this is OK because we// made it a 'pure virtual' method via the '=0' bit. However,// base classes which have any pure virtual methods in them// cannot be 'instantiated' directly; that is, now we can't make// an ordinary sort of Room, but have to specify a kind of Room// when we make it.class Building : public Room {  // the word 'public' is important there, it specifies the *way*  // in which Building is a kind of Room. This is a weird and  // C++ specific concept; for now, accept that you will want  // "public inheritance" almost always.  // notice, no name. Since we "inherited" from Room, we have  // access to that field already. But we need to specify the  // non-Room-specific stuff that a Building has:  int height;  int numberOfWindows;  int address;  public:  void smashWindow() {    cout << "WHEEE!!! Don't get caught now!" << endl;  } // this overrides the definition in Room. Other kinds of  // Room don't have an override for smashWindow, so they'll  // use the Room default.  void talkToPeople() {    cout << "Everyone looks too busy doing stuff to talk to you." << endl;    // Since there is no default talkToPeople(), we *must*    // provide one for Building. Otherwise it becomes an     // "abstract class" (can't be instantiated) too.  }  // We can override sing() too, but because of how C++ works  // with its keyword 'virtual', it probably won't do what you  // expect.  Building(string n, int w, int a) : name(n), numberOfWindows(w), address(a) {    // This is a constructor for Buildings; notice there is no    // return type for the method, not even 'void'. You should    // not write a return statement inside the method body    // (and probably shouldn't throw an exception either).    // The stuff after the : is an "initializer list" and is    // the preferred way of initializing members when you    // just want a straight copy from the input parameters.    // Other more complicated stuff can be handled in here:    height = numberOfWindows * 10;    // at that point numberOfWindows is already set by the    // initializer list.    // Within the body of a constructor, or any method, we    // can use the keyword "this" as a *pointer to* the    // current object. We can also refer to any member    // variables since they are available in this scope.    // We could have also written:    // this->height = w * 10;  }}// I left out Street and Park; see if you can figure out what // they'd look like.


OK, so now we're ready to code around these objects:

Room city[25][25];// initialize the Rooms.city[0][0] = Building("OMGWTFBANK", 100, 0);city[0][1] = City(...);...// Set the player up in the middle of the map.Room playerLoc = city[12][12];switch (what_the_user_wants_to_do()) {  case SMASHY_SMASHY:    playerLoc.smashWindows();  case SING:    playerLoc.sing();  case OMGPRETTYFLOWARZ:    playerLoc.pickFlowers();  case IWILLHAXORYUO:    playerLoc.talkToPeople();}


Oops, we're back to using a switch statement. Using classes and objects to handle the user input is a lot trickier, so for now you may have to just live with having one of these (and there should only be one, in your main loop where you get the user input).

Also notice that when we make the "method calls" on playerLoc, we don't have to know what type of Room the player is in. The virtual-ness takes care of that. But sing() will behave the same way even if we overrided it for some room type, because that method was *not* virtual. That's the "it won't work like you expect" part I mentioned earlier. To get at the overriding version of the sing() method, you need to tell the compiler the type of the object at compile-time, because you didn't ask it to check at run-time.

Anyway. Notice how much we've saved here. Before, we would have written one of those switch statements (or else-if-chains) inside every one of those methods (except perhaps sing()), since we need to check the room type *and* the user command. Now the class structure takes care of checking the room type for us.

SELF STUDY PROJECT:

You'll notice I didn't implement any kind of movement between rooms. That doesn't make for a very exciting game, since the user doesn't get to see all the other rooms you so painstakingly created. The easy way to handle this is to keep track of an xloc and yloc instead (coordinates within the array where the user is), and in the main loop, adjust those values as needed (according to movement commands) and grab the new playerLoc out of the array. This way still leaves you stuck with a 2-d grid layout for your world though; not very interesting. So the project is, work it out so that you can connect the rooms in any way that you like. Recall that objects can contain other objects; this unfortunately doesn't work if that 'inclusion' is recursive (since where would you stop? Infinite sized objects, onos!), but there is an easy way around that: include *pointers* instead. Thus, you'd want to add some collection (vector, list, plain old array, or even just X many separate variables) of Room pointers to the Room structure (remember, all Rooms will include that information). In order to access the Room pointers directly from methods of Room subclasses, you will need to make them 'protected' (that will still protect them from being modified from outside - by other classes or free functions). Then, set up your movement command to call a Room method, which somehow determines which Room pointer to 'follow', and returns that pointer. Finally, set up the playerLoc to be the pointed-at room.
wow... i was going to try to help, untill i scrolled down to Zahlman's post [smile]
FTA, my 2D futuristic action MMORPG
Yeah. I probably shouldn't even be taking up so much time with these posts, I have stuff to get done :s I'm thinking at some point I'll track down my long posts and refactor them into an article for GameDev, or something. :)
Yeah that's quite a novel you've got there!
Those must be some well deserved rating points you have there if you help everyone out this much!
"In order to understand recursion, you must first understand recursion."
My website dedicated to sorting algorithms

This topic is closed to new replies.

Advertisement