Plugging into the Game Engine


Before you start writing any code to hook things together, you need to make sure that you have the required variables and constants declared. Add the declarations from Listing 17.1 to your game engine class.

Listing 17.1. Declarations
 // Constants for starting locations and rotations within the levels private static readonly Vector3 Player1StartLocation = new Vector3(-2600,0,-2600); private static readonly Vector3 Player2StartLocation = new Vector3(2600,0,2600); private const float Player1StartRotation = (float)Math.PI / 4.0f; private const float Player2StartRotation = (float)Math.PI + (float)Math.PI / 4.0f; // Store the sky box private Skybox worldSky = null; // Store the level private Level worldLevel = null; // The local player private Player localPlayer = null; // The remote player private Player remotePlayer = null; // The bullet collection private Bullets allBullets = new Bullets(); // The sound engine private SoundEngine sounds = null; // The network engine private NetworkEngine network = null; private float networkTime = 0.0f; // How often should we update the network data? private float minNetworkTime = 0.1f; // is the world loaded? private bool isWorldLoaded = false; 

The constants listed here are pretty self-explanatory. When the players start (either player one or two), they need to start in a particular location, and it would probably be beneficial to have them rotated a certain way. You wouldn't want to start the game looking at the wall! The rest of the variables are the classes you've written in the previous chapters, such as the sky box and the level classes. Notice that there are two player classes, one for your local player and the other for the remote player that can join your session. The collection of bullets is stored in the game engine as well.

You can see that the two "engines" are declared here as well. The sound engine is relatively small and self-explanatory, but the networking engine has a few different things that go along with it. First, you see the network time, which stores the last time a network packet was sent, and then a minimum network time, which is the minimum amount of time that must elapse before you can send any more network packets. This time is here so you can throttle the amount of data you're passing into the network. If too much data is being passed, this value increases, but it decreases if the network is too idle.

Now, you want to create the sound and network engines somewhere, so find the constructor for the game engine. Create these objects before you initialize the graphics engine. Add the code in Listing 17.2 to the end of your constructor to initialize the network object.

Listing 17.2. Initializing Network Objects
 // Try to create the network engine as well network = new NetworkEngine(); // Hook the events network.DestroyedPlayer += new     Microsoft.DirectX.DirectPlay.PlayerDestroyedEventHandler(     OnRemoveRemotePlayer); network.NewPlayer += new     Microsoft.DirectX.DirectPlay.PlayerCreatedEventHandler(     OnCreateRemotePlayer); 

Next, find the Main method in your game class and add this line before the call to the MainLoop method from the sample framework to initialize the sound engine:

 // Try to create the sound engine tankersEngine.sounds = new SoundEngine(sampleFramework.Window); 

The sound engine will handle the code internally if it fails because sound isn't a "requirement" for the game. Remember from your implementation, the sound engine holds a variable designed to not play sounds if the initialization fails for some reason. The network code, however, is a requirement, and you'll notice that there are no checks here for any failures. That is handled by the caller of this method, in this case, the "main" method of the application. Notice also that the events for the new and destroyed players are hooked here. You need to include these implementations into the game engine as well (see Listing 17.3). Also remember to add a using clause for DirectPlay into your game engine file (using Microsoft.DirectX.DirectPlay;); otherwise, the preceding code won't compile.

