Microsoft Visual Web Developer 2008 Express Edition
 Microsoft XNA Game Studio 3.1
 Silverlight 3
 SilverSprite 3.0 Alpha 2

■Silverlight + XNAでゲーム作成 Prev  Top  Next

ドメイン

Silverlightネタですが、前回サンプルゲームを作成したので、あれで終了しようと思ってましたが、 ネット上でいろいろ調べてみて面白そなネタがいくつか見つかったので、もうしばらくSilverlightネタ続けたいと思います。
今回は、XNAで作成したプログラムを Silverlight 上で実行するためのサンプルを作成します。 これをやるには SilverSprite というライブラリが必要となります。サイトは SilverSprite です。 2010/4/12現在ではアルファ版となっています。ライセンスについては SilverSprite のサイトを参照してください。

では開発手順について説明していきます。

1.まずXNAでプログラム作成します。まあとりあえずこれをやらないとSilverSpriteを使用する意味がないので。 XNAについては当サイトでも解説してますが、バージョンが古いので参考にしない方がいいかも。

2.次にSilverSpriteのセットアップです。SilverSpriteのサイトからSilverSpriteのライブラリをダウンロードして任意のフォルダに解凍します。 解凍すると次の4ファイルが展開されます。
  ・SilverArcade.SilverSprite.dll
  ・SilverArcade.SilverSprite.Core.dll
  ・SilverArcade.SilverSprite.Core.Phone.dll
  ・FarseerPhysics.dll

3.解凍されたファイルを下の画像で示しているフォルダにコピーします。なおこのフォルダ構成は、Silverlight 3 Tool をインストールした場合です。 ライブラリを置く場所

4.次に Silverlight用のプロジェクトを作成します。

5.プロジェクトの設定を行います。
Silverlight用のプロジェクトのソリューションエクスプローラー
いきなり色々ファイルが追加されていますが、まずはSilverSprite用のライブラリを参照設定で追加します。 参照設定上で右クリックして、「参照の追加」をクリックします。
参照の追加
上の画面が表示されるので、必要となる2つのライブラリを選択して「OK」をクリックします。 なおコンポーネント名で重複するものがあるので間違わないように。右の方にスクロールするとパスが表示されるので、SilverArcade.SilverSprite.Core.dllの方を追加するようにしてください。

6.Contentフォルダとそれ以下の各種リソースはXNA側で開発したものと同じファイルをインポートします。ただし、ビルドアクションを「Resource」に変更することを忘れないように。
プロパティ

ビルドアクションについてはMSDNを参照してください。

7.App.xaml.csファイルでは Silverlight の起動もととなるクラスを指定しますが、今回のサンプルではデフォルトのままMainPageクラスを指定するので変更しません。

8.MainPage.xamlでXNAで作成したクラスを表示するための設定を行います。

---MainPage.xaml---


<UserControl x:Class="SilverlightApplication1.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    xmlns:game="clr-namespace:XNASample"
    mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
  <Grid x:Name="LayoutRoot">
    <Canvas>
      <game:Game1 x:Name="game"/>
    </Canvas>
  </Grid>
</UserControl>

MainPage.xaml.csは変更なしです。

9.最後に XNA で作成したソースです。

---Game1.cs---


using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
#if SILVERLIGHT
using SilverlightApplication1;
#endif
namespace XNASample
{
    // <summary>
    // This is the main type for your game
    // </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch = null;
        Texture2D TexPlayer = null;
        Texture2D TexPlayerShoot = null;
        Texture2D TexEnemy = null;
        Texture2D TexEnemyShoot = null;
        Texture2D TexEx = null;
        SoundEffect SoundShoot = null;
        SoundEffect SoundEx = null;
        SpriteFont Font = null;

        KeyboardState keyState;

        struct UnitType
        {
            public Vector2 pos;
            public int Width;
            public int Height;
            public bool Visibility;
            public float alpha;
            public float speed;
        }

        UnitType Player;
        UnitType[] Enemy;
        UnitType PlayerShoot;
        UnitType[] EnemyShoot;
        UnitType Ex;

