问题

你想创建一个网络会话(session),这样其他Xbox 360平台或PC就可以找到并加入到你的会话。

解决方案

一台机器首先需要开始一个网络会话,这可以通过使用NetworkSession. Create方法很容易地做到。创建会话的机器就是会话主机。

在创建会话之后,所有连接到这个会话的机器,包括主机,都会监听这个会话产生的任何事件,诸如玩家加入或离开会话等。

工作原理

你需要包含Microsoft.XNA.Framework.Net命名空间,这可以通过在代码顶部添加using代码做到:

using Microsoft.XNA.Framework.Net

本教程中,程序会在三个自定义状态中循环。如前一个教程所述,开始时你处于SignIn状态,要求玩家选择一个账号。前一个教程中的SignedIn状态由CreateSession状态替换, CreateSession状态中你将创建一个新的网络会话并监听它的事件。最后结束于InSession状态。

首先定义这些状态:

public enum GameState ...{ SignIn, CreateSession, InSession}

SignIn状态前一个教程已经讨论过了。让我们开始编写CreateSession状态的代码。

创建一个网络会话

在这个状态中,你将创建一个新的网络会话。首先将这个变量添加到代码中:

NetworkSession networkSession; 

你可以创建一个新的网络会话并使用NetworkSession . Create方法将它存储到刚才定义的变量中:

networkSession = NetworkSession.Create(NetworkSessionType.SystemLink, 4,16); 

这行代码会让机器创建一个其他玩家可以连接的新会话。如你所见,这个方法需要三个参数。

第一个参数指定会话类型。如果所有的玩家连接到同一台机器上应该创建NetworkSessionType. Local类型的会话,例如,四个玩家都有各自的手柄连接到同一台Xbox 360上。

如果一个或多个玩家在不同的机器上,这些机器连接到相同的网络,你应该创建一个NetworkSessionType. SystemLink类型的会话。NetworkSessionType. PlayerMatch类型的会话与SystemLink类型工作方式类似,但是它允许玩家通过Internet连接到会话,使用的是Live的服务,这种情况下,为了使用这些服务,所有参与的玩家都要需要购买Live Gold成员。

注意:因为Zune只能创建连接到另一台Zune的网络连接,在Zune上只能使用NetworkSessionType. Local和NetworkSessionType. SystemLink类型的会话。

第二个参数指定连接到同一个机器的多个玩家是否可以加入网络。如果不是,你需要指定每台机器只有一个玩家。例如,如果你想开始一个本地会话,你应该允许超过一个玩家可以连接到相同的机器。

注意:一个本地玩家指在代码运行的这台机器上的用户。XNA 3.0在每台Windows机器或Zune上只支持一个本地玩家,在Xbox 360允许最多四个玩家。只有另三个玩家都连接到同样的机器上时四个玩家中的每一个才被认为是本地的。

最后一个参数指定可以连接到这个会话的最大玩家数。因为你想处理多玩家游戏,这个数字至少为2,上限是31。

NetworkSession.Create另一个重载方法可以再接受两个参数。第一个参数指定保留给朋友的玩家插槽数量,通过这个方式,只有你标记为朋友的玩家才可以连接到会话。第二个参数让你可以用一个NetworkSession属性作为会话的标签,这样其他玩家可以很容易地找到它。可参见教程3-8看这样一个例子。

注意:如果机器没有连接到网络上,NetworkSession. Create方法会报错,所以你将它封装在一个try-catch结构中。

现在有了一个新会话,你可以设置一些属性了。一个重要的属性定义了如果主机退出的话应该发生什么事。你可以通过将AllowHostMigration属性设置为true指定XNA自动选择clients/peers中的一个变成新的主机:

networkSession.AllowHostMigration = true; 
networkSession.AllowJoinInProgress = false;

如你所见,你也可以指定游戏开始后哪个客户端可被允许加入到会话中。

注意:要特别注意主机的迁移。如果当会话退出时游戏数据只保存在老的主机中,不可能将这个数据自动传送到新主机中。你需将重要的数据复制到至少两台机器上,这样的话,如果主机退出,你可以将这个数据复制到新主机上。

链接到会话事件

现在你的机器是一个网络会话的主机,然后需要知道其他玩家是否连接到这个会话。你可以通过链接一个自定义的方法到网络会话的GamerJoined 事件中做到这点。只要一个玩家加入会话,会话就会自动引发GamerJoined事件。结果是,链接到这个事件的所有方法都会被调用。

