Sign in to follow this  

best way to do this.

This topic is 4842 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 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!

Share this post


Link to post
Share on other sites
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!

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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. :)

Share this post


Link to post
Share on other sites
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!

Share this post


Link to post
Share on other sites
yes,zalhman, thank youi for the long, informative post. however, I havnet even learned any oop yet, so your code was confusimg. however, I feel bad you made that post, so as soon as I leanr oop, I will definetly use your way of doing things. rating for you! and yes, it would be interesting if you took all your long posts like this and put it into an article. it would be very helpful. fo now though, I think ill just stick to my old way, since it seems to be easisitest.

Share this post


Link to post
Share on other sites
Are you trying to hard-code the rooms for the RPG? This may get confusing. I recommend making a map editor to make things easier. There is an excellent tutorial at Game Tutorials (click) It was recently posted and the author of the tutorials is very good, IMHO. If you can read source code, then you'll be able to understand the tutorial. Making the map editor will be more extra work, but its worth it in the long run.

Share this post


Link to post
Share on other sites

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