NET Game 入門

XNA で作成する NET Game の基礎です。

前田稔(Maeda Minoru)の超初心者のプログラム入門

プログラムの説明

  1. XNA には簡単に NET 接続できるソフトが組み込まれています。
    この機能を利用して LAN でつながったパソコンの間でゲーム情報を交換してみましょう。
    通信のテストをするにはホスト(host)とクライアント(client)の二台以上のパソコンが必要です。
    一台(host)だけでもプログラムは動きますが、通信の確認は出来ません。
    私はデスクトップとノートの二台をつないでテストしました。
  2. host も client も同じプログラムを使用します。
    最初に起動したパソコンが host になり、後から起動したパソコンが client になります。
    プログラムを起動するとサインイン(ゲーマープロフィール)の画面が表示されます。
    初めてサインイン新画面を表示した場合はプロフィールの設定画面が表示されます。
    二回目以降はゲーマータグを選択するだけです。
  3. 最初のプログラムを起動すると LAN 内が検索されて、Session が開設されていないと Create Session で Session が開設されます。
    Session が開設されるとキャラクタが表示されて矢印キーの操作で動き回ります。
    次のプログラムを起動すると LAN 内が検索され Session が見つかると Join Session で host に接続されます。
    host に接続されると host 側と client 側のパソコンに、それぞれ二体のキャラクタが表示され、各パソコンから操作出来ます。
    host が故障などで終了しても、client 側が新たな host になり、通信が確保されるようになっています。
  4. 動き回るキャラクタを表示する Character Object Class です。
    Dir はキャラクタの進行方向(前後左右)です。
        class Character
        {
            Texture2D       image;          // テクスチャ
            public Vector2  Pos;            // キャラクタの座標
            public int      Dir = 0;        // キャラクタの向き
            int             frame = 0;      // アニメーション作業領域
    
            public Character(ContentManager content)
            {
                image = content.Load<Texture2D>("Chr17");   // 画像の読み込み
                Pos = new Vector2(100, 100);                // 初期座標
            }
    
            public void Update()
            {
                // 移動(上下左右)
                if (Dir == 0)   Pos.Y--;
                if (Dir == 1)   Pos.X++;
                if (Dir == 2)   Pos.Y++;
                if (Dir == 3)   Pos.X--;
    
                // 移動制限
                Pos.X = MathHelper.Clamp(Pos.X, 0, 740);
                Pos.Y = MathHelper.Clamp(Pos.Y, 20, 510);
            }
    
            // 二枚の画像を切り替えながら描画
            public void Draw(SpriteBatch spriteBatch, GameTime gameTime, Color Cor)
            {
                frame = ((int)gameTime.TotalGameTime.TotalMilliseconds / 300) % 2;
                spriteBatch.Draw(image, Pos, new Rectangle(0, 32 * (Dir*2+frame), 32, 32), Cor);
            }
        }
    
  5. このプログラムで使用する共通領域です。
    networkSession は Net Work の接続を管理する領域です。
    maxGamers はプレイヤー全体の数で、2 〜 31 の範囲で設定します。
    Zune 用ゲームの場合は 2 〜 8 名のプレイヤーにする必要があります。
    maxLocalGamers はこのネットワークセッションでの、同じゲームマシン上のローカルプレイヤーの最大数で 1 〜 4 の範囲で設定します。
    PacketWriter, PacketReader は NET からデータを送受信する Object です。
            // キーボード状態の宣言
            KeyboardState   keyboardState;
            int     playerDir = 0;          // キャラクタの向き
    
            // ネットワークセッションの宣言
            NetworkSession  networkSession;
            const int maxGamers = 16;       // プレイヤー全体の数(2 〜 31)
            const int maxLocalGamers = 4;   // 最大ローカルプレイヤーの数(1 〜 4)
    
            // パケットの宣言
            PacketWriter packetWriter = new PacketWriter();
            PacketReader packetReader = new PacketReader();
    
            // 出力メッセージ
            string  message = "sign in...";
            string  sendMessage = "--";
            string  readMessage = "--";
    
  6. コンストラクタでコンポーネントを追加します。
            public Game1()
            {
                graphics = new GraphicsDeviceManager(this);
                Content.RootDirectory = "Content";
    
                // コンポーネントの追加
                Components.Add(new GamerServicesComponent(this));
            }
    
  7. Initialize() からゲーマープロフィールを設定を呼び出します。
            protected override void Initialize()
            {
                // TODO: Add your initialization logic here
    
                base.Initialize();
                // ゲーマープロフィールを設定
                Guide.ShowSignIn(maxLocalGamers, false);
            }
    
  8. UnloadContent() で Content を開放します。
            protected override void UnloadContent()
            {
                // TODO: Unload any non ContentManager content here
                Content.Unload();
                base.UnloadContent();
            }
    
  9. Update() メソッドです。
    キーボード、コントローラ状態の取得して、Escape, Back で終了します。
    networkSession が null のとき、ネットワークに接続する SetSession() を呼び出します。
    ネットワークに接続されているときは、UpdateNetworkSession() で画面を更新します。
            protected override void Update(GameTime gameTime)
            {
                // Escape キーで終了
                if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed  ||
                    Keyboard.GetState().IsKeyDown(Keys.Escape))
                {   this.Exit();  }
    
                // TODO: Add your update logic here
    
                // セッションの設定とアップデート Screen
                if (networkSession == null)
                {   SetSession();  }
                else
                {   UpdateNetworkSession();  }
    
                base.Update(gameTime);
            }
    
  10. ネットワークに接続する SetSession() です。
    LAN 内を検索してセッションを探します。
    セッションが見つからないときは "Create Session" で host を開設します。
    セッションが見つかったときは "Join Session" で 接続します。
    参加イベント, 離脱イベント, 切断イベントを追加します。
            public void SetSession()
            {
                if (IsActive)
                {
    Console.WriteLine("SetSession が呼ばれました");
                    try
                    {
                        // セッションを探す
                        using (AvailableNetworkSessionCollection availableSessions =
                                    NetworkSession.Find(NetworkSessionType.SystemLink, maxLocalGamers, null))
                        {
                            // セッションが見つからない
                            if (availableSessions.Count == 0)
                            {
                                try
                                {
                                    // セッションの作成
                                    networkSession = NetworkSession.Create(NetworkSessionType.SystemLink,
                                                                           maxLocalGamers, maxGamers);
                                    message = "Create Session";
                                }
                                catch (Exception e)
                                {   message = e.Message;  }
                            }
                            else
                            {
                                // セッションへ接続
                                networkSession = NetworkSession.Join(availableSessions[0]);
                                message = "Join Session";
                            }
                            // 参加イベントの追加
                            networkSession.GamerJoined += new EventHandler<GamerJoinedEventArgs>(SessionJoin);
                            // 離脱イベントの追加
                            networkSession.GamerLeft += new EventHandler<GamerLeftEventArgs>(SessionLeft);
                            // 切断イベントの追加
                            networkSession.SessionEnded += new EventHandler<NetworkSessionEndedEventArgs>(SessionEnd);
                        }
                    }
                    catch (Exception e)
                    {   message = e.Message;  }
                }
            }
    
  11. UpdateNetworkSession() で、ネットワークからゲームの情報を取得して画面を更新します。
    キーボードで自分のキャラクタを操作して、自分の情報をネットワークに送信します。
    packetWriter.Write(player.Dir);
    packetWriter.Write(player.Pos);
    ネットワークから他のプレーヤーの情報を受信します。
    senderPlayer.Dir = packetReader.ReadInt32();
    senderPlayer.Pos = packetReader.ReadVector2();
            public void UpdateNetworkSession()
            {
                // キーボードの状態を取得
                keyboardState = Keyboard.GetState();
                // 移動(キーボード)
                if (keyboardState.IsKeyDown(Keys.Up))       playerDir = 0;
                if (keyboardState.IsKeyDown(Keys.Right))    playerDir = 1;
                if (keyboardState.IsKeyDown(Keys.Down))     playerDir = 2;
                if (keyboardState.IsKeyDown(Keys.Left))     playerDir = 3;
    
                // パケットの送信
                foreach (LocalNetworkGamer gamer in networkSession.LocalGamers)
                {
                    // 移動処理
                    Character player = gamer.Tag as Character;
                    player.Dir = playerDir;
                    player.Update();
    
                    // 移動方向をパケットに書き込む
                    packetWriter.Write(player.Dir);
                    // 移動先をパケットに書き込む
                    packetWriter.Write(player.Pos);
    
                    // 全接続先へパケットを送信
                    gamer.SendData(packetWriter, SendDataOptions.InOrder);
    
                    // 送信パケット内容
                    sendMessage = "Direction: " + player.Dir + ", Position: " + player.Pos.ToString();
                }
    
                // セッションの更新
                networkSession.Update();
    
                // セッションが切断していたら戻る
                if (networkSession == null)     return;
    
                // パケットの読み込み
                foreach (LocalNetworkGamer gamer in networkSession.LocalGamers)
                {
                    while (gamer.IsDataAvailable)
                    {
                        NetworkGamer sender;
    
                        // データの受信
                        gamer.ReceiveData(packetReader, out sender);
    
                        // 自分自身からの送信の場合はスキップ
                        if (sender.IsLocal)     continue;
    
                        // 送り主のキャラクタの呼出
                        Character senderPlayer = sender.Tag as Character;
    
                        // パケットから移動方向を読み込む
                        senderPlayer.Dir = packetReader.ReadInt32();
                        // パケットから移動先を読み込む
                        senderPlayer.Pos = packetReader.ReadVector2();
    
                        // 受信パケット内容
                        readMessage = "Sender: " + sender.Gamertag + ", Direction: " + senderPlayer.Dir
                                                 + ", Position: " + senderPlayer.Pos.ToString();
                    }
                }
            }
    
  12. 描画処理を行う Draw() メソッドです。
    foreach で接続している全てのキャラクタを描画します。
    自分のキャラクタが解るように、自分以外は少し色を変えて描画しています。
            protected override void Draw(GameTime gameTime)
            {
                graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
    
                // TODO: Add your drawing code here
                spriteBatch.Begin();
    
                // ウインドウを描画
                if (networkSession != null)
                {
                    string wstr = "send: " + sendMessage + "  read: " + readMessage;
                    base.Window.Title = wstr;
    
                    // 接続している全てのキャラクタを描画
                    foreach (NetworkGamer gamer in networkSession.AllGamers)
                    {
                        // キャラクターの描画
                        Character player = gamer.Tag as Character;
                        if (gamer.IsLocal)  // 自分を White で描画
                            player.Draw(spriteBatch, gameTime, Color.White);
                        else                // 自分以外は色を変えて描画
                            player.Draw(spriteBatch, gameTime, new Color(0xD0, 0xC0, 0xB0));
                    }
                }
                spriteBatch.End();
    
                base.Draw(gameTime);
            }
    
  13. セッション参加イベントで呼び出されるメソッドです。
            public void SessionJoin(object sender, GamerJoinedEventArgs e)
            {
                // セッション参加メッセージ
                message += "\nJoin: " + e.Gamer.Gamertag;
                // キャラクタの生成
                e.Gamer.Tag = new Character(Content);
                Console.WriteLine(message);
            }
    
  14. セッション離脱イベントで呼び出されるメソッドです。
            public void SessionLeft(object sender, GamerLeftEventArgs e)
            {
                // セッション離脱メッセージ
                message += "\nLeft: " + e.Gamer.Gamertag;
                Console.WriteLine(message);
            }
    
  15. セッション終了イベントで呼び出されるメソッドです。
            public void SessionEnd(object sender, NetworkSessionEndedEventArgs e)
            {
                // セッション終了メッセージ
                message = e.EndReason.ToString();
                // 切断処理
                networkSession.Dispose();
                networkSession = null;
                Console.WriteLine(message);
            }
    
  16. ネットワークに接続して、実際に操作してみないと良く解らないと思います。
    ぜひ完成させて動かしてみて下さい。

【NOTE】

  1. サインイン(ゲーマープロフィール)は Initialize() から呼び出しています。
    Guide.ShowSignIn(maxLocalGamers, false);
    初めてサインイン新画面を表示した場合はプロフィールの設定画面が表示されます。
    二回目以降はゲーマータグを選択するだけです。
  2. コンポーネントを Add すると、サインイン画面に関係なく「Player1 がサインインしました」が表示されます。
    Components.Add(new GamerServicesComponent(this));
    サインインで選択した ID は SessionJoin() で取得します。
  3. SetSession() は Update() から呼び出します。
    host が故障などで終了しても、client 側が新たな host になり、通信が確保されるようになっています。
    if (networkSession == null)
    { SetSession(); }

超初心者の方のために全ソースコードを掲載します。 (^_^;)
全ソースコード

前田稔(Maeda Minoru)の超初心者のプログラム入門

超初心者のプログラム入門(XNA(C#) game program)