20211011のC#に関する記事は6件です。

Unityで簡単なSoundManager

めちゃ久しぶりの投稿です。(最近私用で忙しい、、、) 今後まだ改良予定ですがUnityでSoundManagerを製作しました。 まだ簡単な実装なので今後は3D対応、フェードイン、アウトの対応をする予定です。 SoundList.cs アセットのセット用 namespace GameSound { public class SoundList : MonoBehaviour { [SerializeField] AudioClip[] audioClipList; [SerializeField] string[] tagClipList; Dictionary<string, AudioClip> _audioClipList = null; void Start() { SetDictionary(); } void SetDictionary() { _audioClipList = new Dictionary<string, AudioClip>(); for (int i = 0; i < audioClipList.Length; i++) { if (tagClipList.Length <= i) break; _audioClipList.Add(tagClipList[i], audioClipList[i]); } } public Dictionary<string, AudioClip> GetDictionary() { return _audioClipList; } } } SoundManager.cs using System; using System.Collections; using System.Collections.Generic; using UnityEngine; namespace GameSound { //使用するサウンドの種類 public enum UsingSoundTyp { None, SE, //効果音 BGM, //BGM SE_3D, //3D(今後実装予定) } //サウンドの再生方法 public enum PlaySoundTyp { None, Single, //一回再生する Loop, //一定条件が来るまでループ LoopNum, //指定回数ループ } /// <summary> /// サウンドの通知 /// </summary> public class SoundInfo { public SoundInfo() { } //サウンドのタイプ public UsingSoundTyp m_usingSoundTyp; //念のためつけてるけど現在の使用用途は謎 //サウンドの再生方法 public PlaySoundTyp m_playSoundTyp; //使用するサウンドの文字列 public string m_usingSound; //使用するサウンドがLoopだった際に何回繰り返すか public int m_loopNum = -1; //サウンド終了時に呼ばれる関数の登録 public Func<bool> m_endFunc; //サウンドを一時停止させるかの確認用関数の登録 //trueでストップ public Func<bool> m_pauseFunc; //一時停止させたサウンドをPlayするかの確認用関数の登録 //trueでPlay public Func<bool> m_unpauseFunc; //SoundPlayerを終了させる //trueで終了 public Func<bool> m_stopFunc; } /// <summary> /// サウンドを再生するためのクラス /// </summary> public class SoundPlayer { public SoundPlayer(SoundInfo _soundInfo, AudioSource _audioSource, int _index) { m_soundInfo = _soundInfo; m_audioSource = _audioSource; m_index = _index; Inst(); } //通知データ public SoundInfo m_soundInfo = null; //使用しているAudioSource public AudioSource m_audioSource = null; //使用中のindex public int m_index = -1; int m_loopCount; bool m_playFlg = true; /// <summary> /// 初期化 /// </summary> void Inst() { m_loopCount = 0; m_playFlg = true; m_audioSource.PlayDelayed(0); } /// <summary> /// SoundPlayerの終了 /// </summary> void ReleaseAudioSource() { if (m_soundInfo.m_endFunc != null) m_soundInfo.m_endFunc(); SoundManager.Instance.ReleaseAudioSource(m_index, this); } /// <summary> /// サウンド終了後の処理 /// </summary> void SoundEnd() { switch(m_soundInfo.m_playSoundTyp) { case PlaySoundTyp.Single: //1回のみ ReleaseAudioSource(); break; case PlaySoundTyp.Loop: //ループ m_audioSource.PlayDelayed(0); break; case PlaySoundTyp.LoopNum: //指定回数ループ m_loopCount++; if (m_loopCount >= m_soundInfo.m_loopNum) { ReleaseAudioSource(); } else { m_audioSource.PlayDelayed(0); } break; default: ReleaseAudioSource(); break; } } /// <summary> /// 更新 /// </summary> public void Update() { //一時停止 if(m_soundInfo.m_pauseFunc != null&& m_playFlg) { if(m_soundInfo.m_pauseFunc()) { m_audioSource.Pause(); m_playFlg = false; } } //再開 if (m_soundInfo.m_unpauseFunc != null && !m_playFlg) { if (m_soundInfo.m_unpauseFunc()) { m_audioSource.UnPause(); m_playFlg = true; } } //SoundPlayerの終了 if (m_soundInfo.m_stopFunc != null) { if (m_soundInfo.m_stopFunc()) { m_audioSource.Stop(); m_playFlg = false; ReleaseAudioSource(); } } //サウンド終了時 if (!m_audioSource.isPlaying && m_playFlg) { SoundEnd(); } } } public class SoundManager : Singleton<SoundManager> { [Header("SoundManagerは常にHierarchy下になるようにする")] [SerializeField] SoundList _soundList = null; //オブジェクトプール [SerializeField] AudioSource[] _audioSourceList = null; List<bool> _audioSourceListFlg = null; //サウンドリスト Dictionary<string, AudioClip> _audioClipList = null; //通知リスト public List<SoundInfo> _soundInfosList { get; private set; } = null; //再生しているサウンドのリスト public List<SoundPlayer> _soundPlayerList { get; private set; } = null; //SoundPlayerリストの解放予定のリスト List<SoundPlayer> _releasesoundPlayerList = null; void Start() { Inst(); } void Update() { InfoListCheck(); SoundUpdate(); } /// <summary> /// 初期化 /// </summary> public void Inst() { //AudioClipDictionaryの取得 _soundInfosList = new List<SoundInfo>(); if (_soundList != null) { _audioClipList = _soundList.GetDictionary(); } else { Debug.LogError("Could not get 'SoundList'"); return; } //AudioSourceListの準備 if (_audioSourceList != null) { _audioSourceListFlg = new List<bool>(); for (int i = 0; i < _audioSourceList.Length; i++) { _audioSourceListFlg.Add(false); } } else { Debug.LogError("There is no 'AudioSourceList'"); return; } if(_soundPlayerList == null) _soundPlayerList = new List<SoundPlayer>(); if (_releasesoundPlayerList == null) _releasesoundPlayerList = new List<SoundPlayer>(); } /// <summary> /// 通知チェック /// </summary> void InfoListCheck() { if (_soundInfosList.Count <= 0) return; foreach(var info in _soundInfosList) { if(!_audioSourceListFlg.Contains(false)) { Debug.LogError("No 'AudioSource' available"); continue; } if (!_audioClipList.ContainsKey(info.m_usingSound)) { Debug.LogError($"No '{info.m_usingSound}' AudioClip"); continue; } AudioClip audioClip = _audioClipList[info.m_usingSound]; int index = 0; for (int i = 0; i < _audioSourceListFlg.Count; i++) { if (!_audioSourceListFlg[i]) { index = i; _audioSourceListFlg[i] = true; break; } } _audioSourceList[index].clip = audioClip; SoundPlayer soundPlayer = new SoundPlayer(info, _audioSourceList[index], index); _soundPlayerList.Add(soundPlayer); } _soundInfosList.Clear(); } /// <summary> /// 通知を取得 /// </summary> /// <param name="_soundInfo"></param> public void SetInfo(SoundInfo _soundInfo) { _soundInfosList.Add(_soundInfo); } /// <summary> /// 使用中のAudioSourceの解放 /// </summary> /// <param name="_index"></param> public void ReleaseAudioSource(int _index, SoundPlayer _soundPlayer) { _audioSourceList[_index].clip = null; _audioSourceListFlg[_index] = false; _releasesoundPlayerList.Add(_soundPlayer); } /// <summary> /// 解放予定リストの確認 /// </summary> void ReleaseCheck() { //解放予定があれば解放 if (_releasesoundPlayerList.Count > 0) { foreach (var _soundPlayer in _releasesoundPlayerList) { _soundPlayerList.Remove(_soundPlayer); } _releasesoundPlayerList.Clear(); } } /// <summary> /// サウンドの更新 /// </summary> void SoundUpdate() { if (_soundPlayerList == null) return; foreach(var _soundPlayer in _soundPlayerList) { _soundPlayer.Update(); } ReleaseCheck(); } } } 使用例 void Update() { if(Input.GetKeyDown(KeyCode.P)) { SoundInfo soundInfo = new SoundInfo() { m_usingSoundTyp = UsingSoundTyp.SE, m_playSoundTyp = PlaySoundTyp.LoopNum, m_loopNum = 3, m_usingSound="SE", m_endFunc = TestEnd, }; SoundManager.Instance.SetInfo(soundInfo); } if (Input.GetKeyDown(KeyCode.O)) { SoundInfo soundInfo = new SoundInfo() { m_usingSoundTyp = UsingSoundTyp.BGM, m_playSoundTyp = PlaySoundTyp.Single, m_usingSound = "BGM", m_endFunc = TestEnd, }; SoundManager.Instance.SetInfo(soundInfo); } if(Input.GetKeyDown(KeyCode.J)) { SoundInfo soundInfo = new SoundInfo() { m_usingSoundTyp = UsingSoundTyp.BGM, m_playSoundTyp = PlaySoundTyp.Loop, m_usingSound = "BGM", m_endFunc = TestEnd, m_pauseFunc = Pause, m_unpauseFunc = UnPause, m_stopFunc = Stop, }; SoundManager.Instance.SetInfo(soundInfo); } } bool TestEnd() { Debug.Log("End"); return true; } bool Pause() { if (Input.GetKeyDown(KeyCode.I)) { return true; } return false; } bool UnPause() { if (Input.GetKeyDown(KeyCode.U)) { return true; } return false; } bool Stop() { if (Input.GetKeyDown(KeyCode.Y)) { return true; } return false; }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C#9.0 レコードのはじめの一歩 【初心者向け】

レコードのはじめの一歩 この記事では、C#9.0で追加されたレコードを、主に初心者向けに解説します。 note:この記事では、C#9.0で追加されたrecordによるデータ構造を取り扱います。 データベースの話題はこの記事では扱いませんのでご注意ください。 レコードの定義 「どのような値を順番に持つかを決めて、そのデータ構造に名前を付ける」のがレコードの機能です。 レコードを定義する record MyData(string Name, int Number); 上のようにMyDataという名前のレコードを定義することができます。 classを定義するのと同じ階層に定義しましょう。 格納する値を指定している (string Name, int Number) の部分を「位置指定パラメータ」と呼びます。 note: はじめは、「位置指定パラメータ」に、int , double , stringなどの基本的な型の組み合わせを使いましょう。 配列や独自のクラスなどを指定したい場合は、後半の注意点も確認してください。 インスタンスの生成 定義通りの順番に実際の値を格納したインスタンス(実体)を生成できます。 インスタンスを生成する MyData data1 = new MyData("なまえ", 123); レコードのインスタンスは、以下のような性質を持っています。 値を読み取ることができる 値を書き換えることができない 一部を書き換えたコピーを作ることができる それぞれ読み取る string a = data1.Name; int b = data1.Number; いっぺんに読み取る (string x, int y) = data1; Numberだけ書き換えたコピーを作る MyData data2 = data1 with { Number = 234 }; with式は、C#9.0時点ではレコードの特徴的な機能の一つです。 with式の機能は今後拡張される予定です。 比較、文字列化 レコードでは、格納した値をいっぺんに比較できます。 2つのインスタンスを比較する if(data1 == data2) { /* NameとNumberがすべて一致したら何かの処理を行う */ } また、レコードではプロパティの中身まで文字列化してくれます。 中身まで表示してくれる。 Console.WriteLine(data1); // 出力=> "MyData { Name = なまえ, Number = 123 }" これらの機能は、クラスでも自分で実装することで可能です。 レコードはこの面倒な実装を勝手にやってくれます。 基本はここまで レコードの基本の使い方はここまでです。 ここからは追加の説明になります。 レコードのメンバー追加 クラスと同じように、レコードにもメソッドなどのメンバーを追加することもできます。 レコードの中身を追加? record MyAdvanceData(string Name, int Number) { /* こうやってメソッドなどを定義することもできる */ } ただ、ある程度のメンバーを定義しないと役に立たないクラスと違って、レコードでは「位置指定パラメータ」を指定するだけで十分に役に立ちます。 不要な場合は 何も書かない方が良い でしょう。 ここにメンバーを追加する例は、また別の記事で書くつもりです。 注意点 「値を書き換えることができない」と書きましたが、「位置指定パラメータ」で指定した 型の種類によっては注意が必要 です。 例えば、配列のような書き換え可能な型は、以下のような書き換えができてしまいます。 こういう定義には要注意 record MyArrayData(int[] Values); 不変ではない型を格納すると…… int[] array = new int[] { 2, 3, 5, 8 }; MyArrayData data = new MyArrayData(array); // 配列の中身の書き換えはdataの中身も書き換えてしまう。 array[1] = 10; // 格納した後の配列内の値を書き換えることができてしまう。 data.Values[2] = 13; また、比較に関しては、配列の中身まで比較されることはありません。 こういうレコードの定義の良し悪しは、簡単に断定できるものではありませんが、「レコード型に格納したからといって、 不変な性質が追加されるわけではない 」という事は頭に入れておいてください。 不変な性質を保ちつつ配列を格納するレコードの例は、別の記事で書くつもりです。 不変な配列やリストについて、参考までに↓ 使いこなせていなかった System.Collections.Immutable" おわりに クラスとレコードで、できる事はそんなに大きく違うわけではありませんが、「変更しない値を保持することが主な目的」で「複雑な機能を必要としない」ならば、レコードは選択肢に入ります。 まずは、レコードらしい基本的な使い方が出来そうな場面から使ってみてください。 複雑なレコードの使い方もできますし、そういうサンプルを私も公開する予定ですが、 シンプルな使い方こそがレコードの最大の魅力 だと思っています。 Qiitaに限らず、レコードの紹介記事ではレコードを使う意味がほとんど無いサンプルを時折見かけます。 多くの場合、メンバーを書き換えすぎていることが原因のように思います。 「メンバーの追加定義や書き換えはしなくてもいいんだよ」ということが、この記事で伝われば幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[C#] ブール値を反転させて再代入するときの超短い書き方

trueとfalseを反転させて再代入するとき、!演算子を使ってやればいいんですが、長ったらしい変数だとちょっとヤです。 var longNameBecauseGoodNameOmoitsukankatta = true; longNameBecauseGoodNameOmoitsukankatta = !longNameBecauseGoodNameOmoitsukankatta; Console.WriteLine(longNameBecauseGoodNameOmoitsukankatta); // false longNameBecauseGoodNameOmoitsukankatta = !longNameBecauseGoodNameOmoitsukankatta; Console.WriteLine(longNameBecauseGoodNameOmoitsukankatta); // true そんなときはxor演算子の^を使いましょう。A XOR trueはNOT Aと等価のため、代入演算子^=を使うとこんなふうに書けます。 var longNameBecauseGoodNameOmoitsukankatta = true; longNameBecauseGoodNameOmoitsukankatta ^= true; Console.WriteLine(longNameBecauseGoodNameOmoitsukankatta); // false longNameBecauseGoodNameOmoitsukankatta ^= true; Console.WriteLine(longNameBecauseGoodNameOmoitsukankatta); // true C# 7.2 から拡張メソッドの第一引数にrefをつけられるようになったので、以下の拡張メソッド定義をすると反転値の代入ということがわかりやくなるかもしれません。.Reverse()は10文字、^= trueは8文字なのでちょっと不利ですが・・・。 public static class Ext { /// <summary>ブール値を反転して自分自身に設定し、その結果を返します。</summary> /// <return>反転後のブール値</return> public static bool Reverse(ref this bool b) => b ^= true; } bool a = true; System.Diagnostics.Debug.WriteLine(a); System.Diagnostics.Debug.WriteLine(a.Reverse()); System.Diagnostics.Debug.WriteLine(a); System.Diagnostics.Debug.WriteLine(a.Reverse()); System.Diagnostics.Debug.WriteLine(a); /* 実行結果 True False False True True */
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AxoCoverのテストコード

初めてAxoCoverを学びましたので、備忘録として残します。 AxoCoverとは Visual Studioの拡張ツールです。 無料で使えるカバレッジツールでです。 使い方はVisual Studioのメニューバーのツールから[AxoCover]を選択します。 左上のRunを選択してテストします。結果表示はCoverを選択します。度のソースコードがテストされたのか確認できます。 Reportを表示するとどのコードがどのくらいの合格率かパーセンテージで確認できます。 テスト結果をHTML表示することもできます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Chaining Assertionでのテストコード

初めてChaining Assertionを学びましたので、備忘録として残します。 Chaining Assertionとは何か C#の単体テストのためのライブラリです。 テストコードを書きやすくする[Chaining Assertion]とサーバーに接続しなくてもテストできる[Moq]があります。 インストール インストールは[NuGetパッケージの管理]から行います。 メニューのプロジェクトから開きます。 それぞれインストールします。 ViewModelBase.csの追加 クラスを追加して、以下のように記述します。 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Runtime.CompilerServices; using System.ComponentModel; namespace DDD.WinForm.ViewModels { public abstract class ViewModelBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected bool SetProperty<T>( ref T field, T value, [CallerMemberName]string propertyName = null) { if(Equals(field, value)) { return false; } field = value; var h = this.PropertyChanged; if (h != null) { h(this, new PropertyChangedEventArgs(propertyName)); } return true; } } } このコードを配置することによりテストするファイル(View)とテストコード(ViewModel)がデータバインドしてスト出来るようになります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unityのテストコードについて

初めてUnityでのテストコードを学びましたので、備忘録として残します。 Unityプロジェクトの作成 テスト開発するためにUnityプロジェクトを新規作成します。 Unity Test Ruuner の活用 Unityの標準で搭載されているテストツールです。 Unityのメニュー画面からWindow>General>Test Runnerで開くことが出来ます。 テスト方法には2つの方法があります。 Playモード:実際にバックグラウンドでアプリを起動しながらテストします。 Editモード:別途Editモード用のファイルを追加してテストできます。 テスト時のアプリ起動はしません。 テストするファイルを作成 テストしたいファイルを用意しておきます。 using System.Collections; using System.Collections.Generic; using UnityEngine; public class SoundManager : MonoBehaviour { public static SoundManager instance; private void Awake() { if(instance == null) { instance = this; DontDestroyOnLoad(this.gameObject); } else { Destroy(this.gameObject); } } //--シングルトン終わり-- public AudioSource audioSourceBGM; //BGMのスピーカー public AudioClip[] audioClipsBGM; //BGMの素材 (0:Title, 1:Town, 2:Quest, 3:Battle) public AudioSource audioSourceSE; //SEのスピーカー public AudioClip[] audioClipsSE; //ならす素材 private string v; public SoundManager(string v) { this.v = v; } public void StopBGM() { audioSourceBGM.Stop(); } public void StopSE() { audioSourceSE.Stop(); } // Start is called before the first frame update void Start() { PlayBGM("Title"); } public void PlayBGM(string sceneName) { audioSourceBGM.Stop(); switch (sceneName) { default: case "Title": audioSourceBGM.clip = audioClipsBGM[0]; break; case "Town": audioSourceBGM.clip = audioClipsBGM[1]; break; case "Quest": audioSourceBGM.clip = audioClipsBGM[2]; break; case "Battle": audioSourceBGM.clip = audioClipsBGM[3]; break; } audioSourceBGM.Play(); } public void PlaySE(int index) { audioSourceSE.PlayOneShot(audioClipsSE[index]); //SEを一度鳴らす this.count = 0; } public int count { get; private set; } public int GetName() { return this.count; } } バックグランドBGMの設定やボタンクリック時に音が鳴る設定をしています。 ボタンが鳴るときにPlaySEが起動するのですが、その時にカウントに0を代入するようにしています。 テストコードの作成 テストするためのコードを記述します。 TestRunnerの画面からTestsフォルダを作成してテストファイルを作成します。 using System.Collections; using System.Collections.Generic; using NUnit.Framework; using UnityEngine; using UnityEngine.TestTools; namespace Tests { public class NewTestScript { // A UnityTest behaves like a coroutine in Play Mode. In Edit Mode you can use // `yield return null;` to skip a frame. [UnityTest] public IEnumerator SoundManagerTest() { var x = new SoundManager("count"); //start() Assert.AreEqual(0,x.GetName()); yield return null; } } } countの値と予測値を比べて正しければテストが通るようにしています。 テスト テストコードを記述したら、テストします。 今回はPlayモードで実行しています。 左上のRun Allでテストを実行します。 結果が正しければすべて緑のチェックがつきます。 まとめ Unityでテスト開発するための手順を書きました。 PlayモードとEditモードの選択ができるので、必要に応じて使い分けてください。 テストするファイルの用意とテストコードを用意する必要があります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む