Listing 17.3. Handling Player Connections
 private void OnRemoveRemotePlayer(object sender,      Microsoft.DirectX.DirectPlay.PlayerDestroyedEventArgs e) {     lock(remotePlayer)     {         // The remote player was destroyed.         remotePlayer.IsPlayerConnected = false;     } } private void OnCreateRemotePlayer(object sender,      Microsoft.DirectX.DirectPlay.PlayerCreatedEventArgs e) {     lock(remotePlayer)     {         // Hey, the remote player was created         remotePlayer.IsPlayerConnected = true;         // Tell the remote player what color I am         network.SendNewColor(localPlayer.Color);     } } 

The events that get fired from DirectPlay happen on different threads, which means it's possible to have more than one of these events fire simultaneously (especially on a computer with more than one processor). To make this less of an issue, the remote player object is locked. What the lock keyword does is allow only a single thread access to the variable inside the lock block at any given time, which ensures that two threads won't be accessing the remote player simultaneously.

As you see here, the actual events simply call a new method that (depending on whether the player is joining or leaving) sets the IsPlayerConnected property to true or false, and if they are joining, it sends the local player's color over. So I know what you're thinking, "Wait a minute: I never defined a property like that." You're right, so do that now (in the player class, naturally):

 public bool IsPlayerConnected {     get { return isPlayerActive; }     set { isPlayerActive = value; } } 

This property is used throughout the game engine to determine whether the remote player has been created and connected yet. With the networking and sound engines out of the way for now, you need to make sure that the user interface gets created and called. In the OnCreateDevice method right after you create the debug font, add the following lines of code:

 // Create the main UI Screen mainScreen = new MainUiScreen(device, screenWidth, screenHeight, this); mainScreen.HostGame += new EventHandler(OnHostGame); mainScreen.JoinGame += new EventHandler(OnJoinGame); mainScreen.Quit += new EventHandler(OnMainQuit); 

The main user interface can inform the game engine that it is ready to play by either hosting a new session or joining an existing session or that the application should quit. You can see the implementations of these event handlers in Listing 17.4.

Listing 17.4. Starting a Game
 /// <summary> /// The user is going to play now and be the host, load everything /// </summary> private void OnHostGame(object sender, EventArgs e) {     // Get the screen and draw it so the loading screen shows up     MainUiScreen screen = sender as MainUiScreen;     FullsceneRender();     // Load the world     LoadWorld(true, screen.TankColor);     // Now host     network.Host();     // We're done with the main menu     isMainMenuShowing = false; } /// <summary> /// The user is ready to join a game now /// </summary> private void OnJoinGame(object sender, EventArgs e) {     // Get the screen and draw it so the loading screen shows up     MainUiScreen screen = sender as MainUiScreen;     FullsceneRender();     // Load the world     LoadWorld(false, screen.TankColor);     // Now join     network.Join(screen.HostName);     // We're done with the main menu     isMainMenuShowing = false; } /// <summary> /// The user wants to quit, let them /// </summary> private void OnMainQuit(object sender, EventArgs e) {     // Allow the application to quit now     sampleFramework.CloseWindow(); } 

In the quit scenario, the only obvious solution here is to close the form, which is what you do. When the other options are selected (either hosting or joining a new session), the scene is rendered once more. The user interface is still showing for this render, but it is updated to show the loading screen, which allows the game engine to load the models in the background in the LoadWorld method (see Listing 17.5). After the world is loaded, the network either hosts a new session or joins an existing one, depending on the options chosen.

Listing 17.5. Getting Engine Ready for Play
 /// <summary> /// Will load everything the world needs /// </summary> private void LoadWorld(bool isHost, TankColors color) {     // Create the players and hook the events     localPlayer = new Player(device, this, Environment.UserName, true);     localPlayer.Color = color;     localPlayer.Fired += new EventHandler(OnPlayerFire);     // Now create the remote player     remotePlayer = new Player(device, this, string.Empty, false);     remotePlayer.Fired += new EventHandler(OnPlayerFire);     network.UpdatePlayer(remotePlayer);     // Start at the default location     if (isHost)     {         // Host starts as player 1, remote as player2         localPlayer.Position = Player1StartLocation;         localPlayer.Rotation = Player1StartRotation;         remotePlayer.Position = Player2StartLocation;         remotePlayer.Rotation = Player2StartRotation;     }     else     {         remotePlayer.Position = Player1StartLocation;         remotePlayer.Rotation = Player1StartRotation;         localPlayer.Position = Player2StartLocation;         localPlayer.Rotation = Player2StartRotation;     }     // Create the world sky box     worldSky = new Skybox(device);     // Create the level     worldLevel = new Level(device);     // Now the world is loaded     isWorldLoaded = true; } 

The object of this method is to load all the data that the world (game) requires while playing. This part can take a few seconds even on a faster computer, which is why the loading screen is displayed in the user interface. Notice that not only is the local player created now, but the remote player is created as well. When the remote player joins the game, you don't want to have to wait for his data to load, too, so you do it all at once, and then when that player joins, you simply mark him as connected (as you did in the NewPlayer event handler just a short while ago). Notice too that the fire event is hooked on each player. (The implementation is in Listing 17.6.)

Whether or not the local player is the host determines where the local and remote players are placed. Notice that the host is always placed in the Player 1 position, and the joining player is always placed at the Player 2 position. After the players are placed, the sky box and the level are loaded and the world is marked as being loaded (because it now is). Back in the event handlers, you'll notice that the main menu variable is marked as false because it is no longer showing the user interface and the game is ready to be played.

Listing 17.6. Handling Player Firing
 /// <summary> /// The player fired its weapon /// </summary> private void OnPlayerFire(object sender, EventArgs e) {     // Play the fire sound     sounds.FireWeapon();     Player p = sender as Player;     // Make sure only one thread has access to this collection     lock(allBullets)     {         p.FireRound(device, allBullets);     }     if ((p.IsLocal) && (remotePlayer.IsPlayerConnected))     {         // Also send this over network         network.SendFireMessage();     } } 

I should point a few things out here. First, notice that the fire weapon sound is played when the gun is fired. Finally, you'll be able to hear some sound! Next, notice the FireRound method (which takes in the bullets' collection) is wrapped inside a Lock statement. Because this event can be fired from the networking engine (and thus can happen on a separate thread), you need to ensure that only one thread has access to the bullets' collection at a time. Assuming that the local player is the one firing the bullet, and the remote player has been connected, you also want to send the information over the network that you just fired the bullet. This step allows the remote player to see it as well.

You also need to make sure that the IsLocal property is defined in the player class:

 public bool IsLocal {     get { return isPlayerLocal; } } 



Beginning 3D Game Programming
Beginning 3D Game Programming
ISBN: 0672326612
EAN: 2147483647
Year: 2003
Pages: 191
Authors: Tom Miller

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net