「XNAメモ」タグのついた投稿

前回に引き続き、iTween使えよ!って内容です。

 

今回は前回製作したTweenControlを拡張して処理が終わったら何かを行う方法を紹介します。

 

まずは、処理後に呼ぶメソッドを保存する為の変数を容易します。

 

public delegate void TweenEndFunc();
TweenEndFunc m_tweenEndFunction;

 

次に開始時の処理に終了時のメソッドを渡せるようにします。

 

        public void Start(T start, T end, float duration, TweenEndFunc onTweenEndFunc = null)
        {
            m_start = start;
            m_target = end;
            m_duration = duration;
            m_timer = 0.0f;
            CurrentValue = m_start;
            m_tweenEndFunction = onTweenEndFunc;

            if (duration == 0.0f)
            {
                IsActive = false;
            }
            else
            {
                IsActive = true;
            }
        }

 

用意が出来ましたので、更新処理の中に処理が終わった時(IsActiveがfalseになる時)にメソッドを呼ぶようにします。

 

        public void Update(float delta)
        {
            if (!IsActive)
            {
                return;
            }

            m_timer += delta;
            if (m_timer >= m_duration)
            {
                m_timer = m_duration;
                IsActive = false;

                // 処理が終わった時のメソッドが登録されていれば、呼ぶ
                if (m_tweenEndFunction != null)
                {
                    m_tweenEndFunction();
                }
            }

            float percentage = Math.Min(m_timer / m_duration, 1.0f);
            CurrentValue = m_lerpFunction(m_start, m_target, percentage);
        }

 

後は開始処理を呼ぶ時に実際にメソッドを渡す様にするだけです。

 

        public void StopBgm()
        {
            m_volumeTweener.Start(MediaPlayer.Volume, 0.0f, m_fadeDuration, _StopBgm);
        }

        protected void _StopBgm()
        {
            MediaPlayer.Stop();
        }

 

上記の例ではBgmのフェードが終わったら再生を止めています。

これを使えばフェードイン、アウトを繰り返して行う事も出来ます。

 

以上です。今回はソースコードへのリンクを置いておきます。

そーす

Vector2.Lerp()等を使用する際、開始、目標、現在の割合を変数を書く必要があり、使用頻度が増えると面倒なのである程度流用出来る方法を紹介します。

Unity使ってる人からすればiTween使えよ!って感じではありますが、まあそこはスルーで。

 

使用例の一例としてはこの動画のナイフを拾った所の演出です。

https://youtu.be/nYo9Cz80FwI

 

まずは、基本的なクラスと変数の宣言です。

