XNA入門

STEP.06 XNA で複数プレイヤーを動かす

このチュートリアルの完成イメージ

完成イメージ@ 完成イメージA

ダウンロード

概要・解説

前回までのチュートリアルでは、Windows Form アプリケーションを使用してクライアントとサーバー間の通信を行うところまで作成しました。

このチュートリアルでは XNA を使用してサーバーから受け取った情報でプレイヤーを動かしてみたいと思います。 STEP.02 で作成したXNA のプロジェクトに、STEP.05 の内容を加える形で作成しました。 また、サーバー側の処理もログインした後に、初期情報として周りのプレイヤーを追加する情報を受け取るように修正しています。 ログインの処理までは、前回作成した Windows Form アプリケーションに手を加えたものを使用して、XNA の起動後に ReaderThread と WriterThread を開始しています。 前回同様、幾つかウインドウを立ち上げて各キャラクタが動くことを確認してください。

ちょっとそれっぽい見た目になってきたと思いますが、少し動かすとプレイヤーの位置がバラバラであることに気がつくと思います。 これは、それぞれのクライアントで各プレイヤーの座標を管理しているために発生します。 これを解決するためにサーバー側でプレイヤーの座標を管理する対応が必要となると思われます。 この問題はなかなか厄介ですが次回以降少しずつ解決していきたいと思います。

注意事項

このチュートリアルは、REFMAP様が配布しているフリー画像素材を使用しています。 このソフト内で使用されている画像を、このゲームを遊ぶ以外の用途には使用しないで下さい。
※ 詳細は First Seed Material をご確認ください。

ソリューション設定

STEP.06 のソリューション設定は次の図のようになっています。

ソリューション設定@
    control (前回まで作成していた ClientEmulator を取り込みました。)
  • INetwork.cs
  • NetworkForm.cs

※クライアント側の名前空間を修正しています。サーバー側に合わせて小文字で始まるように修正しました。


JAVA ソースコード(サーバー)

※重要と思われるクラスを抜粋しています。全ソースコードはページ上部のダウンロードからご確認ください。

Server.java
package sample2DRPG;

import java.nio.*;
import java.io.*;
import java.nio.channels.*;
import java.net.*;
import java.util.*;

import sample2DRPG.charactor.*;
import sample2DRPG.command.*;
import sample2DRPG.message.*;
import sample2DRPG.util.*;

public class Server implements Runnable
{
    // デフォルトポート番号
    public static final int DEFAULT_PORT = 65000;

    // キー選択処理のタイムアウトを定義(ミリ秒)
    protected static final int SELECT_TIME = 100;
    
    // ポート番号
    protected int port;

    // バッファ
    protected ByteBuffer buffer;
    
    // 終了予定の接続
    protected LinkedList<SelectionKey> closeq 
        = new LinkedList<SelectionKey>();

    // 接続のリスト
    protected HashMap<Integer, Connection> conmap 
        = new HashMap<Integer, Connection>();

    // TODO: プレイヤー以外も管理するようにする予定
    // オブジェクトマップ
    protected HashMap<Integer, Player> objmap 
        = new HashMap<Integer, Player>();

    // セレクタ
    private Selector selector;
    
    // サーバーソケットチャネル
    private ServerSocketChannel serverSocketChannel;

    /**
     * コンストラクタ
     * コンストラクタでポートを指定しなかった場合はデフォルトポート番号(65000)が使用されます。
     */

    public Server()
    {
        this(DEFAULT_PORT);
    }
    
    public Server(int port)
    {
        this.port = port;
                
        // 書き込み用バッファを初期化
        buffer = ByteBuffer.allocate(8192);            
        buffer.order(ByteOrder.LITTLE_ENDIAN);

        // メッセージマネージャ生成
        MessageManager.create();
    }

    /**
     * メイン処理
     */

