・ゲーム
とりあえずニコ動にぽえビルド紹介動画を一本うp。
3.1ではDark PactかVaal Pactまたはその両方がnerfされる気がしてならない。
さて本題、今回は画像およびサウンドの管理を頑張ってみたよ。
今回やったのは以下のこと↓
・リソース(画像/サウンドデータ)をアセット内およびアセット外からロード
・Luaからアセット外のデータをロード(UnityおよびC#からロジックを完全分離)
・サウンドマネージャーを実装
・イントロ/ループ分割形式のBGMを特に意識せず再生可能
・フェードを実装(フェードアウト/クロスフェード再生に対応)
・音量を調整可能
・サウンドデータの管理を意識する必要なし
⇒任意でサウンドデータを解放できる機構は欲しいかも?
ソースコードはこんな感じ↓
AbyssLibU_Misc.cs:Luaから画像に干渉するためのアクセッサ
using System.IO; using UnityEngine; namespace AbyssLibU { ////// UnityのSpriteクラスのマネージャです。 /// (主にLuaのために)Spriteクラスに対する簡易化された操作を提供します。 /// public static class SpriteManager { ////// Resourcesフォルダからテクスチャデータを読み込みます。 /// /// テクスチャデータのパスを指定します。 ///UnityのSpriteクラスを返します。 public static Sprite LoadResources(string path) { Sprite result = Resources.Load(path); if(result==null){ throw new FileNotFoundException("Could not find resource \"" + path + "\""); } return result; } /// /// 外部データからテクスチャデータを読み込みます。 /// /// テクスチャデータのパスを指定します。 /// テクスチャのRect領域を指定します。 /// Rectに対するピボット地点の相対位置を指定します。 ///UnityのSpriteクラスを返します。 public static Sprite LoadExternalData(string path, Rect? rect = null, Vector2? pivot = null) { BinaryReader bin = new BinaryReader(new FileStream(path, FileMode.Open)); byte[] result = bin.ReadBytes((int)bin.BaseStream.Length); bin.Close(); Texture2D texture = new Texture2D(1, 1); texture.LoadImage(result); return Sprite.Create(texture, rect ?? new Rect(0, 0, texture.width, texture.height), pivot ?? new Vector2(0.5f, 0.5f)); } ////// テクスチャデータを読み込みます。 /// Resourcesフォルダ⇒外部データの順に読み込みを試みます。 /// /// テクスチャデータのパスを指定します。 /// テクスチャのRect領域を指定します。Resourcesフォルダから読み込む場合は無視されます。 /// Rectに対するピボット地点の相対位置を指定します。Resourcesフォルダから読み込む場合は無視されます。 ///UnityのSpriteクラスを返します。 public static Sprite Load(string path, Rect? rect = null, Vector2? pivot = null) { try { return LoadResources(path); } catch (FileNotFoundException) { return LoadExternalData(path, rect, pivot); } } } ////// UnityのImageクラスのマネージャです。 /// (主にLuaのために)Imageクラスに対する簡易化された操作を提供します。 /// public static class ImageManager { ////// ImageクラスにSpriteクラスを設定します。 /// /// Imageクラスの名前を指定します。 /// 設定するSpriteクラスを指定します。 public static void SetSprite(string name, Sprite sprite) { try { GameObject.Find(name).GetComponent().sprite = sprite; } catch (System.NullReferenceException) { throw new System.NullReferenceException("Could not find component \"" + name + "\""); } } /// /// ImageクラスからRectTransformクラスを取得します。 /// /// Imageクラスの名前を指定します。 ///UnityのRectTransformクラスを返します。 public static RectTransform GetRectTransform(string name) { try { RectTransform result = GameObject.Find(name).GetComponent().GetComponent (); return result; } catch (System.NullReferenceException) { throw new System.NullReferenceException("Could not find component \"" + name + "\""); } } } }
AbyssLibU_AudioManager.cs:サウンドマネージャ
using System; using System.IO; using System.Collections; using System.Collections.Generic; using UnityEngine; ////// UnityにおけるBGM/SEの管理を行うシングルトンマネージャです。 /// BGM/SEの読み込み、再生、ボリューム設定等の操作を提供します。 /// public class AbyssLibU_AudioManager : MonoBehaviour { //シングルトン private static AbyssLibU_AudioManager _instance; public static AbyssLibU_AudioManager Instance { get { if (_instance == null) { _instance = (AbyssLibU_AudioManager)FindObjectOfType(typeof(AbyssLibU_AudioManager)); if (_instance == null) { throw new MissingComponentException(typeof(AbyssLibU_AudioManager) + "is nothing"); } } return _instance; } } //音量保存用のkeyとデフォルト値 private const string BGM_VOLUME_KEY = "BGM_VOLUME"; private const string SE_VOLUME_KEY = "SE_VOLUME"; private const float BGM_VOLUME_DEFULT = 1.0f; private const float SE_VOLUME_DEFULT = 1.0f; //イントロ/ループ再生の遅延 private float DELAY_INTROLOOP = 0.1f; //BGMがフェードイン/アウト中か? private bool _isFadeIn = false; private bool _isFadeOut = false; //BGMのフェードにかかる時間 public const float BGM_FADE_SPEED_LOW = 0.3f; public const float BGM_FADE_SPEED_HIGH = 0.9f; private float _bgmFadeSpeed = BGM_FADE_SPEED_HIGH; //AudioSource private AudioSource[,] _bgmSource; private int _bgmSourceIterator = 0; private List_seSourceList; private const int DEFAULT_SE_SOURCE_NUM = 10; //AudioClip private Dictionary _AudioClips; /// /// 初期化処理です。 /// AudioSourceを作成します。 /// private void Awake() { //シングルトン if (this != Instance) { Destroy(this); return; } DontDestroyOnLoad(this.gameObject); //AudioSourceを作成 for (int i = 0; i < DEFAULT_SE_SOURCE_NUM + 4; i++) { gameObject.AddComponent(); } AudioSource[] audioSources = GetComponents (); _bgmSource = new AudioSource[2,2]; _seSourceList = new List (); for (int i = 0; i < audioSources.Length; i++) { audioSources[i].playOnAwake = false; switch(i){ case(0): _bgmSource[0,0] = audioSources[i]; _bgmSource[0,0].volume = PlayerPrefs.GetFloat(BGM_VOLUME_KEY, BGM_VOLUME_DEFULT); break; case(1): audioSources[i].loop = true; _bgmSource[0,1] = audioSources[i]; _bgmSource[0,1].volume = PlayerPrefs.GetFloat(BGM_VOLUME_KEY, BGM_VOLUME_DEFULT); break; case(2): _bgmSource[1,0] = audioSources[i]; _bgmSource[1,0].volume = PlayerPrefs.GetFloat(BGM_VOLUME_KEY, BGM_VOLUME_DEFULT); break; case(3): audioSources[i].loop = true; _bgmSource[1,1] = audioSources[i]; _bgmSource[1,1].volume = PlayerPrefs.GetFloat(BGM_VOLUME_KEY, BGM_VOLUME_DEFULT); break; default: _seSourceList.Add(audioSources[i]); audioSources[i].volume = PlayerPrefs.GetFloat(SE_VOLUME_KEY, SE_VOLUME_DEFULT); break; } } _AudioClips = new Dictionary (); } /// /// フレーム毎処理です。 /// フェード処理を行います。 /// private void Update() { float volume = PlayerPrefs.GetFloat(BGM_VOLUME_KEY, BGM_VOLUME_DEFULT); //フェードイン if (_isFadeIn) { _bgmSource[_bgmSourceIterator, 0].volume += Time.deltaTime * _bgmFadeSpeed * volume; _bgmSource[_bgmSourceIterator, 1].volume += Time.deltaTime * _bgmFadeSpeed * volume; if (_bgmSource[_bgmSourceIterator, 0].volume >= volume || _bgmSource[_bgmSourceIterator, 1].volume >= volume) { _bgmSource[_bgmSourceIterator, 0].volume = volume; _bgmSource[_bgmSourceIterator, 1].volume = volume; _isFadeIn = false; } } //フェードアウト if (_isFadeOut) { _bgmSource[1 - _bgmSourceIterator, 0].volume -= Time.deltaTime * _bgmFadeSpeed * volume; _bgmSource[1 - _bgmSourceIterator, 1].volume -= Time.deltaTime * _bgmFadeSpeed * volume; if (_bgmSource[1 - _bgmSourceIterator, 0].volume <= 0 || _bgmSource[1 - _bgmSourceIterator, 1].volume <= 0) { _bgmSource[1 - _bgmSourceIterator, 0].Stop(); _bgmSource[1 - _bgmSourceIterator, 1].Stop(); _bgmSource[1 - _bgmSourceIterator, 0].volume = volume; _bgmSource[1 - _bgmSourceIterator, 1].volume = volume; _isFadeOut = false; } } } //================================================================================= //サウンドデータの読み込み //================================================================================= ////// Resourcesフォルダからサウンドデータを読み込みます。 /// /// サウンドデータのパスを指定します。 public void LoadResources(string path) { if(!_AudioClips.ContainsKey(path)) { AudioClip result = Resources.Load(path); if (result == null) { throw new FileNotFoundException("Could not find resource \"" + path + "\""); } result.name = path; _AudioClips[path] = result; } } /// /// 外部データからサウンドデータを読み込みます。 /// /// サウンドデータのパスを指定します。 public void LoadExternalData(string path) { if (!_AudioClips.ContainsKey(path)) { Uri URIPath = new Uri(path); if (URIPath.IsFile) { WWW www = new WWW(URIPath.AbsoluteUri); if (www.error == "404 Not Found") { throw new FileNotFoundException("Could not find file \"" + path + "\""); } while (!www.isDone){} AudioClip result = WWWAudioExtensions.GetAudioClip(www); result.name = path; _AudioClips[path] = result; } } } ////// サウンドデータを読み込みます。 /// Resourcesフォルダ⇒外部データの順に読み込みを試みます。 /// /// public void Load(string path) { try { LoadResources(path); } catch (FileNotFoundException) { LoadExternalData(path); } } //================================================================================= //BGM //================================================================================= ////// BGMのボリュームを設定します。 /// ボリュームの設定は即座に反映されます。 /// /// BGMのボリュームを指定します。 public void SetBGMVolume(float volume) { float oldVolume = PlayerPrefs.GetFloat(BGM_VOLUME_KEY, BGM_VOLUME_DEFULT); PlayerPrefs.SetFloat(BGM_VOLUME_KEY, volume); foreach (AudioSource bgmSource in _bgmSource) { bgmSource.volume *= (oldVolume == 0 ? volume : (volume / oldVolume)); } } ////// BGMを即座に停止します。 /// public void StopBGM() { foreach(AudioSource bgmSource in _bgmSource){ bgmSource.Stop(); bgmSource.volume = PlayerPrefs.GetFloat(BGM_VOLUME_KEY, BGM_VOLUME_DEFULT); } _isFadeIn = false; _isFadeOut = false; _bgmSourceIterator = 0; } ////// BGMをフェード停止します。 /// /// フェード速度を指定します。 public void FadeOutBGM(float fadeSpeed = BGM_FADE_SPEED_LOW) { if (_bgmSource[_bgmSourceIterator, 0].isPlaying || _bgmSource[_bgmSourceIterator, 1].isPlaying) { _isFadeOut = true; _bgmFadeSpeed = fadeSpeed; _bgmSourceIterator = 1 - _bgmSourceIterator; } } ////// BGMを再生します。 /// イントロ/ループ分割に対応しています。 /// 前BGMの停止およびサウンドデータの読み込みを自動で行います。 /// /// ループ部分を指定します。 /// イントロ部分を指定します。 public void PlayBGM(string loop, string intro="") { //前BGMの停止 _bgmSource[_bgmSourceIterator, 0].Stop(); _bgmSource[_bgmSourceIterator, 1].Stop(); //読み込み Load(loop); _bgmSource[_bgmSourceIterator, 1].clip = _AudioClips[loop]; _bgmSource[_bgmSourceIterator, 1].volume = PlayerPrefs.GetFloat(BGM_VOLUME_KEY, BGM_VOLUME_DEFULT); if (intro != "") { Load(intro); _bgmSource[_bgmSourceIterator, 0].clip = _AudioClips[intro]; _bgmSource[_bgmSourceIterator, 0].volume = PlayerPrefs.GetFloat(BGM_VOLUME_KEY, BGM_VOLUME_DEFULT); _bgmSource[_bgmSourceIterator, 0].PlayScheduled(AudioSettings.dspTime + DELAY_INTROLOOP); _bgmSource[_bgmSourceIterator, 1].PlayScheduled(AudioSettings.dspTime + DELAY_INTROLOOP + _bgmSource[_bgmSourceIterator, 0].clip.length); } else { _bgmSource[_bgmSourceIterator, 1].Play(); } } ////// BGMをフェード再生します。 /// イントロ/ループ分割に対応しています。 /// 前BGMのフェード停止およびサウンドデータの読み込みを自動で行います。 /// /// ループ部分を指定します。 /// フェード速度を指定します。 /// イントロ部分を指定します。 public void FadeBGM(string loop, float fadeSpeed, string intro = "") { if (_bgmSource[_bgmSourceIterator, 0].isPlaying || _bgmSource[_bgmSourceIterator, 1].isPlaying) { //BGM再生中の場合はフェード再生 if (_bgmSource[_bgmSourceIterator, 1].clip != null && _bgmSource[_bgmSourceIterator, 1].clip.name == loop) { //ただし再生するBGM=前BGMの場合は何もしない return; } //前BGMをフェード停止 FadeOutBGM(fadeSpeed); //読み込み Load(loop); _bgmSource[_bgmSourceIterator, 1].clip = _AudioClips[loop]; _bgmSource[_bgmSourceIterator, 1].volume = 0.0f; if (intro != "") { Load(intro); _bgmSource[_bgmSourceIterator, 0].clip = _AudioClips[intro]; _bgmSource[_bgmSourceIterator, 0].volume = 0.0f; _bgmSource[_bgmSourceIterator, 0].PlayScheduled(AudioSettings.dspTime + DELAY_INTROLOOP); _bgmSource[_bgmSourceIterator, 1].PlayScheduled(AudioSettings.dspTime + DELAY_INTROLOOP + _bgmSource[_bgmSourceIterator, 0].clip.length); } else { _bgmSource[_bgmSourceIterator, 1].Play(); } _isFadeIn = true; } else { //BGM再生中でない場合は普通に再生 PlayBGM(loop, intro); } } //================================================================================= //SE //================================================================================= ////// SEのボリュームを設定します。 /// ボリュームの設定は即座に反映されます。 /// /// SEのボリュームを指定します。 public void SetSEVolume(float volume) { float oldVolume = PlayerPrefs.GetFloat(SE_VOLUME_KEY, SE_VOLUME_DEFULT); PlayerPrefs.SetFloat(SE_VOLUME_KEY, volume); foreach (AudioSource seSource in _seSourceList) { seSource.volume *= (oldVolume == 0 ? volume : (volume / oldVolume)); } } ////// SEを即座に停止します。 /// public void StopSE() { foreach (AudioSource seSource in _seSourceList) { seSource.Stop(); seSource.volume = PlayerPrefs.GetFloat(SE_VOLUME_KEY, SE_VOLUME_DEFULT); } } ////// SEを再生します。 /// サウンドデータの読み込みを自動で行います。 /// /// サウンドデータのパスを指定します。 /// SEのボリュームを指定します。ボリュームは乗算で設定されます。 public void PlaySE(string path, float volume = 1.0f) { //読み込み Load(path); foreach (AudioSource seSource in _seSourceList) { if (!seSource.isPlaying) { //未使用のAudioSourceで再生する seSource.clip = _AudioClips[path]; seSource.volume = PlayerPrefs.GetFloat(SE_VOLUME_KEY, SE_VOLUME_DEFULT) * volume; seSource.Play(); return; } } //未使用のAudioSourceがなければ新規作成・追加 AudioSource result = gameObject.AddComponent(); result.playOnAwake = false; result.clip = _AudioClips[path]; result.volume = PlayerPrefs.GetFloat(SE_VOLUME_KEY, SE_VOLUME_DEFULT) * volume; result.Play(); _seSourceList.Add(result); } }
ButtonEvents.cs:Unityと呼び出すボタンイベント用(C#からの呼び出し元
using System.IO; using UnityEngine; using XLua; public class ButtonEvents : MonoBehaviour { //絵(アセット内)をクリック public void ButtonS1_onClick() { AbyssLibU.ImageManager.SetSprite("Image", AbyssLibU.SpriteManager.Load("02")); } //絵(アセット外)をクリック public void ButtonS2_onClick() { AbyssLibU.ImageManager.SetSprite("Image", AbyssLibU.SpriteManager.Load(System.Environment.CurrentDirectory + "\\" + "03.jpg")); //AbyssLibU.ImageManager.SetSprite("Image", AbyssLibU.SpriteManager.LoadExternalData(System.Environment.CurrentDirectory + "\\" + "03.jpg", //new Rect(0, 0, 100, 100), new Vector2(0.5f, 0.5f))); //一部読み込み //AbyssLibU.ImageManager.GetRectTransform("Image").sizeDelta = new Vector2(100, 100); //サイズ設定 //AbyssLibU.ImageManager.GetRectTransform("Image").position = new Vector3(400, 400); //位置設定 } //絵(アセット外・Lua)をクリック public void ButtonS3_onClick() { //Lua起動 LuaEnv le = new LuaEnv(); System.IO.StreamReader sr = new System.IO.StreamReader(System.Environment.CurrentDirectory + "\\" + "ButtonEvents1.lua.txt", System.Text.Encoding.GetEncoding("UTF-8")); le.DoString(sr.ReadToEnd()); sr.Close(); } //絵(デフォルト)をクリック public void ButtonS4_onClick() { AbyssLibU.ImageManager.SetSprite("Image", AbyssLibU.SpriteManager.Load("01")); } //曲(アセット内)をクリック public void ButtonB1_onClick() { AbyssLibU_AudioManager.Instance.PlayBGM("01"); } //曲(アセット外)をクリック public void ButtonB2_onClick() { AbyssLibU_AudioManager.Instance.PlayBGM(System.Environment.CurrentDirectory + "\\" + "02.ogg"); } //曲(アセット外・Lua)をクリック public void ButtonB3_onClick() { //Lua起動 LuaEnv le = new LuaEnv(); System.IO.StreamReader sr = new System.IO.StreamReader(System.Environment.CurrentDirectory + "\\" + "ButtonEvents2.lua.txt", System.Text.Encoding.GetEncoding("UTF-8")); le.DoString(sr.ReadToEnd()); sr.Close(); } //曲(イントロ/ループ)をクリック public void ButtonB4_onClick() { AbyssLibU_AudioManager.Instance.PlayBGM("Battle-impalpable_loop", "Battle-impalpable_intro"); } //フェード(アセット内)をクリック public void ButtonB5_onClick() { AbyssLibU_AudioManager.Instance.FadeBGM("01", AbyssLibU_AudioManager.BGM_FADE_SPEED_LOW); } //フェード(アセット外)をクリック public void ButtonB6_onClick() { AbyssLibU_AudioManager.Instance.FadeBGM(System.Environment.CurrentDirectory + "\\" + "02.ogg", AbyssLibU_AudioManager.BGM_FADE_SPEED_LOW); } //フェード(アセット外・Lua)をクリック public void ButtonB7_onClick() { //Lua起動 LuaEnv le = new LuaEnv(); System.IO.StreamReader sr = new System.IO.StreamReader(System.Environment.CurrentDirectory + "\\" + "ButtonEvents3.lua.txt", System.Text.Encoding.GetEncoding("UTF-8")); le.DoString(sr.ReadToEnd()); sr.Close(); } //フェード(イントロ/ループ)をクリック public void ButtonB8_onClick() { AbyssLibU_AudioManager.Instance.FadeBGM("Battle-impalpable_loop", AbyssLibU_AudioManager.BGM_FADE_SPEED_LOW, "Battle-impalpable_intro"); } //音量(100%)をクリック public void ButtonB9_onClick() { AbyssLibU_AudioManager.Instance.SetBGMVolume(1.0f); } //音量(50%)をクリック public void ButtonB10_onClick() { AbyssLibU_AudioManager.Instance.SetBGMVolume(0.5f); } //停止をクリック public void ButtonB11_onClick() { AbyssLibU_AudioManager.Instance.StopBGM(); } //フェード停止をクリック public void ButtonB12_onClick() { AbyssLibU_AudioManager.Instance.FadeOutBGM(); } //音(アセット内)をクリック public void ButtonSE1_onClick() { AbyssLibU_AudioManager.Instance.PlaySE("g_clear"); } //音(アセット外)をクリック public void ButtonSE2_onClick() { AbyssLibU_AudioManager.Instance.PlaySE(System.Environment.CurrentDirectory + "\\" + "OnCut.wav"); } //音(アセット外・Lua)をクリック public void ButtonSE3_onClick() { //Lua起動 LuaEnv le = new LuaEnv(); System.IO.StreamReader sr = new System.IO.StreamReader(System.Environment.CurrentDirectory + "\\" + "ButtonEvents4.lua.txt", System.Text.Encoding.GetEncoding("UTF-8")); le.DoString(sr.ReadToEnd()); sr.Close(); } //音量(100%)をクリック public void ButtonSE4_onClick() { AbyssLibU_AudioManager.Instance.SetSEVolume(1.0f); } //音量(50%)をクリック public void ButtonSE5_onClick() { AbyssLibU_AudioManager.Instance.SetSEVolume(0.5f); } //停止をクリック public void ButtonSE6_onClick() { AbyssLibU_AudioManager.Instance.StopSE(); } }
ButtonEvents1.lua.txt:Luaから画像変更(一部読み込み・サイズ設定・位置設定はコメントアウト中)
local ImageManager = CS.AbyssLibU.ImageManager local SpriteManager = CS.AbyssLibU.SpriteManager ImageManager.SetSprite('Image', SpriteManager.Load('04.jpg')) -- ImageManager.SetSprite('Image', SpriteManager.Load('04.jpg', CS.UnityEngine.Rect(0, 0, 100, 100), CS.UnityEngine.Vector2(0.5, 0.5))) -- 一部読み込み -- ImageManager.GetRectTransform('Image').sizeDelta = CS.UnityEngine.Vector2(100, 100) -- サイズ設定 -- ImageManager.GetRectTransform('Image').position = CS.UnityEngine.Vector3(400, 400) -- 位置設定
ButtonEvents2.lua.txt:LuaからBGM再生
local AudioManager = CS.AbyssLibU_AudioManager.Instance AudioManager:PlayBGM(CS.System.Environment.CurrentDirectory..'\\03.ogg')
ButtonEvents3.lua.txt:LuaからBGMクロスフェード再生
local AudioManager = CS.AbyssLibU_AudioManager.Instance AudioManager:FadeBGM(CS.System.Environment.CurrentDirectory..'\\03.ogg', 0.3)
ButtonEvents4.lua.txt:LuaからSE再生
local AudioManager = CS.AbyssLibU_AudioManager.Instance AudioManager:PlaySE(CS.System.Environment.CurrentDirectory..'\\OnUsePower.wav')
今後の予定はこんな感じ↓
プロジェクト3:動的UIをテスト
プロジェクト4:ジョーカースクリプトを色々テスト
プロジェクト5:過去に制作したゲーム(ゆっくりスイーパー)をUnityでリメイク
プロジェクト6:ゲーム用AIの作成(あんまりUnity関係ない)
多分5と6は長期で並行しつつ進めていく感じかな。
とりあえず、今回はこんなところで。