XNA入門

STEP.07 名前とマップチップの表示

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

完成イメージ@

ダウンロード

概要・解説

今回はちょっと一休みをして見た目を改善したいと思います。

プレイヤー名を頭上に表示して、形だけですがマップチップを表示しています。 マップを出すことでだいぶ雰囲気が変わると思います。 プログラムの中身はあまり重要では無いため参考程度にご確認ください。

注意事項

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

ソリューション設定

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

ソリューション設定@
    Content
  • map01.dds
    field
  • Field2D.cs
    util
  • SpriteGenerator.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) {
            Connection conn = (Connection)key.attachment();
            if (objmap.containsKey(conn.connid)) {
                objmap.remove(conn.connid);
            }
            System.out.println("接続が切れました。[" + conn + "]");
            closeq.add(key);

        } catch(IOException ioe) {
            ioe.printStackTrace();
            Connection conn = (Connection)key.attachment();
            if (objmap.containsKey(conn.connid)) {
                objmap.remove(conn.connid);
            }
            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# ソースコード(クライアント)

Field2D.cs
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

namespace sample2DRPG.field
{
    /// <summary>
    /// マップ定義は未定のため、雰囲気だけ。
    /// </summary>
    public class Field2D
    {
        // マップチップは 16 x 16 で固定
        public static readonly int WIDTH = 16;
        public static readonly int HEIGHT = 16;

        // マップ用テクスチャ
        Texture2D texture;

        // マップチップ
        Rectangle[] rect;

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="texture"></param>
        public Field2D(Texture2D texture)
        {
            this.texture = texture;

            int w = texture.Width;
            int h = texture.Height;
            int count = (w/WIDTH) * (h/HEIGHT);
            rect = new Rectangle[count];
            int ii = 0;

            // あらかじめマップチップの定義を作っておく
            for (int y = 0; y < h; y += HEIGHT)
                for (int x = 0; x < w; x += WIDTH)
                {
                    rect[ii] = new Rectangle(x, y, WIDTH, HEIGHT);
                    ii++;
                }
        }

        /// <summary>
        /// マップ描画
        /// </summary>
        /// <param name="sprite"></param>
        public void Draw(SpriteBatch sprite)
        {
            int ii = 0;
            Vector2 pos;
            for (int y = 0; y < 15; y++)
                for (int x = 0; x < 20; x++)
                {
                    pos = new Vector2(x * WIDTH, y * HEIGHT);
                    sprite.Draw(texture, pos, rect[map[ii]], Color.White);
                    ii++;
                }
        }

        /// <summary>
        /// マップ定義
        /// TODO: サーバーからマップ定義を受信する(エクセルで作成)
        /// </summary>
        int[] map = 
        {
            302,302,302,302,302,302,302,302,302,302,302,302,420,421,422,302,302,302,302,302,
            302,3026767676767676767,302,302,420,421,422,302,159,160,161,302,
            30267676767676767676767,302,420,421,422,302,219,220,221,302,
            302676767676767676767,302,302,420,421,422,302,302,302,302,302,
            302,3026767676767676767,302,302,420,421,422,302,302,302,302,302,
            302,302,302,302,302,302,302,302,302,302,302,302,420,421,422,302,302,302,302,302,
            391,391,391,391,391,391,391,391,391,391,391,391,421,421,421,391,391,391,391,391,
            421,421,421,421,421,421,421,421,421,421,421,421,421,421,421,421,421,421,421,421,
            451,451,451,451,451,451,451,451,451,451,451,451,421,421,421,451,451,451,451,451,
            302,302,302,302,302,302,302,302,302,302,302,302,420,421,422,302,302,302,302,302,
            302,302,302,302,302,302,302,302,302,302,302,302,420,421,422,302,302,302,302,302,
            302,302,302,302,302,302,302,302,302,302,302,302,420,421,422,302,302,302,302,302,
            302,302,302,302,302,302,302,302,302,302,302,302,420,421,422,302,302,302,302,302,
            302,302,302,302,302,302,302,302,302,302,302,302,420,421,422,302,302,302,302,302,
            302,302,302,302,302,302,302,302,302,302,302,302,420,421,422,302,302,302,302,302 
        };
    }
}

SpriteGenerator.cs
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;
using GraphicsDevice = Microsoft.Xna.Framework.Graphics.GraphicsDevice;
using Texture2D      = Microsoft.Xna.Framework.Graphics.Texture2D;
using TextureUsage   = Microsoft.Xna.Framework.Graphics.TextureUsage;
using SurfaceFormat  = Microsoft.Xna.Framework.Graphics.SurfaceFormat;

namespace sample2DRPG.util
{
    public class SpriteGenerator
    {
        // デフォルトフォント
        public static readonly Font DEFAULT_FONT = new Font("MS Pゴシック"8);

        private GraphicsDevice _device;

        private Graphics _grahics;

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="device">Microsoft.Xna.Framework.Graphics.GraphicsDevice</param>
        /// <param name="hwnd">ウインドウを識別するハンドル</param>
        public SpriteGenerator(GraphicsDevice device, IntPtr hwnd)
        {
            this._grahics = Graphics.FromHwnd(hwnd);
            this._device = device;
        }

        /// <summary>
        /// デフォルトフォントで文字列からスプライトを生成します。
        /// </summary>
        /// <param name="text">スプライト化する文字列</param>
        /// <param name="argb">32 ビットの ARGB 値を指定する値。</param>
        /// <returns>Texture2D</returns>
        public Texture2D CreateFontSprite(string text, int argb)
        {
            Color color = Color.FromArgb(argb);
            return CreateFontSprite(text, DEFAULT_FONT, color);
        }

        /// <summary>
        /// デフォルトフォントで文字列からスプライトを生成します。
        /// </summary>
        /// <param name="text">スプライト化する文字列</param>
        /// <param name="alpha">アルファのコンポーネント。有効な値は 0 から 255 です。</param>
        /// <param name="red">赤のコンポーネント。有効な値は 0 から 255 です。</param>
        /// <param name="green">緑のコンポーネント。有効な値は 0 から 255 です。</param>
        /// <param name="blue">青のコンポーネント。有効な値は 0 から 255 です。</param>
        /// <returns>Texture2D</returns>
        public Texture2D CreateFontSprite(string text, 
            int alpha, int red, int green, int blue)
        {
            Color color = Color.FromArgb(alpha, red, green, blue);
            return CreateFontSprite(text, DEFAULT_FONT, color);
        }

        /// <summary>
        /// フォントを指定して文字列からスプライトを生成します。
        /// </summary>
        /// <param name="text">スプライト化する文字列</param>
        /// <param name="familyName">System.Drawing.FontFamily の文字列形式。</param>
        /// <param name="emSize">フォントの em サイズ(単位はポイント)。</param>
        /// <param name="argb">32 ビットの ARGB 値を指定する値。</param>
        /// <returns>Texture2D</returns>
        public Texture2D CreateFontSprite(string text,
            string familyName, int emSize, int argb)
        {
            Color color = Color.FromArgb(argb);
            using (Font font = new Font(familyName, emSize)) {
                return CreateFontSprite(text, font, color);
            }
        }

        /// <summary>
        /// フォントを指定して文字列からスプライトを生成します。
        /// </summary>
        /// <param name="text">スプライト化する文字列</param>
        /// <param name="familyName">System.Drawing.FontFamily の文字列形式。</param>
        /// <param name="emSize">フォントの em サイズ(単位はポイント)。</param>
        /// <param name="alpha">アルファのコンポーネント。有効な値は 0 から 255 です。</param>
        /// <param name="red">赤のコンポーネント。有効な値は 0 から 255 です。</param>
        /// <param name="green">緑のコンポーネント。有効な値は 0 から 255 です。</param>
        /// <param name="blue">青のコンポーネント。有効な値は 0 から 255 です。</param>
        /// <returns>Texture2D</returns>
        public Texture2D CreateFontSprite(string text, string familyName, 
            int emSize, int alpha, int red, int green, int blue)
        {
            Color color = Color.FromArgb(alpha, red, green, blue);
            using (Font font = new Font(familyName, emSize)) {
                return CreateFontSprite(text, font, color);
            }
        }

        /// <summary>
        /// 指定した文字列からスプライトを生成します。
        /// </summary>
        /// <param name="text">スプライト化する文字列</param>
        /// <param name="font">文字列のテキスト形式を定義する System.Drawing.Font</param>
        /// <param name="color"></param>
        /// <returns>Texture2D</returns>
        public Texture2D CreateFontSprite(string text, Font font, Color color)
        {
            Size size = TextRenderer.MeasureText(_grahics, text, font);
            Bitmap bmp = new Bitmap(size.Width, size.Height,
                                    PixelFormat.Format32bppArgb);
            using (Graphics g = Graphics.FromImage(bmp))
            {
                TextRenderer.DrawText(g, text, font, new Point(00), color);
                return BmpToTexture2D(bmp);
            }
        }

        /// <summary>
        /// 高さ、および幅で指定された外接する四角形によって定義される
        /// 楕円のスプライトを生成します。
        /// </summary>
        /// <param name="width">楕円を定義する外接する四角形の幅。</param>
        /// <param name="height">楕円を定義する外接する四角形の高さ。</param>
        /// <param name="alpha">アルファのコンポーネント。有効な値は 0 から 255 です。</param>
        /// <param name="red">赤のコンポーネント。有効な値は 0 から 255 です。</param>
        /// <param name="green">緑のコンポーネント。有効な値は 0 から 255 です。</param>
        /// <param name="blue">青のコンポーネント。有効な値は 0 から 255 です。</param>
        /// <returns>Texture2D</returns>
        public Texture2D CreateEllipseSprite(int width, int height,
            int alpha, int red, int green, int blue)
        {
            Color color = Color.FromArgb(alpha, red, green, blue);
            using (Pen pen = new Pen(color)) {
                return CreateEllipseSprite(width, height, color);
            }
        }

        /// <summary>
        /// 高さ、および幅で指定された外接する四角形によって定義される
        /// 楕円のスプライトを生成します。
        /// </summary>
        /// <param name="width">楕円を定義する外接する四角形の幅。</param>
        /// <param name="height">楕円を定義する外接する四角形の高さ。</param>
        /// <param name="argb">32 ビットの ARGB 値を指定する値。</param>
        /// <returns>Texture2D</returns>
        public Texture2D CreateEllipseSprite(int width, int height, int argb)
        {
            Color color = Color.FromArgb(argb);
            using (Pen pen = new Pen(color)) {
                return CreateEllipseSprite(width, height, color);
            }
        }

        /// <summary>
        /// 高さ、および幅で指定された外接する四角形によって定義される
        /// 楕円のスプライトを生成します。
        /// </summary>
        /// <param name="width">楕円を定義する外接する四角形の幅。</param>
        /// <param name="height">楕円を定義する外接する四角形の高さ。</param>
        /// <param name="color">楕円の色を示す System.Drawing.Color 構造体。</param>
        /// <returns>Texture2D</returns>
        public Texture2D CreateEllipseSprite(int width, int height, Color color)
        {
            using (Pen pen = new Pen(color)) {
                return CreateEllipseSprite(pen, width, height);
            }
        }

        /// <summary>
        /// 高さ、および幅で指定された外接する四角形によって定義される
        /// 楕円のスプライトを生成します。
        /// </summary>
        /// <param name="pen">楕円の色、幅、スタイルを決定する System.Drawing.Pen。</param>
        /// <param name="width">楕円を定義する外接する四角形の幅。</param>
        /// <param name="height">楕円を定義する外接する四角形の高さ。</param>
        /// <returns>Texture2D</returns>
        public Texture2D CreateEllipseSprite(Pen pen, int width, int height)
        {
            Bitmap bmp = new Bitmap(width + 1, height + 1, PixelFormat.Format32bppArgb);
            using (Graphics g = Graphics.FromImage(bmp))
            {
                g.DrawEllipse(pen, 00, width, height);
                return BmpToTexture2D(bmp);
            }
        }

        /// <summary>
        /// ビットマップをスプライトに変換します
        /// </summary>
        /// <param name="bmp">Bitmap</param>
        /// <returns>Texture2D</returns>
        private Texture2D BmpToTexture2D(Bitmap bmp)
        {
            int w = bmp.Width, h = bmp.Height;
            Rectangle bmpRc = new Rectangle(00, w, h);
            Texture2D tex2D = new Texture2D(this._device, w, h, 1,
                TextureUsage.None, SurfaceFormat.Color);
            BitmapData bmpData = bmp.LockBits(bmpRc, 
                ImageLockMode.ReadOnly, bmp.PixelFormat);

            IntPtr ptr = bmpData.Scan0;
            int stride = bmpData.Stride;
            int bytes = stride * h;
            byte[] b = new byte[bytes];
            Marshal.Copy(ptr, b, 0, bytes);
            bmp.UnlockBits(bmpData);
            tex2D.SetData<byte>(b);
            return tex2D;
        }
    }
}

更新日 2008/06/08