    public void run()
    {
        try {
            selector = Selector.open();
    
            serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.configureBlocking(false);
            serverSocketChannel.socket().bind(new InetSocketAddress(port));
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

            System.out.println("Startup server [port=" + port + "]");
            
            // ループ処理
            while (true) {
                selector.select(SELECT_TIME);
                Iterator<SelectionKey> it = selector.selectedKeys().iterator();
                while (it.hasNext()) {
                    SelectionKey key = it.next();
                    it.remove();

                    if (key.isAcceptable()) {
                        System.out.println("ACCEPTABLE");
                        handleAcceptable(key);
                    }

                    if (key.isConnectable()) {
                        System.out.println("CONNECTABLE");
                    }

                    if (key.isReadable()) {
                        System.out.println("READABLE");
                        handleReadable(key);
                    }
                    
                    if (key.isWritable()) {
                        System.out.println("WRITABLE");
                    }

                    while ((key = closeq.poll()) != null) {
                        close(key);
                    }
                }
            }
            
        } catch(IOException ioe) {
            System.out.println("例外が発生したため終了します。[exception=" + ioe + "]");
        }
    }
    
    /**
     * 接続受入
     */

    protected void handleAcceptable(SelectionKey key)
    {
        try {
            SocketChannel channel = serverSocketChannel.accept();
            channel.configureBlocking(false);
    
            // 読み取り可能なキーを登録してコネクションを付属させる
            SelectionKey selKey = channel.register(selector, SelectionKey.OP_READ);
            selKey.attach(new Connection(channel));

        } catch(IOException e) {
            System.out.println("例外が発生しました。[exception=" + e + "]");
            e.printStackTrace();
        }
    }
    
    /**
     * 読込処理
     */

    protected void handleReadable(SelectionKey key)
    {
        try {
            Connection conn = (Connection)key.attachment();

            // チャネルからメッセージの受信を行います。
            // 1メッセージ分のデータが受信できた場合にはバイト配列からメッセージ
            // を生成します。受信したデータが1メッセージ未満の場合は、次回読み込
            // み後に再度、1メッセージ分の受信ができたかを確認します。読み込み中
            // のバッファは接続ごとにConnectionクラスで管理されます。
            if (conn.read())
            {
                IMessage msg;
                while((msg = conn.receive()) != null)
                {
                    if (msg instanceof KeyDownMessage) {
                        KeyDownMessage keydown = (KeyDownMessage) msg;
                        
                        // プレイヤー更新コマンド発行
                        UpdPlayerCommand cmd = new UpdPlayerCommand();
                        cmd.id = conn.connid;
                        
                        // 押下されたキーによりアクションを設定
                        if (keydown.key == Keyboard.Up) {
                            cmd.action = 0;
                        } else if (keydown.key == Keyboard.Right) {
                            cmd.action = 1;
                        } else if (keydown.key == Keyboard.Down) {
                            cmd.action = 2;
                        } else if (keydown.key == Keyboard.Left) {
                            cmd.action = 3;
                        }
                        
                        sendToLoginUser(cmd);
                        
                    } else if (msg instanceof KeyUpMessage) {
                        // プレイヤー更新コマンド発行
                        UpdPlayerCommand cmd = new UpdPlayerCommand();
                        cmd.id = conn.connid;
                        cmd.action = -1;
                        sendToLoginUser(cmd);
                        
                    } else if (msg instanceof LoginRequest) {
                        LoginRequest req = (LoginRequest) msg;
                        
                        // ユーザーマップを更新
                        conmap.put(conn.connid, conn);
                        
                        // ログイン応答を返す
                        LoginResponse res = new LoginResponse();
                        res.connid = conn.connid;
                        send(conn, res);
                        
                        // オブジェクトを返す
                        for(Player obj : objmap.values())
                        {
                            send(conn, new AddPlayerCommand(obj));
                        }
                        
                        // 本来データベース等から読み込んだ情報を返すべきですが、
                        // チュートリアルでは、キャラクタタイプは便宜上、0〜4の
                        // 数値を設定しています。
                        Player player = new Player();
                        player.id = conn.connid;
                        player.name = req.username;
                        player.type = conn.connid % 5;
                        
                        // オブジェクトマップを更新
                        objmap.put(player.id, player);
                        
                        // プレイヤー追加コマンド発行
                        sendToLoginUser(new AddPlayerCommand(player));
                        
                    } else if (msg instanceof LogoutRequest) {
                        // ユーザーマップを更新
                        conmap.remove(conn.connid);
                        
                        // オブジェクトマップを更新
                        objmap.remove(conn.connid);

                        // ログイン応答を返す
                        LogoutResponse res = new LogoutResponse();
                        send(conn, res);

                        // プレイヤー削除コマンド発行
                        RemPlayerCommand cmd = new RemPlayerCommand();
                        cmd.id = conn.connid;
                        sendToLoginUser(cmd);
                        
                    } else if (msg instanceof CloseRequest) {
                        System.out.println("接続を閉じます。[" + conn + "]");
                        closeq.add(key);
                    }
                }
                conn.endReceive();
            }

        } catch(EOFException eof) {
            System.out.println("接続が切れました。[" + key.attachment() + "]");
            closeq.add(key);

        } catch(IOException ioe) {
            ioe.printStackTrace();
            closeq.add(key);

        } catch(Exception e) {
            System.out.println("例外が発生しました。[exception=" + e + "]");
            e.printStackTrace();
        }
    }
    