        // 敵の移動モード
        int enemyMoveMode;
        int enemyMoveCnt;

        int FPS;
        int DrawCnt;
        DateTime now;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            // XNA上の表示領域のサイズを設定
            // Silverlightの場合 XAML で設定したサイズにする
#if SILVERLIGHT
            graphics.PreferredBackBufferWidth = (int)App.Current.Host.Content.ActualWidth;
            graphics.PreferredBackBufferHeight = (int)App.Current.Host.Content.ActualHeight;
#else
            graphics.PreferredBackBufferWidth = 640;
            graphics.PreferredBackBufferHeight = 480;
#endif
        }

        // <summary>
        // LoadContent will be called once per game and is the place to load
        // all of your content.
        // </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            // TODO: use Content to load your game content here
            TexPlayer = Content.Load<Texture2D>("Player");
            TexEnemy = Content.Load<Texture2D>("enemy");
            TexPlayerShoot = Content.Load<Texture2D>("PlayerShoot");
            TexEnemyShoot = Content.Load<Texture2D>("EnemyShoot");
            TexEx = Content.Load<Texture2D>("Ex");

            SoundShoot = Content.Load<SoundEffect>("ShootSE");
            SoundEx = Content.Load<SoundEffect>("ExSE");

            Font = Content.Load<SpriteFont>("Font");
        }

        // <summary>
        // UnloadContent will be called once per game and is the place to unload
        // all content.
        // </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        // <summary>
        // Allows the game to perform any initialization it needs to before starting to run.
        // This is where it can query for any required services and load any non-graphic
        // related content.  Calling base.Initialize will enumerate through any components
        // and initialize them as well.
        // </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here
            base.Initialize();

            playerInitialize();
            enemyInitialize();
            playershootInitialize();
            enemyshootInitialize();
            exInitialize();

            FPS = 0;
            DrawCnt = 0;
            now = DateTime.Now;
        }

        private void playerInitialize()
        {
            Player.Width = 30;
            Player.Height = 30;
            Player.pos = new Vector2((float)(graphics.PreferredBackBufferWidth / 2 - Player.Width / 2),
                                           (float)(graphics.PreferredBackBufferHeight - Player.Height - 10));
            Player.speed = 0.0f;
            Player.Visibility = true;
        }

        private void enemyInitialize()
        {
            const int offsetw = 80;
            const int offseth = 50;
            const int sleft = 20;
            const int stop = 30;

            int cnt = 0;
            int left = sleft;
            int top = stop;

            enemyMoveMode = 0;
            enemyMoveCnt = 0;

            // 敵を適当に作成する
            if (Enemy == null)
            {
                Enemy = new UnitType[21];
            }

            for (int i = 0; i < Enemy.Length; i++)
            {
                Enemy[i] = new UnitType();

                Enemy[i].Visibility = true;
                Enemy[i].Width = 30;
                Enemy[i].Height = 30;

                Enemy[i].pos.X = left;
                Enemy[i].pos.Y = top;

                if (cnt < 6)
                {
                    left += offsetw;
                    cnt++;
                }
                else
                {
                    left = sleft;
                    top += offseth;
                    cnt = 0;
                }
            }
        }

        private void playershootInitialize()
        {
            PlayerShoot.Width = 10;
            PlayerShoot.Height = 10;
            PlayerShoot.Visibility = false;
        }

        private void enemyshootInitialize()
        {
            EnemyShoot = new UnitType[10];

            for (int i = 0; i < EnemyShoot.Length; i++)
            {
                EnemyShoot[i].Width = 10;
                EnemyShoot[i].Height = 10;
                EnemyShoot[i].Visibility = false;
            }
        }

        private void exInitialize()
        {
            Ex.Width = 60;
            Ex.Height = 60;
            Ex.Visibility = false;
            Ex.alpha = 255.0f;
        }

        //キー入力
        private void InputKey()
        {
            //左キー入力
            if (keyState.IsKeyDown(Keys.Left) == true)
            {
                Player.speed -= 2.0f;
            }
            //右キー入力
            if (keyState.IsKeyDown(Keys.Right) == true)
            {
                Player.speed += 2.0f;
            }
            //上キー入力
            if (keyState.IsKeyDown(Keys.Up) == true)
            {
                // 弾を非表示のときのみショットを撃てる
                if (PlayerShoot.Visibility == false)
                {
                    // 弾の座標を自機の座標から計算し、設定する
                    PlayerShoot.pos = new Vector2(Player.pos.X + Player.Width / 2 - PlayerShoot.Width / 2,
                                                  Player.pos.Y + Player.Height / 2 - PlayerShoot.Height / 2);
                    PlayerShoot.Visibility = true;

                    // SE再生
                    SoundShoot.Play();
                }
            }
        }

        // 自機の座標更新
        private void playerUpdate()
        {
            // 自機の位置を計算
            double left = (double)Player.pos.X + Player.speed;

            // 自機がスクリーンからはみ出たら戻す
            if (left < 0.0)
                left = 0.0;
            if (left > graphics.PreferredBackBufferWidth - Player.Width)
                left = graphics.PreferredBackBufferWidth - Player.Width;

            // 座標を設定する
            Player.pos.X = (int)left;

            // 減速
            Player.speed *= 0.9f;
            if (Player.speed > -0.05 && Player.speed < 0.05f)
                Player.speed = 0.0f;
        }

        // 敵の移動
        private void enemyUpdate()
        {
            Random rnd = new Random();
            int addX = 0, addY = 0;
            int j;

            switch (enemyMoveMode)
            {
                // 右に移動
                case 0:
                    enemyMoveCnt++;
                    if (enemyMoveCnt > 80)
                    {
                        enemyMoveCnt = 0;
                        enemyMoveMode++;
                    }
                    addX = 1;
                    addY = 0;
                    break;
                // 下に移動
                case 1:
                    enemyMoveCnt++;
                    if (enemyMoveCnt > 30)
                    {
                        enemyMoveCnt = 0;
                        enemyMoveMode++;
                    }
                    addX = 0;
                    addY = 1;
                    break;
                // 左に移動
                case 2:
                    enemyMoveCnt++;
                    if (enemyMoveCnt > 80)
                    {
                        enemyMoveCnt = 0;
                        enemyMoveMode++;
                    }
                    addX = -1;
                    addY = 0;
                    break;
                // 下に移動
                case 3:
                    enemyMoveCnt++;
                    if (enemyMoveCnt > 30)
                    {
                        enemyMoveCnt = 0;
                        enemyMoveMode = 0;
                    }
                    addX = 0;
                    addY = 1;
                    break;
            }

            j = 0;
            for (int i = 0; i < Enemy.Length; i++)
            {
                if (Enemy[i].Visibility)
                {
                    Enemy[i].pos.X += addX;
                    Enemy[i].pos.Y += addY;

                    if (rnd.Next(500) == 0)
                    {
                        // 弾発射
                        while (j < EnemyShoot.Length)
                        {
                            if (EnemyShoot[j].Visibility == false)
                            {
                                EnemyShoot[j].pos = new Vector2(Enemy[i].pos.X + Enemy[i].Width / 2 - EnemyShoot[j].Width / 2,
                                                                 Enemy[i].pos.Y + Enemy[i].Height / 2 - EnemyShoot[j].Height / 2);
                                EnemyShoot[j].Visibility = true;

                                // SE再生
                                SoundShoot.Play();

                                break;
                            }
                            j++;
                        }
                    }
                }
            }
        }

        // 自機の弾の更新
        private void playershootUpdate()
        {
            if (PlayerShoot.Visibility == true)
            {
                PlayerShoot.pos.Y -= 10.0f;
                if (PlayerShoot.pos.Y < -PlayerShoot.Height)
                    PlayerShoot.Visibility = false;
            }
        }

        // 敵の弾の更新
        private void enemyshootUpdate()
        {
            for (int i = 0; i < EnemyShoot.Length; i++)
            {
                if (EnemyShoot[i].Visibility == true)
                {
                    EnemyShoot[i].pos.Y += 5.0f;
                    if (EnemyShoot[i].pos.Y >= graphics.PreferredBackBufferHeight)
                        EnemyShoot[i].Visibility = false;
                }
            }
        }

        // エフェクトの更新
        private void ExUpdate()
        {
            if (Ex.Visibility)
            {
                Ex.alpha -= 5.0f;
                if (Ex.alpha <= 0.0f)
                {
                    Ex.Visibility = false;
                }
            }
        }

        private void playerDraw()
        {
            spriteBatch.Draw(TexPlayer,
                                  new Rectangle((int)Player.pos.X,
                                                (int)Player.pos.Y,
                                                Player.Width,
                                                Player.Height),
                                                Color.White);
        }

        void enemyDraw()
        {
            for (int i = 0; i < Enemy.Length; i++)
            {
                if (Enemy[i].Visibility)
                {
                    spriteBatch.Draw(TexEnemy,
                                          new Rectangle((int)Enemy[i].pos.X,
                                                        (int)Enemy[i].pos.Y,
                                                        Enemy[i].Width,
                                                        Enemy[i].Height),
                                                        Color.White);
                }
            }
        }

        private void playershootDraw()
        {
            if (PlayerShoot.Visibility)
            {
                spriteBatch.Draw(TexPlayerShoot,
                                      new Rectangle((int)PlayerShoot.pos.X,
                                                    (int)PlayerShoot.pos.Y,
                                                    PlayerShoot.Width,
                                                    PlayerShoot.Height),
                                                    Color.White);
            }
        }

        private void enemyshootDraw()
        {
            for (int i = 0; i < EnemyShoot.Length; i++)
            {
                if (EnemyShoot[i].Visibility)
                {
                    spriteBatch.Draw(TexEnemyShoot,
                                          new Rectangle((int)EnemyShoot[i].pos.X,
                                                        (int)EnemyShoot[i].pos.Y,
                                                        EnemyShoot[i].Width,
                                                        EnemyShoot[i].Height),
                                                        Color.White);
                }
            }
        }

        private void ExDraw()
        {
            if (Ex.Visibility)
            {
                spriteBatch.Draw(TexEx,
                                  new Rectangle((int)Ex.pos.X,
                                                (int)Ex.pos.Y,
                                                Ex.Width,
                                                Ex.Height),
                                                new Color(Color.White, (byte)Ex.alpha));
            }
        }

        private void CollisionAll()
        {
            // 自機の弾と敵との当たり判定

            double shootL = PlayerShoot.pos.X;
            double shootR = PlayerShoot.pos.X + PlayerShoot.Width;
            double shootT = PlayerShoot.pos.Y;
            double shootB = PlayerShoot.pos.Y + PlayerShoot.Height;

            double bodyL;
            double bodyR;
            double bodyT;
            double bodyB;

            // 敵と自機の弾との当たり判定
            for (int i = 0; i < Enemy.Length; i++)
            {
                if (Enemy[i].Visibility)
                {
                    bodyL = Enemy[i].pos.X;
                    bodyR = Enemy[i].pos.X + Enemy[i].Width;
                    bodyT = Enemy[i].pos.Y;
                    bodyB = Enemy[i].pos.Y + Enemy[i].Height;

                    // 当たり判定チェック
                    if (!(shootR < bodyL ||
                          bodyR < shootL ||
                          shootB < bodyT ||
                          bodyB < shootT))
                    {
                        // ヒットしたので非表示にする。
                        Enemy[i].Visibility = false;
                        PlayerShoot.Visibility = false;

                        // 効果音再生
                        SoundEx.Play();

                        // エフェクトを表示
                        Ex.Visibility = true;
                        Ex.alpha = 255.0f;
                        Ex.pos = new Vector2(Enemy[i].pos.X + Enemy[i].Width / 2 - Ex.Width / 2,
                                              Enemy[i].pos.Y + Enemy[i].Height / 2 - Ex.Height / 2);
                        break;
                    }
                }
            }

            bool flg = false;
            for (int i = 0; i < Enemy.Length; i++)
            {
                if (Enemy[i].Visibility)
                {
                    flg = true;
                    break;
                }
            }
            if (flg == false)
            {
                // クリア
                //                ChangeMode(2);
                return;
            }

            // 自機と敵の弾との当たり判定

            shootL = Player.pos.X;
            shootR = Player.pos.X + Player.Width;
            shootT = Player.pos.Y;
            shootB = Player.pos.Y + Player.Height;

            // 敵の弾の座標
            for (int i = 0; i < EnemyShoot.Length; i++)
            {
                if (EnemyShoot[i].Visibility)
                {
                    bodyL = EnemyShoot[i].pos.X;
                    bodyR = EnemyShoot[i].pos.X + EnemyShoot[i].Width;
                    bodyT = EnemyShoot[i].pos.Y;
                    bodyB = EnemyShoot[i].pos.Y + EnemyShoot[i].Height;

                    // 当たり判定チェック
                    if (!(shootR < bodyL ||
                          bodyR < shootL ||
                          shootB < bodyT ||
                          bodyB < shootT))
                    {
                        // ヒットしたのでゲームオーバー
                        //                        ChangeMode(1);

                        // 効果音再生
                        SoundEx.Play();
                        return;
                    }
                }
            }

            // 自機と敵との当たり判定            
            for (int i = 0; i < Enemy.Length; i++)
            {
                if (Enemy[i].Visibility)
                {
                    bodyL = Enemy[i].pos.X;
                    bodyR = Enemy[i].pos.X + Enemy[i].Width;
                    bodyT = Enemy[i].pos.Y;
                    bodyB = Enemy[i].pos.Y + Enemy[i].Height;

                    // 当たり判定チェック
                    if (!(shootR < bodyL ||
                          bodyR < shootL ||
                          shootB < bodyT ||
                          bodyB < shootT))
                    {
                        // ヒットしたのでゲームオーバー
                        //                        ChangeMode(1);
                        // 効果音再生
                        SoundEx.Play();
                        return;
                    }
                }
            }
        }

        // <summary>
        // Allows the game to run logic such as updating the world,
        // checking for collisions, gathering input, and playing audio.
        // </summary>
        // <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                Exit();

            base.Update(gameTime);
        }

        // <summary>
        // This is called when the game should draw itself.
        // </summary>
        // <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            keyState = Keyboard.GetState(); //キーボード入力状態

            GraphicsDevice.Clear(Color.Aqua);

            // TODO: Add your drawing code here

            // キーボード入力
            InputKey();
            // 座標更新
            playerUpdate();
            enemyUpdate();
            playershootUpdate();
            enemyshootUpdate();
            ExUpdate();

            // 当たり判定
            CollisionAll();

            // 描画
            //スプライトの描画開始
            spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
            playerDraw();
            enemyDraw();
            playershootDraw();
            enemyshootDraw();
            ExDraw();
            spriteBatch.End();

            // FPS 計測
            DrawCnt++;
            DateTime n = DateTime.Now;
            TimeSpan t = n - now;
            if (t.TotalSeconds >= 1)
            {
                FPS = DrawCnt;
                now = n;
                DrawCnt = 0;
            }
            spriteBatch.Begin();
            spriteBatch.DrawString(Font, "FPS:" + FPS.ToString(), Vector2.Zero, Color.Red);
            spriteBatch.End();

            base.Draw(gameTime);
        }
    }
}

内容は若干省略してますが、前回までに作成したスペースインベーダーと同じです。 このファイルをプロジェクトにインポートして、コンパイルすれば実行できるはずです。

さて、このGame1.csは XNA と Silverlight のそれぞれのプロジェクトでまったく変更なしでコンパイルできます。 といっても変更する必要があるところを条件付コンパイルで分岐させているからですが。 このサンプルでは表示領域の縦横の幅を取得するところでやってます。#if SILVERLIGHTのところです。でもこれだけの変更で XNA と Silverlight の両方で コンパイルできるので正直すごいと思う。


Prev  Top  Next
inserted by FC2 system