現在値、開始値、目標値、掛かる時間、現在の時間、動いているかを保持しています。

   public class TweenControl<T>
   {

       public T CurrentValue { get; private set; }
       T m_start;
       T m_target;
       float m_duration;
       float m_timer;
       public bool IsActive { get; private set; }

 

Genericにしている部分に、floatやVector2等の使用するデータ型が入ります。

 

次に、Lerp()等を呼ぶ処理の準備をします。今回使用する方法は、インスタンス生成時にLerpの計算に使用するメソッドをコンストラクタで渡します。そのメソッドへの参照を使用し、毎フレーム更新していきます。

ですので、まずは渡すメソッドの定義と参照の保存を上記に追加します。

 

       public delegate T LerpFunc<T>(T start, T end, float amount);
       LerpFunc<T> m_lerpFunction;

       public TweenControl(LerpFunc<T> lerpFunction)
       {
           m_tweenEndFunction = null;
           m_lerpFunction = lerpFunction;
           CurrentValue = default(T);
           m_start = default(T);
           m_target = default(T);
           m_duration = 0.0f;
           m_timer = 0.0f;
           IsActive = false;
       }

 

ついでにコンストラクタも書いておきます。

必要な変数の定義が出来ましたので、実際に計算を行う為の処理に入ります。

まずは、開始させる為の処理です。

 

       public void Start(T start, T end, float duration)
       {
           m_start = start;
           m_target = end;
           m_duration = duration;
           m_timer = 0.0f;
           CurrentValue = m_start;

           if (duration == 0.0f)
           {
               IsActive = false;
           }
           else
           {
               IsActive = true;
           }
       }

 

やっている事は開始、目標、掛かる時間を渡し、各値を保存しているだけです。

一応0.0秒の場合はそこで終わらせるようにしています。

 

次は毎フレーム計算を実装します。

やる事は.Lerp()系メソッドを使う時に行う処理をやるだけです。

 

public void Update( float delta )
{
           if (!IsActive)
           {
               return;
           }

           m_timer += delta;
           if (m_timer >= m_duration)
           {
               m_timer = m_duration;
               IsActive = false;
           }

           float percentage = Math.Min(m_timer / m_duration, 1.0f);
           CurrentValue = m_lerpFunction(m_start, m_target, percentage);
}

 

今回は一時停止出来る様に動いていない時はそのままの値で止るようにしています。

 

後は実際に使ってみるだけです。

 

TweenControl<float> m_alphaTweener;
TweenControl<Vector2> m_moveTweener;
 
...

m_alphaTweener = new TweenControl<float>( MathHelper.Lerp );
m_moveTweener = new TweenControl<Vector2>( Vector2.Lerp);

...

public void Start()
{
              m_alphaTweener.Start( 0.0f, 1.0f, TWEEN_DURATION );
              m_moveTweener.Start(startPos, endpos, TWEEN_DURATION );
}

...

public override void Update (float delta)
{
             m_alphaTweener.Update( delta );
             m_moveTweener.Update( delta );
// ...その他の処理
}

...

protected override void DrawSelf (SpriteBatch sp)
{
              Vector2 pos = m_moveTweener.CurrentValue();
              sp.Draw( m_texture, pos, null, Colour * m_alphaTweener.CurrentValue, 0.0f, Origin, Scale, SpriteEffects.None, Layer );
}

 

以上ですー。多分次回はこれを拡張してTween処理が終わったら何かする処理をご紹介します。多分、きっと、もしかして・・・

今回は、FPS操作に関係する処理を紹介します。

まず、FPSを指定した数字に変更する処理です。

行う事は簡単で、Game1クラス内で、TargetElapsedTime プロパティを変更するだけです。

TargetElapsedTime は1フレームでの目標の経過時間を保存しています。

TargetElapsedTimeはTimeSpan型なので、TimeSpanを使用して以下のように設定します。今回は30FPSに設定しています。

Double fps = 30.0;
TargetElapsedTime = TimeSpan.FromSeconds(1.0 / fps);

これで30FPSになるように設定されます。ちなみに、上記で [ 1 / 30 ] と設定されているのは、[ 1秒 / 一秒内の目標フレーム数 ] で、1フレームの目標経過時間を得られるからです。

他のFPS操作として、FPSを固定したくない場合(Update()が呼ばれる間隔を一定にしたくない時)、固定しない様に変更出来ます。

IsFixedTimeStep = false;

これはテスト等に使用できますが、ゲームを作って配布する際にはFPS固定した方が良いので、あくまでテスト方法として覚えておくのが個人的にオススメです。

 

記念すべき(?)50回目のXNAメモです!!
内容としては、前回の記事の続きです。今回は、セーブデータをロードする方法を紹介します。

今回の記事は、前回の記事で作成した物を再利用するので、そちらを見ていない方は、先に読む事をオススメします。

ロードの手順ですが、基本的にはセーブと同じで、ロード用クラスを作成し、それを前回作成したSaveDataHandlerクラスを継承させます。そして、Process()メソッドをオーバーライドし、ロード処理を行います。

今回行う処理は、まずロードするファイルが存在するかを確認し、存在した場合XmlSerializerを使用してファイルをSaveGameDataに変換するだけです(このクラスはセーブデータの中身を保存している為、セーブ時に使用した任意の物に置き換えてください)。

では実装処理です。

class DataLoader : SaveDataHandler
{
        public DataLoader()
            : base()
        {
        }

        protected override bool Process(SaveGameData data)
        {
            // ファイルが存在しない場合、処理を終了する
            if (!m_storageContainer.FileExists(m_filename))
            {
                return false;
            }

            // ロード処理
            using (Stream stream = m_storageContainer.OpenFile(m_filename, FileMode.Open))
            {
                XmlSerializer serializer = new XmlSerializer(typeof(SaveGameData));

                // 読み込んだファイルをSaveGameDataに変換
                data = (serializer.Deserialize(stream) as SaveGameData);
            }

            return true;
        }
}

短いですが、実装は以上です。

クラスの使用方法自体は前回と同じなので、前回の記事をご参照ください。

久しぶりの更新です!

今回はXBOXでデバイスを選択し、セーブ、ロードをする手順を紹介して欲しいとのリクエストがあったので、それに関して紹介します。

ただ、現在自分がXBOXでテスト出来る環境が無い為、まずはWindowsでは動作確認出来た物を紹介します(と言っても両方で使用出来るはずなので、ただ単にテストしてないだけです)。

まずはテスト用に保存するクラスを作成します。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Xml.Serialization;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
using Microsoft.Xna.Framework.GamerServices;

namespace Save
{
[XmlInclude(typeof(Unit)), XmlInclude(typeof(Player))]
public class GameObject
{
public Vector3 Position { get; set; }
}

public class Unit : GameObject
{
public float Life { get; set; }
}

public class Player : GameObject
{
public string Name { get; set; }
}

今回はセーブデータをxml形式で保存します。その際、作ったクラスを保存する為にGameObjectクラスの宣言部分にXmlIncludeを追加しています。

次に実際にセーブするデータを保存するクラスを宣言します。内容はテスト用に適当に詰めたので、必要に応じて変更してください。

public class SaveGameData
{
public string PlayerName;
public Vector2 AvatarPosition;
public int Level;
public int Score;
public List<GameObject> GameObjects;
}

下準備が出来たので、実際にセーブ関連の実装を始めます。

今回はセーブだけを紹介しますが、ロードを実装する時と途中まで処理が一緒なので(実際にデータを扱う所までは同じです)、元となるクラスを実装した後に、それを継承したセーブ用クラスを作成します。

XNA(XBOX)でセーブデータを扱う際、以下の手順が必要です:
– 保存先、もしくは読込先、のデバイスの選択(と獲得)
– 保存先、もしくは読込先、のコンテナの選択(と獲得)
– 選択先のデバイス+コンテナへのファイル操作

Windowsで実行する場合デバイスは勝手に選択され、セーブデータはマイドキュメント上に作成されます(自分の場合は「SavedGames」と言う名前のフォルダが作成され、保存されていました)。

なのでまず、これらのステップを表すenumを宣言します。ついでに、変数も記載しておきます。

class SaveDataHandler
{
protected enum States
{
None,                        // 何も無い状態
ReadyToSelectStorageDevice,  // デバイス選択を出来る状態
SelectingStorageDevice,      // デバイス選択中
ReadyToOpenStorageContainer, // コンテナをあける準備が出来た状態
OpeningStorageContainer,     // コンテナを開いている最中
HandlingFile                 // ファイル操作中
}

protected StorageDevice m_storageDevice;
protected States m_state;
protected IAsyncResult m_asyncResult;
protected StorageContainer m_storageContainer;
protected PlayerIndex m_controller;
protected string m_filename;    // 保存されるファイル名
protected string m_diplayName;  // 使用されるコンテナ(ディレクトリ)名
protected SaveGameData m_data;

public SaveDataHandler()
{
m_data = null;
m_state = States.None;
m_storageContainer = null;
m_storageDevice = null;
m_filename = "";
m_diplayName = "";
}

public SaveGameData Data
{
get { return m_data; }
set { m_data = value; }
}

次に更新処理です。更新処理内では、先ほど宣言したenumを使って現在の状態を記録し、ソレに応じて違う処理を行います。

まずは、デバイス選択を始める前の処理です。

public void Update(float delta)
{
switch (m_state)
{
case States.ReadyToSelectStorageDevice:
#if XBOX
if (!Guide.IsVisible)
#endif
{
// デバイス選択を表示(Windowだと表示されない)
m_asyncResult = StorageDevice.BeginShowSelector(m_controller, null, null);
m_state = States.SelectingStorageDevice;
}
break;

GuidはXBOX用の処理なので、Windows版では出ないように#if処理を付けています。

次にデバイスを選択する処理です。デバイス選択が終わったら、次に進む処理を実装しています。

case States.SelectingStorageDevice:
if (m_asyncResult.IsCompleted)
{
// デバイス選択を閉じる(Windowだと表示されない)
m_storageDevice = StorageDevice.EndShowSelector(m_asyncResult);
m_state = States.ReadyToOpenStorageContainer;
}
break;

次は、コンテナを開く準備です。

case States.ReadyToOpenStorageContainer:

if (m_storageDevice == null || !m_storageDevice.IsConnected)
{
// デバイスを獲得出来ていない場合、もしくは接続されていない場合は、選択に戻る
m_state = States.ReadyToSelectStorageDevice;
}
else
{
// 指定されたゲームのコンテナを開く
m_asyncResult = m_storageDevice.BeginOpenContainer(m_diplayName, null, null);
m_state = States.OpeningStorageContainer;
}
break;

次はデバイスと同じく、終了したら次に進む処理を追加します。

case States.OpeningStorageContainer:

if (m_asyncResult.IsCompleted)
{
// コンテナを開いたので、処理を終了させる
m_storageContainer = m_storageDevice.EndOpenContainer(m_asyncResult);
m_state = States.HandlingFile;
}
break;

そしてファイル操作時の処理を実装します。

case States.HandlingFile:

if (m_storageContainer == null)
{
// コンテナを獲得できていない場合、戻る
m_state = States.ReadyToOpenStorageContainer;
}
else
{
try
{
// 継承されたクラスで行われる、ファイル処理
Process(m_data);
}
catch (IOException e)
{
// 問題があった場合
OnError(e.Message);
}
finally
{
// コンテナを破棄し、他も初期化する
m_storageContainer.Dispose();
m_storageContainer = null;
m_state = States.None;
m_filename = "";
m_diplayName = "";
}
}
break;
}
}

protected virtual bool Process(SaveGameData data)
{
return false;
}

protected virtual void OnError(string exceptionMessage)
{
Debug.WriteLine(exceptionMessage);
}

ここで行われるProcess()とOnError()メソッドは、継承先のクラスで実際にセーブ処理や、ロード処理、そしてエラー処理を行います。

今回は面倒だから省きましたが、実装方法がしっくり来ない方は、abstractメソッドに変更する等すると、ましになるかもしれません。

更新処理は実装したので、次は処理を開始する(セーブ時に呼び出す)メソッドを実装します。やっている事は初期化だけです。

public void Start(string fileName, string displayName, SaveGameData data, PlayerIndex controller)
{
// 処理を開始 + 初期化
if (m_state == States.None)
{
m_data = data;
m_filename = fileName;
m_diplayName = displayName;
m_state = States.ReadyToOpenStorageContainer;
m_controller = controller;
}
}

このクラス内でなくても良いのですが、セーブ時用に既存のファイルを削除する処理を最後に追加します。

protected void DeleteExistingData(string fileName)
{
// 指定されたファイルが存在した場合、消去する
if (m_storageContainer.FileExists(fileName))
{
m_storageContainer.DeleteFile(fileName);
}
}

これでこのクラスの実装は終わりです。

では次にセーブ用クラスを実装します。行う事は、Process()メソッドをオーバーライドし、セーブ処理を行うだけです。データ保存には、XmlSerializerを使用します。

class DataSaver : SaveDataHandler
{
public DataSaver()
: base()
{
}

protected override bool Process(SaveGameData data)
{
// 存在するデータを消去
DeleteExistingData(m_filename);

// 保存処理
using (Stream stream = m_storageContainer.CreateFile(m_filename))
{
XmlSerializer serializer = new XmlSerializer(typeof(SaveGameData));
serializer.Serialize(stream, data);
}

return true;
}
}

これでセーブ用クラスの実装は全部終わりました。後は使用するだけです。

SaveGameData saveGameData = new SaveGameData()
{
PlayerName = "Ookumaneko",
AvatarPosition = new Vector2(2, -50),
Level = 10,
Score = 9999999990,
GameObjects = new List<GameObject>(),
};

dataSaver.Start("SaveData1.sav", "TetrangleSaveData", saveGameData, PlayerIndex.One);

以上です。これでマイドキュメント上にファイルが追加されるはずです。

今回は、PC、XBOX両方で出来るだけ同じコードを使用出来る用に、XNAと.Netの機能を主に使用しています。なので、PCに限定してゲームを作る際、データを暗号化していないので、紹介した処理をそのまま使用するのはあまりオススメしていません。

使用したい場合は、何かファイルをバイナリー化したり、暗号化するなど変化を加えてから使用してください。XBOX編はテスト出来たら紹介します(内容はほぼ変わらないと思いますが)。

今回は、内容が長くなった為、説明がざっくりしすぎている所が結構あるので、何時か補足を追加するかもしれません。