    /**
     * ログイン中のユーザーにコマンドを発行
     * @param cmd
     */

    protected void sendToLoginUser(IPlayerCommand cmd)
    {
        Iterator<Connection> it = conmap.values().iterator();
        while (it.hasNext()) {
            try {
                Connection conn = it.next();
                if (conn.channel.isConnected()) {
                    send(conn, cmd);
                } else {
                    it.remove();
                }
                
            } catch(IOException ioe) {
                System.out.println("例外が発生しました。[exception=" + ioe + "]");
                ioe.printStackTrace();
                it.remove();
            }
        }
    }

    /**
     * メッセージ送信
     * @param channel 送信対象
     * @param msg 送信するメッセージ
     */

    protected void send(Connection conn, IMessage msg) throws IOException
    {
        MessageManager.set(buffer, msg);
        buffer.rewind();
        conn.channel.write(buffer);
        System.out.println("SEND >>> " + conn.connid + " [" + msg + "]");
            
    }

    /**
     * 接続終了
     */

    protected void close(SelectionKey key)
    {
        try {
            key.cancel();
            key.channel().close();

        } catch(IOException e) {
            System.out.println("例外が発生しました。[exception=" + e + "]");
            e.printStackTrace();
        }
    }
    
    /**
     * エントリーポイント
     * @param argv
     * @throws Exception
     */

    public static void main(String[] argv) throws Exception
    {
        Server server = new Server();
        server.run();
    }
}

C# ソースコード(クライアント)

INetwork.cs
using System;
using System.Collections.Generic;
using System.Text;
using sample2DRPG.command;
using sample2DRPG.io;
using sample2DRPG.message;

namespace sample2DRPG.control
{
    public interface INetwork
    {
        /// <summary>
        /// メッセージを送信
        /// </summary>
        void postMessage(IMessage msg);

        /// <summary>
        /// スレッドを開始
        /// </summary>
        void start();

        /// <summary>
        /// プレイヤーコマンドイベント
        /// </summary>
        PlayerCommandDelegate PlayerEvent 
        { 
            get;
            set
        }

        /// <summary>
        /// 読み取りエラーイベント
        /// </summary>
        ErrorProcessDelegate ReadErrorEvent 
        {
            get;
            set;
        }

        /// <summary>
        /// 書き込みエラーイベント
        /// </summary>
        ErrorProcessDelegate WriteErrorEvent
        {
            get;
            set;
        }
    }
}

NetworkForm.cs
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Windows.Forms;
using Microsoft.Xna.Framework;
using sample2DRPG.charactor;
using sample2DRPG.command;
using sample2DRPG.io;
using sample2DRPG.message;

