Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Microsoft CSharp Programming For The Absolute Beginner (2002) [eng]-1.pdf
Скачиваний:
46
Добавлен:
16.08.2013
Размер:
15.71 Mб
Скачать

In the Real World

This strategy of organizing your information into tables and then building a class to represent the table data isn’t just for game programming. In fact, it’s the key to any kind of programming that involves large amounts of information. Getting a handle on your data is clearly the starting point of writing a good program. If you design your data well, your program will flow towards completion with relative smoothness. If you’re sloppy in the way you design data (for example, you don't clearly think through how the user will get from one room to another), you will struggle throughout the entire process. Nothing seems to have a more important effect on a programmer's success than his understanding of the data.

Creating the Dungeon Class

I am proud of my Room class because it will help me with my goal of building a dungeon. However, the Room represents just one row of the chart. I need to represent many rows at once. The easiest way to group the rooms is to build another class that holds an array of rooms. That class is named the Dungeon class:

using System;

namespace Adventure

{

///<summary>

///Class for storing a dungeon. Mainly holds an array of rooms.

///Designed to be stored in a serial form.

///3/11/02, Andy Harris

///</summary>

[Serializable] public class Dungeon

{

private string pName; private int pNumRooms = 20; private Room[] pRooms;

public string Name { set {

pName = value;

}// end set get {

return pName;

}// end get

}// end name property

public int

NumRooms {

//no set

− make it read−only

get {

 

return

pNumRooms;

} // end get

}// end numRooms property

public Room[] Rooms{ set {

pRooms = value; } // end set

get {

return pRooms;

263

} // end get

}// end property

public Dungeon(){

Rooms = new Room[pNumRooms];

}// end constructor

}// end class def

}// end namespace

The Dungeon class has three properties and a constructor. The Name property is the name of the current game. The numRooms property is a read−only property that stores the number of rooms in the current dungeon. I made the numRooms property read−only because it can cause some serious problems if the number of rooms is changed thoughtlessly. I preset the number of rooms at 20, but changing the Dungeon class to handle more rooms would be easy. However, none of the files stored with the 20−room version of the program will work with the new one, and vice versa. It seems that 20 rooms is enough to build complex adventures (such as the Enigma game) without becoming overwhelming.

The most critical property of the Dungeon class is the array of Room objects, named (cleverly enough) Rooms. By having the rooms stored in an array, I gain several important advantages. First, I don’t have to worry about adding a room number to the room class, because the index in the array will serve as the room number. Second, accessing each room by its index will be easy. Third, because the array of rooms is part of the Dungeon class, I can store and load all the rooms at once by serializing Dungeon.

The Dungeon class is serializable. Because it includes instances of the Room class, Room must be serializable as well. The combination of Room and Dungeon completes the basic data structure for the game.

Writing the Game Class

It might surprise you that the actual game form is probably the simplest part of the program. All the careful work designing the data makes the game itself quite simple to write.

Creating the Game Form's Visual Design

The Game class is designed around the metaphor of a scroll, with arrows pointing in four directions. Figure 9.20 shows the game form's visual design. The most obvious feature of the game window is the central label named lblDescription. I added a scroll image as the background image of the label and gave it a font that reminded me of a treasure map. The four surrounding labels are used to describe what will happen when the player moves in a particular direction. Each of these labels features a background image as well. I used figures of pointing hands to illustrate the possible positions. Each label also features text that describes the name of the room the user will encounter if he goes in that direction.

264

Figure 9.20: The game window features several labels and a menu.

In the Real World

Because this program involves several forms, it is important that they have a unifying visual design. I built a Scroll image in my image editor and placed it on the back of every form, so it looks as though the instructions are written on a treasure map. I also chose similar fonts throughout the program and kept the general layout of the editor and the game screen similar, even though the actual controls are completely different in these two forms. It pays to keep visual unity in your program to reassure the user that he is still using your program even though he changes screens several times.

LblName will hold the name of the current room. The form contains one menu with only two choices. If the user chooses to edit the game, the current form closes, and the editor opens with the current game loaded in it. If the user chooses to quit playing, the program returns to the main screen.

Building the Game Class Instance Variables

The Game class has only two instance variables. Both are used to keep track of the adventure data:

private int currentRoom = 1;

public Dungeon theDungeon = new Dungeon();

The currentRoom variable is used to specify which room is currently being displayed. theDungeon refers to the current dungeon structure, which, in turn, holds all the room data.

Initializing in Game_Load()

When a Windows program first loads, it automatically calls a Load() method. I decided to add all my initialization code to Game_Load() rather than to the constructor. I like the fact that the constructor

265

has all the Designer−generated code and my custom initialization goes in the Game_Load() method. The code for Game_Load() simply calls other custom methods:

private void Game_Load(object sender, System.EventArgs e) { setupRooms();

showRoom(1); } // end load

The setupRooms() method (as you will see shortly) gives default values to each of the rooms. The showRooms() method takes a room number as a parameter and displays the appropriate room on the form.

Setting Up the Rooms

The setupRooms() method is an interesting method. It’s an artifact from the early development process but is still a useful method. It sets up a default game. Mainly, I used setupRooms() before I had the save and load procedures working, to test the basic operation of the program. Later versions of the program made this method unnecessary, but it remains in the code in case I want to use it again:

