WisdomSoft - for your serial experiences.

11.6 ホスト移行

通常、セッション中にホストが切断されると、セッションに参加している全てのゲーマーの接続が失われます。AllowHostMigration プロパティを true に設定することで、ホストがセッションから抜けたとき、別のゲーマーがホストを自動的に引き継ぎます。

11.6.1 自動的にホストを移行する

オンライン対戦中に、ホスト権限のゲーマーがゲームから抜け出したり、何らかの理由でネットワークから切断されてしまうと、セッションの状態が自動的に End となりゲームが終了します。他の参加者にとっては、せっかく参加したゲームが突然中断されることになり、再びセッションを検索する作業からやり直さなければならなくなってしまいます。このような事態を回避する方法にホストの移行が考えられます。

ホストの移行は、ホスト権限を持つゲーマーがセッションを中断すると、自動的にセッションに残っている他のゲーマーから自動的に新しいホストが選択される仕組みです。デフォルトでは、ホストの移行は実行されません。ホストの移行を有効にするには AllowHostMigration プロパティを true に設定します。

NetworkSession クラス AllowHostMigration プロパティ
public bool AllowHostMigration { get; set; }

このプロパティはホストの移行が有効であれば true を、そうでなければ false を返します。このプロパティの値を変更できるのはホストだけです。

ホストの移行が発生してもホスト権限が移行するだけで、ゲームデータなどが引き継がれるわけではありません。ホストに情報を集約させるクライアントサーバー型の通信構造を持つゲームであれば、ホストの移行を行ってもゲームをそのまま実行することは難しく、一度ゲームを初期化する必要があるでしょう。一方で、ピアツーピア型の通信構造であればホストの移行が発生しても大きな影響を受けません。

ホストの移行が発生すると HostChanged イベントが発生します。

NetworkSession クラス HostChanged イベント
public event EventHandler<HostChangedEventArgs> HostChanged

このイベントに登録したデリゲートは、パラメータに Microsoft.Xna.Framework.Net.HostChangedEventArgs クラスのオブジェクトを受け取ります。

Microsoft.Xna.Framework.Net.HostChangedEventArgs クラス
public class HostChangedEventArgs : EventArgs

このクラスは、新しくホストとなるゲーマーを NewHost プロパティから、以前ホストだったゲーマーを OldHost プロパティから提供します。

HostChangedEventArgs クラス NewHost プロパティ
public NetworkGamer NewHost { get; }
HostChangedEventArgs クラス OldHost プロパティ
public NetworkGamer OldHost { get; }

これらのプロパティから、どのゲーマーからホストが移ったのかを調べられます。

コード1 (Win, Xbox 360)
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Net;

public class Test : Game
{
    public static void Main(string[] args)
    {
        using (Game game = new Test()) game.Run();
    }

    private GraphicsDeviceManager graphicsDeviceManager;
    private NetworkSession session;
    private AvailableNetworkSessionCollection sessions;
    private SpriteBatch sprite;
    private SpriteFont font;
    private string text, eventLog;

    public Test()
    {
        graphicsDeviceManager = new GraphicsDeviceManager(this);
        Components.Add(new GamerServicesComponent(this));
        eventLog = "";
    }

    protected override void LoadContent()
    {
        sprite = new SpriteBatch(GraphicsDevice);
        font = Content.Load<SpriteFont>("Content/TestFont");
        base.LoadContent();
    }

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

        SignedInGamer gamer = Gamer.SignedInGamers[PlayerIndex.One];
        if (gamer == null)
        {
            text = "Please sign in as player one.";
            if (!Guide.IsVisible) Guide.ShowSignIn(1, true);
        }
        else
        {
            if (session != null) UpdateSession(state);
            else if (sessions != null) UpdateFindSession(state);
            else UpdateMenu(state);
        }
        base.Update(gameTime);
    }

    private void UpdateMenu(GamePadState state)
    {
        text = "A BUTTON: Create a network session\n";
        text += "X BUTTON: Find a network session\n\n";

        if (state.Buttons.A == ButtonState.Pressed)
        {
            session = NetworkSession.Create(NetworkSessionType.SystemLink, 1, 16);

            //ホスト移行を許可
            session.AllowHostMigration = true;
            eventLog = "";
        }
        else if (state.Buttons.X == ButtonState.Pressed)
        {
            sessions = NetworkSession.Find(NetworkSessionType.SystemLink, 1, null);
            eventLog = "";
        }
    }

    private void UpdateSession(GamePadState state)
    {
        text = "Network session is available\n";
        text += "B BUTTON: Dispose a network session\n\n";

        session.Update();

        //セッション参加者のリストを表示
        foreach (NetworkGamer gamer in session.AllGamers)
            text += gamer.Gamertag + (gamer.IsHost ? ", HOST" : "") + "\n";

        text += "\n" + eventLog;

        if (state.Buttons.B == ButtonState.Pressed)
        {
            session.Dispose();
            session = null;
        }
    }

    private void UpdateFindSession(GamePadState state)
    {
        text = "Network session search results\n";
        text += "B BUTTON: Return to menu\n\n";

        if (state.Buttons.B == ButtonState.Pressed) sessions = null;
        else if (sessions.Count == 0) text += "Session not found";
        else
        {
            session = NetworkSession.Join(sessions[0]);

            //ホスト移行イベントにデリゲートを登録
            session.HostChanged += session_HostChanged;
            sessions = null;
        }
    }

    private void session_HostChanged(object sender, HostChangedEventArgs e)
    {
        //新しいホストと以前のホストの情報をログに保存
        eventLog += "Host changed: NewHost=" + e.NewHost.Gamertag + ", OldHost=" + e.OldHost.Gamertag + "\n";
    }

    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.White);

        sprite.Begin();
        sprite.DrawString(font, text, Vector2.Zero, Color.Black);
        sprite.End();

        base.Draw(gameTime);
    }
}
実行結果
コード1 実行結果
コード1 実行結果

コード1で作成されるセッションは、Create() メソッドでオブジェクトを取得した直後に AllowHostMigration プロパティを true に設定しています。よって、ホストがセッションを抜け出すと、セッションに参加している別のゲーマーが自動的にホストを引き継ぎます。

UpdateFindSession() メソッド内の処理では、セッションを検索して Join() メソッドから得られた NetworkSession オブジェクトの HostChanged イベントにデリゲートを登録しているので、ホストが変更されると session_HostChanged() メソッドが呼び出さます。このメソッドでは、移行前のホストと移行後のホストのゲーマータグを eventLog フィールドに加えています。