namespace sample2DRPG.control
{
    public class NetworkForm : Form, INetwork
    {
        // プレイヤーコマンドデリゲート定義
        public PlayerCommandDelegate playerEvent;

        // 接続状況
        private bool logined;

        // 接続識別番号
        private int connid;

        // TCPクライアント
        private TcpClient tcpClient;

        // 読み取りスレッド
        private ReaderThread reader;

        // 書き込みスレッド
        private WriterThread writer;

        // 既定のタイムアウト(ミリ秒)
        public static readonly int DEFAULT_TIMEOUT = 5000;

        private GroupBox grpConnected;
        private Button btnLogin;
        private Panel pnlLogin;
        private Label lblName;
        private TextBox txtUsername;
        private Label lblPass;
        private TextBox txtPassword;
        private TextBox txtPort;
        private TextBox txtHost;
        private Label lblPort;
        private Label lblHost;
        private CheckBox chkConnect;

        /// <summary>
        /// ログイン状況
        /// </summary>
        public bool IsLogin
        {
            get { return logined; }
        }

        /// <summary>
        /// プレイヤーコマンドイベント
        /// </summary>
        public PlayerCommandDelegate PlayerEvent
        {
            get { return playerEvent; }
            set { playerEvent = value; }
        }

        /// <summary>
        /// 読み取りエラーイベント
        /// </summary>
        public ErrorProcessDelegate ReadErrorEvent 
        {
            get { return reader.ErrorProcess; }
            set { reader.ErrorProcess = value; }
        }

        /// <summary>
        /// 書き込みエラーイベント
        /// </summary>
        public ErrorProcessDelegate WriteErrorEvent
        {
            get { return writer.ErrorProcess; }
            set { writer.ErrorProcess = value; }
        }

        // コンストラクタ
        public NetworkForm()
        {
            InitializeComponent();
            MessageManager.create();
        }

        /// <summary>
        /// コマンド実行
        /// </summary>
        public void dispatchCommand(IPlayerCommand cmd)
        {
            if (InvokeRequired)
            {
                Invoke(new PlayerCommandDelegate(dispatchCommand), cmd);
                return;
            }
            if (playerEvent != null)
            {
                playerEvent(cmd);
            }
        }

        /// <summary>
        /// // スレッド起動
        /// </summary>
        public void start()
        {
            if (!IsLogin) return;

            if (reader != null && !reader.isRunning)
            {
                reader.start();
            }
            if (writer != null && !writer.isRunning)
            {
                writer.start();
            }
        }