private void setupRooms(){

//used to set up a 'default' game.

//Also used to test before editor was finished

theDungeon.Rooms[0] = new Room( "Game Over",

"You have lost", 0, 0, 0, 0);

theDungeon.Rooms[1] = new Room( "Start",

"Go North", 2, 0, 0, 0);

theDungeon.Rooms[2] = new Room( "Room 2",

"Go East", 0, 3, 1, 0);

theDungeon.Rooms[3] = new Room( "Room 3",

"Go South", 0, 0, 4, 2);

theDungeon.Rooms[4] = new Room( "You Win!",

"You have won!", 3, 0, 0, 0);

} // end setupRooms

Trick If you look at the Game class code on the CD−ROM, you will see that it also includes a Main() method. When I started writing this program, I began with the Room and Dungeon classes. Then I wrote the Game class as a standalone program. When I was able to get the basic form of the game working, I was ready to add the editor and main menu classes. It’s very common for programs to live through several iterations like this. Even after I added the other forms, I kept in some of the code that allows the Game class to be run as a standalone program, because I might want that functionality again.

266

Showing a Room

The main way the game communicates with the user is by loading values in the various labels, based on a given room number. The showRoom() method performs this task:

private void showRoom(int roomNum){ // show a room on the form

int nextRoom;

currentRoom = roomNum;

lblName.Text = theDungeon.Rooms[roomNum].Name; lblDescription.Text =

theDungeon.Rooms[roomNum].Description;

nextRoom = theDungeon.Rooms[roomNum].North; lblNorth.Text = theDungeon.Rooms[nextRoom].Name;

nextRoom = theDungeon.Rooms[roomNum].East; lblEast.Text = theDungeon.Rooms[nextRoom].Name;

nextRoom = theDungeon.Rooms[roomNum].South; lblSouth.Text = theDungeon.Rooms[nextRoom].Name;

nextRoom = theDungeon.Rooms[roomNum].West; lblWest.Text = theDungeon.Rooms[nextRoom].Name;

} // end showRoom

The showRoom() method requires a room number as a parameter. It then examines the Rooms array of theDungeon. It extracts the appropriate elements from the current room and copies the values to appropriate parts of the screen. I copied the Name property from the current room to the room name label (lblName). I also copied the Description property over to lblDescription. The Room class stores the indices of the rooms in each direction, but these indices are integers, which mean nothing to the user. Instead, I used the nextRoom variable to determine the index in each direction and then requested the Name property associated with that variable. This results in room names in each direction label.

Responding to Label Events

The user indicates which room he wants to visit next by clicking one of the direction labels. I added code to each of the direction arrows to respond to the user’s requests:

//label events

private void lblNorth_Click(object sender, System.EventArgs e) {

showRoom(theDungeon.Rooms[currentRoom].North);

}

private void lblEast_Click(object sender, System.EventArgs e) {

showRoom(theDungeon.Rooms[currentRoom].East);

}

private void lblSouth_Click(object sender, System.EventArgs e) {

showRoom(theDungeon.Rooms[currentRoom].South);

}

private void lblWest_Click(object sender,

267

System.EventArgs e) { showRoom(theDungeon.Rooms[currentRoom].West);

}

The code for all the direction labels follows a common plan. In each case, I simply call the showRoom() method with the index of the correct direction property of the current room.

Creating the Open Game Method

I actually created two distinct versions of the OpenGame() method. The first version was needed when the game program was meant to stand on its own. It calls the fileOpener() to request the file name from the user and then reads a dungeon from the file, using the binary formatter to deserialize the data. It then sets the current room to room number 1 and shows that room. Finally, it closes the file stream.

public void OpenGame(){ //no longer needed

//read the data from a binary file FileStream s;

BinaryFormatter bf = new BinaryFormatter(); if (fileOpener.ShowDialog() !=

DialogResult.Cancel){

s = new FileStream(fileName, FileMode.Open); theDungeon = (Dungeon) bf.Deserialize(s); currentRoom = 1;

showRoom(currentRoom);

s.Close();

} // end if

}// end openGame

public void OpenGame(Dungeon passedDungeon){ theDungeon = passedDungeon;

currentRoom = 1; showRoom(currentRoom);

} // end OpenGame

The second version of the OpenGame() method is used when the Game class is run as part of the Adventure Kit. In that case, I decided that the user should choose a game before calling the Game class. As you will see when you examine the MainMenu code, a dungeon will already be loaded in memory when the Game class is started from MainMenu. The new version of OpenGame simply takes a dungeon as a parameter and copies it to theDungeon. It then sets the current room to 1 and shows the room.

Trick Remember, there’s nothing wrong with having two versions of the same method, as long as they have different sets of parameters. The OpenGame() method is a good illustration of the power of polymorphism.

Responding to the Menu Events

The game form supports a very simple method. The two menu items allow the user to close the game form and return to the main window or to edit the currently loaded game.

The exit code clears the current form from memory using this.Dispose():

private void mnuExit_Click(object sender, System.EventArgs e) { this.Dispose();

} // end mnuExit

268

Соседние файлы в предмете Программирование на C++