下面这行代码将自定义的GamerJoinedEventHandler方法连接到GamerJoined事件:

networkSession.GamerJoined += GamerJoinedEventHandler; 

在GamerJoinedEventHandler方法中,你可以放置当一个新玩家加入到会话中时需要执行的代码。下面的例子中将一行包含玩家名称的文字显示在屏幕上:

void GamerJoinedEventHandler(object sender, GamerJoinedEventArgs e)
...{
    log.Add(e.Gamer.Gamertag + " joined the current session");
}

和所有事件处理方法一样,这个方法接受引发事件的对象(本例中是网络会话),还有第二个参数包含事件类型的特定信息。本例中,GamerJoinedEventArgs包含对应加入这个会话的玩家的Gamer对象。

网络会话可以引发你想要监听的其他事件,包括GamerLeft事件、GameStarted事件、GameEnded事件(这个事件表示会话从大厅切换到游戏模式,可参见教程8-7)、SessionEnded事件和HostChanged事件。

在AllowHostMigration设置为false且主机退出会话时或主机在网络会话上调用Dispose方法时都会引发SessionEnded事件。在AllowHostMigration设置为true且主机退出会话时会引发HostChanged事件。

下面的代码通过网络会话监听GamerJoined,GamerLeft和HostChanged事件,并在屏幕上显示对应的信息:

void HookSessionEvents()
...{
    log.Add("Listening for session events");
    networkSession.GamerJoined += GamerJoinedEventHandler; 
    networkSession.GamerLeft += GamerLeftEventHandler; 
    networkSession.HostChanged += HostChangedEventHandler;
}

void GamerJoinedEventHandler(object sender, GamerJoinedEventArgs e) 
...{
    log.Add(e.Gamer.Gamertag + " joined the current session"); 
}

void GamerLeftEventHandler(object sender, GamerLeftEventArgs e) 
...{
    log.Add(e.Gamer.Gamertag + " left the current session"); 
}

void HostChangedEventHandler(object sender, HostChangedEventArgs e) 
...{
    log.Add("Host migration detected");
    NetworkSession eventRaisingSession = (NetworkSession)sender;
    if (eventRaisingSession.IsHost)
        log.Add("This machine has become the new Host!"); 
}

在主机迁移的情况中,sender对象(这里是指一个网络会话)首先转换为一个NetworkSession对象,这样你可以获取它的属性。你可以使用其中的IsHost属性检测是否这个机器变成了会话的新主机。

确保在创建会话后立即调用HookSessionEvents方法,下面是Update方法中的CreateSession状态的代码:

case GameState.CreateSession:
...{
    networkSession = NetworkSession.Create(NetworkSessionType.SystemLink, 4, 16); 
    networkSession.AllowHostMigration = true;
    networkSession.AllowJoinInProgress = false;
    log.Add("New session created");

    HookSessionEvents();
    currentGameState = GameState.InSession; 
}

会话被创建,设置AllowHostMigration和AllowJoinInProgress的值,程序会监听任何由会话引发的事件。

更新网络会话

连接到一个会话后,你需要以一定的时间间隔更新它。显然应该在游戏的更新过程中放置这个代码。现在,在InSession 状态中只需进行一个操作:

case GameState.InSession:
...{
    networkSession.Update();
}
break;
代码

这里是最终的Update方法。程序开始时处于SignIn状态,之后在CreateSession状态中创建一个会话。程序结束时处于InSession状态。

protected override void Update(GameTime gameTime)
...{
    if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) 
        this.Exit();

    if (this.IsActive)
...    {
        switch (currentGameState)
...       {
            case GameState.SignIn:
...            {
                if (Gamer.SignedInGamers.Count < 1)
...                {
                    Guide.ShowSignIn(1, false);
                    log.Add("Opened User SignIn Interface");
                }
                else
...                {
                    currentGameState = GameState.CreateSession; 
                    log.Add(Gamer.SignedInGamers[0].Gamertag + " logged in - proceed to CreateSession");
                } 
            }
            break;

            case GameState.CreateSession:
...            {
                networkSession = NetworkSession.Create(NetworkSessionType.SystemLink, 4, 16);
                networkSession.AllowHostMigration = true;
                networkSession.AllowJoinInProgress = false;
                log.Add("New session created");

                HookSessionEvents();
                currentGameState = GameState.InSession;
            }
            break;

            case GameState.InSession:
...            {
                networkSession.Update();
            }
            break;
        }
    }
    base.Update(gameTime); 
}