        /// <summary>
        /// 送信スレッドの送信リストへメッセージを登録
        /// </summary>
        /// <param name="msg"></param>
        public void postMessage(IMessage msg)
        {
            if (writer == nullreturn;

            writer.postMessage(msg);
        }
        
        /// <summary>
        /// 使用中のリソースをクリーンアップ
        /// </summary>
        protected override void Dispose(bool disposing)
        {
            // ネットワークを閉じる
            close();

            base.Dispose(disposing);
        }

        #region private void InitializeComponent()
        private void InitializeComponent()
        {
            this.grpConnected = new System.Windows.Forms.GroupBox();
            this.btnLogin = new System.Windows.Forms.Button();
            this.pnlLogin = new System.Windows.Forms.Panel();
            this.lblName = new System.Windows.Forms.Label();
            this.txtUsername = new System.Windows.Forms.TextBox();
            this.lblPass = new System.Windows.Forms.Label();
            this.txtPassword = new System.Windows.Forms.TextBox();
            this.txtPort = new System.Windows.Forms.TextBox();
            this.txtHost = new System.Windows.Forms.TextBox();
            this.lblPort = new System.Windows.Forms.Label();
            this.lblHost = new System.Windows.Forms.Label();
            this.chkConnect = new System.Windows.Forms.CheckBox();
            this.grpConnected.SuspendLayout();
            this.pnlLogin.SuspendLayout();
            this.SuspendLayout();
            // 
            // grpConnected
            // 
            this.grpConnected.Controls.Add(this.btnLogin);
            this.grpConnected.Controls.Add(this.pnlLogin);
            this.grpConnected.Enabled = false;
            this.grpConnected.Location = new System.Drawing.Point(1298);
            this.grpConnected.Name = "grpConnected";
            this.grpConnected.Size = new System.Drawing.Size(26876);
            this.grpConnected.TabIndex = 9;
            this.grpConnected.TabStop = false;
            this.grpConnected.Text = "未接続";
            // 
            // btnLogin
            // 
            this.btnLogin.Location = new System.Drawing.Point(21144);
            this.btnLogin.Name = "btnLogin";
            this.btnLogin.Size = new System.Drawing.Size(5123);
            this.btnLogin.TabIndex = 8;
            this.btnLogin.Text = "ログイン";
            this.btnLogin.UseVisualStyleBackColor = true;
            this.btnLogin.Click += new System.EventHandler(this.btnLogin_Click);
            // 
            // pnlLogin
            // 
            this.pnlLogin.Controls.Add(this.lblName);
            this.pnlLogin.Controls.Add(this.txtUsername);
            this.pnlLogin.Controls.Add(this.lblPass);
            this.pnlLogin.Controls.Add(this.txtPassword);
            this.pnlLogin.Location = new System.Drawing.Point(818);
            this.pnlLogin.Name = "pnlLogin";
            this.pnlLogin.Size = new System.Drawing.Size(25451);
            this.pnlLogin.TabIndex = 4;
            this.pnlLogin.EnabledChanged += new System.EventHandler(this.pnlLogin_EnabledChanged);
            // 
            // lblName
            // 
            this.lblName.AutoSize = true;
            this.lblName.Location = new System.Drawing.Point(36);
            this.lblName.Name = "lblName";
            this.lblName.Size = new System.Drawing.Size(5812);
            this.lblName.TabIndex = 7;
            this.lblName.Text = "UserName";
            // 
            // txtUsername
            // 
            this.txtUsername.Location = new System.Drawing.Point(623);
            this.txtUsername.MaxLength = 256;
            this.txtUsername.Name = "txtUsername";
            this.txtUsername.Size = new System.Drawing.Size(12419);
            this.txtUsername.TabIndex = 5;
            // 
            // lblPass
            // 
            this.lblPass.AutoSize = true;
            this.lblPass.Location = new System.Drawing.Point(231);
            this.lblPass.Name = "lblPass";
            this.lblPass.Size = new System.Drawing.Size(5412);
            this.lblPass.TabIndex = 6;
            this.lblPass.Text = "Password";
            // 
            // txtPassword
            // 
            this.txtPassword.Location = new System.Drawing.Point(6228);
            this.txtPassword.MaxLength = 256;
            this.txtPassword.Name = "txtPassword";
            this.txtPassword.PasswordChar = '*';
            this.txtPassword.Size = new System.Drawing.Size(12419);
            this.txtPassword.TabIndex = 6;
            // 
            // txtPort
            // 
            this.txtPort.ImeMode = System.Windows.Forms.ImeMode.Disable;
            this.txtPort.Location = new System.Drawing.Point(17259);
            this.txtPort.MaxLength = 5;
            this.txtPort.Name = "txtPort";
            this.txtPort.Size = new System.Drawing.Size(4219);
            this.txtPort.TabIndex = 6;
            this.txtPort.Text = "65000";
            // 
            // txtHost
            // 
            this.txtHost.ImeMode = System.Windows.Forms.ImeMode.Disable;
            this.txtHost.Location = new System.Drawing.Point(4759);
            this.txtHost.Name = "txtHost";
            this.txtHost.Size = new System.Drawing.Size(8719);
            this.txtHost.TabIndex = 4;
            this.txtHost.Text = "localhost";
            // 
            // lblPort
            // 
            this.lblPort.AutoSize = true;
            this.lblPort.Location = new System.Drawing.Point(14062);
            this.lblPort.Name = "lblPort";
            this.lblPort.Size = new System.Drawing.Size(2612);
            this.lblPort.TabIndex = 8;
            this.lblPort.Text = "Port";
            // 
            // lblHost
            // 
            this.lblHost.AutoSize = true;
            this.lblHost.Location = new System.Drawing.Point(1262);
            this.lblHost.Name = "lblHost";
            this.lblHost.Size = new System.Drawing.Size(2912);
            this.lblHost.TabIndex = 5;
            this.lblHost.Text = "Host";
            // 
            // chkConnect
            // 
            this.chkConnect.Appearance = System.Windows.Forms.Appearance.Button;
            this.chkConnect.AutoSize = true;
            this.chkConnect.Location = new System.Drawing.Point(22357);
            this.chkConnect.MinimumSize = new System.Drawing.Size(510);
            this.chkConnect.Name = "chkConnect";
            this.chkConnect.Size = new System.Drawing.Size(5122);
            this.chkConnect.TabIndex = 7;
            this.chkConnect.Text = "接続";
            this.chkConnect.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
            this.chkConnect.UseVisualStyleBackColor = true;
            this.chkConnect.CheckedChanged += new System.EventHandler(this.chkConnect_CheckedChanged);
            // 
            // NetworkForm
            // 
            this.BackColor = System.Drawing.SystemColors.Control;
            this.ClientSize = new System.Drawing.Size(292236);
            this.Controls.Add(this.grpConnected);
            this.Controls.Add(this.txtPort);
            this.Controls.Add(this.txtHost);
            this.Controls.Add(this.lblPort);
            this.Controls.Add(this.lblHost);
            this.Controls.Add(this.chkConnect);
            this.Name = "NetworkForm";
            this.Text = "Login";
            this.grpConnected.ResumeLayout(false);
            this.pnlLogin.ResumeLayout(false);
            this.pnlLogin.PerformLayout();
            this.ResumeLayout(false);
            this.PerformLayout();

        }
        #endregion

        /// <summary>
        /// 接続処理
        /// </summary>
        private void connect()
        {
            string host = txtHost.Text;
            int port = 0;
            if (!Int32.TryParse(txtPort.Text, out port))
            {
                MessageBox.Show("Portには数字を入力してください。");
                return;
            }

            // TCP クライアントを生成
            tcpClient = new TcpClient();

            tcpClient.ReceiveBufferSize = 8192;

            tcpClient.SendBufferSize = 8192;

            // サーバーへ接続する
            tcpClient.Connect(host, port);
        }

        /// <summary>
        /// 切断処理
        /// </summary>
        private void close()
        {
            if (tcpClient == nullreturn;

            // ログアウトする
            if (logined) logout();
                
            // スレッド終了までブロック(既定のタイムアウト5秒)
            if (writer != null)
            {
                writer.join(DEFAULT_TIMEOUT);
                writer = null;
            }
            if (reader != null)
            {
                reader.join(DEFAULT_TIMEOUT);
                reader = null;
            }

            // サーバーから切断する
            if (tcpClient.Connected)
            {
                CloseRequest req = new CloseRequest();
                byte[] dst = MessageManager.set(req);
                tcpClient.Client.Send(dst);
            }
            tcpClient = null;
        }

        /// <summary>
        /// ログイン
        /// </summary>
        private IMessage login()
        {
            reader = new ReaderThread(this, tcpClient);
            writer = new WriterThread(this, tcpClient);

            // エラー処理登録
            reader.ErrorProcess +=
                new ErrorProcessDelegate(errorProcess);
            writer.ErrorProcess +=
                new ErrorProcessDelegate(errorProcess);

            // ログイン要求作成
            LoginRequest req = new LoginRequest();
            req.username = txtUsername.Text;
            req.password = txtPassword.Text;
            if (req.username.Length == 0) req.username = "blank";

            // ログイン要求を送信
            writer.loginRequest(req);

            // ログイン応答を受信
            IMessage msg = null;
            try
            {
                // タイムアウトを5秒に設定
                tcpClient.ReceiveTimeout = DEFAULT_TIMEOUT;

                // ログイン前に送信されたメッセージは破棄
                while (!(msg is LoginResponse))
                {
                    msg = reader.receive();
                }
                logined = true;
            }
            finally
            {
                tcpClient.ReceiveTimeout = 0;
            }
            return msg;
        }

        /// <summary>
        /// ログアウト
        /// </summary>
        private void logout()
        {
            // 先に読み取りスレッドを停止する。但し、read()で
            // ブロック中の場合、Logoutの応答を受けて完全に停止する
            if (reader != null && reader.isRunning)
            {
                reader.shutdown();
            }

            // ログアウト要求を送信して送信スレッドを停止する
            if (writer != null)
            {
                writer.logoutRequest(new LogoutRequest());

                writer.shutdown();
            }
            logined = false;
        }

        /// <summary>
        /// 接続/切断ボタン押下
        /// </summary>
        private void chkConnect_CheckedChanged(object sender, EventArgs e)
        {
            CheckBox cb = sender as CheckBox;
            bool enable = false;
            try
            {
                if (cb.Checked)
                {
                    // サーバーへ接続
                    connect();

                    // ボタンを次の操作の名称に変更
                    cb.Text = "切断";

                    // グループタイトル更新
                    grpConnected.Text = "接続中 [ " + tcpClient.Client.RemoteEndPoint + " ]";

                    enable = true;
                }
                else
                {
                    // サーバーから切断
                    close();

                    // ボタンを次の操作の名称に変更
                    cb.Text = "接続";

                    // グループタイトル更新
                    grpConnected.Text = "未接続";

                    // ログインパネル初期化
                    txtUsername.Clear();
                    txtPassword.Clear();
                }
                // グループEnable設定
                grpConnected.Enabled = enable;

                // テキストボックスEnable設定
                txtHost.Enabled = !enable;
                txtPort.Enabled = !enable;
                pnlLogin.Enabled = enable;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
                cb.Checked = !cb.Checked;
            }
        }

        /// <summary>
        /// ログイン
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnLogin_Click(object sender, EventArgs e)
        {
            // ログインパネルを使用不可に設定
            pnlLogin.Enabled = false;

            // ログイン処理
            IMessage msg = login();

            bool result = msg is LoginResponse;
            if (!result) throw new SystemException("ログイン応答が受信できません。");

            // 識別番号を設定
            LoginResponse res = msg as LoginResponse;
            this.connid = res.connid;

            // ログインフォームを閉じる
            this.Hide();
        }

        /// <summary>
        /// ログインパネルのEnable設定
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void pnlLogin_EnabledChanged(object sender, EventArgs e)
        {
            Panel panel = sender as Panel;
            btnLogin.Enabled = panel.Enabled;
            txtUsername.Enabled = panel.Enabled;
            txtPassword.Enabled = panel.Enabled;
        }

        /// <summary>
        /// 送受信スレッドのエラー処理
        /// </summary>
        private void errorProcess(NetworkThread sender, Exception e)
        {
            // ネットワークから切断
            close();
        }
    }
}

Program.cs
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
using sample2DRPG.charactor;
using sample2DRPG.command;
using sample2DRPG.control;
using sample2DRPG.message;
using sample2DRPG.util;

namespace sample2DRPG
{
    /// <summary>
    /// 簡略化のため Program.cs に Game クラスを記述
    /// </summary>
    public class GameMain : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        // ネットワークインタフェース
        public INetwork network;

        // プレイヤー情報
        public Dictionary<int, Player> playerList;

        public double ftime = 0;

        public int frame = 0;

        // 前回のキー押下状態
        KeyboardState prevKey;

        public GameMain()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";

            // 画面サイズ
            graphics.PreferredBackBufferWidth = 320;
            graphics.PreferredBackBufferHeight = 240;

            // マウス表示
            IsMouseVisible = true;
        }

        /// <summary>
        /// 初期化
        /// </summary>
        protected override void Initialize()
        {
            base.Initialize();

            // イベント登録
            network.PlayerEvent += new PlayerCommandDelegate(PlayerEvent);

            network.start();
        }

        /// <summary>
        /// リソース読み込み
        /// </summary>
        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);

            // 索引付きプレイヤーリストを生成
            playerList = new Dictionary<int, Player>();

            // キャラクタ定義を読み込み
            string[] assetName = 
            {
                "chara01",
                "chara02",
                "chara03",
                "chara04",
                "chara05"
            };
            CharaManager.Load(Content, assetName);
        }

        /// <summary>
        /// リソース破棄
        /// </summary>
        protected override void UnloadContent()
        {
        }

        /// <summary>
        /// 更新処理
        /// </summary>
        /// <param name="gameTime"></param>
        protected override void Update(GameTime gameTime)
        {
            // 1秒間に描画したフレーム数をタイトルに表示
            ftime += gameTime.ElapsedGameTime.TotalSeconds;
            if (ftime > 1)
            {
                Window.Title = String.Format("[FPS={0}]", frame);
                ftime -= 1;
                frame = 0;
            }

            // キー入力状況を取得
            KeyboardState keyState = Keyboard.GetState();

            // エスケープ押下で終了
            if (keyState.IsKeyDown(Keys.Escape)) Exit();

            // キーの押下状態が変わった場合
            if (!keyState.Equals(prevKey))
            {
                prevKey = keyState;

                if (keyState.IsKeyDown(Keys.Up))
                {
                    network.postMessage(new KeyDownMessage(Keymap.Up));
                }
                else if (keyState.IsKeyDown(Keys.Right))
                {
                    network.postMessage(new KeyDownMessage(Keymap.Right));
                }
                else if (keyState.IsKeyDown(Keys.Down))
                {
                    network.postMessage(new KeyDownMessage(Keymap.Down));
                }
                else if (keyState.IsKeyDown(Keys.Left))
                {
                    network.postMessage(new KeyDownMessage(Keymap.Left));
                }
                else 
                {
                    network.postMessage(new KeyUpMessage());
                }
            }

            // プレイヤー情報を更新
            foreach (Player player in playerList.Values)
            {
                player.Update(gameTime);
            }

            base.Update(gameTime);
        }

        /// <summary>
        /// 描画処理
        /// </summary>
        /// <param name="gameTime"></param>
        protected override void Draw(GameTime gameTime)
        {
            // frame add
            frame++;

            graphics.GraphicsDevice.Clear(Color.CornflowerBlue);

            spriteBatch.Begin();

            // プレイヤー情報を描画
            foreach (Player player in playerList.Values)
            {
                player.Draw(spriteBatch);
            }

            spriteBatch.End();

            base.Draw(gameTime);
        }

        /// <summary>
        /// プレイヤーイベント
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        protected void PlayerEvent(IPlayerCommand command)
        {
            lock (playerList)
            {
                command.Execute(playerList);
            }
        }
    }

    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        static void Main(string[] args)
        {
            // ビジュアルスタイルを有効にする
            System.Windows.Forms.Application.EnableVisualStyles();

            NetworkForm network = new NetworkForm();
            GameMain game = new GameMain();
            game.network = network;
            try
            {
                // 接続ウインドウを表示
                network.ShowDialog();

                // XNA のゲームウインドウを表示
                if (network.IsLogin)
                {
                    game.Run();
                }
            }
            finally
            {
                if (network != null)
                {
                    network.Dispose();
                }
                if (game != null)
                {
                    game.Dispose();
                }
            }
        }
    }
}

次回予定

次回はちょっと一息で、見た目を改善したいと思います。

更新日 2008/06/07